@scalar/openapi-parser 0.17.0 → 0.18.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,17 @@
1
1
  # @scalar/openapi-parser
2
2
 
3
+ ## 0.18.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 2d7f995: refactor: use more common straight apostrophe ' instead of the real apostrophe ’
8
+
9
+ ## 0.18.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 291f09d: feat(openapi-parser): ensure unique hashes and support custom compression
14
+
3
15
  ## 0.17.0
4
16
 
5
17
  ### Minor Changes
package/README.md CHANGED
@@ -293,7 +293,7 @@ const { specification } = filter(
293
293
 
294
294
  ### Upgrade your OpenAPI document
295
295
 
296
- There’s an `upgrade` command to upgrade all your OpenAPI documents to the latest OpenAPI version.
296
+ There's an `upgrade` command to upgrade all your OpenAPI documents to the latest OpenAPI version.
297
297
 
298
298
  ```ts
299
299
  import { upgrade } from '@scalar/openapi-parser'
@@ -320,7 +320,7 @@ and adds them to the global tags array and normalizes security scheme types.
320
320
  This makes your document as OpenAPI-compliant as possible with minimal effort, handling many common specification
321
321
  requirements automatically.
322
322
 
323
- > ⚠️ This doesn’t support Swagger 2.0 documents.
323
+ > ⚠️ This doesn't support Swagger 2.0 documents.
324
324
 
325
325
  ```ts
326
326
  import { sanitize } from '@scalar/openapi-parser'
@@ -336,7 +336,7 @@ console.log(result)
336
336
 
337
337
  ### Then/Catch syntax
338
338
 
339
- If you’re more the then/catch type of guy, that’s fine:
339
+ If you're more the then/catch type of guy, that's fine:
340
340
 
341
341
  ```ts
342
342
  import { validate } from '@scalar/openapi-parser'
@@ -400,7 +400,7 @@ const { filesystem } = await load('./openapi.yaml', {
400
400
  const result = await dereference(filesystem)
401
401
  ```
402
402
 
403
- As you see, `load()` supports plugins. You can write your own plugin, if you’d like to fetch API defintions from another data source, for example your database. Look at the source code of the `readFiles` to learn how this could look like.
403
+ As you see, `load()` supports plugins. You can write your own plugin, if you'd like to fetch API defintions from another data source, for example your database. Look at the source code of the `readFiles` to learn how this could look like.
404
404
 
405
405
  #### Directly load URLs
406
406
 
@@ -421,7 +421,7 @@ const { filesystem } = await load(
421
421
 
422
422
  #### Intercept HTTP requests
423
423
 
424
- If you’re using the package in a browser environment, you may run into CORS issues when fetching from URLs. You can intercept the requests, for example to use a proxy, though:
424
+ If you're using the package in a browser environment, you may run into CORS issues when fetching from URLs. You can intercept the requests, for example to use a proxy, though:
425
425
 
426
426
  ```ts
427
427
  import { dereference, load } from '@scalar/openapi-parser'
@@ -442,15 +442,15 @@ const { filesystem } = await load(
442
442
 
443
443
  ## Community
444
444
 
445
- We are API nerds. You too? Let’s chat on Discord: <https://discord.gg/scalar>
445
+ We are API nerds. You too? Let's chat on Discord: <https://discord.gg/scalar>
446
446
 
447
447
  ## Thank you!
448
448
 
449
449
  Thanks a ton for all the help and inspiration:
450
450
 
451
- - [@philsturgeon](https://github.com/philsturgeon) to make sure we build something we won’t hate.
451
+ - [@philsturgeon](https://github.com/philsturgeon) to make sure we build something we won't hate.
452
452
  - We took a lot of inspiration from [@seriousme](https://github.com/seriousme) and his package [openapi-schema-validator](https://github.com/seriousme/openapi-schema-validator) early-on.
453
- - You could consider this package the modern successor of [@apidevtools/swagger-parser](https://github.com/APIDevTools/swagger-parser), we even test against it to make sure we’re getting the same results (where intended).
453
+ - You could consider this package the modern successor of [@apidevtools/swagger-parser](https://github.com/APIDevTools/swagger-parser), we even test against it to make sure we're getting the same results (where intended).
454
454
  - We stole a lot of example specification from [@mermade](https://github.com/mermade) to test against.
455
455
 
456
456
  ## License
@@ -3897,10 +3897,10 @@ export declare const OpenApiVersions: OpenApiVersion[];
3897
3897
  * List of error messages used in the Validator
3898
3898
  */
3899
3899
  export declare const ERRORS: {
3900
- readonly EMPTY_OR_INVALID: "Can’t find JSON, YAML or filename in data.";
3901
- readonly OPENAPI_VERSION_NOT_SUPPORTED: "Can’t find supported Swagger/OpenAPI version in the provided document, version must be a string.";
3902
- readonly INVALID_REFERENCE: "Can’t resolve reference: %s";
3903
- readonly EXTERNAL_REFERENCE_NOT_FOUND: "Can’t resolve external reference: %s";
3900
+ readonly EMPTY_OR_INVALID: "Can't find JSON, YAML or filename in data.";
3901
+ readonly OPENAPI_VERSION_NOT_SUPPORTED: "Can't find supported Swagger/OpenAPI version in the provided document, version must be a string.";
3902
+ readonly INVALID_REFERENCE: "Can't resolve reference: %s";
3903
+ readonly EXTERNAL_REFERENCE_NOT_FOUND: "Can't resolve external reference: %s";
3904
3904
  readonly FILE_DOES_NOT_EXIST: "File does not exist: %s";
3905
3905
  readonly NO_CONTENT: "No content found";
3906
3906
  };
@@ -8,11 +8,11 @@ const OpenApiSpecifications = {
8
8
  };
9
9
  const OpenApiVersions = Object.keys(OpenApiSpecifications);
10
10
  const ERRORS = {
11
- EMPTY_OR_INVALID: "Can\u2019t find JSON, YAML or filename in data.",
11
+ EMPTY_OR_INVALID: "Can't find JSON, YAML or filename in data.",
12
12
  // URI_MUST_BE_STRING: 'uri parameter or $id attribute must be a string',
13
- OPENAPI_VERSION_NOT_SUPPORTED: "Can\u2019t find supported Swagger/OpenAPI version in the provided document, version must be a string.",
14
- INVALID_REFERENCE: "Can\u2019t resolve reference: %s",
15
- EXTERNAL_REFERENCE_NOT_FOUND: "Can\u2019t resolve external reference: %s",
13
+ OPENAPI_VERSION_NOT_SUPPORTED: "Can't find supported Swagger/OpenAPI version in the provided document, version must be a string.",
14
+ INVALID_REFERENCE: "Can't resolve reference: %s",
15
+ EXTERNAL_REFERENCE_NOT_FOUND: "Can't resolve external reference: %s",
16
16
  FILE_DOES_NOT_EXIST: "File does not exist: %s",
17
17
  NO_CONTENT: "No content found"
18
18
  };
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/configuration/index.ts"],
4
- "sourcesContent": ["import Swagger20 from '@/schemas/v2.0/schema'\nimport OpenApi30 from '@/schemas/v3.0/schema'\nimport OpenApi31 from '@/schemas/v3.1/schema'\n\n/**\n * A list of the supported OpenAPI specifications\n */\nexport const OpenApiSpecifications = {\n '2.0': Swagger20,\n '3.0': OpenApi30,\n '3.1': OpenApi31,\n}\n\nexport type OpenApiVersion = keyof typeof OpenApiSpecifications\n\nexport const OpenApiVersions = Object.keys(OpenApiSpecifications) as OpenApiVersion[]\n\n/**\n * List of error messages used in the Validator\n */\nexport const ERRORS = {\n EMPTY_OR_INVALID: 'Can\u2019t find JSON, YAML or filename in data.',\n // URI_MUST_BE_STRING: 'uri parameter or $id attribute must be a string',\n OPENAPI_VERSION_NOT_SUPPORTED:\n 'Can\u2019t find supported Swagger/OpenAPI version in the provided document, version must be a string.',\n INVALID_REFERENCE: 'Can\u2019t resolve reference: %s',\n EXTERNAL_REFERENCE_NOT_FOUND: 'Can\u2019t resolve external reference: %s',\n FILE_DOES_NOT_EXIST: 'File does not exist: %s',\n NO_CONTENT: 'No content found',\n} as const\n\nexport type ValidationError = keyof typeof ERRORS\n"],
4
+ "sourcesContent": ["import Swagger20 from '@/schemas/v2.0/schema'\nimport OpenApi30 from '@/schemas/v3.0/schema'\nimport OpenApi31 from '@/schemas/v3.1/schema'\n\n/**\n * A list of the supported OpenAPI specifications\n */\nexport const OpenApiSpecifications = {\n '2.0': Swagger20,\n '3.0': OpenApi30,\n '3.1': OpenApi31,\n}\n\nexport type OpenApiVersion = keyof typeof OpenApiSpecifications\n\nexport const OpenApiVersions = Object.keys(OpenApiSpecifications) as OpenApiVersion[]\n\n/**\n * List of error messages used in the Validator\n */\nexport const ERRORS = {\n EMPTY_OR_INVALID: \"Can't find JSON, YAML or filename in data.\",\n // URI_MUST_BE_STRING: 'uri parameter or $id attribute must be a string',\n OPENAPI_VERSION_NOT_SUPPORTED:\n \"Can't find supported Swagger/OpenAPI version in the provided document, version must be a string.\",\n INVALID_REFERENCE: \"Can't resolve reference: %s\",\n EXTERNAL_REFERENCE_NOT_FOUND: \"Can't resolve external reference: %s\",\n FILE_DOES_NOT_EXIST: 'File does not exist: %s',\n NO_CONTENT: 'No content found',\n} as const\n\nexport type ValidationError = keyof typeof ERRORS\n"],
5
5
  "mappings": "AAAA,OAAO,eAAe;AACtB,OAAO,eAAe;AACtB,OAAO,eAAe;AAKf,MAAM,wBAAwB;AAAA,EACnC,OAAO;AAAA,EACP,OAAO;AAAA,EACP,OAAO;AACT;AAIO,MAAM,kBAAkB,OAAO,KAAK,qBAAqB;AAKzD,MAAM,SAAS;AAAA,EACpB,kBAAkB;AAAA;AAAA,EAElB,+BACE;AAAA,EACF,mBAAmB;AAAA,EACnB,8BAA8B;AAAA,EAC9B,qBAAqB;AAAA,EACrB,YAAY;AACd;",
6
6
  "names": []
7
7
  }
@@ -53,7 +53,7 @@ export type AjvOptions = {
53
53
  */
54
54
  export type Filesystem = FilesystemEntry[];
55
55
  /**
56
- * Holds all information about a single file (doesn’t have to be a literal file, see Filesystem).
56
+ * Holds all information about a single file (doesn't have to be a literal file, see Filesystem).
57
57
  */
58
58
  export type FilesystemEntry = {
59
59
  dir: string;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/utils/betterAjvErrors/json/get-meta-from-path.ts"],
4
- "sourcesContent": ["import { getPointers } from './utils'\n\nexport default function getMetaFromPath(jsonAst, dataPath, includeIdentifierLocation) {\n const pointers = getPointers(dataPath)\n const lastPointerIndex = pointers.length - 1\n return pointers.reduce((obj, pointer, idx) => {\n switch (obj?.type) {\n case 'Object': {\n const filtered = obj.members.filter((child) => child.name.value === pointer)\n if (filtered.length !== 1) {\n throw new Error(`Couldn't find property ${pointer} of ${dataPath}`)\n }\n\n const { name, value } = filtered[0]\n return includeIdentifierLocation && idx === lastPointerIndex ? name : value\n }\n case 'Array':\n return obj.elements[pointer]\n default:\n // Unexpected type\n // console.log(obj)\n\n // Anyway, if there\u2019s location, let\u2019s just use it.\n if (obj.loc) {\n return obj\n }\n }\n }, jsonAst.body)\n}\n"],
4
+ "sourcesContent": ["import { getPointers } from './utils'\n\nexport default function getMetaFromPath(jsonAst, dataPath, includeIdentifierLocation) {\n const pointers = getPointers(dataPath)\n const lastPointerIndex = pointers.length - 1\n return pointers.reduce((obj, pointer, idx) => {\n switch (obj?.type) {\n case 'Object': {\n const filtered = obj.members.filter((child) => child.name.value === pointer)\n if (filtered.length !== 1) {\n throw new Error(`Couldn't find property ${pointer} of ${dataPath}`)\n }\n\n const { name, value } = filtered[0]\n return includeIdentifierLocation && idx === lastPointerIndex ? name : value\n }\n case 'Array':\n return obj.elements[pointer]\n default:\n // Unexpected type\n // console.log(obj)\n\n // Anyway, if there's location, let's just use it.\n if (obj.loc) {\n return obj\n }\n }\n }, jsonAst.body)\n}\n"],
5
5
  "mappings": "AAAA,SAAS,mBAAmB;AAEb,SAAR,gBAAiC,SAAS,UAAU,2BAA2B;AACpF,QAAM,WAAW,YAAY,QAAQ;AACrC,QAAM,mBAAmB,SAAS,SAAS;AAC3C,SAAO,SAAS,OAAO,CAAC,KAAK,SAAS,QAAQ;AAC5C,YAAQ,KAAK,MAAM;AAAA,MACjB,KAAK,UAAU;AACb,cAAM,WAAW,IAAI,QAAQ,OAAO,CAAC,UAAU,MAAM,KAAK,UAAU,OAAO;AAC3E,YAAI,SAAS,WAAW,GAAG;AACzB,gBAAM,IAAI,MAAM,0BAA0B,OAAO,OAAO,QAAQ,EAAE;AAAA,QACpE;AAEA,cAAM,EAAE,MAAM,MAAM,IAAI,SAAS,CAAC;AAClC,eAAO,6BAA6B,QAAQ,mBAAmB,OAAO;AAAA,MACxE;AAAA,MACA,KAAK;AACH,eAAO,IAAI,SAAS,OAAO;AAAA,MAC7B;AAKE,YAAI,IAAI,KAAK;AACX,iBAAO;AAAA,QACT;AAAA,IACJ;AAAA,EACF,GAAG,QAAQ,IAAI;AACjB;",
6
6
  "names": []
7
7
  }
@@ -143,20 +143,6 @@ export declare function prefixInternalRef(input: string, prefix: string[]): stri
143
143
  * ```
144
144
  */
145
145
  export declare function prefixInternalRefRecursive(input: unknown, prefix: string[]): string;
146
- /**
147
- * Generates a short SHA-1 hash from a string value.
148
- * This function is used to create unique identifiers for external references
149
- * while keeping the hash length manageable. It uses the Web Crypto API to
150
- * generate a SHA-1 hash and returns the first 7 characters of the hex string.
151
- * If the hash would be all numbers, it ensures at least one letter is included.
152
- *
153
- * @param value - The string to hash
154
- * @returns A 7-character hexadecimal hash with at least one letter
155
- * @example
156
- * // Returns "2ae91d7"
157
- * await getHash("https://example.com/schema.json")
158
- */
159
- export declare function getHash(value: string): Promise<string>;
160
146
  /**
161
147
  * Represents a plugin that handles resolving references from external sources.
162
148
  * Plugins are responsible for fetching and processing data from different sources
@@ -212,6 +198,11 @@ type Config = {
212
198
  * in an x-ext-urls section for reference mapping.
213
199
  */
214
200
  urlMap?: boolean;
201
+ /**
202
+ * Optional function to compress input URLs or file paths before bundling.
203
+ * Returns either a Promise resolving to the compressed string or the compressed string directly.
204
+ */
205
+ compress?: (value: string) => Promise<string> | string;
215
206
  /**
216
207
  * Optional hooks to monitor the bundler's lifecycle.
217
208
  * Allows tracking the progress and status of reference resolution.
@@ -290,6 +281,6 @@ type Config = {
290
281
  * // The function will first fetch the OpenAPI spec from the URL,
291
282
  * // then bundle all its external references into the x-ext section
292
283
  */
293
- export declare function bundle(input: UnknownObject | string, config: Config): Promise<unknown>;
284
+ export declare function bundle(input: UnknownObject | string, config: Config): Promise<object>;
294
285
  export {};
295
286
  //# sourceMappingURL=bundle.d.ts.map
@@ -1 +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;AAQ5C;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,WAOxC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,WAEvC;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;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+CG;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;;;;;;;;;;;;GAYG;AACH,wBAAsB,OAAO,CAAC,KAAK,EAAE,MAAM,mBAe1C;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;;;GAGG;AACH,KAAK,MAAM,GAAG;IACZ;;;;OAIG;IACH,OAAO,EAAE,MAAM,EAAE,CAAA;IAEjB;;;;OAIG;IACH,IAAI,CAAC,EAAE,aAAa,CAAA;IAEpB;;;;OAIG;IACH,KAAK,CAAC,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,aAAa,CAAC,CAAC,CAAA;IAE3C;;;;OAIG;IACH,YAAY,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC,CAAA;IAE3B;;;;OAIG;IACH,SAAS,EAAE,OAAO,CAAA;IAElB;;;;OAIG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;IAEhB;;;OAGG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;QACd,kDAAkD;QAClD,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAA;QACjF,+CAA+C;QAC/C,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAA;QACjF,uDAAuD;QACvD,gBAAgB,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAA;KACpF,CAAC,CAAA;CACH,CAAA;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgEG;AACH,wBAAsB,MAAM,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,EAAE,MAAM,EAAE,MAAM,oBA8LzE"}
1
+ {"version":3,"file":"bundle.d.ts","sourceRoot":"","sources":["../../../src/utils/bundle/bundle.ts"],"names":[],"mappings":"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"}
@@ -4,6 +4,7 @@ import { getSegmentsFromPath } from "../../utils/get-segments-from-path.js";
4
4
  import { isObject } from "../../utils/is-object.js";
5
5
  import { isYaml } from "../../utils/is-yaml.js";
6
6
  import { isJson } from "../../utils/is-json.js";
7
+ import { getHash, uniqueValueGeneratorFactory } from "../../utils/bundle/value-generator.js";
7
8
  function isRemoteUrl(value) {
8
9
  try {
9
10
  const url = new URL(value);
@@ -115,15 +116,21 @@ const resolveAndCopyReferences = (targetDocument, sourceDocument, referencePath,
115
116
  };
116
117
  traverse(referencedValue);
117
118
  };
118
- async function getHash(value) {
119
- const encoder = new TextEncoder();
120
- const data = encoder.encode(value);
121
- const hashBuffer = await crypto.subtle.digest("SHA-1", data);
122
- const hashArray = Array.from(new Uint8Array(hashBuffer));
123
- const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
124
- const hash = hashHex.substring(0, 7);
125
- return hash.match(/^\d+$/) ? "a" + hash.substring(1) : hash;
126
- }
119
+ const extensions = {
120
+ /**
121
+ * Custom OpenAPI extension key used to store external references.
122
+ * This key will contain all bundled external documents.
123
+ * The x-ext key is used to maintain a clean separation between the main
124
+ * OpenAPI document and its bundled external references.
125
+ */
126
+ externalDocuments: "x-ext",
127
+ /**
128
+ * Custom OpenAPI extension key used to maintain a mapping between
129
+ * hashed keys and their original URLs in x-ext.
130
+ * This mapping is essential for tracking the source of bundled references
131
+ */
132
+ externalDocumentsMappings: "x-ext-urls"
133
+ };
127
134
  async function bundle(input, config) {
128
135
  const cache = config.cache ?? /* @__PURE__ */ new Map();
129
136
  const resolveInput = async () => {
@@ -131,7 +138,7 @@ async function bundle(input, config) {
131
138
  return input;
132
139
  }
133
140
  const result = await resolveContents(input, config.plugins);
134
- if (result.ok) {
141
+ if (result.ok && typeof result.data === "object") {
135
142
  return result.data;
136
143
  }
137
144
  throw new Error(
@@ -140,8 +147,6 @@ async function bundle(input, config) {
140
147
  };
141
148
  const rawSpecification = await resolveInput();
142
149
  const documentRoot = config.root ?? rawSpecification;
143
- const EXTERNAL_KEY = "x-ext";
144
- const EXTERNAL_URL_MAPPING = "x-ext-urls";
145
150
  const isPartialBundling = config.root !== void 0 && config.root !== rawSpecification;
146
151
  const processedNodes = config.visitedNodes ?? /* @__PURE__ */ new Set();
147
152
  const defaultOrigin = () => {
@@ -153,6 +158,13 @@ async function bundle(input, config) {
153
158
  }
154
159
  return "";
155
160
  };
161
+ if (documentRoot[extensions.externalDocumentsMappings] === void 0) {
162
+ documentRoot[extensions.externalDocumentsMappings] = {};
163
+ }
164
+ const { generate } = uniqueValueGeneratorFactory(
165
+ config.compress ?? getHash,
166
+ documentRoot[extensions.externalDocumentsMappings]
167
+ );
156
168
  const bundler = async (root, origin = defaultOrigin(), isChunkParent = false) => {
157
169
  if (!isObject(root) && !Array.isArray(root)) {
158
170
  return;
@@ -172,7 +184,7 @@ async function bundle(input, config) {
172
184
  }
173
185
  const [prefix, path2 = ""] = ref.split("#", 2);
174
186
  const resolvedPath = resolveReferencePath(origin, prefix);
175
- const hashPath = await getHash(resolvedPath);
187
+ const compressedPath = await generate(resolvedPath);
176
188
  const seen = cache.has(resolvedPath);
177
189
  if (!seen) {
178
190
  cache.set(resolvedPath, resolveContents(resolvedPath, config.plugins));
@@ -182,25 +194,27 @@ async function bundle(input, config) {
182
194
  if (result.ok) {
183
195
  if (!seen) {
184
196
  if (!isChunk) {
185
- prefixInternalRefRecursive(result.data, [EXTERNAL_KEY, hashPath]);
197
+ prefixInternalRefRecursive(result.data, [extensions.externalDocuments, compressedPath]);
186
198
  }
187
199
  await bundler(result.data, isChunk ? origin : resolvedPath, isChunk);
188
- if (config.urlMap) {
189
- setValueAtPath(documentRoot, `/${EXTERNAL_URL_MAPPING}/${escapeJsonPointer(resolvedPath)}`, hashPath);
190
- }
200
+ setValueAtPath(
201
+ documentRoot,
202
+ `/${extensions.externalDocumentsMappings}/${escapeJsonPointer(compressedPath)}`,
203
+ resolvedPath
204
+ );
191
205
  }
192
206
  if (config.treeShake === true) {
193
207
  resolveAndCopyReferences(
194
208
  documentRoot,
195
- { [EXTERNAL_KEY]: { [hashPath]: result.data } },
196
- prefixInternalRef(`#${path2}`, [EXTERNAL_KEY, hashPath]).substring(1),
197
- EXTERNAL_KEY,
198
- hashPath
209
+ { [extensions.externalDocuments]: { [compressedPath]: result.data } },
210
+ prefixInternalRef(`#${path2}`, [extensions.externalDocuments, compressedPath]).substring(1),
211
+ extensions.externalDocuments,
212
+ compressedPath
199
213
  );
200
214
  } else if (!seen) {
201
- setValueAtPath(documentRoot, `/${EXTERNAL_KEY}/${hashPath}`, result.data);
215
+ setValueAtPath(documentRoot, `/${extensions.externalDocuments}/${compressedPath}`, result.data);
202
216
  }
203
- root.$ref = prefixInternalRef(`#${path2}`, [EXTERNAL_KEY, hashPath]);
217
+ root.$ref = prefixInternalRef(`#${path2}`, [extensions.externalDocuments, compressedPath]);
204
218
  config?.hooks?.onResolveSuccess?.(root);
205
219
  return;
206
220
  }
@@ -211,7 +225,7 @@ async function bundle(input, config) {
211
225
  }
212
226
  await Promise.all(
213
227
  Object.entries(root).map(async ([key, value]) => {
214
- if (key === EXTERNAL_KEY) {
228
+ if (key === extensions.externalDocuments) {
215
229
  return;
216
230
  }
217
231
  await bundler(value, origin, isChunkParent);
@@ -219,11 +233,13 @@ async function bundle(input, config) {
219
233
  );
220
234
  };
221
235
  await bundler(rawSpecification);
236
+ if (!config.urlMap && !isPartialBundling) {
237
+ delete documentRoot[extensions.externalDocumentsMappings];
238
+ }
222
239
  return rawSpecification;
223
240
  }
224
241
  export {
225
242
  bundle,
226
- getHash,
227
243
  getNestedValue,
228
244
  isFilePath,
229
245
  isLocalRef,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
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'\nimport { isYaml } from '@/utils/is-yaml'\nimport { isJson } from '@/utils/is-json'\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) {\n try {\n const url = new URL(value)\n return url.protocol === 'http:' || url.protocol === 'https:'\n } catch {\n return false\n }\n}\n\n/**\n * Checks if a string represents a file path by ensuring it's not a remote URL,\n * YAML content, or JSON content.\n *\n * @param value - The string to check\n * @returns true if the string appears to be a file path, false otherwise\n * @example\n * ```ts\n * isFilePath('./schemas/user.json') // true\n * isFilePath('https://example.com/schema.json') // false\n * isFilePath('{\"type\": \"object\"}') // false\n * isFilePath('type: object') // false\n * ```\n */\nexport function isFilePath(value: string) {\n return !isRemoteUrl(value) && !isYaml(value) && !isJson(value)\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 * \u26A0\uFE0F Warning: Be careful with object keys that look like numbers (e.g. \"123\") as this function\n * will interpret them as array indices and create arrays instead of objects. If you need to\n * use numeric-looking keys, consider prefixing them with a non-numeric character.\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 *\n * @example\n * // \u26A0\uFE0F Warning: This will create an array instead of an object with key \"123\"\n * setValueAtPath(obj, '/foo/123/bar', 'value')\n * // Result:\n * // {\n * // foo: [\n * // undefined,\n * // undefined,\n * // undefined,\n * // { bar: 'value' }\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 * If the hash would be all numbers, it ensures at least one letter is included.\n *\n * @param value - The string to hash\n * @returns A 7-character hexadecimal hash with at least one letter\n * @example\n * // Returns \"2ae91d7\"\n * await 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 first 7 characters of the hash, ensuring at least one letter\n const hash = hashHex.substring(0, 7)\n return hash.match(/^\\d+$/) ? 'a' + hash.substring(1) : hash\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\n/**\n * Configuration options for the bundler.\n * Controls how external references are resolved and processed during bundling.\n */\ntype Config = {\n /**\n * Array of plugins that handle resolving references from different sources.\n * Each plugin is responsible for fetching and processing data from specific sources\n * like URLs or the filesystem.\n */\n plugins: Plugin[]\n\n /**\n * Optional root object that serves as the base document when bundling a subpart.\n * This allows resolving references relative to the root document's location,\n * ensuring proper path resolution for nested references.\n */\n root?: UnknownObject\n\n /**\n * Optional cache to store promises of resolved references.\n * Helps avoid duplicate fetches/reads of the same resource by storing\n * the resolution promises for reuse.\n */\n cache?: Map<string, Promise<ResolveResult>>\n\n /**\n * Cache of visited nodes during partial bundling.\n * Used to prevent re-bundling the same tree multiple times when doing partial bundling,\n * improving performance by avoiding redundant processing of already bundled sections.\n */\n visitedNodes?: Set<unknown>\n\n /**\n * Enable tree shaking to optimize the bundle size.\n * When enabled, only the parts of external documents that are actually referenced\n * will be included in the final bundle.\n */\n treeShake: boolean\n\n /**\n * Optional flag to generate a URL map.\n * When enabled, tracks the original source URLs of bundled references\n * in an x-ext-urls section for reference mapping.\n */\n urlMap?: boolean\n\n /**\n * Optional hooks to monitor the bundler's lifecycle.\n * Allows tracking the progress and status of reference resolution.\n */\n hooks?: Partial<{\n /** Called when starting to resolve a reference */\n onResolveStart: (node: Record<string, unknown> & Record<'$ref', unknown>) => void\n /** Called when a reference resolution fails */\n onResolveError: (node: Record<string, unknown> & Record<'$ref', unknown>) => void\n /** Called when a reference is successfully resolved */\n onResolveSuccess: (node: Record<string, unknown> & Record<'$ref', unknown>) => void\n }>\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 to bundle. Can be either an object or string.\n * If a string is provided, it will be resolved using the provided plugins.\n * If no plugin can process the input, the onReferenceError hook will be invoked\n * and an error will be emitted to the console.\n * @param config - Configuration object containing plugins and options for bundling OpenAPI specifications\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, {\n * plugins: [fetchUrls()],\n * treeShake: true,\n * urlMap: true,\n * hooks: {\n * onResolveStart: (ref) => console.log('Resolving:', ref.$ref),\n * onResolveSuccess: (ref) => console.log('Resolved:', ref.$ref),\n * onResolveError: (ref) => console.log('Failed to resolve:', ref.$ref)\n * }\n * })\n * // Result:\n * // {\n * // paths: {\n * // '/users': {\n * // $ref: '#/x-ext/abc123'\n * // }\n * // },\n * // 'x-ext': {\n * // 'abc123': {\n * // // Resolved content from users.yaml\n * // }\n * // },\n * // 'x-ext-urls': {\n * // 'https://example.com/schemas/users.yaml': 'abc123'\n * // }\n * // }\n *\n * // Example with URL input\n * const bundledFromUrl = await bundle('https://example.com/openapi.yaml', {\n * plugins: [fetchUrls()],\n * treeShake: true,\n * urlMap: true,\n * hooks: {\n * onResolveStart: (ref) => console.log('Resolving:', ref.$ref),\n * onResolveSuccess: (ref) => console.log('Resolved:', ref.$ref),\n * onResolveError: (ref) => console.log('Failed to resolve:', ref.$ref)\n * }\n * })\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 new Error(\n 'Failed to resolve input: Please provide a valid string value or pass a loader to process the input',\n )\n }\n\n // Resolve the input specification, which could be either a direct object or a string URL/path\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 // Indicates whether we're performing a partial bundle operation, which occurs when\n // a root document is provided that differs from the raw specification being bundled\n const isPartialBundling = config.root !== undefined && config.root !== rawSpecification\n\n // Set of nodes that have already been processed during bundling to prevent duplicate processing\n const processedNodes = config.visitedNodes ?? new Set()\n\n // Determines the initial origin path for the bundler based on the input type.\n // For string inputs that are URLs or file paths, uses the input as the origin.\n // For non-string inputs or other string types, returns an empty string.\n const defaultOrigin = () => {\n if (typeof input !== 'string') {\n return ''\n }\n\n if (isRemoteUrl(input) || isFilePath(input)) {\n return input\n }\n\n return ''\n }\n\n const bundler = async (root: unknown, origin: string = defaultOrigin(), isChunkParent = false) => {\n if (!isObject(root) && !Array.isArray(root)) {\n return\n }\n\n // Skip if this node has already been processed to prevent infinite recursion\n // and duplicate processing of the same node\n if (processedNodes.has(root)) {\n return\n }\n // Mark this node as processed before continuing\n processedNodes.add(root)\n\n if (typeof root === 'object' && '$ref' in root && typeof root['$ref'] === 'string') {\n const ref = root['$ref']\n const isChunk = '$global' in root && typeof root['$global'] === 'boolean' && root['$global']\n\n if (isLocalRef(ref)) {\n if (isPartialBundling) {\n // When doing partial bundling, we need to recursively bundle all dependencies\n // referenced by this local reference to ensure the partial bundle is complete.\n // This includes not just the direct reference but also all its dependencies,\n // creating a complete and self-contained partial bundle.\n await bundler(getNestedValue(documentRoot, getSegmentsFromPath(ref.substring(1))), origin, isChunkParent)\n }\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 config?.hooks?.onResolveStart?.(root)\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 // Skip prefixing for chunks since they are meant to be self-contained and their\n // internal references should remain relative to their original location. Chunks\n // are typically used for modular components that need to maintain their own\n // reference context without being affected by the main document's structure.\n if (!isChunk) {\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\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, isChunk ? origin : resolvedPath, isChunk)\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 config?.hooks?.onResolveSuccess?.(root)\n return\n }\n\n config?.hooks?.onResolveError?.(root)\n return console.warn(\n `Failed to resolve external reference \"${resolvedPath}\". 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, isChunkParent)\n }),\n )\n }\n\n await bundler(rawSpecification)\n return rawSpecification\n}\n"],
5
- "mappings": "AACA,SAAS,yBAAyB;AAClC,OAAO,UAAU;AACjB,SAAS,2BAA2B;AACpC,SAAS,gBAAgB;AACzB,SAAS,cAAc;AACvB,SAAS,cAAc;AAchB,SAAS,YAAY,OAAe;AACzC,MAAI;AACF,UAAM,MAAM,IAAI,IAAI,KAAK;AACzB,WAAO,IAAI,aAAa,WAAW,IAAI,aAAa;AAAA,EACtD,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAgBO,SAAS,WAAW,OAAe;AACxC,SAAO,CAAC,YAAY,KAAK,KAAK,CAAC,OAAO,KAAK,KAAK,CAAC,OAAO,KAAK;AAC/D;AAaO,SAAS,WAAW,OAAwB;AACjD,SAAO,MAAM,WAAW,GAAG;AAC7B;AAiBA,eAAe,gBAAgB,OAAe,SAA2C;AACvF,QAAM,SAAS,QAAQ,KAAK,CAAC,MAAM,EAAE,SAAS,KAAK,CAAC;AAEpD,MAAI,QAAQ;AACV,WAAO,OAAO,KAAK,KAAK;AAAA,EAC1B;AAEA,SAAO;AAAA,IACL,IAAI;AAAA,EACN;AACF;AAWO,SAAS,eAAe,QAA6B,UAAoB;AAC9E,SAAO,SAAS,OAAY,CAAC,KAAK,QAAQ;AACxC,QAAI,QAAQ,QAAW;AACrB,aAAO;AAAA,IACT;AACA,WAAO,IAAI,GAAG;AAAA,EAChB,GAAG,MAAM;AACX;AAkDO,SAAS,eAAe,KAAUA,OAAc,OAAkB;AACvE,MAAIA,UAAS,IAAI;AACf,UAAM,IAAI,MAAM,uCAAuC;AAAA,EACzD;AAEA,QAAM,QAAQ,oBAAoBA,KAAI;AAEtC,MAAI,UAAU;AAEd,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,UAAM,MAAM,MAAM,CAAC;AACnB,UAAM,SAAS,MAAM,MAAM,SAAS;AAEpC,UAAM,UAAU,MAAM,IAAI,CAAC;AAC3B,UAAM,gBAAgB,QAAQ,KAAK,WAAW,EAAE;AAEhD,QAAI,QAAQ;AACV,cAAQ,GAAG,IAAI;AAAA,IACjB,OAAO;AACL,UAAI,EAAE,OAAO,YAAY,OAAO,QAAQ,GAAG,MAAM,UAAU;AACzD,gBAAQ,GAAG,IAAI,gBAAgB,CAAC,IAAI,CAAC;AAAA,MACvC;AACA,gBAAU,QAAQ,GAAG;AAAA,IACvB;AAAA,EACF;AACF;AAkBA,SAAS,qBAAqB,MAAc,cAAsB;AAChE,MAAI,YAAY,YAAY,GAAG;AAC7B,WAAO;AAAA,EACT;AAEA,MAAI,YAAY,IAAI,GAAG;AACrB,UAAM,MAAM,IAAI,IAAI,IAAI;AAExB,UAAM,aAAa,KAAK,KAAK,KAAK,QAAQ,IAAI,QAAQ,GAAG,YAAY;AACrE,WAAO,IAAI,IAAI,YAAY,IAAI,EAAE,SAAS;AAAA,EAC5C;AAEA,SAAO,KAAK,KAAK,KAAK,QAAQ,IAAI,GAAG,YAAY;AACnD;AAcO,SAAS,kBAAkB,OAAe,QAAkB;AACjE,MAAI,CAAC,WAAW,KAAK,GAAG;AACtB,UAAM;AAAA,EACR;AAEA,SAAO,KAAK,OAAO,IAAI,iBAAiB,EAAE,KAAK,GAAG,CAAC,GAAG,MAAM,UAAU,CAAC,CAAC;AAC1E;AA2BO,SAAS,2BAA2B,OAAgB,QAAkB;AAC3E,MAAI,CAAC,SAAS,KAAK,GAAG;AACpB;AAAA,EACF;AAEA,SAAO,OAAO,KAAK,EAAE,QAAQ,CAAC,OAAO,2BAA2B,IAAI,MAAM,CAAC;AAE3E,MAAI,OAAO,UAAU,YAAY,UAAU,SAAS,OAAO,MAAM,MAAM,MAAM,UAAU;AACrF,UAAM,MAAM,MAAM,MAAM;AAExB,QAAI,CAAC,WAAW,GAAG,GAAG;AACpB;AAAA,IACF;AAEA,WAAQ,MAAM,MAAM,IAAI,kBAAkB,KAAK,MAAM;AAAA,EACvD;AACF;AAqCA,MAAM,2BAA2B,CAC/B,gBACA,gBACA,eACA,iBACA,aACA,iBAAiB,oBAAI,IAAI,MACtB;AACH,QAAM,kBAAkB,eAAe,gBAAgB,oBAAoB,aAAa,CAAC;AAEzF,MAAI,eAAe,IAAI,eAAe,GAAG;AACvC;AAAA,EACF;AACA,iBAAe,IAAI,eAAe;AAElC,iBAAe,gBAAgB,eAAe,eAAe;AAG7D,QAAM,WAAW,CAAC,SAAkB;AAClC,QAAI,CAAC,QAAQ,OAAO,SAAS,UAAU;AACrC;AAAA,IACF;AAEA,QAAI,UAAU,QAAQ,OAAO,KAAK,MAAM,MAAM,UAAU;AAKtD,UAAI,KAAK,MAAM,EAAE,WAAW,KAAK,eAAe,IAAI,kBAAkB,WAAW,CAAC,EAAE,GAAG;AACrF;AAAA,UACE;AAAA,UACA;AAAA,UACA,KAAK,MAAM,EAAE,UAAU,CAAC;AAAA,UACxB;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,eAAW,SAAS,OAAO,OAAO,IAAI,GAAG;AACvC,eAAS,KAAK;AAAA,IAChB;AAAA,EACF;AAEA,WAAS,eAAe;AAC1B;AAeA,eAAsB,QAAQ,OAAe;AAE3C,QAAM,UAAU,IAAI,YAAY;AAChC,QAAM,OAAO,QAAQ,OAAO,KAAK;AAGjC,QAAM,aAAa,MAAM,OAAO,OAAO,OAAO,SAAS,IAAI;AAG3D,QAAM,YAAY,MAAM,KAAK,IAAI,WAAW,UAAU,CAAC;AACvD,QAAM,UAAU,UAAU,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE;AAG7E,QAAM,OAAO,QAAQ,UAAU,GAAG,CAAC;AACnC,SAAO,KAAK,MAAM,OAAO,IAAI,MAAM,KAAK,UAAU,CAAC,IAAI;AACzD;AAiJA,eAAsB,OAAO,OAA+B,QAAgB;AAG1E,QAAM,QAAQ,OAAO,SAAS,oBAAI,IAAoC;AAOtE,QAAM,eAAe,YAAY;AAC/B,QAAI,OAAO,UAAU,UAAU;AAC7B,aAAO;AAAA,IACT;AACA,UAAM,SAAS,MAAM,gBAAgB,OAAO,OAAO,OAAO;AAE1D,QAAI,OAAO,IAAI;AACb,aAAO,OAAO;AAAA,IAChB;AAEA,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAGA,QAAM,mBAAmB,MAAM,aAAa;AAI5C,QAAM,eAAe,OAAO,QAAQ;AAIpC,QAAM,eAAe;AAIrB,QAAM,uBAAuB;AAI7B,QAAM,oBAAoB,OAAO,SAAS,UAAa,OAAO,SAAS;AAGvE,QAAM,iBAAiB,OAAO,gBAAgB,oBAAI,IAAI;AAKtD,QAAM,gBAAgB,MAAM;AAC1B,QAAI,OAAO,UAAU,UAAU;AAC7B,aAAO;AAAA,IACT;AAEA,QAAI,YAAY,KAAK,KAAK,WAAW,KAAK,GAAG;AAC3C,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,EACT;AAEA,QAAM,UAAU,OAAO,MAAe,SAAiB,cAAc,GAAG,gBAAgB,UAAU;AAChG,QAAI,CAAC,SAAS,IAAI,KAAK,CAAC,MAAM,QAAQ,IAAI,GAAG;AAC3C;AAAA,IACF;AAIA,QAAI,eAAe,IAAI,IAAI,GAAG;AAC5B;AAAA,IACF;AAEA,mBAAe,IAAI,IAAI;AAEvB,QAAI,OAAO,SAAS,YAAY,UAAU,QAAQ,OAAO,KAAK,MAAM,MAAM,UAAU;AAClF,YAAM,MAAM,KAAK,MAAM;AACvB,YAAM,UAAU,aAAa,QAAQ,OAAO,KAAK,SAAS,MAAM,aAAa,KAAK,SAAS;AAE3F,UAAI,WAAW,GAAG,GAAG;AACnB,YAAI,mBAAmB;AAKrB,gBAAM,QAAQ,eAAe,cAAc,oBAAoB,IAAI,UAAU,CAAC,CAAC,CAAC,GAAG,QAAQ,aAAa;AAAA,QAC1G;AACA;AAAA,MACF;AAEA,YAAM,CAAC,QAAQA,QAAO,EAAE,IAAI,IAAI,MAAM,KAAK,CAAC;AAI5C,YAAM,eAAe,qBAAqB,QAAQ,MAAM;AACxD,YAAM,WAAW,MAAM,QAAQ,YAAY;AAE3C,YAAM,OAAO,MAAM,IAAI,YAAY;AAEnC,UAAI,CAAC,MAAM;AACT,cAAM,IAAI,cAAc,gBAAgB,cAAc,OAAO,OAAO,CAAC;AAAA,MACvE;AAEA,cAAQ,OAAO,iBAAiB,IAAI;AAGpC,YAAM,SAAS,MAAM,MAAM,IAAI,YAAY;AAE3C,UAAI,OAAO,IAAI;AAGb,YAAI,CAAC,MAAM;AAKT,cAAI,CAAC,SAAS;AAQZ,uCAA2B,OAAO,MAAM,CAAC,cAAc,QAAQ,CAAC;AAAA,UAClE;AAMA,gBAAM,QAAQ,OAAO,MAAM,UAAU,SAAS,cAAc,OAAO;AAInE,cAAI,OAAO,QAAQ;AACjB,2BAAe,cAAc,IAAI,oBAAoB,IAAI,kBAAkB,YAAY,CAAC,IAAI,QAAQ;AAAA,UACtG;AAAA,QACF;AAEA,YAAI,OAAO,cAAc,MAAM;AAI7B;AAAA,YACE;AAAA,YACA,EAAE,CAAC,YAAY,GAAG,EAAE,CAAC,QAAQ,GAAG,OAAO,KAAK,EAAE;AAAA,YAC9C,kBAAkB,IAAIA,KAAI,IAAI,CAAC,cAAc,QAAQ,CAAC,EAAE,UAAU,CAAC;AAAA,YACnE;AAAA,YACA;AAAA,UACF;AAAA,QACF,WAAW,CAAC,MAAM;AAMhB,yBAAe,cAAc,IAAI,YAAY,IAAI,QAAQ,IAAI,OAAO,IAAI;AAAA,QAC1E;AAKA,aAAK,OAAO,kBAAkB,IAAIA,KAAI,IAAI,CAAC,cAAc,QAAQ,CAAC;AAClE,gBAAQ,OAAO,mBAAmB,IAAI;AACtC;AAAA,MACF;AAEA,cAAQ,OAAO,iBAAiB,IAAI;AACpC,aAAO,QAAQ;AAAA,QACb,yCAAyC,YAAY;AAAA,MACvD;AAAA,IACF;AAKA,UAAM,QAAQ;AAAA,MACZ,OAAO,QAAQ,IAAI,EAAE,IAAI,OAAO,CAAC,KAAK,KAAK,MAAM;AAC/C,YAAI,QAAQ,cAAc;AACxB;AAAA,QACF;AAEA,cAAM,QAAQ,OAAO,QAAQ,aAAa;AAAA,MAC5C,CAAC;AAAA,IACH;AAAA,EACF;AAEA,QAAM,QAAQ,gBAAgB;AAC9B,SAAO;AACT;",
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'\nimport { isYaml } from '@/utils/is-yaml'\nimport { isJson } from '@/utils/is-json'\nimport { getHash, uniqueValueGeneratorFactory } from '@/utils/bundle/value-generator'\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) {\n try {\n const url = new URL(value)\n return url.protocol === 'http:' || url.protocol === 'https:'\n } catch {\n return false\n }\n}\n\n/**\n * Checks if a string represents a file path by ensuring it's not a remote URL,\n * YAML content, or JSON content.\n *\n * @param value - The string to check\n * @returns true if the string appears to be a file path, false otherwise\n * @example\n * ```ts\n * isFilePath('./schemas/user.json') // true\n * isFilePath('https://example.com/schema.json') // false\n * isFilePath('{\"type\": \"object\"}') // false\n * isFilePath('type: object') // false\n * ```\n */\nexport function isFilePath(value: string) {\n return !isRemoteUrl(value) && !isYaml(value) && !isJson(value)\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 * \u26A0\uFE0F Warning: Be careful with object keys that look like numbers (e.g. \"123\") as this function\n * will interpret them as array indices and create arrays instead of objects. If you need to\n * use numeric-looking keys, consider prefixing them with a non-numeric character.\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 *\n * @example\n * // \u26A0\uFE0F Warning: This will create an array instead of an object with key \"123\"\n * setValueAtPath(obj, '/foo/123/bar', 'value')\n * // Result:\n * // {\n * // foo: [\n * // undefined,\n * // undefined,\n * // undefined,\n * // { bar: 'value' }\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 * 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\n/**\n * Configuration options for the bundler.\n * Controls how external references are resolved and processed during bundling.\n */\ntype Config = {\n /**\n * Array of plugins that handle resolving references from different sources.\n * Each plugin is responsible for fetching and processing data from specific sources\n * like URLs or the filesystem.\n */\n plugins: Plugin[]\n\n /**\n * Optional root object that serves as the base document when bundling a subpart.\n * This allows resolving references relative to the root document's location,\n * ensuring proper path resolution for nested references.\n */\n root?: UnknownObject\n\n /**\n * Optional cache to store promises of resolved references.\n * Helps avoid duplicate fetches/reads of the same resource by storing\n * the resolution promises for reuse.\n */\n cache?: Map<string, Promise<ResolveResult>>\n\n /**\n * Cache of visited nodes during partial bundling.\n * Used to prevent re-bundling the same tree multiple times when doing partial bundling,\n * improving performance by avoiding redundant processing of already bundled sections.\n */\n visitedNodes?: Set<unknown>\n\n /**\n * Enable tree shaking to optimize the bundle size.\n * When enabled, only the parts of external documents that are actually referenced\n * will be included in the final bundle.\n */\n treeShake: boolean\n\n /**\n * Optional flag to generate a URL map.\n * When enabled, tracks the original source URLs of bundled references\n * in an x-ext-urls section for reference mapping.\n */\n urlMap?: boolean\n\n /**\n * Optional function to compress input URLs or file paths before bundling.\n * Returns either a Promise resolving to the compressed string or the compressed string directly.\n */\n compress?: (value: string) => Promise<string> | string\n\n /**\n * Optional hooks to monitor the bundler's lifecycle.\n * Allows tracking the progress and status of reference resolution.\n */\n hooks?: Partial<{\n /** Called when starting to resolve a reference */\n onResolveStart: (node: Record<string, unknown> & Record<'$ref', unknown>) => void\n /** Called when a reference resolution fails */\n onResolveError: (node: Record<string, unknown> & Record<'$ref', unknown>) => void\n /** Called when a reference is successfully resolved */\n onResolveSuccess: (node: Record<string, unknown> & Record<'$ref', unknown>) => void\n }>\n}\n\n/**\n * Extension keys used for bundling external references in OpenAPI documents.\n * These custom extensions help maintain the structure and traceability of bundled documents.\n */\nconst extensions = {\n /**\n * Custom OpenAPI extension key used to store external references.\n * This key will contain all bundled external documents.\n * The x-ext key is used to maintain a clean separation between the main\n * OpenAPI document and its bundled external references.\n */\n externalDocuments: 'x-ext',\n\n /**\n * Custom OpenAPI extension key used to maintain a mapping between\n * hashed keys and their original URLs in x-ext.\n * This mapping is essential for tracking the source of bundled references\n */\n externalDocumentsMappings: 'x-ext-urls',\n} as const\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 to bundle. Can be either an object or string.\n * If a string is provided, it will be resolved using the provided plugins.\n * If no plugin can process the input, the onReferenceError hook will be invoked\n * and an error will be emitted to the console.\n * @param config - Configuration object containing plugins and options for bundling OpenAPI specifications\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, {\n * plugins: [fetchUrls()],\n * treeShake: true,\n * urlMap: true,\n * hooks: {\n * onResolveStart: (ref) => console.log('Resolving:', ref.$ref),\n * onResolveSuccess: (ref) => console.log('Resolved:', ref.$ref),\n * onResolveError: (ref) => console.log('Failed to resolve:', ref.$ref)\n * }\n * })\n * // Result:\n * // {\n * // paths: {\n * // '/users': {\n * // $ref: '#/x-ext/abc123'\n * // }\n * // },\n * // 'x-ext': {\n * // 'abc123': {\n * // // Resolved content from users.yaml\n * // }\n * // },\n * // 'x-ext-urls': {\n * // 'https://example.com/schemas/users.yaml': 'abc123'\n * // }\n * // }\n *\n * // Example with URL input\n * const bundledFromUrl = await bundle('https://example.com/openapi.yaml', {\n * plugins: [fetchUrls()],\n * treeShake: true,\n * urlMap: true,\n * hooks: {\n * onResolveStart: (ref) => console.log('Resolving:', ref.$ref),\n * onResolveSuccess: (ref) => console.log('Resolved:', ref.$ref),\n * onResolveError: (ref) => console.log('Failed to resolve:', ref.$ref)\n * }\n * })\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 && typeof result.data === 'object') {\n return result.data\n }\n\n throw new Error(\n 'Failed to resolve input: Please provide a valid string value or pass a loader to process the input',\n )\n }\n\n // Resolve the input specification, which could be either a direct object or a string URL/path\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 // Indicates whether we're performing a partial bundle operation, which occurs when\n // a root document is provided that differs from the raw specification being bundled\n const isPartialBundling = config.root !== undefined && config.root !== rawSpecification\n\n // Set of nodes that have already been processed during bundling to prevent duplicate processing\n const processedNodes = config.visitedNodes ?? new Set()\n\n // Determines the initial origin path for the bundler based on the input type.\n // For string inputs that are URLs or file paths, uses the input as the origin.\n // For non-string inputs or other string types, returns an empty string.\n const defaultOrigin = () => {\n if (typeof input !== 'string') {\n return ''\n }\n\n if (isRemoteUrl(input) || isFilePath(input)) {\n return input\n }\n\n return ''\n }\n\n // Create the cache to store the compressed values to their map values\n if (documentRoot[extensions.externalDocumentsMappings] === undefined) {\n documentRoot[extensions.externalDocumentsMappings] = {}\n }\n const { generate } = uniqueValueGeneratorFactory(\n config.compress ?? getHash,\n documentRoot[extensions.externalDocumentsMappings],\n )\n\n const bundler = async (root: unknown, origin: string = defaultOrigin(), isChunkParent = false) => {\n if (!isObject(root) && !Array.isArray(root)) {\n return\n }\n\n // Skip if this node has already been processed to prevent infinite recursion\n // and duplicate processing of the same node\n if (processedNodes.has(root)) {\n return\n }\n // Mark this node as processed before continuing\n processedNodes.add(root)\n\n if (typeof root === 'object' && '$ref' in root && typeof root['$ref'] === 'string') {\n const ref = root['$ref']\n const isChunk = '$global' in root && typeof root['$global'] === 'boolean' && root['$global']\n\n if (isLocalRef(ref)) {\n if (isPartialBundling) {\n // When doing partial bundling, we need to recursively bundle all dependencies\n // referenced by this local reference to ensure the partial bundle is complete.\n // This includes not just the direct reference but also all its dependencies,\n // creating a complete and self-contained partial bundle.\n await bundler(getNestedValue(documentRoot, getSegmentsFromPath(ref.substring(1))), origin, isChunkParent)\n }\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\n // Generate a unique compressed path for the external document\n // This is used as a key to store and reference the bundled external document\n // The compression helps reduce the overall file size of the bundled document\n const compressedPath = await generate(resolvedPath)\n\n const seen = cache.has(resolvedPath)\n\n if (!seen) {\n cache.set(resolvedPath, resolveContents(resolvedPath, config.plugins))\n }\n\n config?.hooks?.onResolveStart?.(root)\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 // Skip prefixing for chunks since they are meant to be self-contained and their\n // internal references should remain relative to their original location. Chunks\n // are typically used for modular components that need to maintain their own\n // reference context without being affected by the main document's structure.\n if (!isChunk) {\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, [extensions.externalDocuments, compressedPath])\n }\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, isChunk ? origin : resolvedPath, isChunk)\n\n // Store the mapping between hashed keys and original URLs in x-ext-urls\n // This allows tracking which external URLs were bundled and their corresponding locations\n setValueAtPath(\n documentRoot,\n `/${extensions.externalDocumentsMappings}/${escapeJsonPointer(compressedPath)}`,\n resolvedPath,\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 { [extensions.externalDocuments]: { [compressedPath]: result.data } },\n prefixInternalRef(`#${path}`, [extensions.externalDocuments, compressedPath]).substring(1),\n extensions.externalDocuments,\n compressedPath,\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, `/${extensions.externalDocuments}/${compressedPath}`, 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}`, [extensions.externalDocuments, compressedPath])\n config?.hooks?.onResolveSuccess?.(root)\n return\n }\n\n config?.hooks?.onResolveError?.(root)\n return console.warn(\n `Failed to resolve external reference \"${resolvedPath}\". 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 === extensions.externalDocuments) {\n return\n }\n\n await bundler(value, origin, isChunkParent)\n }),\n )\n }\n\n await bundler(rawSpecification)\n\n // Keep urlMappings when doing partial bundling to track hash values and handle collisions\n // For full bundling without urlMap config, remove the mappings to clean up the output\n if (!config.urlMap && !isPartialBundling) {\n // Remove the external document mappings from the output when doing a full bundle without urlMap config\n delete documentRoot[extensions.externalDocumentsMappings]\n }\n\n return rawSpecification\n}\n"],
5
+ "mappings": "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",
6
6
  "names": ["path"]
7
7
  }
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Generates a short SHA-1 hash from a string value.
3
+ * This function is used to create unique identifiers for external references
4
+ * while keeping the hash length manageable. It uses the Web Crypto API to
5
+ * generate a SHA-1 hash and returns the first 7 characters of the hex string.
6
+ * If the hash would be all numbers, it ensures at least one letter is included.
7
+ *
8
+ * @param value - The string to hash
9
+ * @returns A 7-character hexadecimal hash with at least one letter
10
+ * @example
11
+ * // Returns "2ae91d7"
12
+ * await getHash("https://example.com/schema.json")
13
+ */
14
+ export declare function getHash(value: string): Promise<string>;
15
+ /**
16
+ * Generates a unique compressed value for a string, handling collisions by recursively compressing
17
+ * until a unique value is found. This is used to create unique identifiers for external
18
+ * references in the bundled OpenAPI document.
19
+ *
20
+ * @param compress - Function that generates a compressed value from a string
21
+ * @param value - The original string value to compress
22
+ * @param compressedToValue - Object mapping compressed values to their original values
23
+ * @param prevCompressedValue - Optional previous compressed value to use as input for generating a new value
24
+ * @param depth - Current recursion depth to prevent infinite loops
25
+ * @returns A unique compressed value that doesn't conflict with existing values
26
+ *
27
+ * @example
28
+ * const valueMap = {}
29
+ * // First call generates compressed value for "example.com/schema.json"
30
+ * const value1 = await generateUniqueValue(compress, "example.com/schema.json", valueMap)
31
+ * // Returns something like "2ae91d7"
32
+ *
33
+ * // Second call with same value returns same compressed value
34
+ * const value2 = await generateUniqueValue(compress, "example.com/schema.json", valueMap)
35
+ * // Returns same value as value1
36
+ *
37
+ * // Call with different value generates new unique compressed value
38
+ * const value3 = await generateUniqueValue(compress, "example.com/other.json", valueMap)
39
+ * // Returns different value like "3bf82e9"
40
+ */
41
+ export declare function generateUniqueValue(compress: (value: string) => Promise<string> | string, value: string, compressedToValue: Record<string, string>, prevCompressedValue?: string, depth?: number): Promise<string>;
42
+ /**
43
+ * Factory function that creates a value generator with caching capabilities.
44
+ * The generator maintains a bidirectional mapping between original values and their compressed forms.
45
+ *
46
+ * @param compress - Function that generates a compressed value from a string
47
+ * @param compressedToValue - Initial mapping of compressed values to their original values
48
+ * @returns An object with a generate method that produces unique compressed values
49
+ *
50
+ * @example
51
+ * const compress = (value) => value.substring(0, 6) // Simple compression example
52
+ * const initialMap = { 'abc123': 'example.com/schema.json' }
53
+ * const generator = uniqueValueGeneratorFactory(compress, initialMap)
54
+ *
55
+ * // Generate compressed value for new string
56
+ * const compressed = await generator.generate('example.com/other.json')
57
+ * // Returns something like 'example'
58
+ *
59
+ * // Generate compressed value for existing string
60
+ * const cached = await generator.generate('example.com/schema.json')
61
+ * // Returns 'abc123' from cache
62
+ */
63
+ export declare const uniqueValueGeneratorFactory: (compress: (value: string) => Promise<string> | string, compressedToValue: Record<string, string>) => {
64
+ /**
65
+ * Generates a unique compressed value for the given input string.
66
+ * First checks if a compressed value already exists in the cache.
67
+ * If not, generates a new unique compressed value and stores it in the cache.
68
+ *
69
+ * @param value - The original string value to compress
70
+ * @returns A Promise that resolves to the compressed string value
71
+ *
72
+ * @example
73
+ * const generator = uniqueValueGeneratorFactory(compress, {})
74
+ * const compressed = await generator.generate('example.com/schema.json')
75
+ * // Returns a unique compressed value like 'example'
76
+ */
77
+ generate: (value: string) => Promise<string>;
78
+ };
79
+ //# sourceMappingURL=value-generator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"value-generator.d.ts","sourceRoot":"","sources":["../../../src/utils/bundle/value-generator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,wBAAsB,OAAO,CAAC,KAAK,EAAE,MAAM,mBAe1C;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,mBAAmB,CACvC,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,EACrD,KAAK,EAAE,MAAM,EACb,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACzC,mBAAmB,CAAC,EAAE,MAAM,EAC5B,KAAK,SAAI,mBAoBV;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,2BAA2B,aAC5B,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,qBAClC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IAMvC;;;;;;;;;;;;OAYG;sBACqB,MAAM;CAqBjC,CAAA"}
@@ -0,0 +1,55 @@
1
+ async function getHash(value) {
2
+ const encoder = new TextEncoder();
3
+ const data = encoder.encode(value);
4
+ const hashBuffer = await crypto.subtle.digest("SHA-1", data);
5
+ const hashArray = Array.from(new Uint8Array(hashBuffer));
6
+ const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
7
+ const hash = hashHex.substring(0, 7);
8
+ return hash.match(/^\d+$/) ? "a" + hash.substring(1) : hash;
9
+ }
10
+ async function generateUniqueValue(compress, value, compressedToValue, prevCompressedValue, depth = 0) {
11
+ const MAX_DEPTH = 100;
12
+ if (depth >= MAX_DEPTH) {
13
+ throw "Can not generate unique compressed values";
14
+ }
15
+ const compressedValue = await compress(prevCompressedValue ?? value);
16
+ if (compressedToValue[compressedValue] !== void 0 && compressedToValue[compressedValue] !== value) {
17
+ return generateUniqueValue(compress, value, compressedToValue, compressedValue, depth + 1);
18
+ }
19
+ compressedToValue[compressedValue] = value;
20
+ return compressedValue;
21
+ }
22
+ const uniqueValueGeneratorFactory = (compress, compressedToValue) => {
23
+ const valueToCompressed = Object.fromEntries(Object.entries(compressedToValue).map(([key, value]) => [value, key]));
24
+ return {
25
+ /**
26
+ * Generates a unique compressed value for the given input string.
27
+ * First checks if a compressed value already exists in the cache.
28
+ * If not, generates a new unique compressed value and stores it in the cache.
29
+ *
30
+ * @param value - The original string value to compress
31
+ * @returns A Promise that resolves to the compressed string value
32
+ *
33
+ * @example
34
+ * const generator = uniqueValueGeneratorFactory(compress, {})
35
+ * const compressed = await generator.generate('example.com/schema.json')
36
+ * // Returns a unique compressed value like 'example'
37
+ */
38
+ generate: async (value) => {
39
+ const cache = valueToCompressed[value];
40
+ if (cache) {
41
+ return cache;
42
+ }
43
+ const generatedValue = await generateUniqueValue(compress, value, compressedToValue);
44
+ const compressedValue = generatedValue.match(/^\d+$/) ? `a${generatedValue}` : generatedValue;
45
+ valueToCompressed[value] = compressedValue;
46
+ return compressedValue;
47
+ }
48
+ };
49
+ };
50
+ export {
51
+ generateUniqueValue,
52
+ getHash,
53
+ uniqueValueGeneratorFactory
54
+ };
55
+ //# sourceMappingURL=value-generator.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../../src/utils/bundle/value-generator.ts"],
4
+ "sourcesContent": ["/**\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 * If the hash would be all numbers, it ensures at least one letter is included.\n *\n * @param value - The string to hash\n * @returns A 7-character hexadecimal hash with at least one letter\n * @example\n * // Returns \"2ae91d7\"\n * await 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 first 7 characters of the hash, ensuring at least one letter\n const hash = hashHex.substring(0, 7)\n return hash.match(/^\\d+$/) ? 'a' + hash.substring(1) : hash\n}\n\n/**\n * Generates a unique compressed value for a string, handling collisions by recursively compressing\n * until a unique value is found. This is used to create unique identifiers for external\n * references in the bundled OpenAPI document.\n *\n * @param compress - Function that generates a compressed value from a string\n * @param value - The original string value to compress\n * @param compressedToValue - Object mapping compressed values to their original values\n * @param prevCompressedValue - Optional previous compressed value to use as input for generating a new value\n * @param depth - Current recursion depth to prevent infinite loops\n * @returns A unique compressed value that doesn't conflict with existing values\n *\n * @example\n * const valueMap = {}\n * // First call generates compressed value for \"example.com/schema.json\"\n * const value1 = await generateUniqueValue(compress, \"example.com/schema.json\", valueMap)\n * // Returns something like \"2ae91d7\"\n *\n * // Second call with same value returns same compressed value\n * const value2 = await generateUniqueValue(compress, \"example.com/schema.json\", valueMap)\n * // Returns same value as value1\n *\n * // Call with different value generates new unique compressed value\n * const value3 = await generateUniqueValue(compress, \"example.com/other.json\", valueMap)\n * // Returns different value like \"3bf82e9\"\n */\nexport async function generateUniqueValue(\n compress: (value: string) => Promise<string> | string,\n value: string,\n compressedToValue: Record<string, string>,\n prevCompressedValue?: string,\n depth = 0,\n) {\n // Prevent infinite recursion by limiting depth\n const MAX_DEPTH = 100\n\n if (depth >= MAX_DEPTH) {\n throw 'Can not generate unique compressed values'\n }\n\n // Compress the value, using previous compressed value if provided\n const compressedValue = await compress(prevCompressedValue ?? value)\n\n // Handle collision by recursively trying with compressed value as input\n if (compressedToValue[compressedValue] !== undefined && compressedToValue[compressedValue] !== value) {\n return generateUniqueValue(compress, value, compressedToValue, compressedValue, depth + 1)\n }\n\n // Store mapping and return unique compressed value\n compressedToValue[compressedValue] = value\n return compressedValue\n}\n\n/**\n * Factory function that creates a value generator with caching capabilities.\n * The generator maintains a bidirectional mapping between original values and their compressed forms.\n *\n * @param compress - Function that generates a compressed value from a string\n * @param compressedToValue - Initial mapping of compressed values to their original values\n * @returns An object with a generate method that produces unique compressed values\n *\n * @example\n * const compress = (value) => value.substring(0, 6) // Simple compression example\n * const initialMap = { 'abc123': 'example.com/schema.json' }\n * const generator = uniqueValueGeneratorFactory(compress, initialMap)\n *\n * // Generate compressed value for new string\n * const compressed = await generator.generate('example.com/other.json')\n * // Returns something like 'example'\n *\n * // Generate compressed value for existing string\n * const cached = await generator.generate('example.com/schema.json')\n * // Returns 'abc123' from cache\n */\nexport const uniqueValueGeneratorFactory = (\n compress: (value: string) => Promise<string> | string,\n compressedToValue: Record<string, string>,\n) => {\n // Create a reverse mapping from original values to their compressed forms\n const valueToCompressed = Object.fromEntries(Object.entries(compressedToValue).map(([key, value]) => [value, key]))\n\n return {\n /**\n * Generates a unique compressed value for the given input string.\n * First checks if a compressed value already exists in the cache.\n * If not, generates a new unique compressed value and stores it in the cache.\n *\n * @param value - The original string value to compress\n * @returns A Promise that resolves to the compressed string value\n *\n * @example\n * const generator = uniqueValueGeneratorFactory(compress, {})\n * const compressed = await generator.generate('example.com/schema.json')\n * // Returns a unique compressed value like 'example'\n */\n generate: async (value: string) => {\n // Check if we already have a compressed value for this input\n const cache = valueToCompressed[value]\n if (cache) {\n return cache\n }\n\n // Generate a new unique compressed value\n const generatedValue = await generateUniqueValue(compress, value, compressedToValue)\n\n // Ensure the generated string contains at least one non-numeric character\n // This prevents the `setValueAtPath` function from interpreting the value as an array index\n // by forcing it to be treated as an object property name\n const compressedValue = generatedValue.match(/^\\d+$/) ? `a${generatedValue}` : generatedValue\n\n // Store the new mapping in our cache\n valueToCompressed[value] = compressedValue\n\n return compressedValue\n },\n }\n}\n"],
5
+ "mappings": "AAaA,eAAsB,QAAQ,OAAe;AAE3C,QAAM,UAAU,IAAI,YAAY;AAChC,QAAM,OAAO,QAAQ,OAAO,KAAK;AAGjC,QAAM,aAAa,MAAM,OAAO,OAAO,OAAO,SAAS,IAAI;AAG3D,QAAM,YAAY,MAAM,KAAK,IAAI,WAAW,UAAU,CAAC;AACvD,QAAM,UAAU,UAAU,IAAI,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE;AAG7E,QAAM,OAAO,QAAQ,UAAU,GAAG,CAAC;AACnC,SAAO,KAAK,MAAM,OAAO,IAAI,MAAM,KAAK,UAAU,CAAC,IAAI;AACzD;AA4BA,eAAsB,oBACpB,UACA,OACA,mBACA,qBACA,QAAQ,GACR;AAEA,QAAM,YAAY;AAElB,MAAI,SAAS,WAAW;AACtB,UAAM;AAAA,EACR;AAGA,QAAM,kBAAkB,MAAM,SAAS,uBAAuB,KAAK;AAGnE,MAAI,kBAAkB,eAAe,MAAM,UAAa,kBAAkB,eAAe,MAAM,OAAO;AACpG,WAAO,oBAAoB,UAAU,OAAO,mBAAmB,iBAAiB,QAAQ,CAAC;AAAA,EAC3F;AAGA,oBAAkB,eAAe,IAAI;AACrC,SAAO;AACT;AAuBO,MAAM,8BAA8B,CACzC,UACA,sBACG;AAEH,QAAM,oBAAoB,OAAO,YAAY,OAAO,QAAQ,iBAAiB,EAAE,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,OAAO,GAAG,CAAC,CAAC;AAElH,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAcL,UAAU,OAAO,UAAkB;AAEjC,YAAM,QAAQ,kBAAkB,KAAK;AACrC,UAAI,OAAO;AACT,eAAO;AAAA,MACT;AAGA,YAAM,iBAAiB,MAAM,oBAAoB,UAAU,OAAO,iBAAiB;AAKnF,YAAM,kBAAkB,eAAe,MAAM,OAAO,IAAI,IAAI,cAAc,KAAK;AAG/E,wBAAkB,KAAK,IAAI;AAE3B,aAAO;AAAA,IACT;AAAA,EACF;AACF;",
6
+ "names": []
7
+ }
@@ -2,7 +2,7 @@ import type { AnyObject } from '../types/index.js';
2
2
  /**
3
3
  * Walks through the specification and returns all references as an array.
4
4
  *
5
- * Warning: Doesn’t return internal references.
5
+ * Warning: Doesn't return internal references.
6
6
  */
7
7
  export declare function getListOfReferences(specification: AnyObject): string[];
8
8
  //# sourceMappingURL=get-list-of-references.d.ts.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/utils/get-list-of-references.ts"],
4
- "sourcesContent": ["import type { AnyObject } from '@/types/index'\nimport { traverse } from './traverse'\n\n/**\n * Walks through the specification and returns all references as an array.\n *\n * Warning: Doesn\u2019t return internal references.\n */\nexport function getListOfReferences(specification: AnyObject) {\n const references: string[] = []\n\n // Make sure we\u2019re dealing with an object\n if (!specification || typeof specification !== 'object') {\n return references\n }\n\n // Traverse the specification and collect all references\n traverse(specification, (value: any) => {\n if (value.$ref && typeof value.$ref === 'string' && !value.$ref.startsWith('#')) {\n references.push(value.$ref.split('#')[0])\n }\n\n return value\n })\n\n // Remove duplicates\n return [...new Set(references)]\n}\n"],
4
+ "sourcesContent": ["import type { AnyObject } from '@/types/index'\nimport { traverse } from './traverse'\n\n/**\n * Walks through the specification and returns all references as an array.\n *\n * Warning: Doesn't return internal references.\n */\nexport function getListOfReferences(specification: AnyObject) {\n const references: string[] = []\n\n // Make sure we're dealing with an object\n if (!specification || typeof specification !== 'object') {\n return references\n }\n\n // Traverse the specification and collect all references\n traverse(specification, (value: any) => {\n if (value.$ref && typeof value.$ref === 'string' && !value.$ref.startsWith('#')) {\n references.push(value.$ref.split('#')[0])\n }\n\n return value\n })\n\n // Remove duplicates\n return [...new Set(references)]\n}\n"],
5
5
  "mappings": "AACA,SAAS,gBAAgB;AAOlB,SAAS,oBAAoB,eAA0B;AAC5D,QAAM,aAAuB,CAAC;AAG9B,MAAI,CAAC,iBAAiB,OAAO,kBAAkB,UAAU;AACvD,WAAO;AAAA,EACT;AAGA,WAAS,eAAe,CAAC,UAAe;AACtC,QAAI,MAAM,QAAQ,OAAO,MAAM,SAAS,YAAY,CAAC,MAAM,KAAK,WAAW,GAAG,GAAG;AAC/E,iBAAW,KAAK,MAAM,KAAK,MAAM,GAAG,EAAE,CAAC,CAAC;AAAA,IAC1C;AAEA,WAAO;AAAA,EACT,CAAC;AAGD,SAAO,CAAC,GAAG,IAAI,IAAI,UAAU,CAAC;AAChC;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/utils/load/load.ts"],
4
- "sourcesContent": ["import { ERRORS } from '@/configuration'\nimport type {\n AnyApiDefinitionFormat,\n AnyObject,\n ErrorObject,\n Filesystem,\n LoadResult,\n ThrowOnErrorOption,\n} from '@/types/index'\nimport { getEntrypoint } from '@/utils/get-entrypoint'\nimport { getListOfReferences } from '@/utils/get-list-of-references'\nimport { makeFilesystem } from '@/utils/make-filesystem'\nimport { normalize } from '@/utils/normalize'\n\nexport type LoadPlugin = {\n check: (value?: any) => boolean\n get: (value: any) => any\n resolvePath?: (value: any, reference: string) => string\n getDir?: (value: any) => string\n getFilename?: (value: any) => string\n}\n\nexport type LoadOptions = {\n plugins?: LoadPlugin[]\n filename?: string\n filesystem?: Filesystem\n} & ThrowOnErrorOption\n\n/**\n * @deprecated This function is deprecated and will be removed in a future version.\n * Please use the new bundler utility instead:\n * ```ts\n * import { bundle } from \"@scalar/openapi-parser\"\n * ```\n *\n * Loads an OpenAPI document, including any external references.\n *\n * This function handles loading content from various sources, normalizes the content,\n * and recursively loads any external references found within the definition.\n *\n * It builds a filesystem representation of all loaded content and collects any errors\n * encountered during the process.\n */\nexport async function load(value: AnyApiDefinitionFormat, options?: LoadOptions): Promise<LoadResult> {\n const errors: ErrorObject[] = []\n\n // Don\u2019t load a reference twice, check the filesystem before fetching something\n if (options?.filesystem?.find((entry) => entry.filename === value)) {\n return {\n specification: getEntrypoint(options.filesystem)?.specification,\n filesystem: options.filesystem,\n errors,\n }\n }\n\n // Check whether the value is an URL or file path\n const plugin = options?.plugins?.find((thisPlugin) => thisPlugin.check(value))\n\n let content: AnyObject\n\n if (plugin) {\n try {\n content = normalize(await plugin.get(value))\n } catch (_error) {\n if (options?.throwOnError) {\n throw new Error(ERRORS.EXTERNAL_REFERENCE_NOT_FOUND.replace('%s', value as string))\n }\n\n errors.push({\n code: 'EXTERNAL_REFERENCE_NOT_FOUND',\n message: ERRORS.EXTERNAL_REFERENCE_NOT_FOUND.replace('%s', value as string),\n })\n\n return {\n specification: null,\n filesystem: [],\n errors,\n }\n }\n } else {\n content = normalize(value)\n }\n\n // No content\n if (content === undefined) {\n if (options?.throwOnError) {\n throw new Error('No content to load')\n }\n\n errors.push({\n code: 'NO_CONTENT',\n message: ERRORS.NO_CONTENT,\n })\n\n return {\n specification: null,\n filesystem: [],\n errors,\n }\n }\n\n let filesystem = makeFilesystem(content, {\n filename: options?.filename ?? null,\n })\n\n // Get references from file system entry, or from the content\n const newEntry = options?.filename\n ? filesystem.find((entry) => entry.filename === options?.filename)\n : getEntrypoint(filesystem)\n\n const listOfReferences = newEntry.references ?? getListOfReferences(content)\n\n // No other references\n if (listOfReferences.length === 0) {\n return {\n specification: getEntrypoint(filesystem)?.specification,\n filesystem,\n errors,\n }\n }\n\n // Load other external references\n for (const reference of listOfReferences) {\n // Find a matching plugin\n const otherPlugin = options?.plugins?.find((thisPlugin) => thisPlugin.check(reference))\n\n // Skip if no plugin is found (internal references don\u2019t need a plugin for example)\n if (!otherPlugin) {\n continue\n }\n\n const target =\n otherPlugin.check(reference) && otherPlugin.resolvePath ? otherPlugin.resolvePath(value, reference) : reference\n\n // Don\u2019t load a reference twice, check the filesystem before fetching something\n if (filesystem.find((entry) => entry.filename === reference)) {\n continue\n }\n\n const { filesystem: referencedFiles, errors: newErrors } = await load(target, {\n ...options,\n // Make the filename the exact same value as the $ref\n // TODO: This leads to problems, if there are multiple references with the same file name but in different folders\n filename: reference,\n })\n\n errors.push(...newErrors)\n\n filesystem = [\n ...filesystem,\n ...referencedFiles.map((file) => {\n return {\n ...file,\n isEntrypoint: false,\n }\n }),\n ]\n }\n\n return {\n specification: getEntrypoint(filesystem)?.specification,\n filesystem,\n errors,\n }\n}\n"],
4
+ "sourcesContent": ["import { ERRORS } from '@/configuration'\nimport type {\n AnyApiDefinitionFormat,\n AnyObject,\n ErrorObject,\n Filesystem,\n LoadResult,\n ThrowOnErrorOption,\n} from '@/types/index'\nimport { getEntrypoint } from '@/utils/get-entrypoint'\nimport { getListOfReferences } from '@/utils/get-list-of-references'\nimport { makeFilesystem } from '@/utils/make-filesystem'\nimport { normalize } from '@/utils/normalize'\n\nexport type LoadPlugin = {\n check: (value?: any) => boolean\n get: (value: any) => any\n resolvePath?: (value: any, reference: string) => string\n getDir?: (value: any) => string\n getFilename?: (value: any) => string\n}\n\nexport type LoadOptions = {\n plugins?: LoadPlugin[]\n filename?: string\n filesystem?: Filesystem\n} & ThrowOnErrorOption\n\n/**\n * @deprecated This function is deprecated and will be removed in a future version.\n * Please use the new bundler utility instead:\n * ```ts\n * import { bundle } from \"@scalar/openapi-parser\"\n * ```\n *\n * Loads an OpenAPI document, including any external references.\n *\n * This function handles loading content from various sources, normalizes the content,\n * and recursively loads any external references found within the definition.\n *\n * It builds a filesystem representation of all loaded content and collects any errors\n * encountered during the process.\n */\nexport async function load(value: AnyApiDefinitionFormat, options?: LoadOptions): Promise<LoadResult> {\n const errors: ErrorObject[] = []\n\n // Don't load a reference twice, check the filesystem before fetching something\n if (options?.filesystem?.find((entry) => entry.filename === value)) {\n return {\n specification: getEntrypoint(options.filesystem)?.specification,\n filesystem: options.filesystem,\n errors,\n }\n }\n\n // Check whether the value is an URL or file path\n const plugin = options?.plugins?.find((thisPlugin) => thisPlugin.check(value))\n\n let content: AnyObject\n\n if (plugin) {\n try {\n content = normalize(await plugin.get(value))\n } catch (_error) {\n if (options?.throwOnError) {\n throw new Error(ERRORS.EXTERNAL_REFERENCE_NOT_FOUND.replace('%s', value as string))\n }\n\n errors.push({\n code: 'EXTERNAL_REFERENCE_NOT_FOUND',\n message: ERRORS.EXTERNAL_REFERENCE_NOT_FOUND.replace('%s', value as string),\n })\n\n return {\n specification: null,\n filesystem: [],\n errors,\n }\n }\n } else {\n content = normalize(value)\n }\n\n // No content\n if (content === undefined) {\n if (options?.throwOnError) {\n throw new Error('No content to load')\n }\n\n errors.push({\n code: 'NO_CONTENT',\n message: ERRORS.NO_CONTENT,\n })\n\n return {\n specification: null,\n filesystem: [],\n errors,\n }\n }\n\n let filesystem = makeFilesystem(content, {\n filename: options?.filename ?? null,\n })\n\n // Get references from file system entry, or from the content\n const newEntry = options?.filename\n ? filesystem.find((entry) => entry.filename === options?.filename)\n : getEntrypoint(filesystem)\n\n const listOfReferences = newEntry.references ?? getListOfReferences(content)\n\n // No other references\n if (listOfReferences.length === 0) {\n return {\n specification: getEntrypoint(filesystem)?.specification,\n filesystem,\n errors,\n }\n }\n\n // Load other external references\n for (const reference of listOfReferences) {\n // Find a matching plugin\n const otherPlugin = options?.plugins?.find((thisPlugin) => thisPlugin.check(reference))\n\n // Skip if no plugin is found (internal references don't need a plugin for example)\n if (!otherPlugin) {\n continue\n }\n\n const target =\n otherPlugin.check(reference) && otherPlugin.resolvePath ? otherPlugin.resolvePath(value, reference) : reference\n\n // Don't load a reference twice, check the filesystem before fetching something\n if (filesystem.find((entry) => entry.filename === reference)) {\n continue\n }\n\n const { filesystem: referencedFiles, errors: newErrors } = await load(target, {\n ...options,\n // Make the filename the exact same value as the $ref\n // TODO: This leads to problems, if there are multiple references with the same file name but in different folders\n filename: reference,\n })\n\n errors.push(...newErrors)\n\n filesystem = [\n ...filesystem,\n ...referencedFiles.map((file) => {\n return {\n ...file,\n isEntrypoint: false,\n }\n }),\n ]\n }\n\n return {\n specification: getEntrypoint(filesystem)?.specification,\n filesystem,\n errors,\n }\n}\n"],
5
5
  "mappings": "AAAA,SAAS,cAAc;AASvB,SAAS,qBAAqB;AAC9B,SAAS,2BAA2B;AACpC,SAAS,sBAAsB;AAC/B,SAAS,iBAAiB;AA+B1B,eAAsB,KAAK,OAA+B,SAA4C;AACpG,QAAM,SAAwB,CAAC;AAG/B,MAAI,SAAS,YAAY,KAAK,CAAC,UAAU,MAAM,aAAa,KAAK,GAAG;AAClE,WAAO;AAAA,MACL,eAAe,cAAc,QAAQ,UAAU,GAAG;AAAA,MAClD,YAAY,QAAQ;AAAA,MACpB;AAAA,IACF;AAAA,EACF;AAGA,QAAM,SAAS,SAAS,SAAS,KAAK,CAAC,eAAe,WAAW,MAAM,KAAK,CAAC;AAE7E,MAAI;AAEJ,MAAI,QAAQ;AACV,QAAI;AACF,gBAAU,UAAU,MAAM,OAAO,IAAI,KAAK,CAAC;AAAA,IAC7C,SAAS,QAAQ;AACf,UAAI,SAAS,cAAc;AACzB,cAAM,IAAI,MAAM,OAAO,6BAA6B,QAAQ,MAAM,KAAe,CAAC;AAAA,MACpF;AAEA,aAAO,KAAK;AAAA,QACV,MAAM;AAAA,QACN,SAAS,OAAO,6BAA6B,QAAQ,MAAM,KAAe;AAAA,MAC5E,CAAC;AAED,aAAO;AAAA,QACL,eAAe;AAAA,QACf,YAAY,CAAC;AAAA,QACb;AAAA,MACF;AAAA,IACF;AAAA,EACF,OAAO;AACL,cAAU,UAAU,KAAK;AAAA,EAC3B;AAGA,MAAI,YAAY,QAAW;AACzB,QAAI,SAAS,cAAc;AACzB,YAAM,IAAI,MAAM,oBAAoB;AAAA,IACtC;AAEA,WAAO,KAAK;AAAA,MACV,MAAM;AAAA,MACN,SAAS,OAAO;AAAA,IAClB,CAAC;AAED,WAAO;AAAA,MACL,eAAe;AAAA,MACf,YAAY,CAAC;AAAA,MACb;AAAA,IACF;AAAA,EACF;AAEA,MAAI,aAAa,eAAe,SAAS;AAAA,IACvC,UAAU,SAAS,YAAY;AAAA,EACjC,CAAC;AAGD,QAAM,WAAW,SAAS,WACtB,WAAW,KAAK,CAAC,UAAU,MAAM,aAAa,SAAS,QAAQ,IAC/D,cAAc,UAAU;AAE5B,QAAM,mBAAmB,SAAS,cAAc,oBAAoB,OAAO;AAG3E,MAAI,iBAAiB,WAAW,GAAG;AACjC,WAAO;AAAA,MACL,eAAe,cAAc,UAAU,GAAG;AAAA,MAC1C;AAAA,MACA;AAAA,IACF;AAAA,EACF;AAGA,aAAW,aAAa,kBAAkB;AAExC,UAAM,cAAc,SAAS,SAAS,KAAK,CAAC,eAAe,WAAW,MAAM,SAAS,CAAC;AAGtF,QAAI,CAAC,aAAa;AAChB;AAAA,IACF;AAEA,UAAM,SACJ,YAAY,MAAM,SAAS,KAAK,YAAY,cAAc,YAAY,YAAY,OAAO,SAAS,IAAI;AAGxG,QAAI,WAAW,KAAK,CAAC,UAAU,MAAM,aAAa,SAAS,GAAG;AAC5D;AAAA,IACF;AAEA,UAAM,EAAE,YAAY,iBAAiB,QAAQ,UAAU,IAAI,MAAM,KAAK,QAAQ;AAAA,MAC5E,GAAG;AAAA;AAAA;AAAA,MAGH,UAAU;AAAA,IACZ,CAAC;AAED,WAAO,KAAK,GAAG,SAAS;AAExB,iBAAa;AAAA,MACX,GAAG;AAAA,MACH,GAAG,gBAAgB,IAAI,CAAC,SAAS;AAC/B,eAAO;AAAA,UACL,GAAG;AAAA,UACH,cAAc;AAAA,QAChB;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO;AAAA,IACL,eAAe,cAAc,UAAU,GAAG;AAAA,IAC1C;AAAA,IACA;AAAA,EACF;AACF;",
6
6
  "names": []
7
7
  }
@@ -3,7 +3,7 @@ import type { Filesystem } from '../types/index.js';
3
3
  /**
4
4
  * Normalize the OpenAPI document (YAML, JSON, object) to a JavaScript object.
5
5
  *
6
- * Doesn’t modify the object if it’s a `Filesystem` (multiple files) already.
6
+ * Doesn't modify the object if it's a `Filesystem` (multiple files) already.
7
7
  */
8
8
  export declare function normalize(content: string | UnknownObject | Filesystem): UnknownObject | Filesystem;
9
9
  //# sourceMappingURL=normalize.d.ts.map
@@ -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 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"],
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't modify the object if it's 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
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
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/utils/resolve-references.ts"],
4
- "sourcesContent": ["import type { OpenAPI } from '@scalar/openapi-types'\n\nimport { ERRORS } from '@/configuration'\nimport type { AnyObject, ErrorObject, Filesystem, FilesystemEntry, ThrowOnErrorOption } from '@/types/index'\nimport { getEntrypoint } from './get-entrypoint'\nimport { getSegmentsFromPath } from './get-segments-from-path'\nimport { isObject } from './is-object'\nimport { makeFilesystem } from './make-filesystem'\n\n// TODO: Add support for all pointer words\n// export const pointerWords = new Set([\n// '$ref',\n// '$id',\n// '$anchor',\n// '$dynamicRef',\n// '$dynamicAnchor',\n// '$schema',\n// ])\n\nexport type ResolveReferencesResult = {\n valid: boolean\n errors: ErrorObject[]\n schema: OpenAPI.Document\n}\n\nexport type ResolveReferencesOptions = ThrowOnErrorOption & {\n /**\n * Fired when dereferenced a schema.\n *\n * Note that for object schemas, its properties may not be dereferenced when the hook is called.\n */\n onDereference?: (data: { schema: AnyObject; ref: string }) => void\n}\n\n/**\n * Takes a specification and resolves all references.\n */\nexport function resolveReferences(\n // Just a specification, or a set of files.\n input: AnyObject | Filesystem,\n // Additional options to control the behaviour\n options?: ResolveReferencesOptions,\n // Fallback to the entrypoint\n file?: FilesystemEntry,\n // Errors that occurred during the process\n errors: ErrorObject[] = [],\n): ResolveReferencesResult {\n // Detach from input\n const clonedInput = structuredClone(input)\n\n // Make it a filesystem, even if it\u2019s just one file\n const filesystem = makeFilesystem(clonedInput)\n\n // Get the main file\n const entrypoint = getEntrypoint(filesystem)\n\n const finalInput = file?.specification ?? entrypoint.specification\n\n // Does it look like an OpenAPI document?\n if (!isObject(finalInput)) {\n if (options?.throwOnError) {\n throw new Error(ERRORS.NO_CONTENT)\n }\n\n return {\n valid: false,\n errors,\n schema: finalInput as OpenAPI.Document,\n }\n }\n\n // Recursively resolve all references\n dereference(finalInput, filesystem, file ?? entrypoint, new WeakSet(), errors, options)\n\n // Remove duplicats (according to message) from errors\n errors = errors.filter(\n (error, index, self) => index === self.findIndex((t) => t.message === error.message && t.code === error.code),\n )\n\n // Return the resolved specification\n return {\n valid: errors.length === 0,\n errors,\n schema: finalInput as OpenAPI.Document,\n }\n}\n\n/**\n * Resolves the circular reference to an object and deletes the $ref properties (in-place).\n */\nfunction dereference(\n schema: AnyObject,\n filesystem: Filesystem,\n entrypoint: FilesystemEntry,\n // references to resolved object\n resolvedSchemas: WeakSet<object>,\n // error output\n errors: ErrorObject[],\n\n options?: ResolveReferencesOptions,\n): void {\n if (schema === null || resolvedSchemas.has(schema)) {\n return\n }\n resolvedSchemas.add(schema)\n\n function resolveExternal(externalFile: FilesystemEntry) {\n dereference(externalFile.specification, filesystem, externalFile, resolvedSchemas, errors, options)\n\n return externalFile\n }\n\n while (schema.$ref !== undefined) {\n // Find the referenced content\n const resolved = resolveUri(schema.$ref, options, entrypoint, filesystem, resolveExternal, errors)\n\n // invalid\n if (typeof resolved !== 'object' || resolved === null) {\n break\n }\n const dereferencedRef = schema.$ref\n\n // Get rid of the reference\n delete schema.$ref\n\n for (const key of Object.keys(resolved)) {\n if (schema[key] === undefined) {\n schema[key] = resolved[key]\n }\n }\n\n if (dereferencedRef) {\n options?.onDereference?.({ schema, ref: dereferencedRef })\n }\n }\n\n // Iterate over the whole object\n for (const value of Object.values(schema)) {\n if (typeof value === 'object' && value !== null) {\n dereference(value, filesystem, entrypoint, resolvedSchemas, errors, options)\n }\n }\n}\n\n/**\n * Resolves a URI to a part of the specification\n *\n * The output is not necessarily dereferenced\n */\nfunction resolveUri(\n // 'foobar.json#/foo/bar'\n uri: string,\n options: ResolveReferencesOptions,\n // { filename: './foobar.json '}\n file: FilesystemEntry,\n // [ { filename: './foobar.json '} ]\n filesystem: Filesystem,\n\n // a function to resolve references in external file\n resolve: (file: FilesystemEntry) => FilesystemEntry,\n\n errors: ErrorObject[],\n): AnyObject | undefined {\n // Ignore invalid URIs\n if (typeof uri !== 'string') {\n if (options?.throwOnError) {\n throw new Error(ERRORS.INVALID_REFERENCE.replace('%s', uri))\n }\n\n errors.push({\n code: 'INVALID_REFERENCE',\n message: ERRORS.INVALID_REFERENCE.replace('%s', uri),\n })\n\n return undefined\n }\n\n // Understand the URI\n const [prefix, path] = uri.split('#', 2)\n\n /** Check whether the file is pointing to itself */\n const isDifferentFile = prefix !== file.filename\n\n // External references\n if (prefix && isDifferentFile) {\n const externalReference = filesystem.find((entry) => {\n return entry.filename === prefix\n })\n\n if (!externalReference) {\n if (options?.throwOnError) {\n throw new Error(ERRORS.EXTERNAL_REFERENCE_NOT_FOUND.replace('%s', prefix))\n }\n\n errors.push({\n code: 'EXTERNAL_REFERENCE_NOT_FOUND',\n message: ERRORS.EXTERNAL_REFERENCE_NOT_FOUND.replace('%s', prefix),\n })\n\n return undefined\n }\n // $ref: 'other-file.yaml'\n if (path === undefined) {\n return externalReference.specification\n }\n\n // $ref: 'other-file.yaml#/foo/bar'\n // resolve refs first before accessing properties directly\n return resolveUri(`#${path}`, options, resolve(externalReference), filesystem, resolve, errors)\n }\n\n // Pointers\n const segments = getSegmentsFromPath(path)\n\n // Try to find the URI\n try {\n return segments.reduce((acc, key) => {\n return acc[key]\n }, file.specification)\n } catch (_error) {\n if (options?.throwOnError) {\n throw new Error(ERRORS.INVALID_REFERENCE.replace('%s', uri))\n }\n\n errors.push({\n code: 'INVALID_REFERENCE',\n message: ERRORS.INVALID_REFERENCE.replace('%s', uri),\n })\n }\n\n return undefined\n}\n"],
4
+ "sourcesContent": ["import type { OpenAPI } from '@scalar/openapi-types'\n\nimport { ERRORS } from '@/configuration'\nimport type { AnyObject, ErrorObject, Filesystem, FilesystemEntry, ThrowOnErrorOption } from '@/types/index'\nimport { getEntrypoint } from './get-entrypoint'\nimport { getSegmentsFromPath } from './get-segments-from-path'\nimport { isObject } from './is-object'\nimport { makeFilesystem } from './make-filesystem'\n\n// TODO: Add support for all pointer words\n// export const pointerWords = new Set([\n// '$ref',\n// '$id',\n// '$anchor',\n// '$dynamicRef',\n// '$dynamicAnchor',\n// '$schema',\n// ])\n\nexport type ResolveReferencesResult = {\n valid: boolean\n errors: ErrorObject[]\n schema: OpenAPI.Document\n}\n\nexport type ResolveReferencesOptions = ThrowOnErrorOption & {\n /**\n * Fired when dereferenced a schema.\n *\n * Note that for object schemas, its properties may not be dereferenced when the hook is called.\n */\n onDereference?: (data: { schema: AnyObject; ref: string }) => void\n}\n\n/**\n * Takes a specification and resolves all references.\n */\nexport function resolveReferences(\n // Just a specification, or a set of files.\n input: AnyObject | Filesystem,\n // Additional options to control the behaviour\n options?: ResolveReferencesOptions,\n // Fallback to the entrypoint\n file?: FilesystemEntry,\n // Errors that occurred during the process\n errors: ErrorObject[] = [],\n): ResolveReferencesResult {\n // Detach from input\n const clonedInput = structuredClone(input)\n\n // Make it a filesystem, even if it's just one file\n const filesystem = makeFilesystem(clonedInput)\n\n // Get the main file\n const entrypoint = getEntrypoint(filesystem)\n\n const finalInput = file?.specification ?? entrypoint.specification\n\n // Does it look like an OpenAPI document?\n if (!isObject(finalInput)) {\n if (options?.throwOnError) {\n throw new Error(ERRORS.NO_CONTENT)\n }\n\n return {\n valid: false,\n errors,\n schema: finalInput as OpenAPI.Document,\n }\n }\n\n // Recursively resolve all references\n dereference(finalInput, filesystem, file ?? entrypoint, new WeakSet(), errors, options)\n\n // Remove duplicats (according to message) from errors\n errors = errors.filter(\n (error, index, self) => index === self.findIndex((t) => t.message === error.message && t.code === error.code),\n )\n\n // Return the resolved specification\n return {\n valid: errors.length === 0,\n errors,\n schema: finalInput as OpenAPI.Document,\n }\n}\n\n/**\n * Resolves the circular reference to an object and deletes the $ref properties (in-place).\n */\nfunction dereference(\n schema: AnyObject,\n filesystem: Filesystem,\n entrypoint: FilesystemEntry,\n // references to resolved object\n resolvedSchemas: WeakSet<object>,\n // error output\n errors: ErrorObject[],\n\n options?: ResolveReferencesOptions,\n): void {\n if (schema === null || resolvedSchemas.has(schema)) {\n return\n }\n resolvedSchemas.add(schema)\n\n function resolveExternal(externalFile: FilesystemEntry) {\n dereference(externalFile.specification, filesystem, externalFile, resolvedSchemas, errors, options)\n\n return externalFile\n }\n\n while (schema.$ref !== undefined) {\n // Find the referenced content\n const resolved = resolveUri(schema.$ref, options, entrypoint, filesystem, resolveExternal, errors)\n\n // invalid\n if (typeof resolved !== 'object' || resolved === null) {\n break\n }\n const dereferencedRef = schema.$ref\n\n // Get rid of the reference\n delete schema.$ref\n\n for (const key of Object.keys(resolved)) {\n if (schema[key] === undefined) {\n schema[key] = resolved[key]\n }\n }\n\n if (dereferencedRef) {\n options?.onDereference?.({ schema, ref: dereferencedRef })\n }\n }\n\n // Iterate over the whole object\n for (const value of Object.values(schema)) {\n if (typeof value === 'object' && value !== null) {\n dereference(value, filesystem, entrypoint, resolvedSchemas, errors, options)\n }\n }\n}\n\n/**\n * Resolves a URI to a part of the specification\n *\n * The output is not necessarily dereferenced\n */\nfunction resolveUri(\n // 'foobar.json#/foo/bar'\n uri: string,\n options: ResolveReferencesOptions,\n // { filename: './foobar.json '}\n file: FilesystemEntry,\n // [ { filename: './foobar.json '} ]\n filesystem: Filesystem,\n\n // a function to resolve references in external file\n resolve: (file: FilesystemEntry) => FilesystemEntry,\n\n errors: ErrorObject[],\n): AnyObject | undefined {\n // Ignore invalid URIs\n if (typeof uri !== 'string') {\n if (options?.throwOnError) {\n throw new Error(ERRORS.INVALID_REFERENCE.replace('%s', uri))\n }\n\n errors.push({\n code: 'INVALID_REFERENCE',\n message: ERRORS.INVALID_REFERENCE.replace('%s', uri),\n })\n\n return undefined\n }\n\n // Understand the URI\n const [prefix, path] = uri.split('#', 2)\n\n /** Check whether the file is pointing to itself */\n const isDifferentFile = prefix !== file.filename\n\n // External references\n if (prefix && isDifferentFile) {\n const externalReference = filesystem.find((entry) => {\n return entry.filename === prefix\n })\n\n if (!externalReference) {\n if (options?.throwOnError) {\n throw new Error(ERRORS.EXTERNAL_REFERENCE_NOT_FOUND.replace('%s', prefix))\n }\n\n errors.push({\n code: 'EXTERNAL_REFERENCE_NOT_FOUND',\n message: ERRORS.EXTERNAL_REFERENCE_NOT_FOUND.replace('%s', prefix),\n })\n\n return undefined\n }\n // $ref: 'other-file.yaml'\n if (path === undefined) {\n return externalReference.specification\n }\n\n // $ref: 'other-file.yaml#/foo/bar'\n // resolve refs first before accessing properties directly\n return resolveUri(`#${path}`, options, resolve(externalReference), filesystem, resolve, errors)\n }\n\n // Pointers\n const segments = getSegmentsFromPath(path)\n\n // Try to find the URI\n try {\n return segments.reduce((acc, key) => {\n return acc[key]\n }, file.specification)\n } catch (_error) {\n if (options?.throwOnError) {\n throw new Error(ERRORS.INVALID_REFERENCE.replace('%s', uri))\n }\n\n errors.push({\n code: 'INVALID_REFERENCE',\n message: ERRORS.INVALID_REFERENCE.replace('%s', uri),\n })\n }\n\n return undefined\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -5,7 +5,7 @@ export { DEFAULT_TITLE, DEFAULT_VERSION } from './utils/addInfoObject.js';
5
5
  /**
6
6
  * Make an OpenAPI document a valid and clean OpenAPI document
7
7
  *
8
- * @deprecated We’re about to drop this from the package.
8
+ * @deprecated We're about to drop this from the package.
9
9
  */
10
10
  export declare function sanitize(definition: AnyObject): OpenAPI.Document;
11
11
  //# sourceMappingURL=sanitize.d.ts.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/utils/transform/sanitize.ts"],
4
- "sourcesContent": ["import type { OpenAPI } from '@scalar/openapi-types'\n\nimport type { AnyObject } from '@/types/index'\nimport { addInfoObject } from './utils/addInfoObject'\nimport { addLatestOpenApiVersion } from './utils/addLatestOpenApiVersion'\nimport { addMissingTags } from './utils/addMissingTags'\nimport { normalizeSecuritySchemes } from './utils/normalizeSecuritySchemes'\nimport { rejectSwaggerDocuments } from './utils/rejectSwaggerDocuments'\n\nexport { DEFAULT_OPENAPI_VERSION } from './utils/addLatestOpenApiVersion'\nexport { DEFAULT_TITLE, DEFAULT_VERSION } from './utils/addInfoObject'\n\n/**\n * Make an OpenAPI document a valid and clean OpenAPI document\n *\n * @deprecated We\u2019re about to drop this from the package.\n */\nexport function sanitize(definition: AnyObject): OpenAPI.Document {\n const transformers = [\n rejectSwaggerDocuments,\n addLatestOpenApiVersion,\n addInfoObject,\n addMissingTags,\n normalizeSecuritySchemes,\n ]\n\n return transformers.reduce((doc, transformer) => transformer(doc), definition)\n}\n"],
4
+ "sourcesContent": ["import type { OpenAPI } from '@scalar/openapi-types'\n\nimport type { AnyObject } from '@/types/index'\nimport { addInfoObject } from './utils/addInfoObject'\nimport { addLatestOpenApiVersion } from './utils/addLatestOpenApiVersion'\nimport { addMissingTags } from './utils/addMissingTags'\nimport { normalizeSecuritySchemes } from './utils/normalizeSecuritySchemes'\nimport { rejectSwaggerDocuments } from './utils/rejectSwaggerDocuments'\n\nexport { DEFAULT_OPENAPI_VERSION } from './utils/addLatestOpenApiVersion'\nexport { DEFAULT_TITLE, DEFAULT_VERSION } from './utils/addInfoObject'\n\n/**\n * Make an OpenAPI document a valid and clean OpenAPI document\n *\n * @deprecated We're about to drop this from the package.\n */\nexport function sanitize(definition: AnyObject): OpenAPI.Document {\n const transformers = [\n rejectSwaggerDocuments,\n addLatestOpenApiVersion,\n addInfoObject,\n addMissingTags,\n normalizeSecuritySchemes,\n ]\n\n return transformers.reduce((doc, transformer) => transformer(doc), definition)\n}\n"],
5
5
  "mappings": "AAGA,SAAS,qBAAqB;AAC9B,SAAS,+BAA+B;AACxC,SAAS,sBAAsB;AAC/B,SAAS,gCAAgC;AACzC,SAAS,8BAA8B;AAEvC,SAAS,+BAA+B;AACxC,SAAS,eAAe,uBAAuB;AAOxC,SAAS,SAAS,YAAyC;AAChE,QAAM,eAAe;AAAA,IACnB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,SAAO,aAAa,OAAO,CAAC,KAAK,gBAAgB,YAAY,GAAG,GAAG,UAAU;AAC/E;",
6
6
  "names": []
7
7
  }
@@ -31,7 +31,7 @@ function upgradeFromThreeToThreeOne(originalContent) {
31
31
  return content;
32
32
  }
33
33
  const applyChangesToDocument = (schema, path) => {
34
- if (schema.type !== "undefined" && schema.nullable === true) {
34
+ if (schema.type !== void 0 && schema.nullable === true) {
35
35
  schema.type = [schema.type, "null"];
36
36
  delete schema.nullable;
37
37
  }
@@ -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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",
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
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../src/utils/upgrade-from-two-to-three.ts"],
4
- "sourcesContent": ["import type { OpenAPIV2, OpenAPIV3 } from '@scalar/openapi-types'\nimport type { UnknownObject } from '@scalar/types/utils'\n\nimport { traverse } from './traverse'\n\n/**\n * Upgrade Swagger 2.0 to OpenAPI 3.0\n *\n * https://swagger.io/blog/news/whats-new-in-openapi-3-0/\n */\nexport function upgradeFromTwoToThree(originalSpecification: UnknownObject) {\n let specification = originalSpecification\n\n // Version\n if (\n specification !== null &&\n typeof specification === 'object' &&\n typeof specification.swagger === 'string' &&\n specification.swagger?.startsWith('2.0')\n ) {\n specification.openapi = '3.0.4'\n delete specification.swagger\n } else {\n // Skip if it\u2019s something else than 3.0.x\n return specification\n }\n\n // Servers\n if (specification.host) {\n const schemes =\n Array.isArray(specification.schemes) && specification.schemes?.length ? specification.schemes : ['http']\n\n specification.servers = schemes.map((scheme: string[]) => ({\n url: `${scheme}://${specification.host}${specification.basePath ?? ''}`,\n }))\n\n delete specification.basePath\n delete specification.schemes\n delete specification.host\n } else if (specification.basePath) {\n specification.servers = [{ url: specification.basePath }]\n delete specification.basePath\n }\n\n // Schemas\n if (specification.definitions) {\n specification.components = Object.assign({}, specification.components, {\n schemas: specification.definitions,\n })\n\n delete specification.definitions\n\n // Rewrite $refs to definitions\n specification = traverse(specification, (schema) => {\n // Rewrite $refs to components\n if (typeof schema.$ref === 'string' && schema.$ref.startsWith('#/definitions/')) {\n schema.$ref = schema.$ref.replace(/^#\\/definitions\\//, '#/components/schemas/')\n }\n\n return schema\n })\n }\n\n // Transform file type to string with binary format\n specification = traverse(specification, (schema) => {\n if (schema.type === 'file') {\n schema.type = 'string'\n schema.format = 'binary'\n }\n\n return schema\n })\n\n // Paths\n if (typeof specification.paths === 'object') {\n for (const path in specification.paths) {\n if (Object.hasOwn(specification.paths, path)) {\n const pathItem = specification.paths[path]\n\n for (const method in pathItem) {\n if (Object.hasOwn(pathItem, method)) {\n const operationItem = pathItem[method]\n\n // Request bodies\n if (operationItem.parameters) {\n const bodyParameter = structuredClone(\n operationItem.parameters.find((parameter: OpenAPIV3.ParameterObject) => parameter.in === 'body') ?? {},\n )\n\n if (bodyParameter && Object.keys(bodyParameter).length) {\n delete bodyParameter.name\n delete bodyParameter.in\n\n const consumes = specification.consumes ?? operationItem.consumes ?? ['application/json']\n\n if (typeof operationItem.requestBody !== 'object') {\n operationItem.requestBody = {}\n }\n\n if (typeof operationItem.requestBody.content !== 'object') {\n operationItem.requestBody.content = {}\n }\n\n const { schema, ...requestBody } = bodyParameter\n\n operationItem.requestBody = {\n ...operationItem.requestBody,\n ...requestBody,\n }\n\n for (const type of consumes) {\n operationItem.requestBody.content[type] = {\n schema: schema,\n }\n }\n }\n\n // Delete body parameter\n operationItem.parameters = operationItem.parameters.filter(\n (parameter: OpenAPIV2.ParameterObject) => parameter.in !== 'body',\n )\n\n delete operationItem.consumes\n\n // formData parameters\n const formDataParameters = operationItem.parameters.filter(\n (parameter: OpenAPIV2.ParameterObject) => parameter.in === 'formData',\n )\n\n if (formDataParameters.length > 0) {\n if (typeof operationItem.requestBody !== 'object') {\n operationItem.requestBody = {}\n }\n\n if (typeof operationItem.requestBody.content !== 'object') {\n operationItem.requestBody.content = {}\n }\n\n operationItem.requestBody.content['application/x-www-form-urlencoded'] = {\n schema: {\n type: 'object',\n properties: {},\n required: [], // Initialize required array\n },\n }\n\n for (const param of formDataParameters) {\n operationItem.requestBody.content['application/x-www-form-urlencoded'].schema.properties[param.name] =\n {\n type: param.type,\n description: param.description,\n }\n\n // Add to required array if param is required\n if (param.required) {\n operationItem.requestBody.content['application/x-www-form-urlencoded'].schema.required.push(\n param.name,\n )\n }\n }\n\n // Remove formData parameters from the parameters array\n operationItem.parameters = operationItem.parameters.filter(\n (parameter: OpenAPIV2.ParameterObject) => parameter.in !== 'formData',\n )\n }\n\n operationItem.parameters = operationItem.parameters.map((parameter) =>\n transformParameterObject(parameter),\n )\n }\n\n // Responses\n if (operationItem.responses) {\n for (const response in operationItem.responses) {\n if (Object.hasOwn(operationItem.responses, response)) {\n const responseItem = operationItem.responses[response]\n\n if (responseItem.headers) {\n responseItem.headers = Object.entries(responseItem.headers).reduce((acc, [name, header]) => {\n return {\n [name]: transformParameterObject(header),\n ...acc,\n }\n }, {})\n }\n if (responseItem.schema) {\n const produces = specification.produces ?? operationItem.produces ?? ['application/json']\n\n if (typeof responseItem.content !== 'object') {\n responseItem.content = {}\n }\n\n for (const type of produces) {\n responseItem.content[type] = {\n schema: responseItem.schema,\n }\n }\n\n delete responseItem.schema\n }\n }\n }\n }\n\n delete operationItem.produces\n\n // Delete empty parameters\n if (operationItem.parameters?.length === 0) {\n delete operationItem.parameters\n }\n }\n }\n }\n }\n }\n\n // Upgrade securityDefinitions\n if (specification.securityDefinitions) {\n if (typeof specification.components !== 'object') {\n specification.components = {}\n }\n\n // Assert that components is of type OpenAPIV3.ComponentsObject\n specification.components = specification.components as OpenAPIV3.ComponentsObject\n\n Object.assign(specification.components, { securitySchemes: {} })\n\n for (const [key, securityScheme] of Object.entries(specification.securityDefinitions)) {\n if (typeof securityScheme === 'object') {\n if ('type' in securityScheme && securityScheme.type === 'oauth2') {\n const { flow, authorizationUrl, tokenUrl, scopes } = securityScheme as {\n type: 'oauth2'\n flow?: string\n authorizationUrl?: string\n tokenUrl?: string\n scopes?: Record<string, string>\n }\n\n // Assert that securitySchemes is of type OpenAPIV3.SecuritySchemeObject\n Object.assign((specification.components as OpenAPIV3.ComponentsObject).securitySchemes, {\n [key]: {\n type: 'oauth2',\n flows: {\n [flow as string]: Object.assign(\n {},\n authorizationUrl && { authorizationUrl },\n tokenUrl && { tokenUrl },\n scopes && { scopes },\n ),\n },\n },\n })\n } else if ('type' in securityScheme && securityScheme.type === 'basic') {\n Object.assign((specification.components as OpenAPIV3.ComponentsObject).securitySchemes, {\n [key]: {\n type: 'http',\n scheme: 'basic',\n },\n })\n } else {\n Object.assign((specification.components as OpenAPIV3.ComponentsObject).securitySchemes, {\n [key]: securityScheme,\n })\n }\n }\n }\n\n delete specification.securityDefinitions\n }\n\n return specification as OpenAPIV3.Document\n}\n\nfunction transformItemsObject<T extends Record<PropertyKey, unknown>>(obj: T): OpenAPIV3.SchemaObject {\n const schemaProperties = [\n 'type',\n 'format',\n 'items',\n 'maximum',\n 'exclusiveMaximum',\n 'minimum',\n 'exclusiveMinimum',\n 'maxLength',\n 'minLength',\n 'pattern',\n 'maxItems',\n 'minItems',\n 'uniqueItems',\n 'enum',\n 'multipleOf',\n ]\n\n return schemaProperties.reduce((acc, property) => {\n if (Object.hasOwn(obj, property)) {\n acc[property] = obj[property]\n delete obj[property]\n }\n\n return acc\n }, {} as OpenAPIV3.SchemaObject)\n}\n\nfunction transformParameterObject(parameter: OpenAPIV2.ParameterObject): OpenAPIV3.ParameterObject {\n // it is important to call getParameterSerializationStyle first because transformItemsObject modifies properties on which getParameterSerializationStyle rely on\n const serializationStyle = getParameterSerializationStyle(parameter)\n const schema = transformItemsObject(parameter)\n\n delete parameter.collectionFormat\n delete parameter.default\n\n return {\n schema,\n ...serializationStyle,\n ...parameter,\n }\n}\n\ntype CollectionFormat = 'csv' | 'ssv' | 'tsv' | 'pipes' | 'multi'\n\ntype ParameterSerializationStyle = { style?: string; explode?: boolean }\n\nconst querySerialization: Record<CollectionFormat, ParameterSerializationStyle> = {\n ssv: {\n style: 'spaceDelimited',\n explode: false,\n },\n pipes: {\n style: 'pipeDelimited',\n explode: false,\n },\n multi: {\n style: 'form',\n explode: true,\n },\n csv: {\n style: 'form',\n explode: false,\n },\n tsv: {},\n}\n\nconst pathAndHeaderSerialization: Record<CollectionFormat, ParameterSerializationStyle> = {\n ssv: {},\n pipes: {},\n multi: {},\n csv: {\n style: 'simple',\n explode: false,\n },\n tsv: {},\n}\n\nconst serializationStyles = {\n header: pathAndHeaderSerialization,\n query: querySerialization,\n path: pathAndHeaderSerialization,\n} as const\n\nfunction getParameterSerializationStyle(parameter: OpenAPIV2.ParameterObject): ParameterSerializationStyle {\n if (\n parameter.type !== 'array' ||\n !(parameter.in === 'query' || parameter.in === 'path' || parameter.in === 'header')\n ) {\n return {}\n }\n\n const collectionFormat = parameter.collectionFormat ?? 'csv'\n\n return serializationStyles[parameter.in][collectionFormat]\n}\n"],
4
+ "sourcesContent": ["import type { OpenAPIV2, OpenAPIV3 } from '@scalar/openapi-types'\nimport type { UnknownObject } from '@scalar/types/utils'\n\nimport { traverse } from './traverse'\n\n/**\n * Upgrade Swagger 2.0 to OpenAPI 3.0\n *\n * https://swagger.io/blog/news/whats-new-in-openapi-3-0/\n */\nexport function upgradeFromTwoToThree(originalSpecification: UnknownObject) {\n let specification = originalSpecification\n\n // Version\n if (\n specification !== null &&\n typeof specification === 'object' &&\n typeof specification.swagger === 'string' &&\n specification.swagger?.startsWith('2.0')\n ) {\n specification.openapi = '3.0.4'\n delete specification.swagger\n } else {\n // Skip if it's something else than 3.0.x\n return specification\n }\n\n // Servers\n if (specification.host) {\n const schemes =\n Array.isArray(specification.schemes) && specification.schemes?.length ? specification.schemes : ['http']\n\n specification.servers = schemes.map((scheme: string[]) => ({\n url: `${scheme}://${specification.host}${specification.basePath ?? ''}`,\n }))\n\n delete specification.basePath\n delete specification.schemes\n delete specification.host\n } else if (specification.basePath) {\n specification.servers = [{ url: specification.basePath }]\n delete specification.basePath\n }\n\n // Schemas\n if (specification.definitions) {\n specification.components = Object.assign({}, specification.components, {\n schemas: specification.definitions,\n })\n\n delete specification.definitions\n\n // Rewrite $refs to definitions\n specification = traverse(specification, (schema) => {\n // Rewrite $refs to components\n if (typeof schema.$ref === 'string' && schema.$ref.startsWith('#/definitions/')) {\n schema.$ref = schema.$ref.replace(/^#\\/definitions\\//, '#/components/schemas/')\n }\n\n return schema\n })\n }\n\n // Transform file type to string with binary format\n specification = traverse(specification, (schema) => {\n if (schema.type === 'file') {\n schema.type = 'string'\n schema.format = 'binary'\n }\n\n return schema\n })\n\n // Paths\n if (typeof specification.paths === 'object') {\n for (const path in specification.paths) {\n if (Object.hasOwn(specification.paths, path)) {\n const pathItem = specification.paths[path]\n\n for (const method in pathItem) {\n if (Object.hasOwn(pathItem, method)) {\n const operationItem = pathItem[method]\n\n // Request bodies\n if (operationItem.parameters) {\n const bodyParameter = structuredClone(\n operationItem.parameters.find((parameter: OpenAPIV3.ParameterObject) => parameter.in === 'body') ?? {},\n )\n\n if (bodyParameter && Object.keys(bodyParameter).length) {\n delete bodyParameter.name\n delete bodyParameter.in\n\n const consumes = specification.consumes ?? operationItem.consumes ?? ['application/json']\n\n if (typeof operationItem.requestBody !== 'object') {\n operationItem.requestBody = {}\n }\n\n if (typeof operationItem.requestBody.content !== 'object') {\n operationItem.requestBody.content = {}\n }\n\n const { schema, ...requestBody } = bodyParameter\n\n operationItem.requestBody = {\n ...operationItem.requestBody,\n ...requestBody,\n }\n\n for (const type of consumes) {\n operationItem.requestBody.content[type] = {\n schema: schema,\n }\n }\n }\n\n // Delete body parameter\n operationItem.parameters = operationItem.parameters.filter(\n (parameter: OpenAPIV2.ParameterObject) => parameter.in !== 'body',\n )\n\n delete operationItem.consumes\n\n // formData parameters\n const formDataParameters = operationItem.parameters.filter(\n (parameter: OpenAPIV2.ParameterObject) => parameter.in === 'formData',\n )\n\n if (formDataParameters.length > 0) {\n if (typeof operationItem.requestBody !== 'object') {\n operationItem.requestBody = {}\n }\n\n if (typeof operationItem.requestBody.content !== 'object') {\n operationItem.requestBody.content = {}\n }\n\n operationItem.requestBody.content['application/x-www-form-urlencoded'] = {\n schema: {\n type: 'object',\n properties: {},\n required: [], // Initialize required array\n },\n }\n\n for (const param of formDataParameters) {\n operationItem.requestBody.content['application/x-www-form-urlencoded'].schema.properties[param.name] =\n {\n type: param.type,\n description: param.description,\n }\n\n // Add to required array if param is required\n if (param.required) {\n operationItem.requestBody.content['application/x-www-form-urlencoded'].schema.required.push(\n param.name,\n )\n }\n }\n\n // Remove formData parameters from the parameters array\n operationItem.parameters = operationItem.parameters.filter(\n (parameter: OpenAPIV2.ParameterObject) => parameter.in !== 'formData',\n )\n }\n\n operationItem.parameters = operationItem.parameters.map((parameter) =>\n transformParameterObject(parameter),\n )\n }\n\n // Responses\n if (operationItem.responses) {\n for (const response in operationItem.responses) {\n if (Object.hasOwn(operationItem.responses, response)) {\n const responseItem = operationItem.responses[response]\n\n if (responseItem.headers) {\n responseItem.headers = Object.entries(responseItem.headers).reduce((acc, [name, header]) => {\n return {\n [name]: transformParameterObject(header),\n ...acc,\n }\n }, {})\n }\n if (responseItem.schema) {\n const produces = specification.produces ?? operationItem.produces ?? ['application/json']\n\n if (typeof responseItem.content !== 'object') {\n responseItem.content = {}\n }\n\n for (const type of produces) {\n responseItem.content[type] = {\n schema: responseItem.schema,\n }\n }\n\n delete responseItem.schema\n }\n }\n }\n }\n\n delete operationItem.produces\n\n // Delete empty parameters\n if (operationItem.parameters?.length === 0) {\n delete operationItem.parameters\n }\n }\n }\n }\n }\n }\n\n // Upgrade securityDefinitions\n if (specification.securityDefinitions) {\n if (typeof specification.components !== 'object') {\n specification.components = {}\n }\n\n // Assert that components is of type OpenAPIV3.ComponentsObject\n specification.components = specification.components as OpenAPIV3.ComponentsObject\n\n Object.assign(specification.components, { securitySchemes: {} })\n\n for (const [key, securityScheme] of Object.entries(specification.securityDefinitions)) {\n if (typeof securityScheme === 'object') {\n if ('type' in securityScheme && securityScheme.type === 'oauth2') {\n const { flow, authorizationUrl, tokenUrl, scopes } = securityScheme as {\n type: 'oauth2'\n flow?: string\n authorizationUrl?: string\n tokenUrl?: string\n scopes?: Record<string, string>\n }\n\n // Assert that securitySchemes is of type OpenAPIV3.SecuritySchemeObject\n Object.assign((specification.components as OpenAPIV3.ComponentsObject).securitySchemes, {\n [key]: {\n type: 'oauth2',\n flows: {\n [flow as string]: Object.assign(\n {},\n authorizationUrl && { authorizationUrl },\n tokenUrl && { tokenUrl },\n scopes && { scopes },\n ),\n },\n },\n })\n } else if ('type' in securityScheme && securityScheme.type === 'basic') {\n Object.assign((specification.components as OpenAPIV3.ComponentsObject).securitySchemes, {\n [key]: {\n type: 'http',\n scheme: 'basic',\n },\n })\n } else {\n Object.assign((specification.components as OpenAPIV3.ComponentsObject).securitySchemes, {\n [key]: securityScheme,\n })\n }\n }\n }\n\n delete specification.securityDefinitions\n }\n\n return specification as OpenAPIV3.Document\n}\n\nfunction transformItemsObject<T extends Record<PropertyKey, unknown>>(obj: T): OpenAPIV3.SchemaObject {\n const schemaProperties = [\n 'type',\n 'format',\n 'items',\n 'maximum',\n 'exclusiveMaximum',\n 'minimum',\n 'exclusiveMinimum',\n 'maxLength',\n 'minLength',\n 'pattern',\n 'maxItems',\n 'minItems',\n 'uniqueItems',\n 'enum',\n 'multipleOf',\n ]\n\n return schemaProperties.reduce((acc, property) => {\n if (Object.hasOwn(obj, property)) {\n acc[property] = obj[property]\n delete obj[property]\n }\n\n return acc\n }, {} as OpenAPIV3.SchemaObject)\n}\n\nfunction transformParameterObject(parameter: OpenAPIV2.ParameterObject): OpenAPIV3.ParameterObject {\n // it is important to call getParameterSerializationStyle first because transformItemsObject modifies properties on which getParameterSerializationStyle rely on\n const serializationStyle = getParameterSerializationStyle(parameter)\n const schema = transformItemsObject(parameter)\n\n delete parameter.collectionFormat\n delete parameter.default\n\n return {\n schema,\n ...serializationStyle,\n ...parameter,\n }\n}\n\ntype CollectionFormat = 'csv' | 'ssv' | 'tsv' | 'pipes' | 'multi'\n\ntype ParameterSerializationStyle = { style?: string; explode?: boolean }\n\nconst querySerialization: Record<CollectionFormat, ParameterSerializationStyle> = {\n ssv: {\n style: 'spaceDelimited',\n explode: false,\n },\n pipes: {\n style: 'pipeDelimited',\n explode: false,\n },\n multi: {\n style: 'form',\n explode: true,\n },\n csv: {\n style: 'form',\n explode: false,\n },\n tsv: {},\n}\n\nconst pathAndHeaderSerialization: Record<CollectionFormat, ParameterSerializationStyle> = {\n ssv: {},\n pipes: {},\n multi: {},\n csv: {\n style: 'simple',\n explode: false,\n },\n tsv: {},\n}\n\nconst serializationStyles = {\n header: pathAndHeaderSerialization,\n query: querySerialization,\n path: pathAndHeaderSerialization,\n} as const\n\nfunction getParameterSerializationStyle(parameter: OpenAPIV2.ParameterObject): ParameterSerializationStyle {\n if (\n parameter.type !== 'array' ||\n !(parameter.in === 'query' || parameter.in === 'path' || parameter.in === 'header')\n ) {\n return {}\n }\n\n const collectionFormat = parameter.collectionFormat ?? 'csv'\n\n return serializationStyles[parameter.in][collectionFormat]\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -17,7 +17,7 @@
17
17
  "parser",
18
18
  "typescript"
19
19
  ],
20
- "version": "0.17.0",
20
+ "version": "0.18.1",
21
21
  "engines": {
22
22
  "node": ">=20"
23
23
  },
@@ -63,19 +63,19 @@
63
63
  "ajv-formats": "^3.0.1",
64
64
  "jsonpointer": "^5.0.1",
65
65
  "leven": "^4.0.0",
66
- "yaml": "^2.4.5"
66
+ "yaml": "2.8.0"
67
67
  },
68
68
  "devDependencies": {
69
69
  "@apidevtools/swagger-parser": "^10.1.0",
70
- "@types/node": "^20.17.10",
70
+ "@types/node": "^22.9.0",
71
71
  "fastify": "^5.3.3",
72
72
  "json-to-ast": "^2.1.0",
73
73
  "just-diff": "^6.0.2",
74
74
  "tinybench": "^2.8.0",
75
75
  "vite": "5.4.19",
76
- "@scalar/build-tooling": "0.2.3",
77
- "@scalar/types": "0.2.3",
78
- "@scalar/openapi-types": "0.3.3"
76
+ "@scalar/build-tooling": "0.2.4",
77
+ "@scalar/openapi-types": "0.3.5",
78
+ "@scalar/types": "0.2.7"
79
79
  },
80
80
  "scripts": {
81
81
  "build": "scalar-build-esbuild",