@scalar/hono-api-reference 0.11.14 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -4,5 +4,6 @@ export { Scalar,
4
4
  * @deprecated Use `Scalar` instead.
5
5
  */
6
6
  Scalar as apiReference, };
7
+ export type { ServeConfiguration } from './scalar.js';
7
8
  export type { ApiReferenceConfiguration } from './types.js';
8
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AAEjC,OAAO,EACL,MAAM;AACN;;GAEG;AACH,MAAM,IAAI,YAAY,GACvB,CAAA;AAED,YAAY,EAAE,yBAAyB,EAAE,MAAM,SAAS,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,UAAU,CAAA;AAEjC,OAAO,EACL,MAAM;AACN;;GAEG;AACH,MAAM,IAAI,YAAY,GACvB,CAAA;AAED,YAAY,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAA;AAClD,YAAY,EAAE,yBAAyB,EAAE,MAAM,SAAS,CAAA"}
package/dist/scalar.d.ts CHANGED
@@ -1,9 +1,53 @@
1
1
  import type { Context, Env, MiddlewareHandler } from 'hono';
2
+ import { Hono } from 'hono';
2
3
  import type { ApiReferenceConfiguration } from './types.js';
3
4
  type Configuration<E extends Env> = Partial<ApiReferenceConfiguration> | ((c: Context<E>) => Partial<ApiReferenceConfiguration> | Promise<Partial<ApiReferenceConfiguration>>);
4
5
  /**
5
- * The Hono middleware for the Scalar API Reference.
6
+ * An OpenAPI document.
7
+ *
8
+ * Typed loosely as an object on purpose, so it accepts both plain document objects and the typed
9
+ * return values of generators like Zod OpenAPI Hono's `getOpenAPI31Document()`, whose interfaces do
10
+ * not carry an index signature.
6
11
  */
7
- export declare const Scalar: <E extends Env>(configOrResolver: Configuration<E>) => MiddlewareHandler<E>;
12
+ type OpenApiDocument = object;
13
+ /**
14
+ * The configuration for `Scalar.serve`.
15
+ *
16
+ * On top of the universal API Reference configuration, this takes the OpenAPI `document` to render
17
+ * and expose. Because `Scalar.serve` sources the document itself, `content` and `url` are managed for
18
+ * you and are not accepted here.
19
+ */
20
+ export type ServeConfiguration<E extends Env> = Omit<Partial<ApiReferenceConfiguration>, 'content' | 'url'> & {
21
+ /**
22
+ * The OpenAPI document to render and serve.
23
+ *
24
+ * Pass the document directly, or a function that returns it. The function receives the Hono
25
+ * `Context`, so it can read `c.env` or `c.req` and build the document per request. This is also how
26
+ * you wire up an `OpenAPIHono` app:
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * app.route('/scalar', Scalar.serve({
31
+ * document: () => app.getOpenAPI31Document({ openapi: '3.1.0', info: { title: 'Example', version: 'v1' } }),
32
+ * }))
33
+ * ```
34
+ */
35
+ document: OpenApiDocument | ((c: Context<E>) => OpenApiDocument | Promise<OpenApiDocument>);
36
+ /**
37
+ * Path, relative to the mount point, where the OpenAPI document is served as JSON.
38
+ *
39
+ * @default '/openapi.json'
40
+ */
41
+ documentPath?: string;
42
+ };
43
+ /**
44
+ * Render the Scalar API Reference in a Hono app.
45
+ *
46
+ * Use `Scalar(...)` as a middleware to render the reference on a route, or `Scalar.serve(...)` to
47
+ * serve the reference and its OpenAPI document together from a single mount.
48
+ */
49
+ export declare const Scalar: (<E extends Env>(configOrResolver: Configuration<E>) => MiddlewareHandler<E>) & {
50
+ serve: <E extends Env>(options: ServeConfiguration<E>) => Hono<E>;
51
+ };
8
52
  export {};
9
53
  //# sourceMappingURL=scalar.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"scalar.d.ts","sourceRoot":"","sources":["../src/scalar.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAA;AAE3D,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,SAAS,CAAA;AAgExD,KAAK,aAAa,CAAC,CAAC,SAAS,GAAG,IAC5B,OAAO,CAAC,yBAAyB,CAAC,GAClC,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,yBAAyB,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,yBAAyB,CAAC,CAAC,CAAC,CAAA;AAEzG;;GAEG;AACH,eAAO,MAAM,MAAM,GAAI,CAAC,SAAS,GAAG,EAAE,kBAAkB,aAAa,CAAC,CAAC,CAAC,KAAG,iBAAiB,CAAC,CAAC,CAoB7F,CAAA"}
1
+ {"version":3,"file":"scalar.d.ts","sourceRoot":"","sources":["../src/scalar.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAA;AAC3D,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAE3B,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,SAAS,CAAA;AAgExD,KAAK,aAAa,CAAC,CAAC,SAAS,GAAG,IAC5B,OAAO,CAAC,yBAAyB,CAAC,GAClC,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,KAAK,OAAO,CAAC,yBAAyB,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,yBAAyB,CAAC,CAAC,CAAC,CAAA;AA2BzG;;;;;;GAMG;AACH,KAAK,eAAe,GAAG,MAAM,CAAA;AAE7B;;;;;;GAMG;AACH,MAAM,MAAM,kBAAkB,CAAC,CAAC,SAAS,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,yBAAyB,CAAC,EAAE,SAAS,GAAG,KAAK,CAAC,GAAG;IAC5G;;;;;;;;;;;;;OAaG;IACH,QAAQ,EAAE,eAAe,GAAG,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,KAAK,eAAe,GAAG,OAAO,CAAC,eAAe,CAAC,CAAC,CAAA;IAC3F;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB,CAAA;AAgDD;;;;;GAKG;AACH,eAAO,MAAM,MAAM,IAlHO,CAAC,SAAS,GAAG,oBAAoB,aAAa,CAAC,CAAC,CAAC,KAAG,iBAAiB,CAAC,CAAC,CAAC;YA+E7E,CAAC,SAAS,GAAG,WAAW,kBAAkB,CAAC,CAAC,CAAC,KAAG,IAAI,CAAC,CAAC,CAAC;CAmCC,CAAA"}
package/dist/scalar.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { renderApiReference } from '@scalar/client-side-rendering';
2
+ import { Hono } from 'hono';
2
3
  /**
3
4
  * The default configuration for the API Reference.
4
5
  */
@@ -62,7 +63,7 @@ const customTheme = `
62
63
  /**
63
64
  * The Hono middleware for the Scalar API Reference.
64
65
  */
65
- export const Scalar = (configOrResolver) => {
66
+ const scalarMiddleware = (configOrResolver) => {
66
67
  return async (c) => {
67
68
  let resolvedConfig = {};
68
69
  if (typeof configOrResolver === 'function') {
@@ -81,3 +82,47 @@ export const Scalar = (configOrResolver) => {
81
82
  return c.html(renderApiReference({ config, pageTitle, cdn, nonce }, customTheme));
82
83
  };
83
84
  };
85
+ /**
86
+ * Serve the API Reference and its OpenAPI document together as a mountable Hono app.
87
+ *
88
+ * Unlike the `Scalar` middleware — which only renders the reference and expects the document to live
89
+ * somewhere else — this exposes both from a single call, so you do not have to wire up a separate
90
+ * document route and keep its `url` in sync.
91
+ *
92
+ * Mount it with `app.route(...)`:
93
+ *
94
+ * @example
95
+ * ```ts
96
+ * import { Scalar } from '@scalar/hono-api-reference'
97
+ *
98
+ * // Serves the reference at `/scalar` and the document at `/scalar/openapi.json`
99
+ * app.route('/scalar', Scalar.serve({ document: myOpenApiDocument }))
100
+ * ```
101
+ */
102
+ const scalarServe = (options) => {
103
+ const { document, documentPath = '/openapi.json', ...rest } = options;
104
+ const app = new Hono();
105
+ const resolveDocument = (c) => typeof document === 'function' ? document(c) : document;
106
+ // Expose the OpenAPI document as JSON, so a single call powers both the reference and its source.
107
+ app.get(documentPath, async (c) => c.json(await resolveDocument(c)));
108
+ // Render the reference, pointing it at the document we expose above.
109
+ app.get('/', (c) => {
110
+ // Build a root-absolute URL to the document from the current request path. This keeps the
111
+ // reference working wherever it is mounted, and whether or not the request has a trailing slash.
112
+ const url = `${c.req.path.replace(/\/+$/, '')}${documentPath}`;
113
+ const { cdn, pageTitle, nonce, ...config } = {
114
+ ...DEFAULT_CONFIGURATION,
115
+ ...rest,
116
+ url,
117
+ };
118
+ return c.html(renderApiReference({ config, pageTitle, cdn, nonce }, customTheme));
119
+ });
120
+ return app;
121
+ };
122
+ /**
123
+ * Render the Scalar API Reference in a Hono app.
124
+ *
125
+ * Use `Scalar(...)` as a middleware to render the reference on a route, or `Scalar.serve(...)` to
126
+ * serve the reference and its OpenAPI document together from a single mount.
127
+ */
128
+ export const Scalar = Object.assign(scalarMiddleware, { serve: scalarServe });
@@ -206,4 +206,72 @@ describe('Scalar', () => {
206
206
  expect(text).toContain('Test API');
207
207
  expect(text).toContain('deepSpace');
208
208
  });
209
+ describe('serve', () => {
210
+ const document = {
211
+ openapi: '3.1.0',
212
+ info: { title: 'Serve API', version: '1.0.0' },
213
+ paths: {},
214
+ };
215
+ it('serves the OpenAPI document as JSON at the mount point', async () => {
216
+ const app = new Hono();
217
+ app.route('/scalar', Scalar.serve({ document }));
218
+ const response = await app.request('/scalar/openapi.json');
219
+ expect(response.status).toBe(200);
220
+ expect(response.headers.get('content-type')).toContain('application/json');
221
+ expect(await response.json()).toEqual(document);
222
+ });
223
+ it('renders the reference at the mount point, pointing at the served document', async () => {
224
+ const app = new Hono();
225
+ app.route('/scalar', Scalar.serve({ document }));
226
+ const response = await app.request('/scalar');
227
+ expect(response.status).toBe(200);
228
+ expect(response.headers.get('content-type')).toContain('text/html');
229
+ const text = await response.text();
230
+ expect(text).toContain('<title>Scalar API Reference</title>');
231
+ // The reference points at the document we serve alongside it
232
+ expect(text).toContain('/scalar/openapi.json');
233
+ // The document is referenced by URL, not inlined
234
+ expect(text).not.toContain('Serve API');
235
+ });
236
+ it('serves the document at a custom documentPath', async () => {
237
+ const app = new Hono();
238
+ app.route('/scalar', Scalar.serve({ document, documentPath: '/spec.json' }));
239
+ const json = await app.request('/scalar/spec.json');
240
+ expect(json.status).toBe(200);
241
+ expect(await json.json()).toEqual(document);
242
+ const html = await (await app.request('/scalar')).text();
243
+ expect(html).toContain('/scalar/spec.json');
244
+ });
245
+ it('resolves the document from a function with access to the context', async () => {
246
+ const app = new Hono();
247
+ app.route('/scalar', Scalar.serve({
248
+ document: (c) => ({ ...document, info: { ...document.info, title: c.req.path } }),
249
+ }));
250
+ const response = await app.request('/scalar/openapi.json');
251
+ expect(await response.json()).toMatchObject({ info: { title: '/scalar/openapi.json' } });
252
+ });
253
+ it('passes through reference options like pageTitle and theme', async () => {
254
+ const app = new Hono();
255
+ app.route('/scalar', Scalar.serve({ document, pageTitle: 'Serve Title', theme: 'kepler' }));
256
+ const text = await (await app.request('/scalar')).text();
257
+ expect(text).toContain('<title>Serve Title</title>');
258
+ // A theme was provided, so the custom Hono theme CSS is not injected
259
+ expect(text).not.toContain('--scalar-color-1: rgba(255, 255, 245, .86);');
260
+ });
261
+ it('includes the hono integration marker and the custom theme by default', async () => {
262
+ const app = new Hono();
263
+ app.route('/scalar', Scalar.serve({ document }));
264
+ const text = await (await app.request('/scalar')).text();
265
+ expect(text).toContain('_integration": "hono"');
266
+ expect(text).toContain('--scalar-color-1: rgba(255, 255, 245, .86);');
267
+ });
268
+ it('works when mounted at the root', async () => {
269
+ const app = new Hono();
270
+ app.route('/', Scalar.serve({ document }));
271
+ const html = await (await app.request('/')).text();
272
+ expect(html).toContain('/openapi.json');
273
+ const json = await app.request('/openapi.json');
274
+ expect(await json.json()).toEqual(document);
275
+ });
276
+ });
209
277
  });
package/package.json CHANGED
@@ -10,7 +10,7 @@
10
10
  "url": "git+https://github.com/scalar/scalar.git",
11
11
  "directory": "integrations/hono"
12
12
  },
13
- "version": "0.11.14",
13
+ "version": "0.12.0",
14
14
  "engines": {
15
15
  "node": ">=22"
16
16
  },
@@ -44,7 +44,7 @@
44
44
  "documentation": "https://scalar.com/products/api-references/integrations/hono"
45
45
  },
46
46
  "dependencies": {
47
- "@scalar/client-side-rendering": "0.3.7"
47
+ "@scalar/client-side-rendering": "0.3.10"
48
48
  },
49
49
  "devDependencies": {
50
50
  "@hono/node-server": "^1.19.10",
@@ -52,7 +52,7 @@
52
52
  "hono": "^4.12.7",
53
53
  "vite": "8.1.5",
54
54
  "vitest": "4.1.10",
55
- "@scalar/openapi-to-markdown": "0.5.39"
55
+ "@scalar/openapi-to-markdown": "0.5.43"
56
56
  },
57
57
  "peerDependencies": {
58
58
  "hono": "^4.12.5"