bxo 0.0.5-dev.7 → 0.0.5-dev.71

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,49 @@
1
+ import BXO from "../src/index";
2
+ import { cors } from "../plugins";
3
+
4
+ const app = new BXO();
5
+
6
+ // Use the CORS plugin
7
+ app.use(cors({
8
+ origin: ["http://localhost:3000", "http://localhost:3001"],
9
+ methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
10
+ allowedHeaders: ["Content-Type", "Authorization"],
11
+ credentials: true
12
+ }));
13
+
14
+ // Add some routes
15
+ app.get("/api/users", async (ctx) => {
16
+ return ctx.json([
17
+ { id: 1, name: "John Doe" },
18
+ { id: 2, name: "Jane Smith" }
19
+ ]);
20
+ });
21
+
22
+ app.post("/api/users", async (ctx) => {
23
+ const user = ctx.body as { name: string };
24
+ return ctx.json({ id: 3, name: user.name }, 201);
25
+ });
26
+
27
+ // Custom beforeRequest hook example
28
+ app.beforeRequest(async (req) => {
29
+ console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`);
30
+ return req; // Continue with the request
31
+ });
32
+
33
+ // Custom afterRequest hook example
34
+ app.afterRequest(async (req, res) => {
35
+ console.log(`[${new Date().toISOString()}] Response: ${res.status}`);
36
+ return res; // Return the modified response
37
+ });
38
+
39
+ // Custom error handler
40
+ app.onError(async (error, req) => {
41
+ console.error(`Error handling ${req.method} ${req.url}:`, error);
42
+ return new Response("Something went wrong", { status: 500 });
43
+ });
44
+
45
+ // Start the server
46
+ app.start();
47
+
48
+ console.log("Server running on http://localhost:3000");
49
+ console.log("Try making a CORS request from another origin!");
@@ -0,0 +1,5 @@
1
+ <html>
2
+ <body>
3
+ <h1>Hello, world!</h1>
4
+ </body>
5
+ </html>
@@ -0,0 +1,191 @@
1
+ import BXO, { z } from "../src";
2
+ import index from "./index.html";
3
+ import { openapi } from "../plugins/openapi";
4
+
5
+ async function main() {
6
+ const bxo = new BXO();
7
+
8
+ bxo.default("/", index);
9
+ bxo.default("/*", index);
10
+
11
+ // API routes with comprehensive metadata
12
+ bxo.get("/api/get/:id", (ctx) => {
13
+ return new Response(ctx.params.id + ctx.query.name, {
14
+ headers: {
15
+ "Content-Type": "text/html"
16
+ }
17
+ });
18
+ }, {
19
+ query: z.object({
20
+ name: z.number()
21
+ }),
22
+ response: {
23
+ 200: z.object({
24
+ name: z.string()
25
+ })
26
+ },
27
+ detail: {
28
+ tags: ["API"],
29
+ summary: "Get data by ID",
30
+ description: "Retrieve data using an ID and name query parameter",
31
+ params: {
32
+ id: z.string().describe("Unique identifier for the data")
33
+ }
34
+ }
35
+ });
36
+
37
+ bxo.post("/api/post", (ctx) => {
38
+ console.log(ctx.body)
39
+ return new Response("Hello" + ctx.body.name, {
40
+ headers: {
41
+ "Content-Type": "text/html"
42
+ }
43
+ });
44
+ }, {
45
+ detail: {
46
+ tags: ["API"],
47
+ summary: "Create new data",
48
+ description: "Submit new data with name and avatar file",
49
+ defaultContentType: "multipart/form-data"
50
+ },
51
+ body: z.object({
52
+ name: z.string(),
53
+ avatar: z.file()
54
+ }),
55
+ response: {
56
+ 200: z.object({
57
+ name: z.string()
58
+ }),
59
+ 400: z.object({
60
+ error: z.string()
61
+ })
62
+ }
63
+ });
64
+
65
+ // Additional routes to showcase different features
66
+ bxo.get("/api/users", (ctx) => {
67
+ return ctx.json({ users: ["John", "Jane", "Bob"] });
68
+ }, {
69
+ detail: {
70
+ tags: ["Users"],
71
+ summary: "Get all users",
72
+ description: "Retrieve a list of all users in the system"
73
+ },
74
+ response: {
75
+ 200: z.object({
76
+ users: z.array(z.string())
77
+ })
78
+ }
79
+ });
80
+
81
+ bxo.get("/api/users/:id", (ctx) => {
82
+ const id = ctx.params.id;
83
+ return ctx.json({ user: { id, name: "John Doe", email: "john@example.com" } });
84
+ }, {
85
+ detail: {
86
+ tags: ["Users"],
87
+ summary: "Get user by ID",
88
+ description: "Retrieve a specific user by their unique identifier",
89
+ params: {
90
+ id: z.string().describe("User's unique identifier")
91
+ }
92
+ },
93
+ response: {
94
+ 200: z.object({
95
+ user: z.object({
96
+ id: z.string(),
97
+ name: z.string(),
98
+ email: z.string()
99
+ })
100
+ }),
101
+ 404: z.object({
102
+ error: z.string()
103
+ })
104
+ }
105
+ });
106
+
107
+ bxo.post("/api/users", (ctx) => {
108
+ const userData = ctx.body;
109
+ return ctx.json({ message: "User created", user: userData });
110
+ }, {
111
+ detail: {
112
+ tags: ["Users"],
113
+ summary: "Create new user",
114
+ description: "Create a new user account with the provided information"
115
+ },
116
+ body: z.object({
117
+ name: z.string().min(1, "Name is required"),
118
+ email: z.string().email("Invalid email format"),
119
+ age: z.number().min(18, "Must be at least 18 years old").optional()
120
+ }),
121
+ response: {
122
+ 201: z.object({
123
+ message: z.string(),
124
+ user: z.object({
125
+ name: z.string(),
126
+ email: z.string(),
127
+ age: z.number().optional()
128
+ })
129
+ }),
130
+ 400: z.object({
131
+ error: z.string(),
132
+ issues: z.array(z.any()).optional()
133
+ })
134
+ }
135
+ });
136
+
137
+ // Health check route
138
+ bxo.get("/health", (ctx) => {
139
+ return ctx.json({
140
+ status: "ok",
141
+ timestamp: new Date().toISOString(),
142
+ uptime: process.uptime()
143
+ });
144
+ }, {
145
+ detail: {
146
+ tags: ["System"],
147
+ summary: "Health check",
148
+ description: "Check the health status of the API server"
149
+ },
150
+ response: {
151
+ 200: z.object({
152
+ status: z.string(),
153
+ timestamp: z.string(),
154
+ uptime: z.number()
155
+ })
156
+ }
157
+ });
158
+
159
+ // Use OpenAPI plugin with enhanced configuration
160
+ bxo.use(openapi({
161
+ path: "/docs",
162
+ jsonPath: "/openapi.json",
163
+ defaultTags: ["API"],
164
+ securitySchemes: {
165
+ bearerAuth: {
166
+ type: "http",
167
+ scheme: "bearer",
168
+ bearerFormat: "JWT",
169
+ description: "JWT token for authentication"
170
+ },
171
+ apiKeyAuth: {
172
+ type: "apiKey",
173
+ in: "header",
174
+ name: "X-API-Key",
175
+ description: "API key for authentication"
176
+ }
177
+ },
178
+ globalSecurity: [
179
+ { bearerAuth: [] },
180
+ { apiKeyAuth: [] }
181
+ ],
182
+ openapiConfig: {}
183
+ }));
184
+
185
+ bxo.start();
186
+ console.log(`Server is running on http://localhost:${bxo.server?.port}`);
187
+ console.log(`OpenAPI documentation available at http://localhost:${bxo.server?.port}/docs`);
188
+ console.log(`OpenAPI JSON available at http://localhost:${bxo.server?.port}/openapi.json`);
189
+ }
190
+
191
+ main();
@@ -0,0 +1,132 @@
1
+ import BXO, { z } from "../src";
2
+ import { openapi } from "../plugins/openapi";
3
+
4
+ // Create a BXO app with OpenAPI plugin
5
+ const app = new BXO()
6
+ .use(openapi({
7
+ path: "/docs",
8
+ jsonPath: "/openapi.json",
9
+ defaultTags: ["API"],
10
+ securitySchemes: {
11
+ bearerAuth: {
12
+ type: "http",
13
+ scheme: "bearer",
14
+ bearerFormat: "JWT",
15
+ description: "JWT token for authentication"
16
+ },
17
+ apiKeyAuth: {
18
+ type: "apiKey",
19
+ in: "header",
20
+ name: "X-API-Key",
21
+ description: "API key for authentication"
22
+ }
23
+ },
24
+ globalSecurity: [
25
+ { bearerAuth: [] },
26
+ { apiKeyAuth: [] }
27
+ ],
28
+ openapiConfig: {
29
+ info: {
30
+ title: "My API with Security",
31
+ version: "1.0.0",
32
+ description: "An example API with OpenAPI documentation, tags, and security"
33
+ }
34
+ }
35
+ }))
36
+
37
+ // Example routes with tags and security
38
+ app.get("/users", (ctx) => {
39
+ return { users: [] }
40
+ }, {
41
+ detail: {
42
+ tags: ["Users"],
43
+ summary: "Get all users",
44
+ description: "Retrieve a list of all users",
45
+ security: [{ bearerAuth: [] }]
46
+ }
47
+ })
48
+
49
+ app.get("/users/:id", (ctx) => {
50
+ const id = ctx.params.id
51
+ return { user: { id, name: "John Doe" } }
52
+ }, {
53
+ detail: {
54
+ tags: ["Users"],
55
+ summary: "Get user by ID",
56
+ description: "Retrieve a specific user by their ID",
57
+ params: {
58
+ id: z.string().describe("User ID")
59
+ },
60
+ security: [{ apiKeyAuth: [] }]
61
+ }
62
+ })
63
+
64
+ app.post("/users", (ctx) => {
65
+ const body = ctx.body
66
+ return { message: "User created", user: body }
67
+ }, {
68
+ body: z.object({
69
+ name: z.string(),
70
+ email: z.string().email()
71
+ }),
72
+ detail: {
73
+ tags: ["Users"],
74
+ summary: "Create a new user",
75
+ description: "Create a new user with the provided information",
76
+ security: [{ bearerAuth: [] }]
77
+ }
78
+ })
79
+
80
+ app.get("/products", (ctx) => {
81
+ return { products: [] }
82
+ }, {
83
+ detail: {
84
+ tags: ["Products"],
85
+ summary: "Get all products",
86
+ description: "Retrieve a list of all products"
87
+ }
88
+ })
89
+
90
+ app.get("/products/:id", (ctx) => {
91
+ const id = ctx.params.id
92
+ return { product: { id, name: "Sample Product" } }
93
+ }, {
94
+ detail: {
95
+ tags: ["Products"],
96
+ summary: "Get product by ID",
97
+ description: "Retrieve a specific product by its ID",
98
+ params: {
99
+ id: z.string().describe("Product ID")
100
+ }
101
+ }
102
+ })
103
+
104
+ // Admin routes with different security
105
+ app.get("/admin/users", (ctx) => {
106
+ return { adminUsers: [] }
107
+ }, {
108
+ detail: {
109
+ tags: ["Admin"],
110
+ summary: "Get all users (Admin)",
111
+ description: "Admin-only endpoint to retrieve all users",
112
+ security: [{ bearerAuth: [] }, { apiKeyAuth: [] }]
113
+ }
114
+ })
115
+
116
+ // Health check route (no security required)
117
+ app.get("/health", (ctx) => {
118
+ return { status: "ok", timestamp: new Date().toISOString() }
119
+ }, {
120
+ detail: {
121
+ tags: ["System"],
122
+ summary: "Health check",
123
+ description: "Check if the API is running"
124
+ }
125
+ })
126
+
127
+ // Start the server
128
+ app.listen(3000, () => {
129
+ console.log("Server running on http://localhost:3000")
130
+ console.log("OpenAPI docs available at http://localhost:3000/docs")
131
+ console.log("OpenAPI JSON available at http://localhost:3000/openapi.json")
132
+ })
package/package.json CHANGED
@@ -1,20 +1,20 @@
1
1
  {
2
2
  "name": "bxo",
3
- "module": "index.ts",
4
- "version": "0.0.5-dev.7",
5
- "description": "A simple and lightweight web framework for Bun",
3
+ "module": "./src/index.ts",
4
+ "exports": {
5
+ ".": "./src/index.ts",
6
+ "./plugins": "./plugins/index.ts"
7
+ },
8
+ "version": "0.0.5-dev.71",
6
9
  "type": "module",
7
10
  "devDependencies": {
8
11
  "@types/bun": "latest"
9
12
  },
10
- "exports": {
11
- ".": "./index.ts",
12
- "./plugins": "./plugins/index.ts"
13
- },
14
13
  "peerDependencies": {
15
14
  "typescript": "^5"
16
15
  },
17
16
  "dependencies": {
18
- "zod": "^4.0.5"
17
+ "zod": "^4.1.5",
18
+ "zod-openapi": "^5.4.0"
19
19
  }
20
20
  }
package/plugins/cors.ts CHANGED
@@ -1,83 +1,133 @@
1
- import BXO from '../index';
2
-
3
- interface CORSOptions {
4
- origin?: string | string[] | boolean;
5
- methods?: string[];
6
- allowedHeaders?: string[];
7
- credentials?: boolean;
8
- maxAge?: number;
1
+ import BXO from "../src/index";
2
+
3
+ export interface CorsOptions {
4
+ origin?: string | string[] | boolean | ((origin: string) => boolean);
5
+ methods?: string[];
6
+ allowedHeaders?: string[];
7
+ exposedHeaders?: string[];
8
+ credentials?: boolean;
9
+ maxAge?: number;
10
+ preflightContinue?: boolean;
9
11
  }
10
12
 
11
- export function cors(options: CORSOptions = {}): BXO {
12
- const {
13
- origin = '*',
14
- methods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'],
15
- allowedHeaders = ['Content-Type', 'Authorization'],
16
- credentials = false,
17
- maxAge = 86400
18
- } = options;
19
-
20
- const corsInstance = new BXO();
21
-
22
- corsInstance.onRequest(async (ctx: any) => {
23
- // Handle preflight OPTIONS request
24
- if (ctx.request.method === 'OPTIONS') {
25
- const headers: Record<string, string> = {};
26
-
27
- // Handle origin
28
- if (typeof origin === 'boolean') {
29
- if (origin) {
30
- headers['Access-Control-Allow-Origin'] = ctx.request.headers.get('origin') || '*';
13
+ export function cors(options: CorsOptions = {}): BXO {
14
+ const {
15
+ origin = "*",
16
+ methods = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"],
17
+ allowedHeaders = ["Content-Type", "Authorization"],
18
+ exposedHeaders = [],
19
+ credentials = false,
20
+ maxAge = 86400,
21
+ preflightContinue = false
22
+ } = options;
23
+
24
+ const plugin = new BXO();
25
+
26
+ // Handle CORS preflight requests
27
+ plugin.beforeRequest(async (req) => {
28
+ const requestOrigin = req.headers.get("origin");
29
+ const requestMethod = req.method;
30
+
31
+ // Handle preflight OPTIONS request
32
+ if (requestMethod === "OPTIONS") {
33
+ const response = new Response(null, { status: 204 });
34
+
35
+ // Set CORS headers
36
+ setCorsHeaders(response, {
37
+ origin,
38
+ methods,
39
+ allowedHeaders,
40
+ exposedHeaders,
41
+ credentials,
42
+ maxAge,
43
+ requestOrigin
44
+ });
45
+
46
+ if (!preflightContinue) {
47
+ return response;
48
+ }
31
49
  }
32
- } else if (typeof origin === 'string') {
33
- headers['Access-Control-Allow-Origin'] = origin;
34
- } else if (Array.isArray(origin)) {
35
- const requestOrigin = ctx.request.headers.get('origin');
36
- if (requestOrigin && origin.includes(requestOrigin)) {
37
- headers['Access-Control-Allow-Origin'] = requestOrigin;
50
+
51
+ // Continue with normal request processing
52
+ return req;
53
+ });
54
+
55
+ // Add CORS headers to all responses
56
+ plugin.afterRequest(async (req, res) => {
57
+ const requestOrigin = req.headers.get("origin");
58
+
59
+ // Set CORS headers on the response
60
+ setCorsHeaders(res, {
61
+ origin,
62
+ methods,
63
+ allowedHeaders,
64
+ exposedHeaders,
65
+ credentials,
66
+ maxAge,
67
+ requestOrigin
68
+ });
69
+
70
+ return res;
71
+ });
72
+
73
+ return plugin;
74
+ }
75
+
76
+ function setCorsHeaders(
77
+ response: Response,
78
+ options: {
79
+ origin: string | string[] | boolean | ((origin: string) => boolean);
80
+ methods: string[];
81
+ allowedHeaders: string[];
82
+ exposedHeaders: string[];
83
+ credentials: boolean;
84
+ maxAge: number;
85
+ requestOrigin?: string | null;
86
+ }
87
+ ) {
88
+ const { origin, methods, allowedHeaders, exposedHeaders, credentials, maxAge, requestOrigin } = options;
89
+
90
+ // Set Access-Control-Allow-Origin
91
+ if (origin) {
92
+ if (typeof origin === "string") {
93
+ if (origin === "*") {
94
+ response.headers.set("Access-Control-Allow-Origin", "*");
95
+ } else {
96
+ response.headers.set("Access-Control-Allow-Origin", origin);
97
+ }
98
+ } else if (Array.isArray(origin)) {
99
+ // For array of origins, we need to check if the request origin is in the list
100
+ // This is handled in the beforeRequest hook where we have access to the request origin
101
+ if (options.requestOrigin && origin.includes(options.requestOrigin)) {
102
+ response.headers.set("Access-Control-Allow-Origin", options.requestOrigin);
103
+ }
104
+ } else if (origin === true) {
105
+ response.headers.set("Access-Control-Allow-Origin", "*");
38
106
  }
39
- }
40
-
41
- headers['Access-Control-Allow-Methods'] = methods.join(', ');
42
- headers['Access-Control-Allow-Headers'] = allowedHeaders.join(', ');
43
-
44
- if (credentials) {
45
- headers['Access-Control-Allow-Credentials'] = 'true';
46
- }
47
-
48
- headers['Access-Control-Max-Age'] = maxAge.toString();
49
-
50
- ctx.set.status = 204;
51
- ctx.set.headers = { ...ctx.set.headers, ...headers };
52
-
53
- throw new Response(null, { status: 204, headers });
54
107
  }
55
- });
56
-
57
- corsInstance.onResponse(async (ctx: any, response: any) => {
58
- const headers: Record<string, string> = {};
59
-
60
- // Handle origin for actual requests
61
- if (typeof origin === 'boolean') {
62
- if (origin) {
63
- headers['Access-Control-Allow-Origin'] = ctx.request.headers.get('origin') || '*';
64
- }
65
- } else if (typeof origin === 'string') {
66
- headers['Access-Control-Allow-Origin'] = origin;
67
- } else if (Array.isArray(origin)) {
68
- const requestOrigin = ctx.request.headers.get('origin');
69
- if (requestOrigin && origin.includes(requestOrigin)) {
70
- headers['Access-Control-Allow-Origin'] = requestOrigin;
71
- }
108
+
109
+ // Set Access-Control-Allow-Methods
110
+ if (methods.length > 0) {
111
+ response.headers.set("Access-Control-Allow-Methods", methods.join(", "));
72
112
  }
73
113
 
74
- if (credentials) {
75
- headers['Access-Control-Allow-Credentials'] = 'true';
114
+ // Set Access-Control-Allow-Headers
115
+ if (allowedHeaders.length > 0) {
116
+ response.headers.set("Access-Control-Allow-Headers", allowedHeaders.join(", "));
76
117
  }
77
118
 
78
- ctx.set.headers = { ...ctx.set.headers, ...headers };
79
- return response;
80
- });
119
+ // Set Access-Control-Expose-Headers
120
+ if (exposedHeaders.length > 0) {
121
+ response.headers.set("Access-Control-Expose-Headers", exposedHeaders.join(", "));
122
+ }
81
123
 
82
- return corsInstance;
83
- }
124
+ // Set Access-Control-Allow-Credentials
125
+ if (credentials) {
126
+ response.headers.set("Access-Control-Allow-Credentials", "true");
127
+ }
128
+
129
+ // Set Access-Control-Max-Age
130
+ if (maxAge) {
131
+ response.headers.set("Access-Control-Max-Age", maxAge.toString());
132
+ }
133
+ }
package/plugins/index.ts CHANGED
@@ -1,11 +1,2 @@
1
- // Export all plugins
2
- export { cors } from './cors';
3
- export { logger } from './logger';
4
- export { auth, createJWT } from './auth';
5
- export { rateLimit } from './ratelimit';
6
-
7
- // Import BXO for plugin typing
8
- import BXO from '../index';
9
-
10
- // Plugin functions now return BXO instances
11
- export type PluginFactory<T = any> = (options?: T) => BXO;
1
+ export * from "./openapi";
2
+ export * from "./cors";