@kizunajs/openapi 2.0.0-beta.10 → 2.0.0-beta.11

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.mts CHANGED
@@ -1,67 +1,9 @@
1
1
  import { z } from "zod";
2
- import { RoutePath, WithSlug } from "kizunajs/plugin";
2
+ import { RoutePath } from "kizunajs/plugin";
3
3
  import { RouteDefinition } from "kizunajs/generator";
4
4
  import * as _$kizunajs from "kizunajs";
5
- import { ApiDefinition, ClientTarget } from "kizunajs";
5
+ import { ApiDefinition } from "kizunajs";
6
6
 
7
- //#region src/docs-html.d.ts
8
- /**
9
- * The API reference UI rendered by the docs endpoint.
10
- */
11
- type DocsProvider = 'scalar' | 'swagger';
12
- interface DocsHtmlOptions {
13
- /**
14
- * URL the UI fetches the OpenAPI document from. Omit it and pass
15
- * {@link DocsHtmlOptions.specContent} instead.
16
- */
17
- specUrl?: string;
18
- /**
19
- * The document itself, embedded in the page rather than fetched.
20
- */
21
- specContent?: unknown;
22
- /**
23
- * Which API reference UI to render.
24
- *
25
- * @default 'scalar'
26
- */
27
- provider?: DocsProvider;
28
- /**
29
- * The page's `<title>`.
30
- *
31
- * @default 'API Reference'
32
- */
33
- pageTitle?: string;
34
- /**
35
- * Where to load the UI's assets from. Point this at a self-hosted copy for
36
- * air-gapped deployments or a strict CSP.
37
- *
38
- * For `'scalar'` this is the script URL. For `'swagger'` it is the
39
- * directory holding `swagger-ui.css` and `swagger-ui-bundle.js`.
40
- *
41
- * @default 'https://cdn.jsdelivr.net/npm/@scalar/api-reference@1' | 'https://cdn.jsdelivr.net/npm/swagger-ui-dist@5'
42
- */
43
- cdnUrl?: string;
44
- /**
45
- * Merged into `Scalar.createApiReference` or `SwaggerUIBundle`. See the
46
- * provider's own documentation for the available keys.
47
- */
48
- configuration?: Record<string, unknown>;
49
- }
50
- /**
51
- * Render a self-contained page that loads an API reference UI over your OpenAPI
52
- * document. `openApiPlugin` calls this, so reach for it directly only when
53
- * kizuna has no adapter for your framework.
54
- *
55
- * @example
56
- * ```ts
57
- * const html = renderDocsHtml({
58
- * specUrl: '/openapi.json',
59
- * pageTitle: 'My API',
60
- * });
61
- * ```
62
- */
63
- declare const renderDocsHtml: (options: DocsHtmlOptions) => string;
64
- //#endregion
65
7
  //#region src/types.d.ts
66
8
  /**
67
9
  * The OpenAPI Specification version declared in the document's `openapi` field.
@@ -145,126 +87,214 @@ interface OpenApiDocument {
145
87
  schemas?: Record<string, unknown>;
146
88
  };
147
89
  }
148
- interface GenerateOpenApiOptions {
149
- /**
150
- * The OpenAPI Specification version to declare in the document's `openapi` field.
151
- *
152
- * Defaults to `'3.1.0'`.
153
- */
154
- openApiVersion?: OpenApiVersion;
155
- info: OpenApiInfo;
156
- servers?: OpenApiServer[];
157
- externalDocs?: OpenApiExternalDocs;
158
- setOperationId?: boolean | 'concatenated-path';
159
- operationMapper?: (operation: OpenApiOperation, route: RouteDefinition, operationId: string) => OpenApiOperation;
160
- /**
161
- * Document the derived `head` operation on every `GET` path without a declared one.
162
- *
163
- * @default false
164
- */
165
- derivedHead?: boolean;
166
- }
90
+ declare const GenerateOpenApiOptionsSchema: z.ZodObject<{
91
+ openApiVersion: z.ZodOptional<z.ZodLiteral<"3.1.0">>;
92
+ info: z.ZodObject<{
93
+ title: z.ZodString;
94
+ version: z.ZodString;
95
+ description: z.ZodOptional<z.ZodString>;
96
+ }, z.core.$strip>;
97
+ servers: z.ZodOptional<z.ZodArray<z.ZodObject<{
98
+ url: z.ZodString;
99
+ description: z.ZodOptional<z.ZodString>;
100
+ }, z.core.$strip>>>;
101
+ externalDocs: z.ZodOptional<z.ZodObject<{
102
+ url: z.ZodString;
103
+ description: z.ZodOptional<z.ZodString>;
104
+ }, z.core.$strip>>;
105
+ setOperationId: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodLiteral<"concatenated-path">]>>;
106
+ includeHidden: z.ZodOptional<z.ZodBoolean>;
107
+ operationMapper: z.ZodOptional<z.ZodCustom<(operation: OpenApiOperation, route: RouteDefinition, operationId: string) => OpenApiOperation, (operation: OpenApiOperation, route: RouteDefinition, operationId: string) => OpenApiOperation>>;
108
+ derivedHead: z.ZodOptional<z.ZodBoolean>;
109
+ }, z.core.$strip>;
110
+ type GenerateOpenApiOptions = z.output<typeof GenerateOpenApiOptionsSchema>;
167
111
  interface OpenApiRenderer {
168
112
  (format: 'json'): OpenApiDocument;
169
113
  (format: 'yaml'): string;
170
114
  }
171
115
  //#endregion
172
116
  //#region src/plugin.d.ts
173
- declare const OPENAPI_PLUGIN_SLUG = "openApi";
174
117
  type JsonDocumentPath = `${RoutePath}.json`;
175
118
  type YamlDocumentPath = `${RoutePath}.yaml`;
176
- interface OpenApiPluginProps<Slug extends string = typeof OPENAPI_PLUGIN_SLUG> extends GenerateOpenApiOptions {
177
- /**
178
- * What handlers reach this plugin under. Give a second document its own.
179
- *
180
- * @default 'openApi'
181
- */
182
- slug?: Slug;
183
- /**
184
- * Where the reference UI is served.
185
- */
186
- docsPath?: RoutePath;
119
+ declare const OpenApiPluginOptionsSchema: z.ZodObject<{
120
+ openApiVersion: z.ZodOptional<z.ZodLiteral<"3.1.0">>;
121
+ info: z.ZodObject<{
122
+ title: z.ZodString;
123
+ version: z.ZodString;
124
+ description: z.ZodOptional<z.ZodString>;
125
+ }, z.core.$strip>;
126
+ servers: z.ZodOptional<z.ZodArray<z.ZodObject<{
127
+ url: z.ZodString;
128
+ description: z.ZodOptional<z.ZodString>;
129
+ }, z.core.$strip>>>;
130
+ externalDocs: z.ZodOptional<z.ZodObject<{
131
+ url: z.ZodString;
132
+ description: z.ZodOptional<z.ZodString>;
133
+ }, z.core.$strip>>;
134
+ setOperationId: z.ZodOptional<z.ZodUnion<readonly [z.ZodBoolean, z.ZodLiteral<"concatenated-path">]>>;
135
+ includeHidden: z.ZodOptional<z.ZodBoolean>;
136
+ operationMapper: z.ZodOptional<z.ZodCustom<(operation: OpenApiOperation, route: _$kizunajs.RouteDefinition, operationId: string) => OpenApiOperation, (operation: OpenApiOperation, route: _$kizunajs.RouteDefinition, operationId: string) => OpenApiOperation>>;
137
+ derivedHead: z.ZodOptional<z.ZodBoolean>;
138
+ output: z.ZodOptional<z.ZodString>;
139
+ docsPath: z.ZodOptional<z.ZodCustom<`/${string}`, `/${string}`>>;
140
+ jsonPath: z.ZodOptional<z.ZodCustom<`/${string}.json`, `/${string}.json`>>;
141
+ yamlPath: z.ZodOptional<z.ZodCustom<`/${string}.yaml`, `/${string}.yaml`>>;
142
+ provider: z.ZodOptional<z.ZodEnum<{
143
+ scalar: "scalar";
144
+ swagger: "swagger";
145
+ }>>;
146
+ cdnUrl: z.ZodOptional<z.ZodString>;
147
+ pageTitle: z.ZodOptional<z.ZodString>;
148
+ configuration: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
149
+ }, z.core.$strip>;
150
+ type OpenApiPluginProps = z.output<typeof OpenApiPluginOptionsSchema>;
151
+ /**
152
+ * Serve an API reference UI for the OpenAPI document, and the document itself
153
+ * if you publish it.
154
+ *
155
+ * The routes are public. Gate them with your framework's own middleware if that
156
+ * is not what you want.
157
+ *
158
+ * @example
159
+ * ```ts
160
+ * export default defineConfig({
161
+ * routes,
162
+ * plugins: [
163
+ * openApiPlugin({
164
+ * info: {
165
+ * title: 'My API',
166
+ * version: '1.0.0',
167
+ * },
168
+ * docsPath: '/docs',
169
+ * }),
170
+ * ],
171
+ * });
172
+ * ```
173
+ */
174
+ declare const openApiPlugin: {
175
+ <const Slug extends string>(options: {
176
+ info: {
177
+ title: string;
178
+ version: string;
179
+ description?: string | undefined;
180
+ };
181
+ openApiVersion?: "3.1.0" | undefined;
182
+ servers?: {
183
+ url: string;
184
+ description?: string | undefined;
185
+ }[] | undefined;
186
+ externalDocs?: {
187
+ url: string;
188
+ description?: string | undefined;
189
+ } | undefined;
190
+ setOperationId?: boolean | "concatenated-path" | undefined;
191
+ includeHidden?: boolean | undefined;
192
+ operationMapper?: ((operation: OpenApiOperation, route: _$kizunajs.RouteDefinition, operationId: string) => OpenApiOperation) | undefined;
193
+ derivedHead?: boolean | undefined;
194
+ output?: string | undefined;
195
+ docsPath?: `/${string}` | undefined;
196
+ jsonPath?: `/${string}.json` | undefined;
197
+ yamlPath?: `/${string}.yaml` | undefined;
198
+ provider?: "scalar" | "swagger" | undefined;
199
+ cdnUrl?: string | undefined;
200
+ pageTitle?: string | undefined;
201
+ configuration?: Record<string, unknown> | undefined;
202
+ } & {
203
+ slug: Slug;
204
+ }): _$kizunajs.PluginDeclaration<Slug, {
205
+ routes: _$kizunajs.PluginRoutes;
206
+ generators: _$kizunajs.GeneratedFile[];
207
+ }>;
208
+ (options: {
209
+ info: {
210
+ title: string;
211
+ version: string;
212
+ description?: string | undefined;
213
+ };
214
+ openApiVersion?: "3.1.0" | undefined;
215
+ servers?: {
216
+ url: string;
217
+ description?: string | undefined;
218
+ }[] | undefined;
219
+ externalDocs?: {
220
+ url: string;
221
+ description?: string | undefined;
222
+ } | undefined;
223
+ setOperationId?: boolean | "concatenated-path" | undefined;
224
+ includeHidden?: boolean | undefined;
225
+ operationMapper?: ((operation: OpenApiOperation, route: _$kizunajs.RouteDefinition, operationId: string) => OpenApiOperation) | undefined;
226
+ derivedHead?: boolean | undefined;
227
+ output?: string | undefined;
228
+ docsPath?: `/${string}` | undefined;
229
+ jsonPath?: `/${string}.json` | undefined;
230
+ yamlPath?: `/${string}.yaml` | undefined;
231
+ provider?: "scalar" | "swagger" | undefined;
232
+ cdnUrl?: string | undefined;
233
+ pageTitle?: string | undefined;
234
+ configuration?: Record<string, unknown> | undefined;
235
+ }): _$kizunajs.PluginDeclaration<"openApi", {
236
+ routes: _$kizunajs.PluginRoutes;
237
+ generators: _$kizunajs.GeneratedFile[];
238
+ }>;
239
+ };
240
+ //#endregion
241
+ //#region src/docs-html.d.ts
242
+ /**
243
+ * The API reference UI rendered by the docs endpoint.
244
+ */
245
+ type DocsProvider = 'scalar' | 'swagger';
246
+ interface DocsHtmlOptions {
187
247
  /**
188
- * Where the document is served. The UI embeds it, so this is only for
189
- * publishing the file.
248
+ * URL the UI fetches the OpenAPI document from. Omit it and pass
249
+ * {@link DocsHtmlOptions.specContent} instead.
190
250
  */
191
- jsonPath?: JsonDocumentPath;
251
+ specUrl?: string;
192
252
  /**
193
- * Where the document is served as YAML.
253
+ * The document itself, embedded in the page rather than fetched.
194
254
  */
195
- yamlPath?: YamlDocumentPath;
255
+ specContent?: unknown;
196
256
  /**
197
257
  * Which API reference UI to render.
198
258
  *
199
259
  * @default 'scalar'
200
260
  */
201
261
  provider?: DocsProvider;
202
- /**
203
- * Where to load the UI's assets from, for a self-hosted copy. The script
204
- * URL for `'scalar'`; the directory holding `swagger-ui.css` and
205
- * `swagger-ui-bundle.js` for `'swagger'`.
206
- */
207
- cdnUrl?: string;
208
262
  /**
209
263
  * The page's `<title>`.
210
264
  *
211
- * @default the document's `info.title`
265
+ * @default 'API Reference'
212
266
  */
213
267
  pageTitle?: string;
214
268
  /**
215
- * Merged into `Scalar.createApiReference` or `SwaggerUIBundle`.
269
+ * Where to load the UI's assets from. Point this at a self-hosted copy for
270
+ * air-gapped deployments or a strict CSP.
271
+ *
272
+ * For `'scalar'` this is the script URL. For `'swagger'` it is the
273
+ * directory holding `swagger-ui.css` and `swagger-ui-bundle.js`.
274
+ *
275
+ * @default 'https://cdn.jsdelivr.net/npm/@scalar/api-reference@1' | 'https://cdn.jsdelivr.net/npm/swagger-ui-dist@5'
276
+ */
277
+ cdnUrl?: string;
278
+ /**
279
+ * Merged into `Scalar.createApiReference` or `SwaggerUIBundle`. See the
280
+ * provider's own documentation for the available keys.
216
281
  */
217
282
  configuration?: Record<string, unknown>;
218
283
  }
219
284
  /**
220
- * Serve an API reference UI for the OpenAPI document, and the document itself
221
- * if you publish it.
222
- *
223
- * The routes are public. Gate them with your framework's own middleware if that
224
- * is not what you want.
285
+ * Render a self-contained page that loads an API reference UI over your OpenAPI
286
+ * document. `openApiPlugin` calls this, so reach for it directly only when
287
+ * kizuna has no adapter for your framework.
225
288
  *
226
289
  * @example
227
290
  * ```ts
228
- * export default defineConfig({
229
- * routes,
230
- * plugins: [
231
- * openApiPlugin({
232
- * info: {
233
- * title: 'My API',
234
- * version: '1.0.0',
235
- * },
236
- * }),
237
- * ],
291
+ * const html = renderDocsHtml({
292
+ * specUrl: '/openapi.json',
293
+ * pageTitle: 'My API',
238
294
  * });
239
295
  * ```
240
296
  */
241
- declare const declare: (slug: string, props: OpenApiPluginProps<string>) => _$kizunajs.PluginDeclaration<{
242
- readonly yaml?: {
243
- readonly method: "GET";
244
- readonly path: `/${string}.yaml`;
245
- readonly summary: "OpenAPI document, as YAML";
246
- readonly responses: {
247
- readonly 200: z.ZodString;
248
- };
249
- } | undefined;
250
- readonly json?: {
251
- readonly method: "GET";
252
- readonly path: `/${string}.json`;
253
- readonly summary: "OpenAPI document";
254
- readonly responses: {
255
- readonly 200: z.ZodUnknown;
256
- };
257
- } | undefined;
258
- readonly page?: {
259
- readonly method: "GET";
260
- readonly path: `/${string}`;
261
- readonly summary: "API reference";
262
- readonly responses: {
263
- readonly 200: z.ZodString;
264
- };
265
- } | undefined;
266
- }, OpenApiPluginProps<string>, undefined, string, unknown>;
267
- declare function openApiPlugin<const Slug extends string = typeof OPENAPI_PLUGIN_SLUG>(props: OpenApiPluginProps<Slug>): WithSlug<ReturnType<typeof declare>, Slug>;
297
+ declare const renderDocsHtml: (options: DocsHtmlOptions) => string;
268
298
  //#endregion
269
299
  //#region src/generator.d.ts
270
300
  /**
@@ -281,26 +311,4 @@ declare function renderOpenApi(api: ApiDefinition, options: GenerateOpenApiOptio
281
311
  */
282
312
  declare function generateOpenApi(api: ApiDefinition, overrides?: Partial<GenerateOpenApiOptions>): OpenApiRenderer;
283
313
  //#endregion
284
- //#region src/document-target.d.ts
285
- /**
286
- * Where the generated OpenAPI document is written. The format follows the
287
- * output extension: `.json` renders JSON, anything else renders YAML.
288
- */
289
- interface OpenApiDocumentOptions extends Partial<GenerateOpenApiOptions> {
290
- /**
291
- * Path the generated document is written to.
292
- */
293
- output: string;
294
- }
295
- /**
296
- * An OpenAPI document target for `kizuna.config.ts`.
297
- *
298
- * @example
299
- * openApiDocument({ output: './openapi.yaml' });
300
- */
301
- declare const openApiDocument: ({
302
- output,
303
- ...overrides
304
- }: OpenApiDocumentOptions) => ClientTarget;
305
- //#endregion
306
- export { type DocsHtmlOptions, type DocsProvider, type GenerateOpenApiOptions, type JsonDocumentPath, type OpenApiDocument, type OpenApiDocumentOptions, type OpenApiInfo, type OpenApiOperation, type OpenApiParameter, type OpenApiPluginProps, type OpenApiRenderer, type OpenApiResponseObject, type OpenApiServer, type OpenApiTag, type OpenApiVersion, type YamlDocumentPath, generateOpenApi, openApiDocument, openApiPlugin, renderDocsHtml, renderOpenApi };
314
+ export { type DocsHtmlOptions, type DocsProvider, type GenerateOpenApiOptions, type JsonDocumentPath, type OpenApiDocument, type OpenApiInfo, type OpenApiOperation, type OpenApiParameter, type OpenApiPluginProps, type OpenApiRenderer, type OpenApiResponseObject, type OpenApiServer, type OpenApiTag, type OpenApiVersion, type YamlDocumentPath, generateOpenApi, openApiPlugin, renderDocsHtml, renderOpenApi };
package/dist/index.mjs CHANGED
@@ -1,8 +1,8 @@
1
1
  import { z } from "zod";
2
- import { createPlugin } from "kizunajs/plugin";
2
+ import { RoutePathSchema, definePlugin, route } from "kizunajs/plugin";
3
+ import { cacheHeaders, defineGenerator, deprecationHeaders, globalRegistrySchemas, isBinarySchema, isFileSchema, isNamedStream, isStreamResponse, isVoidSchema, parsePath, readDef, readDiscriminatedUnion, readDiscriminatorLiteral, readMeta, readMetaId, readObjectShape, resolveResponseBody, resolveResponseCache, resolveResponseContentType, resolveResponseEtag, resolveResponseHeaders, streamContentType, streamMode, walkApi } from "kizunajs/generator";
3
4
  import { contractOf, rawResponse } from "kizunajs/adapter";
4
5
  import { stringify } from "yaml";
5
- import { cacheHeaders, createGenerator, deprecationHeaders, globalRegistrySchemas, isBinarySchema, isFileSchema, isNamedStream, isStreamResponse, isVoidSchema, parsePath, readDef, readDiscriminatedUnion, readDiscriminatorLiteral, readMeta, readMetaId, readObjectShape, resolveResponseBody, resolveResponseCache, resolveResponseContentType, resolveResponseEtag, resolveResponseHeaders, streamContentType, streamMode } from "kizunajs/generator";
6
6
  import { getStatusText } from "kizunajs";
7
7
  //#region src/generator.ts
8
8
  const streamJsonSchema = (response, toJsonSchema) => {
@@ -161,10 +161,11 @@ const deriveHeadOperation = (getOperation) => {
161
161
  for (const response of Object.values(operation.responses)) delete response.content;
162
162
  return operation;
163
163
  };
164
- const openApiGenerator = createGenerator((options, api) => {
164
+ const openApiWalk = (options, api) => {
165
165
  const paths = {};
166
166
  return {
167
- processRoute({ routeKey, route, routeTags, deprecated }) {
167
+ processRoute({ routeKey, route, routeTags, deprecated, hidden }) {
168
+ if (hidden && options.includeHidden !== true) return;
168
169
  const openApiPath = convertPath(route.path);
169
170
  const method = route.method.toLowerCase();
170
171
  let operation = {
@@ -378,7 +379,7 @@ const openApiGenerator = createGenerator((options, api) => {
378
379
  return renderer;
379
380
  }
380
381
  };
381
- });
382
+ };
382
383
  /**
383
384
  * The permissions a route requires, as `x-kizuna-requires`: what is acted on,
384
385
  * mapped to the verbs the caller has to hold.
@@ -458,17 +459,17 @@ const tagsFromApi = (api) => {
458
459
  * So a build step does not restate the options and drift from what is served.
459
460
  */
460
461
  const optionsFromInstalledPlugin = (api) => {
461
- for (const declaration of Object.values(api.plugins ?? {})) if (declaration.slug === "openApi") return declaration.props;
462
+ for (const plugin of Object.values(api.plugins ?? {})) if (plugin.definedSlug === "openApi") return plugin.options;
462
463
  throw new Error("generateOpenApi reads its options from the config. Name `openApiPlugin` under `plugins` with the API's `info`.");
463
464
  };
464
465
  /**
465
466
  * Render from options held directly, for the plugin's own routes.
466
467
  */
467
468
  function renderOpenApi(api, options) {
468
- const renderer = openApiGenerator(api, {
469
+ const renderer = walkApi(api, openApiWalk({
469
470
  ...options,
470
471
  tagLookup: buildTagLookup(api)
471
- });
472
+ }, api));
472
473
  const tags = tagsFromApi(api);
473
474
  if (tags.length > 0) renderer("json").tags = tags;
474
475
  return renderer;
@@ -560,22 +561,6 @@ const renderDocsHtml = (options) => {
560
561
  });
561
562
  };
562
563
  //#endregion
563
- //#region src/document-target.ts
564
- /**
565
- * An OpenAPI document target for `kizuna.config.ts`.
566
- *
567
- * @example
568
- * openApiDocument({ output: './openapi.yaml' });
569
- */
570
- const openApiDocument = ({ output, ...overrides }) => ({
571
- kind: "openapi",
572
- output,
573
- generate: (contract) => {
574
- const render = generateOpenApi(contract, overrides);
575
- return output.endsWith(".json") ? `${JSON.stringify(render("json"), null, 4)}\n` : render("yaml");
576
- }
577
- });
578
- //#endregion
579
564
  //#region src/server.ts
580
565
  const HTML = "text/html; charset=utf-8";
581
566
  const JSON_TYPE = "application/json";
@@ -585,23 +570,43 @@ const sent = (body, contentType) => new Response(body, {
585
570
  headers: { "content-type": contentType }
586
571
  });
587
572
  /**
588
- * What answers the routes `openApiPlugin` declares: the reference UI, and the
589
- * document itself where the declaration published it.
573
+ * The routes `openApiPlugin` serves: the reference UI, and the document itself
574
+ * where the options publish it. The document is rendered on the first request,
575
+ * once the api it describes has assembled.
590
576
  */
591
- const openApiServe = (props, api) => {
592
- const spec = renderOpenApi(contractOf(api), props);
593
- return { router: {
594
- page: () => rawResponse(sent(renderDocsHtml({
595
- specUrl: props.jsonPath,
596
- specContent: props.jsonPath === void 0 ? spec("json") : void 0,
597
- provider: props.provider,
598
- pageTitle: props.pageTitle ?? spec("json").info.title,
599
- cdnUrl: props.cdnUrl,
600
- configuration: props.configuration
601
- }), HTML)),
602
- json: () => rawResponse(sent(JSON.stringify(spec("json")), JSON_TYPE)),
603
- yaml: () => rawResponse(sent(spec("yaml"), YAML))
604
- } };
577
+ const openApiRoutes = (props, api) => {
578
+ let renderer;
579
+ const spec = ((format) => {
580
+ renderer ??= renderOpenApi(contractOf(api), props);
581
+ return renderer(format);
582
+ });
583
+ const routes = {};
584
+ if (props.docsPath !== void 0) routes.page = route({
585
+ method: "GET",
586
+ path: props.docsPath,
587
+ summary: "API reference",
588
+ responses: { 200: z.string() }
589
+ }).handler(() => rawResponse(sent(renderDocsHtml({
590
+ specUrl: props.jsonPath,
591
+ specContent: props.jsonPath === void 0 ? spec("json") : void 0,
592
+ provider: props.provider,
593
+ pageTitle: props.pageTitle ?? spec("json").info.title,
594
+ cdnUrl: props.cdnUrl,
595
+ configuration: props.configuration
596
+ }), HTML)));
597
+ if (props.jsonPath !== void 0) routes.json = route({
598
+ method: "GET",
599
+ path: props.jsonPath,
600
+ summary: "OpenAPI document",
601
+ responses: { 200: z.unknown() }
602
+ }).handler(() => rawResponse(sent(JSON.stringify(spec("json")), JSON_TYPE)));
603
+ if (props.yamlPath !== void 0) routes.yaml = route({
604
+ method: "GET",
605
+ path: props.yamlPath,
606
+ summary: "OpenAPI document, as YAML",
607
+ responses: { 200: z.string() }
608
+ }).handler(() => rawResponse(sent(spec("yaml"), YAML)));
609
+ return routes;
605
610
  };
606
611
  /**
607
612
  * Serve an API reference UI for the OpenAPI document, and the document itself
@@ -620,38 +625,117 @@ const openApiServe = (props, api) => {
620
625
  * title: 'My API',
621
626
  * version: '1.0.0',
622
627
  * },
628
+ * docsPath: '/docs',
623
629
  * }),
624
630
  * ],
625
631
  * });
626
632
  * ```
627
633
  */
628
- const declare = (slug, props) => createPlugin({
629
- slug,
630
- routes: {
631
- ...props.docsPath === void 0 ? {} : { page: {
632
- method: "GET",
633
- path: props.docsPath,
634
- summary: "API reference",
635
- responses: { 200: z.string() }
636
- } },
637
- ...props.jsonPath === void 0 ? {} : { json: {
638
- method: "GET",
639
- path: props.jsonPath,
640
- summary: "OpenAPI document",
641
- responses: { 200: z.unknown() }
642
- } },
643
- ...props.yamlPath === void 0 ? {} : { yaml: {
644
- method: "GET",
645
- path: props.yamlPath,
646
- summary: "OpenAPI document, as YAML",
647
- responses: { 200: z.string() }
648
- } }
649
- },
650
- props,
651
- serve: (pluginProps, api) => openApiServe(pluginProps, api)
634
+ const openApiPlugin = definePlugin({
635
+ slug: "openApi",
636
+ options: z.object({
637
+ /**
638
+ * The OpenAPI Specification version to declare in the document's `openapi` field.
639
+ *
640
+ * Defaults to `'3.1.0'`.
641
+ */
642
+ openApiVersion: z.literal("3.1.0").optional(),
643
+ info: z.object({
644
+ title: z.string(),
645
+ version: z.string(),
646
+ description: z.string().optional()
647
+ }),
648
+ servers: z.array(z.object({
649
+ url: z.string(),
650
+ description: z.string().optional()
651
+ })).optional(),
652
+ externalDocs: z.object({
653
+ url: z.string(),
654
+ description: z.string().optional()
655
+ }).optional(),
656
+ setOperationId: z.union([z.boolean(), z.literal("concatenated-path")]).optional(),
657
+ /**
658
+ * Document `hidden` routes too, for a document your own team reads.
659
+ *
660
+ * @default false
661
+ */
662
+ includeHidden: z.boolean().optional(),
663
+ operationMapper: z.custom((value) => typeof value === "function", { error: "must be a function" }).optional(),
664
+ /**
665
+ * Document the derived `head` operation on every `GET` path without a declared one.
666
+ *
667
+ * @default false
668
+ */
669
+ derivedHead: z.boolean().optional()
670
+ }).extend({
671
+ /**
672
+ * Where `kizuna generate` writes the document. `.json` renders JSON,
673
+ * anything else YAML.
674
+ *
675
+ * @example
676
+ * output: './openapi.yaml',
677
+ */
678
+ output: z.string().optional(),
679
+ /**
680
+ * Where the reference UI is served.
681
+ */
682
+ docsPath: RoutePathSchema.optional(),
683
+ /**
684
+ * Where the document is served. The UI embeds it, so this is only for
685
+ * publishing the file.
686
+ */
687
+ jsonPath: z.custom((value) => typeof value === "string" && value.startsWith("/") && value.endsWith(".json"), { error: "must start with / and end in .json" }).optional(),
688
+ /**
689
+ * Where the document is served as YAML.
690
+ */
691
+ yamlPath: z.custom((value) => typeof value === "string" && value.startsWith("/") && value.endsWith(".yaml"), { error: "must start with / and end in .yaml" }).optional(),
692
+ /**
693
+ * Which API reference UI to render.
694
+ *
695
+ * @default 'scalar'
696
+ */
697
+ provider: z.enum(["scalar", "swagger"]).optional(),
698
+ /**
699
+ * Where to load the UI's assets from, for a self-hosted copy. The script
700
+ * URL for `'scalar'`; the directory holding `swagger-ui.css` and
701
+ * `swagger-ui-bundle.js` for `'swagger'`.
702
+ */
703
+ cdnUrl: z.string().optional(),
704
+ /**
705
+ * The page's `<title>`.
706
+ *
707
+ * @default the document's `info.title`
708
+ */
709
+ pageTitle: z.string().optional(),
710
+ /**
711
+ * Merged into `Scalar.createApiReference` or `SwaggerUIBundle`.
712
+ */
713
+ configuration: z.record(z.string(), z.unknown()).optional()
714
+ }),
715
+ setup: ({ options, api }) => {
716
+ const { output, ...document } = options;
717
+ return {
718
+ routes: openApiRoutes(options, api),
719
+ generators: output === void 0 ? [] : [openApiDocumentGenerator({
720
+ output,
721
+ json: output.endsWith(".json"),
722
+ document
723
+ })]
724
+ };
725
+ }
726
+ });
727
+ /**
728
+ * Writes one document, in the format its `output` names.
729
+ */
730
+ const openApiDocumentGenerator = defineGenerator({
731
+ options: z.object({
732
+ json: z.boolean(),
733
+ document: z.custom()
734
+ }),
735
+ generate: ({ options, api }) => ({ finalize: () => {
736
+ const render = renderOpenApi(api, options.document);
737
+ return options.json ? `${JSON.stringify(render("json"), null, 4)}\n` : render("yaml");
738
+ } })
652
739
  });
653
- function openApiPlugin(props) {
654
- return declare(props.slug ?? "openApi", props);
655
- }
656
740
  //#endregion
657
- export { generateOpenApi, openApiDocument, openApiPlugin, renderDocsHtml, renderOpenApi };
741
+ export { generateOpenApi, openApiPlugin, renderDocsHtml, renderOpenApi };