Push build

This commit is contained in:
2025-05-05 19:20:31 +02:00
parent 07adf7ecd4
commit f88fbc71de
21 changed files with 2769 additions and 0 deletions

34
dist/cjs/index.d.ts vendored Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View File

102
dist/types.d.ts vendored Normal file
View 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
View File

26
dist/utils.d.ts vendored Normal file
View 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
View 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
};