@scalar/openapi-parser 0.19.0 → 0.20.1

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,38 @@
1
1
  # @scalar/openapi-parser
2
2
 
3
+ ## 0.20.1
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [88385b1]
8
+ - @scalar/json-magic@0.3.1
9
+
10
+ ## 0.20.0
11
+
12
+ ### Minor Changes
13
+
14
+ - c418e92: feat(openapi-parser): join multiple openapi documents
15
+
16
+ ### Patch Changes
17
+
18
+ - d4adeba: fix: ensure upgrader doesn't lose schema properties
19
+ - 66b18fc: feat: update the references to handle $refs from the magic proxy
20
+ - 5f022b5: feat: use registry.scalar.com URLs as example
21
+ - Updated dependencies [b93e1fe]
22
+ - Updated dependencies [0fcd446]
23
+ - Updated dependencies [c4bf497]
24
+ - Updated dependencies [d8adbed]
25
+ - Updated dependencies [0c80ef0]
26
+ - @scalar/json-magic@0.3.0
27
+ - @scalar/openapi-types@0.3.7
28
+
29
+ ## 0.19.1
30
+
31
+ ### Patch Changes
32
+
33
+ - e203e90: fix: can’t import plugins from parser package anymore
34
+ - @scalar/openapi-types@0.3.7
35
+
3
36
  ## 0.19.0
4
37
 
5
38
  ### Minor Changes
package/README.md CHANGED
@@ -210,7 +210,7 @@ import { fetchUrls } from '@scalar/openapi-parser/plugins/fetch-urls'
210
210
 
211
211
  // Load a file and all referenced files
212
212
  const { filesystem } = await load(
213
- 'https://cdn.jsdelivr.net/npm/@scalar/galaxy/dist/latest.yaml',
213
+ 'https://registry.scalar.com/@scalar/apis/galaxy/latest?format=yaml',
214
214
  {
215
215
  plugins: [fetchUrls()],
216
216
  },
@@ -227,7 +227,7 @@ import { fetchUrls } from '@scalar/openapi-parser/plugins/fetch-urls'
227
227
 
228
228
  // Load a file and all referenced files
229
229
  const { filesystem } = await load(
230
- 'https://cdn.jsdelivr.net/npm/@scalar/galaxy/dist/latest.yaml',
230
+ 'https://registry.scalar.com/@scalar/apis/galaxy/latest?format=yaml',
231
231
  {
232
232
  plugins: [
233
233
  fetchUrls({
package/dist/index.d.ts CHANGED
@@ -14,6 +14,7 @@ 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 { join } from './utils/join/index.js';
17
18
  export type { LoadResult, Filesystem, AnyObject, ErrorObject } from './types/index.js';
18
19
  export { openapi } from './utils/openapi/openapi.js';
19
20
  //# sourceMappingURL=index.d.ts.map
@@ -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;AAC7E,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,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,IAAI,EAAE,MAAM,cAAc,CAAA;AACnC,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAC7E,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAA"}
package/dist/index.js CHANGED
@@ -14,6 +14,7 @@ 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 { join } from "./utils/join/index.js";
17
18
  import { openapi } from "./utils/openapi/openapi.js";
18
19
  export {
19
20
  dereference,
@@ -21,6 +22,7 @@ export {
21
22
  filter,
22
23
  isJson,
23
24
  isYaml,
25
+ join,
24
26
  load,
25
27
  normalize,
26
28
  openapi,
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'\nexport { openapi } from './utils/openapi/openapi'\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;AAEzB,SAAS,eAAe;",
4
+ "sourcesContent": ["// biome-ignore lint/performance/noBarrelFile: <explanation>\nexport { 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 { join } from './utils/join'\nexport type { LoadResult, Filesystem, AnyObject, ErrorObject } from './types'\nexport { openapi } from './utils/openapi/openapi'\n"],
5
+ "mappings": "AACA,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,YAAY;AAErB,SAAS,eAAe;",
6
6
  "names": []
7
7
  }
@@ -0,0 +1,3 @@
1
+ export { join } from './join.js';
2
+ export type { Conflicts } from './join.js';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/utils/join/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAC7B,YAAY,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAA"}
@@ -0,0 +1,5 @@
1
+ import { join } from "./join.js";
2
+ export {
3
+ join
4
+ };
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/utils/join/index.ts"],
4
+ "sourcesContent": ["// biome-ignore lint/performance/noBarrelFile: <explanation>\nexport { join } from './join'\nexport type { Conflicts } from './join'\n"],
5
+ "mappings": "AACA,SAAS,YAAY;",
6
+ "names": []
7
+ }
@@ -0,0 +1,59 @@
1
+ import type { UnknownObject } from '../../types/index.js';
2
+ import type { OpenAPIV3_1 } from '@scalar/openapi-types';
3
+ export type Conflicts = {
4
+ type: 'path';
5
+ path: string;
6
+ method: string;
7
+ } | {
8
+ type: 'webhook';
9
+ path: string;
10
+ method: string;
11
+ } | {
12
+ type: 'component';
13
+ componentType: string;
14
+ name: string;
15
+ };
16
+ type JoinResult = {
17
+ ok: true;
18
+ document: OpenAPIV3_1.Document;
19
+ } | {
20
+ ok: false;
21
+ conflicts: Conflicts[];
22
+ };
23
+ /**
24
+ * Joins multiple OpenAPI documents into a single document.
25
+ *
26
+ * - Merges the "info" object, paths, webhooks, tags, and servers from all input documents.
27
+ * - If there are conflicting paths or webhooks (same path and method), returns a list of conflicts.
28
+ * - Only the first occurrence of a tag (by name) or server (by url) is included.
29
+ * - The merge is performed in reverse order, so the first document in the input array has the highest precedence.
30
+ *
31
+ * @param inputs - Array of OpenAPI documents (UnknownObject) to join
32
+ * @returns {JoinResult} - { ok: true, document } if successful, or { ok: false, conflicts } if there are conflicts
33
+ *
34
+ * @example
35
+ * const doc1 = {
36
+ * info: { title: "API 1", version: "1.0.0" },
37
+ * paths: { "/foo": { get: { summary: "Get Foo" } } },
38
+ * tags: [{ name: "foo" }],
39
+ * servers: [{ url: "https://api1.example.com" }]
40
+ * }
41
+ * const doc2 = {
42
+ * info: { description: "Second API" },
43
+ * paths: { "/bar": { get: { summary: "Get Bar" } } },
44
+ * tags: [{ name: "bar" }],
45
+ * servers: [{ url: "https://api2.example.com" }]
46
+ * }
47
+ * const result = join([doc1, doc2])
48
+ * // result.ok === true
49
+ * // result.document.info.title === "API 1"
50
+ * // result.document.info.description === "Second API"
51
+ * // result.document.paths has both "/foo" and "/bar"
52
+ * // result.document.tags contains both "foo" and "bar"
53
+ * // result.document.servers contains both server URLs
54
+ */
55
+ export declare const join: (inputs: UnknownObject[], config?: {
56
+ prefixComponents: string[];
57
+ }) => Promise<JoinResult>;
58
+ export {};
59
+ //# sourceMappingURL=join.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"join.d.ts","sourceRoot":"","sources":["../../../src/utils/join/join.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAI5C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAuOxD,MAAM,MAAM,SAAS,GACjB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAC9C;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACjD;IAAE,IAAI,EAAE,WAAW,CAAC;IAAC,aAAa,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAA;AAC9D,KAAK,UAAU,GAAG;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,EAAE,WAAW,CAAC,QAAQ,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,SAAS,EAAE,SAAS,EAAE,CAAA;CAAE,CAAA;AAEtG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,eAAO,MAAM,IAAI,WAAkB,aAAa,EAAE,WAAW;IAAE,gBAAgB,EAAE,MAAM,EAAE,CAAA;CAAE,KAAG,OAAO,CAAC,UAAU,CAkE/G,CAAA"}
@@ -0,0 +1,178 @@
1
+ import { mergeObjects } from "../../utils/join/merge-objects.js";
2
+ import { upgrade } from "../../utils/upgrade.js";
3
+ import { bundle } from "@scalar/json-magic/bundle";
4
+ const getSetIntersection = (a, b) => {
5
+ const result = [];
6
+ for (const value of a) {
7
+ if (b.has(value)) {
8
+ result.push(value);
9
+ }
10
+ }
11
+ return result;
12
+ };
13
+ const withDefault = (value, defaultValue) => {
14
+ if (Array.isArray(value)) {
15
+ return value.length ? value : defaultValue;
16
+ }
17
+ if (typeof value === "object" && value !== null) {
18
+ return Object.keys(value).length ? value : defaultValue;
19
+ }
20
+ return value ?? defaultValue;
21
+ };
22
+ const mergePaths = (inputs) => {
23
+ const result = {};
24
+ const conflicts = [];
25
+ for (const paths of inputs) {
26
+ if (typeof paths !== "object") {
27
+ continue;
28
+ }
29
+ for (const [path, pathItem] of Object.entries(paths)) {
30
+ if (!result[path]) {
31
+ result[path] = pathItem;
32
+ continue;
33
+ }
34
+ const intersectingKeys = getSetIntersection(new Set(Object.keys(result[path])), new Set(Object.keys(pathItem)));
35
+ result[path] = { ...result[path], ...pathItem };
36
+ intersectingKeys.forEach((key) => conflicts.push({ method: key, path }));
37
+ }
38
+ }
39
+ return { paths: result, conflicts };
40
+ };
41
+ const mergeTags = (inputs) => {
42
+ const cache = /* @__PURE__ */ new Set();
43
+ const result = [];
44
+ for (const tags of inputs) {
45
+ for (const tag of tags) {
46
+ if (!cache.has(tag.name)) {
47
+ result.push(tag);
48
+ }
49
+ cache.add(tag.name);
50
+ }
51
+ }
52
+ return result;
53
+ };
54
+ const mergeServers = (inputs) => {
55
+ const cache = /* @__PURE__ */ new Set();
56
+ const result = [];
57
+ for (const servers of inputs) {
58
+ for (const server of servers) {
59
+ if (!cache.has(server.url)) {
60
+ result.push(server);
61
+ }
62
+ cache.add(server.url);
63
+ }
64
+ }
65
+ return result;
66
+ };
67
+ const mergeComponents = (inputs) => {
68
+ const result = {};
69
+ const conflicts = [];
70
+ for (const components of inputs) {
71
+ if (typeof components !== "object") {
72
+ continue;
73
+ }
74
+ for (const [key, value] of Object.entries(components)) {
75
+ for (const [name, component] of Object.entries(value)) {
76
+ if (!result[key]) {
77
+ result[key] = {};
78
+ }
79
+ if (result[key][name]) {
80
+ conflicts.push({ componentType: key, name });
81
+ } else {
82
+ result[key][name] = component;
83
+ }
84
+ }
85
+ }
86
+ }
87
+ return { components: result, conflicts };
88
+ };
89
+ const prefixComponents = async (inputs, prefixes) => {
90
+ for (const index of inputs.keys()) {
91
+ await bundle(inputs[index], {
92
+ treeShake: false,
93
+ urlMap: false,
94
+ plugins: [
95
+ // Plugin to update $ref values to use the prefixed component names
96
+ {
97
+ type: "lifecycle",
98
+ onBeforeNodeProcess: (node) => {
99
+ const ref = node["$ref"];
100
+ if (typeof ref !== "string") {
101
+ return;
102
+ }
103
+ if (!ref.startsWith("#/components/")) {
104
+ return;
105
+ }
106
+ const parts = ref.split("/");
107
+ if (parts.length < 4) {
108
+ return;
109
+ }
110
+ parts[3] = `${prefixes[index] ?? ""}${parts[3]}`;
111
+ node["$ref"] = parts.join("/");
112
+ }
113
+ },
114
+ // Plugin to rename component keys with the prefix
115
+ {
116
+ type: "lifecycle",
117
+ onBeforeNodeProcess: (node, context) => {
118
+ if (context.path.length === 2 && context.path[0] === "components") {
119
+ const prefix = prefixes[index];
120
+ Object.keys(node).forEach((key) => {
121
+ const newKey = `${prefix ?? ""}${key}`;
122
+ const childNode = node[key];
123
+ delete node[key];
124
+ node[newKey] = childNode;
125
+ });
126
+ }
127
+ }
128
+ }
129
+ ]
130
+ });
131
+ }
132
+ };
133
+ const join = async (inputs, config) => {
134
+ const upgraded = inputs.map((it) => upgrade(it).specification);
135
+ if (config?.prefixComponents) {
136
+ await prefixComponents(upgraded, config.prefixComponents);
137
+ }
138
+ upgraded.reverse();
139
+ const info = upgraded.reduce((acc, curr) => {
140
+ if (curr.info && typeof curr.info === "object") {
141
+ return mergeObjects(acc, curr.info);
142
+ }
143
+ return acc;
144
+ }, {});
145
+ const { paths, conflicts: pathConflicts } = mergePaths(upgraded.map((it) => it.paths ?? {}));
146
+ const { paths: webhooks, conflicts: webhookConflicts } = mergePaths(upgraded.map((it) => it.webhooks ?? {}));
147
+ const tags = mergeTags(upgraded.map((it) => it.tags ?? []));
148
+ const servers = mergeServers(upgraded.map((it) => it.servers ?? []));
149
+ const { components, conflicts: componentConflicts } = mergeComponents(upgraded.map((it) => it.components ?? {}));
150
+ const result = upgraded.reduce((acc, curr) => ({ ...acc, ...curr }), {});
151
+ const conflicts = [
152
+ ...pathConflicts.map((it) => ({ type: "path", ...it })),
153
+ ...webhookConflicts.map((it) => ({ type: "webhook", ...it })),
154
+ ...componentConflicts.map((it) => ({ type: "component", ...it }))
155
+ ];
156
+ if (conflicts.length) {
157
+ return {
158
+ ok: false,
159
+ conflicts
160
+ };
161
+ }
162
+ return {
163
+ ok: true,
164
+ document: {
165
+ ...result,
166
+ info,
167
+ paths,
168
+ webhooks: withDefault(webhooks, void 0),
169
+ tags: withDefault(tags, void 0),
170
+ servers: withDefault(servers, void 0),
171
+ components: withDefault(components, void 0)
172
+ }
173
+ };
174
+ };
175
+ export {
176
+ join
177
+ };
178
+ //# sourceMappingURL=join.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/utils/join/join.ts"],
4
+ "sourcesContent": ["import type { UnknownObject } from '@/types'\nimport { mergeObjects } from '@/utils/join/merge-objects'\nimport { upgrade } from '@/utils/upgrade'\nimport { bundle } from '@scalar/json-magic/bundle'\nimport type { OpenAPIV3_1 } from '@scalar/openapi-types'\n\n/**\n * Returns the intersection of two sets as an array.\n *\n * @param a - The first set\n * @param b - The second set\n * @returns An array containing the elements present in both sets\n *\n * @example\n * const setA = new Set([1, 2, 3, 4])\n * const setB = new Set([3, 4, 5, 6])\n * const intersection = getSetIntersection(setA, setB)\n * // intersection: [3, 4]\n */\nconst getSetIntersection = <T>(a: Set<T>, b: Set<T>): T[] => {\n const result: T[] = []\n for (const value of a) {\n if (b.has(value)) {\n result.push(value)\n }\n }\n return result\n}\n\n/**\n * Returns the value if it is not nullish (or, for arrays, not empty), otherwise returns the provided default value.\n * Useful for handling OpenAPI fields that may be missing or empty.\n */\nconst withDefault = <T, K>(value: T, defaultValue: K): T | K => {\n if (Array.isArray(value)) {\n return value.length ? value : defaultValue\n }\n\n if (typeof value === 'object' && value !== null) {\n return Object.keys(value).length ? value : defaultValue\n }\n\n return value ?? defaultValue\n}\n\n/**\n * Merges multiple OpenAPI PathsObjects into a single PathsObject.\n * - If a path does not exist in the result, it is added directly.\n * - If a path already exists, its operations (get, post, etc.) are merged.\n * - If the same operation (e.g., \"get\") exists for the same path in multiple inputs,\n * a conflict is recorded for that path and method.\n *\n * @param inputs - Array of OpenAPIV3_1.PathsObject to merge\n * @returns An object containing the merged paths and a list of conflicts\n */\nconst mergePaths = (inputs: OpenAPIV3_1.PathsObject[]) => {\n const result: OpenAPIV3_1.PathsObject = {}\n const conflicts: { path: string; method: string }[] = []\n\n for (const paths of inputs) {\n if (typeof paths !== 'object') {\n continue\n }\n\n for (const [path, pathItem] of Object.entries(paths)) {\n if (!result[path]) {\n // If the path does not exist, add it directly\n result[path] = pathItem\n continue\n }\n\n // Find intersecting operation keys (e.g., \"get\", \"post\") for this path\n const intersectingKeys = getSetIntersection(new Set(Object.keys(result[path])), new Set(Object.keys(pathItem)))\n\n // If the path exists, merge the operations (get, post, etc.)\n result[path] = { ...result[path], ...pathItem }\n // Record conflicts for each intersecting operation key\n intersectingKeys.forEach((key) => conflicts.push({ method: key, path }))\n }\n }\n\n return { paths: result, conflicts }\n}\n\n/**\n * Merges multiple arrays of OpenAPI TagObjects into a single array, ensuring uniqueness by tag name.\n * - If a tag with the same name appears in multiple arrays, only the first occurrence is included in the result.\n *\n * @param inputs - Array of arrays of OpenAPIV3_1.TagObject to merge\n * @returns An array of unique TagObjects (by name)\n */\nconst mergeTags = (inputs: OpenAPIV3_1.TagObject[][]) => {\n const cache = new Set<string>()\n const result: OpenAPIV3_1.TagObject[] = []\n\n for (const tags of inputs) {\n for (const tag of tags) {\n if (!cache.has(tag.name)) {\n result.push(tag)\n }\n cache.add(tag.name)\n }\n }\n\n return result\n}\n\n/**\n * Merges multiple arrays of OpenAPI ServerObjects into a single array, ensuring uniqueness by server URL.\n * - If a server with the same URL appears in multiple arrays, only the first occurrence is included in the result.\n *\n * @param inputs - Array of arrays of OpenAPIV3_1.ServerObject to merge\n * @returns An array of unique ServerObjects (by url)\n */\nconst mergeServers = (inputs: OpenAPIV3_1.ServerObject[][]) => {\n const cache = new Set<string>()\n const result: OpenAPIV3_1.ServerObject[] = []\n\n for (const servers of inputs) {\n for (const server of servers) {\n if (!cache.has(server.url)) {\n result.push(server)\n }\n cache.add(server.url)\n }\n }\n\n return result\n}\n\n/**\n * Merges multiple OpenAPI ComponentsObject instances into a single components object.\n * - If a component with the same type and name appears in multiple inputs, only the first occurrence is included.\n * - Any conflicts (duplicate component names within the same type) are recorded in the `conflicts` array.\n *\n * @param inputs - Array of OpenAPIV3_1.ComponentsObject to merge\n * @returns An object containing the merged components and an array of conflicts\n */\nconst mergeComponents = (inputs: OpenAPIV3_1.ComponentsObject[]) => {\n const result: OpenAPIV3_1.ComponentsObject = {}\n const conflicts: { componentType: string; name: string }[] = []\n\n for (const components of inputs) {\n if (typeof components !== 'object') {\n continue\n }\n\n // Merge each component type (schemas, responses, parameters, etc.)\n for (const [key, value] of Object.entries(components)) {\n for (const [name, component] of Object.entries(value)) {\n if (!result[key]) {\n result[key] = {}\n }\n\n if (result[key][name]) {\n // If the component already exists, record a conflict\n conflicts.push({ componentType: key, name })\n } else {\n // Otherwise, add the component\n result[key][name] = component\n }\n }\n }\n }\n\n return { components: result, conflicts }\n}\n\n/**\n * Prefixes component names and their references in multiple OpenAPI documents.\n *\n * This function mutates each input document in-place by:\n * 1. Prefixing all component names (e.g., schema names) with the corresponding prefix.\n * 2. Updating all $ref values that point to components to use the prefixed names.\n *\n * This is useful when merging multiple OpenAPI documents to avoid component name collisions.\n *\n * @param inputs - Array of OpenAPI documents to mutate.\n * @param prefixes - Array of prefixes to apply to each document's components.\n */\nconst prefixComponents = async (inputs: OpenAPIV3_1.Document[], prefixes: string[]) => {\n for (const index of inputs.keys()) {\n await bundle(inputs[index], {\n treeShake: false,\n urlMap: false,\n plugins: [\n // Plugin to update $ref values to use the prefixed component names\n {\n type: 'lifecycle',\n onBeforeNodeProcess: (node) => {\n const ref = node['$ref']\n\n if (typeof ref !== 'string') {\n return\n }\n\n // Only process refs that point to components\n if (!ref.startsWith('#/components/')) {\n return\n }\n\n const parts = ref.split('/')\n // Ensure the ref has the expected structure: #/components/{type}/{name}\n if (parts.length < 4) {\n return\n }\n\n // Prefix the component name (parts[3]) with the provided prefix\n parts[3] = `${prefixes[index] ?? ''}${parts[3]}`\n\n node['$ref'] = parts.join('/')\n },\n },\n // Plugin to rename component keys with the prefix\n {\n type: 'lifecycle',\n onBeforeNodeProcess: (node, context) => {\n // Check if the node is a component type object (e.g., schemas, responses) under \"components\"\n if (context.path.length === 2 && context.path[0] === 'components') {\n const prefix = prefixes[index]\n\n Object.keys(node).forEach((key) => {\n const newKey = `${prefix ?? ''}${key}`\n const childNode = node[key]\n delete node[key]\n node[newKey] = childNode\n })\n }\n },\n },\n ],\n })\n }\n}\n\nexport type Conflicts =\n | { type: 'path'; path: string; method: string }\n | { type: 'webhook'; path: string; method: string }\n | { type: 'component'; componentType: string; name: string }\ntype JoinResult = { ok: true; document: OpenAPIV3_1.Document } | { ok: false; conflicts: Conflicts[] }\n\n/**\n * Joins multiple OpenAPI documents into a single document.\n *\n * - Merges the \"info\" object, paths, webhooks, tags, and servers from all input documents.\n * - If there are conflicting paths or webhooks (same path and method), returns a list of conflicts.\n * - Only the first occurrence of a tag (by name) or server (by url) is included.\n * - The merge is performed in reverse order, so the first document in the input array has the highest precedence.\n *\n * @param inputs - Array of OpenAPI documents (UnknownObject) to join\n * @returns {JoinResult} - { ok: true, document } if successful, or { ok: false, conflicts } if there are conflicts\n *\n * @example\n * const doc1 = {\n * info: { title: \"API 1\", version: \"1.0.0\" },\n * paths: { \"/foo\": { get: { summary: \"Get Foo\" } } },\n * tags: [{ name: \"foo\" }],\n * servers: [{ url: \"https://api1.example.com\" }]\n * }\n * const doc2 = {\n * info: { description: \"Second API\" },\n * paths: { \"/bar\": { get: { summary: \"Get Bar\" } } },\n * tags: [{ name: \"bar\" }],\n * servers: [{ url: \"https://api2.example.com\" }]\n * }\n * const result = join([doc1, doc2])\n * // result.ok === true\n * // result.document.info.title === \"API 1\"\n * // result.document.info.description === \"Second API\"\n * // result.document.paths has both \"/foo\" and \"/bar\"\n * // result.document.tags contains both \"foo\" and \"bar\"\n * // result.document.servers contains both server URLs\n */\nexport const join = async (inputs: UnknownObject[], config?: { prefixComponents: string[] }): Promise<JoinResult> => {\n // Reverse the input list and upgrade them (first input has highest precedence)\n const upgraded = inputs.map((it) => upgrade(it).specification)\n\n // Preprocess documents by prefixing components if specified\n if (config?.prefixComponents) {\n await prefixComponents(upgraded, config.prefixComponents)\n }\n\n // Reverse the upgraded documents to ensure the first document has the highest precedence\n upgraded.reverse()\n\n // Merge only the \"info\" object from all inputs\n const info = upgraded.reduce<OpenAPIV3_1.InfoObject>((acc, curr) => {\n if (curr.info && typeof curr.info === 'object') {\n return mergeObjects(acc, curr.info)\n }\n return acc\n }, {} as OpenAPIV3_1.InfoObject)\n\n // Merge paths from all documents, collecting conflicts\n const { paths, conflicts: pathConflicts } = mergePaths(upgraded.map((it) => it.paths ?? {}))\n\n // Merge webhooks from all documents, collecting conflicts\n const { paths: webhooks, conflicts: webhookConflicts } = mergePaths(upgraded.map((it) => it.webhooks ?? {}))\n\n // Merge tags, ensuring uniqueness by tag name\n const tags = mergeTags(upgraded.map((it) => it.tags ?? []))\n\n // Merge servers, ensuring uniqueness by server url\n const servers = mergeServers(upgraded.map((it) => it.servers ?? []))\n\n // Merge components, collecting conflicts\n const { components, conflicts: componentConflicts } = mergeComponents(upgraded.map((it) => it.components ?? {}))\n\n // Merge all documents in the upgraded array into a single object (shallow merge)\n const result = upgraded.reduce<UnknownObject>((acc, curr) => ({ ...acc, ...curr }), {})\n\n // Collect all conflicts (paths and webhooks)\n const conflicts: Conflicts[] = [\n ...pathConflicts.map((it) => ({ type: 'path', ...it }) as const),\n ...webhookConflicts.map((it) => ({ type: 'webhook', ...it }) as const),\n ...componentConflicts.map((it) => ({ type: 'component', ...it }) as const),\n ]\n\n // If there are any conflicts, return them\n if (conflicts.length) {\n return {\n ok: false,\n conflicts,\n }\n }\n\n // Return the merged OpenAPI document\n return {\n ok: true,\n document: {\n ...result,\n info,\n paths,\n webhooks: withDefault(webhooks, undefined),\n tags: withDefault(tags, undefined),\n servers: withDefault(servers, undefined),\n components: withDefault(components, undefined),\n },\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Deep merges two objects, combining their properties recursively.
3
+ *
4
+ * ⚠️ Note: This operation assumes there are no key collisions between the objects.
5
+ * @param a - Target object to merge into
6
+ * @param b - Source object to merge from
7
+ * @returns The merged object (mutates and returns a)
8
+ *
9
+ * @example
10
+ * // Simple merge
11
+ * const a = { name: 'John' }
12
+ * const b = { age: 30 }
13
+ * mergeObjects(a, b) // { name: 'John', age: 30 }
14
+ *
15
+ * // Nested merge
16
+ * const a = { user: { name: 'John' } }
17
+ * const b = { user: { age: 30 } }
18
+ * mergeObjects(a, b) // { user: { name: 'John', age: 30 } }
19
+ */
20
+ export declare const mergeObjects: <R>(a: Record<string, unknown>, b: Record<string, unknown>) => R;
21
+ //# sourceMappingURL=merge-objects.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"merge-objects.d.ts","sourceRoot":"","sources":["../../../src/utils/join/merge-objects.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,YAAY,GAAI,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,CAiBxF,CAAA"}
@@ -0,0 +1,20 @@
1
+ const mergeObjects = (a, b) => {
2
+ for (const key in b) {
3
+ if (!(key in a)) {
4
+ a[key] = b[key];
5
+ } else {
6
+ const aValue = a[key];
7
+ const bValue = b[key];
8
+ if (typeof aValue === "object" && aValue !== null && typeof bValue === "object" && bValue !== null) {
9
+ mergeObjects(aValue, bValue);
10
+ } else {
11
+ a[key] = bValue;
12
+ }
13
+ }
14
+ }
15
+ return a;
16
+ };
17
+ export {
18
+ mergeObjects
19
+ };
20
+ //# sourceMappingURL=merge-objects.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/utils/join/merge-objects.ts"],
4
+ "sourcesContent": ["/**\n * Deep merges two objects, combining their properties recursively.\n *\n * \u26A0\uFE0F Note: This operation assumes there are no key collisions between the objects.\n * @param a - Target object to merge into\n * @param b - Source object to merge from\n * @returns The merged object (mutates and returns a)\n *\n * @example\n * // Simple merge\n * const a = { name: 'John' }\n * const b = { age: 30 }\n * mergeObjects(a, b) // { name: 'John', age: 30 }\n *\n * // Nested merge\n * const a = { user: { name: 'John' } }\n * const b = { user: { age: 30 } }\n * mergeObjects(a, b) // { user: { name: 'John', age: 30 } }\n */\nexport const mergeObjects = <R>(a: Record<string, unknown>, b: Record<string, unknown>): R => {\n for (const key in b) {\n if (!(key in a)) {\n a[key] = b[key]\n } else {\n const aValue = a[key]\n const bValue = b[key]\n\n if (typeof aValue === 'object' && aValue !== null && typeof bValue === 'object' && bValue !== null) {\n mergeObjects(aValue as Record<string, unknown>, bValue as Record<string, unknown>)\n } else {\n a[key] = bValue // Overwrite with b's value if not an object\n }\n }\n }\n\n return a as R\n}\n"],
5
+ "mappings": "AAmBO,MAAM,eAAe,CAAI,GAA4B,MAAkC;AAC5F,aAAW,OAAO,GAAG;AACnB,QAAI,EAAE,OAAO,IAAI;AACf,QAAE,GAAG,IAAI,EAAE,GAAG;AAAA,IAChB,OAAO;AACL,YAAM,SAAS,EAAE,GAAG;AACpB,YAAM,SAAS,EAAE,GAAG;AAEpB,UAAI,OAAO,WAAW,YAAY,WAAW,QAAQ,OAAO,WAAW,YAAY,WAAW,MAAM;AAClG,qBAAa,QAAmC,MAAiC;AAAA,MACnF,OAAO;AACL,UAAE,GAAG,IAAI;AAAA,MACX;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;",
6
+ "names": []
7
+ }
@@ -77,23 +77,27 @@ const applyChangesToDocument = (schema, path) => {
77
77
  if (path.includes("content") && path.includes("application/octet-stream")) {
78
78
  return {};
79
79
  }
80
+ const { format: _, ...rest } = schema;
80
81
  if (schema.type === "string") {
81
82
  if (schema.format === "binary") {
82
83
  return {
84
+ ...rest,
83
85
  type: "string",
84
86
  contentMediaType: "application/octet-stream"
85
87
  };
86
88
  }
87
89
  if (schema.format === "base64") {
88
90
  return {
91
+ ...rest,
89
92
  type: "string",
90
93
  contentEncoding: "base64"
91
94
  };
92
95
  }
93
96
  if (schema.format === "byte") {
94
97
  const parentPath = path.slice(0, -1);
95
- const contentMediaType = parentPath.find((_, index) => path[index - 1] === "content");
98
+ const contentMediaType = parentPath.find((_2, index) => path[index - 1] === "content");
96
99
  return {
100
+ ...rest,
97
101
  type: "string",
98
102
  contentEncoding: "base64",
99
103
  contentMediaType
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/utils/upgrade-from-three-to-three-one.ts"],
4
- "sourcesContent": ["import type { OpenAPIV3_1 } from '@scalar/openapi-types'\nimport type { UnknownObject } from '@scalar/types/utils'\n\nimport { traverse } from './traverse'\n\n// Create Sets for faster schema path lookups\nconst SCHEMA_SEGMENTS = new Set([\n 'properties',\n 'items',\n 'allOf',\n 'anyOf',\n 'oneOf',\n 'not',\n 'additionalProperties',\n 'schema',\n])\n\n/** Determine if the current path is within a schema - optimized version */\nexport function isSchemaPath(path: string[]): boolean {\n // Check for schema segments first (most common case)\n if (path.some((segment) => SCHEMA_SEGMENTS.has(segment))) {\n return true\n }\n\n // Check for schema suffix\n if (path.some((segment) => segment.endsWith('Schema'))) {\n return true\n }\n\n // Check for components/schemas path\n if (path.length >= 2 && path[0] === 'components' && path[1] === 'schemas') {\n return true\n }\n\n return false\n}\n\n/**\n * Upgrade from OpenAPI 3.0.x to 3.1.1\n *\n * https://www.openapis.org/blog/2021/02/16/migrating-from-openapi-3-0-to-3-1-0\n */\nexport function upgradeFromThreeToThreeOne(originalContent: UnknownObject) {\n let content = originalContent\n\n // Version check - early return if not 3.0.x\n if (content === null || typeof content.openapi !== 'string' || !content.openapi.startsWith('3.0')) {\n return content\n }\n\n content.openapi = '3.1.1'\n\n // Single traversal that handles all transformations\n content = traverse(content, applyChangesToDocument)\n\n return content as OpenAPIV3_1.Document\n}\n\nconst applyChangesToDocument = (schema: UnknownObject, path: string[]) => {\n // 1. Handle nullable types\n if (schema.type !== undefined && schema.nullable === true) {\n schema.type = [schema.type, 'null']\n delete schema.nullable\n }\n\n // 2. Handle exclusiveMinimum and exclusiveMaximum\n if (schema.exclusiveMinimum === true) {\n schema.exclusiveMinimum = schema.minimum\n delete schema.minimum\n } else if (schema.exclusiveMinimum === false) {\n delete schema.exclusiveMinimum\n }\n\n if (schema.exclusiveMaximum === true) {\n schema.exclusiveMaximum = schema.maximum\n delete schema.maximum\n } else if (schema.exclusiveMaximum === false) {\n delete schema.exclusiveMaximum\n }\n\n // 3. Handle examples\n if (schema.example !== undefined) {\n if (isSchemaPath(path)) {\n schema.examples = [schema.example]\n } else {\n schema.examples = {\n default: {\n value: schema.example,\n },\n }\n }\n delete schema.example\n }\n\n // 4. Handle multipart file uploads\n if (schema.type === 'object' && schema.properties !== undefined) {\n const parentPath = path.slice(0, -1)\n const isMultipart = parentPath.some((segment, index) => {\n return segment === 'content' && path[index + 1] === 'multipart/form-data'\n })\n\n if (isMultipart) {\n for (const value of Object.values(schema.properties)) {\n if (\n typeof value === 'object' &&\n value !== null &&\n 'type' in value &&\n 'format' in value &&\n value.type === 'string' &&\n value.format === 'binary'\n ) {\n ;(value as any).contentMediaType = 'application/octet-stream'\n delete (value as any).format\n }\n }\n }\n }\n\n // 5. Handle binary file uploads\n if (path.includes('content') && path.includes('application/octet-stream')) {\n return {}\n }\n\n if (schema.type === 'string') {\n if (schema.format === 'binary') {\n return {\n type: 'string',\n contentMediaType: 'application/octet-stream',\n }\n }\n\n if (schema.format === 'base64') {\n return {\n type: 'string',\n contentEncoding: 'base64',\n }\n }\n\n if (schema.format === 'byte') {\n const parentPath = path.slice(0, -1)\n const contentMediaType = parentPath.find((_, index) => path[index - 1] === 'content')\n return {\n type: 'string',\n contentEncoding: 'base64',\n contentMediaType,\n }\n }\n }\n\n // 6. Handle x-webhooks\n if (schema['x-webhooks'] !== undefined) {\n schema.webhooks = schema['x-webhooks']\n delete schema['x-webhooks']\n }\n\n return schema\n}\n"],
5
- "mappings": "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",
6
- "names": []
4
+ "sourcesContent": ["import type { OpenAPIV3_1 } from '@scalar/openapi-types'\nimport type { UnknownObject } from '@scalar/types/utils'\n\nimport { traverse } from './traverse'\n\n// Create Sets for faster schema path lookups\nconst SCHEMA_SEGMENTS = new Set([\n 'properties',\n 'items',\n 'allOf',\n 'anyOf',\n 'oneOf',\n 'not',\n 'additionalProperties',\n 'schema',\n])\n\n/** Determine if the current path is within a schema - optimized version */\nexport function isSchemaPath(path: string[]): boolean {\n // Check for schema segments first (most common case)\n if (path.some((segment) => SCHEMA_SEGMENTS.has(segment))) {\n return true\n }\n\n // Check for schema suffix\n if (path.some((segment) => segment.endsWith('Schema'))) {\n return true\n }\n\n // Check for components/schemas path\n if (path.length >= 2 && path[0] === 'components' && path[1] === 'schemas') {\n return true\n }\n\n return false\n}\n\n/**\n * Upgrade from OpenAPI 3.0.x to 3.1.1\n *\n * https://www.openapis.org/blog/2021/02/16/migrating-from-openapi-3-0-to-3-1-0\n */\nexport function upgradeFromThreeToThreeOne(originalContent: UnknownObject) {\n let content = originalContent\n\n // Version check - early return if not 3.0.x\n if (content === null || typeof content.openapi !== 'string' || !content.openapi.startsWith('3.0')) {\n return content\n }\n\n content.openapi = '3.1.1'\n\n // Single traversal that handles all transformations\n content = traverse(content, applyChangesToDocument)\n\n return content as OpenAPIV3_1.Document\n}\n\nconst applyChangesToDocument = (schema: UnknownObject, path: string[]) => {\n // 1. Handle nullable types\n if (schema.type !== undefined && schema.nullable === true) {\n schema.type = [schema.type, 'null']\n delete schema.nullable\n }\n\n // 2. Handle exclusiveMinimum and exclusiveMaximum\n if (schema.exclusiveMinimum === true) {\n schema.exclusiveMinimum = schema.minimum\n delete schema.minimum\n } else if (schema.exclusiveMinimum === false) {\n delete schema.exclusiveMinimum\n }\n\n if (schema.exclusiveMaximum === true) {\n schema.exclusiveMaximum = schema.maximum\n delete schema.maximum\n } else if (schema.exclusiveMaximum === false) {\n delete schema.exclusiveMaximum\n }\n\n // 3. Handle examples\n if (schema.example !== undefined) {\n if (isSchemaPath(path)) {\n schema.examples = [schema.example]\n } else {\n schema.examples = {\n default: {\n value: schema.example,\n },\n }\n }\n delete schema.example\n }\n\n // 4. Handle multipart file uploads\n if (schema.type === 'object' && schema.properties !== undefined) {\n const parentPath = path.slice(0, -1)\n const isMultipart = parentPath.some((segment, index) => {\n return segment === 'content' && path[index + 1] === 'multipart/form-data'\n })\n\n if (isMultipart) {\n for (const value of Object.values(schema.properties)) {\n if (\n typeof value === 'object' &&\n value !== null &&\n 'type' in value &&\n 'format' in value &&\n value.type === 'string' &&\n value.format === 'binary'\n ) {\n ;(value as any).contentMediaType = 'application/octet-stream'\n delete (value as any).format\n }\n }\n }\n }\n\n // 5. Handle binary file uploads\n if (path.includes('content') && path.includes('application/octet-stream')) {\n return {}\n }\n\n // 6. Handle older formats\n const { format: _, ...rest } = schema\n\n if (schema.type === 'string') {\n if (schema.format === 'binary') {\n return {\n ...rest,\n type: 'string',\n contentMediaType: 'application/octet-stream',\n }\n }\n\n if (schema.format === 'base64') {\n return {\n ...rest,\n type: 'string',\n contentEncoding: 'base64',\n }\n }\n\n if (schema.format === 'byte') {\n const parentPath = path.slice(0, -1)\n const contentMediaType = parentPath.find((_, index) => path[index - 1] === 'content')\n return {\n ...rest,\n type: 'string',\n contentEncoding: 'base64',\n contentMediaType,\n }\n }\n }\n\n // 7. Handle x-webhooks\n if (schema['x-webhooks'] !== undefined) {\n schema.webhooks = schema['x-webhooks']\n delete schema['x-webhooks']\n }\n\n return schema\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["_"]
7
7
  }
package/package.json CHANGED
@@ -17,7 +17,7 @@
17
17
  "parser",
18
18
  "typescript"
19
19
  ],
20
- "version": "0.19.0",
20
+ "version": "0.20.1",
21
21
  "engines": {
22
22
  "node": ">=20"
23
23
  },
@@ -29,6 +29,16 @@
29
29
  "import": "./dist/index.js",
30
30
  "types": "./dist/index.d.ts",
31
31
  "default": "./dist/index.js"
32
+ },
33
+ "./plugins/fetch-urls": {
34
+ "import": "./dist/plugins/fetch-urls/index.js",
35
+ "types": "./dist/plugins/fetch-urls/index.d.ts",
36
+ "default": "./dist/plugins/fetch-urls/index.js"
37
+ },
38
+ "./plugins/read-files": {
39
+ "import": "./dist/plugins/read-files/index.js",
40
+ "types": "./dist/plugins/read-files/index.d.ts",
41
+ "default": "./dist/plugins/read-files/index.js"
32
42
  }
33
43
  },
34
44
  "files": [
@@ -44,6 +54,7 @@
44
54
  "jsonpointer": "^5.0.1",
45
55
  "leven": "^4.0.0",
46
56
  "yaml": "2.8.0",
57
+ "@scalar/json-magic": "0.3.1",
47
58
  "@scalar/openapi-types": "0.3.7"
48
59
  },
49
60
  "devDependencies": {
@@ -54,8 +65,8 @@
54
65
  "just-diff": "^6.0.2",
55
66
  "tinybench": "^2.8.0",
56
67
  "vite": "6.1.6",
57
- "@scalar/types": "0.2.11",
58
- "@scalar/build-tooling": "0.2.4"
68
+ "@scalar/build-tooling": "0.2.6",
69
+ "@scalar/types": "0.2.13"
59
70
  },
60
71
  "scripts": {
61
72
  "build": "scalar-build-esbuild",