@kizunajs/openapi 2.0.0-beta.1 → 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.cjs +171 -68
- package/dist/index.d.cts +178 -170
- package/dist/index.d.mts +177 -169
- package/dist/index.mjs +166 -69
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
2
|
let zod = require("zod");
|
|
3
3
|
let kizunajs_plugin = require("kizunajs/plugin");
|
|
4
|
+
let kizunajs_generator = require("kizunajs/generator");
|
|
4
5
|
let kizunajs_adapter = require("kizunajs/adapter");
|
|
5
6
|
let yaml = require("yaml");
|
|
6
|
-
let kizunajs_generator = require("kizunajs/generator");
|
|
7
7
|
let kizunajs = require("kizunajs");
|
|
8
8
|
//#region src/generator.ts
|
|
9
9
|
const streamJsonSchema = (response, toJsonSchema) => {
|
|
@@ -80,12 +80,25 @@ const buildDiscriminatorBlock = (propertyName, options) => {
|
|
|
80
80
|
} : { propertyName };
|
|
81
81
|
};
|
|
82
82
|
/**
|
|
83
|
+
* What the wire carries for a type JSON Schema has no keyword for, which
|
|
84
|
+
* `unrepresentable: 'any'` would otherwise leave as an empty schema.
|
|
85
|
+
*/
|
|
86
|
+
const JSON_ENCODING = {
|
|
87
|
+
date: {
|
|
88
|
+
type: "string",
|
|
89
|
+
format: "date-time"
|
|
90
|
+
},
|
|
91
|
+
bigint: { type: "string" }
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
83
94
|
* kizuna's metadata widens JSON Schema: `deprecated` may carry a message and
|
|
84
95
|
* `example` holds one value or several. Emit `deprecated: true` and an
|
|
85
96
|
* `examples` array; the message stays in outputs with a place for it, like
|
|
86
97
|
* Swift's `@available`.
|
|
87
98
|
*/
|
|
88
|
-
const normalizeMeta = ({ jsonSchema }) => {
|
|
99
|
+
const normalizeMeta = ({ zodSchema, jsonSchema }) => {
|
|
100
|
+
const encoding = JSON_ENCODING[(0, kizunajs_generator.readDef)(zodSchema).type ?? ""];
|
|
101
|
+
if (encoding && Object.keys(jsonSchema).length === 0) Object.assign(jsonSchema, encoding);
|
|
89
102
|
if (typeof jsonSchema.deprecated === "string") jsonSchema.deprecated = true;
|
|
90
103
|
if ("example" in jsonSchema) {
|
|
91
104
|
const given = Array.isArray(jsonSchema.example) ? jsonSchema.example : [jsonSchema.example];
|
|
@@ -149,10 +162,11 @@ const deriveHeadOperation = (getOperation) => {
|
|
|
149
162
|
for (const response of Object.values(operation.responses)) delete response.content;
|
|
150
163
|
return operation;
|
|
151
164
|
};
|
|
152
|
-
const
|
|
165
|
+
const openApiWalk = (options, api) => {
|
|
153
166
|
const paths = {};
|
|
154
167
|
return {
|
|
155
|
-
processRoute({ routeKey, route, routeTags, deprecated }) {
|
|
168
|
+
processRoute({ routeKey, route, routeTags, deprecated, hidden }) {
|
|
169
|
+
if (hidden && options.includeHidden !== true) return;
|
|
156
170
|
const openApiPath = convertPath(route.path);
|
|
157
171
|
const method = route.method.toLowerCase();
|
|
158
172
|
let operation = {
|
|
@@ -366,7 +380,7 @@ const openApiGenerator = (0, kizunajs_generator.createGenerator)((options, api)
|
|
|
366
380
|
return renderer;
|
|
367
381
|
}
|
|
368
382
|
};
|
|
369
|
-
}
|
|
383
|
+
};
|
|
370
384
|
/**
|
|
371
385
|
* The permissions a route requires, as `x-kizuna-requires`: what is acted on,
|
|
372
386
|
* mapped to the verbs the caller has to hold.
|
|
@@ -446,17 +460,17 @@ const tagsFromApi = (api) => {
|
|
|
446
460
|
* So a build step does not restate the options and drift from what is served.
|
|
447
461
|
*/
|
|
448
462
|
const optionsFromInstalledPlugin = (api) => {
|
|
449
|
-
for (const
|
|
463
|
+
for (const plugin of Object.values(api.plugins ?? {})) if (plugin.definedSlug === "openApi") return plugin.options;
|
|
450
464
|
throw new Error("generateOpenApi reads its options from the config. Name `openApiPlugin` under `plugins` with the API's `info`.");
|
|
451
465
|
};
|
|
452
466
|
/**
|
|
453
467
|
* Render from options held directly, for the plugin's own routes.
|
|
454
468
|
*/
|
|
455
469
|
function renderOpenApi(api, options) {
|
|
456
|
-
const renderer =
|
|
470
|
+
const renderer = (0, kizunajs_generator.walkApi)(api, openApiWalk({
|
|
457
471
|
...options,
|
|
458
472
|
tagLookup: buildTagLookup(api)
|
|
459
|
-
});
|
|
473
|
+
}, api));
|
|
460
474
|
const tags = tagsFromApi(api);
|
|
461
475
|
if (tags.length > 0) renderer("json").tags = tags;
|
|
462
476
|
return renderer;
|
|
@@ -548,22 +562,6 @@ const renderDocsHtml = (options) => {
|
|
|
548
562
|
});
|
|
549
563
|
};
|
|
550
564
|
//#endregion
|
|
551
|
-
//#region src/document-target.ts
|
|
552
|
-
/**
|
|
553
|
-
* An OpenAPI document target for `kizuna.config.ts`.
|
|
554
|
-
*
|
|
555
|
-
* @example
|
|
556
|
-
* openApiDocument({ output: './openapi.yaml' });
|
|
557
|
-
*/
|
|
558
|
-
const openApiDocument = ({ output, ...overrides }) => ({
|
|
559
|
-
kind: "openapi",
|
|
560
|
-
output,
|
|
561
|
-
generate: (contract) => {
|
|
562
|
-
const render = generateOpenApi(contract, overrides);
|
|
563
|
-
return output.endsWith(".json") ? `${JSON.stringify(render("json"), null, 4)}\n` : render("yaml");
|
|
564
|
-
}
|
|
565
|
-
});
|
|
566
|
-
//#endregion
|
|
567
565
|
//#region src/server.ts
|
|
568
566
|
const HTML = "text/html; charset=utf-8";
|
|
569
567
|
const JSON_TYPE = "application/json";
|
|
@@ -573,24 +571,129 @@ const sent = (body, contentType) => new Response(body, {
|
|
|
573
571
|
headers: { "content-type": contentType }
|
|
574
572
|
});
|
|
575
573
|
/**
|
|
576
|
-
*
|
|
577
|
-
*
|
|
574
|
+
* The routes `openApiPlugin` serves: the reference UI, and the document itself
|
|
575
|
+
* where the options publish it. The document is rendered on the first request,
|
|
576
|
+
* once the api it describes has assembled.
|
|
578
577
|
*/
|
|
579
|
-
const
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
578
|
+
const openApiRoutes = (props, api) => {
|
|
579
|
+
let renderer;
|
|
580
|
+
const spec = ((format) => {
|
|
581
|
+
renderer ??= renderOpenApi((0, kizunajs_adapter.contractOf)(api), props);
|
|
582
|
+
return renderer(format);
|
|
583
|
+
});
|
|
584
|
+
const routes = {};
|
|
585
|
+
if (props.docsPath !== void 0) routes.page = (0, kizunajs_plugin.route)({
|
|
586
|
+
method: "GET",
|
|
587
|
+
path: props.docsPath,
|
|
588
|
+
summary: "API reference",
|
|
589
|
+
responses: { 200: zod.z.string() }
|
|
590
|
+
}).handler(() => (0, kizunajs_adapter.rawResponse)(sent(renderDocsHtml({
|
|
591
|
+
specUrl: props.jsonPath,
|
|
592
|
+
specContent: props.jsonPath === void 0 ? spec("json") : void 0,
|
|
593
|
+
provider: props.provider,
|
|
594
|
+
pageTitle: props.pageTitle ?? spec("json").info.title,
|
|
595
|
+
cdnUrl: props.cdnUrl,
|
|
596
|
+
configuration: props.configuration
|
|
597
|
+
}), HTML)));
|
|
598
|
+
if (props.jsonPath !== void 0) routes.json = (0, kizunajs_plugin.route)({
|
|
599
|
+
method: "GET",
|
|
600
|
+
path: props.jsonPath,
|
|
601
|
+
summary: "OpenAPI document",
|
|
602
|
+
responses: { 200: zod.z.unknown() }
|
|
603
|
+
}).handler(() => (0, kizunajs_adapter.rawResponse)(sent(JSON.stringify(spec("json")), JSON_TYPE)));
|
|
604
|
+
if (props.yamlPath !== void 0) routes.yaml = (0, kizunajs_plugin.route)({
|
|
605
|
+
method: "GET",
|
|
606
|
+
path: props.yamlPath,
|
|
607
|
+
summary: "OpenAPI document, as YAML",
|
|
608
|
+
responses: { 200: zod.z.string() }
|
|
609
|
+
}).handler(() => (0, kizunajs_adapter.rawResponse)(sent(spec("yaml"), YAML)));
|
|
610
|
+
return routes;
|
|
593
611
|
};
|
|
612
|
+
//#endregion
|
|
613
|
+
//#region src/types.ts
|
|
614
|
+
const GenerateOpenApiOptionsSchema = zod.z.object({
|
|
615
|
+
/**
|
|
616
|
+
* The OpenAPI Specification version to declare in the document's `openapi` field.
|
|
617
|
+
*
|
|
618
|
+
* Defaults to `'3.1.0'`.
|
|
619
|
+
*/
|
|
620
|
+
openApiVersion: zod.z.literal("3.1.0").optional(),
|
|
621
|
+
info: zod.z.object({
|
|
622
|
+
title: zod.z.string(),
|
|
623
|
+
version: zod.z.string(),
|
|
624
|
+
description: zod.z.string().optional()
|
|
625
|
+
}),
|
|
626
|
+
servers: zod.z.array(zod.z.object({
|
|
627
|
+
url: zod.z.string(),
|
|
628
|
+
description: zod.z.string().optional()
|
|
629
|
+
})).optional(),
|
|
630
|
+
externalDocs: zod.z.object({
|
|
631
|
+
url: zod.z.string(),
|
|
632
|
+
description: zod.z.string().optional()
|
|
633
|
+
}).optional(),
|
|
634
|
+
setOperationId: zod.z.union([zod.z.boolean(), zod.z.literal("concatenated-path")]).optional(),
|
|
635
|
+
/**
|
|
636
|
+
* Document `hidden` routes too, for a document your own team reads.
|
|
637
|
+
*
|
|
638
|
+
* @default false
|
|
639
|
+
*/
|
|
640
|
+
includeHidden: zod.z.boolean().optional(),
|
|
641
|
+
operationMapper: zod.z.custom((value) => typeof value === "function", { error: "must be a function" }).optional(),
|
|
642
|
+
/**
|
|
643
|
+
* Document the derived `head` operation on every `GET` path without a declared one.
|
|
644
|
+
*
|
|
645
|
+
* @default false
|
|
646
|
+
*/
|
|
647
|
+
derivedHead: zod.z.boolean().optional()
|
|
648
|
+
});
|
|
649
|
+
//#endregion
|
|
650
|
+
//#region src/plugin.ts
|
|
651
|
+
const OPENAPI_PLUGIN_SLUG = "openApi";
|
|
652
|
+
const OpenApiPluginOptionsSchema = GenerateOpenApiOptionsSchema.extend({
|
|
653
|
+
/**
|
|
654
|
+
* Where `kizuna generate` writes the document. `.json` renders JSON,
|
|
655
|
+
* anything else YAML.
|
|
656
|
+
*
|
|
657
|
+
* @example
|
|
658
|
+
* output: './openapi.yaml',
|
|
659
|
+
*/
|
|
660
|
+
output: zod.z.string().optional(),
|
|
661
|
+
/**
|
|
662
|
+
* Where the reference UI is served.
|
|
663
|
+
*/
|
|
664
|
+
docsPath: kizunajs_plugin.RoutePathSchema.optional(),
|
|
665
|
+
/**
|
|
666
|
+
* Where the document is served. The UI embeds it, so this is only for
|
|
667
|
+
* publishing the file.
|
|
668
|
+
*/
|
|
669
|
+
jsonPath: zod.z.custom((value) => typeof value === "string" && value.startsWith("/") && value.endsWith(".json"), { error: "must start with / and end in .json" }).optional(),
|
|
670
|
+
/**
|
|
671
|
+
* Where the document is served as YAML.
|
|
672
|
+
*/
|
|
673
|
+
yamlPath: zod.z.custom((value) => typeof value === "string" && value.startsWith("/") && value.endsWith(".yaml"), { error: "must start with / and end in .yaml" }).optional(),
|
|
674
|
+
/**
|
|
675
|
+
* Which API reference UI to render.
|
|
676
|
+
*
|
|
677
|
+
* @default 'scalar'
|
|
678
|
+
*/
|
|
679
|
+
provider: zod.z.enum(["scalar", "swagger"]).optional(),
|
|
680
|
+
/**
|
|
681
|
+
* Where to load the UI's assets from, for a self-hosted copy. The script
|
|
682
|
+
* URL for `'scalar'`; the directory holding `swagger-ui.css` and
|
|
683
|
+
* `swagger-ui-bundle.js` for `'swagger'`.
|
|
684
|
+
*/
|
|
685
|
+
cdnUrl: zod.z.string().optional(),
|
|
686
|
+
/**
|
|
687
|
+
* The page's `<title>`.
|
|
688
|
+
*
|
|
689
|
+
* @default the document's `info.title`
|
|
690
|
+
*/
|
|
691
|
+
pageTitle: zod.z.string().optional(),
|
|
692
|
+
/**
|
|
693
|
+
* Merged into `Scalar.createApiReference` or `SwaggerUIBundle`.
|
|
694
|
+
*/
|
|
695
|
+
configuration: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
|
|
696
|
+
});
|
|
594
697
|
/**
|
|
595
698
|
* Serve an API reference UI for the OpenAPI document, and the document itself
|
|
596
699
|
* if you publish it.
|
|
@@ -608,42 +711,42 @@ const openApiServe = (props, api) => {
|
|
|
608
711
|
* title: 'My API',
|
|
609
712
|
* version: '1.0.0',
|
|
610
713
|
* },
|
|
714
|
+
* docsPath: '/docs',
|
|
611
715
|
* }),
|
|
612
716
|
* ],
|
|
613
717
|
* });
|
|
614
718
|
* ```
|
|
615
719
|
*/
|
|
616
|
-
const
|
|
617
|
-
slug,
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
720
|
+
const openApiPlugin = (0, kizunajs_plugin.definePlugin)({
|
|
721
|
+
slug: OPENAPI_PLUGIN_SLUG,
|
|
722
|
+
options: OpenApiPluginOptionsSchema,
|
|
723
|
+
setup: ({ options, api }) => {
|
|
724
|
+
const { output, ...document } = options;
|
|
725
|
+
return {
|
|
726
|
+
routes: openApiRoutes(options, api),
|
|
727
|
+
generators: output === void 0 ? [] : [openApiDocumentGenerator({
|
|
728
|
+
output,
|
|
729
|
+
json: output.endsWith(".json"),
|
|
730
|
+
document
|
|
731
|
+
})]
|
|
732
|
+
};
|
|
733
|
+
}
|
|
734
|
+
});
|
|
735
|
+
/**
|
|
736
|
+
* Writes one document, in the format its `output` names.
|
|
737
|
+
*/
|
|
738
|
+
const openApiDocumentGenerator = (0, kizunajs_generator.defineGenerator)({
|
|
739
|
+
options: zod.z.object({
|
|
740
|
+
json: zod.z.boolean(),
|
|
741
|
+
document: zod.z.custom()
|
|
742
|
+
}),
|
|
743
|
+
generate: ({ options, api }) => ({ finalize: () => {
|
|
744
|
+
const render = renderOpenApi(api, options.document);
|
|
745
|
+
return options.json ? `${JSON.stringify(render("json"), null, 4)}\n` : render("yaml");
|
|
746
|
+
} })
|
|
640
747
|
});
|
|
641
|
-
function openApiPlugin(props) {
|
|
642
|
-
return declare(props.slug ?? "openApi", props);
|
|
643
|
-
}
|
|
644
748
|
//#endregion
|
|
645
749
|
exports.generateOpenApi = generateOpenApi;
|
|
646
|
-
exports.openApiDocument = openApiDocument;
|
|
647
750
|
exports.openApiPlugin = openApiPlugin;
|
|
648
751
|
exports.renderDocsHtml = renderDocsHtml;
|
|
649
752
|
exports.renderOpenApi = renderOpenApi;
|