@elysiajs/openapi 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,257 @@
1
+ // src/openapi.ts
2
+ import { t } from "elysia";
3
+ var capitalize = (word) => word.charAt(0).toUpperCase() + word.slice(1);
4
+ var toRef = (name) => t.Ref(`#/components/schemas/${name}`);
5
+ var toOperationId = (method, paths) => {
6
+ let operationId = method.toLowerCase();
7
+ if (!paths || paths === "/") return operationId + "Index";
8
+ for (const path of paths.split("/"))
9
+ operationId += path.includes(":") ? "By" + capitalize(path.replace(":", "")) : capitalize(path);
10
+ operationId = operationId.replace(/\?/g, "Optional");
11
+ return operationId;
12
+ };
13
+ var optionalParamsRegex = /(\/:\w+\?)/g;
14
+ var getPossiblePath = (path) => {
15
+ const optionalParams = path.match(optionalParamsRegex);
16
+ if (!optionalParams) return [path];
17
+ const originalPath = path.replace(/\?/g, "");
18
+ const paths = [originalPath];
19
+ for (let i = 0; i < optionalParams.length; i++) {
20
+ const newPath = path.replace(optionalParams[i], "");
21
+ paths.push(...getPossiblePath(newPath));
22
+ }
23
+ return paths;
24
+ };
25
+ function toOpenAPISchema(app, exclude, references) {
26
+ const {
27
+ methods: excludeMethods = ["OPTIONS"],
28
+ staticFile: excludeStaticFile = true,
29
+ tags: excludeTags
30
+ } = exclude ?? {};
31
+ const excludePaths = Array.isArray(exclude?.paths) ? exclude.paths : typeof exclude?.paths !== "undefined" ? [exclude.paths] : [];
32
+ const paths = /* @__PURE__ */ Object.create(null);
33
+ const routes = app.getGlobalRoutes();
34
+ if (references) {
35
+ if (!Array.isArray(references)) references = [references];
36
+ for (let i = 0; i < references.length; i++) {
37
+ const reference = references[i];
38
+ if (typeof reference === "function") references[i] = reference();
39
+ }
40
+ }
41
+ for (const route of routes) {
42
+ if (route.hooks?.detail?.hide) continue;
43
+ const method = route.method.toLowerCase();
44
+ if (excludeStaticFile && route.path.includes(".") || excludePaths.includes(route.path) || excludeMethods.includes(method))
45
+ continue;
46
+ const hooks = route.hooks ?? {};
47
+ if (references)
48
+ for (const reference of references) {
49
+ const refer = reference[route.path]?.[method];
50
+ if (!refer) continue;
51
+ if (!hooks.body && refer.body) hooks.body = refer.body;
52
+ if (!hooks.query && refer.query) hooks.query = refer.query;
53
+ if (!hooks.params && refer.params) hooks.params = refer.params;
54
+ if (!hooks.headers && refer.headers)
55
+ hooks.headers = refer.headers;
56
+ if (!hooks.response && refer.response) {
57
+ hooks.response = {};
58
+ for (const [status, schema] of Object.entries(
59
+ refer.response
60
+ ))
61
+ if (!hooks.response[status])
62
+ hooks.response[status] = schema;
63
+ }
64
+ }
65
+ if (excludeTags && hooks.detail.tags?.some((tag) => excludeTags?.includes(tag)))
66
+ continue;
67
+ const operation = {
68
+ ...hooks.detail
69
+ };
70
+ const parameters = [];
71
+ if (hooks.params) {
72
+ if (typeof hooks.params === "string")
73
+ hooks.params = toRef(hooks.params);
74
+ if (hooks.params.type === "object" && hooks.params.properties) {
75
+ for (const [paramName, paramSchema] of Object.entries(
76
+ hooks.params.properties
77
+ ))
78
+ parameters.push({
79
+ name: paramName,
80
+ in: "path",
81
+ required: true,
82
+ // Path parameters are always required
83
+ schema: paramSchema
84
+ });
85
+ }
86
+ }
87
+ if (hooks.query) {
88
+ if (typeof hooks.query === "string")
89
+ hooks.query = toRef(hooks.query);
90
+ if (hooks.query.type === "object" && hooks.query.properties) {
91
+ const required = hooks.query.required || [];
92
+ for (const [queryName, querySchema] of Object.entries(
93
+ hooks.query.properties
94
+ ))
95
+ parameters.push({
96
+ name: queryName,
97
+ in: "query",
98
+ required: required.includes(queryName),
99
+ schema: querySchema
100
+ });
101
+ }
102
+ }
103
+ if (hooks.headers) {
104
+ if (typeof hooks.headers === "string")
105
+ hooks.headers = toRef(hooks.headers);
106
+ if (hooks.headers.type === "object" && hooks.headers.properties) {
107
+ const required = hooks.headers.required || [];
108
+ for (const [headerName, headerSchema] of Object.entries(
109
+ hooks.headers.properties
110
+ ))
111
+ parameters.push({
112
+ name: headerName,
113
+ in: "header",
114
+ required: required.includes(headerName),
115
+ schema: headerSchema
116
+ });
117
+ }
118
+ }
119
+ if (hooks.cookie) {
120
+ if (typeof hooks.cookie === "string")
121
+ hooks.cookie = toRef(hooks.cookie);
122
+ if (hooks.cookie.type === "object" && hooks.cookie.properties) {
123
+ const required = hooks.cookie.required || [];
124
+ for (const [cookieName, cookieSchema] of Object.entries(
125
+ hooks.cookie.properties
126
+ ))
127
+ parameters.push({
128
+ name: cookieName,
129
+ in: "cookie",
130
+ required: required.includes(cookieName),
131
+ schema: cookieSchema
132
+ });
133
+ }
134
+ }
135
+ if (parameters.length > 0) operation.parameters = parameters;
136
+ if (hooks.body) {
137
+ if (typeof hooks.body === "string") hooks.body = toRef(hooks.body);
138
+ if (hooks.parse) {
139
+ const content = {};
140
+ const parsers = hooks.parse;
141
+ for (const parser of parsers) {
142
+ if (typeof parser.fn === "function") continue;
143
+ switch (parser.fn) {
144
+ case "text":
145
+ case "text/plain":
146
+ content["text/plain"] = { schema: hooks.body };
147
+ continue;
148
+ case "urlencoded":
149
+ case "application/x-www-form-urlencoded":
150
+ content["application/x-www-form-urlencoded"] = {
151
+ schema: hooks.body
152
+ };
153
+ continue;
154
+ case "json":
155
+ case "application/json":
156
+ content["application/json"] = { schema: hooks.body };
157
+ continue;
158
+ case "formdata":
159
+ case "multipart/form-data":
160
+ content["multipart/form-data"] = {
161
+ schema: hooks.body
162
+ };
163
+ continue;
164
+ }
165
+ }
166
+ operation.requestBody = { content, required: true };
167
+ } else {
168
+ operation.requestBody = {
169
+ content: {
170
+ "application/json": {
171
+ schema: hooks.body
172
+ },
173
+ "application/x-www-form-urlencoded": {
174
+ schema: hooks.body
175
+ },
176
+ "multipart/form-data": {
177
+ schema: hooks.body
178
+ }
179
+ },
180
+ required: true
181
+ };
182
+ }
183
+ }
184
+ if (hooks.response) {
185
+ operation.responses = {};
186
+ if (typeof hooks.response === "object" && !hooks.response.type && !hooks.response.$ref) {
187
+ for (let [status, schema] of Object.entries(hooks.response)) {
188
+ if (typeof schema === "string") schema = toRef(schema);
189
+ const { type, examples, $ref, ...options } = schema;
190
+ operation.responses[status] = {
191
+ description: `Response for status ${status}`,
192
+ ...options,
193
+ content: type === "void" || type === "null" || type === "undefined" ? schema : {
194
+ "application/json": {
195
+ schema
196
+ }
197
+ }
198
+ };
199
+ }
200
+ } else {
201
+ if (typeof hooks.response === "string")
202
+ hooks.response = toRef(hooks.response);
203
+ operation.responses["200"] = {
204
+ description: "Successful response",
205
+ content: {
206
+ "application/json": {
207
+ schema: hooks.response
208
+ }
209
+ }
210
+ };
211
+ }
212
+ }
213
+ for (let path of getPossiblePath(route.path)) {
214
+ const operationId = toOperationId(route.method, path);
215
+ path = path.replace(/:([^/]+)/g, "{$1}");
216
+ if (!paths[path]) paths[path] = {};
217
+ const current = paths[path];
218
+ if (method !== "all") {
219
+ current[method] = {
220
+ ...operation,
221
+ operationId
222
+ };
223
+ continue;
224
+ }
225
+ for (const method2 of [
226
+ "get",
227
+ "post",
228
+ "put",
229
+ "delete",
230
+ "patch",
231
+ "head",
232
+ "options",
233
+ "trace"
234
+ ])
235
+ current[method2] = {
236
+ ...operation,
237
+ operationId
238
+ };
239
+ }
240
+ }
241
+ const schemas = app.getGlobalDefinitions?.().type;
242
+ return {
243
+ components: {
244
+ schemas
245
+ },
246
+ paths
247
+ };
248
+ }
249
+ var withHeaders = (schema, headers) => Object.assign(schema, {
250
+ headers
251
+ });
252
+ export {
253
+ capitalize,
254
+ getPossiblePath,
255
+ toOpenAPISchema,
256
+ withHeaders
257
+ };
@@ -0,0 +1,3 @@
1
+ import type { OpenAPIV3 } from 'openapi-types';
2
+ import { ElysiaOpenAPIConfig } from '../types';
3
+ export declare const ScalarRender: (info: OpenAPIV3.InfoObject, config: NonNullable<ElysiaOpenAPIConfig["scalar"]>) => string;
@@ -0,0 +1,158 @@
1
+ // src/scalar/index.ts
2
+ var elysiaCSS = `.light-mode {
3
+ --scalar-color-1: #2a2f45;
4
+ --scalar-color-2: #757575;
5
+ --scalar-color-3: #8e8e8e;
6
+ --scalar-color-accent: #f06292;
7
+
8
+ --scalar-background-1: #fff;
9
+ --scalar-background-2: #f6f6f6;
10
+ --scalar-background-3: #e7e7e7;
11
+
12
+ --scalar-border-color: rgba(0, 0, 0, 0.1);
13
+ }
14
+ .dark-mode {
15
+ --scalar-color-1: rgba(255, 255, 255, 0.9);
16
+ --scalar-color-2: rgba(156, 163, 175, 1);
17
+ --scalar-color-3: rgba(255, 255, 255, 0.44);
18
+ --scalar-color-accent: #f06292;
19
+
20
+ --scalar-background-1: #111728;
21
+ --scalar-background-2: #1e293b;
22
+ --scalar-background-3: #334155;
23
+ --scalar-background-accent: #f062921f;
24
+
25
+ --scalar-border-color: rgba(255, 255, 255, 0.1);
26
+ }
27
+
28
+ /* Document Sidebar */
29
+ .light-mode .t-doc__sidebar,
30
+ .dark-mode .t-doc__sidebar {
31
+ --scalar-sidebar-background-1: var(--scalar-background-1);
32
+ --scalar-sidebar-color-1: var(--scalar-color-1);
33
+ --scalar-sidebar-color-2: var(--scalar-color-2);
34
+ --scalar-sidebar-border-color: var(--scalar-border-color);
35
+
36
+ --scalar-sidebar-item-hover-background: var(--scalar-background-2);
37
+ --scalar-sidebar-item-hover-color: currentColor;
38
+
39
+ --scalar-sidebar-item-active-background: #f062921f;
40
+ --scalar-sidebar-color-active: var(--scalar-color-accent);
41
+
42
+ --scalar-sidebar-search-background: transparent;
43
+ --scalar-sidebar-search-color: var(--scalar-color-3);
44
+ --scalar-sidebar-search-border-color: var(--scalar-border-color);
45
+ }
46
+
47
+ /* advanced */
48
+ .light-mode {
49
+ --scalar-button-1: rgb(49 53 56);
50
+ --scalar-button-1-color: #fff;
51
+ --scalar-button-1-hover: rgb(28 31 33);
52
+
53
+ --scalar-color-green: #069061;
54
+ --scalar-color-red: #ef0006;
55
+ --scalar-color-yellow: #edbe20;
56
+ --scalar-color-blue: #0082d0;
57
+ --scalar-color-orange: #fb892c;
58
+ --scalar-color-purple: #5203d1;
59
+
60
+ --scalar-scrollbar-color: rgba(0, 0, 0, 0.18);
61
+ --scalar-scrollbar-color-active: rgba(0, 0, 0, 0.36);
62
+ }
63
+ .dark-mode {
64
+ --scalar-button-1: #f6f6f6;
65
+ --scalar-button-1-color: #000;
66
+ --scalar-button-1-hover: #e7e7e7;
67
+
68
+ --scalar-color-green: #a3ffa9;
69
+ --scalar-color-red: #ffa3a3;
70
+ --scalar-color-yellow: #fffca3;
71
+ --scalar-color-blue: #a5d6ff;
72
+ --scalar-color-orange: #e2ae83;
73
+ --scalar-color-purple: #d2a8ff;
74
+
75
+ --scalar-scrollbar-color: rgba(255, 255, 255, 0.24);
76
+ --scalar-scrollbar-color-active: rgba(255, 255, 255, 0.48);
77
+ }
78
+ .section-flare {
79
+ width: 100%;
80
+ height: 400px;
81
+ position: absolute;
82
+ }
83
+ .section-flare-item:first-of-type:before {
84
+ content: "";
85
+ position: absolute;
86
+ top: 0;
87
+ right: 0;
88
+ bottom: 0;
89
+ left: 0;
90
+ --stripes: repeating-linear-gradient(100deg, #fff 0%, #fff 0%, transparent 2%, transparent 12%, #fff 17%);
91
+ --stripesDark: repeating-linear-gradient(100deg, #000 0%, #000 0%, transparent 10%, transparent 12%, #000 17%);
92
+ --rainbow: repeating-linear-gradient(100deg, #60a5fa 10%, #e879f9 16%, #5eead4 22%, #60a5fa 30%);
93
+ contain: strict;
94
+ contain-intrinsic-size: 100vw 40vh;
95
+ background-image: var(--stripesDark), var(--rainbow);
96
+ background-size: 300%, 200%;
97
+ background-position: 50% 50%, 50% 50%;
98
+ filter: opacity(20%) saturate(200%);
99
+ -webkit-mask-image: radial-gradient(ellipse at 100% 0%, black 40%, transparent 70%);
100
+ mask-image: radial-gradient(ellipse at 100% 0%, black 40%, transparent 70%);
101
+ pointer-events: none;
102
+ }
103
+ .section-flare-item:first-of-type:after {
104
+ content: "";
105
+ position: absolute;
106
+ top: 0;
107
+ right: 0;
108
+ bottom: 0;
109
+ left: 0;
110
+ background-image: var(--stripes), var(--rainbow);
111
+ background-size: 200%, 100%;
112
+ background-attachment: fixed;
113
+ mix-blend-mode: difference;
114
+ background-image: var(--stripesDark), var(--rainbow);
115
+ pointer-events: none;
116
+ }
117
+ .light-mode .section-flare-item:first-of-type:after,
118
+ .light-mode .section-flare-item:first-of-type:before {
119
+ background-image: var(--stripes), var(--rainbow);
120
+ filter: opacity(4%) saturate(200%);
121
+ }`;
122
+ var ScalarRender = (info, config) => `<!doctype html>
123
+ <html>
124
+ <head>
125
+ <title>${info.title}</title>
126
+ <meta
127
+ name="description"
128
+ content="${info.description}"
129
+ />
130
+ <meta
131
+ name="og:description"
132
+ content="${info.description}"
133
+ />
134
+ <meta charset="utf-8" />
135
+ <meta
136
+ name="viewport"
137
+ content="width=device-width, initial-scale=1" />
138
+ <style>
139
+ body {
140
+ margin: 0;
141
+ }
142
+ </style>
143
+ <style>
144
+ ${config.customCss ?? elysiaCSS}
145
+ </style>
146
+ </head>
147
+ <body>
148
+ <script
149
+ id="api-reference"
150
+ data-url="${config.url}"
151
+ >
152
+ </script>
153
+ <script src="${config.cdn}" crossorigin></script>
154
+ </body>
155
+ </html>`;
156
+ export {
157
+ ScalarRender
158
+ };
@@ -0,0 +1,7 @@
1
+ import { OpenAPIV3 } from 'openapi-types';
2
+ import { ElysiaOpenAPIConfig } from '../types';
3
+ import { SwaggerUIOptions } from './types';
4
+ type SchemaObject = OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject;
5
+ export declare function transformDateProperties(schema: SchemaObject): SchemaObject;
6
+ export declare const SwaggerUIRender: (info: OpenAPIV3.InfoObject, config: NonNullable<ElysiaOpenAPIConfig["swagger"]> & SwaggerUIOptions) => string;
7
+ export {};
@@ -0,0 +1,103 @@
1
+ // src/swagger/index.ts
2
+ function isSchemaObject(schema) {
3
+ return "type" in schema || "properties" in schema || "items" in schema;
4
+ }
5
+ function isDateTimeProperty(key, schema) {
6
+ return (key === "createdAt" || key === "updatedAt") && "anyOf" in schema && Array.isArray(schema.anyOf);
7
+ }
8
+ function transformDateProperties(schema) {
9
+ if (!isSchemaObject(schema) || typeof schema !== "object" || schema === null)
10
+ return schema;
11
+ const newSchema = { ...schema };
12
+ Object.entries(newSchema).forEach(([key, value]) => {
13
+ if (isSchemaObject(value)) {
14
+ if (isDateTimeProperty(key, value)) {
15
+ const dateTimeFormat = value.anyOf?.find(
16
+ (item) => isSchemaObject(item) && item.format === "date-time"
17
+ );
18
+ if (dateTimeFormat) {
19
+ const dateTimeSchema = {
20
+ type: "string",
21
+ format: "date-time",
22
+ default: dateTimeFormat.default
23
+ };
24
+ newSchema[key] = dateTimeSchema;
25
+ }
26
+ } else {
27
+ ;
28
+ newSchema[key] = transformDateProperties(value);
29
+ }
30
+ }
31
+ });
32
+ return newSchema;
33
+ }
34
+ var SwaggerUIRender = (info, config) => {
35
+ const {
36
+ version = "latest",
37
+ theme = `https://unpkg.com/swagger-ui-dist@${version ?? "latest"}/swagger-ui.css`,
38
+ cdn = `https://unpkg.com/swagger-ui-dist@${version}/swagger-ui-bundle.js`,
39
+ autoDarkMode = true,
40
+ ...rest
41
+ } = config;
42
+ const stringifiedOptions = JSON.stringify(
43
+ {
44
+ dom_id: "#swagger-ui",
45
+ ...rest
46
+ },
47
+ (_, value) => typeof value === "function" ? void 0 : value
48
+ );
49
+ const options = JSON.parse(stringifiedOptions);
50
+ if (options.components && options.components.schemas)
51
+ options.components.schemas = Object.fromEntries(
52
+ Object.entries(options.components.schemas).map(([key, schema]) => [
53
+ key,
54
+ transformDateProperties(schema)
55
+ ])
56
+ );
57
+ return `<!DOCTYPE html>
58
+ <html lang="en">
59
+ <head>
60
+ <meta charset="utf-8" />
61
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
62
+ <title>${info.title}</title>
63
+ <meta
64
+ name="description"
65
+ content="${info.description}"
66
+ />
67
+ <meta
68
+ name="og:description"
69
+ content="${info.description}"
70
+ />
71
+ ${autoDarkMode && typeof theme === "string" ? `<style>
72
+ @media (prefers-color-scheme: dark) {
73
+ body {
74
+ background-color: #222;
75
+ color: #faf9a;
76
+ }
77
+ .swagger-ui {
78
+ filter: invert(92%) hue-rotate(180deg);
79
+ }
80
+
81
+ .swagger-ui .microlight {
82
+ filter: invert(100%) hue-rotate(180deg);
83
+ }
84
+ }
85
+ </style>` : ""}
86
+ ${typeof theme === "string" ? `<link rel="stylesheet" href="${theme}" />` : `<link rel="stylesheet" media="(prefers-color-scheme: light)" href="${theme.light}" />
87
+ <link rel="stylesheet" media="(prefers-color-scheme: dark)" href="${theme.dark}" />`}
88
+ </head>
89
+ <body>
90
+ <div id="swagger-ui"></div>
91
+ <script src="${cdn}" crossorigin></script>
92
+ <script>
93
+ window.onload = () => {
94
+ window.ui = SwaggerUIBundle(${stringifiedOptions});
95
+ };
96
+ </script>
97
+ </body>
98
+ </html>`;
99
+ };
100
+ export {
101
+ SwaggerUIRender,
102
+ transformDateProperties
103
+ };