@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.
Files changed (92) hide show
  1. package/README.md +9 -84
  2. package/dist/index.d.mts +629 -0
  3. package/dist/index.d.mts.map +1 -0
  4. package/dist/index.mjs +1887 -0
  5. package/dist/index.mjs.map +1 -0
  6. package/package.json +42 -78
  7. package/src/__tests__/bundle.test.ts +59 -0
  8. package/src/__tests__/index.test.ts +43 -0
  9. package/src/__tests__/pointer.test.ts +34 -0
  10. package/src/__tests__/utils.ts +3 -0
  11. package/{lib → src}/bundle.ts +143 -229
  12. package/{lib → src}/dereference.ts +20 -43
  13. package/{lib → src}/index.ts +103 -125
  14. package/{lib → src}/options.ts +13 -9
  15. package/{lib → src}/parse.ts +19 -15
  16. package/src/parsers/binary.ts +13 -0
  17. package/{lib → src}/parsers/json.ts +5 -6
  18. package/src/parsers/text.ts +21 -0
  19. package/{lib → src}/parsers/yaml.ts +9 -9
  20. package/{lib → src}/pointer.ts +42 -23
  21. package/{lib → src}/ref.ts +25 -21
  22. package/{lib → src}/refs.ts +23 -26
  23. package/{lib → src}/resolve-external.ts +91 -60
  24. package/{lib → src}/resolvers/file.ts +7 -10
  25. package/{lib → src}/resolvers/url.ts +12 -8
  26. package/{lib → src}/types/index.ts +9 -2
  27. package/src/util/convert-path-to-posix.ts +8 -0
  28. package/{lib → src}/util/errors.ts +38 -36
  29. package/{lib → src}/util/is-windows.ts +1 -1
  30. package/{lib → src}/util/plugins.ts +7 -8
  31. package/{lib → src}/util/url.ts +41 -42
  32. package/dist/lib/__tests__/bundle.test.d.ts +0 -1
  33. package/dist/lib/__tests__/bundle.test.js +0 -50
  34. package/dist/lib/__tests__/index.test.d.ts +0 -1
  35. package/dist/lib/__tests__/index.test.js +0 -43
  36. package/dist/lib/__tests__/pointer.test.d.ts +0 -1
  37. package/dist/lib/__tests__/pointer.test.js +0 -27
  38. package/dist/lib/bundle.d.ts +0 -26
  39. package/dist/lib/bundle.js +0 -600
  40. package/dist/lib/dereference.d.ts +0 -11
  41. package/dist/lib/dereference.js +0 -226
  42. package/dist/lib/index.d.ts +0 -92
  43. package/dist/lib/index.js +0 -525
  44. package/dist/lib/options.d.ts +0 -61
  45. package/dist/lib/options.js +0 -45
  46. package/dist/lib/parse.d.ts +0 -13
  47. package/dist/lib/parse.js +0 -87
  48. package/dist/lib/parsers/binary.d.ts +0 -2
  49. package/dist/lib/parsers/binary.js +0 -12
  50. package/dist/lib/parsers/json.d.ts +0 -2
  51. package/dist/lib/parsers/json.js +0 -38
  52. package/dist/lib/parsers/text.d.ts +0 -2
  53. package/dist/lib/parsers/text.js +0 -18
  54. package/dist/lib/parsers/yaml.d.ts +0 -2
  55. package/dist/lib/parsers/yaml.js +0 -28
  56. package/dist/lib/pointer.d.ts +0 -88
  57. package/dist/lib/pointer.js +0 -297
  58. package/dist/lib/ref.d.ts +0 -180
  59. package/dist/lib/ref.js +0 -226
  60. package/dist/lib/refs.d.ts +0 -127
  61. package/dist/lib/refs.js +0 -232
  62. package/dist/lib/resolve-external.d.ts +0 -13
  63. package/dist/lib/resolve-external.js +0 -151
  64. package/dist/lib/resolvers/file.d.ts +0 -6
  65. package/dist/lib/resolvers/file.js +0 -61
  66. package/dist/lib/resolvers/url.d.ts +0 -17
  67. package/dist/lib/resolvers/url.js +0 -62
  68. package/dist/lib/types/index.d.ts +0 -43
  69. package/dist/lib/types/index.js +0 -2
  70. package/dist/lib/util/convert-path-to-posix.d.ts +0 -1
  71. package/dist/lib/util/convert-path-to-posix.js +0 -14
  72. package/dist/lib/util/errors.d.ts +0 -56
  73. package/dist/lib/util/errors.js +0 -112
  74. package/dist/lib/util/is-windows.d.ts +0 -1
  75. package/dist/lib/util/is-windows.js +0 -6
  76. package/dist/lib/util/plugins.d.ts +0 -16
  77. package/dist/lib/util/plugins.js +0 -45
  78. package/dist/lib/util/url.d.ts +0 -79
  79. package/dist/lib/util/url.js +0 -285
  80. package/dist/vite.config.d.ts +0 -2
  81. package/dist/vite.config.js +0 -19
  82. package/lib/__tests__/bundle.test.ts +0 -52
  83. package/lib/__tests__/index.test.ts +0 -45
  84. package/lib/__tests__/pointer.test.ts +0 -26
  85. package/lib/__tests__/spec/circular-ref-with-description.json +0 -11
  86. package/lib/__tests__/spec/multiple-refs.json +0 -34
  87. package/lib/__tests__/spec/openapi-paths-ref.json +0 -46
  88. package/lib/__tests__/spec/path-parameter.json +0 -16
  89. package/lib/parsers/binary.ts +0 -13
  90. package/lib/parsers/text.ts +0 -21
  91. package/lib/util/convert-path-to-posix.ts +0 -11
  92. /package/{LICENSE → LICENSE.md} +0 -0
package/dist/index.mjs ADDED
@@ -0,0 +1,1887 @@
1
+ import { Ono, ono } from "@jsdevtools/ono";
2
+ import { join, win32 } from "node:path";
3
+ import yaml, { JSON_SCHEMA } from "js-yaml";
4
+ import fs from "fs";
5
+
6
+ //#region src/util/convert-path-to-posix.ts
7
+ function convertPathToPosix(filePath) {
8
+ if (filePath.startsWith("\\\\?\\")) return filePath;
9
+ return filePath.replaceAll("\\", "/");
10
+ }
11
+
12
+ //#endregion
13
+ //#region src/util/is-windows.ts
14
+ const isWindowsConst = /^win/.test(globalThis.process ? globalThis.process.platform : "");
15
+ const isWindows = () => isWindowsConst;
16
+
17
+ //#endregion
18
+ //#region src/util/url.ts
19
+ const forwardSlashPattern = /\//g;
20
+ const protocolPattern = /^(\w{2,}):\/\//i;
21
+ const urlEncodePatterns = [[/\?/g, "%3F"], [/#/g, "%23"]];
22
+ const urlDecodePatterns = [
23
+ /%23/g,
24
+ "#",
25
+ /%24/g,
26
+ "$",
27
+ /%26/g,
28
+ "&",
29
+ /%2C/g,
30
+ ",",
31
+ /%40/g,
32
+ "@"
33
+ ];
34
+ /**
35
+ * Returns resolved target URL relative to a base URL in a manner similar to that of a Web browser resolving an anchor tag HREF.
36
+ *
37
+ * @returns
38
+ */
39
+ function resolve(from, to) {
40
+ const fromUrl = new URL(convertPathToPosix(from), "resolve://");
41
+ const resolvedUrl = new URL(convertPathToPosix(to), fromUrl);
42
+ const endSpaces = to.match(/(\s*)$/)?.[1] || "";
43
+ if (resolvedUrl.protocol === "resolve:") {
44
+ const { hash, pathname, search } = resolvedUrl;
45
+ return pathname + search + hash + endSpaces;
46
+ }
47
+ return resolvedUrl.toString() + endSpaces;
48
+ }
49
+ /**
50
+ * Returns the current working directory (in Node) or the current page URL (in browsers).
51
+ *
52
+ * @returns
53
+ */
54
+ function cwd() {
55
+ if (typeof window !== "undefined") return location.href;
56
+ const path = process.cwd();
57
+ const lastChar = path.slice(-1);
58
+ if (lastChar === "/" || lastChar === "\\") return path;
59
+ else return path + "/";
60
+ }
61
+ /**
62
+ * Returns the protocol of the given URL, or `undefined` if it has no protocol.
63
+ *
64
+ * @param path
65
+ * @returns
66
+ */
67
+ function getProtocol(path) {
68
+ const match = protocolPattern.exec(path || "");
69
+ if (match) return match[1].toLowerCase();
70
+ }
71
+ /**
72
+ * Returns the lowercased file extension of the given URL,
73
+ * or an empty string if it has no extension.
74
+ *
75
+ * @param path
76
+ * @returns
77
+ */
78
+ function getExtension(path) {
79
+ const lastDot = path.lastIndexOf(".");
80
+ if (lastDot > -1) return stripQuery(path.substr(lastDot).toLowerCase());
81
+ return "";
82
+ }
83
+ /**
84
+ * Removes the query, if any, from the given path.
85
+ *
86
+ * @param path
87
+ * @returns
88
+ */
89
+ function stripQuery(path) {
90
+ const queryIndex = path.indexOf("?");
91
+ if (queryIndex > -1) path = path.substr(0, queryIndex);
92
+ return path;
93
+ }
94
+ /**
95
+ * Returns the hash (URL fragment), of the given path.
96
+ * If there is no hash, then the root hash ("#") is returned.
97
+ *
98
+ * @param path
99
+ * @returns
100
+ */
101
+ function getHash(path) {
102
+ if (!path) return "#";
103
+ const hashIndex = path.indexOf("#");
104
+ if (hashIndex > -1) return path.substring(hashIndex);
105
+ return "#";
106
+ }
107
+ /**
108
+ * Removes the hash (URL fragment), if any, from the given path.
109
+ *
110
+ * @param path
111
+ * @returns
112
+ */
113
+ function stripHash(path) {
114
+ if (!path) return "";
115
+ const hashIndex = path.indexOf("#");
116
+ if (hashIndex > -1) path = path.substring(0, hashIndex);
117
+ return path;
118
+ }
119
+ /**
120
+ * Determines whether the given path is a filesystem path.
121
+ * This includes "file://" URLs.
122
+ *
123
+ * @param path
124
+ * @returns
125
+ */
126
+ function isFileSystemPath(path) {
127
+ if (typeof window !== "undefined" || typeof process !== "undefined" && process.browser) return false;
128
+ const protocol = getProtocol(path);
129
+ return protocol === void 0 || protocol === "file";
130
+ }
131
+ /**
132
+ * Converts a filesystem path to a properly-encoded URL.
133
+ *
134
+ * This is intended to handle situations where JSON Schema $Ref Parser is called
135
+ * with a filesystem path that contains characters which are not allowed in URLs.
136
+ *
137
+ * @example
138
+ * The following filesystem paths would be converted to the following URLs:
139
+ *
140
+ * <"!@#$%^&*+=?'>.json ==> %3C%22!@%23$%25%5E&*+=%3F\'%3E.json
141
+ * C:\\My Documents\\File (1).json ==> C:/My%20Documents/File%20(1).json
142
+ * file://Project #42/file.json ==> file://Project%20%2342/file.json
143
+ *
144
+ * @param path
145
+ * @returns
146
+ */
147
+ function fromFileSystemPath(path) {
148
+ if (isWindows()) {
149
+ const projectDir = cwd();
150
+ const upperPath = path.toUpperCase();
151
+ const posixUpper = convertPathToPosix(projectDir).toUpperCase();
152
+ const hasProjectDir = upperPath.includes(posixUpper);
153
+ const hasProjectUri = upperPath.includes(posixUpper);
154
+ const isAbsolutePath = win32.isAbsolute(path) || path.startsWith("http://") || path.startsWith("https://") || path.startsWith("file://");
155
+ if (!(hasProjectDir || hasProjectUri || isAbsolutePath) && !projectDir.startsWith("http")) path = join(projectDir, path);
156
+ path = convertPathToPosix(path);
157
+ }
158
+ path = encodeURI(path);
159
+ for (const pattern of urlEncodePatterns) path = path.replace(pattern[0], pattern[1]);
160
+ return path;
161
+ }
162
+ /**
163
+ * Converts a URL to a local filesystem path.
164
+ */
165
+ function toFileSystemPath(path, keepFileProtocol) {
166
+ path = decodeURI(path);
167
+ for (let i = 0; i < urlDecodePatterns.length; i += 2) path = path.replace(urlDecodePatterns[i], urlDecodePatterns[i + 1]);
168
+ let isFileUrl = path.substr(0, 7).toLowerCase() === "file://";
169
+ if (isFileUrl) {
170
+ path = path[7] === "/" ? path.substr(8) : path.substr(7);
171
+ if (isWindows() && path[1] === "/") path = path[0] + ":" + path.substr(1);
172
+ if (keepFileProtocol) path = "file:///" + path;
173
+ else {
174
+ isFileUrl = false;
175
+ path = isWindows() ? path : "/" + path;
176
+ }
177
+ }
178
+ if (isWindows() && !isFileUrl) {
179
+ path = path.replace(forwardSlashPattern, "\\");
180
+ if (path.substr(1, 2) === ":\\") path = path[0].toUpperCase() + path.substr(1);
181
+ }
182
+ return path;
183
+ }
184
+
185
+ //#endregion
186
+ //#region src/util/errors.ts
187
+ var JSONParserError = class extends Error {
188
+ name;
189
+ message;
190
+ source;
191
+ path;
192
+ code;
193
+ constructor(message, source) {
194
+ super();
195
+ this.code = "EUNKNOWN";
196
+ this.name = "JSONParserError";
197
+ this.message = message;
198
+ this.source = source;
199
+ this.path = null;
200
+ Ono.extend(this);
201
+ }
202
+ get footprint() {
203
+ return `${this.path}+${this.source}+${this.code}+${this.message}`;
204
+ }
205
+ };
206
+ var JSONParserErrorGroup = class JSONParserErrorGroup extends Error {
207
+ files;
208
+ constructor(parser) {
209
+ super();
210
+ this.files = parser;
211
+ this.name = "JSONParserErrorGroup";
212
+ this.message = `${this.errors.length} error${this.errors.length > 1 ? "s" : ""} occurred while reading '${toFileSystemPath(parser.$refs._root$Ref.path)}'`;
213
+ Ono.extend(this);
214
+ }
215
+ static getParserErrors(parser) {
216
+ const errors = [];
217
+ for (const $ref of Object.values(parser.$refs._$refs)) if ($ref.errors) errors.push(...$ref.errors);
218
+ return errors;
219
+ }
220
+ get errors() {
221
+ return JSONParserErrorGroup.getParserErrors(this.files);
222
+ }
223
+ };
224
+ var ParserError = class extends JSONParserError {
225
+ code = "EPARSER";
226
+ name = "ParserError";
227
+ constructor(message, source) {
228
+ super(`Error parsing ${source}: ${message}`, source);
229
+ }
230
+ };
231
+ var ResolverError = class extends JSONParserError {
232
+ code = "ERESOLVER";
233
+ name = "ResolverError";
234
+ ioErrorCode;
235
+ constructor(ex, source) {
236
+ super(ex.message || `Error reading file "${source}"`, source);
237
+ if ("code" in ex) this.ioErrorCode = String(ex.code);
238
+ }
239
+ };
240
+ var MissingPointerError = class extends JSONParserError {
241
+ code = "EMISSINGPOINTER";
242
+ name = "MissingPointerError";
243
+ constructor(token, path) {
244
+ super(`Missing $ref pointer "${getHash(path)}". Token "${token}" does not exist.`, stripHash(path));
245
+ }
246
+ };
247
+ var InvalidPointerError = class extends JSONParserError {
248
+ code = "EUNMATCHEDRESOLVER";
249
+ name = "InvalidPointerError";
250
+ constructor(pointer, path) {
251
+ super(`Invalid $ref pointer "${pointer}". Pointers must begin with "#/"`, stripHash(path));
252
+ }
253
+ };
254
+ function isHandledError(err) {
255
+ return err instanceof JSONParserError || err instanceof JSONParserErrorGroup;
256
+ }
257
+ function normalizeError(err) {
258
+ if (err.path === null) err.path = [];
259
+ return err;
260
+ }
261
+
262
+ //#endregion
263
+ //#region src/ref.ts
264
+ /**
265
+ * This class represents a single JSON reference and its resolved value.
266
+ *
267
+ * @class
268
+ */
269
+ var $Ref = class $Ref {
270
+ /**
271
+ * The file path or URL of the referenced file.
272
+ * This path is relative to the path of the main JSON schema file.
273
+ *
274
+ * This path does NOT contain document fragments (JSON pointers). It always references an ENTIRE file.
275
+ * Use methods such as {@link $Ref#get}, {@link $Ref#resolve}, and {@link $Ref#exists} to get
276
+ * specific JSON pointers within the file.
277
+ *
278
+ * @type {string}
279
+ */
280
+ path;
281
+ /**
282
+ * The resolved value of the JSON reference.
283
+ * Can be any JSON type, not just objects. Unknown file types are represented as Buffers (byte arrays).
284
+ *
285
+ * @type {?*}
286
+ */
287
+ value;
288
+ /**
289
+ * The {@link $Refs} object that contains this {@link $Ref} object.
290
+ *
291
+ * @type {$Refs}
292
+ */
293
+ $refs;
294
+ /**
295
+ * Indicates the type of {@link $Ref#path} (e.g. "file", "http", etc.)
296
+ */
297
+ pathType;
298
+ /**
299
+ * List of all errors. Undefined if no errors.
300
+ */
301
+ errors = [];
302
+ constructor($refs) {
303
+ this.$refs = $refs;
304
+ }
305
+ /**
306
+ * Pushes an error to errors array.
307
+ *
308
+ * @param err - The error to be pushed
309
+ * @returns
310
+ */
311
+ addError(err) {
312
+ if (this.errors === void 0) this.errors = [];
313
+ const existingErrors = this.errors.map(({ footprint }) => footprint);
314
+ if ("errors" in err && Array.isArray(err.errors)) this.errors.push(...err.errors.map(normalizeError).filter(({ footprint }) => !existingErrors.includes(footprint)));
315
+ else if (!("footprint" in err) || !existingErrors.includes(err.footprint)) this.errors.push(normalizeError(err));
316
+ }
317
+ /**
318
+ * Determines whether the given JSON reference exists within this {@link $Ref#value}.
319
+ *
320
+ * @param path - The full path being resolved, optionally with a JSON pointer in the hash
321
+ * @param options
322
+ * @returns
323
+ */
324
+ exists(path, options) {
325
+ try {
326
+ this.resolve(path, options);
327
+ return true;
328
+ } catch {
329
+ return false;
330
+ }
331
+ }
332
+ /**
333
+ * Resolves the given JSON reference within this {@link $Ref#value} and returns the resolved value.
334
+ *
335
+ * @param path - The full path being resolved, optionally with a JSON pointer in the hash
336
+ * @param options
337
+ * @returns - Returns the resolved value
338
+ */
339
+ get(path, options) {
340
+ return this.resolve(path, options)?.value;
341
+ }
342
+ /**
343
+ * Resolves the given JSON reference within this {@link $Ref#value}.
344
+ *
345
+ * @param path - The full path being resolved, optionally with a JSON pointer in the hash
346
+ * @param options
347
+ * @param friendlyPath - The original user-specified path (used for error messages)
348
+ * @param pathFromRoot - The path of `obj` from the schema root
349
+ * @returns
350
+ */
351
+ resolve(path, options, friendlyPath, pathFromRoot) {
352
+ return new pointer_default(this, path, friendlyPath).resolve(this.value, options, pathFromRoot);
353
+ }
354
+ /**
355
+ * Sets the value of a nested property within this {@link $Ref#value}.
356
+ * If the property, or any of its parents don't exist, they will be created.
357
+ *
358
+ * @param path - The full path of the property to set, optionally with a JSON pointer in the hash
359
+ * @param value - The value to assign
360
+ */
361
+ set(path, value) {
362
+ this.value = new pointer_default(this, path).set(this.value, value);
363
+ }
364
+ /**
365
+ * Determines whether the given value is a JSON reference.
366
+ *
367
+ * @param value - The value to inspect
368
+ * @returns
369
+ */
370
+ static is$Ref(value) {
371
+ return Boolean(value) && typeof value === "object" && value !== null && "$ref" in value && typeof value.$ref === "string" && value.$ref.length > 0;
372
+ }
373
+ /**
374
+ * Determines whether the given value is an external JSON reference.
375
+ *
376
+ * @param value - The value to inspect
377
+ * @returns
378
+ */
379
+ static isExternal$Ref(value) {
380
+ return $Ref.is$Ref(value) && value.$ref[0] !== "#";
381
+ }
382
+ /**
383
+ * Determines whether the given value is a JSON reference, and whether it is allowed by the options.
384
+ *
385
+ * @param value - The value to inspect
386
+ * @param options
387
+ * @returns
388
+ */
389
+ static isAllowed$Ref(value) {
390
+ if (this.is$Ref(value)) {
391
+ if (value.$ref.substring(0, 2) === "#/" || value.$ref === "#") return true;
392
+ else if (value.$ref[0] !== "#") return true;
393
+ }
394
+ }
395
+ /**
396
+ * Determines whether the given value is a JSON reference that "extends" its resolved value.
397
+ * That is, it has extra properties (in addition to "$ref"), so rather than simply pointing to
398
+ * an existing value, this $ref actually creates a NEW value that is a shallow copy of the resolved
399
+ * value, plus the extra properties.
400
+ *
401
+ * @example: {
402
+ person: {
403
+ properties: {
404
+ firstName: { type: string }
405
+ lastName: { type: string }
406
+ }
407
+ }
408
+ employee: {
409
+ properties: {
410
+ $ref: #/person/properties
411
+ salary: { type: number }
412
+ }
413
+ }
414
+ }
415
+ * In this example, "employee" is an extended $ref, since it extends "person" with an additional
416
+ * property (salary). The result is a NEW value that looks like this:
417
+ *
418
+ * {
419
+ * properties: {
420
+ * firstName: { type: string }
421
+ * lastName: { type: string }
422
+ * salary: { type: number }
423
+ * }
424
+ * }
425
+ *
426
+ * @param value - The value to inspect
427
+ * @returns
428
+ */
429
+ static isExtended$Ref(value) {
430
+ return $Ref.is$Ref(value) && Object.keys(value).length > 1;
431
+ }
432
+ /**
433
+ * Returns the resolved value of a JSON Reference.
434
+ * If necessary, the resolved value is merged with the JSON Reference to create a new object
435
+ *
436
+ * @example: {
437
+ person: {
438
+ properties: {
439
+ firstName: { type: string }
440
+ lastName: { type: string }
441
+ }
442
+ }
443
+ employee: {
444
+ properties: {
445
+ $ref: #/person/properties
446
+ salary: { type: number }
447
+ }
448
+ }
449
+ } When "person" and "employee" are merged, you end up with the following object:
450
+ *
451
+ * {
452
+ * properties: {
453
+ * firstName: { type: string }
454
+ * lastName: { type: string }
455
+ * salary: { type: number }
456
+ * }
457
+ * }
458
+ *
459
+ * @param $ref - The JSON reference object (the one with the "$ref" property)
460
+ * @param resolvedValue - The resolved value, which can be any type
461
+ * @returns - Returns the dereferenced value
462
+ */
463
+ static dereference($ref, resolvedValue) {
464
+ if (resolvedValue && typeof resolvedValue === "object" && $Ref.isExtended$Ref($ref)) {
465
+ const merged = {};
466
+ for (const key of Object.keys($ref)) if (key !== "$ref") merged[key] = $ref[key];
467
+ for (const key of Object.keys(resolvedValue)) if (!(key in merged)) merged[key] = resolvedValue[key];
468
+ return merged;
469
+ } else return resolvedValue;
470
+ }
471
+ };
472
+ var ref_default = $Ref;
473
+
474
+ //#endregion
475
+ //#region src/pointer.ts
476
+ const slashes = /\//g;
477
+ const tildes = /~/g;
478
+ const escapedSlash = /~1/g;
479
+ const escapedTilde = /~0/g;
480
+ const safeDecodeURIComponent = (encodedURIComponent) => {
481
+ try {
482
+ return decodeURIComponent(encodedURIComponent);
483
+ } catch {
484
+ return encodedURIComponent;
485
+ }
486
+ };
487
+ /**
488
+ * This class represents a single JSON pointer and its resolved value.
489
+ *
490
+ * @param $ref
491
+ * @param path
492
+ * @param [friendlyPath] - The original user-specified path (used for error messages)
493
+ * @class
494
+ */
495
+ var Pointer = class Pointer {
496
+ /**
497
+ * The {@link $Ref} object that contains this {@link Pointer} object.
498
+ */
499
+ $ref;
500
+ /**
501
+ * The file path or URL, containing the JSON pointer in the hash.
502
+ * This path is relative to the path of the main JSON schema file.
503
+ */
504
+ path;
505
+ /**
506
+ * The original path or URL, used for error messages.
507
+ */
508
+ originalPath;
509
+ /**
510
+ * The value of the JSON pointer.
511
+ * Can be any JSON type, not just objects. Unknown file types are represented as Buffers (byte arrays).
512
+ */
513
+ value;
514
+ /**
515
+ * Indicates whether the pointer references itself.
516
+ */
517
+ circular;
518
+ /**
519
+ * The number of indirect references that were traversed to resolve the value.
520
+ * Resolving a single pointer may require resolving multiple $Refs.
521
+ */
522
+ indirections;
523
+ constructor($ref, path, friendlyPath) {
524
+ this.$ref = $ref;
525
+ this.path = path;
526
+ this.originalPath = friendlyPath || path;
527
+ this.value = void 0;
528
+ this.circular = false;
529
+ this.indirections = 0;
530
+ }
531
+ /**
532
+ * Resolves the value of a nested property within the given object.
533
+ *
534
+ * @param obj - The object that will be crawled
535
+ * @param options
536
+ * @param pathFromRoot - the path of place that initiated resolving
537
+ *
538
+ * @returns
539
+ * Returns a JSON pointer whose {@link Pointer#value} is the resolved value.
540
+ * If resolving this value required resolving other JSON references, then
541
+ * the {@link Pointer#$ref} and {@link Pointer#path} will reflect the resolution path
542
+ * of the resolved value.
543
+ */
544
+ resolve(obj, options, pathFromRoot) {
545
+ const tokens = Pointer.parse(this.path, this.originalPath);
546
+ this.value = unwrapOrThrow(obj);
547
+ const errors = [];
548
+ for (let i = 0; i < tokens.length; i++) {
549
+ if (resolveIf$Ref(this, options, pathFromRoot)) this.path = Pointer.join(this.path, tokens.slice(i));
550
+ if (typeof this.value === "object" && this.value !== null && !isRootPath(pathFromRoot) && "$ref" in this.value) return this;
551
+ const token = tokens[i];
552
+ if (this.value[token] === void 0 || this.value[token] === null && i === tokens.length - 1) {
553
+ let didFindSubstringSlashMatch = false;
554
+ for (let j = tokens.length - 1; j > i; j--) {
555
+ const joinedToken = tokens.slice(i, j + 1).join("/");
556
+ if (this.value[joinedToken] !== void 0) {
557
+ this.value = this.value[joinedToken];
558
+ i = j;
559
+ didFindSubstringSlashMatch = true;
560
+ break;
561
+ }
562
+ }
563
+ if (didFindSubstringSlashMatch) continue;
564
+ this.value = null;
565
+ errors.push(new MissingPointerError(token, decodeURI(this.originalPath)));
566
+ } else this.value = this.value[token];
567
+ }
568
+ if (errors.length > 0) throw errors.length === 1 ? errors[0] : new AggregateError(errors, "Multiple missing pointer errors");
569
+ if (!this.value || this.value.$ref && resolve(this.path, this.value.$ref) !== pathFromRoot) resolveIf$Ref(this, options, pathFromRoot);
570
+ return this;
571
+ }
572
+ /**
573
+ * Sets the value of a nested property within the given object.
574
+ *
575
+ * @param obj - The object that will be crawled
576
+ * @param value - the value to assign
577
+ * @param options
578
+ *
579
+ * @returns
580
+ * Returns the modified object, or an entirely new object if the entire object is overwritten.
581
+ */
582
+ set(obj, value, options) {
583
+ const tokens = Pointer.parse(this.path);
584
+ let token;
585
+ if (tokens.length === 0) {
586
+ this.value = value;
587
+ return value;
588
+ }
589
+ this.value = unwrapOrThrow(obj);
590
+ for (let i = 0; i < tokens.length - 1; i++) {
591
+ resolveIf$Ref(this, options);
592
+ token = tokens[i];
593
+ if (this.value && this.value[token] !== void 0) this.value = this.value[token];
594
+ else this.value = setValue(this, token, {});
595
+ }
596
+ resolveIf$Ref(this, options);
597
+ token = tokens[tokens.length - 1];
598
+ setValue(this, token, value);
599
+ return obj;
600
+ }
601
+ /**
602
+ * Parses a JSON pointer (or a path containing a JSON pointer in the hash)
603
+ * and returns an array of the pointer's tokens.
604
+ * (e.g. "schema.json#/definitions/person/name" => ["definitions", "person", "name"])
605
+ *
606
+ * The pointer is parsed according to RFC 6901
607
+ * {@link https://tools.ietf.org/html/rfc6901#section-3}
608
+ *
609
+ * @param path
610
+ * @param [originalPath]
611
+ * @returns
612
+ */
613
+ static parse(path, originalPath) {
614
+ const pointer = getHash(path).substring(1);
615
+ if (!pointer) return [];
616
+ const split = pointer.split("/");
617
+ for (let i = 0; i < split.length; i++) split[i] = safeDecodeURIComponent(split[i].replace(escapedSlash, "/").replace(escapedTilde, "~"));
618
+ if (split[0] !== "") throw new InvalidPointerError(pointer, originalPath === void 0 ? path : originalPath);
619
+ return split.slice(1);
620
+ }
621
+ /**
622
+ * Creates a JSON pointer path, by joining one or more tokens to a base path.
623
+ *
624
+ * @param base - The base path (e.g. "schema.json#/definitions/person")
625
+ * @param tokens - The token(s) to append (e.g. ["name", "first"])
626
+ * @returns
627
+ */
628
+ static join(base, tokens) {
629
+ if (base.indexOf("#") === -1) base += "#";
630
+ tokens = Array.isArray(tokens) ? tokens : [tokens];
631
+ for (let i = 0; i < tokens.length; i++) {
632
+ const token = tokens[i];
633
+ base += "/" + encodeURIComponent(token.replace(tildes, "~0").replace(slashes, "~1"));
634
+ }
635
+ return base;
636
+ }
637
+ };
638
+ /**
639
+ * If the given pointer's {@link Pointer#value} is a JSON reference,
640
+ * then the reference is resolved and {@link Pointer#value} is replaced with the resolved value.
641
+ * In addition, {@link Pointer#path} and {@link Pointer#$ref} are updated to reflect the
642
+ * resolution path of the new value.
643
+ *
644
+ * @param pointer
645
+ * @param options
646
+ * @param [pathFromRoot] - the path of place that initiated resolving
647
+ * @returns - Returns `true` if the resolution path changed
648
+ */
649
+ function resolveIf$Ref(pointer, options, pathFromRoot) {
650
+ if (ref_default.isAllowed$Ref(pointer.value)) {
651
+ const $refPath = resolve(pointer.path, pointer.value.$ref);
652
+ if ($refPath === pointer.path && !isRootPath(pathFromRoot)) pointer.circular = true;
653
+ else {
654
+ const resolved = pointer.$ref.$refs._resolve($refPath, pointer.path, options);
655
+ if (resolved === null) return false;
656
+ pointer.indirections += resolved.indirections + 1;
657
+ if (ref_default.isExtended$Ref(pointer.value)) {
658
+ pointer.value = ref_default.dereference(pointer.value, resolved.value);
659
+ return false;
660
+ } else {
661
+ pointer.$ref = resolved.$ref;
662
+ pointer.path = resolved.path;
663
+ pointer.value = resolved.value;
664
+ }
665
+ return true;
666
+ }
667
+ }
668
+ }
669
+ var pointer_default = Pointer;
670
+ /**
671
+ * Sets the specified token value of the {@link Pointer#value}.
672
+ *
673
+ * The token is evaluated according to RFC 6901.
674
+ * {@link https://tools.ietf.org/html/rfc6901#section-4}
675
+ *
676
+ * @param pointer - The JSON Pointer whose value will be modified
677
+ * @param token - A JSON Pointer token that indicates how to modify `obj`
678
+ * @param value - The value to assign
679
+ * @returns - Returns the assigned value
680
+ */
681
+ function setValue(pointer, token, value) {
682
+ if (pointer.value && typeof pointer.value === "object") if (token === "-" && Array.isArray(pointer.value)) pointer.value.push(value);
683
+ else pointer.value[token] = value;
684
+ else throw new JSONParserError(`Error assigning $ref pointer "${pointer.path}". \nCannot set "${token}" of a non-object.`);
685
+ return value;
686
+ }
687
+ function unwrapOrThrow(value) {
688
+ if (isHandledError(value)) throw value;
689
+ return value;
690
+ }
691
+ function isRootPath(pathFromRoot) {
692
+ return typeof pathFromRoot == "string" && Pointer.parse(pathFromRoot).length == 0;
693
+ }
694
+
695
+ //#endregion
696
+ //#region src/bundle.ts
697
+ /**
698
+ * Fast lookup using Map instead of linear search with deep equality
699
+ */
700
+ const createInventoryLookup = () => {
701
+ const lookup = /* @__PURE__ */ new Map();
702
+ const objectIds = /* @__PURE__ */ new WeakMap();
703
+ let idCounter = 0;
704
+ const getObjectId = (obj) => {
705
+ if (!objectIds.has(obj)) objectIds.set(obj, `obj_${++idCounter}`);
706
+ return objectIds.get(obj);
707
+ };
708
+ const createInventoryKey = ($refParent, $refKey) => `${getObjectId($refParent)}_${$refKey}`;
709
+ return {
710
+ add: (entry) => {
711
+ const key = createInventoryKey(entry.parent, entry.key);
712
+ lookup.set(key, entry);
713
+ },
714
+ find: ($refParent, $refKey) => {
715
+ const key = createInventoryKey($refParent, $refKey);
716
+ return lookup.get(key);
717
+ },
718
+ remove: (entry) => {
719
+ const key = createInventoryKey(entry.parent, entry.key);
720
+ lookup.delete(key);
721
+ }
722
+ };
723
+ };
724
+ /**
725
+ * Determine the container type from a JSON Pointer path.
726
+ * Analyzes the path tokens to identify the appropriate OpenAPI component container.
727
+ *
728
+ * @param path - The JSON Pointer path to analyze
729
+ * @returns The container type: "schemas", "parameters", "requestBodies", "responses", or "headers"
730
+ */
731
+ const getContainerTypeFromPath = (path) => {
732
+ const tokens = pointer_default.parse(path);
733
+ const has = (t) => tokens.includes(t);
734
+ if (has("parameters")) return "parameters";
735
+ if (has("requestBody")) return "requestBodies";
736
+ if (has("headers")) return "headers";
737
+ if (has("responses")) return "responses";
738
+ if (has("schema")) return "schemas";
739
+ return "schemas";
740
+ };
741
+ /**
742
+ * Inventories the given JSON Reference (i.e. records detailed information about it so we can
743
+ * optimize all $refs in the schema), and then crawls the resolved value.
744
+ */
745
+ const inventory$Ref = ({ $refKey, $refParent, $refs, indirections, inventory, inventoryLookup, options, path, pathFromRoot, resolvedRefs = /* @__PURE__ */ new Map(), visitedObjects = /* @__PURE__ */ new WeakSet() }) => {
746
+ const $ref = $refKey === null ? $refParent : $refParent[$refKey];
747
+ const $refPath = resolve(path, $ref.$ref);
748
+ let pointer = resolvedRefs.get($refPath);
749
+ if (!pointer) {
750
+ try {
751
+ pointer = $refs._resolve($refPath, pathFromRoot, options);
752
+ } catch (error) {
753
+ if (error instanceof MissingPointerError) {
754
+ console.warn(`Skipping unresolvable $ref: ${$refPath}`);
755
+ return;
756
+ }
757
+ throw error;
758
+ }
759
+ if (pointer) resolvedRefs.set($refPath, pointer);
760
+ }
761
+ if (pointer === null) return;
762
+ const depth = pointer_default.parse(pathFromRoot).length;
763
+ const file = stripHash(pointer.path);
764
+ const hash = getHash(pointer.path);
765
+ const external = file !== $refs._root$Ref.path;
766
+ const extended = ref_default.isExtended$Ref($ref);
767
+ indirections += pointer.indirections;
768
+ const existingEntry = inventoryLookup.find($refParent, $refKey);
769
+ if (existingEntry && existingEntry.pathFromRoot === pathFromRoot) if (depth < existingEntry.depth || indirections < existingEntry.indirections) {
770
+ removeFromInventory(inventory, existingEntry);
771
+ inventoryLookup.remove(existingEntry);
772
+ } else return;
773
+ const newEntry = {
774
+ $ref,
775
+ circular: pointer.circular,
776
+ depth,
777
+ extended,
778
+ external,
779
+ file,
780
+ hash,
781
+ indirections,
782
+ key: $refKey,
783
+ originalContainerType: external ? getContainerTypeFromPath(pointer.path) : void 0,
784
+ parent: $refParent,
785
+ pathFromRoot,
786
+ value: pointer.value
787
+ };
788
+ inventory.push(newEntry);
789
+ inventoryLookup.add(newEntry);
790
+ if (!existingEntry || external) crawl$1({
791
+ $refs,
792
+ indirections: indirections + 1,
793
+ inventory,
794
+ inventoryLookup,
795
+ key: null,
796
+ options,
797
+ parent: pointer.value,
798
+ path: pointer.path,
799
+ pathFromRoot,
800
+ resolvedRefs,
801
+ visitedObjects
802
+ });
803
+ };
804
+ /**
805
+ * Recursively crawls the given value, and inventories all JSON references.
806
+ */
807
+ const crawl$1 = ({ $refs, indirections, inventory, inventoryLookup, key, options, parent, path, pathFromRoot, resolvedRefs = /* @__PURE__ */ new Map(), visitedObjects = /* @__PURE__ */ new WeakSet() }) => {
808
+ const obj = key === null ? parent : parent[key];
809
+ if (obj && typeof obj === "object" && !ArrayBuffer.isView(obj)) {
810
+ if (visitedObjects.has(obj)) return;
811
+ if (ref_default.isAllowed$Ref(obj)) inventory$Ref({
812
+ $refKey: key,
813
+ $refParent: parent,
814
+ $refs,
815
+ indirections,
816
+ inventory,
817
+ inventoryLookup,
818
+ options,
819
+ path,
820
+ pathFromRoot,
821
+ resolvedRefs,
822
+ visitedObjects
823
+ });
824
+ else {
825
+ visitedObjects.add(obj);
826
+ const keys = Object.keys(obj).sort((a, b) => {
827
+ if (a === "definitions") return -1;
828
+ else if (b === "definitions") return 1;
829
+ else return a.length - b.length;
830
+ });
831
+ for (const key$1 of keys) {
832
+ const keyPath = pointer_default.join(path, key$1);
833
+ const keyPathFromRoot = pointer_default.join(pathFromRoot, key$1);
834
+ const value = obj[key$1];
835
+ if (ref_default.isAllowed$Ref(value)) inventory$Ref({
836
+ $refKey: key$1,
837
+ $refParent: obj,
838
+ $refs,
839
+ indirections,
840
+ inventory,
841
+ inventoryLookup,
842
+ options,
843
+ path,
844
+ pathFromRoot: keyPathFromRoot,
845
+ resolvedRefs,
846
+ visitedObjects
847
+ });
848
+ else crawl$1({
849
+ $refs,
850
+ indirections,
851
+ inventory,
852
+ inventoryLookup,
853
+ key: key$1,
854
+ options,
855
+ parent: obj,
856
+ path: keyPath,
857
+ pathFromRoot: keyPathFromRoot,
858
+ resolvedRefs,
859
+ visitedObjects
860
+ });
861
+ }
862
+ }
863
+ }
864
+ };
865
+ /**
866
+ * Remap external refs by hoisting resolved values into a shared container in the root schema
867
+ * and pointing all occurrences to those internal definitions. Internal refs remain internal.
868
+ */
869
+ function remap(parser, inventory) {
870
+ const root = parser.schema;
871
+ inventory.sort((a, b) => {
872
+ if (a.file !== b.file) return a.file < b.file ? -1 : 1;
873
+ else if (a.hash !== b.hash) return a.hash < b.hash ? -1 : 1;
874
+ else if (a.circular !== b.circular) return a.circular ? -1 : 1;
875
+ else if (a.extended !== b.extended) return a.extended ? 1 : -1;
876
+ else if (a.indirections !== b.indirections) return a.indirections - b.indirections;
877
+ else if (a.depth !== b.depth) return a.depth - b.depth;
878
+ else {
879
+ const aDefinitionsIndex = a.pathFromRoot.lastIndexOf("/definitions");
880
+ const bDefinitionsIndex = b.pathFromRoot.lastIndexOf("/definitions");
881
+ if (aDefinitionsIndex !== bDefinitionsIndex) return bDefinitionsIndex - aDefinitionsIndex;
882
+ else return a.pathFromRoot.length - b.pathFromRoot.length;
883
+ }
884
+ });
885
+ const ensureContainer = (type) => {
886
+ const isOas3 = !!(root && typeof root === "object" && typeof root.openapi === "string");
887
+ const isOas2 = !!(root && typeof root === "object" && typeof root.swagger === "string");
888
+ if (isOas3) {
889
+ if (!root.components || typeof root.components !== "object") root.components = {};
890
+ if (!root.components[type] || typeof root.components[type] !== "object") root.components[type] = {};
891
+ return {
892
+ obj: root.components[type],
893
+ prefix: `#/components/${type}`
894
+ };
895
+ }
896
+ if (isOas2) {
897
+ if (type === "schemas") {
898
+ if (!root.definitions || typeof root.definitions !== "object") root.definitions = {};
899
+ return {
900
+ obj: root.definitions,
901
+ prefix: "#/definitions"
902
+ };
903
+ }
904
+ if (type === "parameters") {
905
+ if (!root.parameters || typeof root.parameters !== "object") root.parameters = {};
906
+ return {
907
+ obj: root.parameters,
908
+ prefix: "#/parameters"
909
+ };
910
+ }
911
+ if (type === "responses") {
912
+ if (!root.responses || typeof root.responses !== "object") root.responses = {};
913
+ return {
914
+ obj: root.responses,
915
+ prefix: "#/responses"
916
+ };
917
+ }
918
+ if (!root.definitions || typeof root.definitions !== "object") root.definitions = {};
919
+ return {
920
+ obj: root.definitions,
921
+ prefix: "#/definitions"
922
+ };
923
+ }
924
+ if (root && typeof root === "object") {
925
+ if (root.components && typeof root.components === "object") {
926
+ if (!root.components[type] || typeof root.components[type] !== "object") root.components[type] = {};
927
+ return {
928
+ obj: root.components[type],
929
+ prefix: `#/components/${type}`
930
+ };
931
+ }
932
+ if (root.definitions && typeof root.definitions === "object") return {
933
+ obj: root.definitions,
934
+ prefix: "#/definitions"
935
+ };
936
+ if (!root.components || typeof root.components !== "object") root.components = {};
937
+ if (!root.components[type] || typeof root.components[type] !== "object") root.components[type] = {};
938
+ return {
939
+ obj: root.components[type],
940
+ prefix: `#/components/${type}`
941
+ };
942
+ }
943
+ root.definitions = root.definitions || {};
944
+ return {
945
+ obj: root.definitions,
946
+ prefix: "#/definitions"
947
+ };
948
+ };
949
+ /**
950
+ * Choose the appropriate component container for bundling.
951
+ * Prioritizes the original container type from external files over usage location.
952
+ *
953
+ * @param entry - The inventory entry containing reference information
954
+ * @returns The container type to use for bundling
955
+ */
956
+ const chooseComponent = (entry) => {
957
+ if (entry.originalContainerType) return entry.originalContainerType;
958
+ return getContainerTypeFromPath(entry.pathFromRoot);
959
+ };
960
+ const targetToNameByPrefix = /* @__PURE__ */ new Map();
961
+ const usedNamesByObj = /* @__PURE__ */ new Map();
962
+ const sanitize = (name) => name.replace(/[^A-Za-z0-9_-]/g, "_");
963
+ const baseName = (filePath) => {
964
+ try {
965
+ const parts = filePath.split("#")[0].split("/");
966
+ const filename = parts[parts.length - 1] || "schema";
967
+ const dot = filename.lastIndexOf(".");
968
+ return sanitize(dot > 0 ? filename.substring(0, dot) : filename);
969
+ } catch {
970
+ return "schema";
971
+ }
972
+ };
973
+ const lastToken = (hash) => {
974
+ if (!hash || hash === "#") return "root";
975
+ const tokens = hash.replace(/^#\//, "").split("/");
976
+ return sanitize(tokens[tokens.length - 1] || "root");
977
+ };
978
+ const uniqueName = (containerObj, proposed) => {
979
+ if (!usedNamesByObj.has(containerObj)) usedNamesByObj.set(containerObj, new Set(Object.keys(containerObj || {})));
980
+ const used = usedNamesByObj.get(containerObj);
981
+ let name = proposed;
982
+ let i = 2;
983
+ while (used.has(name)) name = `${proposed}_${i++}`;
984
+ used.add(name);
985
+ return name;
986
+ };
987
+ for (const entry of inventory) {
988
+ if (!entry || !entry.$ref || typeof entry.$ref !== "object") continue;
989
+ if (!entry.external) {
990
+ if (!entry.extended && entry.$ref && typeof entry.$ref === "object") entry.$ref.$ref = entry.hash;
991
+ continue;
992
+ }
993
+ if (entry.circular) {
994
+ if (entry.$ref && typeof entry.$ref === "object") entry.$ref.$ref = entry.pathFromRoot;
995
+ continue;
996
+ }
997
+ const { obj: container, prefix } = ensureContainer(chooseComponent(entry));
998
+ const targetKey = `${entry.file}::${entry.hash}`;
999
+ if (!targetToNameByPrefix.has(prefix)) targetToNameByPrefix.set(prefix, /* @__PURE__ */ new Map());
1000
+ const namesForPrefix = targetToNameByPrefix.get(prefix);
1001
+ let defName = namesForPrefix.get(targetKey);
1002
+ if (!defName) {
1003
+ let proposedBase = baseName(entry.file);
1004
+ try {
1005
+ const parserAny = parser;
1006
+ if (parserAny && parserAny.sourcePathToPrefix && typeof parserAny.sourcePathToPrefix.get === "function") {
1007
+ const withoutHash = (entry.file || "").split("#")[0];
1008
+ const mapped = parserAny.sourcePathToPrefix.get(withoutHash);
1009
+ if (mapped && typeof mapped === "string") proposedBase = mapped;
1010
+ }
1011
+ } catch {}
1012
+ defName = uniqueName(container, `${proposedBase}_${lastToken(entry.hash)}`);
1013
+ namesForPrefix.set(targetKey, defName);
1014
+ container[defName] = entry.value;
1015
+ }
1016
+ const refPath = `${prefix}/${defName}`;
1017
+ if (entry.extended && entry.$ref && typeof entry.$ref === "object") entry.$ref.$ref = refPath;
1018
+ else entry.parent[entry.key] = { $ref: refPath };
1019
+ }
1020
+ }
1021
+ function removeFromInventory(inventory, entry) {
1022
+ const index = inventory.indexOf(entry);
1023
+ inventory.splice(index, 1);
1024
+ }
1025
+ /**
1026
+ * Bundles all external JSON references into the main JSON schema, thus resulting in a schema that
1027
+ * only has *internal* references, not any *external* references.
1028
+ * This method mutates the JSON schema object, adding new references and re-mapping existing ones.
1029
+ *
1030
+ * @param parser
1031
+ * @param options
1032
+ */
1033
+ function bundle(parser, options) {
1034
+ const inventory = [];
1035
+ const inventoryLookup = createInventoryLookup();
1036
+ const visitedObjects = /* @__PURE__ */ new WeakSet();
1037
+ const resolvedRefs = /* @__PURE__ */ new Map();
1038
+ crawl$1({
1039
+ $refs: parser.$refs,
1040
+ indirections: 0,
1041
+ inventory,
1042
+ inventoryLookup,
1043
+ key: "schema",
1044
+ options,
1045
+ parent: parser,
1046
+ path: parser.$refs._root$Ref.path + "#",
1047
+ pathFromRoot: "#",
1048
+ resolvedRefs,
1049
+ visitedObjects
1050
+ });
1051
+ remap(parser, inventory);
1052
+ }
1053
+
1054
+ //#endregion
1055
+ //#region src/parsers/binary.ts
1056
+ const BINARY_REGEXP = /\.(jpeg|jpg|gif|png|bmp|ico)$/i;
1057
+ const binaryParser = {
1058
+ canHandle: (file) => Buffer.isBuffer(file.data) && BINARY_REGEXP.test(file.url),
1059
+ handler: (file) => Buffer.isBuffer(file.data) ? file.data : Buffer.from(file.data),
1060
+ name: "binary"
1061
+ };
1062
+
1063
+ //#endregion
1064
+ //#region src/parsers/json.ts
1065
+ const jsonParser = {
1066
+ canHandle: (file) => file.extension === ".json",
1067
+ async handler(file) {
1068
+ let data = file.data;
1069
+ if (Buffer.isBuffer(data)) data = data.toString();
1070
+ if (typeof data !== "string") return data;
1071
+ if (!data.trim().length) return;
1072
+ try {
1073
+ return JSON.parse(data);
1074
+ } catch (error) {
1075
+ try {
1076
+ const firstCurlyBrace = data.indexOf("{");
1077
+ data = data.slice(firstCurlyBrace);
1078
+ return JSON.parse(data);
1079
+ } catch (error$1) {
1080
+ throw new ParserError(error$1.message, file.url);
1081
+ }
1082
+ }
1083
+ },
1084
+ name: "json"
1085
+ };
1086
+
1087
+ //#endregion
1088
+ //#region src/parsers/text.ts
1089
+ const TEXT_REGEXP = /\.(txt|htm|html|md|xml|js|min|map|css|scss|less|svg)$/i;
1090
+ const textParser = {
1091
+ canHandle: (file) => (typeof file.data === "string" || Buffer.isBuffer(file.data)) && TEXT_REGEXP.test(file.url),
1092
+ handler(file) {
1093
+ if (typeof file.data === "string") return file.data;
1094
+ if (!Buffer.isBuffer(file.data)) throw new ParserError("data is not text", file.url);
1095
+ return file.data.toString("utf-8");
1096
+ },
1097
+ name: "text"
1098
+ };
1099
+
1100
+ //#endregion
1101
+ //#region src/parsers/yaml.ts
1102
+ const yamlParser = {
1103
+ canHandle: (file) => [
1104
+ ".yaml",
1105
+ ".yml",
1106
+ ".json"
1107
+ ].includes(file.extension),
1108
+ handler: async (file) => {
1109
+ const data = Buffer.isBuffer(file.data) ? file.data.toString() : file.data;
1110
+ if (typeof data !== "string") return data;
1111
+ try {
1112
+ return yaml.load(data, { schema: JSON_SCHEMA });
1113
+ } catch (error) {
1114
+ throw new ParserError(error?.message || "Parser Error", file.url);
1115
+ }
1116
+ },
1117
+ name: "yaml"
1118
+ };
1119
+
1120
+ //#endregion
1121
+ //#region src/options.ts
1122
+ const getJsonSchemaRefParserDefaultOptions = () => ({
1123
+ dereference: {
1124
+ circular: true,
1125
+ excludedPathMatcher: () => false,
1126
+ referenceResolution: "relative"
1127
+ },
1128
+ parse: {
1129
+ binary: { ...binaryParser },
1130
+ json: { ...jsonParser },
1131
+ text: { ...textParser },
1132
+ yaml: { ...yamlParser }
1133
+ }
1134
+ });
1135
+
1136
+ //#endregion
1137
+ //#region src/util/plugins.ts
1138
+ /**
1139
+ * Runs the specified method of the given plugins, in order, until one of them returns a successful result.
1140
+ * Each method can return a synchronous value, a Promise, or call an error-first callback.
1141
+ * If the promise resolves successfully, or the callback is called without an error, then the result
1142
+ * is immediately returned and no further plugins are called.
1143
+ * If the promise rejects, or the callback is called with an error, then the next plugin is called.
1144
+ * If ALL plugins fail, then the last error is thrown.
1145
+ */
1146
+ async function run(plugins, file) {
1147
+ let index = 0;
1148
+ let lastError;
1149
+ let plugin;
1150
+ return new Promise((resolve$1, reject) => {
1151
+ const runNextPlugin = async () => {
1152
+ plugin = plugins[index++];
1153
+ if (!plugin) return reject(lastError);
1154
+ try {
1155
+ const result = await plugin.handler(file);
1156
+ if (result !== void 0) return resolve$1({
1157
+ plugin,
1158
+ result
1159
+ });
1160
+ if (index === plugins.length) throw new Error("No promise has been returned.");
1161
+ } catch (error) {
1162
+ lastError = {
1163
+ error,
1164
+ plugin
1165
+ };
1166
+ runNextPlugin();
1167
+ }
1168
+ };
1169
+ runNextPlugin();
1170
+ });
1171
+ }
1172
+
1173
+ //#endregion
1174
+ //#region src/parse.ts
1175
+ /**
1176
+ * Prepares the file object so we can populate it with data and other values
1177
+ * when it's read and parsed. This "file object" will be passed to all
1178
+ * resolvers and parsers.
1179
+ */
1180
+ function newFile(path) {
1181
+ let url = path;
1182
+ const hashIndex = url.indexOf("#");
1183
+ let hash = "";
1184
+ if (hashIndex > -1) {
1185
+ hash = url.substring(hashIndex);
1186
+ url = url.substring(0, hashIndex);
1187
+ }
1188
+ return {
1189
+ extension: getExtension(url),
1190
+ hash,
1191
+ url
1192
+ };
1193
+ }
1194
+ /**
1195
+ * Parses the given file's contents, using the configured parser plugins.
1196
+ */
1197
+ async function parseFile(file, options) {
1198
+ try {
1199
+ const parsers = [
1200
+ options.json,
1201
+ options.yaml,
1202
+ options.text,
1203
+ options.binary
1204
+ ];
1205
+ const filtered = parsers.filter((plugin) => plugin.canHandle(file));
1206
+ return await run(filtered.length ? filtered : parsers, file);
1207
+ } catch (error) {
1208
+ if (error && error.message && error.message.startsWith("Error parsing")) throw error;
1209
+ if (!error || !("error" in error)) throw ono.syntax(`Unable to parse ${file.url}`);
1210
+ if (error.error instanceof ParserError) throw error.error;
1211
+ throw new ParserError(error.error.message, file.url);
1212
+ }
1213
+ }
1214
+
1215
+ //#endregion
1216
+ //#region src/refs.ts
1217
+ /**
1218
+ * When you call the resolve method, the value that gets passed to the callback function (or Promise) is a $Refs object. This same object is accessible via the parser.$refs property of $RefParser objects.
1219
+ *
1220
+ * This object is a map of JSON References and their resolved values. It also has several convenient helper methods that make it easy for you to navigate and manipulate the JSON References.
1221
+ *
1222
+ * See https://apitools.dev/json-schema-ref-parser/docs/refs.html
1223
+ */
1224
+ var $Refs = class {
1225
+ /**
1226
+ * This property is true if the schema contains any circular references. You may want to check this property before serializing the dereferenced schema as JSON, since JSON.stringify() does not support circular references by default.
1227
+ *
1228
+ * See https://apitools.dev/json-schema-ref-parser/docs/refs.html#circular
1229
+ */
1230
+ circular;
1231
+ /**
1232
+ * Returns the paths/URLs of all the files in your schema (including the main schema file).
1233
+ *
1234
+ * See https://apitools.dev/json-schema-ref-parser/docs/refs.html#pathstypes
1235
+ *
1236
+ * @param types (optional) Optionally only return certain types of paths ("file", "http", etc.)
1237
+ */
1238
+ paths(...types) {
1239
+ return getPaths(this._$refs, types.flat()).map((path) => convertPathToPosix(path.decoded));
1240
+ }
1241
+ /**
1242
+ * Returns a map of paths/URLs and their correspond values.
1243
+ *
1244
+ * See https://apitools.dev/json-schema-ref-parser/docs/refs.html#valuestypes
1245
+ *
1246
+ * @param types (optional) Optionally only return values from certain locations ("file", "http", etc.)
1247
+ */
1248
+ values(...types) {
1249
+ const $refs = this._$refs;
1250
+ return getPaths($refs, types.flat()).reduce((obj, path) => {
1251
+ obj[convertPathToPosix(path.decoded)] = $refs[path.encoded].value;
1252
+ return obj;
1253
+ }, {});
1254
+ }
1255
+ /**
1256
+ * Returns `true` if the given path exists in the schema; otherwise, returns `false`
1257
+ *
1258
+ * See https://apitools.dev/json-schema-ref-parser/docs/refs.html#existsref
1259
+ *
1260
+ * @param $ref The JSON Reference path, optionally with a JSON Pointer in the hash
1261
+ */
1262
+ /**
1263
+ * Determines whether the given JSON reference exists.
1264
+ *
1265
+ * @param path - The path being resolved, optionally with a JSON pointer in the hash
1266
+ * @param [options]
1267
+ * @returns
1268
+ */
1269
+ exists(path, options) {
1270
+ try {
1271
+ this._resolve(path, "", options);
1272
+ return true;
1273
+ } catch {
1274
+ return false;
1275
+ }
1276
+ }
1277
+ /**
1278
+ * Resolves the given JSON reference and returns the resolved value.
1279
+ *
1280
+ * @param path - The path being resolved, with a JSON pointer in the hash
1281
+ * @param [options]
1282
+ * @returns - Returns the resolved value
1283
+ */
1284
+ get(path, options) {
1285
+ return this._resolve(path, "", options).value;
1286
+ }
1287
+ /**
1288
+ * Sets the value at the given path in the schema. If the property, or any of its parents, don't exist, they will be created.
1289
+ *
1290
+ * @param path The JSON Reference path, optionally with a JSON Pointer in the hash
1291
+ * @param value The value to assign. Can be anything (object, string, number, etc.)
1292
+ */
1293
+ set(path, value) {
1294
+ const absPath = resolve(this._root$Ref.path, path);
1295
+ const withoutHash = stripHash(absPath);
1296
+ const $ref = this._$refs[withoutHash];
1297
+ if (!$ref) throw ono(`Error resolving $ref pointer "${path}". \n"${withoutHash}" not found.`);
1298
+ $ref.set(absPath, value);
1299
+ }
1300
+ /**
1301
+ * Returns the specified {@link $Ref} object, or undefined.
1302
+ *
1303
+ * @param path - The path being resolved, optionally with a JSON pointer in the hash
1304
+ * @returns
1305
+ * @protected
1306
+ */
1307
+ _get$Ref(path) {
1308
+ path = resolve(this._root$Ref.path, path);
1309
+ const withoutHash = stripHash(path);
1310
+ return this._$refs[withoutHash];
1311
+ }
1312
+ /**
1313
+ * Creates a new {@link $Ref} object and adds it to this {@link $Refs} object.
1314
+ *
1315
+ * @param path - The file path or URL of the referenced file
1316
+ */
1317
+ _add(path) {
1318
+ const withoutHash = stripHash(path);
1319
+ const $ref = new ref_default(this);
1320
+ $ref.path = withoutHash;
1321
+ this._$refs[withoutHash] = $ref;
1322
+ this._root$Ref = this._root$Ref || $ref;
1323
+ return $ref;
1324
+ }
1325
+ /**
1326
+ * Resolves the given JSON reference.
1327
+ *
1328
+ * @param path - The path being resolved, optionally with a JSON pointer in the hash
1329
+ * @param pathFromRoot - The path of `obj` from the schema root
1330
+ * @param [options]
1331
+ * @returns
1332
+ * @protected
1333
+ */
1334
+ _resolve(path, pathFromRoot, options) {
1335
+ const absPath = resolve(this._root$Ref.path, path);
1336
+ const withoutHash = stripHash(absPath);
1337
+ const $ref = this._$refs[withoutHash];
1338
+ if (!$ref) throw ono(`Error resolving $ref pointer "${path}". \n"${withoutHash}" not found.`);
1339
+ if ($ref.value === void 0) {
1340
+ console.warn(`$ref entry exists but value is undefined: ${withoutHash}`);
1341
+ return null;
1342
+ }
1343
+ return $ref.resolve(absPath, options, path, pathFromRoot);
1344
+ }
1345
+ /**
1346
+ * A map of paths/urls to {@link $Ref} objects
1347
+ *
1348
+ * @type {object}
1349
+ * @protected
1350
+ */
1351
+ _$refs = {};
1352
+ /**
1353
+ * The {@link $Ref} object that is the root of the JSON schema.
1354
+ *
1355
+ * @type {$Ref}
1356
+ * @protected
1357
+ */
1358
+ _root$Ref;
1359
+ constructor() {
1360
+ /**
1361
+ * Indicates whether the schema contains any circular references.
1362
+ *
1363
+ * @type {boolean}
1364
+ */
1365
+ this.circular = false;
1366
+ this._$refs = {};
1367
+ this._root$Ref = null;
1368
+ }
1369
+ /**
1370
+ * Returns the paths of all the files/URLs that are referenced by the JSON schema,
1371
+ * including the schema itself.
1372
+ *
1373
+ * @param [types] - Only return paths of the given types ("file", "http", etc.)
1374
+ * @returns
1375
+ */
1376
+ /**
1377
+ * Returns the map of JSON references and their resolved values.
1378
+ *
1379
+ * @param [types] - Only return references of the given types ("file", "http", etc.)
1380
+ * @returns
1381
+ */
1382
+ /**
1383
+ * Returns a POJO (plain old JavaScript object) for serialization as JSON.
1384
+ *
1385
+ * @returns {object}
1386
+ */
1387
+ toJSON = this.values;
1388
+ };
1389
+ /**
1390
+ * Returns the encoded and decoded paths keys of the given object.
1391
+ *
1392
+ * @param $refs - The object whose keys are URL-encoded paths
1393
+ * @param [types] - Only return paths of the given types ("file", "http", etc.)
1394
+ * @returns
1395
+ */
1396
+ function getPaths($refs, types) {
1397
+ let paths = Object.keys($refs);
1398
+ types = Array.isArray(types[0]) ? types[0] : Array.prototype.slice.call(types);
1399
+ if (types.length > 0 && types[0]) paths = paths.filter((key) => types.includes($refs[key].pathType));
1400
+ return paths.map((path) => ({
1401
+ decoded: $refs[path].pathType === "file" ? toFileSystemPath(path, true) : path,
1402
+ encoded: path
1403
+ }));
1404
+ }
1405
+
1406
+ //#endregion
1407
+ //#region src/resolvers/file.ts
1408
+ const fileResolver = { handler: async ({ file }) => {
1409
+ let path;
1410
+ try {
1411
+ path = toFileSystemPath(file.url);
1412
+ } catch (error) {
1413
+ throw new ResolverError(ono.uri(error, `Malformed URI: ${file.url}`), file.url);
1414
+ }
1415
+ try {
1416
+ file.data = await fs.promises.readFile(path);
1417
+ } catch (error) {
1418
+ throw new ResolverError(ono(error, `Error opening file "${path}"`), path);
1419
+ }
1420
+ } };
1421
+
1422
+ //#endregion
1423
+ //#region src/resolvers/url.ts
1424
+ const sendRequest = async ({ fetchOptions, redirects = [], timeout = 6e4, url }) => {
1425
+ url = new URL(url);
1426
+ redirects.push(url.href);
1427
+ const controller = new AbortController();
1428
+ const timeoutId = setTimeout(() => {
1429
+ controller.abort();
1430
+ }, timeout);
1431
+ const response = await fetch(url, {
1432
+ signal: controller.signal,
1433
+ ...fetchOptions
1434
+ });
1435
+ clearTimeout(timeoutId);
1436
+ if (response.status >= 300 && response.status <= 399) {
1437
+ if (redirects.length > 5) throw new ResolverError(ono({ status: response.status }, `Error requesting ${redirects[0]}. \nToo many redirects: \n ${redirects.join(" \n ")}`));
1438
+ if (!("location" in response.headers) || !response.headers.location) throw ono({ status: response.status }, `HTTP ${response.status} redirect with no location header`);
1439
+ return sendRequest({
1440
+ fetchOptions,
1441
+ redirects,
1442
+ timeout,
1443
+ url: resolve(url.href, response.headers.location)
1444
+ });
1445
+ }
1446
+ return {
1447
+ fetchOptions,
1448
+ response
1449
+ };
1450
+ };
1451
+ const urlResolver = { handler: async ({ arrayBuffer, fetch: _fetch, file }) => {
1452
+ let data = arrayBuffer;
1453
+ if (!data) try {
1454
+ const { fetchOptions, response } = await sendRequest({
1455
+ fetchOptions: {
1456
+ method: "GET",
1457
+ ..._fetch
1458
+ },
1459
+ url: file.url
1460
+ });
1461
+ if (response.status >= 400) {
1462
+ if (response.status !== 405 || fetchOptions?.method !== "HEAD") throw ono({ status: response.status }, `HTTP ERROR ${response.status}`);
1463
+ }
1464
+ data = response.body ? await response.arrayBuffer() : /* @__PURE__ */ new ArrayBuffer(0);
1465
+ } catch (error) {
1466
+ throw new ResolverError(ono(error, `Error requesting ${file.url}`), file.url);
1467
+ }
1468
+ file.data = Buffer.from(data);
1469
+ } };
1470
+
1471
+ //#endregion
1472
+ //#region src/resolve-external.ts
1473
+ /**
1474
+ * Crawls the JSON schema, finds all external JSON references, and resolves their values.
1475
+ * This method does not mutate the JSON schema. The resolved values are added to {@link $RefParser#$refs}.
1476
+ *
1477
+ * NOTE: We only care about EXTERNAL references here. INTERNAL references are only relevant when dereferencing.
1478
+ *
1479
+ * @returns
1480
+ * The promise resolves once all JSON references in the schema have been resolved,
1481
+ * including nested references that are contained in externally-referenced files.
1482
+ */
1483
+ async function resolveExternal(parser, options) {
1484
+ const promises = crawl(parser.schema, {
1485
+ $refs: parser.$refs,
1486
+ options: options.parse,
1487
+ path: `${parser.$refs._root$Ref.path}#`
1488
+ });
1489
+ await Promise.all(promises);
1490
+ }
1491
+ /**
1492
+ * Recursively crawls the given value, and resolves any external JSON references.
1493
+ *
1494
+ * @param obj - The value to crawl. If it's not an object or array, it will be ignored.
1495
+ * @returns An array of promises. There will be one promise for each JSON reference in `obj`.
1496
+ * If `obj` does not contain any JSON references, then the array will be empty.
1497
+ * If any of the JSON references point to files that contain additional JSON references,
1498
+ * then the corresponding promise will internally reference an array of promises.
1499
+ */
1500
+ function crawl(obj, { $refs, external = false, options, path, seen = /* @__PURE__ */ new Set() }) {
1501
+ let promises = [];
1502
+ if (obj && typeof obj === "object" && !ArrayBuffer.isView(obj) && !seen.has(obj)) {
1503
+ seen.add(obj);
1504
+ if (ref_default.isExternal$Ref(obj)) promises.push(resolve$Ref(obj, {
1505
+ $refs,
1506
+ options,
1507
+ path,
1508
+ seen
1509
+ }));
1510
+ for (const [key, value] of Object.entries(obj)) promises = promises.concat(crawl(value, {
1511
+ $refs,
1512
+ external,
1513
+ options,
1514
+ path: pointer_default.join(path, key),
1515
+ seen
1516
+ }));
1517
+ }
1518
+ return promises;
1519
+ }
1520
+ /**
1521
+ * Resolves the given JSON Reference, and then crawls the resulting value.
1522
+ *
1523
+ * @param $ref - The JSON Reference to resolve
1524
+ * @param path - The full path of `$ref`, possibly with a JSON Pointer in the hash
1525
+ * @param $refs
1526
+ * @param options
1527
+ *
1528
+ * @returns
1529
+ * The promise resolves once all JSON references in the object have been resolved,
1530
+ * including nested references that are contained in externally-referenced files.
1531
+ */
1532
+ async function resolve$Ref($ref, { $refs, options, path, seen }) {
1533
+ const resolvedPath = resolve(path, $ref.$ref);
1534
+ const withoutHash = stripHash(resolvedPath);
1535
+ const ref = $refs._$refs[withoutHash];
1536
+ if (ref) {
1537
+ const promises = crawl(ref.value, {
1538
+ $refs,
1539
+ external: true,
1540
+ options,
1541
+ path: `${withoutHash}#`,
1542
+ seen
1543
+ });
1544
+ return Promise.all(promises);
1545
+ }
1546
+ const file = newFile(resolvedPath);
1547
+ const $refAdded = $refs._add(file.url);
1548
+ try {
1549
+ const resolvedInput = getResolvedInput({ pathOrUrlOrSchema: resolvedPath });
1550
+ $refAdded.pathType = resolvedInput.type;
1551
+ let promises = [];
1552
+ if (resolvedInput.type !== "json") {
1553
+ await (resolvedInput.type === "file" ? fileResolver : urlResolver).handler({ file });
1554
+ const parseResult = await parseFile(file, options);
1555
+ $refAdded.value = parseResult.result;
1556
+ promises = crawl(parseResult.result, {
1557
+ $refs,
1558
+ external: true,
1559
+ options,
1560
+ path: `${withoutHash}#`,
1561
+ seen
1562
+ });
1563
+ }
1564
+ return Promise.all(promises);
1565
+ } catch (error) {
1566
+ if (isHandledError(error)) $refAdded.value = error;
1567
+ throw error;
1568
+ }
1569
+ }
1570
+
1571
+ //#endregion
1572
+ //#region src/index.ts
1573
+ function getResolvedInput({ pathOrUrlOrSchema }) {
1574
+ if (!pathOrUrlOrSchema) throw ono(`Expected a file path, URL, or object. Got ${pathOrUrlOrSchema}`);
1575
+ const resolvedInput = {
1576
+ path: typeof pathOrUrlOrSchema === "string" ? pathOrUrlOrSchema : "",
1577
+ schema: void 0,
1578
+ type: "url"
1579
+ };
1580
+ if (resolvedInput.path && isFileSystemPath(resolvedInput.path)) {
1581
+ resolvedInput.path = fromFileSystemPath(resolvedInput.path);
1582
+ resolvedInput.type = "file";
1583
+ } else if (!resolvedInput.path && pathOrUrlOrSchema && typeof pathOrUrlOrSchema === "object") if ("$id" in pathOrUrlOrSchema && pathOrUrlOrSchema.$id) {
1584
+ const { hostname, protocol } = new URL(pathOrUrlOrSchema.$id);
1585
+ resolvedInput.path = `${protocol}//${hostname}:${protocol === "https:" ? 443 : 80}`;
1586
+ resolvedInput.type = "url";
1587
+ } else {
1588
+ resolvedInput.schema = pathOrUrlOrSchema;
1589
+ resolvedInput.type = "json";
1590
+ }
1591
+ if (resolvedInput.type !== "json") resolvedInput.path = resolve(cwd(), resolvedInput.path);
1592
+ return resolvedInput;
1593
+ }
1594
+ /**
1595
+ * This class parses a JSON schema, builds a map of its JSON references and their resolved values,
1596
+ * and provides methods for traversing, manipulating, and dereferencing those references.
1597
+ */
1598
+ var $RefParser = class {
1599
+ /**
1600
+ * The resolved JSON references
1601
+ *
1602
+ * @type {$Refs}
1603
+ * @readonly
1604
+ */
1605
+ $refs = new $Refs();
1606
+ options = getJsonSchemaRefParserDefaultOptions();
1607
+ /**
1608
+ * The parsed (and possibly dereferenced) JSON schema object
1609
+ *
1610
+ * @type {object}
1611
+ * @readonly
1612
+ */
1613
+ schema = null;
1614
+ schemaMany = [];
1615
+ schemaManySources = [];
1616
+ sourcePathToPrefix = /* @__PURE__ */ new Map();
1617
+ /**
1618
+ * Bundles all referenced files/URLs into a single schema that only has internal `$ref` pointers. This lets you split-up your schema however you want while you're building it, but easily combine all those files together when it's time to package or distribute the schema to other people. The resulting schema size will be small, since it will still contain internal JSON references rather than being fully-dereferenced.
1619
+ *
1620
+ * This also eliminates the risk of circular references, so the schema can be safely serialized using `JSON.stringify()`.
1621
+ *
1622
+ * See https://apitools.dev/json-schema-ref-parser/docs/ref-parser.html#bundleschema-options-callback
1623
+ *
1624
+ * @param pathOrUrlOrSchema A JSON Schema object, or the file path or URL of a JSON Schema file.
1625
+ */
1626
+ async bundle({ arrayBuffer, fetch: fetch$1, pathOrUrlOrSchema, resolvedInput }) {
1627
+ await this.parse({
1628
+ arrayBuffer,
1629
+ fetch: fetch$1,
1630
+ pathOrUrlOrSchema,
1631
+ resolvedInput
1632
+ });
1633
+ await resolveExternal(this, this.options);
1634
+ if (JSONParserErrorGroup.getParserErrors(this).length > 0) throw new JSONParserErrorGroup(this);
1635
+ bundle(this, this.options);
1636
+ if (JSONParserErrorGroup.getParserErrors(this).length > 0) throw new JSONParserErrorGroup(this);
1637
+ return this.schema;
1638
+ }
1639
+ /**
1640
+ * Bundles multiple roots (files/URLs/objects) into a single schema by creating a synthetic root
1641
+ * that references each input, resolving all externals, and then hoisting via the existing bundler.
1642
+ */
1643
+ async bundleMany({ arrayBuffer, fetch: fetch$1, pathOrUrlOrSchemas, resolvedInputs }) {
1644
+ await this.parseMany({
1645
+ arrayBuffer,
1646
+ fetch: fetch$1,
1647
+ pathOrUrlOrSchemas,
1648
+ resolvedInputs
1649
+ });
1650
+ this.mergeMany();
1651
+ await resolveExternal(this, this.options);
1652
+ if (JSONParserErrorGroup.getParserErrors(this).length > 0) throw new JSONParserErrorGroup(this);
1653
+ bundle(this, this.options);
1654
+ if (JSONParserErrorGroup.getParserErrors(this).length > 0) throw new JSONParserErrorGroup(this);
1655
+ return this.schema;
1656
+ }
1657
+ /**
1658
+ * Parses the given JSON schema.
1659
+ * This method does not resolve any JSON references.
1660
+ * It just reads a single file in JSON or YAML format, and parse it as a JavaScript object.
1661
+ *
1662
+ * @param pathOrUrlOrSchema A JSON Schema object, or the file path or URL of a JSON Schema file.
1663
+ * @returns - The returned promise resolves with the parsed JSON schema object.
1664
+ */
1665
+ async parse({ arrayBuffer, fetch: fetch$1, pathOrUrlOrSchema, resolvedInput: _resolvedInput }) {
1666
+ const resolvedInput = _resolvedInput || getResolvedInput({ pathOrUrlOrSchema });
1667
+ const { path, type } = resolvedInput;
1668
+ let { schema } = resolvedInput;
1669
+ this.schema = null;
1670
+ this.$refs = new $Refs();
1671
+ if (schema) {
1672
+ const $ref = this.$refs._add(path);
1673
+ $ref.pathType = isFileSystemPath(path) ? "file" : "http";
1674
+ $ref.value = schema;
1675
+ } else if (type !== "json") {
1676
+ const file = newFile(path);
1677
+ const $refAdded = this.$refs._add(file.url);
1678
+ $refAdded.pathType = type;
1679
+ try {
1680
+ await (type === "file" ? fileResolver : urlResolver).handler({
1681
+ arrayBuffer,
1682
+ fetch: fetch$1,
1683
+ file
1684
+ });
1685
+ const parseResult = await parseFile(file, this.options.parse);
1686
+ $refAdded.value = parseResult.result;
1687
+ schema = parseResult.result;
1688
+ } catch (error) {
1689
+ if (isHandledError(error)) $refAdded.value = error;
1690
+ throw error;
1691
+ }
1692
+ }
1693
+ if (schema === null || typeof schema !== "object" || Buffer.isBuffer(schema)) throw ono.syntax(`"${this.$refs._root$Ref.path || schema}" is not a valid JSON Schema`);
1694
+ this.schema = schema;
1695
+ return { schema };
1696
+ }
1697
+ async parseMany({ arrayBuffer, fetch: fetch$1, pathOrUrlOrSchemas, resolvedInputs: _resolvedInputs }) {
1698
+ const resolvedInputs = [..._resolvedInputs || []];
1699
+ resolvedInputs.push(...pathOrUrlOrSchemas.map((schema) => getResolvedInput({ pathOrUrlOrSchema: schema })) || []);
1700
+ this.schemaMany = [];
1701
+ this.schemaManySources = [];
1702
+ this.sourcePathToPrefix = /* @__PURE__ */ new Map();
1703
+ for (let i = 0; i < resolvedInputs.length; i++) {
1704
+ const resolvedInput = resolvedInputs[i];
1705
+ const { path, type } = resolvedInput;
1706
+ let { schema } = resolvedInput;
1707
+ if (schema) {} else if (type !== "json") {
1708
+ const file = newFile(path);
1709
+ const $refAdded = this.$refs._add(file.url);
1710
+ $refAdded.pathType = type;
1711
+ try {
1712
+ await (type === "file" ? fileResolver : urlResolver).handler({
1713
+ arrayBuffer: arrayBuffer?.[i],
1714
+ fetch: fetch$1,
1715
+ file
1716
+ });
1717
+ const parseResult = await parseFile(file, this.options.parse);
1718
+ $refAdded.value = parseResult.result;
1719
+ schema = parseResult.result;
1720
+ } catch (error) {
1721
+ if (isHandledError(error)) $refAdded.value = error;
1722
+ throw error;
1723
+ }
1724
+ }
1725
+ if (schema === null || typeof schema !== "object" || Buffer.isBuffer(schema)) throw ono.syntax(`"${this.$refs._root$Ref.path || schema}" is not a valid JSON Schema`);
1726
+ this.schemaMany.push(schema);
1727
+ this.schemaManySources.push(path && path.length ? path : cwd());
1728
+ }
1729
+ return { schemaMany: this.schemaMany };
1730
+ }
1731
+ mergeMany() {
1732
+ const schemas = this.schemaMany || [];
1733
+ if (schemas.length === 0) throw ono("mergeMany called with no schemas. Did you run parseMany?");
1734
+ const merged = {};
1735
+ let chosenOpenapi;
1736
+ let chosenSwagger;
1737
+ for (const s of schemas) {
1738
+ if (!chosenOpenapi && s && typeof s.openapi === "string") chosenOpenapi = s.openapi;
1739
+ if (!chosenSwagger && s && typeof s.swagger === "string") chosenSwagger = s.swagger;
1740
+ if (chosenOpenapi && chosenSwagger) break;
1741
+ }
1742
+ if (typeof chosenOpenapi === "string") merged.openapi = chosenOpenapi;
1743
+ else if (typeof chosenSwagger === "string") merged.swagger = chosenSwagger;
1744
+ const infoAccumulator = {};
1745
+ for (const s of schemas) {
1746
+ const info = s?.info;
1747
+ if (info && typeof info === "object") {
1748
+ for (const [k, v] of Object.entries(info)) if (infoAccumulator[k] === void 0 && v !== void 0) infoAccumulator[k] = JSON.parse(JSON.stringify(v));
1749
+ }
1750
+ }
1751
+ if (Object.keys(infoAccumulator).length > 0) merged.info = infoAccumulator;
1752
+ const servers = [];
1753
+ const seenServers = /* @__PURE__ */ new Set();
1754
+ for (const s of schemas) {
1755
+ const arr = s?.servers;
1756
+ if (Array.isArray(arr)) {
1757
+ for (const srv of arr) if (srv && typeof srv === "object") {
1758
+ const key = `${srv.url || ""}|${srv.description || ""}`;
1759
+ if (!seenServers.has(key)) {
1760
+ seenServers.add(key);
1761
+ servers.push(JSON.parse(JSON.stringify(srv)));
1762
+ }
1763
+ }
1764
+ }
1765
+ }
1766
+ if (servers.length > 0) merged.servers = servers;
1767
+ merged.paths = {};
1768
+ merged.components = {};
1769
+ const componentSections = [
1770
+ "schemas",
1771
+ "parameters",
1772
+ "requestBodies",
1773
+ "responses",
1774
+ "headers",
1775
+ "securitySchemes",
1776
+ "examples",
1777
+ "links",
1778
+ "callbacks"
1779
+ ];
1780
+ for (const sec of componentSections) merged.components[sec] = {};
1781
+ const tagNameSet = /* @__PURE__ */ new Set();
1782
+ const tags = [];
1783
+ const usedOpIds = /* @__PURE__ */ new Set();
1784
+ const baseName = (p) => {
1785
+ try {
1786
+ const parts = p.split("#")[0].split("/");
1787
+ const filename = parts[parts.length - 1] || "schema";
1788
+ const dot = filename.lastIndexOf(".");
1789
+ return (dot > 0 ? filename.substring(0, dot) : filename).replace(/[^A-Za-z0-9_-]/g, "_");
1790
+ } catch {
1791
+ return "schema";
1792
+ }
1793
+ };
1794
+ const unique = (set, proposed) => {
1795
+ let name = proposed;
1796
+ let i = 2;
1797
+ while (set.has(name)) name = `${proposed}_${i++}`;
1798
+ set.add(name);
1799
+ return name;
1800
+ };
1801
+ const rewriteRef = (ref, refMap) => {
1802
+ let m = ref.match(/^#\/components\/([^/]+)\/([^/]+)(.*)$/);
1803
+ if (m) {
1804
+ const base = `#/components/${m[1]}/${m[2]}`;
1805
+ const mapped = refMap.get(base);
1806
+ if (mapped) return mapped + (m[3] || "");
1807
+ }
1808
+ m = ref.match(/^#\/definitions\/([^/]+)(.*)$/);
1809
+ if (m) {
1810
+ const base = `#/components/schemas/${m[1]}`;
1811
+ const mapped = refMap.get(base);
1812
+ if (mapped) return mapped + (m[2] || "");
1813
+ }
1814
+ return ref;
1815
+ };
1816
+ const cloneAndRewrite = (obj, refMap, tagMap, opIdPrefix, basePath) => {
1817
+ if (obj === null || obj === void 0) return obj;
1818
+ if (Array.isArray(obj)) return obj.map((v) => cloneAndRewrite(v, refMap, tagMap, opIdPrefix, basePath));
1819
+ if (typeof obj !== "object") return obj;
1820
+ const out = {};
1821
+ for (const [k, v] of Object.entries(obj)) if (k === "$ref" && typeof v === "string") {
1822
+ const s = v;
1823
+ if (s.startsWith("#")) out[k] = rewriteRef(s, refMap);
1824
+ else if (getProtocol(s) === void 0) out[k] = resolve(basePath + "#", s);
1825
+ else out[k] = s;
1826
+ } else if (k === "tags" && Array.isArray(v) && v.every((x) => typeof x === "string")) out[k] = v.map((t) => tagMap.get(t) || t);
1827
+ else if (k === "operationId" && typeof v === "string") out[k] = unique(usedOpIds, `${opIdPrefix}_${v}`);
1828
+ else out[k] = cloneAndRewrite(v, refMap, tagMap, opIdPrefix, basePath);
1829
+ return out;
1830
+ };
1831
+ for (let i = 0; i < schemas.length; i++) {
1832
+ const schema = schemas[i] || {};
1833
+ const sourcePath = this.schemaManySources[i] || `multi://input/${i + 1}`;
1834
+ const prefix = baseName(sourcePath);
1835
+ const withoutHash = stripHash(sourcePath);
1836
+ const protocol = getProtocol(withoutHash);
1837
+ if (protocol === void 0 || protocol === "file" || protocol === "http" || protocol === "https") this.sourcePathToPrefix.set(withoutHash, prefix);
1838
+ const refMap = /* @__PURE__ */ new Map();
1839
+ const tagMap = /* @__PURE__ */ new Map();
1840
+ const srcComponents = schema.components || {};
1841
+ for (const sec of componentSections) {
1842
+ const group = srcComponents[sec] || {};
1843
+ for (const [name] of Object.entries(group)) {
1844
+ const newName = `${prefix}_${name}`;
1845
+ refMap.set(`#/components/${sec}/${name}`, `#/components/${sec}/${newName}`);
1846
+ }
1847
+ }
1848
+ const srcTags = Array.isArray(schema.tags) ? schema.tags : [];
1849
+ for (const t of srcTags) {
1850
+ if (!t || typeof t !== "object" || typeof t.name !== "string") continue;
1851
+ const desired = t.name;
1852
+ const finalName = tagNameSet.has(desired) ? `${prefix}_${desired}` : desired;
1853
+ tagNameSet.add(finalName);
1854
+ tagMap.set(desired, finalName);
1855
+ if (!tags.find((x) => x && x.name === finalName)) tags.push({
1856
+ ...t,
1857
+ name: finalName
1858
+ });
1859
+ }
1860
+ for (const sec of componentSections) {
1861
+ const group = schema.components && schema.components[sec] || {};
1862
+ for (const [name, val] of Object.entries(group)) {
1863
+ const newName = `${prefix}_${name}`;
1864
+ merged.components[sec][newName] = cloneAndRewrite(val, refMap, tagMap, prefix, stripHash(sourcePath));
1865
+ }
1866
+ }
1867
+ const srcPaths = schema.paths || {};
1868
+ for (const [p, item] of Object.entries(srcPaths)) {
1869
+ let targetPath = p;
1870
+ if (merged.paths[p]) targetPath = `/${prefix}/${p.startsWith("/") ? p.substring(1) : p}`;
1871
+ merged.paths[targetPath] = cloneAndRewrite(item, refMap, tagMap, prefix, stripHash(sourcePath));
1872
+ }
1873
+ }
1874
+ if (tags.length > 0) merged.tags = tags;
1875
+ const rootPath = this.schemaManySources[0] || cwd();
1876
+ this.$refs = new $Refs();
1877
+ const rootRef = this.$refs._add(rootPath);
1878
+ rootRef.pathType = isFileSystemPath(rootPath) ? "file" : "http";
1879
+ rootRef.value = merged;
1880
+ this.schema = merged;
1881
+ return merged;
1882
+ }
1883
+ };
1884
+
1885
+ //#endregion
1886
+ export { $RefParser, getResolvedInput, sendRequest };
1887
+ //# sourceMappingURL=index.mjs.map