@scalar/openapi-parser 0.13.0 → 0.14.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 +6 -0
- package/README.md +134 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +2 -2
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/bundle/bundle.d.ts +193 -0
- package/dist/utils/bundle/bundle.d.ts.map +1 -0
- package/dist/utils/bundle/bundle.js +197 -0
- package/dist/utils/bundle/bundle.js.map +7 -0
- package/dist/utils/bundle/create-limiter.d.ts +21 -0
- package/dist/utils/bundle/create-limiter.d.ts.map +1 -0
- package/dist/utils/bundle/create-limiter.js +31 -0
- package/dist/utils/bundle/create-limiter.js.map +7 -0
- package/dist/utils/bundle/index.d.ts +4 -0
- package/dist/utils/bundle/index.d.ts.map +1 -0
- package/dist/utils/bundle/index.js +9 -0
- package/dist/utils/bundle/index.js.map +7 -0
- package/dist/utils/bundle/plugins/fetch-urls.d.ts +38 -0
- package/dist/utils/bundle/plugins/fetch-urls.d.ts.map +1 -0
- package/dist/utils/bundle/plugins/fetch-urls.js +40 -0
- package/dist/utils/bundle/plugins/fetch-urls.js.map +7 -0
- package/dist/utils/bundle/plugins/read-files.d.ts +29 -0
- package/dist/utils/bundle/plugins/read-files.d.ts.map +1 -0
- package/dist/utils/bundle/plugins/read-files.js +30 -0
- package/dist/utils/bundle/plugins/read-files.js.map +7 -0
- package/dist/utils/normalize.js +2 -2
- package/dist/utils/normalize.js.map +2 -2
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
|
@@ -55,6 +55,140 @@ const specification = `{
|
|
|
55
55
|
const { schema, errors } = await dereference(specification)
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
+
### Bundle external references
|
|
59
|
+
|
|
60
|
+
The OpenAPI specification allows to point to external files (URLs or files). But sometimes, you just want to combine all files into one.
|
|
61
|
+
|
|
62
|
+
#### Plugins
|
|
63
|
+
|
|
64
|
+
##### fetchUrls
|
|
65
|
+
This plugins handles all external urls. It works for both node.js and browser environment
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
import { bundle, fetchUrls } from '@scalar/openapi-parser'
|
|
69
|
+
|
|
70
|
+
const document = {
|
|
71
|
+
openapi: '3.1.0',
|
|
72
|
+
info: { title: 'Bundled API', version: '1.0.0' },
|
|
73
|
+
paths: {},
|
|
74
|
+
components: {
|
|
75
|
+
schemas: {
|
|
76
|
+
User: { $ref: 'https://example.com/user-schema.json#' }
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// This will bundle all external documents and turn all references from external into internal
|
|
82
|
+
await bundle(document, {
|
|
83
|
+
plugins: [fetchUrls()],
|
|
84
|
+
treeShake: true // <------ This flag will try to remove any unused part of the external document
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
console.log(document)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
###### Limiting the number of concurrent requests
|
|
91
|
+
|
|
92
|
+
```ts
|
|
93
|
+
await bundle(document, {
|
|
94
|
+
plugins: [
|
|
95
|
+
fetchUrls({
|
|
96
|
+
limit: 10, // it should run at most 10 requests at the same time
|
|
97
|
+
}),
|
|
98
|
+
],
|
|
99
|
+
treeShake: false
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
###### Custom headers
|
|
105
|
+
To pass custom headers to requests for specific domains you can configure the fetch plugin like the example
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
await bundle(
|
|
109
|
+
document,
|
|
110
|
+
{
|
|
111
|
+
plugins: [
|
|
112
|
+
fetchUrls({
|
|
113
|
+
// Pass custom headers
|
|
114
|
+
// The header will only be attached to the list of domains
|
|
115
|
+
headers: [
|
|
116
|
+
{
|
|
117
|
+
domains: ['example.com'],
|
|
118
|
+
headers: {
|
|
119
|
+
'Authorization': 'Bearer <TOKEN>'
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
]
|
|
123
|
+
}),
|
|
124
|
+
readFiles(),
|
|
125
|
+
],
|
|
126
|
+
treeShake: false
|
|
127
|
+
},
|
|
128
|
+
)
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
###### Bundle from remote url
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
const result = await bundle(
|
|
135
|
+
'https://example.com/openapi.json',
|
|
136
|
+
{
|
|
137
|
+
plugins: [
|
|
138
|
+
fetchUrls(),
|
|
139
|
+
],
|
|
140
|
+
treeShake: false
|
|
141
|
+
},
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
// Bundled document
|
|
145
|
+
console.log(result)
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
##### readFiles
|
|
149
|
+
This plugins handles local files. Only works on node.js environment
|
|
150
|
+
|
|
151
|
+
```ts
|
|
152
|
+
import { bundle, readFiles } from '@scalar/openapi-parser'
|
|
153
|
+
|
|
154
|
+
const document = {
|
|
155
|
+
openapi: '3.1.0',
|
|
156
|
+
info: { title: 'Bundled API', version: '1.0.0' },
|
|
157
|
+
paths: {},
|
|
158
|
+
components: {
|
|
159
|
+
schemas: {
|
|
160
|
+
User: { $ref: './user-schema.json#' }
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// This will bundle all external documents and turn all references from external into internal
|
|
166
|
+
await bundle(document, {
|
|
167
|
+
plugins: [readFiles()],
|
|
168
|
+
treeShake: false
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
console.log(document)
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
###### Bundle from local file
|
|
175
|
+
You can pass the file path directly but make sure to have the correct plugins to handle reading from the local files
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
const result = await bundle(
|
|
179
|
+
'./input.json',
|
|
180
|
+
{
|
|
181
|
+
plugins: [
|
|
182
|
+
readFiles(),
|
|
183
|
+
],
|
|
184
|
+
treeShake: false
|
|
185
|
+
},
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
// Bundled document
|
|
189
|
+
console.log(result)
|
|
190
|
+
```
|
|
191
|
+
|
|
58
192
|
### Track references
|
|
59
193
|
|
|
60
194
|
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:
|
package/dist/index.d.ts
CHANGED
|
@@ -14,5 +14,6 @@ export { upgrade } from './utils/upgrade.js';
|
|
|
14
14
|
export { upgradeFromThreeToThreeOne } from './utils/upgrade-from-three-to-three-one.js';
|
|
15
15
|
export { upgradeFromTwoToThree } from './utils/upgrade-from-two-to-three.js';
|
|
16
16
|
export { validate } from './utils/validate.js';
|
|
17
|
+
export { bundle, fetchUrls, readFiles } from './utils/bundle/index.js';
|
|
17
18
|
export type { LoadResult, Filesystem, AnyObject, ErrorObject } from './types/index.js';
|
|
18
19
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAA;AAC/D,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AACvC,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AACxC,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AACxC,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAA;AACnC,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAA;AACrD,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AACxC,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC3C,OAAO,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAA;AACnE,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAA;AACzC,OAAO,EAAE,0BAA0B,EAAE,MAAM,yCAAyC,CAAA;AACpF,OAAO,EAAE,qBAAqB,EAAE,MAAM,mCAAmC,CAAA;AACzE,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC3C,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAA;AAC/D,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAA;AACvC,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AACxC,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AACxC,OAAO,EAAE,IAAI,EAAE,MAAM,cAAc,CAAA;AACnC,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAA;AACrD,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AACxC,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC3C,OAAO,EAAE,mBAAmB,EAAE,MAAM,+BAA+B,CAAA;AACnE,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAA;AACzC,OAAO,EAAE,0BAA0B,EAAE,MAAM,yCAAyC,CAAA;AACpF,OAAO,EAAE,qBAAqB,EAAE,MAAM,mCAAmC,CAAA;AACzE,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC3C,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC7D,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -14,14 +14,18 @@ import { upgrade } from "./utils/upgrade.js";
|
|
|
14
14
|
import { upgradeFromThreeToThreeOne } from "./utils/upgrade-from-three-to-three-one.js";
|
|
15
15
|
import { upgradeFromTwoToThree } from "./utils/upgrade-from-two-to-three.js";
|
|
16
16
|
import { validate } from "./utils/validate.js";
|
|
17
|
+
import { bundle, fetchUrls, readFiles } from "./utils/bundle/index.js";
|
|
17
18
|
export {
|
|
19
|
+
bundle,
|
|
18
20
|
dereference,
|
|
19
21
|
escapeJsonPointer,
|
|
22
|
+
fetchUrls,
|
|
20
23
|
filter,
|
|
21
24
|
isJson,
|
|
22
25
|
isYaml,
|
|
23
26
|
load,
|
|
24
27
|
normalize,
|
|
28
|
+
readFiles,
|
|
25
29
|
sanitize,
|
|
26
30
|
toJson,
|
|
27
31
|
toYaml,
|
package/dist/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../src/index.ts"],
|
|
4
|
-
"sourcesContent": ["export { dereference } from './utils/dereference'\nexport { escapeJsonPointer } from './utils/escape-json-pointer'\nexport { filter } from './utils/filter'\nexport { isJson } from './utils/is-json'\nexport { isYaml } from './utils/is-yaml'\nexport { load } from './utils/load'\nexport { normalize } from './utils/normalize'\nexport { sanitize } from './utils/transform/sanitize'\nexport { toJson } from './utils/to-json'\nexport { toYaml } from './utils/to-yaml'\nexport { traverse } from './utils/traverse'\nexport { unescapeJsonPointer } from './utils/unescape-json-pointer'\nexport { upgrade } from './utils/upgrade'\nexport { upgradeFromThreeToThreeOne } from './utils/upgrade-from-three-to-three-one'\nexport { upgradeFromTwoToThree } from './utils/upgrade-from-two-to-three'\nexport { validate } from './utils/validate'\nexport type { LoadResult, Filesystem, AnyObject, ErrorObject } from './types'\n"],
|
|
5
|
-
"mappings": "AAAA,SAAS,mBAAmB;AAC5B,SAAS,yBAAyB;AAClC,SAAS,cAAc;AACvB,SAAS,cAAc;AACvB,SAAS,cAAc;AACvB,SAAS,YAAY;AACrB,SAAS,iBAAiB;AAC1B,SAAS,gBAAgB;AACzB,SAAS,cAAc;AACvB,SAAS,cAAc;AACvB,SAAS,gBAAgB;AACzB,SAAS,2BAA2B;AACpC,SAAS,eAAe;AACxB,SAAS,kCAAkC;AAC3C,SAAS,6BAA6B;AACtC,SAAS,gBAAgB;",
|
|
4
|
+
"sourcesContent": ["export { dereference } from './utils/dereference'\nexport { escapeJsonPointer } from './utils/escape-json-pointer'\nexport { filter } from './utils/filter'\nexport { isJson } from './utils/is-json'\nexport { isYaml } from './utils/is-yaml'\nexport { load } from './utils/load'\nexport { normalize } from './utils/normalize'\nexport { sanitize } from './utils/transform/sanitize'\nexport { toJson } from './utils/to-json'\nexport { toYaml } from './utils/to-yaml'\nexport { traverse } from './utils/traverse'\nexport { unescapeJsonPointer } from './utils/unescape-json-pointer'\nexport { upgrade } from './utils/upgrade'\nexport { upgradeFromThreeToThreeOne } from './utils/upgrade-from-three-to-three-one'\nexport { upgradeFromTwoToThree } from './utils/upgrade-from-two-to-three'\nexport { validate } from './utils/validate'\nexport { bundle, fetchUrls, readFiles } from './utils/bundle'\nexport type { LoadResult, Filesystem, AnyObject, ErrorObject } from './types'\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,mBAAmB;AAC5B,SAAS,yBAAyB;AAClC,SAAS,cAAc;AACvB,SAAS,cAAc;AACvB,SAAS,cAAc;AACvB,SAAS,YAAY;AACrB,SAAS,iBAAiB;AAC1B,SAAS,gBAAgB;AACzB,SAAS,cAAc;AACvB,SAAS,cAAc;AACvB,SAAS,gBAAgB;AACzB,SAAS,2BAA2B;AACpC,SAAS,eAAe;AACxB,SAAS,kCAAkC;AAC3C,SAAS,6BAA6B;AACtC,SAAS,gBAAgB;AACzB,SAAS,QAAQ,WAAW,iBAAiB;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ import type { ERRORS, OpenApiVersion } from '../configuration/index.js';
|
|
|
5
5
|
*/
|
|
6
6
|
export type Merge<A, B> = A & Omit<B, keyof A>;
|
|
7
7
|
export type AnyObject = Record<string, any>;
|
|
8
|
+
export type UnknownObject = Record<string, unknown>;
|
|
8
9
|
/**
|
|
9
10
|
* JSON, YAML or object representation of an OpenAPI API definition
|
|
10
11
|
*/
|
|
@@ -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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"}
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import type { UnknownObject } from '../../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Checks if a string is a remote URL (starts with http:// or https://)
|
|
4
|
+
* @param value - The URL string to check
|
|
5
|
+
* @returns true if the string is a remote URL, false otherwise
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* isRemoteUrl('https://example.com/schema.json') // true
|
|
9
|
+
* isRemoteUrl('http://api.example.com/schemas/user.json') // true
|
|
10
|
+
* isRemoteUrl('#/components/schemas/User') // false
|
|
11
|
+
* isRemoteUrl('./local-schema.json') // false
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export declare function isRemoteUrl(value: string): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Checks if a string is a local reference (starts with #)
|
|
17
|
+
* @param value - The reference string to check
|
|
18
|
+
* @returns true if the string is a local reference, false otherwise
|
|
19
|
+
* @example
|
|
20
|
+
* ```ts
|
|
21
|
+
* isLocalRef('#/components/schemas/User') // true
|
|
22
|
+
* isLocalRef('https://example.com/schema.json') // false
|
|
23
|
+
* isLocalRef('./local-schema.json') // false
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare function isLocalRef(value: string): boolean;
|
|
27
|
+
export type ResolveResult = {
|
|
28
|
+
ok: true;
|
|
29
|
+
data: unknown;
|
|
30
|
+
} | {
|
|
31
|
+
ok: false;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Retrieves a nested value from an object using an array of property segments.
|
|
35
|
+
* @param target - The target object to traverse
|
|
36
|
+
* @param segments - Array of property names representing the path to the desired value
|
|
37
|
+
* @returns The value at the specified path, or undefined if the path doesn't exist
|
|
38
|
+
* @example
|
|
39
|
+
* const obj = { foo: { bar: { baz: 42 } } };
|
|
40
|
+
* getNestedValue(obj, ['foo', 'bar', 'baz']); // returns 42
|
|
41
|
+
*/
|
|
42
|
+
export declare function getNestedValue(target: Record<string, any>, segments: string[]): any;
|
|
43
|
+
/**
|
|
44
|
+
* Sets a value at a specified path in an object, creating intermediate objects/arrays as needed.
|
|
45
|
+
* This function traverses the object structure and creates any missing intermediate objects
|
|
46
|
+
* or arrays based on the path segments. If the next segment is a numeric string, it creates
|
|
47
|
+
* an array instead of an object.
|
|
48
|
+
*
|
|
49
|
+
* @param obj - The target object to set the value in
|
|
50
|
+
* @param path - The JSON pointer path where the value should be set
|
|
51
|
+
* @param value - The value to set at the specified path
|
|
52
|
+
* @throws {Error} If attempting to set a value at the root path ('')
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* const obj = {}
|
|
56
|
+
* setValueAtPath(obj, '/foo/bar/0', 'value')
|
|
57
|
+
* // Result:
|
|
58
|
+
* // {
|
|
59
|
+
* // foo: {
|
|
60
|
+
* // bar: ['value']
|
|
61
|
+
* // }
|
|
62
|
+
* // }
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* const obj = { existing: { path: 'old' } }
|
|
66
|
+
* setValueAtPath(obj, '/existing/path', 'new')
|
|
67
|
+
* // Result:
|
|
68
|
+
* // {
|
|
69
|
+
* // existing: {
|
|
70
|
+
* // path: 'new'
|
|
71
|
+
* // }
|
|
72
|
+
* // }
|
|
73
|
+
*/
|
|
74
|
+
export declare function setValueAtPath(obj: any, path: string, value: any): void;
|
|
75
|
+
/**
|
|
76
|
+
* Prefixes an internal JSON reference with a given path prefix.
|
|
77
|
+
* Takes a local reference (starting with #) and prepends the provided prefix segments.
|
|
78
|
+
*
|
|
79
|
+
* @param input - The internal reference string to prefix (must start with #)
|
|
80
|
+
* @param prefix - Array of path segments to prepend to the reference
|
|
81
|
+
* @returns The prefixed reference string
|
|
82
|
+
* @throws Error if input is not a local reference
|
|
83
|
+
* @example
|
|
84
|
+
* prefixInternalRef('#/components/schemas/User', ['definitions'])
|
|
85
|
+
* // Returns: '#/definitions/components/schemas/User'
|
|
86
|
+
*/
|
|
87
|
+
export declare function prefixInternalRef(input: string, prefix: string[]): string;
|
|
88
|
+
/**
|
|
89
|
+
* Updates internal references in an object by adding a prefix to their paths.
|
|
90
|
+
* Recursively traverses the input object and modifies any local $ref references
|
|
91
|
+
* by prepending the given prefix to their paths. This is used when embedding external
|
|
92
|
+
* documents to maintain correct reference paths relative to the main document.
|
|
93
|
+
*
|
|
94
|
+
* @param input - The object to update references in
|
|
95
|
+
* @param prefix - Array of path segments to prepend to internal reference paths
|
|
96
|
+
* @returns void
|
|
97
|
+
* @example
|
|
98
|
+
* ```ts
|
|
99
|
+
* const input = {
|
|
100
|
+
* foo: {
|
|
101
|
+
* $ref: '#/components/schemas/User'
|
|
102
|
+
* }
|
|
103
|
+
* }
|
|
104
|
+
* prefixInternalRefRecursive(input, ['definitions'])
|
|
105
|
+
* // Result:
|
|
106
|
+
* // {
|
|
107
|
+
* // foo: {
|
|
108
|
+
* // $ref: '#/definitions/components/schemas/User'
|
|
109
|
+
* // }
|
|
110
|
+
* // }
|
|
111
|
+
* ```
|
|
112
|
+
*/
|
|
113
|
+
export declare function prefixInternalRefRecursive(input: unknown, prefix: string[]): string;
|
|
114
|
+
/**
|
|
115
|
+
* Generates a short SHA-1 hash from a string value.
|
|
116
|
+
* This function is used to create unique identifiers for external references
|
|
117
|
+
* while keeping the hash length manageable. It uses the Web Crypto API to
|
|
118
|
+
* generate a SHA-1 hash and returns the first 7 characters of the hex string.
|
|
119
|
+
*
|
|
120
|
+
* @param value - The string to hash
|
|
121
|
+
* @returns A 7-character hexadecimal hash
|
|
122
|
+
* @example
|
|
123
|
+
* // Returns "a1b2c3d"
|
|
124
|
+
* getHash("https://example.com/schema.json")
|
|
125
|
+
*/
|
|
126
|
+
export declare function getHash(value: string): Promise<string>;
|
|
127
|
+
/**
|
|
128
|
+
* Represents a plugin that handles resolving references from external sources.
|
|
129
|
+
* Plugins are responsible for fetching and processing data from different sources
|
|
130
|
+
* like URLs or the filesystem. Each plugin must implement validation to determine
|
|
131
|
+
* if it can handle a specific reference, and an execution function to perform
|
|
132
|
+
* the actual resolution.
|
|
133
|
+
*
|
|
134
|
+
* @property validate - Determines if this plugin can handle the given reference
|
|
135
|
+
* @property exec - Fetches and processes the reference, returning the resolved data
|
|
136
|
+
*/
|
|
137
|
+
export type Plugin = {
|
|
138
|
+
validate: (value: string) => boolean;
|
|
139
|
+
exec: (value: string) => Promise<ResolveResult>;
|
|
140
|
+
};
|
|
141
|
+
type Config = {
|
|
142
|
+
plugins: Plugin[];
|
|
143
|
+
root?: UnknownObject;
|
|
144
|
+
cache?: Map<string, Promise<ResolveResult>>;
|
|
145
|
+
treeShake: boolean;
|
|
146
|
+
urlMap?: boolean;
|
|
147
|
+
};
|
|
148
|
+
/**
|
|
149
|
+
* Bundles an OpenAPI specification by resolving all external references.
|
|
150
|
+
* This function traverses the input object recursively and embeds external $ref
|
|
151
|
+
* references into an x-ext section. External references can be URLs or local files.
|
|
152
|
+
* The original $refs are updated to point to their embedded content in the x-ext section.
|
|
153
|
+
* If the input is an object, it will be modified in place by adding an x-ext
|
|
154
|
+
* property to store resolved external references.
|
|
155
|
+
*
|
|
156
|
+
* @param input - The OpenAPI specification object or string to bundle. If a string is provided,
|
|
157
|
+
* it should be a URL or file path that points to an OpenAPI specification.
|
|
158
|
+
* The string will be resolved using the provided plugins before bundling.
|
|
159
|
+
* @param config - Configuration object containing plugins for resolving references
|
|
160
|
+
* @returns A promise that resolves to the bundled specification with all references embedded
|
|
161
|
+
* @example
|
|
162
|
+
* // Example with object input
|
|
163
|
+
* const spec = {
|
|
164
|
+
* paths: {
|
|
165
|
+
* '/users': {
|
|
166
|
+
* $ref: 'https://example.com/schemas/users.yaml'
|
|
167
|
+
* }
|
|
168
|
+
* }
|
|
169
|
+
* }
|
|
170
|
+
*
|
|
171
|
+
* const bundled = await bundle(spec, { plugins: [fetchUrls()] })
|
|
172
|
+
* // Result:
|
|
173
|
+
* // {
|
|
174
|
+
* // paths: {
|
|
175
|
+
* // '/users': {
|
|
176
|
+
* // $ref: '#/x-ext/https~1~1example.com~1schemas~1users.yaml'
|
|
177
|
+
* // }
|
|
178
|
+
* // },
|
|
179
|
+
* // 'x-ext': {
|
|
180
|
+
* // 'https~1~1example.com~1schemas~1users.yaml': {
|
|
181
|
+
* // // Resolved content from users.yaml
|
|
182
|
+
* // }
|
|
183
|
+
* // }
|
|
184
|
+
* // }
|
|
185
|
+
*
|
|
186
|
+
* // Example with URL input
|
|
187
|
+
* const bundledFromUrl = await bundle('https://example.com/openapi.yaml', { plugins: [fetchUrls()] })
|
|
188
|
+
* // The function will first fetch the OpenAPI spec from the URL,
|
|
189
|
+
* // then bundle all its external references into the x-ext section
|
|
190
|
+
*/
|
|
191
|
+
export declare function bundle(input: UnknownObject | string, config: Config): Promise<unknown>;
|
|
192
|
+
export {};
|
|
193
|
+
//# sourceMappingURL=bundle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bundle.d.ts","sourceRoot":"","sources":["../../../src/utils/bundle/bundle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAM5C;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAElD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAEjD;AAED,MAAM,MAAM,aAAa,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,OAAO,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAA;CAAE,CAAA;AA2BvE;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,OAO7E;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,IAAI,CAyBvE;AAiCD;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAMhE;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,UAgB1E;AAqFD;;;;;;;;;;;GAWG;AACH,wBAAsB,OAAO,CAAC,KAAK,EAAE,MAAM,mBAa1C;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,MAAM,GAAG;IAEnB,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAA;IAEpC,IAAI,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,aAAa,CAAC,CAAA;CAChD,CAAA;AAED,KAAK,MAAM,GAAG;IAEZ,OAAO,EAAE,MAAM,EAAE,CAAA;IAGjB,IAAI,CAAC,EAAE,aAAa,CAAA;IAEpB,KAAK,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC,CAAA;IAE3C,SAAS,EAAE,OAAO,CAAA;IAElB,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AACH,wBAAsB,MAAM,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,EAAE,MAAM,EAAE,MAAM,oBA2IzE"}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { escapeJsonPointer } from "../../utils/escape-json-pointer.js";
|
|
2
|
+
import path from "../../polyfills/path.js";
|
|
3
|
+
import { getSegmentsFromPath } from "../../utils/get-segments-from-path.js";
|
|
4
|
+
import { isObject } from "../../utils/is-object.js";
|
|
5
|
+
function isRemoteUrl(value) {
|
|
6
|
+
return value.startsWith("http://") || value.startsWith("https://");
|
|
7
|
+
}
|
|
8
|
+
function isLocalRef(value) {
|
|
9
|
+
return value.startsWith("#");
|
|
10
|
+
}
|
|
11
|
+
async function resolveContents(value, plugins) {
|
|
12
|
+
const plugin = plugins.find((p) => p.validate(value));
|
|
13
|
+
if (plugin) {
|
|
14
|
+
return plugin.exec(value);
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
ok: false
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function getNestedValue(target, segments) {
|
|
21
|
+
return segments.reduce((acc, key) => {
|
|
22
|
+
if (acc === void 0) {
|
|
23
|
+
return void 0;
|
|
24
|
+
}
|
|
25
|
+
return acc[key];
|
|
26
|
+
}, target);
|
|
27
|
+
}
|
|
28
|
+
function setValueAtPath(obj, path2, value) {
|
|
29
|
+
if (path2 === "") {
|
|
30
|
+
throw new Error("Cannot set value at root ('') pointer");
|
|
31
|
+
}
|
|
32
|
+
const parts = getSegmentsFromPath(path2);
|
|
33
|
+
let current = obj;
|
|
34
|
+
for (let i = 0; i < parts.length; i++) {
|
|
35
|
+
const key = parts[i];
|
|
36
|
+
const isLast = i === parts.length - 1;
|
|
37
|
+
const nextKey = parts[i + 1];
|
|
38
|
+
const shouldBeArray = /^\d+$/.test(nextKey ?? "");
|
|
39
|
+
if (isLast) {
|
|
40
|
+
current[key] = value;
|
|
41
|
+
} else {
|
|
42
|
+
if (!(key in current) || typeof current[key] !== "object") {
|
|
43
|
+
current[key] = shouldBeArray ? [] : {};
|
|
44
|
+
}
|
|
45
|
+
current = current[key];
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function resolveReferencePath(base, relativePath) {
|
|
50
|
+
if (isRemoteUrl(relativePath)) {
|
|
51
|
+
return relativePath;
|
|
52
|
+
}
|
|
53
|
+
if (isRemoteUrl(base)) {
|
|
54
|
+
const url = new URL(base);
|
|
55
|
+
const mergedPath = path.join(path.dirname(url.pathname), relativePath);
|
|
56
|
+
return new URL(mergedPath, base).toString();
|
|
57
|
+
}
|
|
58
|
+
return path.join(path.dirname(base), relativePath);
|
|
59
|
+
}
|
|
60
|
+
function prefixInternalRef(input, prefix) {
|
|
61
|
+
if (!isLocalRef(input)) {
|
|
62
|
+
throw "Please provide an internal ref";
|
|
63
|
+
}
|
|
64
|
+
return `#/${prefix.map(escapeJsonPointer).join("/")}${input.substring(1)}`;
|
|
65
|
+
}
|
|
66
|
+
function prefixInternalRefRecursive(input, prefix) {
|
|
67
|
+
if (!isObject(input)) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
Object.values(input).forEach((el) => prefixInternalRefRecursive(el, prefix));
|
|
71
|
+
if (typeof input === "object" && "$ref" in input && typeof input["$ref"] === "string") {
|
|
72
|
+
const ref = input["$ref"];
|
|
73
|
+
if (!isLocalRef(ref)) {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
return input["$ref"] = prefixInternalRef(ref, prefix);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const resolveAndCopyReferences = (targetDocument, sourceDocument, referencePath, externalRefsKey, documentKey, processedNodes = /* @__PURE__ */ new Set()) => {
|
|
80
|
+
const referencedValue = getNestedValue(sourceDocument, getSegmentsFromPath(referencePath));
|
|
81
|
+
if (processedNodes.has(referencedValue)) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
processedNodes.add(referencedValue);
|
|
85
|
+
setValueAtPath(targetDocument, referencePath, referencedValue);
|
|
86
|
+
const traverse = (node) => {
|
|
87
|
+
if (!node || typeof node !== "object") {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if ("$ref" in node && typeof node["$ref"] === "string") {
|
|
91
|
+
if (node["$ref"].startsWith(`#/${externalRefsKey}/${escapeJsonPointer(documentKey)}`)) {
|
|
92
|
+
resolveAndCopyReferences(
|
|
93
|
+
targetDocument,
|
|
94
|
+
sourceDocument,
|
|
95
|
+
node["$ref"].substring(1),
|
|
96
|
+
documentKey,
|
|
97
|
+
externalRefsKey,
|
|
98
|
+
processedNodes
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
for (const value of Object.values(node)) {
|
|
103
|
+
traverse(value);
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
traverse(referencedValue);
|
|
107
|
+
};
|
|
108
|
+
async function getHash(value) {
|
|
109
|
+
const encoder = new TextEncoder();
|
|
110
|
+
const data = encoder.encode(value);
|
|
111
|
+
const hashBuffer = await crypto.subtle.digest("SHA-1", data);
|
|
112
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
113
|
+
const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
114
|
+
return hashHex.slice(0, 7);
|
|
115
|
+
}
|
|
116
|
+
async function bundle(input, config) {
|
|
117
|
+
const cache = config.cache ?? /* @__PURE__ */ new Map();
|
|
118
|
+
const resolveInput = async () => {
|
|
119
|
+
if (typeof input !== "string") {
|
|
120
|
+
return input;
|
|
121
|
+
}
|
|
122
|
+
const result = await resolveContents(input, config.plugins);
|
|
123
|
+
if (result.ok) {
|
|
124
|
+
return result.data;
|
|
125
|
+
}
|
|
126
|
+
throw "Please provide a valid string value or pass a loader to process the input";
|
|
127
|
+
};
|
|
128
|
+
const rawSpecification = await resolveInput();
|
|
129
|
+
const documentRoot = config.root ?? rawSpecification;
|
|
130
|
+
const EXTERNAL_KEY = "x-ext";
|
|
131
|
+
const EXTERNAL_URL_MAPPING = "x-ext-urls";
|
|
132
|
+
const bundler = async (root, origin = typeof input === "string" ? input : "") => {
|
|
133
|
+
if (!isObject(root) && !Array.isArray(root)) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (typeof root === "object" && "$ref" in root && typeof root["$ref"] === "string") {
|
|
137
|
+
const ref = root["$ref"];
|
|
138
|
+
if (isLocalRef(ref)) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const [prefix, path2 = ""] = ref.split("#", 2);
|
|
142
|
+
const resolvedPath = resolveReferencePath(origin, prefix);
|
|
143
|
+
const hashPath = await getHash(resolvedPath);
|
|
144
|
+
const seen = cache.has(resolvedPath);
|
|
145
|
+
if (!seen) {
|
|
146
|
+
cache.set(resolvedPath, resolveContents(resolvedPath, config.plugins));
|
|
147
|
+
}
|
|
148
|
+
const result = await cache.get(resolvedPath);
|
|
149
|
+
if (result.ok) {
|
|
150
|
+
if (!seen) {
|
|
151
|
+
prefixInternalRefRecursive(result.data, [EXTERNAL_KEY, hashPath]);
|
|
152
|
+
await bundler(result.data, resolvedPath);
|
|
153
|
+
if (config.urlMap) {
|
|
154
|
+
setValueAtPath(documentRoot, `/${EXTERNAL_URL_MAPPING}/${escapeJsonPointer(resolvedPath)}`, hashPath);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (config.treeShake === true) {
|
|
158
|
+
resolveAndCopyReferences(
|
|
159
|
+
documentRoot,
|
|
160
|
+
{ [EXTERNAL_KEY]: { [hashPath]: result.data } },
|
|
161
|
+
prefixInternalRef(`#${path2}`, [EXTERNAL_KEY, hashPath]).substring(1),
|
|
162
|
+
EXTERNAL_KEY,
|
|
163
|
+
hashPath
|
|
164
|
+
);
|
|
165
|
+
} else if (!seen) {
|
|
166
|
+
setValueAtPath(documentRoot, `/${EXTERNAL_KEY}/${hashPath}`, result.data);
|
|
167
|
+
}
|
|
168
|
+
root.$ref = prefixInternalRef(`#${path2}`, [EXTERNAL_KEY, hashPath]);
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
return console.warn(
|
|
172
|
+
`Failed to resolve external reference "${prefix}". The reference may be invalid, inaccessible, or missing a loader for this type of reference.`
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
await Promise.all(
|
|
176
|
+
Object.entries(root).map(async ([key, value]) => {
|
|
177
|
+
if (key === EXTERNAL_KEY) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
await bundler(value, origin);
|
|
181
|
+
})
|
|
182
|
+
);
|
|
183
|
+
};
|
|
184
|
+
await bundler(rawSpecification);
|
|
185
|
+
return rawSpecification;
|
|
186
|
+
}
|
|
187
|
+
export {
|
|
188
|
+
bundle,
|
|
189
|
+
getHash,
|
|
190
|
+
getNestedValue,
|
|
191
|
+
isLocalRef,
|
|
192
|
+
isRemoteUrl,
|
|
193
|
+
prefixInternalRef,
|
|
194
|
+
prefixInternalRefRecursive,
|
|
195
|
+
setValueAtPath
|
|
196
|
+
};
|
|
197
|
+
//# sourceMappingURL=bundle.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/utils/bundle/bundle.ts"],
|
|
4
|
+
"sourcesContent": ["import type { UnknownObject } from '@/types'\nimport { escapeJsonPointer } from '@/utils/escape-json-pointer'\nimport path from '@/polyfills/path'\nimport { getSegmentsFromPath } from '@/utils/get-segments-from-path'\nimport { isObject } from '@/utils/is-object'\n\n/**\n * Checks if a string is a remote URL (starts with http:// or https://)\n * @param value - The URL string to check\n * @returns true if the string is a remote URL, false otherwise\n * @example\n * ```ts\n * isRemoteUrl('https://example.com/schema.json') // true\n * isRemoteUrl('http://api.example.com/schemas/user.json') // true\n * isRemoteUrl('#/components/schemas/User') // false\n * isRemoteUrl('./local-schema.json') // false\n * ```\n */\nexport function isRemoteUrl(value: string): boolean {\n return value.startsWith('http://') || value.startsWith('https://')\n}\n\n/**\n * Checks if a string is a local reference (starts with #)\n * @param value - The reference string to check\n * @returns true if the string is a local reference, false otherwise\n * @example\n * ```ts\n * isLocalRef('#/components/schemas/User') // true\n * isLocalRef('https://example.com/schema.json') // false\n * isLocalRef('./local-schema.json') // false\n * ```\n */\nexport function isLocalRef(value: string): boolean {\n return value.startsWith('#')\n}\n\nexport type ResolveResult = { ok: true; data: unknown } | { ok: false }\n\n/**\n * Resolves a string by finding and executing the appropriate plugin.\n * @param value - The string to resolve (URL, file path, etc)\n * @param plugins - Array of plugins that can handle different types of strings\n * @returns A promise that resolves to either the content or an error result\n * @example\n * // Using a URL plugin\n * await resolveContents('https://example.com/schema.json', [urlPlugin])\n * // Using a file plugin\n * await resolveContents('./schemas/user.json', [filePlugin])\n * // No matching plugin returns { ok: false }\n * await resolveContents('#/components/schemas/User', [urlPlugin, filePlugin])\n */\nasync function resolveContents(value: string, plugins: Plugin[]): Promise<ResolveResult> {\n const plugin = plugins.find((p) => p.validate(value))\n\n if (plugin) {\n return plugin.exec(value)\n }\n\n return {\n ok: false,\n }\n}\n\n/**\n * Retrieves a nested value from an object using an array of property segments.\n * @param target - The target object to traverse\n * @param segments - Array of property names representing the path to the desired value\n * @returns The value at the specified path, or undefined if the path doesn't exist\n * @example\n * const obj = { foo: { bar: { baz: 42 } } };\n * getNestedValue(obj, ['foo', 'bar', 'baz']); // returns 42\n */\nexport function getNestedValue(target: Record<string, any>, segments: string[]) {\n return segments.reduce<any>((acc, key) => {\n if (acc === undefined) {\n return undefined\n }\n return acc[key]\n }, target)\n}\n\n/**\n * Sets a value at a specified path in an object, creating intermediate objects/arrays as needed.\n * This function traverses the object structure and creates any missing intermediate objects\n * or arrays based on the path segments. If the next segment is a numeric string, it creates\n * an array instead of an object.\n *\n * @param obj - The target object to set the value in\n * @param path - The JSON pointer path where the value should be set\n * @param value - The value to set at the specified path\n * @throws {Error} If attempting to set a value at the root path ('')\n *\n * @example\n * const obj = {}\n * setValueAtPath(obj, '/foo/bar/0', 'value')\n * // Result:\n * // {\n * // foo: {\n * // bar: ['value']\n * // }\n * // }\n *\n * @example\n * const obj = { existing: { path: 'old' } }\n * setValueAtPath(obj, '/existing/path', 'new')\n * // Result:\n * // {\n * // existing: {\n * // path: 'new'\n * // }\n * // }\n */\nexport function setValueAtPath(obj: any, path: string, value: any): void {\n if (path === '') {\n throw new Error(\"Cannot set value at root ('') pointer\")\n }\n\n const parts = getSegmentsFromPath(path)\n\n let current = obj\n\n for (let i = 0; i < parts.length; i++) {\n const key = parts[i]\n const isLast = i === parts.length - 1\n\n const nextKey = parts[i + 1]\n const shouldBeArray = /^\\d+$/.test(nextKey ?? '')\n\n if (isLast) {\n current[key] = value\n } else {\n if (!(key in current) || typeof current[key] !== 'object') {\n current[key] = shouldBeArray ? [] : {}\n }\n current = current[key]\n }\n }\n}\n\n/**\n * Resolves a reference path by combining a base path with a relative path.\n * Handles both remote URLs and local file paths.\n *\n * @param base - The base path (can be a URL or local file path)\n * @param relativePath - The relative path to resolve against the base\n * @returns The resolved absolute path\n * @example\n * // Resolve remote URL\n * resolveReferencePath('https://example.com/api/schema.json', 'user.json')\n * // Returns: 'https://example.com/api/user.json'\n *\n * // Resolve local path\n * resolveReferencePath('/path/to/schema.json', 'user.json')\n * // Returns: '/path/to/user.json'\n */\nfunction resolveReferencePath(base: string, relativePath: string) {\n if (isRemoteUrl(relativePath)) {\n return relativePath\n }\n\n if (isRemoteUrl(base)) {\n const url = new URL(base)\n\n const mergedPath = path.join(path.dirname(url.pathname), relativePath)\n return new URL(mergedPath, base).toString()\n }\n\n return path.join(path.dirname(base), relativePath)\n}\n\n/**\n * Prefixes an internal JSON reference with a given path prefix.\n * Takes a local reference (starting with #) and prepends the provided prefix segments.\n *\n * @param input - The internal reference string to prefix (must start with #)\n * @param prefix - Array of path segments to prepend to the reference\n * @returns The prefixed reference string\n * @throws Error if input is not a local reference\n * @example\n * prefixInternalRef('#/components/schemas/User', ['definitions'])\n * // Returns: '#/definitions/components/schemas/User'\n */\nexport function prefixInternalRef(input: string, prefix: string[]) {\n if (!isLocalRef(input)) {\n throw 'Please provide an internal ref'\n }\n\n return `#/${prefix.map(escapeJsonPointer).join('/')}${input.substring(1)}`\n}\n\n/**\n * Updates internal references in an object by adding a prefix to their paths.\n * Recursively traverses the input object and modifies any local $ref references\n * by prepending the given prefix to their paths. This is used when embedding external\n * documents to maintain correct reference paths relative to the main document.\n *\n * @param input - The object to update references in\n * @param prefix - Array of path segments to prepend to internal reference paths\n * @returns void\n * @example\n * ```ts\n * const input = {\n * foo: {\n * $ref: '#/components/schemas/User'\n * }\n * }\n * prefixInternalRefRecursive(input, ['definitions'])\n * // Result:\n * // {\n * // foo: {\n * // $ref: '#/definitions/components/schemas/User'\n * // }\n * // }\n * ```\n */\nexport function prefixInternalRefRecursive(input: unknown, prefix: string[]) {\n if (!isObject(input)) {\n return\n }\n\n Object.values(input).forEach((el) => prefixInternalRefRecursive(el, prefix))\n\n if (typeof input === 'object' && '$ref' in input && typeof input['$ref'] === 'string') {\n const ref = input['$ref']\n\n if (!isLocalRef(ref)) {\n return\n }\n\n return (input['$ref'] = prefixInternalRef(ref, prefix))\n }\n}\n\n/**\n * Resolves and copies referenced values from a source document to a target document.\n * This function traverses the document and copies referenced values to the target document,\n * while tracking processed references to avoid duplicates. It only processes references\n * that belong to the same external document.\n *\n * @param targetDocument - The document to copy referenced values to\n * @param sourceDocument - The source document containing the references\n * @param referencePath - The JSON pointer path to the reference\n * @param externalRefsKey - The key used for external references (e.g. 'x-ext')\n * @param documentKey - The key identifying the external document\n * @param processedNodes - Set of already processed nodes to prevent duplicates\n * @example\n * ```ts\n * const source = {\n * components: {\n * schemas: {\n * User: {\n * $ref: '#/x-ext/users~1schema/definitions/Person'\n * }\n * }\n * }\n * }\n *\n * const target = {}\n * resolveAndCopyReferences(\n * target,\n * source,\n * '/components/schemas/User',\n * 'x-ext',\n * 'users/schema'\n * )\n * // Result: target will contain the User schema with resolved references\n * ```\n */\nconst resolveAndCopyReferences = (\n targetDocument: unknown,\n sourceDocument: unknown,\n referencePath: string,\n externalRefsKey: string,\n documentKey: string,\n processedNodes = new Set(),\n) => {\n const referencedValue = getNestedValue(sourceDocument, getSegmentsFromPath(referencePath))\n\n if (processedNodes.has(referencedValue)) {\n return\n }\n processedNodes.add(referencedValue)\n\n setValueAtPath(targetDocument, referencePath, referencedValue)\n\n // Do the same for each local ref\n const traverse = (node: unknown) => {\n if (!node || typeof node !== 'object') {\n return\n }\n\n if ('$ref' in node && typeof node['$ref'] === 'string') {\n // We only process references from the same external document because:\n // 1. Other documents will be handled in separate recursive branches\n // 2. The source document only contains the current document's content\n // This prevents undefined behavior and maintains proper document boundaries\n if (node['$ref'].startsWith(`#/${externalRefsKey}/${escapeJsonPointer(documentKey)}`)) {\n resolveAndCopyReferences(\n targetDocument,\n sourceDocument,\n node['$ref'].substring(1),\n documentKey,\n externalRefsKey,\n processedNodes,\n )\n }\n }\n\n for (const value of Object.values(node)) {\n traverse(value)\n }\n }\n\n traverse(referencedValue)\n}\n\n/**\n * Generates a short SHA-1 hash from a string value.\n * This function is used to create unique identifiers for external references\n * while keeping the hash length manageable. It uses the Web Crypto API to\n * generate a SHA-1 hash and returns the first 7 characters of the hex string.\n *\n * @param value - The string to hash\n * @returns A 7-character hexadecimal hash\n * @example\n * // Returns \"a1b2c3d\"\n * getHash(\"https://example.com/schema.json\")\n */\nexport async function getHash(value: string) {\n // Convert string to ArrayBuffer\n const encoder = new TextEncoder()\n const data = encoder.encode(value)\n\n // Hash the data\n const hashBuffer = await crypto.subtle.digest('SHA-1', data)\n\n // Convert buffer to hex string\n const hashArray = Array.from(new Uint8Array(hashBuffer))\n const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join('')\n\n return hashHex.slice(0, 7)\n}\n\n/**\n * Represents a plugin that handles resolving references from external sources.\n * Plugins are responsible for fetching and processing data from different sources\n * like URLs or the filesystem. Each plugin must implement validation to determine\n * if it can handle a specific reference, and an execution function to perform\n * the actual resolution.\n *\n * @property validate - Determines if this plugin can handle the given reference\n * @property exec - Fetches and processes the reference, returning the resolved data\n */\nexport type Plugin = {\n // Determines if this plugin can handle the given reference value\n validate: (value: string) => boolean\n // Fetches and processes the reference, returning the resolved data\n exec: (value: string) => Promise<ResolveResult>\n}\n\ntype Config = {\n // Array of plugins that handle resolving references from different sources (URLs, files, etc.)\n plugins: Plugin[]\n // Optional root object that serves as the base document when bundling a subpart of the document\n // This allows resolving references relative to the root document's location\n root?: UnknownObject\n // Optional cache to store promises of resolved references to avoid duplicate fetches/reads\n cache?: Map<string, Promise<ResolveResult>>\n // Enable tree shaking, which removes unused references from the final bundle\n treeShake: boolean\n // Optional flag to generate a URL map that tracks the original source URLs of bundled references\n urlMap?: boolean\n}\n\n/**\n * Bundles an OpenAPI specification by resolving all external references.\n * This function traverses the input object recursively and embeds external $ref\n * references into an x-ext section. External references can be URLs or local files.\n * The original $refs are updated to point to their embedded content in the x-ext section.\n * If the input is an object, it will be modified in place by adding an x-ext\n * property to store resolved external references.\n *\n * @param input - The OpenAPI specification object or string to bundle. If a string is provided,\n * it should be a URL or file path that points to an OpenAPI specification.\n * The string will be resolved using the provided plugins before bundling.\n * @param config - Configuration object containing plugins for resolving references\n * @returns A promise that resolves to the bundled specification with all references embedded\n * @example\n * // Example with object input\n * const spec = {\n * paths: {\n * '/users': {\n * $ref: 'https://example.com/schemas/users.yaml'\n * }\n * }\n * }\n *\n * const bundled = await bundle(spec, { plugins: [fetchUrls()] })\n * // Result:\n * // {\n * // paths: {\n * // '/users': {\n * // $ref: '#/x-ext/https~1~1example.com~1schemas~1users.yaml'\n * // }\n * // },\n * // 'x-ext': {\n * // 'https~1~1example.com~1schemas~1users.yaml': {\n * // // Resolved content from users.yaml\n * // }\n * // }\n * // }\n *\n * // Example with URL input\n * const bundledFromUrl = await bundle('https://example.com/openapi.yaml', { plugins: [fetchUrls()] })\n * // The function will first fetch the OpenAPI spec from the URL,\n * // then bundle all its external references into the x-ext section\n */\nexport async function bundle(input: UnknownObject | string, config: Config) {\n // Cache for storing promises of resolved external references (URLs and local files)\n // to avoid duplicate fetches/reads of the same resource\n const cache = config.cache ?? new Map<string, Promise<ResolveResult>>()\n\n /**\n * Resolves the input value by either returning it directly if it's not a string,\n * or attempting to resolve it using the provided plugins if it is a string.\n * @returns The resolved input data or throws an error if resolution fails\n */\n const resolveInput = async () => {\n if (typeof input !== 'string') {\n return input\n }\n const result = await resolveContents(input, config.plugins)\n\n if (result.ok) {\n return result.data\n }\n\n throw 'Please provide a valid string value or pass a loader to process the input'\n }\n\n const rawSpecification = await resolveInput()\n\n // Document root used to write all external documents\n // We need this when we want to do a partial bundle of a document\n const documentRoot = config.root ?? rawSpecification\n\n // Custom OpenAPI extension key used to store external references\n // This key will contain all bundled external documents\n const EXTERNAL_KEY = 'x-ext'\n\n // Custom OpenAPI extension key used to maintain a mapping between\n // original URLs and their corresponding hashed keys in x-ext\n const EXTERNAL_URL_MAPPING = 'x-ext-urls'\n\n const bundler = async (root: any, origin: string = typeof input === 'string' ? input : '') => {\n if (!isObject(root) && !Array.isArray(root)) {\n return\n }\n\n if (typeof root === 'object' && '$ref' in root && typeof root['$ref'] === 'string') {\n const ref = root['$ref']\n\n if (isLocalRef(ref)) {\n return\n }\n\n const [prefix, path = ''] = ref.split('#', 2)\n\n // Combine the current origin with the new path to resolve relative references\n // correctly within the context of the external file being processed\n const resolvedPath = resolveReferencePath(origin, prefix)\n const hashPath = await getHash(resolvedPath)\n\n const seen = cache.has(resolvedPath)\n\n if (!seen) {\n cache.set(resolvedPath, resolveContents(resolvedPath, config.plugins))\n }\n\n // Resolve the remote document\n const result = await cache.get(resolvedPath)\n\n if (result.ok) {\n // Process the result only once to avoid duplicate processing and prevent multiple prefixing\n // of internal references, which would corrupt the reference paths\n if (!seen) {\n // Update internal references in the resolved document to use the correct base path.\n // When we embed external documents, their internal references need to be updated to\n // maintain the correct path context relative to the main document. This is crucial\n // because internal references in the external document are relative to its original\n // location, but when embedded, they need to be relative to their new location in\n // the main document's x-ext section. Without this update, internal references\n // would point to incorrect locations and break the document structure.\n prefixInternalRefRecursive(result.data, [EXTERNAL_KEY, hashPath])\n\n // Recursively process the resolved content\n // to handle any nested references it may contain. We pass the resolvedPath as the new origin\n // to ensure any relative references within this content are resolved correctly relative to\n // their new location in the bundled document.\n await bundler(result.data, resolvedPath)\n\n // Store the mapping between original URLs and their hashed keys in x-ext-urls\n // This allows tracking which external URLs were bundled and their corresponding locations\n if (config.urlMap) {\n setValueAtPath(documentRoot, `/${EXTERNAL_URL_MAPPING}/${escapeJsonPointer(resolvedPath)}`, hashPath)\n }\n }\n\n if (config.treeShake === true) {\n // Store only the subtree that is actually used\n // This optimizes the bundle size by only including the parts of the external document\n // that are referenced, rather than the entire document\n resolveAndCopyReferences(\n documentRoot,\n { [EXTERNAL_KEY]: { [hashPath]: result.data } },\n prefixInternalRef(`#${path}`, [EXTERNAL_KEY, hashPath]).substring(1),\n EXTERNAL_KEY,\n hashPath,\n )\n } else if (!seen) {\n // Store the external document in the main document's x-ext key\n // When tree shaking is disabled, we include the entire external document\n // This preserves all content and is faster since we don't need to analyze and copy\n // specific parts. This approach is ideal when storing the result in memory\n // as it avoids the overhead of tree shaking operations\n setValueAtPath(documentRoot, `/${EXTERNAL_KEY}/${hashPath}`, result.data)\n }\n\n // Update the $ref to point to the embedded document in x-ext\n // This is necessary because we need to maintain the correct path context\n // for the embedded document while preserving its internal structure\n root.$ref = prefixInternalRef(`#${path}`, [EXTERNAL_KEY, hashPath])\n return\n }\n\n return console.warn(\n `Failed to resolve external reference \"${prefix}\". The reference may be invalid, inaccessible, or missing a loader for this type of reference.`,\n )\n }\n\n // Recursively process all child objects to handle nested references\n // This ensures we catch and resolve any $refs that exist deeper in the object tree\n // We skip EXTERNAL_KEY to avoid processing already bundled content\n await Promise.all(\n Object.entries(root).map(async ([key, value]) => {\n if (key === EXTERNAL_KEY) {\n return\n }\n\n await bundler(value, origin)\n }),\n )\n }\n\n await bundler(rawSpecification)\n return rawSpecification\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["path"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a function that limits the number of concurrent executions of async functions.
|
|
3
|
+
*
|
|
4
|
+
* @param maxConcurrent - Maximum number of concurrent executions allowed
|
|
5
|
+
* @returns A function that wraps async functions to limit their concurrent execution
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* const limiter = createLimiter(2) // Allow max 2 concurrent executions
|
|
10
|
+
*
|
|
11
|
+
* // These will run with max 2 at a time
|
|
12
|
+
* const results = await Promise.all([
|
|
13
|
+
* limiter(() => fetch('/api/1')),
|
|
14
|
+
* limiter(() => fetch('/api/2')),
|
|
15
|
+
* limiter(() => fetch('/api/3')),
|
|
16
|
+
* limiter(() => fetch('/api/4'))
|
|
17
|
+
* ])
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export declare function createLimiter(maxConcurrent: number): <T>(fn: () => Promise<T>) => Promise<T>;
|
|
21
|
+
//# sourceMappingURL=create-limiter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-limiter.d.ts","sourceRoot":"","sources":["../../../src/utils/bundle/create-limiter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,aAAa,CAAC,aAAa,EAAE,MAAM,IAgB9B,CAAC,MAAM,MAAM,OAAO,CAAC,CAAC,CAAC,KAAG,OAAO,CAAC,CAAC,CAAC,CAgBxD"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
function createLimiter(maxConcurrent) {
|
|
2
|
+
let activeCount = 0;
|
|
3
|
+
const queue = [];
|
|
4
|
+
const next = () => {
|
|
5
|
+
if (queue.length === 0 || activeCount >= maxConcurrent) {
|
|
6
|
+
return;
|
|
7
|
+
}
|
|
8
|
+
const resolve = queue.shift();
|
|
9
|
+
if (resolve) {
|
|
10
|
+
resolve();
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
const run = async (fn) => {
|
|
14
|
+
if (activeCount >= maxConcurrent) {
|
|
15
|
+
await new Promise((resolve) => queue.push(resolve));
|
|
16
|
+
}
|
|
17
|
+
activeCount++;
|
|
18
|
+
try {
|
|
19
|
+
const result = await fn();
|
|
20
|
+
return result;
|
|
21
|
+
} finally {
|
|
22
|
+
activeCount--;
|
|
23
|
+
next();
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
return run;
|
|
27
|
+
}
|
|
28
|
+
export {
|
|
29
|
+
createLimiter
|
|
30
|
+
};
|
|
31
|
+
//# sourceMappingURL=create-limiter.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/utils/bundle/create-limiter.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * Creates a function that limits the number of concurrent executions of async functions.\n *\n * @param maxConcurrent - Maximum number of concurrent executions allowed\n * @returns A function that wraps async functions to limit their concurrent execution\n *\n * @example\n * ```ts\n * const limiter = createLimiter(2) // Allow max 2 concurrent executions\n *\n * // These will run with max 2 at a time\n * const results = await Promise.all([\n * limiter(() => fetch('/api/1')),\n * limiter(() => fetch('/api/2')),\n * limiter(() => fetch('/api/3')),\n * limiter(() => fetch('/api/4'))\n * ])\n * ```\n */\nexport function createLimiter(maxConcurrent: number) {\n let activeCount = 0\n const queue: (() => void)[] = []\n\n const next = () => {\n if (queue.length === 0 || activeCount >= maxConcurrent) {\n return\n }\n\n const resolve = queue.shift()\n\n if (resolve) {\n resolve()\n }\n }\n\n const run = async <T>(fn: () => Promise<T>): Promise<T> => {\n if (activeCount >= maxConcurrent) {\n await new Promise<void>((resolve) => queue.push(resolve))\n }\n\n activeCount++\n try {\n const result = await fn()\n return result\n } finally {\n activeCount--\n next()\n }\n }\n\n return run\n}\n"],
|
|
5
|
+
"mappings": "AAmBO,SAAS,cAAc,eAAuB;AACnD,MAAI,cAAc;AAClB,QAAM,QAAwB,CAAC;AAE/B,QAAM,OAAO,MAAM;AACjB,QAAI,MAAM,WAAW,KAAK,eAAe,eAAe;AACtD;AAAA,IACF;AAEA,UAAM,UAAU,MAAM,MAAM;AAE5B,QAAI,SAAS;AACX,cAAQ;AAAA,IACV;AAAA,EACF;AAEA,QAAM,MAAM,OAAU,OAAqC;AACzD,QAAI,eAAe,eAAe;AAChC,YAAM,IAAI,QAAc,CAAC,YAAY,MAAM,KAAK,OAAO,CAAC;AAAA,IAC1D;AAEA;AACA,QAAI;AACF,YAAM,SAAS,MAAM,GAAG;AACxB,aAAO;AAAA,IACT,UAAE;AACA;AACA,WAAK;AAAA,IACP;AAAA,EACF;AAEA,SAAO;AACT;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/bundle/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AACjC,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../src/utils/bundle/index.ts"],
|
|
4
|
+
"sourcesContent": ["// biome-ignore lint/performance/noBarrelFile: This is the interface we expose for the bundler\nexport { bundle } from './bundle'\nexport { fetchUrls } from './plugins/fetch-urls'\nexport { readFiles } from './plugins/read-files'\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,cAAc;AACvB,SAAS,iBAAiB;AAC1B,SAAS,iBAAiB;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { type Plugin, type ResolveResult } from '../../../utils/bundle/bundle.js';
|
|
2
|
+
type FetchConfig = Partial<{
|
|
3
|
+
headers: {
|
|
4
|
+
headers: HeadersInit;
|
|
5
|
+
domains: string[];
|
|
6
|
+
}[];
|
|
7
|
+
}>;
|
|
8
|
+
/**
|
|
9
|
+
* Fetches and normalizes data from a remote URL
|
|
10
|
+
* @param url - The URL to fetch data from
|
|
11
|
+
* @returns A promise that resolves to either the normalized data or an error result
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* const result = await fetchUrl('https://api.example.com/data.json')
|
|
15
|
+
* if (result.ok) {
|
|
16
|
+
* console.log(result.data) // The normalized data
|
|
17
|
+
* } else {
|
|
18
|
+
* console.log('Failed to fetch data')
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export declare function fetchUrl(url: string, limiter: <T>(fn: () => Promise<T>) => Promise<T>, config?: FetchConfig): Promise<ResolveResult>;
|
|
23
|
+
/**
|
|
24
|
+
* Creates a plugin for handling remote URL references.
|
|
25
|
+
* This plugin validates and fetches data from HTTP/HTTPS URLs.
|
|
26
|
+
*
|
|
27
|
+
* @returns A plugin object with validate and exec functions
|
|
28
|
+
* @example
|
|
29
|
+
* const urlPlugin = fetchUrls()
|
|
30
|
+
* if (urlPlugin.validate('https://example.com/schema.json')) {
|
|
31
|
+
* const result = await urlPlugin.exec('https://example.com/schema.json')
|
|
32
|
+
* }
|
|
33
|
+
*/
|
|
34
|
+
export declare function fetchUrls(config?: FetchConfig & Partial<{
|
|
35
|
+
limit: number | null;
|
|
36
|
+
}>): Plugin;
|
|
37
|
+
export {};
|
|
38
|
+
//# sourceMappingURL=fetch-urls.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fetch-urls.d.ts","sourceRoot":"","sources":["../../../../src/utils/bundle/plugins/fetch-urls.ts"],"names":[],"mappings":"AACA,OAAO,EAAe,KAAK,MAAM,EAAE,KAAK,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAGpF,KAAK,WAAW,GAAG,OAAO,CAAC;IACzB,OAAO,EAAE;QAAE,OAAO,EAAE,WAAW,CAAC;QAAC,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,EAAE,CAAA;CACvD,CAAC,CAAA;AAEF;;;;;;;;;;;;;GAaG;AACH,wBAAsB,QAAQ,CAC5B,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,EAChD,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,aAAa,CAAC,CA8BxB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC,GAAG,MAAM,CAQ1F"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { normalize } from "../../../utils/normalize.js";
|
|
2
|
+
import { isRemoteUrl } from "../../../utils/bundle/bundle.js";
|
|
3
|
+
import { createLimiter } from "../../../utils/bundle/create-limiter.js";
|
|
4
|
+
async function fetchUrl(url, limiter, config) {
|
|
5
|
+
try {
|
|
6
|
+
const domain = new URL(url).host;
|
|
7
|
+
const headers = config?.headers?.find((a) => a.domains.find((d) => d === domain) !== void 0)?.headers;
|
|
8
|
+
const result = await limiter(
|
|
9
|
+
() => fetch(url, {
|
|
10
|
+
headers
|
|
11
|
+
})
|
|
12
|
+
);
|
|
13
|
+
if (result.ok) {
|
|
14
|
+
const body = await result.text();
|
|
15
|
+
return {
|
|
16
|
+
ok: true,
|
|
17
|
+
data: normalize(body)
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
ok: false
|
|
22
|
+
};
|
|
23
|
+
} catch {
|
|
24
|
+
return {
|
|
25
|
+
ok: false
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function fetchUrls(config) {
|
|
30
|
+
const limiter = config?.limit ? createLimiter(config.limit) : (fn) => fn();
|
|
31
|
+
return {
|
|
32
|
+
validate: (value) => isRemoteUrl(value),
|
|
33
|
+
exec: (value) => fetchUrl(value, limiter, config)
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export {
|
|
37
|
+
fetchUrl,
|
|
38
|
+
fetchUrls
|
|
39
|
+
};
|
|
40
|
+
//# sourceMappingURL=fetch-urls.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/utils/bundle/plugins/fetch-urls.ts"],
|
|
4
|
+
"sourcesContent": ["import { normalize } from '@/utils/normalize'\nimport { isRemoteUrl, type Plugin, type ResolveResult } from '@/utils/bundle/bundle'\nimport { createLimiter } from '@/utils/bundle/create-limiter'\n\ntype FetchConfig = Partial<{\n headers: { headers: HeadersInit; domains: string[] }[]\n}>\n\n/**\n * Fetches and normalizes data from a remote URL\n * @param url - The URL to fetch data from\n * @returns A promise that resolves to either the normalized data or an error result\n * @example\n * ```ts\n * const result = await fetchUrl('https://api.example.com/data.json')\n * if (result.ok) {\n * console.log(result.data) // The normalized data\n * } else {\n * console.log('Failed to fetch data')\n * }\n * ```\n */\nexport async function fetchUrl(\n url: string,\n limiter: <T>(fn: () => Promise<T>) => Promise<T>,\n config?: FetchConfig,\n): Promise<ResolveResult> {\n try {\n const domain = new URL(url).host\n\n // Get the headers that match the domain\n const headers = config?.headers?.find((a) => a.domains.find((d) => d === domain) !== undefined)?.headers\n\n const result = await limiter(() =>\n fetch(url, {\n headers,\n }),\n )\n\n if (result.ok) {\n const body = await result.text()\n\n return {\n ok: true,\n data: normalize(body),\n }\n }\n\n return {\n ok: false,\n }\n } catch {\n return {\n ok: false,\n }\n }\n}\n\n/**\n * Creates a plugin for handling remote URL references.\n * This plugin validates and fetches data from HTTP/HTTPS URLs.\n *\n * @returns A plugin object with validate and exec functions\n * @example\n * const urlPlugin = fetchUrls()\n * if (urlPlugin.validate('https://example.com/schema.json')) {\n * const result = await urlPlugin.exec('https://example.com/schema.json')\n * }\n */\nexport function fetchUrls(config?: FetchConfig & Partial<{ limit: number | null }>): Plugin {\n // If there is a limit specified we limit the number of concurrent calls\n const limiter = config?.limit ? createLimiter(config.limit) : <T>(fn: () => Promise<T>) => fn()\n\n return {\n validate: (value) => isRemoteUrl(value),\n exec: (value) => fetchUrl(value, limiter, config),\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,iBAAiB;AAC1B,SAAS,mBAAoD;AAC7D,SAAS,qBAAqB;AAoB9B,eAAsB,SACpB,KACA,SACA,QACwB;AACxB,MAAI;AACF,UAAM,SAAS,IAAI,IAAI,GAAG,EAAE;AAG5B,UAAM,UAAU,QAAQ,SAAS,KAAK,CAAC,MAAM,EAAE,QAAQ,KAAK,CAAC,MAAM,MAAM,MAAM,MAAM,MAAS,GAAG;AAEjG,UAAM,SAAS,MAAM;AAAA,MAAQ,MAC3B,MAAM,KAAK;AAAA,QACT;AAAA,MACF,CAAC;AAAA,IACH;AAEA,QAAI,OAAO,IAAI;AACb,YAAM,OAAO,MAAM,OAAO,KAAK;AAE/B,aAAO;AAAA,QACL,IAAI;AAAA,QACJ,MAAM,UAAU,IAAI;AAAA,MACtB;AAAA,IACF;AAEA,WAAO;AAAA,MACL,IAAI;AAAA,IACN;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,MACL,IAAI;AAAA,IACN;AAAA,EACF;AACF;AAaO,SAAS,UAAU,QAAkE;AAE1F,QAAM,UAAU,QAAQ,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAI,OAAyB,GAAG;AAE9F,SAAO;AAAA,IACL,UAAU,CAAC,UAAU,YAAY,KAAK;AAAA,IACtC,MAAM,CAAC,UAAU,SAAS,OAAO,SAAS,MAAM;AAAA,EAClD;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type Plugin, type ResolveResult } from '../../../utils/bundle/bundle.js';
|
|
2
|
+
/**
|
|
3
|
+
* Reads and normalizes data from a local file
|
|
4
|
+
* @param path - The file path to read from
|
|
5
|
+
* @returns A promise that resolves to either the normalized data or an error result
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* const result = await readFile('./schemas/user.json')
|
|
9
|
+
* if (result.ok) {
|
|
10
|
+
* console.log(result.data) // The normalized data
|
|
11
|
+
* } else {
|
|
12
|
+
* console.log('Failed to read file')
|
|
13
|
+
* }
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export declare function readFile(path: string): Promise<ResolveResult>;
|
|
17
|
+
/**
|
|
18
|
+
* Creates a plugin for handling local file references.
|
|
19
|
+
* This plugin validates and reads data from local filesystem paths.
|
|
20
|
+
*
|
|
21
|
+
* @returns A plugin object with validate and exec functions
|
|
22
|
+
* @example
|
|
23
|
+
* const filePlugin = readFiles()
|
|
24
|
+
* if (filePlugin.validate('./local-schema.json')) {
|
|
25
|
+
* const result = await filePlugin.exec('./local-schema.json')
|
|
26
|
+
* }
|
|
27
|
+
*/
|
|
28
|
+
export declare function readFiles(): Plugin;
|
|
29
|
+
//# sourceMappingURL=read-files.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read-files.d.ts","sourceRoot":"","sources":["../../../../src/utils/bundle/plugins/read-files.ts"],"names":[],"mappings":"AACA,OAAO,EAAe,KAAK,MAAM,EAAE,KAAK,aAAa,EAAE,MAAM,uBAAuB,CAAA;AAIpF;;;;;;;;;;;;;GAaG;AACH,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAiBnE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,IAAI,MAAM,CAKlC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { normalize } from "../../../utils/normalize.js";
|
|
2
|
+
import { isRemoteUrl } from "../../../utils/bundle/bundle.js";
|
|
3
|
+
const fs = typeof window === "undefined" ? await import("node:fs/promises") : void 0;
|
|
4
|
+
async function readFile(path) {
|
|
5
|
+
if (fs === void 0) {
|
|
6
|
+
throw "Can not use readFiles plugin outside of a node environment";
|
|
7
|
+
}
|
|
8
|
+
try {
|
|
9
|
+
const fileContents = await fs.readFile(path, { encoding: "utf-8" });
|
|
10
|
+
return {
|
|
11
|
+
ok: true,
|
|
12
|
+
data: normalize(fileContents)
|
|
13
|
+
};
|
|
14
|
+
} catch {
|
|
15
|
+
return {
|
|
16
|
+
ok: false
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function readFiles() {
|
|
21
|
+
return {
|
|
22
|
+
validate: (value) => !isRemoteUrl(value),
|
|
23
|
+
exec: (value) => readFile(value)
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export {
|
|
27
|
+
readFile,
|
|
28
|
+
readFiles
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=read-files.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../src/utils/bundle/plugins/read-files.ts"],
|
|
4
|
+
"sourcesContent": ["import { normalize } from '@/utils/normalize'\nimport { isRemoteUrl, type Plugin, type ResolveResult } from '@/utils/bundle/bundle'\n\nconst fs = typeof window === 'undefined' ? await import('node:fs/promises') : undefined\n\n/**\n * Reads and normalizes data from a local file\n * @param path - The file path to read from\n * @returns A promise that resolves to either the normalized data or an error result\n * @example\n * ```ts\n * const result = await readFile('./schemas/user.json')\n * if (result.ok) {\n * console.log(result.data) // The normalized data\n * } else {\n * console.log('Failed to read file')\n * }\n * ```\n */\nexport async function readFile(path: string): Promise<ResolveResult> {\n if (fs === undefined) {\n throw 'Can not use readFiles plugin outside of a node environment'\n }\n\n try {\n const fileContents = await fs.readFile(path, { encoding: 'utf-8' })\n\n return {\n ok: true,\n data: normalize(fileContents),\n }\n } catch {\n return {\n ok: false,\n }\n }\n}\n\n/**\n * Creates a plugin for handling local file references.\n * This plugin validates and reads data from local filesystem paths.\n *\n * @returns A plugin object with validate and exec functions\n * @example\n * const filePlugin = readFiles()\n * if (filePlugin.validate('./local-schema.json')) {\n * const result = await filePlugin.exec('./local-schema.json')\n * }\n */\nexport function readFiles(): Plugin {\n return {\n validate: (value) => !isRemoteUrl(value),\n exec: (value) => readFile(value),\n }\n}\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,iBAAiB;AAC1B,SAAS,mBAAoD;AAE7D,MAAM,KAAK,OAAO,WAAW,cAAc,MAAM,OAAO,kBAAkB,IAAI;AAgB9E,eAAsB,SAAS,MAAsC;AACnE,MAAI,OAAO,QAAW;AACpB,UAAM;AAAA,EACR;AAEA,MAAI;AACF,UAAM,eAAe,MAAM,GAAG,SAAS,MAAM,EAAE,UAAU,QAAQ,CAAC;AAElE,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,MAAM,UAAU,YAAY;AAAA,IAC9B;AAAA,EACF,QAAQ;AACN,WAAO;AAAA,MACL,IAAI;AAAA,IACN;AAAA,EACF;AACF;AAaO,SAAS,YAAoB;AAClC,SAAO;AAAA,IACL,UAAU,CAAC,UAAU,CAAC,YAAY,KAAK;AAAA,IACvC,MAAM,CAAC,UAAU,SAAS,KAAK;AAAA,EACjC;AACF;",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
package/dist/utils/normalize.js
CHANGED
|
@@ -12,8 +12,8 @@ function normalize(content) {
|
|
|
12
12
|
return JSON.parse(content);
|
|
13
13
|
} catch (_error) {
|
|
14
14
|
const hasColon = /^[^:]+:/.test(content);
|
|
15
|
-
const
|
|
16
|
-
if (!hasColon ||
|
|
15
|
+
const isJson = content.slice(0, 50).trimStart().startsWith("{");
|
|
16
|
+
if (!hasColon || isJson) {
|
|
17
17
|
return void 0;
|
|
18
18
|
}
|
|
19
19
|
return parse(content, {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/utils/normalize.ts"],
|
|
4
|
-
"sourcesContent": ["import type { UnknownObject } from '@scalar/types/utils'\nimport { parse } from 'yaml'\n\nimport type { Filesystem } from '@/types/index'\nimport { isFilesystem } from './is-filesystem'\n\n/**\n * Normalize the OpenAPI document (YAML, JSON, object) to a JavaScript object.\n *\n * Doesn\u2019t modify the object if it\u2019s a `Filesystem` (multiple files) already.\n */\nexport function normalize(content: string | UnknownObject | Filesystem): UnknownObject | Filesystem {\n if (content === null) {\n return undefined\n }\n\n if (typeof content === 'string') {\n if (content.trim() === '') {\n return undefined\n }\n\n try {\n return JSON.parse(content)\n } catch (_error) {\n // Does it look like YAML?\n const hasColon = /^[^:]+:/.test(content)\n const
|
|
5
|
-
"mappings": "AACA,SAAS,aAAa;AAGtB,SAAS,oBAAoB;AAOtB,SAAS,UAAU,SAA0E;AAClG,MAAI,YAAY,MAAM;AACpB,WAAO;AAAA,EACT;AAEA,MAAI,OAAO,YAAY,UAAU;AAC/B,QAAI,QAAQ,KAAK,MAAM,IAAI;AACzB,aAAO;AAAA,IACT;AAEA,QAAI;AACF,aAAO,KAAK,MAAM,OAAO;AAAA,IAC3B,SAAS,QAAQ;AAEf,YAAM,WAAW,UAAU,KAAK,OAAO;AACvC,YAAM,
|
|
4
|
+
"sourcesContent": ["import type { UnknownObject } from '@scalar/types/utils'\nimport { parse } from 'yaml'\n\nimport type { Filesystem } from '@/types/index'\nimport { isFilesystem } from './is-filesystem'\n\n/**\n * Normalize the OpenAPI document (YAML, JSON, object) to a JavaScript object.\n *\n * Doesn\u2019t modify the object if it\u2019s a `Filesystem` (multiple files) already.\n */\nexport function normalize(content: string | UnknownObject | Filesystem): UnknownObject | Filesystem {\n if (content === null) {\n return undefined\n }\n\n if (typeof content === 'string') {\n if (content.trim() === '') {\n return undefined\n }\n\n try {\n return JSON.parse(content)\n } catch (_error) {\n // Does it look like YAML?\n const hasColon = /^[^:]+:/.test(content)\n const isJson = content.slice(0, 50).trimStart().startsWith('{')\n\n if (!hasColon || isJson) {\n return undefined\n }\n\n return parse(content, {\n maxAliasCount: 10000,\n })\n }\n }\n\n if (isFilesystem(content)) {\n return content\n }\n\n return content\n}\n"],
|
|
5
|
+
"mappings": "AACA,SAAS,aAAa;AAGtB,SAAS,oBAAoB;AAOtB,SAAS,UAAU,SAA0E;AAClG,MAAI,YAAY,MAAM;AACpB,WAAO;AAAA,EACT;AAEA,MAAI,OAAO,YAAY,UAAU;AAC/B,QAAI,QAAQ,KAAK,MAAM,IAAI;AACzB,aAAO;AAAA,IACT;AAEA,QAAI;AACF,aAAO,KAAK,MAAM,OAAO;AAAA,IAC3B,SAAS,QAAQ;AAEf,YAAM,WAAW,UAAU,KAAK,OAAO;AACvC,YAAM,SAAS,QAAQ,MAAM,GAAG,EAAE,EAAE,UAAU,EAAE,WAAW,GAAG;AAE9D,UAAI,CAAC,YAAY,QAAQ;AACvB,eAAO;AAAA,MACT;AAEA,aAAO,MAAM,SAAS;AAAA,QACpB,eAAe;AAAA,MACjB,CAAC;AAAA,IACH;AAAA,EACF;AAEA,MAAI,aAAa,OAAO,GAAG;AACzB,WAAO;AAAA,EACT;AAEA,SAAO;AACT;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/package.json
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"parser",
|
|
18
18
|
"typescript"
|
|
19
19
|
],
|
|
20
|
-
"version": "0.
|
|
20
|
+
"version": "0.14.0",
|
|
21
21
|
"engines": {
|
|
22
22
|
"node": ">=20"
|
|
23
23
|
},
|
|
@@ -58,14 +58,14 @@
|
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"@apidevtools/swagger-parser": "^10.1.0",
|
|
60
60
|
"@types/node": "^20.17.10",
|
|
61
|
-
"
|
|
61
|
+
"fastify": "^4.26.2",
|
|
62
62
|
"json-to-ast": "^2.1.0",
|
|
63
63
|
"just-diff": "^6.0.2",
|
|
64
64
|
"tinybench": "^2.8.0",
|
|
65
65
|
"vite": "5.4.19",
|
|
66
66
|
"@scalar/openapi-types": "0.3.1",
|
|
67
|
-
"@scalar/
|
|
68
|
-
"@scalar/
|
|
67
|
+
"@scalar/types": "0.2.1",
|
|
68
|
+
"@scalar/build-tooling": "0.2.1"
|
|
69
69
|
},
|
|
70
70
|
"scripts": {
|
|
71
71
|
"build": "scalar-build-esbuild",
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"lint:fix": "eslint . --fix",
|
|
75
75
|
"test": "NODE_OPTIONS=--max_old_space_size=8192 vitest",
|
|
76
76
|
"test:prepare": "vite-node scripts/load-files.ts",
|
|
77
|
-
"test:unit": "
|
|
77
|
+
"test:unit": "vitest",
|
|
78
78
|
"types:build": "scalar-types-build",
|
|
79
79
|
"types:check": "scalar-types-check"
|
|
80
80
|
}
|