@scalar/mock-server 0.15.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/CHANGELOG.md +30 -0
  2. package/dist/create-asyncapi-mock-server.d.ts +2 -0
  3. package/dist/create-asyncapi-mock-server.d.ts.map +1 -1
  4. package/dist/create-asyncapi-mock-server.js +1 -1
  5. package/dist/create-mock-server.d.ts.map +1 -1
  6. package/dist/create-mock-server.js +25 -14
  7. package/dist/routes/mock-any-response.d.ts +1 -1
  8. package/dist/routes/mock-any-response.d.ts.map +1 -1
  9. package/dist/routes/mock-any-response.js +20 -5
  10. package/dist/routes/mock-handler-response.d.ts +1 -1
  11. package/dist/routes/mock-handler-response.d.ts.map +1 -1
  12. package/dist/routes/mock-handler-response.js +34 -7
  13. package/dist/types.d.ts +0 -2
  14. package/dist/types.d.ts.map +1 -1
  15. package/dist/utils/build-handler-context.d.ts +3 -2
  16. package/dist/utils/build-handler-context.d.ts.map +1 -1
  17. package/dist/utils/build-handler-context.js +14 -2
  18. package/dist/utils/get-operation.d.ts +4 -2
  19. package/dist/utils/get-operation.d.ts.map +1 -1
  20. package/dist/utils/get-operation.js +9 -5
  21. package/dist/utils/process-asyncapi-document.d.ts +2 -1
  22. package/dist/utils/process-asyncapi-document.d.ts.map +1 -1
  23. package/dist/utils/process-asyncapi-document.js +10 -2
  24. package/dist/utils/querystring-parameter.d.ts +11 -0
  25. package/dist/utils/querystring-parameter.d.ts.map +1 -0
  26. package/dist/utils/querystring-parameter.js +135 -0
  27. package/dist/utils/select-response-example.d.ts +7 -3
  28. package/dist/utils/select-response-example.d.ts.map +1 -1
  29. package/dist/utils/select-response-example.js +16 -6
  30. package/dist/utils/serialize-response-body.d.ts +1 -1
  31. package/dist/utils/serialize-response-body.d.ts.map +1 -1
  32. package/dist/utils/serialize-response-body.js +15 -11
  33. package/dist/utils/validate-request.d.ts.map +1 -1
  34. package/dist/utils/validate-request.js +29 -1
  35. package/package.json +7 -7
package/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # @scalar/mock-server
2
2
 
3
+ ## 0.17.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#10192](https://github.com/scalar/scalar/pull/10192): Mock-server XML response bytes now use the shared schema-aware serializer instead of `json2xml`, including attributes, namespaces, and root naming. Existing XML response snapshots may need updating. Supplied serialized XML remains unchanged.
8
+
9
+ Generate XML examples from schema metadata, preserving attributes, namespaces, array wrappers, repeated elements, and OpenAPI 3.2 text and CDATA nodes. Use the same XML serialization for request bodies, code snippets, response examples, mock responses, and Markdown documentation. Preserve serialized media examples and escape schema string examples as element text.
10
+
11
+ Explain XML generation failures in response example panels, including the serialized-example escape hatch for large payloads. Expose XML generation failures in mock response headers with `X-Scalar-XML-Error`, containing the first error diagnostic code. Report diagnostics to other consumers through a callback or the developer console, and format element-only descendants within mixed content without changing text values.
12
+
13
+ ### Patch Changes
14
+
15
+ - [#10343](https://github.com/scalar/scalar/pull/10343): Restrict AsyncAPI external references to the source directory and public network addresses, and preserve the source location for preloaded documents.
16
+
17
+ URL inputs to `createAsyncApiMockServer` also reject private network addresses, including localhost. Load local files or pass preloaded document content for local development. Docker documents supplied through `OPENAPI_DOCUMENT` resolve relative references from their temporary `/tmp/openapi.json` or `/tmp/openapi.yaml` file, confined to `/tmp`. Both OpenAPI and AsyncAPI Docker documents preserve the selected source origin.
18
+
19
+ - [#10192](https://github.com/scalar/scalar/pull/10192): Apply edited XML bodies instead of their original serialized or data examples, render schema-free XML examples in Markdown, and share reference decoding and response provenance selection.
20
+
21
+ ## 0.16.0
22
+
23
+ ### Minor Changes
24
+
25
+ - [#10189](https://github.com/scalar/scalar/pull/10189): Support OpenAPI 3.2 `in: querystring` parameters in request validation and custom handlers. Decode JSON, text, and form content from the entire query string, including inherited parameters and form property encoding.
26
+
27
+ Treat only null or undefined as absent validator schemas, preserving boolean `false` whole-query schemas that reject every value. Empty object schemas were already compiled and continue to accept unconstrained values.
28
+
29
+ - [#10186](https://github.com/scalar/scalar/pull/10186): Support additionalOperations with case-sensitive custom HTTP methods, existing operation middleware, and CORS preflight responses.
30
+
31
+ Preserve QUERY in the default CORS method list when adding methods declared by the API description.
32
+
3
33
  ## 0.15.0
4
34
 
5
35
  ### Minor Changes
@@ -9,6 +9,8 @@ export type AsyncApiMockServerOptions = {
9
9
  * string, or an already-parsed object.
10
10
  */
11
11
  document?: string | Record<string, any>;
12
+ /** Source file path or URL for resolving references in an already loaded document. */
13
+ origin?: string;
12
14
  /**
13
15
  * Additional transports appended after the built-in WebSocket and SSE transports. Use this to
14
16
  * support extra protocols (for example SignalR) without changing the core. The first transport
@@ -1 +1 @@
1
- {"version":3,"file":"create-asyncapi-mock-server.d.ts","sourceRoot":"","sources":["../src/create-asyncapi-mock-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,mBAAmB,EAAoB,MAAM,mBAAmB,CAAA;AAC9E,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAK3B,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAoB,MAAM,oBAAoB,CAAA;AAC3F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAM/C,oDAAoD;AACpD,MAAM,MAAM,yBAAyB,GAAG;IACtC;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAEvC;;;;OAIG;IACH,UAAU,CAAC,EAAE,aAAa,EAAE,CAAA;IAE5B,yFAAyF;IACzF,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,gBAAgB,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAA;IAE/F;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,GAAG,gBAAgB,CAAA;CACpC,CAAA;AAED,sDAAsD;AACtD,MAAM,MAAM,kBAAkB,GAAG;IAC/B,sEAAsE;IACtE,GAAG,EAAE,IAAI,CAAA;IACT,wFAAwF;IACxF,SAAS,EAAE;QAAE,MAAM,EAAE,mBAAmB,CAAA;KAAE,CAAA;CAC3C,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,wBAAwB,CAAC,OAAO,EAAE,yBAAyB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAqC9G"}
1
+ {"version":3,"file":"create-asyncapi-mock-server.d.ts","sourceRoot":"","sources":["../src/create-asyncapi-mock-server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,mBAAmB,EAAoB,MAAM,mBAAmB,CAAA;AAC9E,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAK3B,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAoB,MAAM,oBAAoB,CAAA;AAC3F,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAM/C,oDAAoD;AACpD,MAAM,MAAM,yBAAyB,GAAG;IACtC;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAEvC,sFAAsF;IACtF,MAAM,CAAC,EAAE,MAAM,CAAA;IAEf;;;;OAIG;IACH,UAAU,CAAC,EAAE,aAAa,EAAE,CAAA;IAE5B,yFAAyF;IACzF,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,gBAAgB,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,KAAK,IAAI,CAAA;IAE/F;;;;;OAKG;IACH,MAAM,CAAC,EAAE,OAAO,GAAG,gBAAgB,CAAA;CACpC,CAAA;AAED,sDAAsD;AACtD,MAAM,MAAM,kBAAkB,GAAG;IAC/B,sEAAsE;IACtE,GAAG,EAAE,IAAI,CAAA;IACT,wFAAwF;IACxF,SAAS,EAAE;QAAE,MAAM,EAAE,mBAAmB,CAAA;KAAE,CAAA;CAC3C,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,wBAAwB,CAAC,OAAO,EAAE,yBAAyB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAqC9G"}
@@ -22,7 +22,7 @@ import { resolveLogger } from './utils/resolve-logger.js';
22
22
  */
23
23
  export async function createAsyncApiMockServer(options) {
24
24
  const app = new Hono();
25
- const document = await processAsyncApiDocument(options.document);
25
+ const document = await processAsyncApiDocument(options.document, options.origin);
26
26
  const channels = resolveChannels(document);
27
27
  const transports = [...defaultTransports, ...(options.transports ?? [])];
28
28
  const log = resolveLogger(options.logger, false);
@@ -1 +1 @@
1
- {"version":3,"file":"create-mock-server.d.ts","sourceRoot":"","sources":["../src/create-mock-server.ts"],"names":[],"mappings":"AAIA,OAAO,EAAgB,IAAI,EAA0B,MAAM,MAAM,CAAA;AAIjE,OAAO,KAAK,EAAc,iBAAiB,EAAE,MAAM,SAAS,CAAA;AAwD5D;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,aAAa,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CA8NtF"}
1
+ {"version":3,"file":"create-mock-server.d.ts","sourceRoot":"","sources":["../src/create-mock-server.ts"],"names":[],"mappings":"AAGA,OAAO,EAAgB,IAAI,EAA0B,MAAM,MAAM,CAAA;AAIjE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAA;AAwDhD;;GAEG;AACH,wBAAsB,gBAAgB,CAAC,aAAa,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAwOtF"}
@@ -103,7 +103,13 @@ export async function createMockServer(configuration) {
103
103
  }
104
104
  }
105
105
  // CORS headers
106
- app.use(cors());
106
+ const allowedMethods = new Set(['GET', 'HEAD', 'PUT', 'POST', 'DELETE', 'PATCH', 'QUERY']);
107
+ for (const pathItem of Object.values(schema?.paths ?? {})) {
108
+ for (const method of Object.keys(getOperations(getResolvedRef(pathItem)))) {
109
+ allowedMethods.add(method);
110
+ }
111
+ }
112
+ app.use(cors({ origin: '*', allowMethods: [...allowedMethods], exposeHeaders: ['X-Scalar-XML-Error'] }));
107
113
  /** Authentication methods defined in the OpenAPI document */
108
114
  setUpAuthenticationRoutes(app, schema);
109
115
  // Only the instructions honor `logger` (on by default); the util still prints warnings and errors
@@ -136,19 +142,17 @@ export async function createMockServer(configuration) {
136
142
  orderedPathKeys.forEach(({ path, query }) => {
137
143
  // A path item may itself be a `$ref`, so resolve it before reading its operations.
138
144
  const pathItem = getResolvedRef(paths[path]);
139
- const methods = Object.keys(getOperations(pathItem));
145
+ const operations = getOperations(pathItem);
140
146
  /** Keys for all operations of a specified path */
141
- methods.forEach((method) => {
147
+ Object.entries(operations).forEach(([method, operation]) => {
142
148
  const route = honoRouteFromPath(path);
143
- const operation = pathItem?.[method];
144
149
  // Remember which operation this route mocks, so the error handler can name it when something
145
150
  // fails downstream. Recorded on the context rather than mapped back from the request path,
146
151
  // which would not survive the app being mounted under a base path. Registered before the rest
147
152
  // of the route so a failure in request validation is named too. The OpenAPI path key is kept
148
153
  // (rather than the Hono route) because that is what the document author reads.
149
154
  const mockedOperation = {
150
- // `toUpperCase` widens to `string`, so restate the narrower type the method union guarantees.
151
- method: method.toUpperCase(),
155
+ method,
152
156
  path,
153
157
  ...(operation?.operationId ? { operationId: operation.operationId } : {}),
154
158
  };
@@ -188,21 +192,28 @@ export async function createMockServer(configuration) {
188
192
  const hasHandler = handlerCode && typeof handlerCode === 'string' && handlerCode.trim().length > 0;
189
193
  // Route to appropriate handler
190
194
  if (hasHandler) {
191
- handlers.push(async (c) => await mockHandlerResponse(c, operation));
195
+ handlers.push(async (c) => await mockHandlerResponse(c, operation, pathItem?.parameters, schema.openapi));
192
196
  }
193
197
  else {
194
- handlers.push(async (c) => await mockAnyResponse(c, operation));
195
- }
196
- if (query.length === 0) {
197
- handlers.forEach((handler) => app[method](route, handler));
198
- return;
198
+ handlers.push(async (c) => await mockAnyResponse(c, operation, schema.openapi));
199
199
  }
200
200
  // The pinned query parameters are not part of the route, so they are checked here. A request
201
201
  // that does not carry them is handed on to the next matching route — usually the sibling path
202
202
  // key without the query string.
203
203
  const operationChain = every(...handlers);
204
- app[method](route, async (c, next) => {
205
- if (!requestMatchesPinnedQuery(c, query)) {
204
+ // Hono uppercases methods during registration. Match the original method ourselves so
205
+ // additional operations such as COPY and copy remain distinct.
206
+ const register = (handler) => {
207
+ if (method === method.toUpperCase()) {
208
+ app.on(method, route, handler);
209
+ }
210
+ else {
211
+ app.all(route, handler);
212
+ }
213
+ };
214
+ register(async (c, next) => {
215
+ if ((c.req.method !== method && !(method === 'GET' && c.req.method === 'HEAD')) ||
216
+ !requestMatchesPinnedQuery(c, query)) {
206
217
  await next();
207
218
  return;
208
219
  }
@@ -3,5 +3,5 @@ import type { Context } from 'hono';
3
3
  /**
4
4
  * Mock any response
5
5
  */
6
- export declare function mockAnyResponse(c: Context, operation: OpenAPIV3_1.OperationObject): Response;
6
+ export declare function mockAnyResponse(c: Context, operation: OpenAPIV3_1.OperationObject, openapiVersion?: string): Response;
7
7
  //# sourceMappingURL=mock-any-response.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mock-any-response.d.ts","sourceRoot":"","sources":["../../src/routes/mock-any-response.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAIxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAenC;;GAEG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,CAAC,eAAe,YA6IjF"}
1
+ {"version":3,"file":"mock-any-response.d.ts","sourceRoot":"","sources":["../../src/routes/mock-any-response.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAKxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAenC;;GAEG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,CAAC,eAAe,EAAE,cAAc,CAAC,EAAE,MAAM,YA4J1G"}
@@ -1,6 +1,7 @@
1
+ import { isXmlMediaType } from '@scalar/helpers/http/is-xml-media-type';
1
2
  import { getResolvedRef } from '@scalar/workspace-store/helpers/get-resolved-ref';
2
3
  import { getResolvedRefDeep } from '@scalar/workspace-store/helpers/get-resolved-ref-deep';
3
- import { getExampleFromSchema } from '@scalar/workspace-store/request-example';
4
+ import { getExampleFromSchema, getXmlBodyExample } from '@scalar/workspace-store/request-example';
4
5
  import { streamSSE } from 'hono/streaming';
5
6
  import { collectSseEvents, isEventStreamContentType } from '../utils/collect-sse-events.js';
6
7
  import { findPreferredResponseKey } from '../utils/find-preferred-response-key.js';
@@ -15,7 +16,7 @@ import { getStreamingResponse, sendStreamingResponse } from '../utils/streaming-
15
16
  /**
16
17
  * Mock any response
17
18
  */
18
- export function mockAnyResponse(c, operation) {
19
+ export function mockAnyResponse(c, operation, openapiVersion) {
19
20
  // Note: the `onRequest` callback runs as middleware (see `create-mock-server`) so it also fires
20
21
  // for requests rejected before reaching this handler.
21
22
  // Parse the Prefer header (RFC 7240) so clients can request a specific
@@ -102,9 +103,24 @@ export function mockAnyResponse(c, operation) {
102
103
  // Body: a named/singular/first example if one is defined, otherwise generate
103
104
  // a value from the schema. `Prefer: example=<name>` picks a named example.
104
105
  const selectedExample = selectResponseExample(acceptedResponse, prefer.example);
106
+ c.status(statusCode);
107
+ if (isXmlMediaType(acceptedContentType)) {
108
+ const result = getXmlBodyExample(acceptedResponse?.schema, selectedExample, {
109
+ openapiVersion,
110
+ emptyString: 'string',
111
+ variables: pathParameters(c),
112
+ mode: 'read',
113
+ });
114
+ const error = result.diagnostics.find((diagnostic) => diagnostic.severity === 'error');
115
+ if (error) {
116
+ c.header('X-Scalar-XML-Error', error.code);
117
+ }
118
+ return result.xml === undefined ? c.body(null) : c.body(result.xml);
119
+ }
120
+ const provenance = selectedExample?.provenance;
105
121
  const body = (() => {
106
122
  if (selectedExample) {
107
- return normalizeResponseBody(selectedExample.value, responseSchema);
123
+ return provenance ? selectedExample.value : normalizeResponseBody(selectedExample.value, responseSchema);
108
124
  }
109
125
  if (!responseSchema) {
110
126
  return null;
@@ -119,8 +135,7 @@ export function mockAnyResponse(c, operation) {
119
135
  // Re-inferring it from sibling items would wrap a selected primitive in an array.
120
136
  return generated;
121
137
  })();
122
- c.status(statusCode);
123
- const serializedBody = serializeResponseBody(body, acceptedContentType, responseSchema);
138
+ const serializedBody = serializeResponseBody(body, acceptedContentType, responseSchema, provenance);
124
139
  // `JSON.stringify` returns `undefined` for an `undefined` body, which is an empty response.
125
140
  if (serializedBody === undefined) {
126
141
  return c.body(null);
@@ -4,5 +4,5 @@ import type { Context } from 'hono';
4
4
  * Mock response using x-handler code.
5
5
  * Executes the handler and returns its result as the response.
6
6
  */
7
- export declare function mockHandlerResponse(c: Context, operation: OpenAPIV3_1.OperationObject): Promise<Response>;
7
+ export declare function mockHandlerResponse(c: Context, operation: OpenAPIV3_1.OperationObject, pathItemParameters?: OpenAPIV3_1.PathItemObject['parameters'], openapiVersion?: string): Promise<Response>;
8
8
  //# sourceMappingURL=mock-handler-response.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mock-handler-response.d.ts","sourceRoot":"","sources":["../../src/routes/mock-handler-response.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAExD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AA4HnC;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,CAAC,eAAe,qBAoE3F"}
1
+ {"version":3,"file":"mock-handler-response.d.ts","sourceRoot":"","sources":["../../src/routes/mock-handler-response.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAIxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAoJnC;;;GAGG;AACH,wBAAsB,mBAAmB,CACvC,CAAC,EAAE,OAAO,EACV,SAAS,EAAE,WAAW,CAAC,eAAe,EACtC,kBAAkB,CAAC,EAAE,WAAW,CAAC,cAAc,CAAC,YAAY,CAAC,EAC7D,cAAc,CAAC,EAAE,MAAM,qBA8ExB"}
@@ -1,4 +1,6 @@
1
+ import { isXmlMediaType } from '@scalar/helpers/http/is-xml-media-type';
1
2
  import { getResolvedRef } from '@scalar/workspace-store/helpers/get-resolved-ref';
3
+ import { getXmlBodyExample } from '@scalar/workspace-store/request-example';
2
4
  import { buildHandlerContext } from '../utils/build-handler-context.js';
3
5
  import { executeHandler } from '../utils/execute-handler.js';
4
6
  import { generateResponseExample } from '../utils/generate-response-example.js';
@@ -7,16 +9,17 @@ import { normalizeResponseBody } from '../utils/normalize-response-body.js';
7
9
  import { parsePreferHeader } from '../utils/parse-prefer-header.js';
8
10
  import { pathParameters } from '../utils/path-parameters.js';
9
11
  import { selectResponseExample } from '../utils/select-response-example.js';
12
+ import { serializeResponseBody } from '../utils/serialize-response-body.js';
10
13
  import { getStreamingResponse, sendStreamingResponse } from '../utils/streaming-response.js';
11
14
  /**
12
15
  * Get example response from OpenAPI spec for a given status code.
13
- * Returns the example value if found, or null if not available.
16
+ * Returns a serialized payload if found, or null if not available.
14
17
  *
15
18
  * Honors `Prefer: example=<name>` to pick a named example from the
16
19
  * `examples` map; otherwise it falls back to the singular `example`, the
17
20
  * first entry of the map, or a value generated from the schema.
18
21
  */
19
- function getExampleFromResponse(c, statusCode, responses, exampleName) {
22
+ function getExampleFromResponse(c, statusCode, responses, exampleName, openapiVersion) {
20
23
  if (!responses) {
21
24
  return null;
22
25
  }
@@ -39,11 +42,32 @@ function getExampleFromResponse(c, statusCode, responses, exampleName) {
39
42
  const responseSchema = acceptedResponse.schema ? getResolvedRef(acceptedResponse.schema) : undefined;
40
43
  // Extract example (named, singular, or first) or generate from schema
41
44
  const selectedExample = selectResponseExample(acceptedResponse, exampleName);
42
- return selectedExample
45
+ if (isXmlMediaType(acceptedContentType)) {
46
+ c.header('Content-Type', acceptedContentType);
47
+ const result = getXmlBodyExample(acceptedResponse.schema, selectedExample, {
48
+ openapiVersion,
49
+ emptyString: 'string',
50
+ variables: pathParameters(c),
51
+ mode: 'read',
52
+ });
53
+ const error = result.diagnostics.find((diagnostic) => diagnostic.severity === 'error');
54
+ if (error) {
55
+ c.header('X-Scalar-XML-Error', error.code);
56
+ }
57
+ return result.xml ?? null;
58
+ }
59
+ const provenance = selectedExample?.provenance;
60
+ if (selectedExample && provenance) {
61
+ c.header('Content-Type', acceptedContentType);
62
+ return serializeResponseBody(selectedExample.value, acceptedContentType, responseSchema, provenance) ?? null;
63
+ }
64
+ const value = selectedExample
43
65
  ? normalizeResponseBody(selectedExample.value, responseSchema)
44
66
  : responseSchema
45
67
  ? normalizeResponseBody(generateResponseExample(responseSchema, pathParameters(c)), responseSchema)
46
68
  : null;
69
+ // Legacy examples retain the handler fallback's JSON encoding policy.
70
+ return JSON.stringify(value) ?? null;
47
71
  }
48
72
  /**
49
73
  * Determine HTTP status code based on store operation tracking.
@@ -98,7 +122,7 @@ function determineStatusCode(tracking) {
98
122
  * Mock response using x-handler code.
99
123
  * Executes the handler and returns its result as the response.
100
124
  */
101
- export async function mockHandlerResponse(c, operation) {
125
+ export async function mockHandlerResponse(c, operation, pathItemParameters, openapiVersion) {
102
126
  // Note: the `onRequest` callback runs as middleware (see `create-mock-server`) so it also fires
103
127
  // for requests rejected before reaching this handler.
104
128
  // Get x-handler code from operation
@@ -109,7 +133,7 @@ export async function mockHandlerResponse(c, operation) {
109
133
  }
110
134
  try {
111
135
  // Build handler context with tracking
112
- const { context, tracking } = await buildHandlerContext(c, operation);
136
+ const { context, tracking } = await buildHandlerContext(c, operation, pathItemParameters);
113
137
  // Execute handler
114
138
  const { result } = await executeHandler(handlerCode, context);
115
139
  // Determine status code based on all store operations, prioritizing semantically meaningful ones
@@ -137,9 +161,12 @@ export async function mockHandlerResponse(c, operation) {
137
161
  if (result === undefined || result === null) {
138
162
  // Try to pick up example response from OpenAPI spec if available
139
163
  const prefer = parsePreferHeader(c.req.header('Prefer'));
140
- const exampleResponse = getExampleFromResponse(c, statusCode, operation.responses, prefer.example);
164
+ const exampleResponse = getExampleFromResponse(c, statusCode, operation.responses, prefer.example, openapiVersion);
141
165
  if (exampleResponse !== null) {
142
- return c.json(exampleResponse);
166
+ return c.body(exampleResponse);
167
+ }
168
+ if (isXmlMediaType(c.res.headers.get('Content-Type') ?? undefined)) {
169
+ return c.body(null);
143
170
  }
144
171
  return c.json(null);
145
172
  }
package/dist/types.d.ts CHANGED
@@ -2,8 +2,6 @@ import type { OpenAPIV3_1 } from '@scalar/openapi-types';
2
2
  import type { Context } from 'hono';
3
3
  /** Available HTTP methods for Hono routes */
4
4
  export declare const httpMethods: readonly ["get", "put", "post", "delete", "options", "patch"];
5
- /** Valid HTTP method */
6
- export type HttpMethod = (typeof httpMethods)[number];
7
5
  /**
8
6
  * Represents a partial object where at least one of the given properties is required.
9
7
  */
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAEnC,6CAA6C;AAC7C,eAAO,MAAM,WAAW,+DAAgE,CAAA;AAExF,wBAAwB;AACxB,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAA;AAErD;;GAEG;AACH,KAAK,iBAAiB,CAAC,CAAC,EAAE,IAAI,SAAS,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,IAAI,CAAC,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC,GACzF;KACG,CAAC,IAAI,IAAI,CAAC,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;CACzE,CAAC,IAAI,CAAC,CAAA;AAET,uFAAuF;AACvF,MAAM,MAAM,gBAAgB,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;AAErD,KAAK,qBAAqB,GAAG;IAC3B,iGAAiG;IACjG,MAAM,CAAC,EAAE,MAAM,CAAA;IAEf;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAE5C;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAEvC;;OAEG;IACH,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,WAAW,CAAC,eAAe,CAAA;KAAE,KAAK,IAAI,CAAA;IAExF;;;;;;;;;;;OAWG;IACH,eAAe,CAAC,EAAE,OAAO,CAAA;IAEzB;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,EAAE,OAAO,GAAG,gBAAgB,CAAA;CACpC,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG,iBAAiB,CAAC,qBAAqB,EAAE,eAAe,GAAG,UAAU,CAAC,CAAA"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AACxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAEnC,6CAA6C;AAC7C,eAAO,MAAM,WAAW,+DAAgE,CAAA;AAExF;;GAEG;AACH,KAAK,iBAAiB,CAAC,CAAC,EAAE,IAAI,SAAS,MAAM,CAAC,GAAG,MAAM,CAAC,IAAI,IAAI,CAAC,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC,GACzF;KACG,CAAC,IAAI,IAAI,CAAC,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;CACzE,CAAC,IAAI,CAAC,CAAA;AAET,uFAAuF;AACvF,MAAM,MAAM,gBAAgB,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAA;AAErD,KAAK,qBAAqB,GAAG;IAC3B,iGAAiG;IACjG,MAAM,CAAC,EAAE,MAAM,CAAA;IAEf;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAE5C;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAEvC;;OAEG;IACH,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,WAAW,CAAC,eAAe,CAAA;KAAE,KAAK,IAAI,CAAA;IAExF;;;;;;;;;;;OAWG;IACH,eAAe,CAAC,EAAE,OAAO,CAAA;IAEzB;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,EAAE,OAAO,GAAG,gBAAgB,CAAA;CACpC,CAAA;AAED,MAAM,MAAM,iBAAiB,GAAG,iBAAiB,CAAC,qBAAqB,EAAE,eAAe,GAAG,UAAU,CAAC,CAAA"}
@@ -9,7 +9,8 @@ export type HandlerContext = {
9
9
  req: {
10
10
  body: any;
11
11
  params: Record<string, string>;
12
- query: Record<string, string>;
12
+ /** Named query values, or the decoded content of an `in: querystring` parameter. */
13
+ query: unknown;
13
14
  headers: Record<string, string>;
14
15
  };
15
16
  res: Record<string, any>;
@@ -24,6 +25,6 @@ type HandlerContextResult = {
24
25
  /**
25
26
  * Build the handler context from a Hono context.
26
27
  */
27
- export declare function buildHandlerContext(c: Context, operation?: OpenAPIV3_1.OperationObject): Promise<HandlerContextResult>;
28
+ export declare function buildHandlerContext(c: Context, operation?: OpenAPIV3_1.OperationObject, pathItemParameters?: OpenAPIV3_1.PathItemObject['parameters']): Promise<HandlerContextResult>;
28
29
  export {};
29
30
  //# sourceMappingURL=build-handler-context.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"build-handler-context.d.ts","sourceRoot":"","sources":["../../src/utils/build-handler-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAGxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAQnC,OAAO,EAAE,KAAK,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAGjF;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,KAAK,EAAE,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC,cAAc,CAAC,CAAA;IAC5D,GAAG,EAAE;QACH,IAAI,EAAE,GAAG,CAAA;QACT,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;QAC9B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;QAC7B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAChC,CAAA;IACD,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;CACzB,CAAA;AAED;;GAEG;AACH,KAAK,oBAAoB,GAAG;IAC1B,OAAO,EAAE,cAAc,CAAA;IACvB,QAAQ,EAAE,sBAAsB,CAAA;CACjC,CAAA;AA6DD;;GAEG;AACH,wBAAsB,mBAAmB,CACvC,CAAC,EAAE,OAAO,EACV,SAAS,CAAC,EAAE,WAAW,CAAC,eAAe,GACtC,OAAO,CAAC,oBAAoB,CAAC,CA4C/B"}
1
+ {"version":3,"file":"build-handler-context.d.ts","sourceRoot":"","sources":["../../src/utils/build-handler-context.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAGxD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AASnC,OAAO,EAAE,KAAK,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAGjF;;GAEG;AACH,MAAM,MAAM,cAAc,GAAG;IAC3B,KAAK,EAAE,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC,cAAc,CAAC,CAAA;IAC5D,GAAG,EAAE;QACH,IAAI,EAAE,GAAG,CAAA;QACT,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;QAC9B,oFAAoF;QACpF,KAAK,EAAE,OAAO,CAAA;QACd,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAChC,CAAA;IACD,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;CACzB,CAAA;AAED;;GAEG;AACH,KAAK,oBAAoB,GAAG;IAC1B,OAAO,EAAE,cAAc,CAAA;IACvB,QAAQ,EAAE,sBAAsB,CAAA;CACjC,CAAA;AA6DD;;GAEG;AACH,wBAAsB,mBAAmB,CACvC,CAAC,EAAE,OAAO,EACV,SAAS,CAAC,EAAE,WAAW,CAAC,eAAe,EACvC,kBAAkB,CAAC,EAAE,WAAW,CAAC,cAAc,CAAC,YAAY,CAAC,GAC5D,OAAO,CAAC,oBAAoB,CAAC,CAuD/B"}
@@ -6,6 +6,7 @@ import { generateResponseExample } from './generate-response-example.js';
6
6
  import { normalizeResponseBody } from './normalize-response-body.js';
7
7
  import { parsePreferHeader } from './parse-prefer-header.js';
8
8
  import { pathParameters } from './path-parameters.js';
9
+ import { findQuerystringParameter, parseQuerystringParameter } from './querystring-parameter.js';
9
10
  import { createStoreWrapper } from './store-wrapper.js';
10
11
  import { getStreamingResponse } from './streaming-response.js';
11
12
  /**
@@ -55,7 +56,7 @@ function getExampleFromResponse(c, statusCode, responses) {
55
56
  /**
56
57
  * Build the handler context from a Hono context.
57
58
  */
58
- export async function buildHandlerContext(c, operation) {
59
+ export async function buildHandlerContext(c, operation, pathItemParameters) {
59
60
  let body = undefined;
60
61
  try {
61
62
  // Compare case-insensitively, since media types are case-insensitive and request validation
@@ -77,6 +78,17 @@ export async function buildHandlerContext(c, operation) {
77
78
  catch {
78
79
  // Ignore parsing errors, body remains undefined
79
80
  }
81
+ const parameter = findQuerystringParameter(operation, pathItemParameters);
82
+ let query = Object.fromEntries(new URL(c.req.url).searchParams.entries());
83
+ if (parameter) {
84
+ try {
85
+ query = parseQuerystringParameter(c.req.url, parameter);
86
+ }
87
+ catch {
88
+ // With validation disabled, malformed content remains unavailable just like an invalid body.
89
+ query = undefined;
90
+ }
91
+ }
80
92
  const { wrappedStore, tracking } = createStoreWrapper(store);
81
93
  // Build res object with examples for all response status codes
82
94
  const res = {};
@@ -91,7 +103,7 @@ export async function buildHandlerContext(c, operation) {
91
103
  req: {
92
104
  body,
93
105
  params: pathParameters(c),
94
- query: Object.fromEntries(new URL(c.req.url).searchParams.entries()),
106
+ query,
95
107
  headers: Object.fromEntries(Object.entries(c.req.header()).map(([key, value]) => [key, value ?? ''])),
96
108
  },
97
109
  res,
@@ -1,8 +1,10 @@
1
1
  import type { OpenAPIV3_1 } from '@scalar/openapi-types';
2
- import { type HttpMethod } from '../types.js';
3
2
  /**
4
3
  * Takes a dereferenced OpenAPI document and returns all operations.
4
+ * Keys use their wire capitalization: fixed methods are uppercase, additional methods retain their case.
5
5
  * Ignores other attributes, like summary, parameters, etc.
6
6
  */
7
- export declare function getOperations(path?: OpenAPIV3_1.PathItemObject): Record<HttpMethod, OpenAPIV3_1.OperationObject>;
7
+ export declare const getOperations: (path?: OpenAPIV3_1.PathItemObject & {
8
+ additionalOperations?: Record<string, OpenAPIV3_1.OperationObject>;
9
+ }) => Record<string, OpenAPIV3_1.OperationObject>;
8
10
  //# sourceMappingURL=get-operation.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"get-operation.d.ts","sourceRoot":"","sources":["../../src/utils/get-operation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAExD,OAAO,EAAE,KAAK,UAAU,EAAe,MAAM,SAAS,CAAA;AAEtD;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,CAAC,EAAE,WAAW,CAAC,cAAc,GAAG,MAAM,CAAC,UAAU,EAAE,WAAW,CAAC,eAAe,CAAC,CAUhH"}
1
+ {"version":3,"file":"get-operation.d.ts","sourceRoot":"","sources":["../../src/utils/get-operation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAIxD;;;;GAIG;AACH,eAAO,MAAM,aAAa,GACxB,OAAO,WAAW,CAAC,cAAc,GAAG;IAClC,oBAAoB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,eAAe,CAAC,CAAA;CACnE,KACA,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,eAAe,CAc5C,CAAA"}
@@ -1,14 +1,18 @@
1
1
  import { httpMethods } from '../types.js';
2
2
  /**
3
3
  * Takes a dereferenced OpenAPI document and returns all operations.
4
+ * Keys use their wire capitalization: fixed methods are uppercase, additional methods retain their case.
4
5
  * Ignores other attributes, like summary, parameters, etc.
5
6
  */
6
- export function getOperations(path) {
7
- const operations = {};
7
+ export const getOperations = (path) => {
8
+ const operations = new Map();
8
9
  for (const method of httpMethods) {
9
10
  if (path?.[method]) {
10
- operations[method] = path?.[method];
11
+ operations.set(method.toUpperCase(), path[method]);
11
12
  }
12
13
  }
13
- return operations;
14
- }
14
+ for (const [method, operation] of Object.entries(path?.additionalOperations ?? {})) {
15
+ operations.set(method, operation);
16
+ }
17
+ return Object.fromEntries(operations);
18
+ };
@@ -11,10 +11,11 @@ import type { AsyncApiDocument } from '@scalar/types/asyncapi/3.1';
11
11
  * Only AsyncAPI 3.1 is supported; 2.x documents should be upgraded before being passed in.
12
12
  *
13
13
  * @param document - The AsyncAPI document to process. Can be a string (URL/path) or an object.
14
+ * @param origin - Source file path or URL for resolving references in an already loaded document.
14
15
  * @returns A promise that resolves to the AsyncAPI document with lazily resolvable references.
15
16
  * @throws Error if the document cannot be processed or is invalid.
16
17
  */
17
- export declare function processAsyncApiDocument(document: string | Record<string, any> | undefined): Promise<AsyncApiDocument>;
18
+ export declare function processAsyncApiDocument(document: string | Record<string, any> | undefined, origin?: string): Promise<AsyncApiDocument>;
18
19
  /**
19
20
  * Detects whether a loaded document describes an AsyncAPI API (rather than OpenAPI/Swagger).
20
21
  * Used to route an incoming document to the right mock engine.
@@ -1 +1 @@
1
- {"version":3,"file":"process-asyncapi-document.d.ts","sourceRoot":"","sources":["../../src/utils/process-asyncapi-document.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAElE;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,GACjD,OAAO,CAAC,gBAAgB,CAAC,CAiC3B;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,OAAO,GAAG,OAAO,CAE7D"}
1
+ {"version":3,"file":"process-asyncapi-document.d.ts","sourceRoot":"","sources":["../../src/utils/process-asyncapi-document.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAA;AAElE;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,SAAS,EAClD,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,gBAAgB,CAAC,CAsC3B;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,OAAO,GAAG,OAAO,CAE7D"}
@@ -1,5 +1,8 @@
1
+ import path from 'node:path';
2
+ import { cwd } from 'node:process';
1
3
  import { bundle } from '@scalar/json-magic/bundle';
2
4
  import { fetchUrls, parseJson, parseYaml, readFiles } from '@scalar/json-magic/bundle/plugins/node';
5
+ import { isFilePath } from '@scalar/json-magic/helpers/is-file-path';
3
6
  import { createMagicProxy } from '@scalar/json-magic/magic-proxy';
4
7
  /**
5
8
  * Processes an AsyncAPI document by bundling external references and wrapping it so internal
@@ -13,10 +16,11 @@ import { createMagicProxy } from '@scalar/json-magic/magic-proxy';
13
16
  * Only AsyncAPI 3.1 is supported; 2.x documents should be upgraded before being passed in.
14
17
  *
15
18
  * @param document - The AsyncAPI document to process. Can be a string (URL/path) or an object.
19
+ * @param origin - Source file path or URL for resolving references in an already loaded document.
16
20
  * @returns A promise that resolves to the AsyncAPI document with lazily resolvable references.
17
21
  * @throws Error if the document cannot be processed or is invalid.
18
22
  */
19
- export async function processAsyncApiDocument(document) {
23
+ export async function processAsyncApiDocument(document, origin) {
20
24
  // Handle empty/undefined input gracefully with a minimal valid document.
21
25
  if (!document || (typeof document === 'object' && Object.keys(document).length === 0)) {
22
26
  return {
@@ -30,10 +34,14 @@ export async function processAsyncApiDocument(document) {
30
34
  };
31
35
  }
32
36
  let bundled;
37
+ // Keep references inside the source directory and prevent access to internal services.
38
+ const source = origin ?? document;
39
+ const basePath = typeof source === 'string' && isFilePath(source) ? path.dirname(path.resolve(source)) : cwd();
33
40
  try {
34
41
  // Bundle external references; parse string inputs (JSON or YAML) along the way.
35
42
  bundled = await bundle(document, {
36
- plugins: [parseJson(), parseYaml(), readFiles(), fetchUrls()],
43
+ origin,
44
+ plugins: [parseJson(), parseYaml(), readFiles({ basePath }), fetchUrls({ blockPrivateNetworks: true })],
37
45
  treeShake: false,
38
46
  });
39
47
  }
@@ -0,0 +1,11 @@
1
+ import type { OpenAPIV3_2 } from '@scalar/openapi-types';
2
+ /**
3
+ * Find the whole-query parameter, giving operation declarations precedence over path declarations.
4
+ * This module decodes incoming queries; workspace-store/src/helpers/querystring-parameter.ts serializes outgoing ones.
5
+ */
6
+ export declare const findQuerystringParameter: (operation?: OpenAPIV3_2.OperationObject, pathParameters?: OpenAPIV3_2.PathItemObject["parameters"]) => OpenAPIV3_2.ParameterObject | undefined;
7
+ /** Validate JSON-encoded form properties before the coercing form validator can alter their native types. */
8
+ export declare const getQuerystringJsonSchema: (parameter: OpenAPIV3_2.ParameterObject | undefined) => Record<string, unknown> | null;
9
+ /** Decode the complete query without introducing the parameter's documentary name. Throws on malformed JSON or URI escaping. */
10
+ export declare const parseQuerystringParameter: (url: string, parameter: OpenAPIV3_2.ParameterObject) => unknown;
11
+ //# sourceMappingURL=querystring-parameter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"querystring-parameter.d.ts","sourceRoot":"","sources":["../../src/utils/querystring-parameter.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAaxD;;;GAGG;AACH,eAAO,MAAM,wBAAwB,GACnC,YAAY,WAAW,CAAC,eAAe,EACvC,iBAAiB,WAAW,CAAC,cAAc,CAAC,YAAY,CAAC,KACxD,WAAW,CAAC,eAAe,GAAG,SAMwB,CAAA;AAkBzD,6GAA6G;AAC7G,eAAO,MAAM,wBAAwB,GACnC,WAAW,WAAW,CAAC,eAAe,GAAG,SAAS,KACjD,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAkC5B,CAAA;AAED,gIAAgI;AAChI,eAAO,MAAM,yBAAyB,GAAI,KAAK,MAAM,EAAE,WAAW,WAAW,CAAC,eAAe,KAAG,OA0E/F,CAAA"}
@@ -0,0 +1,135 @@
1
+ import { getFirstMediaType } from '@scalar/helpers/http/get-first-media-type';
2
+ import { isJsonMediaType } from '@scalar/helpers/http/is-json-media-type';
3
+ import { parseMimeType } from '@scalar/helpers/http/mime-type';
4
+ import { getResolvedRef } from '@scalar/workspace-store/helpers/get-resolved-ref';
5
+ import { getResolvedRefDeep } from '@scalar/workspace-store/helpers/get-resolved-ref-deep';
6
+ import { deserializeArrayParameter, deserializeObjectParameter, getObjectPropertyNames, isArraySchema, isObjectSchema, resolveSerialization, } from './deserialize-parameter.js';
7
+ /**
8
+ * Find the whole-query parameter, giving operation declarations precedence over path declarations.
9
+ * This module decodes incoming queries; workspace-store/src/helpers/querystring-parameter.ts serializes outgoing ones.
10
+ */
11
+ export const findQuerystringParameter = (operation, pathParameters) => [
12
+ ...(Array.isArray(operation?.parameters) ? operation.parameters : []),
13
+ ...(Array.isArray(pathParameters) ? pathParameters : []),
14
+ ]
15
+ .map((parameter) => getResolvedRef(parameter))
16
+ .find((parameter) => parameter?.in === 'querystring');
17
+ /** Collect form properties through compositions so nullable and composed objects retain their encoding. */
18
+ const getPropertySchemas = (schema) => {
19
+ const properties = {};
20
+ for (const keyword of ['allOf', 'anyOf', 'oneOf']) {
21
+ const branches = schema?.[keyword];
22
+ if (Array.isArray(branches)) {
23
+ for (const branch of branches) {
24
+ if (branch && typeof branch === 'object') {
25
+ Object.assign(properties, getPropertySchemas(branch));
26
+ }
27
+ }
28
+ }
29
+ }
30
+ return { ...properties, ...schema?.properties };
31
+ };
32
+ /** Validate JSON-encoded form properties before the coercing form validator can alter their native types. */
33
+ export const getQuerystringJsonSchema = (parameter) => {
34
+ const [contentType, media] = getFirstMediaType(parameter?.content) ?? [];
35
+ if (parseMimeType(contentType).essence !== 'application/x-www-form-urlencoded') {
36
+ return null;
37
+ }
38
+ const selectJsonProperties = (schema) => {
39
+ const properties = (schema?.properties ?? {});
40
+ const jsonProperties = Object.fromEntries(Object.entries(properties).filter(([name, property]) => {
41
+ const encoding = media?.encoding?.[name];
42
+ if (encoding?.style !== undefined || encoding?.explode !== undefined || encoding?.allowReserved !== undefined) {
43
+ return false;
44
+ }
45
+ const item = isArraySchema(property) ? property.items : property;
46
+ return isJsonMediaType(encoding?.contentType) || isObjectSchema(item) || isArraySchema(item);
47
+ }));
48
+ const result = { properties: jsonProperties };
49
+ for (const keyword of ['allOf', 'anyOf', 'oneOf']) {
50
+ const branches = schema?.[keyword];
51
+ if (Array.isArray(branches)) {
52
+ // Non-JSON fields may distinguish oneOf branches. The complete validator enforces exclusivity.
53
+ const target = keyword === 'oneOf' ? 'anyOf' : keyword;
54
+ const selected = branches.map((branch) => selectJsonProperties(branch));
55
+ if (target in result) {
56
+ result.allOf = [...(Array.isArray(result.allOf) ? result.allOf : []), { [target]: selected }];
57
+ }
58
+ else {
59
+ result[target] = selected;
60
+ }
61
+ }
62
+ }
63
+ return result;
64
+ };
65
+ return selectJsonProperties(getResolvedRefDeep(media?.schema));
66
+ };
67
+ /** Decode the complete query without introducing the parameter's documentary name. Throws on malformed JSON or URI escaping. */
68
+ export const parseQuerystringParameter = (url, parameter) => {
69
+ const query = new URL(url).search.slice(1);
70
+ if (!query) {
71
+ return undefined;
72
+ }
73
+ const [contentType, media] = getFirstMediaType(parameter.content) ?? [];
74
+ const mediaType = parseMimeType(contentType).essence;
75
+ if (mediaType !== 'application/x-www-form-urlencoded') {
76
+ const decoded = decodeURIComponent(query);
77
+ return isJsonMediaType(mediaType) ? JSON.parse(decoded) : decoded;
78
+ }
79
+ const params = new URLSearchParams(query);
80
+ const map = Object.fromEntries([...new Set(params.keys())].map((key) => {
81
+ const values = params.getAll(key);
82
+ return [key, values.length === 1 ? (values[0] ?? '') : values];
83
+ }));
84
+ const result = { ...map };
85
+ const schema = getResolvedRefDeep(media?.schema);
86
+ const properties = getPropertySchemas(schema);
87
+ for (const name of getObjectPropertyNames(schema)) {
88
+ const property = properties?.[name];
89
+ const encoding = media?.encoding?.[name];
90
+ const single = params.get(name) ?? undefined;
91
+ const styleBased = encoding?.style !== undefined || encoding?.explode !== undefined || encoding?.allowReserved !== undefined;
92
+ if (styleBased) {
93
+ const { style, explode } = resolveSerialization('query', encoding?.style, encoding?.explode);
94
+ const value = isObjectSchema(property)
95
+ ? deserializeObjectParameter({
96
+ style,
97
+ explode,
98
+ single,
99
+ map,
100
+ name,
101
+ propertyNames: getObjectPropertyNames(property),
102
+ reservedKeys: new Set(Object.keys(properties)),
103
+ })
104
+ : isArraySchema(property)
105
+ ? deserializeArrayParameter({
106
+ style,
107
+ explode,
108
+ single,
109
+ multi: params.has(name) ? params.getAll(name) : undefined,
110
+ })
111
+ : single;
112
+ if (value !== undefined) {
113
+ if (isObjectSchema(property) &&
114
+ (style === 'deepObject' || (style === 'form' && explode)) &&
115
+ typeof value === 'object' &&
116
+ value !== null) {
117
+ for (const key of Object.keys(value)) {
118
+ delete result[style === 'deepObject' ? `${name}[${key}]` : key];
119
+ }
120
+ }
121
+ result[name] = value;
122
+ }
123
+ continue;
124
+ }
125
+ if (single === undefined) {
126
+ continue;
127
+ }
128
+ const json = isJsonMediaType(encoding?.contentType);
129
+ const decode = (value, itemSchema) => json || isObjectSchema(itemSchema) || isArraySchema(itemSchema) ? JSON.parse(value) : value;
130
+ result[name] = isArraySchema(property)
131
+ ? params.getAll(name).map((value) => decode(value, property?.items))
132
+ : decode(single, property);
133
+ }
134
+ return result;
135
+ };
@@ -1,4 +1,9 @@
1
1
  import type { OpenAPIV3_1 } from '@scalar/openapi-types';
2
+ import type { ExampleObject } from '@scalar/workspace-store/schemas/v3.2/strict/example';
3
+ type SelectedResponseExample = ExampleObject & {
4
+ value: unknown;
5
+ provenance?: 'serialized' | 'data';
6
+ };
2
7
  /**
3
8
  * Pick the example body for a response media type.
4
9
  *
@@ -18,7 +23,6 @@ import type { OpenAPIV3_1 } from '@scalar/openapi-types';
18
23
  */
19
24
  export declare const selectResponseExample: <T extends Pick<OpenAPIV3_1.MediaTypeObject, "example" | "examples"> & {
20
25
  schema?: unknown;
21
- }>(mediaType: T | undefined, exampleName?: string) => {
22
- value: unknown;
23
- } | undefined;
26
+ }>(mediaType: T | undefined, exampleName?: string) => SelectedResponseExample | undefined;
27
+ export {};
24
28
  //# sourceMappingURL=select-response-example.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"select-response-example.d.ts","sourceRoot":"","sources":["../../src/utils/select-response-example.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAGxD;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,qBAAqB,GAChC,CAAC,SAAS,IAAI,CAAC,WAAW,CAAC,eAAe,EAAE,SAAS,GAAG,UAAU,CAAC,GAAG;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,EAE1F,WAAW,CAAC,GAAG,SAAS,EACxB,cAAc,MAAM,KACnB;IAAE,KAAK,EAAE,OAAO,CAAA;CAAE,GAAG,SAoCvB,CAAA"}
1
+ {"version":3,"file":"select-response-example.d.ts","sourceRoot":"","sources":["../../src/utils/select-response-example.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAExD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qDAAqD,CAAA;AAExF,KAAK,uBAAuB,GAAG,aAAa,GAAG;IAAE,KAAK,EAAE,OAAO,CAAC;IAAC,UAAU,CAAC,EAAE,YAAY,GAAG,MAAM,CAAA;CAAE,CAAA;AAarG;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,qBAAqB,GAChC,CAAC,SAAS,IAAI,CAAC,WAAW,CAAC,eAAe,EAAE,SAAS,GAAG,UAAU,CAAC,GAAG;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,EAE1F,WAAW,CAAC,GAAG,SAAS,EACxB,cAAc,MAAM,KACnB,uBAAuB,GAAG,SAkC5B,CAAA"}
@@ -1,4 +1,14 @@
1
1
  import { getResolvedRef } from '@scalar/workspace-store/helpers/get-resolved-ref';
2
+ /** Keep serialized/data examples distinguishable until the media serializer runs. */
3
+ const exampleValue = (example) => {
4
+ if (example?.serializedValue !== undefined) {
5
+ return { serializedValue: example.serializedValue, value: example.serializedValue, provenance: 'serialized' };
6
+ }
7
+ if (example?.dataValue !== undefined) {
8
+ return { dataValue: example.dataValue, value: example.dataValue, provenance: 'data' };
9
+ }
10
+ return example?.value !== undefined ? { value: example.value } : undefined;
11
+ };
2
12
  /**
3
13
  * Pick the example body for a response media type.
4
14
  *
@@ -23,9 +33,9 @@ export const selectResponseExample = (mediaType, exampleName) => {
23
33
  const { example, examples } = mediaType;
24
34
  // 1. A named example requested via `Prefer: example=<name>`
25
35
  if (exampleName && examples && exampleName in examples) {
26
- const value = getResolvedRef(examples[exampleName])?.value;
27
- if (value !== undefined) {
28
- return { value };
36
+ const selected = exampleValue(getResolvedRef(examples[exampleName]));
37
+ if (selected) {
38
+ return selected;
29
39
  }
30
40
  }
31
41
  // 2. The singular `example` keyword
@@ -36,9 +46,9 @@ export const selectResponseExample = (mediaType, exampleName) => {
36
46
  if (examples) {
37
47
  const firstKey = Object.keys(examples)[0];
38
48
  if (firstKey !== undefined) {
39
- const value = getResolvedRef(examples[firstKey])?.value;
40
- if (value !== undefined) {
41
- return { value };
49
+ const selected = exampleValue(getResolvedRef(examples[firstKey]));
50
+ if (selected) {
51
+ return selected;
42
52
  }
43
53
  }
44
54
  }
@@ -6,6 +6,6 @@ type Schema = NonNullable<OpenAPIV3_1.ComponentsObject['schemas']>[string];
6
6
  * Returns `undefined` for an `undefined` body, mirroring `JSON.stringify`, so the caller can send an
7
7
  * empty body rather than the characters `undefined`.
8
8
  */
9
- export declare const serializeResponseBody: (body: unknown, contentType: string | undefined, schema?: Schema) => string | undefined;
9
+ export declare const serializeResponseBody: (body: unknown, contentType: string | undefined, schema?: Schema, provenance?: "data" | "serialized") => string | undefined;
10
10
  export {};
11
11
  //# sourceMappingURL=serialize-response-body.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"serialize-response-body.d.ts","sourceRoot":"","sources":["../../src/utils/serialize-response-body.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAExD,KAAK,MAAM,GAAG,WAAW,CAAC,WAAW,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAA;AAkE1E;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,GAChC,MAAM,OAAO,EACb,aAAa,MAAM,GAAG,SAAS,EAC/B,SAAS,MAAM,KACd,MAAM,GAAG,SAgCX,CAAA"}
1
+ {"version":3,"file":"serialize-response-body.d.ts","sourceRoot":"","sources":["../../src/utils/serialize-response-body.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAKxD,KAAK,MAAM,GAAG,WAAW,CAAC,WAAW,CAAC,gBAAgB,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAA;AA2D1E;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,GAChC,MAAM,OAAO,EACb,aAAa,MAAM,GAAG,SAAS,EAC/B,SAAS,MAAM,EACf,aAAa,MAAM,GAAG,YAAY,KACjC,MAAM,GAAG,SAsCX,CAAA"}
@@ -1,5 +1,8 @@
1
- import { json2xml } from '@scalar/helpers/file/json2xml';
1
+ import { isXmlMediaType } from '@scalar/helpers/http/is-xml-media-type';
2
2
  import { parseMimeType } from '@scalar/helpers/http/mime-type';
3
+ import { serializeXmlExample } from '@scalar/workspace-store/request-example';
4
+ import { coerceValue } from '@scalar/workspace-store/schemas/typebox-coerce';
5
+ import { SchemaObjectSchema } from '@scalar/workspace-store/schemas/v3.2/strict/openapi-document';
3
6
  /**
4
7
  * Whether a media type carries a single JSON document.
5
8
  *
@@ -13,11 +16,6 @@ const isJsonDocumentContentType = (contentType) => {
13
16
  const { subtype } = parseMimeType(contentType);
14
17
  return subtype === 'json' || subtype.endsWith('+json');
15
18
  };
16
- /** Whether a media type carries XML, including suffixed types such as `application/xhtml+xml`. */
17
- const isXmlContentType = (contentType) => {
18
- const { subtype } = parseMimeType(contentType);
19
- return subtype === 'xml' || subtype.endsWith('+xml');
20
- };
21
19
  /**
22
20
  * How the resolved response schema describes the body: as a string, as something else, or not at all.
23
21
  *
@@ -58,11 +56,17 @@ const isSerializedJsonDocument = (value) => {
58
56
  * Returns `undefined` for an `undefined` body, mirroring `JSON.stringify`, so the caller can send an
59
57
  * empty body rather than the characters `undefined`.
60
58
  */
61
- export const serializeResponseBody = (body, contentType, schema) => {
62
- // XML: only an object tree can be turned into a document. `null` is `typeof 'object'` too, but it is
63
- // not a valid XML root, so it falls through to `JSON.stringify` below rather than into `json2xml`.
64
- if (body !== null && typeof body === 'object' && isXmlContentType(contentType)) {
65
- return json2xml(body);
59
+ export const serializeResponseBody = (body, contentType, schema, provenance) => {
60
+ if (provenance === 'serialized' && typeof body === 'string') {
61
+ return body;
62
+ }
63
+ if (provenance === 'data' && isJsonDocumentContentType(contentType)) {
64
+ return JSON.stringify(body);
65
+ }
66
+ if (isXmlMediaType(contentType)) {
67
+ return typeof body === 'string'
68
+ ? body
69
+ : serializeXmlExample(body, coerceValue(SchemaObjectSchema, schema ?? {}), { mode: 'read' }).xml;
66
70
  }
67
71
  if (typeof body === 'string') {
68
72
  // Anywhere but a single JSON document, the characters are the payload: `text/plain`, `text/html`,
@@ -1 +1 @@
1
- {"version":3,"file":"validate-request.d.ts","sourceRoot":"","sources":["../../src/utils/validate-request.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAMxD,OAAO,KAAK,EAAW,iBAAiB,EAAE,MAAM,MAAM,CAAA;AAoStD;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,GAC1B,WAAW,WAAW,CAAC,eAAe,EACtC,qBAAqB,WAAW,CAAC,cAAc,CAAC,YAAY,CAAC,KAC5D,iBAkKF,CAAA"}
1
+ {"version":3,"file":"validate-request.d.ts","sourceRoot":"","sources":["../../src/utils/validate-request.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAe,MAAM,uBAAuB,CAAA;AAMrE,OAAO,KAAK,EAAW,iBAAiB,EAAE,MAAM,MAAM,CAAA;AAuTtD;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,GAC1B,WAAW,WAAW,CAAC,eAAe,EACtC,qBAAqB,WAAW,CAAC,cAAc,CAAC,YAAY,CAAC,KAC5D,iBAmLF,CAAA"}
@@ -1,9 +1,12 @@
1
+ import { getFirstMediaType } from '@scalar/helpers/http/get-first-media-type';
2
+ import { parseMimeType } from '@scalar/helpers/http/mime-type';
1
3
  import { getResolvedRef } from '@scalar/workspace-store/helpers/get-resolved-ref';
2
4
  import { getResolvedRefDeep } from '@scalar/workspace-store/helpers/get-resolved-ref-deep';
3
5
  import Ajv2020 from 'ajv/dist/2020.js';
4
6
  import addFormats from 'ajv-formats';
5
7
  import { getCookie } from 'hono/cookie';
6
8
  import { deserializeArrayParameter, deserializeObjectParameter, getObjectPropertyNames, isArraySchema, isObjectSchema, resolveSerialization, } from './deserialize-parameter.js';
9
+ import { findQuerystringParameter, getQuerystringJsonSchema, parseQuerystringParameter } from './querystring-parameter.js';
7
10
  import { replaceCircularMarkers } from './replace-circular-markers.js';
8
11
  /**
9
12
  * Prepare a resolved schema for Ajv by replacing the `'[circular]'` markers a recursive schema leaves
@@ -113,7 +116,7 @@ const mergeParameters = (pathItemParameters, operationParameters) => {
113
116
  * in isolation so one broken schema never disables the others.
114
117
  */
115
118
  const compileSchema = (ajv, schema, label) => {
116
- if (!schema) {
119
+ if (schema === null || schema === undefined) {
117
120
  return null;
118
121
  }
119
122
  try {
@@ -140,6 +143,9 @@ const compileValidators = (operation, pathItemParameters) => {
140
143
  const queryParameters = buildParameterSchema(parameters, 'query');
141
144
  const headerParameters = buildParameterSchema(parameters, 'header');
142
145
  const cookieParameters = buildParameterSchema(parameters, 'cookie');
146
+ const querystringParameter = findQuerystringParameter(operation, pathItemParameters);
147
+ const [querystringContentType, querystringMedia] = getFirstMediaType(querystringParameter?.content) ?? [];
148
+ const querystringSchema = querystringMedia?.schema;
143
149
  const requestBody = getResolvedRef(operation.requestBody);
144
150
  // Build the body schema defensively; resolving a malformed `$ref` should not crash setup.
145
151
  let bodySchema = null;
@@ -151,6 +157,9 @@ const compileValidators = (operation, pathItemParameters) => {
151
157
  console.error('Error resolving request body schema, skipping body validation:', error);
152
158
  }
153
159
  return {
160
+ querystringParameter,
161
+ querystringJson: compileSchema(bodyAjv, getQuerystringJsonSchema(querystringParameter), 'querystring JSON properties'),
162
+ querystring: compileSchema(parseMimeType(querystringContentType).essence === 'application/x-www-form-urlencoded' ? parameterAjv : bodyAjv, querystringSchema === undefined ? null : asCompilableSchema(getResolvedRefDeep(querystringSchema)), 'querystring parameter'),
154
163
  path: compileSchema(parameterAjv, pathParameters?.schema ?? null, 'path parameter'),
155
164
  query: compileSchema(parameterAjv, queryParameters?.schema ?? null, 'query parameter'),
156
165
  header: compileSchema(parameterAjv, headerParameters?.schema ?? null, 'header parameter'),
@@ -312,6 +321,25 @@ export const validateRequest = (operation, pathItemParameters) => {
312
321
  violations.push(...mapErrors(validator.errors, location));
313
322
  }
314
323
  }
324
+ if (validators.querystringParameter) {
325
+ try {
326
+ const value = parseQuerystringParameter(c.req.url, validators.querystringParameter);
327
+ if (value === undefined) {
328
+ if (validators.querystringParameter.required) {
329
+ violations.push({ location: 'query', path: '', message: 'Query string is required' });
330
+ }
331
+ }
332
+ else if (validators.querystringJson && !validators.querystringJson(value)) {
333
+ violations.push(...mapErrors(validators.querystringJson.errors, 'query'));
334
+ }
335
+ else if (validators.querystring && !validators.querystring(value)) {
336
+ violations.push(...mapErrors(validators.querystring.errors, 'query'));
337
+ }
338
+ }
339
+ catch {
340
+ violations.push({ location: 'query', path: '', message: 'Query string could not be decoded' });
341
+ }
342
+ }
315
343
  // Request body — only `application/json` in this slice
316
344
  if (validators.body || validators.bodyRequired) {
317
345
  // Read from a clone so the original request stream stays intact for the mock or `x-handler`
package/package.json CHANGED
@@ -16,7 +16,7 @@
16
16
  "swagger",
17
17
  "cli"
18
18
  ],
19
- "version": "0.15.0",
19
+ "version": "0.17.0",
20
20
  "engines": {
21
21
  "node": ">=22"
22
22
  },
@@ -53,12 +53,12 @@
53
53
  "dependencies": {
54
54
  "@faker-js/faker": "10.6.0",
55
55
  "@hono/node-server": "^2.1.1",
56
- "@scalar/helpers": "0.13.0",
57
- "@scalar/json-magic": "0.15.0",
56
+ "@scalar/helpers": "0.15.0",
57
+ "@scalar/json-magic": "0.15.2",
58
58
  "@scalar/openapi-types": "0.9.7",
59
- "@scalar/openapi-upgrader": "0.3.0",
60
- "@scalar/types": "0.21.0",
61
- "@scalar/workspace-store": "0.65.0",
59
+ "@scalar/openapi-upgrader": "0.4.0",
60
+ "@scalar/types": "0.22.1",
61
+ "@scalar/workspace-store": "0.67.0",
62
62
  "ajv": "^8.20.0",
63
63
  "ajv-formats": "^3.0.1",
64
64
  "hono": "^4.13.8",
@@ -69,7 +69,7 @@
69
69
  "devDependencies": {
70
70
  "@types/node": "^24.1.0",
71
71
  "@types/ws": "8.18.1",
72
- "undici": "7.24.4",
72
+ "undici": "7.29.0",
73
73
  "vite": "8.1.5"
74
74
  },
75
75
  "scripts": {