@scalar/mock-server 0.14.4 → 0.16.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 (52) hide show
  1. package/CHANGELOG.md +66 -0
  2. package/dist/create-asyncapi-mock-server.d.ts +8 -10
  3. package/dist/create-asyncapi-mock-server.d.ts.map +1 -1
  4. package/dist/create-asyncapi-mock-server.js +6 -10
  5. package/dist/create-mock-server.d.ts.map +1 -1
  6. package/dist/create-mock-server.js +36 -17
  7. package/dist/routes/mock-any-response.d.ts.map +1 -1
  8. package/dist/routes/mock-any-response.js +28 -13
  9. package/dist/routes/mock-handler-response.d.ts +1 -2
  10. package/dist/routes/mock-handler-response.d.ts.map +1 -1
  11. package/dist/routes/mock-handler-response.js +15 -10
  12. package/dist/types.d.ts +2 -2
  13. package/dist/types.d.ts.map +1 -1
  14. package/dist/utils/build-handler-context.d.ts +3 -2
  15. package/dist/utils/build-handler-context.d.ts.map +1 -1
  16. package/dist/utils/build-handler-context.js +23 -2
  17. package/dist/utils/get-oauth2-metadata.d.ts +1 -0
  18. package/dist/utils/get-oauth2-metadata.d.ts.map +1 -1
  19. package/dist/utils/get-oauth2-metadata.js +5 -1
  20. package/dist/utils/get-open-auth-token-urls.d.ts +2 -2
  21. package/dist/utils/get-open-auth-token-urls.d.ts.map +1 -1
  22. package/dist/utils/get-open-auth-token-urls.js +5 -3
  23. package/dist/utils/get-operation.d.ts +4 -2
  24. package/dist/utils/get-operation.d.ts.map +1 -1
  25. package/dist/utils/get-operation.js +9 -5
  26. package/dist/utils/handle-authentication.d.ts +2 -2
  27. package/dist/utils/handle-authentication.d.ts.map +1 -1
  28. package/dist/utils/log-authentication-instructions.d.ts +2 -2
  29. package/dist/utils/log-authentication-instructions.d.ts.map +1 -1
  30. package/dist/utils/log-authentication-instructions.js +7 -0
  31. package/dist/utils/negotiate-content-type.d.ts +4 -0
  32. package/dist/utils/negotiate-content-type.d.ts.map +1 -0
  33. package/dist/utils/negotiate-content-type.js +12 -0
  34. package/dist/utils/process-openapi-document.d.ts +7 -4
  35. package/dist/utils/process-openapi-document.d.ts.map +1 -1
  36. package/dist/utils/process-openapi-document.js +26 -11
  37. package/dist/utils/querystring-parameter.d.ts +11 -0
  38. package/dist/utils/querystring-parameter.d.ts.map +1 -0
  39. package/dist/utils/querystring-parameter.js +135 -0
  40. package/dist/utils/select-response-example.d.ts +3 -1
  41. package/dist/utils/select-response-example.d.ts.map +1 -1
  42. package/dist/utils/set-up-authentication-routes.d.ts.map +1 -1
  43. package/dist/utils/set-up-authentication-routes.js +2 -0
  44. package/dist/utils/set-up-device-authorization.d.ts +5 -0
  45. package/dist/utils/set-up-device-authorization.d.ts.map +1 -0
  46. package/dist/utils/set-up-device-authorization.js +192 -0
  47. package/dist/utils/streaming-response.d.ts +24 -0
  48. package/dist/utils/streaming-response.d.ts.map +1 -0
  49. package/dist/utils/streaming-response.js +65 -0
  50. package/dist/utils/validate-request.d.ts.map +1 -1
  51. package/dist/utils/validate-request.js +29 -1
  52. package/package.json +12 -9
package/CHANGELOG.md CHANGED
@@ -1,5 +1,71 @@
1
1
  # @scalar/mock-server
2
2
 
3
+ ## 0.16.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#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.
8
+
9
+ 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.
10
+
11
+ - [#10186](https://github.com/scalar/scalar/pull/10186): Support additionalOperations with case-sensitive custom HTTP methods, existing operation middleware, and CORS preflight responses.
12
+
13
+ Preserve QUERY in the default CORS method list when adding methods declared by the API description.
14
+
15
+ ## 0.15.0
16
+
17
+ ### Minor Changes
18
+
19
+ - [#10290](https://github.com/scalar/scalar/pull/10290): Update Hono and its Node.js server, WebSocket, and OpenAPI integration dependencies.
20
+
21
+ Replace the deprecated `@hono/node-ws` adapter with Node server v2 WebSocket support. `createAsyncApiMockServer()` now returns `websocket` instead of `injectWebSocket`. Start the server with `serve({ fetch: app.fetch, websocket })` instead of calling `injectWebSocket(server)`.
22
+
23
+ AsyncAPI callers must upgrade to `@hono/node-server` v2. Node server v1 ignores the `websocket` option, so WebSocket channels will silently stop accepting connections if the server dependency is not upgraded.
24
+
25
+ - [#10286](https://github.com/scalar/scalar/pull/10286): Fix JSON and YAML exports for file and URL inputs. Add an origin option to resolve relative references in already loaded documents without fetching the root document again.
26
+ - [#10288](https://github.com/scalar/scalar/pull/10288): Upgrade documents to OpenAPI 3.2 when preparing mock responses, and use OpenAPI 3.2 for the empty-document default.
27
+ - [#10176](https://github.com/scalar/scalar/pull/10176): Generate finite SSE, JSON Lines, NDJSON, and JSON Sequence mock responses from OpenAPI 3.2 itemSchema definitions, including custom handler responses.
28
+
29
+ Honor named examples in custom stream handlers and keep media-type recognition consistent with stream serialization.
30
+
31
+ Use the same stream serializer as documentation examples. SSE objects without valid fields are omitted with a console warning per serialization call; other records still stream normally. The mock serializes each item separately to retain individual chunk writes.
32
+
33
+ - [#10191](https://github.com/scalar/scalar/pull/10191): Support OpenAPI 3.2 OAuth device authorization with verification codes, cancellable token polling, stored credentials, and OAuth metadata discovery. Add mock device authorization and approval endpoints with pending, denial, expiry, and polling backoff responses.
34
+
35
+ Use consistent form-encoded Basic credentials and environment substitution across OAuth token and refresh flows. Allow HTTP metadata and verification links on local development hosts and reserved test domains, coerce discovery fields consistently, and report device-code expiry clearly.
36
+
37
+ ### Patch Changes
38
+
39
+ - [#10211](https://github.com/scalar/scalar/pull/10211): Preserve literal data and tag groups when upgrading to OpenAPI 3.2, migrate XML metadata only in schemas, and remove incompatible legacy XML flags. Make 3.2 upgrades leave the input unchanged, match the complete source version, prevent previously inactive parameter settings from changing serialization, and report path-specific errors for detected compatibility issues that require an author's decision.
40
+
41
+ Tag `kind` values may change: navigation groups are classified from actual operation-tag usage instead of name substrings. Malformed 3.1 versions now report explicit errors, and successful 3.2 upgrades clone the input only once.
42
+
43
+ Expose `UpgradeIncompatibilityError` so Markdown generation can retain OpenAPI 3.1 for descriptions requiring author decisions instead of failing or silently changing semantics. Clone safety and malformed-version errors still propagate.
44
+
45
+ The mock server also retains OpenAPI 3.1 when the strict 3.2 migration reports compatibility diagnostics. Existing inline XML descriptions continue loading without inventing element names.
46
+
47
+ Read only own data properties during migration so inherited parameter lists, XML metadata, and reference targets cannot modify prototype-owned objects.
48
+
49
+ Add `upgrade(input, '3.2', { onIncompatible: 'collect' })` to return a complete document and compatibility diagnostics. Compatible descriptions upgrade to 3.2; incompatible descriptions retain 3.1 without partial transformations. Strict mode remains the default, and malformed-version and clone-safety errors still propagate. The Markdown converter and mock server now use the shared collect mode.
50
+
51
+ - [#10203](https://github.com/scalar/scalar/pull/10203): Add a picker for generated response examples with anyOf or oneOf schema variants.
52
+
53
+ Apply union selections to primitive and array examples in the shared generator without reusing the selection for nested unions.
54
+
55
+ The shared generator change also affects request examples, snippets, mock responses, and AsyncAPI payloads: root primitive/array unions now generate their chosen branch before type inference from sibling properties or items. For example, a string schema with `oneOf: [{ const: "first" }, { const: "second" }]` now generates `"first"` by default, and selecting the second branch generates `"second"`. Keywords for unrelated types do not force object/array generation. Root selections are consumed once; nested unions retain their own default or path-specific choice.
56
+
57
+ Do not show a response variant picker for an empty enum, which permits no valid alternatives.
58
+
59
+ Preserve the generated branch shape in mock HTTP responses instead of re-wrapping selected primitive values as arrays based on root sibling `items`. Explicit authored examples retain the existing array normalization.
60
+
61
+ - [#10206](https://github.com/scalar/scalar/pull/10206): Add generic document identity hooks for bundling and an explicit root URI option for reference proxies. Honor OpenAPI 3.2 `$self` through an OpenAPI plugin in workspace-store, including external documents and partial bundles, and enable it in OpenAPI bundling callers.
62
+
63
+ URI resolution now honors root-relative and protocol-relative URLs, query/fragment references, and trailing-slash directory bases for all bundler consumers. Absolute non-HTTP identifiers remain unchanged instead of becoming filesystem paths; loader support is unchanged. Relative HTTP references retain query strings and fragments and are emitted only when they round-trip to the original URL.
64
+
65
+ Preserve authored reference spellings through serialized partial bundles and editable exports, while keeping older OpenAPI resolution and configured loader restrictions unchanged.
66
+
67
+ Keep references matching authored root schema identifiers intact so schema labels and anchors retain their existing behavior.
68
+
3
69
  ## 0.14.4
4
70
 
5
71
  ### Patch Changes
@@ -1,4 +1,4 @@
1
- import { createNodeWebSocket } from '@hono/node-ws';
1
+ import { type WebSocketServerLike } from '@hono/node-server';
2
2
  import { Hono } from 'hono';
3
3
  import type { MessageDirection, MockTransport } from './transports/types.js';
4
4
  import type { MockServerLogger } from './types.js';
@@ -33,11 +33,10 @@ export type AsyncApiMockServerOptions = {
33
33
  export type AsyncApiMockServer = {
34
34
  /** The Hono app serving SSE channels and WebSocket upgrade routes. */
35
35
  app: Hono;
36
- /**
37
- * Attaches WebSocket handling to the running Node HTTP server returned by `@hono/node-server`'s
38
- * `serve()`. Must be called for WebSocket channels to accept connections.
39
- */
40
- injectWebSocket: ReturnType<typeof createNodeWebSocket>['injectWebSocket'];
36
+ /** Pass this option to `@hono/node-server`'s `serve()` to enable WebSocket channels. */
37
+ websocket: {
38
+ server: WebSocketServerLike;
39
+ };
41
40
  };
42
41
  /**
43
42
  * Create a mock server for an AsyncAPI 3.1 document — the event-driven counterpart of
@@ -45,12 +44,11 @@ export type AsyncApiMockServer = {
45
44
  * default) that emits realistic mock messages generated from the channel's message payload
46
45
  * schemas, the same way the REST mocker generates HTTP response bodies.
47
46
  *
48
- * WebSocket support requires attaching to the HTTP server after `serve()`:
47
+ * Pass the returned WebSocket option to `serve()`:
49
48
  *
50
49
  * ```ts
51
- * const { app, injectWebSocket } = await createAsyncApiMockServer({ document })
52
- * const server = serve({ fetch: app.fetch, port: 3000 })
53
- * injectWebSocket(server)
50
+ * const { app, websocket } = await createAsyncApiMockServer({ document })
51
+ * serve({ fetch: app.fetch, port: 3000, websocket })
54
52
  * ```
55
53
  */
56
54
  export declare function createAsyncApiMockServer(options: AsyncApiMockServerOptions): Promise<AsyncApiMockServer>;
@@ -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,mBAAmB,EAAE,MAAM,eAAe,CAAA;AACnD,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAI3B,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;;;OAGG;IACH,eAAe,EAAE,UAAU,CAAC,OAAO,mBAAmB,CAAC,CAAC,iBAAiB,CAAC,CAAA;CAC3E,CAAA;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,wBAAwB,CAAC,OAAO,EAAE,yBAAyB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CA0C9G"}
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,6 +1,7 @@
1
- import { createNodeWebSocket } from '@hono/node-ws';
1
+ import { upgradeWebSocket } from '@hono/node-server';
2
2
  import { Hono } from 'hono';
3
3
  import { cors } from 'hono/cors';
4
+ import { WebSocketServer } from 'ws';
4
5
  import { defaultTransports } from './transports/index.js';
5
6
  import { generateMessage } from './utils/generate-message.js';
6
7
  import { processAsyncApiDocument } from './utils/process-asyncapi-document.js';
@@ -12,20 +13,15 @@ import { resolveLogger } from './utils/resolve-logger.js';
12
13
  * default) that emits realistic mock messages generated from the channel's message payload
13
14
  * schemas, the same way the REST mocker generates HTTP response bodies.
14
15
  *
15
- * WebSocket support requires attaching to the HTTP server after `serve()`:
16
+ * Pass the returned WebSocket option to `serve()`:
16
17
  *
17
18
  * ```ts
18
- * const { app, injectWebSocket } = await createAsyncApiMockServer({ document })
19
- * const server = serve({ fetch: app.fetch, port: 3000 })
20
- * injectWebSocket(server)
19
+ * const { app, websocket } = await createAsyncApiMockServer({ document })
20
+ * serve({ fetch: app.fetch, port: 3000, websocket })
21
21
  * ```
22
22
  */
23
23
  export async function createAsyncApiMockServer(options) {
24
24
  const app = new Hono();
25
- // The Node WebSocket adapter must be created against the app before routes are registered so the
26
- // `upgradeWebSocket` helper shares this app's lifecycle. `injectWebSocket` is wired to the
27
- // HTTP server by the caller after `serve()`.
28
- const { injectWebSocket, upgradeWebSocket } = createNodeWebSocket({ app });
29
25
  const document = await processAsyncApiDocument(options.document);
30
26
  const channels = resolveChannels(document);
31
27
  const transports = [...defaultTransports, ...(options.transports ?? [])];
@@ -50,5 +46,5 @@ export async function createAsyncApiMockServer(options) {
50
46
  transport.register(channel, context);
51
47
  log(`[asyncapi] ${transport.name} -> ${channel.route} (channel "${channel.id}")`);
52
48
  }
53
- return { app, injectWebSocket };
49
+ return { app, websocket: { server: new WebSocketServer({ noServer: true }) } };
54
50
  }
@@ -1 +1 @@
1
- {"version":3,"file":"create-mock-server.d.ts","sourceRoot":"","sources":["../src/create-mock-server.ts"],"names":[],"mappings":"AAEA,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,CA2NtF"}
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"}
@@ -1,3 +1,5 @@
1
+ import { normalize } from '@scalar/json-magic/helpers/normalize';
2
+ import { getRaw } from '@scalar/json-magic/magic-proxy';
1
3
  import { getResolvedRef, mergeSiblingReferences } from '@scalar/workspace-store/helpers/get-resolved-ref';
2
4
  import { Hono } from 'hono';
3
5
  import { every } from 'hono/combine';
@@ -66,8 +68,14 @@ export async function createMockServer(configuration) {
66
68
  ...(operation ? { operation } : {}),
67
69
  }, 500);
68
70
  });
69
- /** Dereferenced OpenAPI document */
70
- const schema = await processOpenApiDocument(configuration?.document ?? configuration?.specification);
71
+ const input = configuration?.document ?? configuration?.specification;
72
+ const schema = await processOpenApiDocument(input, configuration?.origin);
73
+ const sourceDocument = typeof input === 'string' ? normalize(input) : input;
74
+ // Source locations need a bundled export so relative references remain usable outside the server.
75
+ const exportDocument = configuration?.origin ||
76
+ (typeof input === 'string' && (sourceDocument === null || typeof sourceDocument !== 'object'))
77
+ ? getRaw(schema)
78
+ : input;
71
79
  // Seed data from schemas with x-seed extension
72
80
  // This happens before routes are set up so data is available immediately
73
81
  const schemas = schema?.components?.schemas;
@@ -95,7 +103,13 @@ export async function createMockServer(configuration) {
95
103
  }
96
104
  }
97
105
  // CORS headers
98
- 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] }));
99
113
  /** Authentication methods defined in the OpenAPI document */
100
114
  setUpAuthenticationRoutes(app, schema);
101
115
  // Only the instructions honor `logger` (on by default); the util still prints warnings and errors
@@ -128,19 +142,17 @@ export async function createMockServer(configuration) {
128
142
  orderedPathKeys.forEach(({ path, query }) => {
129
143
  // A path item may itself be a `$ref`, so resolve it before reading its operations.
130
144
  const pathItem = getResolvedRef(paths[path]);
131
- const methods = Object.keys(getOperations(pathItem));
145
+ const operations = getOperations(pathItem);
132
146
  /** Keys for all operations of a specified path */
133
- methods.forEach((method) => {
147
+ Object.entries(operations).forEach(([method, operation]) => {
134
148
  const route = honoRouteFromPath(path);
135
- const operation = pathItem?.[method];
136
149
  // Remember which operation this route mocks, so the error handler can name it when something
137
150
  // fails downstream. Recorded on the context rather than mapped back from the request path,
138
151
  // which would not survive the app being mounted under a base path. Registered before the rest
139
152
  // of the route so a failure in request validation is named too. The OpenAPI path key is kept
140
153
  // (rather than the Hono route) because that is what the document author reads.
141
154
  const mockedOperation = {
142
- // `toUpperCase` widens to `string`, so restate the narrower type the method union guarantees.
143
- method: method.toUpperCase(),
155
+ method,
144
156
  path,
145
157
  ...(operation?.operationId ? { operationId: operation.operationId } : {}),
146
158
  };
@@ -180,21 +192,28 @@ export async function createMockServer(configuration) {
180
192
  const hasHandler = handlerCode && typeof handlerCode === 'string' && handlerCode.trim().length > 0;
181
193
  // Route to appropriate handler
182
194
  if (hasHandler) {
183
- handlers.push(async (c) => await mockHandlerResponse(c, operation));
195
+ handlers.push(async (c) => await mockHandlerResponse(c, operation, pathItem?.parameters));
184
196
  }
185
197
  else {
186
198
  handlers.push(async (c) => await mockAnyResponse(c, operation));
187
199
  }
188
- if (query.length === 0) {
189
- handlers.forEach((handler) => app[method](route, handler));
190
- return;
191
- }
192
200
  // The pinned query parameters are not part of the route, so they are checked here. A request
193
201
  // that does not carry them is handed on to the next matching route — usually the sibling path
194
202
  // key without the query string.
195
203
  const operationChain = every(...handlers);
196
- app[method](route, async (c, next) => {
197
- 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)) {
198
217
  await next();
199
218
  return;
200
219
  }
@@ -203,8 +222,8 @@ export async function createMockServer(configuration) {
203
222
  });
204
223
  });
205
224
  // OpenAPI JSON file
206
- app.get('/openapi.json', (c) => respondWithOpenApiDocument(c, configuration?.document ?? configuration?.specification, 'json'));
225
+ app.get('/openapi.json', (c) => respondWithOpenApiDocument(c, exportDocument, 'json'));
207
226
  // OpenAPI YAML file
208
- app.get('/openapi.yaml', (c) => respondWithOpenApiDocument(c, configuration?.document ?? configuration?.specification, 'yaml'));
227
+ app.get('/openapi.yaml', (c) => respondWithOpenApiDocument(c, exportDocument, 'yaml'));
209
228
  return app;
210
229
  }
@@ -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;AAcnC;;GAEG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,CAAC,eAAe,YA4HjF"}
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,16 +1,17 @@
1
1
  import { getResolvedRef } from '@scalar/workspace-store/helpers/get-resolved-ref';
2
2
  import { getResolvedRefDeep } from '@scalar/workspace-store/helpers/get-resolved-ref-deep';
3
3
  import { getExampleFromSchema } from '@scalar/workspace-store/request-example';
4
- import { accepts } from 'hono/accepts';
5
4
  import { streamSSE } from 'hono/streaming';
6
5
  import { collectSseEvents, isEventStreamContentType } from '../utils/collect-sse-events.js';
7
6
  import { findPreferredResponseKey } from '../utils/find-preferred-response-key.js';
8
7
  import { generateResponseExample } from '../utils/generate-response-example.js';
8
+ import { negotiateContentType } from '../utils/negotiate-content-type.js';
9
9
  import { normalizeResponseBody } from '../utils/normalize-response-body.js';
10
10
  import { parsePreferHeader } from '../utils/parse-prefer-header.js';
11
11
  import { pathParameters } from '../utils/path-parameters.js';
12
12
  import { selectResponseExample } from '../utils/select-response-example.js';
13
13
  import { serializeResponseBody } from '../utils/serialize-response-body.js';
14
+ import { getStreamingResponse, sendStreamingResponse } from '../utils/streaming-response.js';
14
15
  /**
15
16
  * Mock any response
16
17
  */
@@ -61,15 +62,17 @@ export function mockAnyResponse(c, operation) {
61
62
  return c.body(null);
62
63
  }
63
64
  // Content-Type
64
- const acceptedContentType = accepts(c, {
65
- header: 'Accept',
66
- supports: supportedContentTypes,
67
- default: supportedContentTypes.includes('application/json')
68
- ? 'application/json'
69
- : (supportedContentTypes[0] ?? 'text/plain;charset=UTF-8'),
70
- });
65
+ const acceptedContentType = negotiateContentType(c, selectedResponse.content);
71
66
  c.header('Content-Type', acceptedContentType);
72
67
  const acceptedResponse = selectedResponse?.content?.[acceptedContentType];
68
+ const streamingResponse = getStreamingResponse(acceptedResponse, acceptedContentType, {
69
+ exampleName: prefer.example,
70
+ variables: pathParameters(c),
71
+ });
72
+ if (streamingResponse) {
73
+ c.status(statusCode);
74
+ return sendStreamingResponse(c, streamingResponse);
75
+ }
73
76
  const responseSchema = acceptedResponse?.schema ? getResolvedRefDeep(acceptedResponse.schema) : undefined;
74
77
  /** Generates the response body from the schema, or returns `undefined` when there is no schema. */
75
78
  const generateFromSchema = () => responseSchema ? generateResponseExample(responseSchema, pathParameters(c)) : undefined;
@@ -99,11 +102,23 @@ export function mockAnyResponse(c, operation) {
99
102
  // Body: a named/singular/first example if one is defined, otherwise generate
100
103
  // a value from the schema. `Prefer: example=<name>` picks a named example.
101
104
  const selectedExample = selectResponseExample(acceptedResponse, prefer.example);
102
- const body = selectedExample
103
- ? normalizeResponseBody(selectedExample.value, responseSchema)
104
- : responseSchema
105
- ? normalizeResponseBody(generateFromSchema(), responseSchema)
106
- : null;
105
+ const body = (() => {
106
+ if (selectedExample) {
107
+ return normalizeResponseBody(selectedExample.value, responseSchema);
108
+ }
109
+ if (!responseSchema) {
110
+ return null;
111
+ }
112
+ const generated = generateFromSchema();
113
+ // Schema-level examples are authored values too, so retain their array normalization.
114
+ if (responseSchema.example !== undefined ||
115
+ (Array.isArray(responseSchema.examples) && responseSchema.examples.length)) {
116
+ return normalizeResponseBody(generated, responseSchema);
117
+ }
118
+ // The generator already chooses the value shape, including root union branches.
119
+ // Re-inferring it from sibling items would wrap a selected primitive in an array.
120
+ return generated;
121
+ })();
107
122
  c.status(statusCode);
108
123
  const serializedBody = serializeResponseBody(body, acceptedContentType, responseSchema);
109
124
  // `JSON.stringify` returns `undefined` for an `undefined` body, which is an empty response.
@@ -1,9 +1,8 @@
1
1
  import type { OpenAPIV3_1 } from '@scalar/openapi-types';
2
2
  import type { Context } from 'hono';
3
- import type { StatusCode } from 'hono/utils/http-status';
4
3
  /**
5
4
  * Mock response using x-handler code.
6
5
  * Executes the handler and returns its result as the response.
7
6
  */
8
- export declare function mockHandlerResponse(c: Context, operation: OpenAPIV3_1.OperationObject): Promise<(Response & import("hono").TypedResponse<null, StatusCode, "body">) | (Response & import("hono").TypedResponse<any, import("hono/utils/http-status").ContentfulStatusCode, "json">)>;
7
+ export declare function mockHandlerResponse(c: Context, operation: OpenAPIV3_1.OperationObject, pathItemParameters?: OpenAPIV3_1.PathItemObject['parameters']): Promise<Response>;
9
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;AAEnC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AA+HxD;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,WAAW,CAAC,eAAe,gMAyD3F"}
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,CACvC,CAAC,EAAE,OAAO,EACV,SAAS,EAAE,WAAW,CAAC,eAAe,EACtC,kBAAkB,CAAC,EAAE,WAAW,CAAC,cAAc,CAAC,YAAY,CAAC,qBAqE9D"}
@@ -1,12 +1,13 @@
1
1
  import { getResolvedRef } from '@scalar/workspace-store/helpers/get-resolved-ref';
2
- import { accepts } from 'hono/accepts';
3
2
  import { buildHandlerContext } from '../utils/build-handler-context.js';
4
3
  import { executeHandler } from '../utils/execute-handler.js';
5
4
  import { generateResponseExample } from '../utils/generate-response-example.js';
5
+ import { negotiateContentType } from '../utils/negotiate-content-type.js';
6
6
  import { normalizeResponseBody } from '../utils/normalize-response-body.js';
7
7
  import { parsePreferHeader } from '../utils/parse-prefer-header.js';
8
8
  import { pathParameters } from '../utils/path-parameters.js';
9
9
  import { selectResponseExample } from '../utils/select-response-example.js';
10
+ import { getStreamingResponse, sendStreamingResponse } from '../utils/streaming-response.js';
10
11
  /**
11
12
  * Get example response from OpenAPI spec for a given status code.
12
13
  * Returns the example value if found, or null if not available.
@@ -30,13 +31,7 @@ function getExampleFromResponse(c, statusCode, responses, exampleName) {
30
31
  return null;
31
32
  }
32
33
  // Content-Type negotiation
33
- const acceptedContentType = accepts(c, {
34
- header: 'Accept',
35
- supports: supportedContentTypes,
36
- default: supportedContentTypes.includes('application/json')
37
- ? 'application/json'
38
- : (supportedContentTypes[0] ?? 'text/plain;charset=UTF-8'),
39
- });
34
+ const acceptedContentType = negotiateContentType(c, response.content);
40
35
  const acceptedResponse = response.content?.[acceptedContentType];
41
36
  if (!acceptedResponse) {
42
37
  return null;
@@ -103,7 +98,7 @@ function determineStatusCode(tracking) {
103
98
  * Mock response using x-handler code.
104
99
  * Executes the handler and returns its result as the response.
105
100
  */
106
- export async function mockHandlerResponse(c, operation) {
101
+ export async function mockHandlerResponse(c, operation, pathItemParameters) {
107
102
  // Note: the `onRequest` callback runs as middleware (see `create-mock-server`) so it also fires
108
103
  // for requests rejected before reaching this handler.
109
104
  // Get x-handler code from operation
@@ -114,7 +109,7 @@ export async function mockHandlerResponse(c, operation) {
114
109
  }
115
110
  try {
116
111
  // Build handler context with tracking
117
- const { context, tracking } = await buildHandlerContext(c, operation);
112
+ const { context, tracking } = await buildHandlerContext(c, operation, pathItemParameters);
118
113
  // Execute handler
119
114
  const { result } = await executeHandler(handlerCode, context);
120
115
  // Determine status code based on all store operations, prioritizing semantically meaningful ones
@@ -125,6 +120,16 @@ export async function mockHandlerResponse(c, operation) {
125
120
  if (statusCode === 204) {
126
121
  return c.body(null);
127
122
  }
123
+ const response = getResolvedRef(operation.responses?.[String(statusCode)] ?? operation.responses?.default);
124
+ const contentType = negotiateContentType(c, response?.content);
125
+ const streamingResponse = getStreamingResponse(response?.content?.[contentType], contentType, {
126
+ body: result ?? undefined,
127
+ exampleName: parsePreferHeader(c.req.header('Prefer')).example,
128
+ variables: pathParameters(c),
129
+ });
130
+ if (streamingResponse) {
131
+ return sendStreamingResponse(c, streamingResponse);
132
+ }
128
133
  // Set Content-Type header for other responses
129
134
  c.header('Content-Type', 'application/json');
130
135
  // Return the handler result as JSON
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
  */
@@ -13,6 +11,8 @@ type RequireAtLeastOne<T, Keys extends keyof T = keyof T> = Pick<T, Exclude<keyo
13
11
  /** A sink for the informational log lines the mock servers print while starting up. */
14
12
  export type MockServerLogger = (line: string) => void;
15
13
  type BaseMockServerOptions = {
14
+ /** Source file path or URL used to resolve relative references in an already loaded document. */
15
+ origin?: string;
16
16
  /**
17
17
  * The OpenAPI document to use for mocking.
18
18
  * Can be a string (URL or file path) or an object.
@@ -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;;;;;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;AAOnC,OAAO,EAAE,KAAK,sBAAsB,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAEjF;;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;AAqDD;;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"}
@@ -4,8 +4,11 @@ import { accepts } from 'hono/accepts';
4
4
  import { store } from '../libs/store.js';
5
5
  import { generateResponseExample } from './generate-response-example.js';
6
6
  import { normalizeResponseBody } from './normalize-response-body.js';
7
+ import { parsePreferHeader } from './parse-prefer-header.js';
7
8
  import { pathParameters } from './path-parameters.js';
9
+ import { findQuerystringParameter, parseQuerystringParameter } from './querystring-parameter.js';
8
10
  import { createStoreWrapper } from './store-wrapper.js';
11
+ import { getStreamingResponse } from './streaming-response.js';
9
12
  /**
10
13
  * Get example response from OpenAPI spec for a given status code.
11
14
  * Returns the example value if found, or null if not available.
@@ -35,6 +38,13 @@ function getExampleFromResponse(c, statusCode, responses) {
35
38
  if (!acceptedResponse) {
36
39
  return null;
37
40
  }
41
+ const streamingResponse = getStreamingResponse(acceptedResponse, acceptedContentType, {
42
+ exampleName: parsePreferHeader(c.req.header('Prefer')).example,
43
+ variables: pathParameters(c),
44
+ });
45
+ if (streamingResponse) {
46
+ return streamingResponse.body;
47
+ }
38
48
  const responseSchema = acceptedResponse.schema ? getResolvedRefDeep(acceptedResponse.schema) : undefined;
39
49
  // Extract example from example property or generate from schema
40
50
  return acceptedResponse.example !== undefined
@@ -46,7 +56,7 @@ function getExampleFromResponse(c, statusCode, responses) {
46
56
  /**
47
57
  * Build the handler context from a Hono context.
48
58
  */
49
- export async function buildHandlerContext(c, operation) {
59
+ export async function buildHandlerContext(c, operation, pathItemParameters) {
50
60
  let body = undefined;
51
61
  try {
52
62
  // Compare case-insensitively, since media types are case-insensitive and request validation
@@ -68,6 +78,17 @@ export async function buildHandlerContext(c, operation) {
68
78
  catch {
69
79
  // Ignore parsing errors, body remains undefined
70
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
+ }
71
92
  const { wrappedStore, tracking } = createStoreWrapper(store);
72
93
  // Build res object with examples for all response status codes
73
94
  const res = {};
@@ -82,7 +103,7 @@ export async function buildHandlerContext(c, operation) {
82
103
  req: {
83
104
  body,
84
105
  params: pathParameters(c),
85
- query: Object.fromEntries(new URL(c.req.url).searchParams.entries()),
106
+ query,
86
107
  headers: Object.fromEntries(Object.entries(c.req.header()).map(([key, value]) => [key, value ?? ''])),
87
108
  },
88
109
  res,
@@ -3,6 +3,7 @@ import type { OpenAPIV3_2 } from '@scalar/openapi-types';
3
3
  type OAuth2Metadata = {
4
4
  issuer: string;
5
5
  authorization_endpoint?: string;
6
+ device_authorization_endpoint?: string;
6
7
  token_endpoint?: string;
7
8
  response_types_supported: string[];
8
9
  grant_types_supported: string[];
@@ -1 +1 @@
1
- {"version":3,"file":"get-oauth2-metadata.d.ts","sourceRoot":"","sources":["../../src/utils/get-oauth2-metadata.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAIxD,mEAAmE;AACnE,KAAK,cAAc,GAAG;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,sBAAsB,CAAC,EAAE,MAAM,CAAA;IAC/B,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,wBAAwB,EAAE,MAAM,EAAE,CAAA;IAClC,qBAAqB,EAAE,MAAM,EAAE,CAAA;IAC/B,gBAAgB,EAAE,MAAM,EAAE,CAAA;CAC3B,CAAA;AAED,mGAAmG;AACnG,eAAO,MAAM,iBAAiB,GAAI,OAAO,WAAW,CAAC,oBAAoB,CAAC,OAAO,CAAC,EAAE,QAAQ,MAAM,KAAG,cAqBpG,CAAA"}
1
+ {"version":3,"file":"get-oauth2-metadata.d.ts","sourceRoot":"","sources":["../../src/utils/get-oauth2-metadata.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAA;AAIxD,mEAAmE;AACnE,KAAK,cAAc,GAAG;IACpB,MAAM,EAAE,MAAM,CAAA;IACd,sBAAsB,CAAC,EAAE,MAAM,CAAA;IAC/B,6BAA6B,CAAC,EAAE,MAAM,CAAA;IACtC,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,wBAAwB,EAAE,MAAM,EAAE,CAAA;IAClC,qBAAqB,EAAE,MAAM,EAAE,CAAA;IAC/B,gBAAgB,EAAE,MAAM,EAAE,CAAA;CAC3B,CAAA;AAED,mGAAmG;AACnG,eAAO,MAAM,iBAAiB,GAAI,OAAO,WAAW,CAAC,oBAAoB,CAAC,OAAO,CAAC,EAAE,QAAQ,MAAM,KAAG,cA0BpG,CAAA"}
@@ -2,16 +2,20 @@ import { getPathFromUrl } from './get-open-auth-token-urls.js';
2
2
  /** Advertises local mock endpoints instead of sending clients to the real authorization server. */
3
3
  export const getOAuth2Metadata = (flows, origin) => {
4
4
  const authorizationFlow = flows?.authorizationCode ?? flows?.implicit;
5
- const tokenFlow = flows?.authorizationCode ?? flows?.clientCredentials ?? flows?.password;
5
+ const tokenFlow = flows?.authorizationCode ?? flows?.clientCredentials ?? flows?.password ?? flows?.deviceAuthorization;
6
6
  const localUrl = (url) => new URL(getPathFromUrl(url), origin).href;
7
7
  const supportedFlows = [
8
8
  { flow: flows?.authorizationCode, grant: 'authorization_code', response: 'code' },
9
9
  { flow: flows?.implicit, grant: 'implicit', response: 'token' },
10
10
  { flow: flows?.clientCredentials, grant: 'client_credentials' },
11
11
  { flow: flows?.password, grant: 'password' },
12
+ { flow: flows?.deviceAuthorization, grant: 'urn:ietf:params:oauth:grant-type:device_code' },
12
13
  ].filter(({ flow }) => flow);
13
14
  return {
14
15
  issuer: origin,
16
+ ...(flows?.deviceAuthorization
17
+ ? { device_authorization_endpoint: localUrl(flows.deviceAuthorization.deviceAuthorizationUrl || '/oauth/device') }
18
+ : {}),
15
19
  ...(authorizationFlow
16
20
  ? { authorization_endpoint: localUrl(authorizationFlow.authorizationUrl ?? '/oauth/authorize') }
17
21
  : {}),
@@ -1,7 +1,7 @@
1
1
  import type { OpenAPI } from '@scalar/openapi-types';
2
2
  /**
3
- * Extract path from URL. Metadata is fetched at its exact declared URL, so its routes
4
- * preserve trailing slashes instead of using the token-route normalization.
3
+ * Extract path from URL. Metadata routes preserve trailing slashes because discovery
4
+ * fetches the exact declared URL, unlike normalized token routes.
5
5
  */
6
6
  export declare function getPathFromUrl(url: string, { preserveTrailingSlash }?: {
7
7
  preserveTrailingSlash?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"get-open-auth-token-urls.d.ts","sourceRoot":"","sources":["../../src/utils/get-open-auth-token-urls.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAA0B,MAAM,uBAAuB,CAAA;AAG5E;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,MAAM,EACX,EAAE,qBAA6B,EAAE,GAAE;IAAE,qBAAqB,CAAC,EAAE,OAAO,CAAA;CAAO,GAC1E,MAAM,CAYR;AAiBD,wBAAgB,oBAAoB,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,QAAQ,GAAG,MAAM,EAAE,CAuCxE"}
1
+ {"version":3,"file":"get-open-auth-token-urls.d.ts","sourceRoot":"","sources":["../../src/utils/get-open-auth-token-urls.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAuC,MAAM,uBAAuB,CAAA;AAGzF;;;GAGG;AACH,wBAAgB,cAAc,CAC5B,GAAG,EAAE,MAAM,EACX,EAAE,qBAA6B,EAAE,GAAE;IAAE,qBAAqB,CAAC,EAAE,OAAO,CAAA;CAAO,GAC1E,MAAM,CAYR;AAiBD,wBAAgB,oBAAoB,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,QAAQ,GAAG,MAAM,EAAE,CAyCxE"}