@scalar/openapi-parser 0.8.10 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # @scalar/openapi-parser
2
2
 
3
+ ## 0.10.0
4
+
5
+ ### Minor Changes
6
+
7
+ - fbef0c3: fix(openapi-parser): improve performance
8
+
9
+ ### Patch Changes
10
+
11
+ - fbef0c3: fix: doesn’t validate files with external references
12
+
13
+ ## 0.9.0
14
+
15
+ ### Minor Changes
16
+
17
+ - 6fef2f3: feat(openapi-parser): support `onDereference` option on `dereference`
18
+
3
19
  ## 0.8.10
4
20
 
5
21
  ### Patch Changes
package/README.md CHANGED
@@ -63,6 +63,20 @@ const specification = `{
63
63
  const { schema, errors } = await dereference(specification)
64
64
  ```
65
65
 
66
+ ### Track references
67
+
68
+ The `dereference` function accepts an `onDereference` callback option that gets called whenever a reference is resolved. This can be useful for tracking which schemas are being dereferenced:
69
+
70
+ ```ts
71
+ import { dereference } from '@scalar/openapi-parser'
72
+
73
+ const { schema, errors } = await dereference(specification, {
74
+ onDereference: ({ schema, ref }) => {
75
+ //
76
+ },
77
+ })
78
+ ```
79
+
66
80
  ### Modify an OpenAPI document
67
81
 
68
82
  ```ts
@@ -1,5 +1,6 @@
1
- import type { AnyApiDefinitionFormat, DereferenceResult, Filesystem, ThrowOnErrorOption } from '../types';
2
- export type DereferenceOptions = ThrowOnErrorOption;
1
+ import type { AnyApiDefinitionFormat, DereferenceResult, Filesystem } from '../types';
2
+ import { type ResolveReferencesOptions } from './resolveReferences.js';
3
+ export type DereferenceOptions = ResolveReferencesOptions;
3
4
  /**
4
5
  * Resolves all references in an OpenAPI document
5
6
  */
@@ -1 +1 @@
1
- {"version":3,"file":"dereference.d.ts","sourceRoot":"","sources":["../../src/utils/dereference.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,sBAAsB,EACtB,iBAAiB,EACjB,UAAU,EACV,kBAAkB,EACnB,MAAM,UAAU,CAAA;AAMjB,MAAM,MAAM,kBAAkB,GAAG,kBAAkB,CAAA;AAEnD;;GAEG;AACH,wBAAsB,WAAW,CAC/B,KAAK,EAAE,sBAAsB,GAAG,UAAU,EAC1C,OAAO,CAAC,EAAE,kBAAkB,GAC3B,OAAO,CAAC,iBAAiB,CAAC,CAY5B"}
1
+ {"version":3,"file":"dereference.d.ts","sourceRoot":"","sources":["../../src/utils/dereference.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,sBAAsB,EACtB,iBAAiB,EACjB,UAAU,EACX,MAAM,UAAU,CAAA;AAIjB,OAAO,EACL,KAAK,wBAAwB,EAE9B,MAAM,qBAAqB,CAAA;AAE5B,MAAM,MAAM,kBAAkB,GAAG,wBAAwB,CAAA;AAEzD;;GAEG;AACH,wBAAsB,WAAW,CAC/B,KAAK,EAAE,sBAAsB,GAAG,UAAU,EAC1C,OAAO,CAAC,EAAE,kBAAkB,GAC3B,OAAO,CAAC,iBAAiB,CAAC,CAY5B"}
@@ -5,8 +5,14 @@ export type ResolveReferencesResult = {
5
5
  errors: ErrorObject[];
6
6
  schema: OpenAPI.Document;
7
7
  };
8
+ export type ResolveReferencesOptions = ThrowOnErrorOption & {
9
+ onDereference?: (data: {
10
+ schema: AnyObject;
11
+ ref: string;
12
+ }) => void;
13
+ };
8
14
  /**
9
15
  * Takes a specification and resolves all references.
10
16
  */
11
- export declare function resolveReferences(input: AnyObject | Filesystem, options?: ThrowOnErrorOption, file?: FilesystemEntry, errors?: ErrorObject[]): ResolveReferencesResult;
17
+ export declare function resolveReferences(input: AnyObject | Filesystem, options?: ResolveReferencesOptions, file?: FilesystemEntry, errors?: ErrorObject[]): ResolveReferencesResult;
12
18
  //# sourceMappingURL=resolveReferences.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"resolveReferences.d.ts","sourceRoot":"","sources":["../../src/utils/resolveReferences.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAA;AAGpD,OAAO,KAAK,EACV,SAAS,EACT,WAAW,EACX,UAAU,EACV,eAAe,EACf,kBAAkB,EACnB,MAAM,UAAU,CAAA;AAejB,MAAM,MAAM,uBAAuB,GAAG;IACpC,KAAK,EAAE,OAAO,CAAA;IACd,MAAM,EAAE,WAAW,EAAE,CAAA;IACrB,MAAM,EAAE,OAAO,CAAC,QAAQ,CAAA;CACzB,CAAA;AAOD;;GAEG;AACH,wBAAgB,iBAAiB,CAE/B,KAAK,EAAE,SAAS,GAAG,UAAU,EAE7B,OAAO,CAAC,EAAE,kBAAkB,EAE5B,IAAI,CAAC,EAAE,eAAe,EAEtB,MAAM,CAAC,EAAE,WAAW,EAAE,GACrB,uBAAuB,CA+FzB"}
1
+ {"version":3,"file":"resolveReferences.d.ts","sourceRoot":"","sources":["../../src/utils/resolveReferences.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAA;AAGpD,OAAO,KAAK,EACV,SAAS,EACT,WAAW,EACX,UAAU,EACV,eAAe,EACf,kBAAkB,EACnB,MAAM,UAAU,CAAA;AAejB,MAAM,MAAM,uBAAuB,GAAG;IACpC,KAAK,EAAE,OAAO,CAAA;IACd,MAAM,EAAE,WAAW,EAAE,CAAA;IACrB,MAAM,EAAE,OAAO,CAAC,QAAQ,CAAA;CACzB,CAAA;AAED,MAAM,MAAM,wBAAwB,GAAG,kBAAkB,GAAG;IAC1D,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,MAAM,EAAE,SAAS,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAA;CACnE,CAAA;AAOD;;GAEG;AACH,wBAAgB,iBAAiB,CAE/B,KAAK,EAAE,SAAS,GAAG,UAAU,EAE7B,OAAO,CAAC,EAAE,wBAAwB,EAElC,IAAI,CAAC,EAAE,eAAe,EAEtB,MAAM,CAAC,EAAE,WAAW,EAAE,GACrB,uBAAuB,CAqGzB"}
@@ -27,9 +27,6 @@ errors) {
27
27
  const entrypoint = getEntrypoint(filesystem);
28
28
  // Recursively resolve all references
29
29
  resolve(file?.specification ?? entrypoint.specification, filesystem, file ?? entrypoint);
30
- // If we replace references with content, that includes a reference, we can’t deal with that right-away.
31
- // That’s why we need a second run.
32
- resolve(file?.specification ?? entrypoint.specification, filesystem, file ?? entrypoint);
33
30
  // Remove duplicats (according to message) from errors
34
31
  errors = errors.filter((error, index, self) => index ===
35
32
  self.findIndex((t) => t.message === error.message && t.code === error.code));
@@ -43,48 +40,46 @@ errors) {
43
40
  /**
44
41
  * Resolves the circular reference to an object and deletes the $ref properties.
45
42
  */
46
- function resolve(schema, resolveFilesystem, resolveFile) {
47
- let result;
48
- // Iterate over the whole objecct
49
- Object.entries(schema ?? {}).forEach(([_, value]) => {
50
- // Ignore parts without a reference
51
- if (schema.$ref !== undefined) {
52
- // Find the referenced content
53
- const target = resolveUri(schema.$ref, options, resolveFile, resolveFilesystem, errors);
54
- if (target === undefined) {
55
- return undefined;
56
- }
57
- // Get rid of the reference
58
- delete schema.$ref;
59
- if (typeof target === 'object') {
60
- Object.keys(target).forEach((key) => {
61
- if (schema[key] === undefined) {
62
- schema[key] = target[key];
63
- }
64
- });
43
+ function resolve(schema, resolveFilesystem, resolveFile,
44
+ // references to resolved object
45
+ resolved = new WeakSet()) {
46
+ let result = { errors: [] };
47
+ if (schema === null || resolved.has(schema))
48
+ return result;
49
+ resolved.add(schema);
50
+ function resolveExternal(externalFile) {
51
+ resolve(externalFile.specification, resolveFilesystem, externalFile, resolved);
52
+ return externalFile;
53
+ }
54
+ // Ignore parts without a reference
55
+ while (schema.$ref !== undefined) {
56
+ // Find the referenced content
57
+ const target = resolveUri(schema.$ref, options, resolveFile, resolveFilesystem, resolveExternal, errors);
58
+ // invalid
59
+ if (typeof target !== 'object' || target === null)
60
+ break;
61
+ options?.onDereference?.({ schema, ref: schema.$ref });
62
+ // Get rid of the reference
63
+ delete schema.$ref;
64
+ for (const key of Object.keys(target)) {
65
+ if (schema[key] === undefined) {
66
+ schema[key] = target[key];
65
67
  }
66
68
  }
67
- if (typeof value === 'object' && !isCircular(value)) {
68
- result = resolve(value, resolveFilesystem, resolveFile);
69
+ }
70
+ // Iterate over the whole object
71
+ for (const value of Object.values(schema)) {
72
+ if (typeof value === 'object' && value !== null) {
73
+ result = resolve(value, resolveFilesystem, resolveFile, resolved);
69
74
  }
70
- });
71
- return {
72
- errors: result?.errors ?? [],
73
- };
74
- }
75
- }
76
- // TODO: Is there a better way? :D
77
- function isCircular(schema) {
78
- try {
79
- JSON.stringify(schema);
80
- return false;
81
- }
82
- catch (error) {
83
- return true;
75
+ }
76
+ return result;
84
77
  }
85
78
  }
86
79
  /**
87
80
  * Resolves a URI to a part of the specification
81
+ *
82
+ * The output is not necessarily dereferenced
88
83
  */
89
84
  function resolveUri(
90
85
  // 'foobar.json#/foo/bar'
@@ -92,7 +87,9 @@ uri, options,
92
87
  // { filename: './foobar.json '}
93
88
  file,
94
89
  // [ { filename: './foobar.json '} ]
95
- filesystem, errors) {
90
+ filesystem,
91
+ // a function to resolve references in external file
92
+ resolve, errors) {
96
93
  // Ignore invalid URIs
97
94
  if (typeof uri !== 'string') {
98
95
  if (options?.throwOnError) {
@@ -123,13 +120,13 @@ filesystem, errors) {
123
120
  });
124
121
  return;
125
122
  }
126
- const result = resolveReferences(filesystem, options, externalReference, errors);
127
123
  // $ref: 'other-file.yaml'
128
124
  if (path === undefined) {
129
- return result.schema;
125
+ return externalReference.specification;
130
126
  }
131
127
  // $ref: 'other-file.yaml#/foo/bar'
132
- return resolveUri(`#${path}`, options, externalReference, filesystem, errors);
128
+ // resolve refs first before accessing properties directly
129
+ return resolveUri(`#${path}`, options, resolve(externalReference), filesystem, resolve, errors);
133
130
  }
134
131
  // Pointers
135
132
  const segments = getSegmentsFromPath(path);
package/package.json CHANGED
@@ -17,7 +17,7 @@
17
17
  "parser",
18
18
  "typescript"
19
19
  ],
20
- "version": "0.8.10",
20
+ "version": "0.10.0",
21
21
  "engines": {
22
22
  "node": ">=18"
23
23
  },