mirror of
https://github.com/zoriya/elysia-swagger.git
synced 2025-12-06 00:36:10 +00:00
Push build
This commit is contained in:
34
dist/cjs/index.d.ts
vendored
Normal file
34
dist/cjs/index.d.ts
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
import { Elysia } from 'elysia';
|
||||
import type { ElysiaSwaggerConfig } from './types';
|
||||
/**
|
||||
* Plugin for [elysia](https://github.com/elysiajs/elysia) that auto-generate Swagger page.
|
||||
*
|
||||
* @see https://github.com/elysiajs/elysia-swagger
|
||||
*/
|
||||
export declare const swagger: <Path extends string = "/swagger">({ provider, scalarVersion, scalarCDN, scalarConfig, documentation, version, excludeStaticFile, path, specPath, exclude, swaggerOptions, theme, autoDarkMode, excludeMethods, excludeTags }?: ElysiaSwaggerConfig<Path>) => Elysia<"", {
|
||||
decorator: {};
|
||||
store: {};
|
||||
derive: {};
|
||||
resolve: {};
|
||||
}, {
|
||||
typebox: {};
|
||||
error: {};
|
||||
}, {
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
macro: {};
|
||||
macroFn: {};
|
||||
parser: {};
|
||||
}, {}, {
|
||||
derive: {};
|
||||
resolve: {};
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
}, {
|
||||
derive: {};
|
||||
resolve: {};
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
}>;
|
||||
export type { ElysiaSwaggerConfig };
|
||||
export default swagger;
|
||||
603
dist/cjs/index.js
vendored
Normal file
603
dist/cjs/index.js
vendored
Normal file
@@ -0,0 +1,603 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
|
||||
// src/index.ts
|
||||
var index_exports = {};
|
||||
__export(index_exports, {
|
||||
default: () => index_default,
|
||||
swagger: () => swagger
|
||||
});
|
||||
module.exports = __toCommonJS(index_exports);
|
||||
var import_elysia2 = require("elysia");
|
||||
|
||||
// src/swagger/index.ts
|
||||
function isSchemaObject(schema) {
|
||||
return "type" in schema || "properties" in schema || "items" in schema;
|
||||
}
|
||||
function isDateTimeProperty(key, schema) {
|
||||
return (key === "createdAt" || key === "updatedAt") && "anyOf" in schema && Array.isArray(schema.anyOf);
|
||||
}
|
||||
function transformDateProperties(schema) {
|
||||
if (!isSchemaObject(schema) || typeof schema !== "object" || schema === null) {
|
||||
return schema;
|
||||
}
|
||||
const newSchema = { ...schema };
|
||||
Object.entries(newSchema).forEach(([key, value]) => {
|
||||
if (isSchemaObject(value)) {
|
||||
if (isDateTimeProperty(key, value)) {
|
||||
const dateTimeFormat = value.anyOf?.find(
|
||||
(item) => isSchemaObject(item) && item.format === "date-time"
|
||||
);
|
||||
if (dateTimeFormat) {
|
||||
const dateTimeSchema = {
|
||||
type: "string",
|
||||
format: "date-time",
|
||||
default: dateTimeFormat.default
|
||||
};
|
||||
newSchema[key] = dateTimeSchema;
|
||||
}
|
||||
} else {
|
||||
newSchema[key] = transformDateProperties(value);
|
||||
}
|
||||
}
|
||||
});
|
||||
return newSchema;
|
||||
}
|
||||
var SwaggerUIRender = (info, version, theme, stringifiedSwaggerOptions, autoDarkMode) => {
|
||||
const swaggerOptions = JSON.parse(stringifiedSwaggerOptions);
|
||||
if (swaggerOptions.components && swaggerOptions.components.schemas) {
|
||||
swaggerOptions.components.schemas = Object.fromEntries(
|
||||
Object.entries(swaggerOptions.components.schemas).map(([key, schema]) => [
|
||||
key,
|
||||
transformDateProperties(schema)
|
||||
])
|
||||
);
|
||||
}
|
||||
const transformedStringifiedSwaggerOptions = JSON.stringify(swaggerOptions);
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>${info.title}</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="${info.description}"
|
||||
/>
|
||||
<meta
|
||||
name="og:description"
|
||||
content="${info.description}"
|
||||
/>
|
||||
${autoDarkMode && typeof theme === "string" ? `
|
||||
<style>
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background-color: #222;
|
||||
color: #faf9a;
|
||||
}
|
||||
.swagger-ui {
|
||||
filter: invert(92%) hue-rotate(180deg);
|
||||
}
|
||||
|
||||
.swagger-ui .microlight {
|
||||
filter: invert(100%) hue-rotate(180deg);
|
||||
}
|
||||
}
|
||||
</style>` : ""}
|
||||
${typeof theme === "string" ? `<link rel="stylesheet" href="${theme}" />` : `<link rel="stylesheet" media="(prefers-color-scheme: light)" href="${theme.light}" />
|
||||
<link rel="stylesheet" media="(prefers-color-scheme: dark)" href="${theme.dark}" />`}
|
||||
</head>
|
||||
<body>
|
||||
<div id="swagger-ui"></div>
|
||||
<script src="https://unpkg.com/swagger-ui-dist@${version}/swagger-ui-bundle.js" crossorigin></script>
|
||||
<script>
|
||||
window.onload = () => {
|
||||
window.ui = SwaggerUIBundle(${transformedStringifiedSwaggerOptions});
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
};
|
||||
|
||||
// src/scalar/index.ts
|
||||
var import_themes = require("@scalar/themes");
|
||||
var ScalarRender = (info, version, config, cdn) => `<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>${info.title}</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="${info.description}"
|
||||
/>
|
||||
<meta
|
||||
name="og:description"
|
||||
content="${info.description}"
|
||||
/>
|
||||
<meta charset="utf-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1" />
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
${config.customCss ?? import_themes.elysiajsTheme}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
<script src="${cdn ? cdn : `https://cdn.jsdelivr.net/npm/@scalar/api-reference@${version}/dist/browser/standalone.min.js`}" crossorigin></script>
|
||||
|
||||
<script>
|
||||
Scalar.createApiReference('#app', ${JSON.stringify(config)})
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
// src/utils.ts
|
||||
var import_elysia = require("elysia");
|
||||
|
||||
// node_modules/@sinclair/typebox/build/esm/type/symbols/symbols.mjs
|
||||
var TransformKind = Symbol.for("TypeBox.Transform");
|
||||
var ReadonlyKind = Symbol.for("TypeBox.Readonly");
|
||||
var OptionalKind = Symbol.for("TypeBox.Optional");
|
||||
var Hint = Symbol.for("TypeBox.Hint");
|
||||
var Kind = Symbol.for("TypeBox.Kind");
|
||||
|
||||
// src/utils.ts
|
||||
var toOpenAPIPath = (path) => path.split("/").map((x) => {
|
||||
if (x.startsWith(":")) {
|
||||
x = x.slice(1, x.length);
|
||||
if (x.endsWith("?")) x = x.slice(0, -1);
|
||||
x = `{${x}}`;
|
||||
}
|
||||
return x;
|
||||
}).join("/");
|
||||
var mapProperties = (name, schema, models) => {
|
||||
if (schema === void 0) return [];
|
||||
if (typeof schema === "string")
|
||||
if (schema in models) schema = models[schema];
|
||||
else throw new Error(`Can't find model ${schema}`);
|
||||
return Object.entries(schema?.properties ?? []).map(([key, value]) => {
|
||||
const {
|
||||
type: valueType = void 0,
|
||||
description,
|
||||
examples,
|
||||
...schemaKeywords
|
||||
} = value;
|
||||
return {
|
||||
// @ts-ignore
|
||||
description,
|
||||
examples,
|
||||
schema: { type: valueType, ...schemaKeywords },
|
||||
in: name,
|
||||
name: key,
|
||||
// @ts-ignore
|
||||
required: schema.required?.includes(key) ?? false
|
||||
};
|
||||
});
|
||||
};
|
||||
var mapTypesResponse = (types, schema) => {
|
||||
if (typeof schema === "object" && ["void", "undefined", "null"].includes(schema.type))
|
||||
return;
|
||||
const responses = {};
|
||||
for (const type of types) {
|
||||
responses[type] = {
|
||||
schema: typeof schema === "string" ? {
|
||||
$ref: `#/components/schemas/${schema}`
|
||||
} : "$ref" in schema && Kind in schema && schema[Kind] === "Ref" ? {
|
||||
...schema,
|
||||
$ref: `#/components/schemas/${schema.$ref}`
|
||||
} : (0, import_elysia.replaceSchemaType)(
|
||||
{ ...schema },
|
||||
{
|
||||
from: import_elysia.t.Ref(""),
|
||||
// @ts-expect-error
|
||||
to: ({ $ref, ...options }) => {
|
||||
if (!$ref.startsWith(
|
||||
"#/components/schemas/"
|
||||
))
|
||||
return import_elysia.t.Ref(
|
||||
`#/components/schemas/${$ref}`,
|
||||
options
|
||||
);
|
||||
return import_elysia.t.Ref($ref, options);
|
||||
}
|
||||
}
|
||||
)
|
||||
};
|
||||
}
|
||||
return responses;
|
||||
};
|
||||
var capitalize = (word) => word.charAt(0).toUpperCase() + word.slice(1);
|
||||
var generateOperationId = (method, paths) => {
|
||||
let operationId = method.toLowerCase();
|
||||
if (paths === "/") return operationId + "Index";
|
||||
for (const path of paths.split("/")) {
|
||||
if (path.charCodeAt(0) === 123) {
|
||||
operationId += "By" + capitalize(path.slice(1, -1));
|
||||
} else {
|
||||
operationId += capitalize(path);
|
||||
}
|
||||
}
|
||||
return operationId;
|
||||
};
|
||||
var cloneHook = (hook) => {
|
||||
if (!hook) return;
|
||||
if (typeof hook === "string") return hook;
|
||||
if (Array.isArray(hook)) return [...hook];
|
||||
return { ...hook };
|
||||
};
|
||||
var registerSchemaPath = ({
|
||||
schema,
|
||||
path,
|
||||
method,
|
||||
hook,
|
||||
models
|
||||
}) => {
|
||||
hook = cloneHook(hook);
|
||||
if (hook.parse && !Array.isArray(hook.parse)) hook.parse = [hook.parse];
|
||||
let contentType = hook.parse?.map((x) => {
|
||||
switch (typeof x) {
|
||||
case "string":
|
||||
return x;
|
||||
case "object":
|
||||
if (x && typeof x?.fn !== "string")
|
||||
return;
|
||||
switch (x?.fn) {
|
||||
case "json":
|
||||
case "application/json":
|
||||
return "application/json";
|
||||
case "text":
|
||||
case "text/plain":
|
||||
return "text/plain";
|
||||
case "urlencoded":
|
||||
case "application/x-www-form-urlencoded":
|
||||
return "application/x-www-form-urlencoded";
|
||||
case "arrayBuffer":
|
||||
case "application/octet-stream":
|
||||
return "application/octet-stream";
|
||||
case "formdata":
|
||||
case "multipart/form-data":
|
||||
return "multipart/form-data";
|
||||
}
|
||||
}
|
||||
}).filter((x) => x !== void 0);
|
||||
if (!contentType || contentType.length === 0)
|
||||
contentType = ["application/json", "multipart/form-data", "text/plain"];
|
||||
path = toOpenAPIPath(path);
|
||||
const contentTypes = typeof contentType === "string" ? [contentType] : contentType ?? ["application/json"];
|
||||
const bodySchema = cloneHook(hook?.body);
|
||||
const paramsSchema = cloneHook(hook?.params);
|
||||
const headerSchema = cloneHook(hook?.headers);
|
||||
const querySchema = cloneHook(hook?.query);
|
||||
let responseSchema = cloneHook(hook?.response);
|
||||
if (typeof responseSchema === "object") {
|
||||
if (Kind in responseSchema) {
|
||||
const {
|
||||
type,
|
||||
properties,
|
||||
required,
|
||||
additionalProperties,
|
||||
patternProperties,
|
||||
$ref,
|
||||
...rest
|
||||
} = responseSchema;
|
||||
responseSchema = {
|
||||
"200": {
|
||||
...rest,
|
||||
description: rest.description,
|
||||
content: mapTypesResponse(
|
||||
contentTypes,
|
||||
type === "object" || type === "array" ? {
|
||||
type,
|
||||
properties,
|
||||
patternProperties,
|
||||
items: responseSchema.items,
|
||||
required
|
||||
} : responseSchema
|
||||
)
|
||||
}
|
||||
};
|
||||
} else {
|
||||
Object.entries(responseSchema).forEach(
|
||||
([key, value]) => {
|
||||
if (typeof value === "string") {
|
||||
if (!models[value]) return;
|
||||
const {
|
||||
type,
|
||||
properties,
|
||||
required,
|
||||
additionalProperties: _1,
|
||||
patternProperties: _2,
|
||||
...rest
|
||||
} = models[value];
|
||||
responseSchema[key] = {
|
||||
...rest,
|
||||
description: rest.description,
|
||||
content: mapTypesResponse(contentTypes, value)
|
||||
};
|
||||
} else {
|
||||
const {
|
||||
type,
|
||||
properties,
|
||||
required,
|
||||
additionalProperties,
|
||||
patternProperties,
|
||||
...rest
|
||||
} = value;
|
||||
responseSchema[key] = {
|
||||
...rest,
|
||||
description: rest.description,
|
||||
content: mapTypesResponse(
|
||||
contentTypes,
|
||||
type === "object" || type === "array" ? {
|
||||
type,
|
||||
properties,
|
||||
patternProperties,
|
||||
items: value.items,
|
||||
required
|
||||
} : value
|
||||
)
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
} else if (typeof responseSchema === "string") {
|
||||
if (!(responseSchema in models)) return;
|
||||
const {
|
||||
type,
|
||||
properties,
|
||||
required,
|
||||
$ref,
|
||||
additionalProperties: _1,
|
||||
patternProperties: _2,
|
||||
...rest
|
||||
} = models[responseSchema];
|
||||
responseSchema = {
|
||||
// @ts-ignore
|
||||
"200": {
|
||||
...rest,
|
||||
content: mapTypesResponse(contentTypes, responseSchema)
|
||||
}
|
||||
};
|
||||
}
|
||||
const parameters = [
|
||||
...mapProperties("header", headerSchema, models),
|
||||
...mapProperties("path", paramsSchema, models),
|
||||
...mapProperties("query", querySchema, models)
|
||||
];
|
||||
schema[path] = {
|
||||
...schema[path] ? schema[path] : {},
|
||||
[method.toLowerCase()]: {
|
||||
...headerSchema || paramsSchema || querySchema || bodySchema ? { parameters } : {},
|
||||
...responseSchema ? {
|
||||
responses: responseSchema
|
||||
} : {},
|
||||
operationId: hook?.detail?.operationId ?? generateOperationId(method, path),
|
||||
...hook?.detail,
|
||||
...bodySchema ? {
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: mapTypesResponse(
|
||||
contentTypes,
|
||||
typeof bodySchema === "string" ? {
|
||||
$ref: `#/components/schemas/${bodySchema}`
|
||||
} : bodySchema
|
||||
)
|
||||
}
|
||||
} : null
|
||||
}
|
||||
};
|
||||
};
|
||||
var filterPaths = (paths, {
|
||||
excludeStaticFile = true,
|
||||
exclude = []
|
||||
}) => {
|
||||
const newPaths = {};
|
||||
for (const [key, value] of Object.entries(paths))
|
||||
if (!exclude.some((x) => {
|
||||
if (typeof x === "string") return key === x;
|
||||
return x.test(key);
|
||||
}) && !key.includes("*") && (excludeStaticFile ? !key.includes(".") : true)) {
|
||||
Object.keys(value).forEach((method) => {
|
||||
const schema = value[method];
|
||||
if (key.includes("{")) {
|
||||
if (!schema.parameters) schema.parameters = [];
|
||||
schema.parameters = [
|
||||
...key.split("/").filter(
|
||||
(x) => x.startsWith("{") && !schema.parameters.find(
|
||||
(params) => params.in === "path" && params.name === x.slice(1, x.length - 1)
|
||||
)
|
||||
).map((x) => ({
|
||||
schema: { type: "string" },
|
||||
in: "path",
|
||||
name: x.slice(1, x.length - 1),
|
||||
required: true
|
||||
})),
|
||||
...schema.parameters
|
||||
];
|
||||
}
|
||||
if (!schema.responses)
|
||||
schema.responses = {
|
||||
200: {}
|
||||
};
|
||||
});
|
||||
newPaths[key] = value;
|
||||
}
|
||||
return newPaths;
|
||||
};
|
||||
|
||||
// src/index.ts
|
||||
var swagger = ({
|
||||
provider = "scalar",
|
||||
scalarVersion = "latest",
|
||||
scalarCDN = "",
|
||||
scalarConfig = {},
|
||||
documentation = {},
|
||||
version = "5.9.0",
|
||||
excludeStaticFile = true,
|
||||
path = "/swagger",
|
||||
specPath = `${path}/json`,
|
||||
exclude = [],
|
||||
swaggerOptions = {},
|
||||
theme = `https://unpkg.com/swagger-ui-dist@${version}/swagger-ui.css`,
|
||||
autoDarkMode = true,
|
||||
excludeMethods = ["OPTIONS"],
|
||||
excludeTags = []
|
||||
} = {}) => {
|
||||
const schema = {};
|
||||
let totalRoutes = 0;
|
||||
if (!version)
|
||||
version = `https://unpkg.com/swagger-ui-dist@${version}/swagger-ui.css`;
|
||||
const info = {
|
||||
title: "Elysia Documentation",
|
||||
description: "Development documentation",
|
||||
version: "0.0.0",
|
||||
...documentation.info
|
||||
};
|
||||
const app = new import_elysia2.Elysia({ name: "@elysiajs/swagger" });
|
||||
const page = new Response(
|
||||
provider === "swagger-ui" ? SwaggerUIRender(
|
||||
info,
|
||||
version,
|
||||
theme,
|
||||
JSON.stringify(
|
||||
{
|
||||
url: specPath,
|
||||
dom_id: "#swagger-ui",
|
||||
...swaggerOptions
|
||||
},
|
||||
(_, value) => typeof value === "function" ? void 0 : value
|
||||
),
|
||||
autoDarkMode
|
||||
) : ScalarRender(
|
||||
info,
|
||||
scalarVersion,
|
||||
{
|
||||
sources: [{ url: specPath }],
|
||||
...scalarConfig,
|
||||
// so we can showcase the elysia theme
|
||||
_integration: "elysiajs"
|
||||
},
|
||||
scalarCDN
|
||||
),
|
||||
{
|
||||
headers: {
|
||||
"content-type": "text/html; charset=utf8"
|
||||
}
|
||||
}
|
||||
);
|
||||
app.get(path, page, {
|
||||
detail: {
|
||||
hide: true
|
||||
}
|
||||
}).get(
|
||||
specPath,
|
||||
function openAPISchema() {
|
||||
const routes = app.getGlobalRoutes();
|
||||
if (routes.length !== totalRoutes) {
|
||||
const ALLOWED_METHODS = [
|
||||
"GET",
|
||||
"PUT",
|
||||
"POST",
|
||||
"DELETE",
|
||||
"OPTIONS",
|
||||
"HEAD",
|
||||
"PATCH",
|
||||
"TRACE"
|
||||
];
|
||||
totalRoutes = routes.length;
|
||||
routes.forEach((route) => {
|
||||
if (route.hooks?.detail?.hide === true) return;
|
||||
if (excludeMethods.includes(route.method)) return;
|
||||
if (ALLOWED_METHODS.includes(route.method) === false && route.method !== "ALL")
|
||||
return;
|
||||
if (route.method === "ALL")
|
||||
ALLOWED_METHODS.forEach((method) => {
|
||||
registerSchemaPath({
|
||||
schema,
|
||||
hook: route.hooks,
|
||||
method,
|
||||
path: route.path,
|
||||
// @ts-ignore
|
||||
models: app.getGlobalDefinitions?.().type,
|
||||
contentType: route.hooks.type
|
||||
});
|
||||
});
|
||||
else
|
||||
registerSchemaPath({
|
||||
schema,
|
||||
hook: route.hooks,
|
||||
method: route.method,
|
||||
path: route.path,
|
||||
// @ts-ignore
|
||||
models: app.getGlobalDefinitions?.().type,
|
||||
contentType: route.hooks.type
|
||||
});
|
||||
});
|
||||
}
|
||||
return {
|
||||
openapi: "3.0.3",
|
||||
...{
|
||||
...documentation,
|
||||
tags: documentation.tags?.filter(
|
||||
(tag) => !excludeTags?.includes(tag?.name)
|
||||
),
|
||||
info: {
|
||||
title: "Elysia Documentation",
|
||||
description: "Development documentation",
|
||||
version: "0.0.0",
|
||||
...documentation.info
|
||||
}
|
||||
},
|
||||
paths: {
|
||||
...filterPaths(schema, {
|
||||
excludeStaticFile,
|
||||
exclude: Array.isArray(exclude) ? exclude : [exclude]
|
||||
}),
|
||||
...documentation.paths
|
||||
},
|
||||
components: {
|
||||
...documentation.components,
|
||||
schemas: {
|
||||
// @ts-ignore
|
||||
...app.getGlobalDefinitions?.().type,
|
||||
...documentation.components?.schemas
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
{
|
||||
detail: {
|
||||
hide: true
|
||||
}
|
||||
}
|
||||
);
|
||||
return app;
|
||||
};
|
||||
var index_default = swagger;
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
swagger
|
||||
});
|
||||
65
dist/cjs/scalar/index.js
vendored
Normal file
65
dist/cjs/scalar/index.js
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
|
||||
// src/scalar/index.ts
|
||||
var scalar_exports = {};
|
||||
__export(scalar_exports, {
|
||||
ScalarRender: () => ScalarRender
|
||||
});
|
||||
module.exports = __toCommonJS(scalar_exports);
|
||||
var import_themes = require("@scalar/themes");
|
||||
var ScalarRender = (info, version, config, cdn) => `<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>${info.title}</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="${info.description}"
|
||||
/>
|
||||
<meta
|
||||
name="og:description"
|
||||
content="${info.description}"
|
||||
/>
|
||||
<meta charset="utf-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1" />
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
${config.customCss ?? import_themes.elysiajsTheme}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
<script src="${cdn ? cdn : `https://cdn.jsdelivr.net/npm/@scalar/api-reference@${version}/dist/browser/standalone.min.js`}" crossorigin></script>
|
||||
|
||||
<script>
|
||||
Scalar.createApiReference('#app', ${JSON.stringify(config)})
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
ScalarRender
|
||||
});
|
||||
116
dist/cjs/swagger/index.js
vendored
Normal file
116
dist/cjs/swagger/index.js
vendored
Normal file
@@ -0,0 +1,116 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
|
||||
// src/swagger/index.ts
|
||||
var swagger_exports = {};
|
||||
__export(swagger_exports, {
|
||||
SwaggerUIRender: () => SwaggerUIRender
|
||||
});
|
||||
module.exports = __toCommonJS(swagger_exports);
|
||||
function isSchemaObject(schema) {
|
||||
return "type" in schema || "properties" in schema || "items" in schema;
|
||||
}
|
||||
function isDateTimeProperty(key, schema) {
|
||||
return (key === "createdAt" || key === "updatedAt") && "anyOf" in schema && Array.isArray(schema.anyOf);
|
||||
}
|
||||
function transformDateProperties(schema) {
|
||||
if (!isSchemaObject(schema) || typeof schema !== "object" || schema === null) {
|
||||
return schema;
|
||||
}
|
||||
const newSchema = { ...schema };
|
||||
Object.entries(newSchema).forEach(([key, value]) => {
|
||||
if (isSchemaObject(value)) {
|
||||
if (isDateTimeProperty(key, value)) {
|
||||
const dateTimeFormat = value.anyOf?.find(
|
||||
(item) => isSchemaObject(item) && item.format === "date-time"
|
||||
);
|
||||
if (dateTimeFormat) {
|
||||
const dateTimeSchema = {
|
||||
type: "string",
|
||||
format: "date-time",
|
||||
default: dateTimeFormat.default
|
||||
};
|
||||
newSchema[key] = dateTimeSchema;
|
||||
}
|
||||
} else {
|
||||
newSchema[key] = transformDateProperties(value);
|
||||
}
|
||||
}
|
||||
});
|
||||
return newSchema;
|
||||
}
|
||||
var SwaggerUIRender = (info, version, theme, stringifiedSwaggerOptions, autoDarkMode) => {
|
||||
const swaggerOptions = JSON.parse(stringifiedSwaggerOptions);
|
||||
if (swaggerOptions.components && swaggerOptions.components.schemas) {
|
||||
swaggerOptions.components.schemas = Object.fromEntries(
|
||||
Object.entries(swaggerOptions.components.schemas).map(([key, schema]) => [
|
||||
key,
|
||||
transformDateProperties(schema)
|
||||
])
|
||||
);
|
||||
}
|
||||
const transformedStringifiedSwaggerOptions = JSON.stringify(swaggerOptions);
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>${info.title}</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="${info.description}"
|
||||
/>
|
||||
<meta
|
||||
name="og:description"
|
||||
content="${info.description}"
|
||||
/>
|
||||
${autoDarkMode && typeof theme === "string" ? `
|
||||
<style>
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background-color: #222;
|
||||
color: #faf9a;
|
||||
}
|
||||
.swagger-ui {
|
||||
filter: invert(92%) hue-rotate(180deg);
|
||||
}
|
||||
|
||||
.swagger-ui .microlight {
|
||||
filter: invert(100%) hue-rotate(180deg);
|
||||
}
|
||||
}
|
||||
</style>` : ""}
|
||||
${typeof theme === "string" ? `<link rel="stylesheet" href="${theme}" />` : `<link rel="stylesheet" media="(prefers-color-scheme: light)" href="${theme.light}" />
|
||||
<link rel="stylesheet" media="(prefers-color-scheme: dark)" href="${theme.dark}" />`}
|
||||
</head>
|
||||
<body>
|
||||
<div id="swagger-ui"></div>
|
||||
<script src="https://unpkg.com/swagger-ui-dist@${version}/swagger-ui-bundle.js" crossorigin></script>
|
||||
<script>
|
||||
window.onload = () => {
|
||||
window.ui = SwaggerUIBundle(${transformedStringifiedSwaggerOptions});
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
};
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
SwaggerUIRender
|
||||
});
|
||||
18
dist/cjs/swagger/types.js
vendored
Normal file
18
dist/cjs/swagger/types.js
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
|
||||
// src/swagger/types.ts
|
||||
var types_exports = {};
|
||||
module.exports = __toCommonJS(types_exports);
|
||||
102
dist/cjs/types.d.ts
vendored
Normal file
102
dist/cjs/types.d.ts
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
import type { OpenAPIV3 } from 'openapi-types';
|
||||
import type { ApiReferenceConfigurationWithSources } from '@scalar/types/api-reference' with { "resolution-mode": "import" };
|
||||
import type { SwaggerUIOptions } from './swagger/types';
|
||||
export interface ElysiaSwaggerConfig<Path extends string = '/swagger'> {
|
||||
/**
|
||||
* Customize Swagger config, refers to Swagger 2.0 config
|
||||
*
|
||||
* @see https://swagger.io/specification/v2/
|
||||
*/
|
||||
documentation?: Omit<Partial<OpenAPIV3.Document>, 'x-express-openapi-additional-middleware' | 'x-express-openapi-validation-strict'>;
|
||||
/**
|
||||
* Choose your provider, Scalar or Swagger UI
|
||||
*
|
||||
* @default 'scalar'
|
||||
* @see https://github.com/scalar/scalar
|
||||
* @see https://github.com/swagger-api/swagger-ui
|
||||
*/
|
||||
provider?: 'scalar' | 'swagger-ui';
|
||||
/**
|
||||
* Version to use for Scalar cdn bundle
|
||||
*
|
||||
* @default 'latest'
|
||||
* @see https://github.com/scalar/scalar
|
||||
*/
|
||||
scalarVersion?: string;
|
||||
/**
|
||||
* Optional override to specifying the path for the Scalar bundle
|
||||
*
|
||||
* Custom URL or path to locally hosted Scalar bundle
|
||||
*
|
||||
* Lease blank to use default jsdeliver.net CDN
|
||||
*
|
||||
* @default ''
|
||||
* @example 'https://unpkg.com/@scalar/api-reference@1.13.10/dist/browser/standalone.js'
|
||||
* @example '/public/standalone.js'
|
||||
* @see https://github.com/scalar/scalar
|
||||
*/
|
||||
scalarCDN?: string;
|
||||
/**
|
||||
* Scalar configuration to customize scalar
|
||||
*'
|
||||
* @see https://github.com/scalar/scalar/blob/main/documentation/configuration.md
|
||||
*/
|
||||
scalarConfig?: Partial<ApiReferenceConfigurationWithSources>;
|
||||
/**
|
||||
* Version to use for swagger cdn bundle
|
||||
*
|
||||
* @see unpkg.com/swagger-ui-dist
|
||||
*
|
||||
* @default 4.18.2
|
||||
*/
|
||||
version?: string;
|
||||
/**
|
||||
* Determine if Swagger should exclude static files.
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
excludeStaticFile?: boolean;
|
||||
/**
|
||||
* The endpoint to expose OpenAPI Documentation
|
||||
*
|
||||
* @default '/swagger'
|
||||
*/
|
||||
path?: Path;
|
||||
/**
|
||||
* The endpoint to expose OpenAPI JSON specification
|
||||
*
|
||||
* @default '/${path}/json'
|
||||
*/
|
||||
specPath?: string;
|
||||
/**
|
||||
* Paths to exclude from Swagger endpoint
|
||||
*
|
||||
* @default []
|
||||
*/
|
||||
exclude?: string | RegExp | (string | RegExp)[];
|
||||
/**
|
||||
* Options to send to SwaggerUIBundle
|
||||
* Currently, options that are defined as functions such as requestInterceptor
|
||||
* and onComplete are not supported.
|
||||
*/
|
||||
swaggerOptions?: Omit<Partial<SwaggerUIOptions>, 'dom_id' | 'dom_node' | 'spec' | 'url' | 'urls' | 'layout' | 'pluginsOptions' | 'plugins' | 'presets' | 'onComplete' | 'requestInterceptor' | 'responseInterceptor' | 'modelPropertyMacro' | 'parameterMacro'>;
|
||||
/**
|
||||
* Custom Swagger CSS
|
||||
*/
|
||||
theme?: string | {
|
||||
light: string;
|
||||
dark: string;
|
||||
};
|
||||
/**
|
||||
* Using poor man dark mode 😭
|
||||
*/
|
||||
autoDarkMode?: boolean;
|
||||
/**
|
||||
* Exclude methods from Swagger
|
||||
*/
|
||||
excludeMethods?: string[];
|
||||
/**
|
||||
* Exclude tags from Swagger or Scalar
|
||||
*/
|
||||
excludeTags?: string[];
|
||||
}
|
||||
18
dist/cjs/types.js
vendored
Normal file
18
dist/cjs/types.js
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
|
||||
// src/types.ts
|
||||
var types_exports = {};
|
||||
module.exports = __toCommonJS(types_exports);
|
||||
26
dist/cjs/utils.d.ts
vendored
Normal file
26
dist/cjs/utils.d.ts
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
import { type HTTPMethod, type LocalHook } from 'elysia';
|
||||
import { type TSchema } from '@sinclair/typebox';
|
||||
import type { OpenAPIV3 } from 'openapi-types';
|
||||
export declare const toOpenAPIPath: (path: string) => string;
|
||||
export declare const mapProperties: (name: string, schema: TSchema | string | undefined, models: Record<string, TSchema>) => {
|
||||
description: any;
|
||||
examples: any;
|
||||
schema: any;
|
||||
in: string;
|
||||
name: string;
|
||||
required: any;
|
||||
}[];
|
||||
export declare const capitalize: (word: string) => string;
|
||||
export declare const generateOperationId: (method: string, paths: string) => string;
|
||||
export declare const registerSchemaPath: ({ schema, path, method, hook, models }: {
|
||||
schema: Partial<OpenAPIV3.PathsObject>;
|
||||
contentType?: string | string[];
|
||||
path: string;
|
||||
method: HTTPMethod;
|
||||
hook?: LocalHook<any, any, any, any, any, any>;
|
||||
models: Record<string, TSchema>;
|
||||
}) => void;
|
||||
export declare const filterPaths: (paths: Record<string, any>, { excludeStaticFile, exclude }: {
|
||||
excludeStaticFile: boolean;
|
||||
exclude: (string | RegExp)[];
|
||||
}) => Record<string, any>;
|
||||
332
dist/cjs/utils.js
vendored
Normal file
332
dist/cjs/utils.js
vendored
Normal file
@@ -0,0 +1,332 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
|
||||
// src/utils.ts
|
||||
var utils_exports = {};
|
||||
__export(utils_exports, {
|
||||
capitalize: () => capitalize,
|
||||
filterPaths: () => filterPaths,
|
||||
generateOperationId: () => generateOperationId,
|
||||
mapProperties: () => mapProperties,
|
||||
registerSchemaPath: () => registerSchemaPath,
|
||||
toOpenAPIPath: () => toOpenAPIPath
|
||||
});
|
||||
module.exports = __toCommonJS(utils_exports);
|
||||
var import_elysia = require("elysia");
|
||||
|
||||
// node_modules/@sinclair/typebox/build/esm/type/symbols/symbols.mjs
|
||||
var TransformKind = Symbol.for("TypeBox.Transform");
|
||||
var ReadonlyKind = Symbol.for("TypeBox.Readonly");
|
||||
var OptionalKind = Symbol.for("TypeBox.Optional");
|
||||
var Hint = Symbol.for("TypeBox.Hint");
|
||||
var Kind = Symbol.for("TypeBox.Kind");
|
||||
|
||||
// src/utils.ts
|
||||
var toOpenAPIPath = (path) => path.split("/").map((x) => {
|
||||
if (x.startsWith(":")) {
|
||||
x = x.slice(1, x.length);
|
||||
if (x.endsWith("?")) x = x.slice(0, -1);
|
||||
x = `{${x}}`;
|
||||
}
|
||||
return x;
|
||||
}).join("/");
|
||||
var mapProperties = (name, schema, models) => {
|
||||
if (schema === void 0) return [];
|
||||
if (typeof schema === "string")
|
||||
if (schema in models) schema = models[schema];
|
||||
else throw new Error(`Can't find model ${schema}`);
|
||||
return Object.entries(schema?.properties ?? []).map(([key, value]) => {
|
||||
const {
|
||||
type: valueType = void 0,
|
||||
description,
|
||||
examples,
|
||||
...schemaKeywords
|
||||
} = value;
|
||||
return {
|
||||
// @ts-ignore
|
||||
description,
|
||||
examples,
|
||||
schema: { type: valueType, ...schemaKeywords },
|
||||
in: name,
|
||||
name: key,
|
||||
// @ts-ignore
|
||||
required: schema.required?.includes(key) ?? false
|
||||
};
|
||||
});
|
||||
};
|
||||
var mapTypesResponse = (types, schema) => {
|
||||
if (typeof schema === "object" && ["void", "undefined", "null"].includes(schema.type))
|
||||
return;
|
||||
const responses = {};
|
||||
for (const type of types) {
|
||||
responses[type] = {
|
||||
schema: typeof schema === "string" ? {
|
||||
$ref: `#/components/schemas/${schema}`
|
||||
} : "$ref" in schema && Kind in schema && schema[Kind] === "Ref" ? {
|
||||
...schema,
|
||||
$ref: `#/components/schemas/${schema.$ref}`
|
||||
} : (0, import_elysia.replaceSchemaType)(
|
||||
{ ...schema },
|
||||
{
|
||||
from: import_elysia.t.Ref(""),
|
||||
// @ts-expect-error
|
||||
to: ({ $ref, ...options }) => {
|
||||
if (!$ref.startsWith(
|
||||
"#/components/schemas/"
|
||||
))
|
||||
return import_elysia.t.Ref(
|
||||
`#/components/schemas/${$ref}`,
|
||||
options
|
||||
);
|
||||
return import_elysia.t.Ref($ref, options);
|
||||
}
|
||||
}
|
||||
)
|
||||
};
|
||||
}
|
||||
return responses;
|
||||
};
|
||||
var capitalize = (word) => word.charAt(0).toUpperCase() + word.slice(1);
|
||||
var generateOperationId = (method, paths) => {
|
||||
let operationId = method.toLowerCase();
|
||||
if (paths === "/") return operationId + "Index";
|
||||
for (const path of paths.split("/")) {
|
||||
if (path.charCodeAt(0) === 123) {
|
||||
operationId += "By" + capitalize(path.slice(1, -1));
|
||||
} else {
|
||||
operationId += capitalize(path);
|
||||
}
|
||||
}
|
||||
return operationId;
|
||||
};
|
||||
var cloneHook = (hook) => {
|
||||
if (!hook) return;
|
||||
if (typeof hook === "string") return hook;
|
||||
if (Array.isArray(hook)) return [...hook];
|
||||
return { ...hook };
|
||||
};
|
||||
var registerSchemaPath = ({
|
||||
schema,
|
||||
path,
|
||||
method,
|
||||
hook,
|
||||
models
|
||||
}) => {
|
||||
hook = cloneHook(hook);
|
||||
if (hook.parse && !Array.isArray(hook.parse)) hook.parse = [hook.parse];
|
||||
let contentType = hook.parse?.map((x) => {
|
||||
switch (typeof x) {
|
||||
case "string":
|
||||
return x;
|
||||
case "object":
|
||||
if (x && typeof x?.fn !== "string")
|
||||
return;
|
||||
switch (x?.fn) {
|
||||
case "json":
|
||||
case "application/json":
|
||||
return "application/json";
|
||||
case "text":
|
||||
case "text/plain":
|
||||
return "text/plain";
|
||||
case "urlencoded":
|
||||
case "application/x-www-form-urlencoded":
|
||||
return "application/x-www-form-urlencoded";
|
||||
case "arrayBuffer":
|
||||
case "application/octet-stream":
|
||||
return "application/octet-stream";
|
||||
case "formdata":
|
||||
case "multipart/form-data":
|
||||
return "multipart/form-data";
|
||||
}
|
||||
}
|
||||
}).filter((x) => x !== void 0);
|
||||
if (!contentType || contentType.length === 0)
|
||||
contentType = ["application/json", "multipart/form-data", "text/plain"];
|
||||
path = toOpenAPIPath(path);
|
||||
const contentTypes = typeof contentType === "string" ? [contentType] : contentType ?? ["application/json"];
|
||||
const bodySchema = cloneHook(hook?.body);
|
||||
const paramsSchema = cloneHook(hook?.params);
|
||||
const headerSchema = cloneHook(hook?.headers);
|
||||
const querySchema = cloneHook(hook?.query);
|
||||
let responseSchema = cloneHook(hook?.response);
|
||||
if (typeof responseSchema === "object") {
|
||||
if (Kind in responseSchema) {
|
||||
const {
|
||||
type,
|
||||
properties,
|
||||
required,
|
||||
additionalProperties,
|
||||
patternProperties,
|
||||
$ref,
|
||||
...rest
|
||||
} = responseSchema;
|
||||
responseSchema = {
|
||||
"200": {
|
||||
...rest,
|
||||
description: rest.description,
|
||||
content: mapTypesResponse(
|
||||
contentTypes,
|
||||
type === "object" || type === "array" ? {
|
||||
type,
|
||||
properties,
|
||||
patternProperties,
|
||||
items: responseSchema.items,
|
||||
required
|
||||
} : responseSchema
|
||||
)
|
||||
}
|
||||
};
|
||||
} else {
|
||||
Object.entries(responseSchema).forEach(
|
||||
([key, value]) => {
|
||||
if (typeof value === "string") {
|
||||
if (!models[value]) return;
|
||||
const {
|
||||
type,
|
||||
properties,
|
||||
required,
|
||||
additionalProperties: _1,
|
||||
patternProperties: _2,
|
||||
...rest
|
||||
} = models[value];
|
||||
responseSchema[key] = {
|
||||
...rest,
|
||||
description: rest.description,
|
||||
content: mapTypesResponse(contentTypes, value)
|
||||
};
|
||||
} else {
|
||||
const {
|
||||
type,
|
||||
properties,
|
||||
required,
|
||||
additionalProperties,
|
||||
patternProperties,
|
||||
...rest
|
||||
} = value;
|
||||
responseSchema[key] = {
|
||||
...rest,
|
||||
description: rest.description,
|
||||
content: mapTypesResponse(
|
||||
contentTypes,
|
||||
type === "object" || type === "array" ? {
|
||||
type,
|
||||
properties,
|
||||
patternProperties,
|
||||
items: value.items,
|
||||
required
|
||||
} : value
|
||||
)
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
} else if (typeof responseSchema === "string") {
|
||||
if (!(responseSchema in models)) return;
|
||||
const {
|
||||
type,
|
||||
properties,
|
||||
required,
|
||||
$ref,
|
||||
additionalProperties: _1,
|
||||
patternProperties: _2,
|
||||
...rest
|
||||
} = models[responseSchema];
|
||||
responseSchema = {
|
||||
// @ts-ignore
|
||||
"200": {
|
||||
...rest,
|
||||
content: mapTypesResponse(contentTypes, responseSchema)
|
||||
}
|
||||
};
|
||||
}
|
||||
const parameters = [
|
||||
...mapProperties("header", headerSchema, models),
|
||||
...mapProperties("path", paramsSchema, models),
|
||||
...mapProperties("query", querySchema, models)
|
||||
];
|
||||
schema[path] = {
|
||||
...schema[path] ? schema[path] : {},
|
||||
[method.toLowerCase()]: {
|
||||
...headerSchema || paramsSchema || querySchema || bodySchema ? { parameters } : {},
|
||||
...responseSchema ? {
|
||||
responses: responseSchema
|
||||
} : {},
|
||||
operationId: hook?.detail?.operationId ?? generateOperationId(method, path),
|
||||
...hook?.detail,
|
||||
...bodySchema ? {
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: mapTypesResponse(
|
||||
contentTypes,
|
||||
typeof bodySchema === "string" ? {
|
||||
$ref: `#/components/schemas/${bodySchema}`
|
||||
} : bodySchema
|
||||
)
|
||||
}
|
||||
} : null
|
||||
}
|
||||
};
|
||||
};
|
||||
var filterPaths = (paths, {
|
||||
excludeStaticFile = true,
|
||||
exclude = []
|
||||
}) => {
|
||||
const newPaths = {};
|
||||
for (const [key, value] of Object.entries(paths))
|
||||
if (!exclude.some((x) => {
|
||||
if (typeof x === "string") return key === x;
|
||||
return x.test(key);
|
||||
}) && !key.includes("*") && (excludeStaticFile ? !key.includes(".") : true)) {
|
||||
Object.keys(value).forEach((method) => {
|
||||
const schema = value[method];
|
||||
if (key.includes("{")) {
|
||||
if (!schema.parameters) schema.parameters = [];
|
||||
schema.parameters = [
|
||||
...key.split("/").filter(
|
||||
(x) => x.startsWith("{") && !schema.parameters.find(
|
||||
(params) => params.in === "path" && params.name === x.slice(1, x.length - 1)
|
||||
)
|
||||
).map((x) => ({
|
||||
schema: { type: "string" },
|
||||
in: "path",
|
||||
name: x.slice(1, x.length - 1),
|
||||
required: true
|
||||
})),
|
||||
...schema.parameters
|
||||
];
|
||||
}
|
||||
if (!schema.responses)
|
||||
schema.responses = {
|
||||
200: {}
|
||||
};
|
||||
});
|
||||
newPaths[key] = value;
|
||||
}
|
||||
return newPaths;
|
||||
};
|
||||
// Annotate the CommonJS export names for ESM import in node:
|
||||
0 && (module.exports = {
|
||||
capitalize,
|
||||
filterPaths,
|
||||
generateOperationId,
|
||||
mapProperties,
|
||||
registerSchemaPath,
|
||||
toOpenAPIPath
|
||||
});
|
||||
34
dist/index.d.ts
vendored
Normal file
34
dist/index.d.ts
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
import { Elysia } from 'elysia';
|
||||
import type { ElysiaSwaggerConfig } from './types';
|
||||
/**
|
||||
* Plugin for [elysia](https://github.com/elysiajs/elysia) that auto-generate Swagger page.
|
||||
*
|
||||
* @see https://github.com/elysiajs/elysia-swagger
|
||||
*/
|
||||
export declare const swagger: <Path extends string = "/swagger">({ provider, scalarVersion, scalarCDN, scalarConfig, documentation, version, excludeStaticFile, path, specPath, exclude, swaggerOptions, theme, autoDarkMode, excludeMethods, excludeTags }?: ElysiaSwaggerConfig<Path>) => Elysia<"", {
|
||||
decorator: {};
|
||||
store: {};
|
||||
derive: {};
|
||||
resolve: {};
|
||||
}, {
|
||||
typebox: {};
|
||||
error: {};
|
||||
}, {
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
macro: {};
|
||||
macroFn: {};
|
||||
parser: {};
|
||||
}, {}, {
|
||||
derive: {};
|
||||
resolve: {};
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
}, {
|
||||
derive: {};
|
||||
resolve: {};
|
||||
schema: {};
|
||||
standaloneSchema: {};
|
||||
}>;
|
||||
export type { ElysiaSwaggerConfig };
|
||||
export default swagger;
|
||||
578
dist/index.mjs
vendored
Normal file
578
dist/index.mjs
vendored
Normal file
@@ -0,0 +1,578 @@
|
||||
// src/index.ts
|
||||
import { Elysia } from "elysia";
|
||||
|
||||
// src/swagger/index.ts
|
||||
function isSchemaObject(schema) {
|
||||
return "type" in schema || "properties" in schema || "items" in schema;
|
||||
}
|
||||
function isDateTimeProperty(key, schema) {
|
||||
return (key === "createdAt" || key === "updatedAt") && "anyOf" in schema && Array.isArray(schema.anyOf);
|
||||
}
|
||||
function transformDateProperties(schema) {
|
||||
if (!isSchemaObject(schema) || typeof schema !== "object" || schema === null) {
|
||||
return schema;
|
||||
}
|
||||
const newSchema = { ...schema };
|
||||
Object.entries(newSchema).forEach(([key, value]) => {
|
||||
if (isSchemaObject(value)) {
|
||||
if (isDateTimeProperty(key, value)) {
|
||||
const dateTimeFormat = value.anyOf?.find(
|
||||
(item) => isSchemaObject(item) && item.format === "date-time"
|
||||
);
|
||||
if (dateTimeFormat) {
|
||||
const dateTimeSchema = {
|
||||
type: "string",
|
||||
format: "date-time",
|
||||
default: dateTimeFormat.default
|
||||
};
|
||||
newSchema[key] = dateTimeSchema;
|
||||
}
|
||||
} else {
|
||||
newSchema[key] = transformDateProperties(value);
|
||||
}
|
||||
}
|
||||
});
|
||||
return newSchema;
|
||||
}
|
||||
var SwaggerUIRender = (info, version, theme, stringifiedSwaggerOptions, autoDarkMode) => {
|
||||
const swaggerOptions = JSON.parse(stringifiedSwaggerOptions);
|
||||
if (swaggerOptions.components && swaggerOptions.components.schemas) {
|
||||
swaggerOptions.components.schemas = Object.fromEntries(
|
||||
Object.entries(swaggerOptions.components.schemas).map(([key, schema]) => [
|
||||
key,
|
||||
transformDateProperties(schema)
|
||||
])
|
||||
);
|
||||
}
|
||||
const transformedStringifiedSwaggerOptions = JSON.stringify(swaggerOptions);
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>${info.title}</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="${info.description}"
|
||||
/>
|
||||
<meta
|
||||
name="og:description"
|
||||
content="${info.description}"
|
||||
/>
|
||||
${autoDarkMode && typeof theme === "string" ? `
|
||||
<style>
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background-color: #222;
|
||||
color: #faf9a;
|
||||
}
|
||||
.swagger-ui {
|
||||
filter: invert(92%) hue-rotate(180deg);
|
||||
}
|
||||
|
||||
.swagger-ui .microlight {
|
||||
filter: invert(100%) hue-rotate(180deg);
|
||||
}
|
||||
}
|
||||
</style>` : ""}
|
||||
${typeof theme === "string" ? `<link rel="stylesheet" href="${theme}" />` : `<link rel="stylesheet" media="(prefers-color-scheme: light)" href="${theme.light}" />
|
||||
<link rel="stylesheet" media="(prefers-color-scheme: dark)" href="${theme.dark}" />`}
|
||||
</head>
|
||||
<body>
|
||||
<div id="swagger-ui"></div>
|
||||
<script src="https://unpkg.com/swagger-ui-dist@${version}/swagger-ui-bundle.js" crossorigin></script>
|
||||
<script>
|
||||
window.onload = () => {
|
||||
window.ui = SwaggerUIBundle(${transformedStringifiedSwaggerOptions});
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
};
|
||||
|
||||
// src/scalar/index.ts
|
||||
import { elysiajsTheme } from "@scalar/themes";
|
||||
var ScalarRender = (info, version, config, cdn) => `<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>${info.title}</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="${info.description}"
|
||||
/>
|
||||
<meta
|
||||
name="og:description"
|
||||
content="${info.description}"
|
||||
/>
|
||||
<meta charset="utf-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1" />
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
${config.customCss ?? elysiajsTheme}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
<script src="${cdn ? cdn : `https://cdn.jsdelivr.net/npm/@scalar/api-reference@${version}/dist/browser/standalone.min.js`}" crossorigin></script>
|
||||
|
||||
<script>
|
||||
Scalar.createApiReference('#app', ${JSON.stringify(config)})
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
|
||||
// src/utils.ts
|
||||
import { replaceSchemaType, t } from "elysia";
|
||||
|
||||
// node_modules/@sinclair/typebox/build/esm/type/symbols/symbols.mjs
|
||||
var TransformKind = Symbol.for("TypeBox.Transform");
|
||||
var ReadonlyKind = Symbol.for("TypeBox.Readonly");
|
||||
var OptionalKind = Symbol.for("TypeBox.Optional");
|
||||
var Hint = Symbol.for("TypeBox.Hint");
|
||||
var Kind = Symbol.for("TypeBox.Kind");
|
||||
|
||||
// src/utils.ts
|
||||
var toOpenAPIPath = (path) => path.split("/").map((x) => {
|
||||
if (x.startsWith(":")) {
|
||||
x = x.slice(1, x.length);
|
||||
if (x.endsWith("?")) x = x.slice(0, -1);
|
||||
x = `{${x}}`;
|
||||
}
|
||||
return x;
|
||||
}).join("/");
|
||||
var mapProperties = (name, schema, models) => {
|
||||
if (schema === void 0) return [];
|
||||
if (typeof schema === "string")
|
||||
if (schema in models) schema = models[schema];
|
||||
else throw new Error(`Can't find model ${schema}`);
|
||||
return Object.entries(schema?.properties ?? []).map(([key, value]) => {
|
||||
const {
|
||||
type: valueType = void 0,
|
||||
description,
|
||||
examples,
|
||||
...schemaKeywords
|
||||
} = value;
|
||||
return {
|
||||
// @ts-ignore
|
||||
description,
|
||||
examples,
|
||||
schema: { type: valueType, ...schemaKeywords },
|
||||
in: name,
|
||||
name: key,
|
||||
// @ts-ignore
|
||||
required: schema.required?.includes(key) ?? false
|
||||
};
|
||||
});
|
||||
};
|
||||
var mapTypesResponse = (types, schema) => {
|
||||
if (typeof schema === "object" && ["void", "undefined", "null"].includes(schema.type))
|
||||
return;
|
||||
const responses = {};
|
||||
for (const type of types) {
|
||||
responses[type] = {
|
||||
schema: typeof schema === "string" ? {
|
||||
$ref: `#/components/schemas/${schema}`
|
||||
} : "$ref" in schema && Kind in schema && schema[Kind] === "Ref" ? {
|
||||
...schema,
|
||||
$ref: `#/components/schemas/${schema.$ref}`
|
||||
} : replaceSchemaType(
|
||||
{ ...schema },
|
||||
{
|
||||
from: t.Ref(""),
|
||||
// @ts-expect-error
|
||||
to: ({ $ref, ...options }) => {
|
||||
if (!$ref.startsWith(
|
||||
"#/components/schemas/"
|
||||
))
|
||||
return t.Ref(
|
||||
`#/components/schemas/${$ref}`,
|
||||
options
|
||||
);
|
||||
return t.Ref($ref, options);
|
||||
}
|
||||
}
|
||||
)
|
||||
};
|
||||
}
|
||||
return responses;
|
||||
};
|
||||
var capitalize = (word) => word.charAt(0).toUpperCase() + word.slice(1);
|
||||
var generateOperationId = (method, paths) => {
|
||||
let operationId = method.toLowerCase();
|
||||
if (paths === "/") return operationId + "Index";
|
||||
for (const path of paths.split("/")) {
|
||||
if (path.charCodeAt(0) === 123) {
|
||||
operationId += "By" + capitalize(path.slice(1, -1));
|
||||
} else {
|
||||
operationId += capitalize(path);
|
||||
}
|
||||
}
|
||||
return operationId;
|
||||
};
|
||||
var cloneHook = (hook) => {
|
||||
if (!hook) return;
|
||||
if (typeof hook === "string") return hook;
|
||||
if (Array.isArray(hook)) return [...hook];
|
||||
return { ...hook };
|
||||
};
|
||||
var registerSchemaPath = ({
|
||||
schema,
|
||||
path,
|
||||
method,
|
||||
hook,
|
||||
models
|
||||
}) => {
|
||||
hook = cloneHook(hook);
|
||||
if (hook.parse && !Array.isArray(hook.parse)) hook.parse = [hook.parse];
|
||||
let contentType = hook.parse?.map((x) => {
|
||||
switch (typeof x) {
|
||||
case "string":
|
||||
return x;
|
||||
case "object":
|
||||
if (x && typeof x?.fn !== "string")
|
||||
return;
|
||||
switch (x?.fn) {
|
||||
case "json":
|
||||
case "application/json":
|
||||
return "application/json";
|
||||
case "text":
|
||||
case "text/plain":
|
||||
return "text/plain";
|
||||
case "urlencoded":
|
||||
case "application/x-www-form-urlencoded":
|
||||
return "application/x-www-form-urlencoded";
|
||||
case "arrayBuffer":
|
||||
case "application/octet-stream":
|
||||
return "application/octet-stream";
|
||||
case "formdata":
|
||||
case "multipart/form-data":
|
||||
return "multipart/form-data";
|
||||
}
|
||||
}
|
||||
}).filter((x) => x !== void 0);
|
||||
if (!contentType || contentType.length === 0)
|
||||
contentType = ["application/json", "multipart/form-data", "text/plain"];
|
||||
path = toOpenAPIPath(path);
|
||||
const contentTypes = typeof contentType === "string" ? [contentType] : contentType ?? ["application/json"];
|
||||
const bodySchema = cloneHook(hook?.body);
|
||||
const paramsSchema = cloneHook(hook?.params);
|
||||
const headerSchema = cloneHook(hook?.headers);
|
||||
const querySchema = cloneHook(hook?.query);
|
||||
let responseSchema = cloneHook(hook?.response);
|
||||
if (typeof responseSchema === "object") {
|
||||
if (Kind in responseSchema) {
|
||||
const {
|
||||
type,
|
||||
properties,
|
||||
required,
|
||||
additionalProperties,
|
||||
patternProperties,
|
||||
$ref,
|
||||
...rest
|
||||
} = responseSchema;
|
||||
responseSchema = {
|
||||
"200": {
|
||||
...rest,
|
||||
description: rest.description,
|
||||
content: mapTypesResponse(
|
||||
contentTypes,
|
||||
type === "object" || type === "array" ? {
|
||||
type,
|
||||
properties,
|
||||
patternProperties,
|
||||
items: responseSchema.items,
|
||||
required
|
||||
} : responseSchema
|
||||
)
|
||||
}
|
||||
};
|
||||
} else {
|
||||
Object.entries(responseSchema).forEach(
|
||||
([key, value]) => {
|
||||
if (typeof value === "string") {
|
||||
if (!models[value]) return;
|
||||
const {
|
||||
type,
|
||||
properties,
|
||||
required,
|
||||
additionalProperties: _1,
|
||||
patternProperties: _2,
|
||||
...rest
|
||||
} = models[value];
|
||||
responseSchema[key] = {
|
||||
...rest,
|
||||
description: rest.description,
|
||||
content: mapTypesResponse(contentTypes, value)
|
||||
};
|
||||
} else {
|
||||
const {
|
||||
type,
|
||||
properties,
|
||||
required,
|
||||
additionalProperties,
|
||||
patternProperties,
|
||||
...rest
|
||||
} = value;
|
||||
responseSchema[key] = {
|
||||
...rest,
|
||||
description: rest.description,
|
||||
content: mapTypesResponse(
|
||||
contentTypes,
|
||||
type === "object" || type === "array" ? {
|
||||
type,
|
||||
properties,
|
||||
patternProperties,
|
||||
items: value.items,
|
||||
required
|
||||
} : value
|
||||
)
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
} else if (typeof responseSchema === "string") {
|
||||
if (!(responseSchema in models)) return;
|
||||
const {
|
||||
type,
|
||||
properties,
|
||||
required,
|
||||
$ref,
|
||||
additionalProperties: _1,
|
||||
patternProperties: _2,
|
||||
...rest
|
||||
} = models[responseSchema];
|
||||
responseSchema = {
|
||||
// @ts-ignore
|
||||
"200": {
|
||||
...rest,
|
||||
content: mapTypesResponse(contentTypes, responseSchema)
|
||||
}
|
||||
};
|
||||
}
|
||||
const parameters = [
|
||||
...mapProperties("header", headerSchema, models),
|
||||
...mapProperties("path", paramsSchema, models),
|
||||
...mapProperties("query", querySchema, models)
|
||||
];
|
||||
schema[path] = {
|
||||
...schema[path] ? schema[path] : {},
|
||||
[method.toLowerCase()]: {
|
||||
...headerSchema || paramsSchema || querySchema || bodySchema ? { parameters } : {},
|
||||
...responseSchema ? {
|
||||
responses: responseSchema
|
||||
} : {},
|
||||
operationId: hook?.detail?.operationId ?? generateOperationId(method, path),
|
||||
...hook?.detail,
|
||||
...bodySchema ? {
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: mapTypesResponse(
|
||||
contentTypes,
|
||||
typeof bodySchema === "string" ? {
|
||||
$ref: `#/components/schemas/${bodySchema}`
|
||||
} : bodySchema
|
||||
)
|
||||
}
|
||||
} : null
|
||||
}
|
||||
};
|
||||
};
|
||||
var filterPaths = (paths, {
|
||||
excludeStaticFile = true,
|
||||
exclude = []
|
||||
}) => {
|
||||
const newPaths = {};
|
||||
for (const [key, value] of Object.entries(paths))
|
||||
if (!exclude.some((x) => {
|
||||
if (typeof x === "string") return key === x;
|
||||
return x.test(key);
|
||||
}) && !key.includes("*") && (excludeStaticFile ? !key.includes(".") : true)) {
|
||||
Object.keys(value).forEach((method) => {
|
||||
const schema = value[method];
|
||||
if (key.includes("{")) {
|
||||
if (!schema.parameters) schema.parameters = [];
|
||||
schema.parameters = [
|
||||
...key.split("/").filter(
|
||||
(x) => x.startsWith("{") && !schema.parameters.find(
|
||||
(params) => params.in === "path" && params.name === x.slice(1, x.length - 1)
|
||||
)
|
||||
).map((x) => ({
|
||||
schema: { type: "string" },
|
||||
in: "path",
|
||||
name: x.slice(1, x.length - 1),
|
||||
required: true
|
||||
})),
|
||||
...schema.parameters
|
||||
];
|
||||
}
|
||||
if (!schema.responses)
|
||||
schema.responses = {
|
||||
200: {}
|
||||
};
|
||||
});
|
||||
newPaths[key] = value;
|
||||
}
|
||||
return newPaths;
|
||||
};
|
||||
|
||||
// src/index.ts
|
||||
var swagger = ({
|
||||
provider = "scalar",
|
||||
scalarVersion = "latest",
|
||||
scalarCDN = "",
|
||||
scalarConfig = {},
|
||||
documentation = {},
|
||||
version = "5.9.0",
|
||||
excludeStaticFile = true,
|
||||
path = "/swagger",
|
||||
specPath = `${path}/json`,
|
||||
exclude = [],
|
||||
swaggerOptions = {},
|
||||
theme = `https://unpkg.com/swagger-ui-dist@${version}/swagger-ui.css`,
|
||||
autoDarkMode = true,
|
||||
excludeMethods = ["OPTIONS"],
|
||||
excludeTags = []
|
||||
} = {}) => {
|
||||
const schema = {};
|
||||
let totalRoutes = 0;
|
||||
if (!version)
|
||||
version = `https://unpkg.com/swagger-ui-dist@${version}/swagger-ui.css`;
|
||||
const info = {
|
||||
title: "Elysia Documentation",
|
||||
description: "Development documentation",
|
||||
version: "0.0.0",
|
||||
...documentation.info
|
||||
};
|
||||
const app = new Elysia({ name: "@elysiajs/swagger" });
|
||||
const page = new Response(
|
||||
provider === "swagger-ui" ? SwaggerUIRender(
|
||||
info,
|
||||
version,
|
||||
theme,
|
||||
JSON.stringify(
|
||||
{
|
||||
url: specPath,
|
||||
dom_id: "#swagger-ui",
|
||||
...swaggerOptions
|
||||
},
|
||||
(_, value) => typeof value === "function" ? void 0 : value
|
||||
),
|
||||
autoDarkMode
|
||||
) : ScalarRender(
|
||||
info,
|
||||
scalarVersion,
|
||||
{
|
||||
sources: [{ url: specPath }],
|
||||
...scalarConfig,
|
||||
// so we can showcase the elysia theme
|
||||
_integration: "elysiajs"
|
||||
},
|
||||
scalarCDN
|
||||
),
|
||||
{
|
||||
headers: {
|
||||
"content-type": "text/html; charset=utf8"
|
||||
}
|
||||
}
|
||||
);
|
||||
app.get(path, page, {
|
||||
detail: {
|
||||
hide: true
|
||||
}
|
||||
}).get(
|
||||
specPath,
|
||||
function openAPISchema() {
|
||||
const routes = app.getGlobalRoutes();
|
||||
if (routes.length !== totalRoutes) {
|
||||
const ALLOWED_METHODS = [
|
||||
"GET",
|
||||
"PUT",
|
||||
"POST",
|
||||
"DELETE",
|
||||
"OPTIONS",
|
||||
"HEAD",
|
||||
"PATCH",
|
||||
"TRACE"
|
||||
];
|
||||
totalRoutes = routes.length;
|
||||
routes.forEach((route) => {
|
||||
if (route.hooks?.detail?.hide === true) return;
|
||||
if (excludeMethods.includes(route.method)) return;
|
||||
if (ALLOWED_METHODS.includes(route.method) === false && route.method !== "ALL")
|
||||
return;
|
||||
if (route.method === "ALL")
|
||||
ALLOWED_METHODS.forEach((method) => {
|
||||
registerSchemaPath({
|
||||
schema,
|
||||
hook: route.hooks,
|
||||
method,
|
||||
path: route.path,
|
||||
// @ts-ignore
|
||||
models: app.getGlobalDefinitions?.().type,
|
||||
contentType: route.hooks.type
|
||||
});
|
||||
});
|
||||
else
|
||||
registerSchemaPath({
|
||||
schema,
|
||||
hook: route.hooks,
|
||||
method: route.method,
|
||||
path: route.path,
|
||||
// @ts-ignore
|
||||
models: app.getGlobalDefinitions?.().type,
|
||||
contentType: route.hooks.type
|
||||
});
|
||||
});
|
||||
}
|
||||
return {
|
||||
openapi: "3.0.3",
|
||||
...{
|
||||
...documentation,
|
||||
tags: documentation.tags?.filter(
|
||||
(tag) => !excludeTags?.includes(tag?.name)
|
||||
),
|
||||
info: {
|
||||
title: "Elysia Documentation",
|
||||
description: "Development documentation",
|
||||
version: "0.0.0",
|
||||
...documentation.info
|
||||
}
|
||||
},
|
||||
paths: {
|
||||
...filterPaths(schema, {
|
||||
excludeStaticFile,
|
||||
exclude: Array.isArray(exclude) ? exclude : [exclude]
|
||||
}),
|
||||
...documentation.paths
|
||||
},
|
||||
components: {
|
||||
...documentation.components,
|
||||
schemas: {
|
||||
// @ts-ignore
|
||||
...app.getGlobalDefinitions?.().type,
|
||||
...documentation.components?.schemas
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
{
|
||||
detail: {
|
||||
hide: true
|
||||
}
|
||||
}
|
||||
);
|
||||
return app;
|
||||
};
|
||||
var index_default = swagger;
|
||||
export {
|
||||
index_default as default,
|
||||
swagger
|
||||
};
|
||||
3
dist/scalar/index.d.ts
vendored
Normal file
3
dist/scalar/index.d.ts
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
import type { OpenAPIV3 } from 'openapi-types';
|
||||
import type { ApiReferenceConfigurationWithSources } from '@scalar/types/api-reference' with { "resolution-mode": "import" };
|
||||
export declare const ScalarRender: (info: OpenAPIV3.InfoObject, version: string, config: Partial<ApiReferenceConfigurationWithSources>, cdn: string) => string;
|
||||
40
dist/scalar/index.mjs
vendored
Normal file
40
dist/scalar/index.mjs
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
// src/scalar/index.ts
|
||||
import { elysiajsTheme } from "@scalar/themes";
|
||||
var ScalarRender = (info, version, config, cdn) => `<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>${info.title}</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="${info.description}"
|
||||
/>
|
||||
<meta
|
||||
name="og:description"
|
||||
content="${info.description}"
|
||||
/>
|
||||
<meta charset="utf-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1" />
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
${config.customCss ?? elysiajsTheme}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
<script src="${cdn ? cdn : `https://cdn.jsdelivr.net/npm/@scalar/api-reference@${version}/dist/browser/standalone.min.js`}" crossorigin></script>
|
||||
|
||||
<script>
|
||||
Scalar.createApiReference('#app', ${JSON.stringify(config)})
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
export {
|
||||
ScalarRender
|
||||
};
|
||||
5
dist/swagger/index.d.ts
vendored
Normal file
5
dist/swagger/index.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
import { OpenAPIV3 } from 'openapi-types';
|
||||
export declare const SwaggerUIRender: (info: OpenAPIV3.InfoObject, version: string, theme: string | {
|
||||
light: string;
|
||||
dark: string;
|
||||
}, stringifiedSwaggerOptions: string, autoDarkMode?: boolean) => string;
|
||||
91
dist/swagger/index.mjs
vendored
Normal file
91
dist/swagger/index.mjs
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
// src/swagger/index.ts
|
||||
function isSchemaObject(schema) {
|
||||
return "type" in schema || "properties" in schema || "items" in schema;
|
||||
}
|
||||
function isDateTimeProperty(key, schema) {
|
||||
return (key === "createdAt" || key === "updatedAt") && "anyOf" in schema && Array.isArray(schema.anyOf);
|
||||
}
|
||||
function transformDateProperties(schema) {
|
||||
if (!isSchemaObject(schema) || typeof schema !== "object" || schema === null) {
|
||||
return schema;
|
||||
}
|
||||
const newSchema = { ...schema };
|
||||
Object.entries(newSchema).forEach(([key, value]) => {
|
||||
if (isSchemaObject(value)) {
|
||||
if (isDateTimeProperty(key, value)) {
|
||||
const dateTimeFormat = value.anyOf?.find(
|
||||
(item) => isSchemaObject(item) && item.format === "date-time"
|
||||
);
|
||||
if (dateTimeFormat) {
|
||||
const dateTimeSchema = {
|
||||
type: "string",
|
||||
format: "date-time",
|
||||
default: dateTimeFormat.default
|
||||
};
|
||||
newSchema[key] = dateTimeSchema;
|
||||
}
|
||||
} else {
|
||||
newSchema[key] = transformDateProperties(value);
|
||||
}
|
||||
}
|
||||
});
|
||||
return newSchema;
|
||||
}
|
||||
var SwaggerUIRender = (info, version, theme, stringifiedSwaggerOptions, autoDarkMode) => {
|
||||
const swaggerOptions = JSON.parse(stringifiedSwaggerOptions);
|
||||
if (swaggerOptions.components && swaggerOptions.components.schemas) {
|
||||
swaggerOptions.components.schemas = Object.fromEntries(
|
||||
Object.entries(swaggerOptions.components.schemas).map(([key, schema]) => [
|
||||
key,
|
||||
transformDateProperties(schema)
|
||||
])
|
||||
);
|
||||
}
|
||||
const transformedStringifiedSwaggerOptions = JSON.stringify(swaggerOptions);
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>${info.title}</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="${info.description}"
|
||||
/>
|
||||
<meta
|
||||
name="og:description"
|
||||
content="${info.description}"
|
||||
/>
|
||||
${autoDarkMode && typeof theme === "string" ? `
|
||||
<style>
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body {
|
||||
background-color: #222;
|
||||
color: #faf9a;
|
||||
}
|
||||
.swagger-ui {
|
||||
filter: invert(92%) hue-rotate(180deg);
|
||||
}
|
||||
|
||||
.swagger-ui .microlight {
|
||||
filter: invert(100%) hue-rotate(180deg);
|
||||
}
|
||||
}
|
||||
</style>` : ""}
|
||||
${typeof theme === "string" ? `<link rel="stylesheet" href="${theme}" />` : `<link rel="stylesheet" media="(prefers-color-scheme: light)" href="${theme.light}" />
|
||||
<link rel="stylesheet" media="(prefers-color-scheme: dark)" href="${theme.dark}" />`}
|
||||
</head>
|
||||
<body>
|
||||
<div id="swagger-ui"></div>
|
||||
<script src="https://unpkg.com/swagger-ui-dist@${version}/swagger-ui-bundle.js" crossorigin></script>
|
||||
<script>
|
||||
window.onload = () => {
|
||||
window.ui = SwaggerUIBundle(${transformedStringifiedSwaggerOptions});
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
</html>`;
|
||||
};
|
||||
export {
|
||||
SwaggerUIRender
|
||||
};
|
||||
274
dist/swagger/types.d.ts
vendored
Normal file
274
dist/swagger/types.d.ts
vendored
Normal file
@@ -0,0 +1,274 @@
|
||||
/**
|
||||
* Swagger UI type because swagger-ui doesn't export an interface so here's copy paste
|
||||
*
|
||||
* @see swagger-ui/index.d.ts
|
||||
**/
|
||||
export interface SwaggerUIOptions {
|
||||
/**
|
||||
* URL to fetch external configuration document from.
|
||||
*/
|
||||
configUrl?: string | undefined;
|
||||
/**
|
||||
* REQUIRED if domNode is not provided. The ID of a DOM element inside which SwaggerUI will put its user interface.
|
||||
*/
|
||||
dom_id?: string | undefined;
|
||||
/**
|
||||
* A JavaScript object describing the OpenAPI definition. When used, the url parameter will not be parsed. This is useful for testing manually-generated definitions without hosting them
|
||||
*/
|
||||
spec?: {
|
||||
[propName: string]: any;
|
||||
} | undefined;
|
||||
/**
|
||||
* The URL pointing to API definition (normally swagger.json or swagger.yaml). Will be ignored if urls or spec is used.
|
||||
*/
|
||||
url?: string | undefined;
|
||||
/**
|
||||
* An array of API definition objects ([{url: "<url1>", name: "<name1>"},{url: "<url2>", name: "<name2>"}])
|
||||
* used by Topbar plugin. When used and Topbar plugin is enabled, the url parameter will not be parsed.
|
||||
* Names and URLs must be unique among all items in this array, since they're used as identifiers.
|
||||
*/
|
||||
urls?: Array<{
|
||||
url: string;
|
||||
name: string;
|
||||
}> | undefined;
|
||||
/**
|
||||
* The name of a component available via the plugin system to use as the top-level layout
|
||||
* for Swagger UI.
|
||||
*/
|
||||
layout?: string | undefined;
|
||||
/**
|
||||
* A Javascript object to configure plugin integration and behaviors
|
||||
*/
|
||||
pluginsOptions?: PluginsOptions;
|
||||
/**
|
||||
* An array of plugin functions to use in Swagger UI.
|
||||
*/
|
||||
plugins?: SwaggerUIPlugin[] | undefined;
|
||||
/**
|
||||
* An array of presets to use in Swagger UI.
|
||||
* Usually, you'll want to include ApisPreset if you use this option.
|
||||
*/
|
||||
presets?: SwaggerUIPlugin[] | undefined;
|
||||
/**
|
||||
* If set to true, enables deep linking for tags and operations.
|
||||
* See the Deep Linking documentation for more information.
|
||||
*/
|
||||
deepLinking?: boolean | undefined;
|
||||
/**
|
||||
* Controls the display of operationId in operations list. The default is false.
|
||||
*/
|
||||
displayOperationId?: boolean | undefined;
|
||||
/**
|
||||
* The default expansion depth for models (set to -1 completely hide the models).
|
||||
*/
|
||||
defaultModelsExpandDepth?: number | undefined;
|
||||
/**
|
||||
* The default expansion depth for the model on the model-example section.
|
||||
*/
|
||||
defaultModelExpandDepth?: number | undefined;
|
||||
/**
|
||||
* Controls how the model is shown when the API is first rendered.
|
||||
* (The user can always switch the rendering for a given model by clicking the
|
||||
* 'Model' and 'Example Value' links.)
|
||||
*/
|
||||
defaultModelRendering?: 'example' | 'model' | undefined;
|
||||
/**
|
||||
* Controls the display of the request duration (in milliseconds) for "Try it out" requests.
|
||||
*/
|
||||
displayRequestDuration?: boolean | undefined;
|
||||
/**
|
||||
* Controls the default expansion setting for the operations and tags.
|
||||
* It can be 'list' (expands only the tags), 'full' (expands the tags and operations)
|
||||
* or 'none' (expands nothing).
|
||||
*/
|
||||
docExpansion?: 'list' | 'full' | 'none' | undefined;
|
||||
/**
|
||||
* If set, enables filtering.
|
||||
* The top bar will show an edit box that you can use to filter the tagged operations that are shown.
|
||||
* Can be Boolean to enable or disable, or a string, in which case filtering will be enabled
|
||||
* using that string as the filter expression.
|
||||
* Filtering is case sensitive matching the filter expression anywhere inside the tag.
|
||||
*/
|
||||
filter?: boolean | string | undefined;
|
||||
/**
|
||||
* If set, limits the number of tagged operations displayed to at most this many.
|
||||
* The default is to show all operations.
|
||||
*/
|
||||
maxDisplayedTags?: number | undefined;
|
||||
/**
|
||||
* Apply a sort to the operation list of each API.
|
||||
* It can be 'alpha' (sort by paths alphanumerically),
|
||||
* 'method' (sort by HTTP method) or a function (see Array.prototype.sort() to know how sort function works).
|
||||
* Default is the order returned by the server unchanged.
|
||||
*/
|
||||
operationsSorter?: SorterLike | undefined;
|
||||
/**
|
||||
* Controls the display of vendor extension (x-) fields and values for Operations,
|
||||
* Parameters, Responses, and Schema.
|
||||
*/
|
||||
showExtensions?: boolean | undefined;
|
||||
/**
|
||||
* Controls the display of extensions (pattern, maxLength, minLength, maximum, minimum) fields
|
||||
* and values for Parameters.
|
||||
*/
|
||||
showCommonExtensions?: boolean | undefined;
|
||||
/**
|
||||
* Apply a sort to the tag list of each API.
|
||||
* It can be 'alpha' (sort by paths alphanumerically)
|
||||
* or a function (see Array.prototype.sort() to learn how to write a sort function).
|
||||
* Two tag name strings are passed to the sorter for each pass.
|
||||
* Default is the order determined by Swagger UI.
|
||||
*/
|
||||
tagsSorter?: SorterLike | undefined;
|
||||
/**
|
||||
* When enabled, sanitizer will leave style, class and data-* attributes untouched
|
||||
* on all HTML Elements declared inside markdown strings.
|
||||
* This parameter is Deprecated and will be removed in 4.0.0.
|
||||
* @deprecated
|
||||
*/
|
||||
useUnsafeMarkdown?: boolean | undefined;
|
||||
/**
|
||||
* Provides a mechanism to be notified when Swagger UI has finished rendering a newly provided definition.
|
||||
*/
|
||||
onComplete?: (() => any) | undefined;
|
||||
/**
|
||||
* Set to false to deactivate syntax highlighting of payloads and cURL command,
|
||||
* can be otherwise an object with the activate and theme properties.
|
||||
*/
|
||||
syntaxHighlight?: false | {
|
||||
/**
|
||||
* Whether syntax highlighting should be activated or not.
|
||||
*/
|
||||
activate?: boolean | undefined;
|
||||
/**
|
||||
* Highlight.js syntax coloring theme to use. (Only these 6 styles are available.)
|
||||
*/
|
||||
theme?: 'agate' | 'arta' | 'idea' | 'monokai' | 'nord' | 'obsidian' | 'tomorrow-night' | undefined;
|
||||
} | undefined;
|
||||
/**
|
||||
* Controls whether the "Try it out" section should be enabled by default.
|
||||
*/
|
||||
tryItOutEnabled?: boolean | undefined;
|
||||
/**
|
||||
* This is the default configuration section for the the requestSnippets plugin.
|
||||
*/
|
||||
requestSnippets?: {
|
||||
generators?: {
|
||||
[genName: string]: {
|
||||
title: string;
|
||||
syntax: string;
|
||||
};
|
||||
} | undefined;
|
||||
defaultExpanded?: boolean | undefined;
|
||||
/**
|
||||
* e.g. only show curl bash = ["curl_bash"]
|
||||
*/
|
||||
languagesMask?: string[] | undefined;
|
||||
} | undefined;
|
||||
/**
|
||||
* OAuth redirect URL.
|
||||
*/
|
||||
oauth2RedirectUrl?: string | undefined;
|
||||
/**
|
||||
* MUST be a function. Function to intercept remote definition,
|
||||
* "Try it out", and OAuth 2.0 requests.
|
||||
* Accepts one argument requestInterceptor(request) and must return the modified request,
|
||||
* or a Promise that resolves to the modified request.
|
||||
*/
|
||||
requestInterceptor?: ((a: Request) => Request | Promise<Request>) | undefined;
|
||||
/**
|
||||
* MUST be a function. Function to intercept remote definition,
|
||||
* "Try it out", and OAuth 2.0 responses.
|
||||
* Accepts one argument responseInterceptor(response) and must return the modified response,
|
||||
* or a Promise that resolves to the modified response.
|
||||
*/
|
||||
responseInterceptor?: ((a: Response) => Response | Promise<Response>) | undefined;
|
||||
/**
|
||||
* If set to true, uses the mutated request returned from a requestInterceptor
|
||||
* to produce the curl command in the UI, otherwise the request
|
||||
* beforethe requestInterceptor was applied is used.
|
||||
*/
|
||||
showMutatedRequest?: boolean | undefined;
|
||||
/**
|
||||
* List of HTTP methods that have the "Try it out" feature enabled.
|
||||
* An empty array disables "Try it out" for all operations.
|
||||
* This does not filter the operations from the display.
|
||||
*/
|
||||
supportedSubmitMethods?: SupportedHTTPMethods[] | undefined;
|
||||
/**
|
||||
* By default, Swagger UI attempts to validate specs against swagger.io's online validator.
|
||||
* You can use this parameter to set a different validator URL,
|
||||
* for example for locally deployed validators (Validator Badge).
|
||||
* Setting it to either none, 127.0.0.1 or localhost will disable validation.
|
||||
*/
|
||||
validatorUrl?: string | undefined;
|
||||
/**
|
||||
* If set to true, enables passing credentials, as defined in the Fetch standard,
|
||||
* in CORS requests that are sent by the browser.
|
||||
* Note that Swagger UI cannot currently set cookies cross-domain (see swagger-js#1163)
|
||||
* - as a result, you will have to rely on browser-supplied
|
||||
* cookies (which this setting enables sending) that Swagger UI cannot control.
|
||||
*/
|
||||
withCredentials?: boolean | undefined;
|
||||
/**
|
||||
* Function to set default values to each property in model.
|
||||
* Accepts one argument modelPropertyMacro(property), property is immutable
|
||||
*/
|
||||
modelPropertyMacro?: ((propName: Readonly<any>) => any) | undefined;
|
||||
/**
|
||||
* Function to set default value to parameters.
|
||||
* Accepts two arguments parameterMacro(operation, parameter).
|
||||
* Operation and parameter are objects passed for context, both remain immutable
|
||||
*/
|
||||
parameterMacro?: ((operation: Readonly<any>, parameter: Readonly<any>) => any) | undefined;
|
||||
/**
|
||||
* If set to true, it persists authorization data and it would not be lost on browser close/refresh
|
||||
*/
|
||||
persistAuthorization?: boolean | undefined;
|
||||
}
|
||||
interface PluginsOptions {
|
||||
/**
|
||||
* Control behavior of plugins when targeting the same component with wrapComponent.<br/>
|
||||
* - `legacy` (default) : last plugin takes precedence over the others<br/>
|
||||
* - `chain` : chain wrapComponents when targeting the same core component,
|
||||
* allowing multiple plugins to wrap the same component
|
||||
* @default 'legacy'
|
||||
*/
|
||||
pluginLoadType?: PluginLoadType;
|
||||
}
|
||||
type PluginLoadType = 'legacy' | 'chain';
|
||||
type SupportedHTTPMethods = 'get' | 'put' | 'post' | 'delete' | 'options' | 'head' | 'patch' | 'trace';
|
||||
type SorterLike = 'alpha' | 'method' | {
|
||||
(name1: string, name2: string): number;
|
||||
};
|
||||
interface Request {
|
||||
[prop: string]: any;
|
||||
}
|
||||
interface Response {
|
||||
[prop: string]: any;
|
||||
}
|
||||
/**
|
||||
* See https://swagger.io/docs/open-source-tools/swagger-ui/customization/plugin-api/
|
||||
*/
|
||||
interface SwaggerUIPlugin {
|
||||
(system: any): {
|
||||
statePlugins?: {
|
||||
[stateKey: string]: {
|
||||
actions?: Indexable | undefined;
|
||||
reducers?: Indexable | undefined;
|
||||
selectors?: Indexable | undefined;
|
||||
wrapActions?: Indexable | undefined;
|
||||
wrapSelectors?: Indexable | undefined;
|
||||
};
|
||||
} | undefined;
|
||||
components?: Indexable | undefined;
|
||||
wrapComponents?: Indexable | undefined;
|
||||
rootInjects?: Indexable | undefined;
|
||||
afterLoad?: ((system: any) => any) | undefined;
|
||||
fn?: Indexable | undefined;
|
||||
};
|
||||
}
|
||||
interface Indexable {
|
||||
[index: string]: any;
|
||||
}
|
||||
export {};
|
||||
0
dist/swagger/types.mjs
vendored
Normal file
0
dist/swagger/types.mjs
vendored
Normal file
102
dist/types.d.ts
vendored
Normal file
102
dist/types.d.ts
vendored
Normal file
@@ -0,0 +1,102 @@
|
||||
import type { OpenAPIV3 } from 'openapi-types';
|
||||
import type { ApiReferenceConfigurationWithSources } from '@scalar/types/api-reference' with { "resolution-mode": "import" };
|
||||
import type { SwaggerUIOptions } from './swagger/types';
|
||||
export interface ElysiaSwaggerConfig<Path extends string = '/swagger'> {
|
||||
/**
|
||||
* Customize Swagger config, refers to Swagger 2.0 config
|
||||
*
|
||||
* @see https://swagger.io/specification/v2/
|
||||
*/
|
||||
documentation?: Omit<Partial<OpenAPIV3.Document>, 'x-express-openapi-additional-middleware' | 'x-express-openapi-validation-strict'>;
|
||||
/**
|
||||
* Choose your provider, Scalar or Swagger UI
|
||||
*
|
||||
* @default 'scalar'
|
||||
* @see https://github.com/scalar/scalar
|
||||
* @see https://github.com/swagger-api/swagger-ui
|
||||
*/
|
||||
provider?: 'scalar' | 'swagger-ui';
|
||||
/**
|
||||
* Version to use for Scalar cdn bundle
|
||||
*
|
||||
* @default 'latest'
|
||||
* @see https://github.com/scalar/scalar
|
||||
*/
|
||||
scalarVersion?: string;
|
||||
/**
|
||||
* Optional override to specifying the path for the Scalar bundle
|
||||
*
|
||||
* Custom URL or path to locally hosted Scalar bundle
|
||||
*
|
||||
* Lease blank to use default jsdeliver.net CDN
|
||||
*
|
||||
* @default ''
|
||||
* @example 'https://unpkg.com/@scalar/api-reference@1.13.10/dist/browser/standalone.js'
|
||||
* @example '/public/standalone.js'
|
||||
* @see https://github.com/scalar/scalar
|
||||
*/
|
||||
scalarCDN?: string;
|
||||
/**
|
||||
* Scalar configuration to customize scalar
|
||||
*'
|
||||
* @see https://github.com/scalar/scalar/blob/main/documentation/configuration.md
|
||||
*/
|
||||
scalarConfig?: Partial<ApiReferenceConfigurationWithSources>;
|
||||
/**
|
||||
* Version to use for swagger cdn bundle
|
||||
*
|
||||
* @see unpkg.com/swagger-ui-dist
|
||||
*
|
||||
* @default 4.18.2
|
||||
*/
|
||||
version?: string;
|
||||
/**
|
||||
* Determine if Swagger should exclude static files.
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
excludeStaticFile?: boolean;
|
||||
/**
|
||||
* The endpoint to expose OpenAPI Documentation
|
||||
*
|
||||
* @default '/swagger'
|
||||
*/
|
||||
path?: Path;
|
||||
/**
|
||||
* The endpoint to expose OpenAPI JSON specification
|
||||
*
|
||||
* @default '/${path}/json'
|
||||
*/
|
||||
specPath?: string;
|
||||
/**
|
||||
* Paths to exclude from Swagger endpoint
|
||||
*
|
||||
* @default []
|
||||
*/
|
||||
exclude?: string | RegExp | (string | RegExp)[];
|
||||
/**
|
||||
* Options to send to SwaggerUIBundle
|
||||
* Currently, options that are defined as functions such as requestInterceptor
|
||||
* and onComplete are not supported.
|
||||
*/
|
||||
swaggerOptions?: Omit<Partial<SwaggerUIOptions>, 'dom_id' | 'dom_node' | 'spec' | 'url' | 'urls' | 'layout' | 'pluginsOptions' | 'plugins' | 'presets' | 'onComplete' | 'requestInterceptor' | 'responseInterceptor' | 'modelPropertyMacro' | 'parameterMacro'>;
|
||||
/**
|
||||
* Custom Swagger CSS
|
||||
*/
|
||||
theme?: string | {
|
||||
light: string;
|
||||
dark: string;
|
||||
};
|
||||
/**
|
||||
* Using poor man dark mode 😭
|
||||
*/
|
||||
autoDarkMode?: boolean;
|
||||
/**
|
||||
* Exclude methods from Swagger
|
||||
*/
|
||||
excludeMethods?: string[];
|
||||
/**
|
||||
* Exclude tags from Swagger or Scalar
|
||||
*/
|
||||
excludeTags?: string[];
|
||||
}
|
||||
0
dist/types.mjs
vendored
Normal file
0
dist/types.mjs
vendored
Normal file
26
dist/utils.d.ts
vendored
Normal file
26
dist/utils.d.ts
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
import { type HTTPMethod, type LocalHook } from 'elysia';
|
||||
import { type TSchema } from '@sinclair/typebox';
|
||||
import type { OpenAPIV3 } from 'openapi-types';
|
||||
export declare const toOpenAPIPath: (path: string) => string;
|
||||
export declare const mapProperties: (name: string, schema: TSchema | string | undefined, models: Record<string, TSchema>) => {
|
||||
description: any;
|
||||
examples: any;
|
||||
schema: any;
|
||||
in: string;
|
||||
name: string;
|
||||
required: any;
|
||||
}[];
|
||||
export declare const capitalize: (word: string) => string;
|
||||
export declare const generateOperationId: (method: string, paths: string) => string;
|
||||
export declare const registerSchemaPath: ({ schema, path, method, hook, models }: {
|
||||
schema: Partial<OpenAPIV3.PathsObject>;
|
||||
contentType?: string | string[];
|
||||
path: string;
|
||||
method: HTTPMethod;
|
||||
hook?: LocalHook<any, any, any, any, any, any>;
|
||||
models: Record<string, TSchema>;
|
||||
}) => void;
|
||||
export declare const filterPaths: (paths: Record<string, any>, { excludeStaticFile, exclude }: {
|
||||
excludeStaticFile: boolean;
|
||||
exclude: (string | RegExp)[];
|
||||
}) => Record<string, any>;
|
||||
302
dist/utils.mjs
vendored
Normal file
302
dist/utils.mjs
vendored
Normal file
@@ -0,0 +1,302 @@
|
||||
// src/utils.ts
|
||||
import { replaceSchemaType, t } from "elysia";
|
||||
|
||||
// node_modules/@sinclair/typebox/build/esm/type/symbols/symbols.mjs
|
||||
var TransformKind = Symbol.for("TypeBox.Transform");
|
||||
var ReadonlyKind = Symbol.for("TypeBox.Readonly");
|
||||
var OptionalKind = Symbol.for("TypeBox.Optional");
|
||||
var Hint = Symbol.for("TypeBox.Hint");
|
||||
var Kind = Symbol.for("TypeBox.Kind");
|
||||
|
||||
// src/utils.ts
|
||||
var toOpenAPIPath = (path) => path.split("/").map((x) => {
|
||||
if (x.startsWith(":")) {
|
||||
x = x.slice(1, x.length);
|
||||
if (x.endsWith("?")) x = x.slice(0, -1);
|
||||
x = `{${x}}`;
|
||||
}
|
||||
return x;
|
||||
}).join("/");
|
||||
var mapProperties = (name, schema, models) => {
|
||||
if (schema === void 0) return [];
|
||||
if (typeof schema === "string")
|
||||
if (schema in models) schema = models[schema];
|
||||
else throw new Error(`Can't find model ${schema}`);
|
||||
return Object.entries(schema?.properties ?? []).map(([key, value]) => {
|
||||
const {
|
||||
type: valueType = void 0,
|
||||
description,
|
||||
examples,
|
||||
...schemaKeywords
|
||||
} = value;
|
||||
return {
|
||||
// @ts-ignore
|
||||
description,
|
||||
examples,
|
||||
schema: { type: valueType, ...schemaKeywords },
|
||||
in: name,
|
||||
name: key,
|
||||
// @ts-ignore
|
||||
required: schema.required?.includes(key) ?? false
|
||||
};
|
||||
});
|
||||
};
|
||||
var mapTypesResponse = (types, schema) => {
|
||||
if (typeof schema === "object" && ["void", "undefined", "null"].includes(schema.type))
|
||||
return;
|
||||
const responses = {};
|
||||
for (const type of types) {
|
||||
responses[type] = {
|
||||
schema: typeof schema === "string" ? {
|
||||
$ref: `#/components/schemas/${schema}`
|
||||
} : "$ref" in schema && Kind in schema && schema[Kind] === "Ref" ? {
|
||||
...schema,
|
||||
$ref: `#/components/schemas/${schema.$ref}`
|
||||
} : replaceSchemaType(
|
||||
{ ...schema },
|
||||
{
|
||||
from: t.Ref(""),
|
||||
// @ts-expect-error
|
||||
to: ({ $ref, ...options }) => {
|
||||
if (!$ref.startsWith(
|
||||
"#/components/schemas/"
|
||||
))
|
||||
return t.Ref(
|
||||
`#/components/schemas/${$ref}`,
|
||||
options
|
||||
);
|
||||
return t.Ref($ref, options);
|
||||
}
|
||||
}
|
||||
)
|
||||
};
|
||||
}
|
||||
return responses;
|
||||
};
|
||||
var capitalize = (word) => word.charAt(0).toUpperCase() + word.slice(1);
|
||||
var generateOperationId = (method, paths) => {
|
||||
let operationId = method.toLowerCase();
|
||||
if (paths === "/") return operationId + "Index";
|
||||
for (const path of paths.split("/")) {
|
||||
if (path.charCodeAt(0) === 123) {
|
||||
operationId += "By" + capitalize(path.slice(1, -1));
|
||||
} else {
|
||||
operationId += capitalize(path);
|
||||
}
|
||||
}
|
||||
return operationId;
|
||||
};
|
||||
var cloneHook = (hook) => {
|
||||
if (!hook) return;
|
||||
if (typeof hook === "string") return hook;
|
||||
if (Array.isArray(hook)) return [...hook];
|
||||
return { ...hook };
|
||||
};
|
||||
var registerSchemaPath = ({
|
||||
schema,
|
||||
path,
|
||||
method,
|
||||
hook,
|
||||
models
|
||||
}) => {
|
||||
hook = cloneHook(hook);
|
||||
if (hook.parse && !Array.isArray(hook.parse)) hook.parse = [hook.parse];
|
||||
let contentType = hook.parse?.map((x) => {
|
||||
switch (typeof x) {
|
||||
case "string":
|
||||
return x;
|
||||
case "object":
|
||||
if (x && typeof x?.fn !== "string")
|
||||
return;
|
||||
switch (x?.fn) {
|
||||
case "json":
|
||||
case "application/json":
|
||||
return "application/json";
|
||||
case "text":
|
||||
case "text/plain":
|
||||
return "text/plain";
|
||||
case "urlencoded":
|
||||
case "application/x-www-form-urlencoded":
|
||||
return "application/x-www-form-urlencoded";
|
||||
case "arrayBuffer":
|
||||
case "application/octet-stream":
|
||||
return "application/octet-stream";
|
||||
case "formdata":
|
||||
case "multipart/form-data":
|
||||
return "multipart/form-data";
|
||||
}
|
||||
}
|
||||
}).filter((x) => x !== void 0);
|
||||
if (!contentType || contentType.length === 0)
|
||||
contentType = ["application/json", "multipart/form-data", "text/plain"];
|
||||
path = toOpenAPIPath(path);
|
||||
const contentTypes = typeof contentType === "string" ? [contentType] : contentType ?? ["application/json"];
|
||||
const bodySchema = cloneHook(hook?.body);
|
||||
const paramsSchema = cloneHook(hook?.params);
|
||||
const headerSchema = cloneHook(hook?.headers);
|
||||
const querySchema = cloneHook(hook?.query);
|
||||
let responseSchema = cloneHook(hook?.response);
|
||||
if (typeof responseSchema === "object") {
|
||||
if (Kind in responseSchema) {
|
||||
const {
|
||||
type,
|
||||
properties,
|
||||
required,
|
||||
additionalProperties,
|
||||
patternProperties,
|
||||
$ref,
|
||||
...rest
|
||||
} = responseSchema;
|
||||
responseSchema = {
|
||||
"200": {
|
||||
...rest,
|
||||
description: rest.description,
|
||||
content: mapTypesResponse(
|
||||
contentTypes,
|
||||
type === "object" || type === "array" ? {
|
||||
type,
|
||||
properties,
|
||||
patternProperties,
|
||||
items: responseSchema.items,
|
||||
required
|
||||
} : responseSchema
|
||||
)
|
||||
}
|
||||
};
|
||||
} else {
|
||||
Object.entries(responseSchema).forEach(
|
||||
([key, value]) => {
|
||||
if (typeof value === "string") {
|
||||
if (!models[value]) return;
|
||||
const {
|
||||
type,
|
||||
properties,
|
||||
required,
|
||||
additionalProperties: _1,
|
||||
patternProperties: _2,
|
||||
...rest
|
||||
} = models[value];
|
||||
responseSchema[key] = {
|
||||
...rest,
|
||||
description: rest.description,
|
||||
content: mapTypesResponse(contentTypes, value)
|
||||
};
|
||||
} else {
|
||||
const {
|
||||
type,
|
||||
properties,
|
||||
required,
|
||||
additionalProperties,
|
||||
patternProperties,
|
||||
...rest
|
||||
} = value;
|
||||
responseSchema[key] = {
|
||||
...rest,
|
||||
description: rest.description,
|
||||
content: mapTypesResponse(
|
||||
contentTypes,
|
||||
type === "object" || type === "array" ? {
|
||||
type,
|
||||
properties,
|
||||
patternProperties,
|
||||
items: value.items,
|
||||
required
|
||||
} : value
|
||||
)
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
} else if (typeof responseSchema === "string") {
|
||||
if (!(responseSchema in models)) return;
|
||||
const {
|
||||
type,
|
||||
properties,
|
||||
required,
|
||||
$ref,
|
||||
additionalProperties: _1,
|
||||
patternProperties: _2,
|
||||
...rest
|
||||
} = models[responseSchema];
|
||||
responseSchema = {
|
||||
// @ts-ignore
|
||||
"200": {
|
||||
...rest,
|
||||
content: mapTypesResponse(contentTypes, responseSchema)
|
||||
}
|
||||
};
|
||||
}
|
||||
const parameters = [
|
||||
...mapProperties("header", headerSchema, models),
|
||||
...mapProperties("path", paramsSchema, models),
|
||||
...mapProperties("query", querySchema, models)
|
||||
];
|
||||
schema[path] = {
|
||||
...schema[path] ? schema[path] : {},
|
||||
[method.toLowerCase()]: {
|
||||
...headerSchema || paramsSchema || querySchema || bodySchema ? { parameters } : {},
|
||||
...responseSchema ? {
|
||||
responses: responseSchema
|
||||
} : {},
|
||||
operationId: hook?.detail?.operationId ?? generateOperationId(method, path),
|
||||
...hook?.detail,
|
||||
...bodySchema ? {
|
||||
requestBody: {
|
||||
required: true,
|
||||
content: mapTypesResponse(
|
||||
contentTypes,
|
||||
typeof bodySchema === "string" ? {
|
||||
$ref: `#/components/schemas/${bodySchema}`
|
||||
} : bodySchema
|
||||
)
|
||||
}
|
||||
} : null
|
||||
}
|
||||
};
|
||||
};
|
||||
var filterPaths = (paths, {
|
||||
excludeStaticFile = true,
|
||||
exclude = []
|
||||
}) => {
|
||||
const newPaths = {};
|
||||
for (const [key, value] of Object.entries(paths))
|
||||
if (!exclude.some((x) => {
|
||||
if (typeof x === "string") return key === x;
|
||||
return x.test(key);
|
||||
}) && !key.includes("*") && (excludeStaticFile ? !key.includes(".") : true)) {
|
||||
Object.keys(value).forEach((method) => {
|
||||
const schema = value[method];
|
||||
if (key.includes("{")) {
|
||||
if (!schema.parameters) schema.parameters = [];
|
||||
schema.parameters = [
|
||||
...key.split("/").filter(
|
||||
(x) => x.startsWith("{") && !schema.parameters.find(
|
||||
(params) => params.in === "path" && params.name === x.slice(1, x.length - 1)
|
||||
)
|
||||
).map((x) => ({
|
||||
schema: { type: "string" },
|
||||
in: "path",
|
||||
name: x.slice(1, x.length - 1),
|
||||
required: true
|
||||
})),
|
||||
...schema.parameters
|
||||
];
|
||||
}
|
||||
if (!schema.responses)
|
||||
schema.responses = {
|
||||
200: {}
|
||||
};
|
||||
});
|
||||
newPaths[key] = value;
|
||||
}
|
||||
return newPaths;
|
||||
};
|
||||
export {
|
||||
capitalize,
|
||||
filterPaths,
|
||||
generateOperationId,
|
||||
mapProperties,
|
||||
registerSchemaPath,
|
||||
toOpenAPIPath
|
||||
};
|
||||
Reference in New Issue
Block a user