@scalar/openapi-parser 0.10.14 → 0.10.15

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 (34) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +22 -0
  3. package/dist/lib/Validator/Validator.js +1 -1
  4. package/dist/plugins/fetch-urls/fetchUrls.d.ts +1 -0
  5. package/dist/plugins/fetch-urls/fetchUrls.d.ts.map +1 -1
  6. package/dist/plugins/fetch-urls/fetchUrls.js +79 -7
  7. package/dist/plugins/fetch-urls/index.d.ts +1 -1
  8. package/dist/plugins/fetch-urls/index.d.ts.map +1 -1
  9. package/dist/plugins/fetch-urls/index.js +1 -1
  10. package/dist/plugins/read-files/index.d.ts +1 -1
  11. package/dist/plugins/read-files/index.d.ts.map +1 -1
  12. package/dist/plugins/read-files/readFiles.d.ts.map +1 -1
  13. package/dist/plugins/read-files/readFiles.js +18 -3
  14. package/dist/types/index.d.ts +23 -4
  15. package/dist/types/index.d.ts.map +1 -1
  16. package/dist/utils/dereference.js +2 -2
  17. package/dist/utils/filter.js +1 -1
  18. package/dist/utils/isFilesystem.d.ts +2 -1
  19. package/dist/utils/isFilesystem.d.ts.map +1 -1
  20. package/dist/utils/load/load.d.ts +11 -2
  21. package/dist/utils/load/load.d.ts.map +1 -1
  22. package/dist/utils/load/load.js +77 -38
  23. package/dist/utils/makeFilesystem.d.ts +18 -1
  24. package/dist/utils/makeFilesystem.d.ts.map +1 -1
  25. package/dist/utils/makeFilesystem.js +55 -9
  26. package/dist/utils/openapi/actions/details.js +1 -1
  27. package/dist/utils/openapi/actions/toJson.js +1 -1
  28. package/dist/utils/openapi/actions/toYaml.js +1 -1
  29. package/dist/utils/openapi/commands/loadCommand.d.ts +180 -180
  30. package/dist/utils/resolveReferences.d.ts +6 -0
  31. package/dist/utils/resolveReferences.d.ts.map +1 -1
  32. package/dist/utils/resolveReferences.js +19 -9
  33. package/dist/utils/upgrade.js +1 -1
  34. package/package.json +4 -3
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # @scalar/openapi-parser
2
2
 
3
+ ## 0.10.15
4
+
5
+ ### Patch Changes
6
+
7
+ - 4abe4f8: feat: add resolveInternalRefs to bundle documents
8
+ - 4abe4f8: feat: external $ref’s (absolute and relative URLs)
9
+
3
10
  ## 0.10.14
4
11
 
5
12
  ### Patch Changes
package/README.md CHANGED
@@ -239,11 +239,33 @@ import { fetchUrls } from '@scalar/openapi-parser/plugins/fetch-urls'
239
239
  const { filesystem } = await load(
240
240
  'https://cdn.jsdelivr.net/npm/@scalar/galaxy/dist/latest.yaml',
241
241
  {
242
+ // Optional: If you directly pass an OpenAPI (not an URL), let it know where the content came from
243
+ // That’s the only way we can resolve external references, if they are relative.
244
+ source: 'https://cdn.jsdelivr.net/npm/@scalar/galaxy/dist/latest.yaml'
242
245
  plugins: [fetchUrls()],
243
246
  },
244
247
  )
245
248
  ```
246
249
 
250
+ #### Bundle external references
251
+
252
+ By default, `dereference` tries to resolve both internal and external references. You can disable resolving internal references by setting `resolveInternalRefs` to `false`:
253
+
254
+ ```ts
255
+ import { dereference, load } from '@scalar/openapi-parser'
256
+ import { fetchUrls } from '@scalar/openapi-parser/plugins/fetch-urls'
257
+
258
+ // Load a file and all referenced files
259
+ const { filesystem } = await load('https://cdn.jsdelivr.net/npm/@scalar/galaxy/dist/latest.yaml', {
260
+ plugins: [fetchUrls()],
261
+ })
262
+
263
+ const result = await dereference(filesystem, {
264
+ // Skips internal references like `$ref: '#/components/schemas/foobar'`
265
+ resolveInternalRefs: false,
266
+ })
267
+ ```
268
+
247
269
  #### Intercept HTTP requests
248
270
 
249
271
  If you’re using the package in a browser environment, you may run into CORS issues when fetching from URLs. You can intercept the requests, for example to use a proxy, though:
@@ -27,7 +27,7 @@ class Validator {
27
27
  */
28
28
  async validate(filesystem, options) {
29
29
  const entrypoint = filesystem.find((file) => file.isEntrypoint);
30
- const specification = entrypoint?.specification;
30
+ const specification = entrypoint?.content;
31
31
  // TODO: How does this work with a filesystem?
32
32
  this.specification = specification;
33
33
  // TODO: defaulting info.version to keep parser compatible with the previous one
@@ -12,4 +12,5 @@ export declare const fetchUrls: (customConfiguration?: {
12
12
  */
13
13
  fetch?: (url: string) => Promise<Response>;
14
14
  }) => LoadPlugin;
15
+ export declare function getAbsoluteUrl(value: string, source?: unknown): string;
15
16
  //# sourceMappingURL=fetchUrls.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"fetchUrls.d.ts","sourceRoot":"","sources":["../../../src/plugins/fetch-urls/fetchUrls.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AAE1D,eAAO,MAAM,6BAA6B;;CAEzC,CAAA;AAED,eAAO,MAAM,SAAS,EAAE,CAAC,mBAAmB,CAAC,EAAE;IAC7C;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;IACtB;;OAEG;IACH,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;CAC3C,KAAK,UA2CL,CAAA"}
1
+ {"version":3,"file":"fetchUrls.d.ts","sourceRoot":"","sources":["../../../src/plugins/fetch-urls/fetchUrls.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AAE1D,eAAO,MAAM,6BAA6B;;CAEzC,CAAA;AAED,eAAO,MAAM,SAAS,EAAE,CAAC,mBAAmB,CAAC,EAAE;IAC7C;;OAEG;IACH,KAAK,CAAC,EAAE,MAAM,GAAG,KAAK,CAAA;IACtB;;OAEG;IACH,KAAK,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;CAC3C,KAAK,UAqDL,CAAA;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,UAyE7D"}
@@ -1,5 +1,5 @@
1
1
  const fetchUrlsDefaultConfiguration = {
2
- limit: 20,
2
+ limit: 100,
3
3
  };
4
4
  const fetchUrls = (customConfiguration) => {
5
5
  // State
@@ -10,18 +10,24 @@ const fetchUrls = (customConfiguration) => {
10
10
  ...customConfiguration,
11
11
  };
12
12
  return {
13
+ // Make it run after readFiles
14
+ priority: 0,
13
15
  check(value) {
14
16
  // Not a string
15
17
  if (typeof value !== 'string') {
16
18
  return false;
17
19
  }
18
- // Not http/https
19
- if (!value.startsWith('http://') && !value.startsWith('https://')) {
20
+ // Seems to be JSON
21
+ if (value.startsWith('{') || value.startsWith('[')) {
22
+ return false;
23
+ }
24
+ // Seems to be YAML
25
+ if (value.includes('\n')) {
20
26
  return false;
21
27
  }
22
28
  return true;
23
29
  },
24
- async get(value) {
30
+ async get(url) {
25
31
  // Limit ht enumber of requests
26
32
  if (configuration?.limit !== false && numberOfRequests >= configuration?.limit) {
27
33
  console.warn(`[fetchUrls] Maximum number of requests reeached (${configuration?.limit}), skipping request`);
@@ -29,15 +35,81 @@ const fetchUrls = (customConfiguration) => {
29
35
  }
30
36
  try {
31
37
  numberOfRequests++;
32
- const response = await (configuration?.fetch ? configuration.fetch(value) : fetch(value));
38
+ const response = await (configuration?.fetch ? configuration.fetch(url) : fetch(url));
33
39
  return await response.text();
34
40
  }
35
41
  catch (error) {
36
- console.error('[fetchUrls]', error.message, `(${value})`);
42
+ console.error('[fetchUrls]', error.message, `(${url})`);
37
43
  return undefined;
38
44
  }
39
45
  },
46
+ getUri(value, source) {
47
+ return getAbsoluteUrl(value, source);
48
+ },
40
49
  };
41
50
  };
51
+ function getAbsoluteUrl(value, source) {
52
+ if (typeof source !== 'string') {
53
+ return value;
54
+ }
55
+ // Already an absolute URL
56
+ if (value.startsWith('http')) {
57
+ return value;
58
+ }
59
+ // Make it an absolute URL
60
+ if (source) {
61
+ // Skip if source or value is JSON/YAML content
62
+ if (source.startsWith('{') ||
63
+ source.startsWith('[') ||
64
+ source.includes('\n') ||
65
+ value.startsWith('{') ||
66
+ value.startsWith('[') ||
67
+ value.includes('\n')) {
68
+ return value;
69
+ }
70
+ try {
71
+ // If source is an HTTP URL, handle both absolute and relative paths
72
+ if (source.startsWith('http')) {
73
+ const sourceUrl = new URL(source);
74
+ // If value starts with /, treat it as relative to the host root
75
+ if (value.startsWith('/')) {
76
+ // Simply combine the origin with the absolute path
77
+ return `${sourceUrl.origin}${value}`;
78
+ }
79
+ // Otherwise treat it as relative to the source URL's directory
80
+ return new URL(value, sourceUrl.href).toString();
81
+ }
82
+ // Handle file system paths
83
+ if (source.startsWith('/') || !source.startsWith('http')) {
84
+ const sourceParts = source.split('/').filter(Boolean).slice(0, -1);
85
+ const valueParts = value.split('/').filter(Boolean);
86
+ const isAbsolute = source.startsWith('/');
87
+ // If value is absolute path, start fresh
88
+ if (value.startsWith('/')) {
89
+ return value;
90
+ }
91
+ // Process each part of the path
92
+ const resultParts = [...sourceParts];
93
+ for (const part of valueParts) {
94
+ if (part === '..') {
95
+ resultParts.pop();
96
+ }
97
+ else if (part !== '.') {
98
+ resultParts.push(part);
99
+ }
100
+ }
101
+ // Preserve the leading slash for absolute paths, but avoid double slashes
102
+ return isAbsolute ? '/' + resultParts.join('/') : resultParts.join('/');
103
+ }
104
+ return new URL(value, source).toString();
105
+ }
106
+ catch (error) {
107
+ console.warn('[fetchUrls] Failed to resolve URL:', error.message, `(${value})`);
108
+ return value;
109
+ }
110
+ }
111
+ // Otherwise, return the value as is
112
+ return value;
113
+ }
42
114
 
43
- export { fetchUrls, fetchUrlsDefaultConfiguration };
115
+ export { fetchUrls, fetchUrlsDefaultConfiguration, getAbsoluteUrl };
@@ -1,2 +1,2 @@
1
- export * from './fetchUrls.ts';
1
+ export { fetchUrlsDefaultConfiguration, fetchUrls, getAbsoluteUrl, } from './fetchUrls.ts';
2
2
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/plugins/fetch-urls/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/plugins/fetch-urls/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,6BAA6B,EAC7B,SAAS,EACT,cAAc,GACf,MAAM,gBAAgB,CAAA"}
@@ -1 +1 @@
1
- export { fetchUrls, fetchUrlsDefaultConfiguration } from './fetchUrls.js';
1
+ export { fetchUrls, fetchUrlsDefaultConfiguration, getAbsoluteUrl } from './fetchUrls.js';
@@ -1,2 +1,2 @@
1
- export * from './readFiles.ts';
1
+ export { readFiles } from './readFiles.ts';
2
2
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/plugins/read-files/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/plugins/read-files/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"readFiles.d.ts","sourceRoot":"","sources":["../../../src/plugins/read-files/readFiles.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AAE1D,eAAO,MAAM,SAAS,EAAE,MAAM,UAqD7B,CAAA"}
1
+ {"version":3,"file":"readFiles.d.ts","sourceRoot":"","sources":["../../../src/plugins/read-files/readFiles.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AAE1D,eAAO,MAAM,SAAS,EAAE,MAAM,UAyE7B,CAAA"}
@@ -6,6 +6,8 @@ import { isYaml } from '../../utils/isYaml.js';
6
6
 
7
7
  const readFiles = () => {
8
8
  return {
9
+ // Make it run before fetchUrls
10
+ priority: 100,
9
11
  check(value) {
10
12
  // Not a string
11
13
  if (typeof value !== 'string') {
@@ -30,6 +32,9 @@ const readFiles = () => {
30
32
  return true;
31
33
  },
32
34
  async get(value) {
35
+ if (typeof value !== 'string') {
36
+ return false;
37
+ }
33
38
  if (!fs.existsSync(value)) {
34
39
  throw new Error(ERRORS.FILE_DOES_NOT_EXIST.replace('%s', value));
35
40
  }
@@ -41,9 +46,19 @@ const readFiles = () => {
41
46
  return false;
42
47
  }
43
48
  },
44
- resolvePath(value, reference) {
45
- const dir = dirname(value);
46
- return join(dir, reference);
49
+ getUri(value, source) {
50
+ if (typeof value !== 'string') {
51
+ return undefined;
52
+ }
53
+ if (typeof source !== 'string') {
54
+ return value;
55
+ }
56
+ // Already absolute
57
+ if (value.startsWith('/')) {
58
+ return value;
59
+ }
60
+ const dir = dirname(source);
61
+ return join(dir, value);
47
62
  },
48
63
  getDir(value) {
49
64
  return dirname(value);
@@ -55,11 +55,30 @@ export type Filesystem = FilesystemEntry[];
55
55
  * Holds all information about a single file (doesn’t have to be a literal file, see Filesystem).
56
56
  */
57
57
  export type FilesystemEntry = {
58
- dir: string;
58
+ /**
59
+ * The absolute path or URL to the file.
60
+ *
61
+ * @example https://example.com/path/to/file.yaml
62
+ * @example /Users/username/project/path/to/file.yaml
63
+ */
64
+ uri: string | null;
65
+ /**
66
+ * Whether this is the base document, everything else is just referenced by it.
67
+ */
59
68
  isEntrypoint: boolean;
60
- references: string[];
61
- filename: string;
62
- specification: AnyObject;
69
+ /**
70
+ * A map of the references to other files.
71
+ *
72
+ * @example {
73
+ * 'file.yaml': 'https://example.com/folder/file.yaml',
74
+ * '../file2.yaml': 'https://example.com/file2.yaml',
75
+ * }
76
+ */
77
+ references: Record<string, string>;
78
+ /**
79
+ * The actual content of the document
80
+ */
81
+ content: AnyObject;
63
82
  };
64
83
  /**
65
84
  * Options, that can apply to all commands
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/types/index.ts"],"names":[],"mappings":"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"}
@@ -11,10 +11,10 @@ async function dereference(value, options) {
11
11
  const entrypoint = getEntrypoint(filesystem);
12
12
  const result = resolveReferences(filesystem, options);
13
13
  return {
14
- specification: entrypoint.specification,
14
+ specification: entrypoint.content,
15
15
  errors: result.errors,
16
16
  schema: result.schema,
17
- ...details(entrypoint.specification),
17
+ ...details(entrypoint.content),
18
18
  };
19
19
  }
20
20
 
@@ -8,7 +8,7 @@ import { traverse } from './traverse.js';
8
8
  function filter(specification, callback) {
9
9
  const filesystem = makeFilesystem(specification);
10
10
  return {
11
- specification: traverse(getEntrypoint(filesystem).specification, (schema) => {
11
+ specification: traverse(getEntrypoint(filesystem).content, (schema) => {
12
12
  return callback(schema) ? schema : undefined;
13
13
  }),
14
14
  };
@@ -1,5 +1,6 @@
1
+ import type { Filesystem } from '../types/index.ts';
1
2
  /**
2
3
  * Check if the value is a filesystem
3
4
  */
4
- export declare function isFilesystem(value: any): boolean;
5
+ export declare function isFilesystem(value: any): value is Filesystem;
5
6
  //# sourceMappingURL=isFilesystem.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"isFilesystem.d.ts","sourceRoot":"","sources":["../../src/utils/isFilesystem.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,GAAG,WAOtC"}
1
+ {"version":3,"file":"isFilesystem.d.ts","sourceRoot":"","sources":["../../src/utils/isFilesystem.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAEnD;;GAEG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,GAAG,GAAG,KAAK,IAAI,UAAU,CAO5D"}
@@ -1,14 +1,19 @@
1
1
  import type { AnyApiDefinitionFormat, Filesystem, LoadResult, ThrowOnErrorOption } from '../../types/index.ts';
2
2
  export type LoadPlugin = {
3
+ priority?: number;
3
4
  check: (value?: any) => boolean;
4
- get: (value: any) => any;
5
+ get: (value: any, source?: string) => any;
6
+ /**
7
+ * @deprecated Use `getUri` instead
8
+ */
5
9
  resolvePath?: (value: any, reference: string) => string;
10
+ getUri?: (value: any, source?: string) => string;
6
11
  getDir?: (value: any) => string;
7
12
  getFilename?: (value: any) => string;
8
13
  };
9
14
  export type LoadOptions = {
10
15
  plugins?: LoadPlugin[];
11
- filename?: string;
16
+ source?: string | undefined;
12
17
  filesystem?: Filesystem;
13
18
  } & ThrowOnErrorOption;
14
19
  /**
@@ -19,6 +24,10 @@ export type LoadOptions = {
19
24
  *
20
25
  * It builds a filesystem representation of all loaded content and collects any errors
21
26
  * encountered during the process.
27
+ *
28
+ * Known limitation:
29
+ * - TODO: Internal references inside external reference might not work
30
+ * - TODO: Protocol-relative URLs don’t work yet
22
31
  */
23
32
  export declare function load(value: AnyApiDefinitionFormat, options?: LoadOptions): Promise<LoadResult>;
24
33
  //# sourceMappingURL=load.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"load.d.ts","sourceRoot":"","sources":["../../../src/utils/load/load.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,sBAAsB,EAGtB,UAAU,EACV,UAAU,EACV,kBAAkB,EACnB,MAAM,sBAAsB,CAAA;AAM7B,MAAM,MAAM,UAAU,GAAG;IACvB,KAAK,EAAE,CAAC,KAAK,CAAC,EAAE,GAAG,KAAK,OAAO,CAAA;IAC/B,GAAG,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,GAAG,CAAA;IACxB,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,KAAK,MAAM,CAAA;IACvD,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,MAAM,CAAA;IAC/B,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,MAAM,CAAA;CACrC,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,CAAC,EAAE,UAAU,EAAE,CAAA;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,UAAU,CAAC,EAAE,UAAU,CAAA;CACxB,GAAG,kBAAkB,CAAA;AAEtB;;;;;;;;GAQG;AACH,wBAAsB,IAAI,CAAC,KAAK,EAAE,sBAAsB,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAyHpG"}
1
+ {"version":3,"file":"load.d.ts","sourceRoot":"","sources":["../../../src/utils/load/load.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,sBAAsB,EAGtB,UAAU,EACV,UAAU,EACV,kBAAkB,EACnB,MAAM,kBAAkB,CAAA;AAMzB,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,CAAC,KAAK,CAAC,EAAE,GAAG,KAAK,OAAO,CAAA;IAC/B,GAAG,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,GAAG,CAAA;IACzC;;OAEG;IACH,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,SAAS,EAAE,MAAM,KAAK,MAAM,CAAA;IACvD,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,MAAM,CAAA;IAChD,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,MAAM,CAAA;IAC/B,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,KAAK,MAAM,CAAA;CACrC,CAAA;AAED,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,CAAC,EAAE,UAAU,EAAE,CAAA;IACtB,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC3B,UAAU,CAAC,EAAE,UAAU,CAAA;CACxB,GAAG,kBAAkB,CAAA;AAEtB;;;;;;;;;;;;GAYG;AACH,wBAAsB,IAAI,CAAC,KAAK,EAAE,sBAAsB,EAAE,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC,CAgKpG"}
@@ -1,7 +1,7 @@
1
1
  import { ERRORS } from '../../configuration/index.js';
2
2
  import { getEntrypoint } from '../getEntrypoint.js';
3
3
  import { getListOfReferences } from '../getListOfReferences.js';
4
- import { makeFilesystem } from '../makeFilesystem.js';
4
+ import { makeFilesystemEntry } from '../makeFilesystem.js';
5
5
  import { normalize } from '../normalize.js';
6
6
 
7
7
  /**
@@ -12,25 +12,39 @@ import { normalize } from '../normalize.js';
12
12
  *
13
13
  * It builds a filesystem representation of all loaded content and collects any errors
14
14
  * encountered during the process.
15
+ *
16
+ * Known limitation:
17
+ * - TODO: Internal references inside external reference might not work
18
+ * - TODO: Protocol-relative URLs don’t work yet
15
19
  */
16
20
  async function load(value, options) {
17
21
  const errors = [];
18
- // Don’t load a reference twice, check the filesystem before fetching something
19
- if (options?.filesystem?.find((entry) => entry.filename === value)) {
22
+ const filesystem = [...(options?.filesystem ?? [])];
23
+ // Don't load a reference twice, check the filesystem before fetching something
24
+ if (filesystem.find((entry) => entry.uri === options?.source)) {
20
25
  return {
21
- specification: getEntrypoint(options.filesystem)?.specification,
22
- filesystem: options.filesystem,
26
+ specification: getEntrypoint(filesystem)?.content,
27
+ filesystem,
23
28
  errors,
24
29
  };
25
30
  }
26
- // Check whether the value is an URL or file path
27
- const plugin = options?.plugins?.find((thisPlugin) => thisPlugin.check(value));
28
31
  let content;
32
+ let uri;
33
+ // Check whether the value is an file or URL
34
+ const plugin = sortPlugins(options?.plugins)?.find((thisPlugin) => thisPlugin.check(value));
35
+ if (options?.source) {
36
+ uri = options.source;
37
+ }
38
+ // Seems to be a file or URL
29
39
  if (plugin) {
30
40
  try {
31
- content = normalize(await plugin.get(value));
41
+ // Get the absolute URI/path
42
+ uri = plugin.getUri(value, options?.source);
43
+ // Fetch/read the content
44
+ content = normalize(await plugin.get(uri));
32
45
  }
33
46
  catch (_error) {
47
+ // Couldn’t fetch/read the content
34
48
  if (options?.throwOnError) {
35
49
  throw new Error(ERRORS.EXTERNAL_REFERENCE_NOT_FOUND.replace('%s', value));
36
50
  }
@@ -40,15 +54,16 @@ async function load(value, options) {
40
54
  });
41
55
  return {
42
56
  specification: null,
43
- filesystem: [],
57
+ filesystem,
44
58
  errors,
45
59
  };
46
60
  }
47
61
  }
48
62
  else {
63
+ // No plugin found, so it’s just the content, I guess.
49
64
  content = normalize(value);
50
65
  }
51
- // No content
66
+ // Oops, no content
52
67
  if (content === undefined) {
53
68
  if (options?.throwOnError) {
54
69
  throw new Error('No content to load');
@@ -59,61 +74,85 @@ async function load(value, options) {
59
74
  });
60
75
  return {
61
76
  specification: null,
62
- filesystem: [],
77
+ filesystem,
63
78
  errors,
64
79
  };
65
80
  }
66
- let filesystem = makeFilesystem(content, {
67
- filename: options?.filename ?? null,
81
+ const entryExistsAlready = options?.filesystem?.find((entry) => uri && entry.uri === uri);
82
+ if (entryExistsAlready) {
83
+ return {
84
+ specification: getEntrypoint(filesystem)?.content,
85
+ filesystem,
86
+ errors,
87
+ };
88
+ }
89
+ // Get all external references from the content
90
+ const listOfReferences = getListOfReferences(content);
91
+ const source =
92
+ // A given URL/path
93
+ options?.source ??
94
+ // The reference that we come from
95
+ (typeof value === 'string'
96
+ ? value
97
+ : // Nothing
98
+ undefined);
99
+ // Map the references to their absolute URIs
100
+ const mapOfReferences = listOfReferences.reduce((acc, reference) => {
101
+ const plugin = sortPlugins(options?.plugins)?.find((p) => p.check(reference));
102
+ const absoluteUri = plugin?.getUri(reference, source);
103
+ return { ...acc, [reference]: absoluteUri };
104
+ }, {});
105
+ const entry = makeFilesystemEntry(content, {
106
+ isEntrypoint: false,
107
+ uri,
108
+ references: mapOfReferences,
68
109
  });
69
- // Get references from file system entry, or from the content
70
- const newEntry = options?.filename
71
- ? filesystem.find((entry) => entry.filename === options?.filename)
72
- : getEntrypoint(filesystem);
73
- const listOfReferences = newEntry.references ?? getListOfReferences(content);
110
+ if (!filesystem.find((entry) => entry.uri === uri)) {
111
+ filesystem.push({
112
+ ...entry,
113
+ isEntrypoint: filesystem.length === 0,
114
+ });
115
+ }
74
116
  // No other references
75
117
  if (listOfReferences.length === 0) {
76
118
  return {
77
- specification: getEntrypoint(filesystem)?.specification,
119
+ specification: entry.content,
78
120
  filesystem,
79
121
  errors,
80
122
  };
81
123
  }
82
124
  // Load other external references
83
125
  for (const reference of listOfReferences) {
126
+ // Work with the absolute URI
127
+ const absoluteUri = mapOfReferences[reference];
84
128
  // Find a matching plugin
85
- const otherPlugin = options?.plugins?.find((thisPlugin) => thisPlugin.check(reference));
86
- // Skip if no plugin is found (internal references don’t need a plugin for example)
129
+ const otherPlugin = sortPlugins(options?.plugins)?.find((thisPlugin) => thisPlugin.check(absoluteUri));
130
+ // Skip if no plugin is found (internal references don't need a plugin for example)
87
131
  if (!otherPlugin) {
88
132
  continue;
89
133
  }
90
- const target = otherPlugin.check(reference) && otherPlugin.resolvePath ? otherPlugin.resolvePath(value, reference) : reference;
91
- // Don’t load a reference twice, check the filesystem before fetching something
92
- if (filesystem.find((entry) => entry.filename === reference)) {
134
+ // Don't load a reference twice, check the filesystem before fetching something
135
+ if (options?.filesystem?.find((entry) => entry.uri === absoluteUri)) {
93
136
  continue;
94
137
  }
95
- const { filesystem: referencedFiles, errors: newErrors } = await load(target, {
138
+ const { filesystem: referencedFiles, errors: newErrors } = await load(absoluteUri, {
96
139
  ...options,
97
- // Make the filename the exact same value as the $ref
98
- // TODO: This leads to problems, if there are multiple references with the same file name but in different folders
99
- filename: reference,
140
+ source: absoluteUri,
141
+ filesystem,
100
142
  });
101
143
  errors.push(...newErrors);
102
- filesystem = [
103
- ...filesystem,
104
- ...referencedFiles.map((file) => {
105
- return {
106
- ...file,
107
- isEntrypoint: false,
108
- };
109
- }),
110
- ];
144
+ referencedFiles
145
+ .filter((file) => !filesystem.find((entry) => entry.uri === file.uri))
146
+ .forEach((file) => filesystem.push(file));
111
147
  }
112
148
  return {
113
- specification: getEntrypoint(filesystem)?.specification,
149
+ specification: entry.content,
114
150
  filesystem,
115
151
  errors,
116
152
  };
117
153
  }
154
+ function sortPlugins(plugins = []) {
155
+ return plugins?.sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
156
+ }
118
157
 
119
158
  export { load };
@@ -1,3 +1,20 @@
1
1
  import type { AnyObject, Filesystem, FilesystemEntry } from '../types/index.ts';
2
- export declare function makeFilesystem(value: string | AnyObject | Filesystem, overwrites?: Partial<FilesystemEntry>): Filesystem;
2
+ export declare function makeFilesystem(
3
+ /**
4
+ * Pass whatever you have, it will be normalized and then used as the content.
5
+ */
6
+ value: string | AnyObject | Filesystem | FilesystemEntry,
7
+ /**
8
+ * If you know better, you can overwrite the default values.
9
+ */
10
+ overwrites?: Partial<FilesystemEntry> & (FilesystemEntry['references'] | {})): Filesystem;
11
+ export declare function makeFilesystemEntry(
12
+ /**
13
+ * Pass whatever you have, it will be normalized and then used as the content.
14
+ */
15
+ value: string | AnyObject | Filesystem,
16
+ /**
17
+ * If you know better, you can overwrite the default values.
18
+ */
19
+ overwrites?: Partial<FilesystemEntry> & (FilesystemEntry['references'] | {})): FilesystemEntry;
3
20
  //# sourceMappingURL=makeFilesystem.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"makeFilesystem.d.ts","sourceRoot":"","sources":["../../src/utils/makeFilesystem.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAK/E,wBAAgB,cAAc,CAC5B,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,UAAU,EACtC,UAAU,GAAE,OAAO,CAAC,eAAe,CAAM,GACxC,UAAU,CAoBZ"}
1
+ {"version":3,"file":"makeFilesystem.d.ts","sourceRoot":"","sources":["../../src/utils/makeFilesystem.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAA;AAI/E,wBAAgB,cAAc;AAC5B;;GAEG;AACH,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,UAAU,GAAG,eAAe;AACxD;;GAEG;AACH,UAAU,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,CAAC,eAAe,CAAC,YAAY,CAAC,GAAG,EAAE,CAAC,GAC3E,UAAU,CA4BZ;AAED,wBAAgB,mBAAmB;AACjC;;GAEG;AACH,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,UAAU;AACtC;;GAEG;AACH,UAAU,CAAC,EAAE,OAAO,CAAC,eAAe,CAAC,GAAG,CAAC,eAAe,CAAC,YAAY,CAAC,GAAG,EAAE,CAAC,GAC3E,eAAe,CAmBjB"}