@btst/stack 1.6.0 → 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/index.cjs +7 -1
- package/dist/api/index.d.cts +2 -2
- package/dist/api/index.d.mts +2 -2
- package/dist/api/index.d.ts +2 -2
- package/dist/api/index.mjs +7 -1
- package/dist/client/index.cjs +6 -2
- package/dist/client/index.d.cts +2 -1
- package/dist/client/index.d.mts +2 -1
- package/dist/client/index.d.ts +2 -1
- package/dist/client/index.mjs +6 -2
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/packages/better-stack/src/plugins/open-api/api/generator.cjs +300 -0
- package/dist/packages/better-stack/src/plugins/open-api/api/generator.mjs +284 -0
- package/dist/packages/better-stack/src/plugins/open-api/api/plugin.cjs +115 -0
- package/dist/packages/better-stack/src/plugins/open-api/api/plugin.mjs +113 -0
- package/dist/packages/better-stack/src/plugins/open-api/db.cjs +7 -0
- package/dist/packages/better-stack/src/plugins/open-api/db.mjs +5 -0
- package/dist/packages/better-stack/src/plugins/open-api/logo.cjs +8 -0
- package/dist/packages/better-stack/src/plugins/open-api/logo.mjs +6 -0
- package/dist/packages/better-stack/src/plugins/route-docs/client/components/loading/docs-skeleton.cjs +43 -0
- package/dist/packages/better-stack/src/plugins/route-docs/client/components/loading/docs-skeleton.mjs +41 -0
- package/dist/packages/better-stack/src/plugins/route-docs/client/components/pages/docs-page.cjs +794 -0
- package/dist/packages/better-stack/src/plugins/route-docs/client/components/pages/docs-page.mjs +788 -0
- package/dist/packages/better-stack/src/plugins/route-docs/client/plugin.cjs +111 -0
- package/dist/packages/better-stack/src/plugins/route-docs/client/plugin.mjs +106 -0
- package/dist/packages/better-stack/src/plugins/route-docs/generator.cjs +244 -0
- package/dist/packages/better-stack/src/plugins/route-docs/generator.mjs +227 -0
- package/dist/packages/ui/src/components/sheet.cjs +25 -0
- package/dist/packages/ui/src/components/sheet.mjs +24 -1
- package/dist/plugins/api/index.d.cts +2 -2
- package/dist/plugins/api/index.d.mts +2 -2
- package/dist/plugins/api/index.d.ts +2 -2
- package/dist/plugins/blog/api/index.d.cts +1 -1
- package/dist/plugins/blog/api/index.d.mts +1 -1
- package/dist/plugins/blog/api/index.d.ts +1 -1
- package/dist/plugins/blog/client/hooks/index.d.cts +2 -2
- package/dist/plugins/blog/client/hooks/index.d.mts +2 -2
- package/dist/plugins/blog/client/hooks/index.d.ts +2 -2
- package/dist/plugins/blog/client/index.d.cts +1 -1
- package/dist/plugins/blog/client/index.d.mts +1 -1
- package/dist/plugins/blog/client/index.d.ts +1 -1
- package/dist/plugins/blog/query-keys.d.cts +2 -2
- package/dist/plugins/blog/query-keys.d.mts +2 -2
- package/dist/plugins/blog/query-keys.d.ts +2 -2
- package/dist/plugins/client/index.d.cts +2 -2
- package/dist/plugins/client/index.d.mts +2 -2
- package/dist/plugins/client/index.d.ts +2 -2
- package/dist/plugins/open-api/api/index.cjs +9 -0
- package/dist/plugins/open-api/api/index.d.cts +95 -0
- package/dist/plugins/open-api/api/index.d.mts +95 -0
- package/dist/plugins/open-api/api/index.d.ts +95 -0
- package/dist/plugins/open-api/api/index.mjs +2 -0
- package/dist/plugins/route-docs/client/index.cjs +10 -0
- package/dist/plugins/route-docs/client/index.d.cts +126 -0
- package/dist/plugins/route-docs/client/index.d.mts +126 -0
- package/dist/plugins/route-docs/client/index.d.ts +126 -0
- package/dist/plugins/route-docs/client/index.mjs +1 -0
- package/dist/plugins/route-docs/client.css +3 -0
- package/dist/plugins/route-docs/style.css +19 -0
- package/dist/shared/{stack.ByOugz9d.d.cts → stack.u9iYV6vt.d.cts} +28 -3
- package/dist/shared/{stack.ByOugz9d.d.mts → stack.u9iYV6vt.d.mts} +28 -3
- package/dist/shared/{stack.ByOugz9d.d.ts → stack.u9iYV6vt.d.ts} +28 -3
- package/package.json +28 -1
- package/src/api/index.ts +14 -2
- package/src/client/index.ts +11 -4
- package/src/plugins/open-api/api/generator.ts +433 -0
- package/src/plugins/open-api/api/index.ts +8 -0
- package/src/plugins/open-api/api/plugin.ts +243 -0
- package/src/plugins/open-api/db.ts +7 -0
- package/src/plugins/open-api/logo.ts +7 -0
- package/src/plugins/route-docs/client/components/loading/docs-skeleton.tsx +82 -0
- package/src/plugins/route-docs/client/components/loading/index.tsx +1 -0
- package/src/plugins/route-docs/client/components/pages/docs-page.tsx +1240 -0
- package/src/plugins/route-docs/client/index.ts +7 -0
- package/src/plugins/route-docs/client/plugin.tsx +187 -0
- package/src/plugins/route-docs/client.css +3 -0
- package/src/plugins/route-docs/generator.ts +385 -0
- package/src/plugins/route-docs/index.ts +12 -0
- package/src/plugins/route-docs/style.css +19 -0
- package/src/types.ts +34 -2
- package/dist/shared/{stack.CcI4sYJP.d.mts → stack.DLhzx1-D.d.cts} +1 -1
- package/dist/shared/{stack.CcI4sYJP.d.ts → stack.DLhzx1-D.d.mts} +1 -1
- package/dist/shared/{stack.CcI4sYJP.d.cts → stack.DLhzx1-D.d.ts} +1 -1
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import * as z from 'zod';
|
|
2
|
+
|
|
3
|
+
function toOpenApiPath(path) {
|
|
4
|
+
return path.split("/").map((part) => part.startsWith(":") ? `{${part.slice(1)}}` : part).join("/");
|
|
5
|
+
}
|
|
6
|
+
function getTypeFromZodType(zodType) {
|
|
7
|
+
if (zodType instanceof z.ZodString) return "string";
|
|
8
|
+
if (zodType instanceof z.ZodNumber) return "number";
|
|
9
|
+
if (zodType instanceof z.ZodBoolean) return "boolean";
|
|
10
|
+
if (zodType instanceof z.ZodArray) return "array";
|
|
11
|
+
if (zodType instanceof z.ZodObject) return "object";
|
|
12
|
+
const type = zodType.type;
|
|
13
|
+
if (type === "string") return "string";
|
|
14
|
+
if (type === "number") return "number";
|
|
15
|
+
if (type === "boolean") return "boolean";
|
|
16
|
+
if (type === "array") return "array";
|
|
17
|
+
if (type === "object") return "object";
|
|
18
|
+
return "string";
|
|
19
|
+
}
|
|
20
|
+
function processZodType(zodType) {
|
|
21
|
+
if (zodType instanceof z.ZodOptional) {
|
|
22
|
+
const innerType = zodType._def?.innerType || zodType.unwrap?.();
|
|
23
|
+
if (innerType) {
|
|
24
|
+
return processZodType(innerType);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
if (zodType instanceof z.ZodNullable) {
|
|
28
|
+
const innerType = zodType._def?.innerType || zodType.unwrap?.();
|
|
29
|
+
if (innerType) {
|
|
30
|
+
const innerSchema = processZodType(innerType);
|
|
31
|
+
return {
|
|
32
|
+
...innerSchema,
|
|
33
|
+
nullable: true
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
if (zodType instanceof z.ZodDefault) {
|
|
38
|
+
const innerType = zodType._def?.innerType;
|
|
39
|
+
const defaultValue = zodType._def?.defaultValue?.();
|
|
40
|
+
if (innerType) {
|
|
41
|
+
const innerSchema = processZodType(innerType);
|
|
42
|
+
if (defaultValue !== void 0) {
|
|
43
|
+
return {
|
|
44
|
+
...innerSchema,
|
|
45
|
+
default: defaultValue
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
return innerSchema;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (zodType instanceof z.ZodObject) {
|
|
52
|
+
const shape = zodType.shape || zodType._def?.shape?.();
|
|
53
|
+
if (shape) {
|
|
54
|
+
const properties = {};
|
|
55
|
+
const required = [];
|
|
56
|
+
for (const [key, value] of Object.entries(shape)) {
|
|
57
|
+
if (value instanceof z.ZodType) {
|
|
58
|
+
properties[key] = processZodType(value);
|
|
59
|
+
if (!(value instanceof z.ZodOptional)) {
|
|
60
|
+
required.push(key);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
type: "object",
|
|
66
|
+
properties,
|
|
67
|
+
...required.length > 0 ? { required } : {}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (zodType instanceof z.ZodArray) {
|
|
72
|
+
const elementType = zodType._def?.type || zodType.element;
|
|
73
|
+
return {
|
|
74
|
+
type: "array",
|
|
75
|
+
items: elementType ? processZodType(elementType) : { type: "string" }
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
if (zodType instanceof z.ZodEnum) {
|
|
79
|
+
const values = zodType._def?.values || zodType.options;
|
|
80
|
+
return {
|
|
81
|
+
type: "string",
|
|
82
|
+
enum: values
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
if (zodType instanceof z.ZodLiteral) {
|
|
86
|
+
const value = zodType._def?.value || zodType.value;
|
|
87
|
+
let type;
|
|
88
|
+
if (value === null) {
|
|
89
|
+
type = "null";
|
|
90
|
+
} else if (value === void 0) {
|
|
91
|
+
return { nullable: true };
|
|
92
|
+
} else {
|
|
93
|
+
type = typeof value;
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
type,
|
|
97
|
+
const: value
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
if (zodType instanceof z.ZodUnion) {
|
|
101
|
+
const options = zodType._def?.options || zodType.options;
|
|
102
|
+
if (options && Array.isArray(options)) {
|
|
103
|
+
return {
|
|
104
|
+
oneOf: options.map((opt) => processZodType(opt))
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (zodType._def?.coerce) {
|
|
109
|
+
const innerType = zodType._def?.innerType;
|
|
110
|
+
if (innerType) {
|
|
111
|
+
return processZodType(innerType);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
type: getTypeFromZodType(zodType)
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function getParameters(options) {
|
|
119
|
+
const parameters = [];
|
|
120
|
+
if (options.query instanceof z.ZodObject) {
|
|
121
|
+
const shape = options.query.shape || options.query._def?.shape?.();
|
|
122
|
+
if (shape) {
|
|
123
|
+
for (const [key, value] of Object.entries(shape)) {
|
|
124
|
+
if (value instanceof z.ZodType) {
|
|
125
|
+
parameters.push({
|
|
126
|
+
name: key,
|
|
127
|
+
in: "query",
|
|
128
|
+
required: !(value instanceof z.ZodOptional),
|
|
129
|
+
schema: processZodType(value)
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (options.params instanceof z.ZodObject) {
|
|
136
|
+
const shape = options.params.shape || options.params._def?.shape?.();
|
|
137
|
+
if (shape) {
|
|
138
|
+
for (const [key, value] of Object.entries(shape)) {
|
|
139
|
+
if (value instanceof z.ZodType) {
|
|
140
|
+
parameters.push({
|
|
141
|
+
name: key,
|
|
142
|
+
in: "path",
|
|
143
|
+
required: true,
|
|
144
|
+
schema: processZodType(value)
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
return parameters;
|
|
151
|
+
}
|
|
152
|
+
function getRequestBody(options) {
|
|
153
|
+
if (!options.body) return void 0;
|
|
154
|
+
if (options.body instanceof z.ZodType) {
|
|
155
|
+
const schema = processZodType(options.body);
|
|
156
|
+
const isOptional = options.body instanceof z.ZodOptional;
|
|
157
|
+
return {
|
|
158
|
+
required: !isOptional,
|
|
159
|
+
content: {
|
|
160
|
+
"application/json": {
|
|
161
|
+
schema
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
return void 0;
|
|
167
|
+
}
|
|
168
|
+
function createErrorSchema() {
|
|
169
|
+
return {
|
|
170
|
+
type: "object",
|
|
171
|
+
properties: {
|
|
172
|
+
message: { type: "string" }
|
|
173
|
+
},
|
|
174
|
+
required: ["message"]
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function getErrorResponses() {
|
|
178
|
+
return {
|
|
179
|
+
"400": {
|
|
180
|
+
description: "Bad Request",
|
|
181
|
+
content: { "application/json": { schema: createErrorSchema() } }
|
|
182
|
+
},
|
|
183
|
+
"401": {
|
|
184
|
+
description: "Unauthorized",
|
|
185
|
+
content: { "application/json": { schema: createErrorSchema() } }
|
|
186
|
+
},
|
|
187
|
+
"403": {
|
|
188
|
+
description: "Forbidden",
|
|
189
|
+
content: { "application/json": { schema: createErrorSchema() } }
|
|
190
|
+
},
|
|
191
|
+
"404": {
|
|
192
|
+
description: "Not Found",
|
|
193
|
+
content: { "application/json": { schema: createErrorSchema() } }
|
|
194
|
+
},
|
|
195
|
+
"500": {
|
|
196
|
+
description: "Internal Server Error",
|
|
197
|
+
content: { "application/json": { schema: createErrorSchema() } }
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
function generateOpenAPISchema(context, options) {
|
|
202
|
+
const paths = {};
|
|
203
|
+
const tags = [];
|
|
204
|
+
for (const [pluginKey, plugin] of Object.entries(context.plugins)) {
|
|
205
|
+
if (pluginKey === "openApi" || plugin.name === "open-api") {
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
const pluginRoutes = plugin.routes(context.adapter, context);
|
|
209
|
+
const tagName = pluginKey.charAt(0).toUpperCase() + pluginKey.slice(1);
|
|
210
|
+
tags.push({
|
|
211
|
+
name: tagName,
|
|
212
|
+
description: `${tagName} plugin endpoints`
|
|
213
|
+
});
|
|
214
|
+
for (const [routeKey, endpoint] of Object.entries(pluginRoutes)) {
|
|
215
|
+
const ep = endpoint;
|
|
216
|
+
const path = ep.path;
|
|
217
|
+
const endpointOptions = ep.options || {};
|
|
218
|
+
const method = (endpointOptions.method || "GET").toLowerCase();
|
|
219
|
+
if (!path) continue;
|
|
220
|
+
const openApiPath = toOpenApiPath(path);
|
|
221
|
+
if (!paths[openApiPath]) {
|
|
222
|
+
paths[openApiPath] = {};
|
|
223
|
+
}
|
|
224
|
+
const operation = {
|
|
225
|
+
tags: [tagName],
|
|
226
|
+
operationId: `${pluginKey}_${routeKey}`,
|
|
227
|
+
summary: endpointOptions.metadata?.openapi?.summary,
|
|
228
|
+
description: endpointOptions.metadata?.openapi?.description,
|
|
229
|
+
parameters: getParameters(endpointOptions),
|
|
230
|
+
responses: {
|
|
231
|
+
"200": {
|
|
232
|
+
description: "Successful response",
|
|
233
|
+
content: {
|
|
234
|
+
"application/json": {
|
|
235
|
+
schema: { type: "object" }
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
},
|
|
239
|
+
...getErrorResponses()
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
if (["post", "put", "patch"].includes(method)) {
|
|
243
|
+
const requestBody = getRequestBody(endpointOptions);
|
|
244
|
+
if (requestBody) {
|
|
245
|
+
operation.requestBody = requestBody;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
paths[openApiPath][method] = operation;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return {
|
|
252
|
+
openapi: "3.1.0",
|
|
253
|
+
info: {
|
|
254
|
+
title: options?.title || "Better Stack API",
|
|
255
|
+
description: options?.description || "API Reference for your Better Stack application",
|
|
256
|
+
version: options?.version || "1.0.0"
|
|
257
|
+
},
|
|
258
|
+
servers: [
|
|
259
|
+
{
|
|
260
|
+
url: context.basePath,
|
|
261
|
+
description: "API Server"
|
|
262
|
+
}
|
|
263
|
+
],
|
|
264
|
+
tags,
|
|
265
|
+
paths,
|
|
266
|
+
components: {
|
|
267
|
+
securitySchemes: {
|
|
268
|
+
bearerAuth: {
|
|
269
|
+
type: "http",
|
|
270
|
+
scheme: "bearer",
|
|
271
|
+
description: "Bearer token authentication"
|
|
272
|
+
},
|
|
273
|
+
cookieAuth: {
|
|
274
|
+
type: "apiKey",
|
|
275
|
+
in: "cookie",
|
|
276
|
+
name: "session",
|
|
277
|
+
description: "Session cookie authentication"
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export { generateOpenAPISchema };
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const api = require('@btst/stack/plugins/api');
|
|
4
|
+
const db = require('../db.cjs');
|
|
5
|
+
const generator = require('./generator.cjs');
|
|
6
|
+
const logo = require('../logo.cjs');
|
|
7
|
+
|
|
8
|
+
function escapeHtml(str) {
|
|
9
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
10
|
+
}
|
|
11
|
+
function escapeJsonForHtml(json) {
|
|
12
|
+
return json.replace(/</g, "\\u003c");
|
|
13
|
+
}
|
|
14
|
+
function getScalarHTML(schema, theme = "default", nonce) {
|
|
15
|
+
const nonceAttr = nonce ? ` nonce="${escapeHtml(nonce)}"` : "";
|
|
16
|
+
const encodedLogo = encodeURIComponent(logo.logo);
|
|
17
|
+
const title = schema.info?.title || "API Reference";
|
|
18
|
+
const description = schema.info?.description || "API Reference";
|
|
19
|
+
return `<!doctype html>
|
|
20
|
+
<html>
|
|
21
|
+
<head>
|
|
22
|
+
<title>${escapeHtml(title)}</title>
|
|
23
|
+
<meta charset="utf-8" />
|
|
24
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
25
|
+
</head>
|
|
26
|
+
<body>
|
|
27
|
+
<script
|
|
28
|
+
id="api-reference"
|
|
29
|
+
type="application/json"${nonceAttr}>
|
|
30
|
+
${escapeJsonForHtml(JSON.stringify(schema))}
|
|
31
|
+
<\/script>
|
|
32
|
+
<script${nonceAttr}>
|
|
33
|
+
var configuration = {
|
|
34
|
+
favicon: "data:image/svg+xml;utf8,${encodedLogo}",
|
|
35
|
+
theme: "${theme}",
|
|
36
|
+
metaData: {
|
|
37
|
+
title: ${JSON.stringify(title)},
|
|
38
|
+
description: ${JSON.stringify(description)},
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
document.getElementById('api-reference').dataset.configuration =
|
|
43
|
+
JSON.stringify(configuration)
|
|
44
|
+
<\/script>
|
|
45
|
+
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"${nonceAttr}><\/script>
|
|
46
|
+
</body>
|
|
47
|
+
</html>`;
|
|
48
|
+
}
|
|
49
|
+
const openApiBackendPlugin = (options) => {
|
|
50
|
+
const referencePath = options?.path ?? "/reference";
|
|
51
|
+
let storedContext = null;
|
|
52
|
+
return api.defineBackendPlugin({
|
|
53
|
+
name: "open-api",
|
|
54
|
+
dbPlugin: db.openApiSchema,
|
|
55
|
+
routes: (_adapter, context) => {
|
|
56
|
+
storedContext = context ?? null;
|
|
57
|
+
const generateSchema = api.createEndpoint(
|
|
58
|
+
"/open-api/schema",
|
|
59
|
+
{
|
|
60
|
+
method: "GET"
|
|
61
|
+
},
|
|
62
|
+
async (ctx) => {
|
|
63
|
+
if (!storedContext) {
|
|
64
|
+
throw ctx.error(500, {
|
|
65
|
+
message: "OpenAPI context not available"
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
const schema = generator.generateOpenAPISchema(storedContext, {
|
|
69
|
+
title: options?.title,
|
|
70
|
+
description: options?.description,
|
|
71
|
+
version: options?.version
|
|
72
|
+
});
|
|
73
|
+
return schema;
|
|
74
|
+
}
|
|
75
|
+
);
|
|
76
|
+
const reference = api.createEndpoint(
|
|
77
|
+
referencePath,
|
|
78
|
+
{
|
|
79
|
+
method: "GET"
|
|
80
|
+
},
|
|
81
|
+
async (ctx) => {
|
|
82
|
+
if (options?.disableDefaultReference) {
|
|
83
|
+
throw ctx.error(404, {
|
|
84
|
+
message: "Reference page is disabled"
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
if (!storedContext) {
|
|
88
|
+
throw ctx.error(500, {
|
|
89
|
+
message: "OpenAPI context not available"
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
const schema = generator.generateOpenAPISchema(storedContext, {
|
|
93
|
+
title: options?.title,
|
|
94
|
+
description: options?.description,
|
|
95
|
+
version: options?.version
|
|
96
|
+
});
|
|
97
|
+
return new Response(
|
|
98
|
+
getScalarHTML(schema, options?.theme, options?.nonce),
|
|
99
|
+
{
|
|
100
|
+
headers: {
|
|
101
|
+
"Content-Type": "text/html; charset=utf-8"
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
);
|
|
107
|
+
return {
|
|
108
|
+
generateSchema,
|
|
109
|
+
reference
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
exports.openApiBackendPlugin = openApiBackendPlugin;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { defineBackendPlugin, createEndpoint } from '@btst/stack/plugins/api';
|
|
2
|
+
import { openApiSchema } from '../db.mjs';
|
|
3
|
+
import { generateOpenAPISchema } from './generator.mjs';
|
|
4
|
+
import { logo } from '../logo.mjs';
|
|
5
|
+
|
|
6
|
+
function escapeHtml(str) {
|
|
7
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
8
|
+
}
|
|
9
|
+
function escapeJsonForHtml(json) {
|
|
10
|
+
return json.replace(/</g, "\\u003c");
|
|
11
|
+
}
|
|
12
|
+
function getScalarHTML(schema, theme = "default", nonce) {
|
|
13
|
+
const nonceAttr = nonce ? ` nonce="${escapeHtml(nonce)}"` : "";
|
|
14
|
+
const encodedLogo = encodeURIComponent(logo);
|
|
15
|
+
const title = schema.info?.title || "API Reference";
|
|
16
|
+
const description = schema.info?.description || "API Reference";
|
|
17
|
+
return `<!doctype html>
|
|
18
|
+
<html>
|
|
19
|
+
<head>
|
|
20
|
+
<title>${escapeHtml(title)}</title>
|
|
21
|
+
<meta charset="utf-8" />
|
|
22
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
23
|
+
</head>
|
|
24
|
+
<body>
|
|
25
|
+
<script
|
|
26
|
+
id="api-reference"
|
|
27
|
+
type="application/json"${nonceAttr}>
|
|
28
|
+
${escapeJsonForHtml(JSON.stringify(schema))}
|
|
29
|
+
<\/script>
|
|
30
|
+
<script${nonceAttr}>
|
|
31
|
+
var configuration = {
|
|
32
|
+
favicon: "data:image/svg+xml;utf8,${encodedLogo}",
|
|
33
|
+
theme: "${theme}",
|
|
34
|
+
metaData: {
|
|
35
|
+
title: ${JSON.stringify(title)},
|
|
36
|
+
description: ${JSON.stringify(description)},
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
document.getElementById('api-reference').dataset.configuration =
|
|
41
|
+
JSON.stringify(configuration)
|
|
42
|
+
<\/script>
|
|
43
|
+
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"${nonceAttr}><\/script>
|
|
44
|
+
</body>
|
|
45
|
+
</html>`;
|
|
46
|
+
}
|
|
47
|
+
const openApiBackendPlugin = (options) => {
|
|
48
|
+
const referencePath = options?.path ?? "/reference";
|
|
49
|
+
let storedContext = null;
|
|
50
|
+
return defineBackendPlugin({
|
|
51
|
+
name: "open-api",
|
|
52
|
+
dbPlugin: openApiSchema,
|
|
53
|
+
routes: (_adapter, context) => {
|
|
54
|
+
storedContext = context ?? null;
|
|
55
|
+
const generateSchema = createEndpoint(
|
|
56
|
+
"/open-api/schema",
|
|
57
|
+
{
|
|
58
|
+
method: "GET"
|
|
59
|
+
},
|
|
60
|
+
async (ctx) => {
|
|
61
|
+
if (!storedContext) {
|
|
62
|
+
throw ctx.error(500, {
|
|
63
|
+
message: "OpenAPI context not available"
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
const schema = generateOpenAPISchema(storedContext, {
|
|
67
|
+
title: options?.title,
|
|
68
|
+
description: options?.description,
|
|
69
|
+
version: options?.version
|
|
70
|
+
});
|
|
71
|
+
return schema;
|
|
72
|
+
}
|
|
73
|
+
);
|
|
74
|
+
const reference = createEndpoint(
|
|
75
|
+
referencePath,
|
|
76
|
+
{
|
|
77
|
+
method: "GET"
|
|
78
|
+
},
|
|
79
|
+
async (ctx) => {
|
|
80
|
+
if (options?.disableDefaultReference) {
|
|
81
|
+
throw ctx.error(404, {
|
|
82
|
+
message: "Reference page is disabled"
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
if (!storedContext) {
|
|
86
|
+
throw ctx.error(500, {
|
|
87
|
+
message: "OpenAPI context not available"
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
const schema = generateOpenAPISchema(storedContext, {
|
|
91
|
+
title: options?.title,
|
|
92
|
+
description: options?.description,
|
|
93
|
+
version: options?.version
|
|
94
|
+
});
|
|
95
|
+
return new Response(
|
|
96
|
+
getScalarHTML(schema, options?.theme, options?.nonce),
|
|
97
|
+
{
|
|
98
|
+
headers: {
|
|
99
|
+
"Content-Type": "text/html; charset=utf-8"
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
);
|
|
105
|
+
return {
|
|
106
|
+
generateSchema,
|
|
107
|
+
reference
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export { openApiBackendPlugin };
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const logo = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" fill="none">
|
|
4
|
+
<rect width="100" height="100" rx="20" fill="#0ea5e9"/>
|
|
5
|
+
<path d="M25 35h50M25 50h50M25 65h35" stroke="white" stroke-width="8" stroke-linecap="round"/>
|
|
6
|
+
</svg>`;
|
|
7
|
+
|
|
8
|
+
exports.logo = logo;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
const logo = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" fill="none">
|
|
2
|
+
<rect width="100" height="100" rx="20" fill="#0ea5e9"/>
|
|
3
|
+
<path d="M25 35h50M25 50h50M25 65h35" stroke="white" stroke-width="8" stroke-linecap="round"/>
|
|
4
|
+
</svg>`;
|
|
5
|
+
|
|
6
|
+
export { logo };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const jsxRuntime = require('react/jsx-runtime');
|
|
5
|
+
const skeleton = require('../../../../../../../ui/src/components/skeleton.cjs');
|
|
6
|
+
const scrollArea = require('../../../../../../../ui/src/components/scroll-area.cjs');
|
|
7
|
+
const card = require('../../../../../../../ui/src/components/card.cjs');
|
|
8
|
+
|
|
9
|
+
function DocsPageSkeleton() {
|
|
10
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-screen bg-background", children: [
|
|
11
|
+
/* @__PURE__ */ jsxRuntime.jsxs("aside", { className: "hidden md:block w-72 border-r bg-card shrink-0", children: [
|
|
12
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 border-b", children: /* @__PURE__ */ jsxRuntime.jsx(skeleton.Skeleton, { className: "h-4 w-16" }) }),
|
|
13
|
+
/* @__PURE__ */ jsxRuntime.jsx(scrollArea.ScrollArea, { className: "h-[calc(100vh-57px)]", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-3 space-y-4", children: [1, 2, 3].map((i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
14
|
+
/* @__PURE__ */ jsxRuntime.jsx(skeleton.Skeleton, { className: "h-8 w-full" }),
|
|
15
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "ml-2 space-y-1", children: [1, 2, 3].map((j) => /* @__PURE__ */ jsxRuntime.jsx(skeleton.Skeleton, { className: "h-7 w-full" }, j)) })
|
|
16
|
+
] }, i)) }) })
|
|
17
|
+
] }),
|
|
18
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "md:hidden fixed top-0 left-0 right-0 z-40 bg-card border-b", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between p-4", children: [
|
|
19
|
+
/* @__PURE__ */ jsxRuntime.jsx(skeleton.Skeleton, { className: "h-4 w-24" }),
|
|
20
|
+
/* @__PURE__ */ jsxRuntime.jsx(skeleton.Skeleton, { className: "h-8 w-20" })
|
|
21
|
+
] }) }),
|
|
22
|
+
/* @__PURE__ */ jsxRuntime.jsx("main", { className: "flex-1 overflow-auto pt-16 md:pt-0", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "max-w-4xl mx-auto p-4 sm:p-6 lg:p-8 space-y-4 sm:space-y-6", children: [
|
|
23
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
24
|
+
/* @__PURE__ */ jsxRuntime.jsx(skeleton.Skeleton, { className: "h-8 sm:h-9 w-48 sm:w-64" }),
|
|
25
|
+
/* @__PURE__ */ jsxRuntime.jsx(skeleton.Skeleton, { className: "h-4 sm:h-5 w-72 sm:w-96" })
|
|
26
|
+
] }),
|
|
27
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-px bg-border" }),
|
|
28
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
|
|
29
|
+
/* @__PURE__ */ jsxRuntime.jsx(skeleton.Skeleton, { className: "h-6 w-24" }),
|
|
30
|
+
/* @__PURE__ */ jsxRuntime.jsx(skeleton.Skeleton, { className: "h-6 w-20" })
|
|
31
|
+
] }),
|
|
32
|
+
/* @__PURE__ */ jsxRuntime.jsxs(card.Card, { children: [
|
|
33
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 sm:p-6", children: /* @__PURE__ */ jsxRuntime.jsx(skeleton.Skeleton, { className: "h-6 w-32 mb-4" }) }),
|
|
34
|
+
/* @__PURE__ */ jsxRuntime.jsxs(card.CardContent, { className: "pt-0 space-y-4", children: [
|
|
35
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "hidden md:block space-y-2", children: [1, 2, 3, 4].map((i) => /* @__PURE__ */ jsxRuntime.jsx(skeleton.Skeleton, { className: "h-12 w-full" }, i)) }),
|
|
36
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "md:hidden space-y-3", children: [1, 2, 3].map((i) => /* @__PURE__ */ jsxRuntime.jsx(skeleton.Skeleton, { className: "h-28 w-full rounded-lg" }, i)) })
|
|
37
|
+
] })
|
|
38
|
+
] })
|
|
39
|
+
] }) })
|
|
40
|
+
] });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
exports.DocsPageSkeleton = DocsPageSkeleton;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
3
|
+
import { Skeleton } from '../../../../../../../ui/src/components/skeleton.mjs';
|
|
4
|
+
import { ScrollArea } from '../../../../../../../ui/src/components/scroll-area.mjs';
|
|
5
|
+
import { Card, CardContent } from '../../../../../../../ui/src/components/card.mjs';
|
|
6
|
+
|
|
7
|
+
function DocsPageSkeleton() {
|
|
8
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex min-h-screen bg-background", children: [
|
|
9
|
+
/* @__PURE__ */ jsxs("aside", { className: "hidden md:block w-72 border-r bg-card shrink-0", children: [
|
|
10
|
+
/* @__PURE__ */ jsx("div", { className: "p-4 border-b", children: /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-16" }) }),
|
|
11
|
+
/* @__PURE__ */ jsx(ScrollArea, { className: "h-[calc(100vh-57px)]", children: /* @__PURE__ */ jsx("div", { className: "p-3 space-y-4", children: [1, 2, 3].map((i) => /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
12
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-8 w-full" }),
|
|
13
|
+
/* @__PURE__ */ jsx("div", { className: "ml-2 space-y-1", children: [1, 2, 3].map((j) => /* @__PURE__ */ jsx(Skeleton, { className: "h-7 w-full" }, j)) })
|
|
14
|
+
] }, i)) }) })
|
|
15
|
+
] }),
|
|
16
|
+
/* @__PURE__ */ jsx("div", { className: "md:hidden fixed top-0 left-0 right-0 z-40 bg-card border-b", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between p-4", children: [
|
|
17
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-24" }),
|
|
18
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-8 w-20" })
|
|
19
|
+
] }) }),
|
|
20
|
+
/* @__PURE__ */ jsx("main", { className: "flex-1 overflow-auto pt-16 md:pt-0", children: /* @__PURE__ */ jsxs("div", { className: "max-w-4xl mx-auto p-4 sm:p-6 lg:p-8 space-y-4 sm:space-y-6", children: [
|
|
21
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
22
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-8 sm:h-9 w-48 sm:w-64" }),
|
|
23
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-4 sm:h-5 w-72 sm:w-96" })
|
|
24
|
+
] }),
|
|
25
|
+
/* @__PURE__ */ jsx("div", { className: "h-px bg-border" }),
|
|
26
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
27
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-6 w-24" }),
|
|
28
|
+
/* @__PURE__ */ jsx(Skeleton, { className: "h-6 w-20" })
|
|
29
|
+
] }),
|
|
30
|
+
/* @__PURE__ */ jsxs(Card, { children: [
|
|
31
|
+
/* @__PURE__ */ jsx("div", { className: "p-4 sm:p-6", children: /* @__PURE__ */ jsx(Skeleton, { className: "h-6 w-32 mb-4" }) }),
|
|
32
|
+
/* @__PURE__ */ jsxs(CardContent, { className: "pt-0 space-y-4", children: [
|
|
33
|
+
/* @__PURE__ */ jsx("div", { className: "hidden md:block space-y-2", children: [1, 2, 3, 4].map((i) => /* @__PURE__ */ jsx(Skeleton, { className: "h-12 w-full" }, i)) }),
|
|
34
|
+
/* @__PURE__ */ jsx("div", { className: "md:hidden space-y-3", children: [1, 2, 3].map((i) => /* @__PURE__ */ jsx(Skeleton, { className: "h-28 w-full rounded-lg" }, i)) })
|
|
35
|
+
] })
|
|
36
|
+
] })
|
|
37
|
+
] }) })
|
|
38
|
+
] });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export { DocsPageSkeleton };
|