@btst/stack 1.6.0 → 1.7.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.d.cts +1 -1
- package/dist/client/index.d.mts +1 -1
- package/dist/client/index.d.ts +1 -1
- 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/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/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/shared/{stack.ByOugz9d.d.cts → stack.CSce37mX.d.cts} +15 -2
- package/dist/shared/{stack.ByOugz9d.d.mts → stack.CSce37mX.d.mts} +15 -2
- package/dist/shared/{stack.ByOugz9d.d.ts → stack.CSce37mX.d.ts} +15 -2
- package/package.json +14 -1
- package/src/api/index.ts +14 -2
- 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/types.ts +15 -1
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { defineBackendPlugin } from "@btst/stack/plugins/api";
|
|
2
|
+
import { createEndpoint } from "@btst/stack/plugins/api";
|
|
3
|
+
import { openApiSchema } from "../db";
|
|
4
|
+
import { generateOpenAPISchema } from "./generator";
|
|
5
|
+
import { logo } from "../logo";
|
|
6
|
+
import type { BetterStackContext } from "../../../types";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Scalar API Reference themes
|
|
10
|
+
*/
|
|
11
|
+
export type ScalarTheme =
|
|
12
|
+
| "alternate"
|
|
13
|
+
| "default"
|
|
14
|
+
| "moon"
|
|
15
|
+
| "purple"
|
|
16
|
+
| "solarized"
|
|
17
|
+
| "bluePlanet"
|
|
18
|
+
| "saturn"
|
|
19
|
+
| "kepler"
|
|
20
|
+
| "mars"
|
|
21
|
+
| "deepSpace"
|
|
22
|
+
| "laserwave"
|
|
23
|
+
| "none";
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* OpenAPI plugin configuration options
|
|
27
|
+
*/
|
|
28
|
+
export interface OpenAPIOptions {
|
|
29
|
+
/**
|
|
30
|
+
* The path to the OpenAPI reference page
|
|
31
|
+
* This path is relative to the API base path
|
|
32
|
+
* @default "/reference"
|
|
33
|
+
*/
|
|
34
|
+
path?: string;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Disable the default HTML reference page
|
|
38
|
+
* Only the JSON schema endpoint will be available
|
|
39
|
+
* @default false
|
|
40
|
+
*/
|
|
41
|
+
disableDefaultReference?: boolean;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Theme for the Scalar API Reference page
|
|
45
|
+
* @default "default"
|
|
46
|
+
*/
|
|
47
|
+
theme?: ScalarTheme;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* CSP nonce for inline scripts
|
|
51
|
+
* Required for strict Content Security Policy
|
|
52
|
+
*/
|
|
53
|
+
nonce?: string;
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Custom title for the API documentation
|
|
57
|
+
* @default "Better Stack API"
|
|
58
|
+
*/
|
|
59
|
+
title?: string;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Custom description for the API documentation
|
|
63
|
+
*/
|
|
64
|
+
description?: string;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* API version string
|
|
68
|
+
* @default "1.0.0"
|
|
69
|
+
*/
|
|
70
|
+
version?: string;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Escape HTML entities to prevent XSS and ensure proper rendering
|
|
75
|
+
*/
|
|
76
|
+
function escapeHtml(str: string): string {
|
|
77
|
+
return str
|
|
78
|
+
.replace(/&/g, "&")
|
|
79
|
+
.replace(/</g, "<")
|
|
80
|
+
.replace(/>/g, ">")
|
|
81
|
+
.replace(/"/g, """)
|
|
82
|
+
.replace(/'/g, "'");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Escape JSON for safe embedding in HTML script tags.
|
|
87
|
+
* Replaces < with \u003c to prevent </script> from closing the tag prematurely.
|
|
88
|
+
* This is valid JSON and will be parsed correctly.
|
|
89
|
+
*/
|
|
90
|
+
function escapeJsonForHtml(json: string): string {
|
|
91
|
+
return json.replace(/</g, "\\u003c");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Generate the HTML page for Scalar API Reference
|
|
96
|
+
*/
|
|
97
|
+
function getScalarHTML(
|
|
98
|
+
schema: Record<string, any>,
|
|
99
|
+
theme: ScalarTheme = "default",
|
|
100
|
+
nonce?: string,
|
|
101
|
+
): string {
|
|
102
|
+
const nonceAttr = nonce ? ` nonce="${escapeHtml(nonce)}"` : "";
|
|
103
|
+
const encodedLogo = encodeURIComponent(logo);
|
|
104
|
+
|
|
105
|
+
const title = schema.info?.title || "API Reference";
|
|
106
|
+
const description = schema.info?.description || "API Reference";
|
|
107
|
+
|
|
108
|
+
return `<!doctype html>
|
|
109
|
+
<html>
|
|
110
|
+
<head>
|
|
111
|
+
<title>${escapeHtml(title)}</title>
|
|
112
|
+
<meta charset="utf-8" />
|
|
113
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
114
|
+
</head>
|
|
115
|
+
<body>
|
|
116
|
+
<script
|
|
117
|
+
id="api-reference"
|
|
118
|
+
type="application/json"${nonceAttr}>
|
|
119
|
+
${escapeJsonForHtml(JSON.stringify(schema))}
|
|
120
|
+
</script>
|
|
121
|
+
<script${nonceAttr}>
|
|
122
|
+
var configuration = {
|
|
123
|
+
favicon: "data:image/svg+xml;utf8,${encodedLogo}",
|
|
124
|
+
theme: "${theme}",
|
|
125
|
+
metaData: {
|
|
126
|
+
title: ${JSON.stringify(title)},
|
|
127
|
+
description: ${JSON.stringify(description)},
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
document.getElementById('api-reference').dataset.configuration =
|
|
132
|
+
JSON.stringify(configuration)
|
|
133
|
+
</script>
|
|
134
|
+
<script src="https://cdn.jsdelivr.net/npm/@scalar/api-reference"${nonceAttr}></script>
|
|
135
|
+
</body>
|
|
136
|
+
</html>`;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* OpenAPI plugin for Better Stack
|
|
141
|
+
*
|
|
142
|
+
* Automatically generates OpenAPI 3.1 documentation for all registered plugins.
|
|
143
|
+
* Provides both a JSON schema endpoint and an interactive Scalar UI reference page.
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* ```ts
|
|
147
|
+
* const { handler } = betterStack({
|
|
148
|
+
* basePath: "/api/data",
|
|
149
|
+
* plugins: {
|
|
150
|
+
* blog: blogBackendPlugin(),
|
|
151
|
+
* cms: cmsBackendPlugin({ ... }),
|
|
152
|
+
* openApi: openApiBackendPlugin({ theme: "moon" }),
|
|
153
|
+
* },
|
|
154
|
+
* adapter: (db) => createMemoryAdapter(db)({}),
|
|
155
|
+
* });
|
|
156
|
+
*
|
|
157
|
+
* // Access:
|
|
158
|
+
* // - GET /api/data/open-api/schema - JSON schema
|
|
159
|
+
* // - GET /api/data/reference - Interactive Scalar UI
|
|
160
|
+
* ```
|
|
161
|
+
*/
|
|
162
|
+
export const openApiBackendPlugin = (options?: OpenAPIOptions) => {
|
|
163
|
+
const referencePath = options?.path ?? "/reference";
|
|
164
|
+
|
|
165
|
+
// Store context for use in endpoint handlers
|
|
166
|
+
let storedContext: BetterStackContext | null = null;
|
|
167
|
+
|
|
168
|
+
return defineBackendPlugin({
|
|
169
|
+
name: "open-api",
|
|
170
|
+
dbPlugin: openApiSchema,
|
|
171
|
+
|
|
172
|
+
routes: (_adapter, context) => {
|
|
173
|
+
// Store context for endpoint handlers
|
|
174
|
+
storedContext = context ?? null;
|
|
175
|
+
|
|
176
|
+
const generateSchema = createEndpoint(
|
|
177
|
+
"/open-api/schema",
|
|
178
|
+
{
|
|
179
|
+
method: "GET",
|
|
180
|
+
},
|
|
181
|
+
async (ctx) => {
|
|
182
|
+
if (!storedContext) {
|
|
183
|
+
throw ctx.error(500, {
|
|
184
|
+
message: "OpenAPI context not available",
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const schema = generateOpenAPISchema(storedContext, {
|
|
189
|
+
title: options?.title,
|
|
190
|
+
description: options?.description,
|
|
191
|
+
version: options?.version,
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
return schema;
|
|
195
|
+
},
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
const reference = createEndpoint(
|
|
199
|
+
referencePath,
|
|
200
|
+
{
|
|
201
|
+
method: "GET",
|
|
202
|
+
},
|
|
203
|
+
async (ctx) => {
|
|
204
|
+
if (options?.disableDefaultReference) {
|
|
205
|
+
throw ctx.error(404, {
|
|
206
|
+
message: "Reference page is disabled",
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (!storedContext) {
|
|
211
|
+
throw ctx.error(500, {
|
|
212
|
+
message: "OpenAPI context not available",
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const schema = generateOpenAPISchema(storedContext, {
|
|
217
|
+
title: options?.title,
|
|
218
|
+
description: options?.description,
|
|
219
|
+
version: options?.version,
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
return new Response(
|
|
223
|
+
getScalarHTML(schema, options?.theme, options?.nonce),
|
|
224
|
+
{
|
|
225
|
+
headers: {
|
|
226
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
);
|
|
230
|
+
},
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
generateSchema,
|
|
235
|
+
reference,
|
|
236
|
+
} as const;
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
export type OpenApiRouter = ReturnType<
|
|
242
|
+
ReturnType<typeof openApiBackendPlugin>["routes"]
|
|
243
|
+
>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Better Stack logo SVG for use in Scalar API Reference
|
|
3
|
+
*/
|
|
4
|
+
export const logo = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" fill="none">
|
|
5
|
+
<rect width="100" height="100" rx="20" fill="#0ea5e9"/>
|
|
6
|
+
<path d="M25 35h50M25 50h50M25 65h35" stroke="white" stroke-width="8" stroke-linecap="round"/>
|
|
7
|
+
</svg>`;
|
package/src/types.ts
CHANGED
|
@@ -2,6 +2,19 @@ import type { Route, createRouter } from "@btst/yar";
|
|
|
2
2
|
import type { Adapter, DatabaseDefinition, DbPlugin } from "@btst/db";
|
|
3
3
|
import type { Endpoint, Router } from "better-call";
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* Context passed to backend plugins during route creation
|
|
7
|
+
* Provides access to all registered plugins for introspection (used by openAPI plugin)
|
|
8
|
+
*/
|
|
9
|
+
export interface BetterStackContext {
|
|
10
|
+
/** All registered backend plugins */
|
|
11
|
+
plugins: Record<string, BackendPlugin<any>>;
|
|
12
|
+
/** The API base path (e.g., "/api/data") */
|
|
13
|
+
basePath: string;
|
|
14
|
+
/** The database adapter */
|
|
15
|
+
adapter: Adapter;
|
|
16
|
+
}
|
|
17
|
+
|
|
5
18
|
/**
|
|
6
19
|
* Backend plugin definition
|
|
7
20
|
* Defines API routes and data access for a feature
|
|
@@ -23,8 +36,9 @@ export interface BackendPlugin<
|
|
|
23
36
|
*
|
|
24
37
|
* @param adapter - Better DB adapter instance with methods:
|
|
25
38
|
* create, update, updateMany, delete, deleteMany, findOne, findMany, count
|
|
39
|
+
* @param context - Optional context with access to all plugins (for introspection)
|
|
26
40
|
*/
|
|
27
|
-
routes: (adapter: Adapter) => TRoutes;
|
|
41
|
+
routes: (adapter: Adapter, context?: BetterStackContext) => TRoutes;
|
|
28
42
|
dbPlugin: DbPlugin;
|
|
29
43
|
}
|
|
30
44
|
|