bxo 0.0.5-dev.8 → 0.0.5-dev.80

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,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
+ })
@@ -0,0 +1,115 @@
1
+ import BXO, { z } from "../src";
2
+
3
+ async function main() {
4
+ const app = new BXO({ serve: { port: 3002 } });
5
+
6
+ // Example 1: Header validation with passthrough
7
+ app.get("/api/headers", (ctx) => {
8
+ return ctx.json({
9
+ message: "Headers received",
10
+ // All headers are available, including extra ones not in schema
11
+ allHeaders: ctx.headers,
12
+ // Schema-validated headers are typed
13
+ contentType: ctx.headers["content-type"],
14
+ authorization: ctx.headers["authorization"]
15
+ });
16
+ }, {
17
+ headers: z.object({
18
+ "content-type": z.string().optional(),
19
+ "authorization": z.string().optional()
20
+ }).passthrough() // This allows extra headers to pass through
21
+ });
22
+
23
+ // Example 2: Cookie validation with passthrough
24
+ app.get("/api/cookies", (ctx) => {
25
+ return ctx.json({
26
+ message: "Cookies received",
27
+ // All cookies are available, including extra ones not in schema
28
+ allCookies: ctx.cookies,
29
+ // Schema-validated cookies are typed
30
+ sessionId: ctx.cookies.sessionId,
31
+ theme: ctx.cookies.theme
32
+ });
33
+ }, {
34
+ cookies: z.object({
35
+ sessionId: z.string().optional(),
36
+ theme: z.enum(["light", "dark"]).optional()
37
+ }).passthrough() // This allows extra cookies to pass through
38
+ });
39
+
40
+ // Example 3: Both headers and cookies with passthrough
41
+ app.post("/api/auth", (ctx) => {
42
+ return ctx.json({
43
+ message: "Authentication successful",
44
+ headers: {
45
+ // Only the required headers are typed
46
+ contentType: ctx.headers["content-type"],
47
+ authorization: ctx.headers["authorization"],
48
+ // But all headers are available
49
+ allHeaders: ctx.headers
50
+ },
51
+ cookies: {
52
+ // Only the required cookies are typed
53
+ sessionId: ctx.cookies.sessionId,
54
+ theme: ctx.cookies.theme,
55
+ // But all cookies are available
56
+ allCookies: ctx.cookies
57
+ }
58
+ });
59
+ }, {
60
+ headers: z.object({
61
+ "content-type": z.string(),
62
+ "authorization": z.string()
63
+ }).passthrough(),
64
+ cookies: z.object({
65
+ sessionId: z.string(),
66
+ theme: z.enum(["light", "dark"])
67
+ }).passthrough(),
68
+ body: z.object({
69
+ username: z.string(),
70
+ password: z.string()
71
+ })
72
+ });
73
+
74
+ // Example 4: Without passthrough (strict validation)
75
+ app.get("/api/strict", (ctx) => {
76
+ return ctx.json({
77
+ message: "Strict validation - only schema fields available",
78
+ headers: ctx.headers,
79
+ cookies: ctx.cookies
80
+ });
81
+ }, {
82
+ headers: z.object({
83
+ "content-type": z.string().optional()
84
+ }), // No passthrough - will fail if extra headers are present
85
+ cookies: z.object({
86
+ sessionId: z.string().optional()
87
+ }) // No passthrough - will fail if extra cookies are present
88
+ });
89
+
90
+ // Example 5: Demonstrating the difference
91
+ app.get("/api/demo", (ctx) => {
92
+ // Set some cookies to demonstrate
93
+ ctx.set.cookie("sessionId", "abc123");
94
+ ctx.set.cookie("theme", "dark");
95
+ ctx.set.cookie("extraCookie", "this-will-be-available-with-passthrough");
96
+
97
+ return ctx.json({
98
+ message: "Check the cookies in your browser dev tools",
99
+ note: "extraCookie will be available in /api/cookies but not in /api/strict"
100
+ });
101
+ });
102
+
103
+ app.start();
104
+ console.log(`Passthrough validation example server running on http://localhost:${app.server?.port}`);
105
+ console.log("\nTry these endpoints:");
106
+ console.log("GET /api/headers (with extra headers)");
107
+ console.log("GET /api/cookies (with extra cookies)");
108
+ console.log("POST /api/auth (with both headers and cookies)");
109
+ console.log("GET /api/strict (strict validation - may fail with extra fields)");
110
+ console.log("GET /api/demo (sets some cookies for testing)");
111
+ console.log("\nExample with curl:");
112
+ console.log('curl -H "Content-Type: application/json" -H "Authorization: Bearer token123" -H "X-Custom-Header: value" -b "sessionId=abc123; theme=dark; extraCookie=test" http://localhost:3002/api/headers');
113
+ }
114
+
115
+ main().catch(console.error);
@@ -0,0 +1,93 @@
1
+ import BXO from "../src";
2
+
3
+ async function main() {
4
+ const app = new BXO({ serve: { port: 3000 } });
5
+
6
+ // Route with space in the path
7
+ app.get("/api/resources/Workspace Item", (ctx) => {
8
+ return ctx.json({
9
+ message: "Found Workspace Item resource!",
10
+ path: ctx.request.url,
11
+ pathname: new URL(ctx.request.url).pathname
12
+ });
13
+ });
14
+
15
+ // Route with URL-encoded space
16
+ app.get("/api/resources/Workspace%20Item", (ctx) => {
17
+ return ctx.json({
18
+ message: "Found URL-encoded Workspace Item resource!",
19
+ path: ctx.request.url,
20
+ pathname: new URL(ctx.request.url).pathname
21
+ });
22
+ });
23
+
24
+ // Route with path parameter (recommended approach)
25
+ app.get("/api/resources/:resourceType", (ctx) => {
26
+ return ctx.json({
27
+ message: `Found resource type: ${ctx.params.resourceType}`,
28
+ path: ctx.request.url,
29
+ pathname: new URL(ctx.request.url).pathname,
30
+ params: ctx.params
31
+ });
32
+ });
33
+
34
+ // Route with multiple path parameters including URL-encoded spaces
35
+ app.get("/api/resources/:resourceType/:id", (ctx) => {
36
+ return ctx.json({
37
+ message: `Found resource: ${ctx.params.resourceType} with ID: ${ctx.params.id}`,
38
+ path: ctx.request.url,
39
+ pathname: new URL(ctx.request.url).pathname,
40
+ params: ctx.params
41
+ });
42
+ });
43
+
44
+ // Test route to show the difference
45
+ app.get("/test", (ctx) => {
46
+ return ctx.text(`
47
+ <!DOCTYPE html>
48
+ <html>
49
+ <head>
50
+ <title>URL Encoding Test</title>
51
+ </head>
52
+ <body>
53
+ <h1>URL Encoding Test</h1>
54
+ <p>Test the following URLs:</p>
55
+ <ul>
56
+ <li><a href="/api/resources/Workspace Item">/api/resources/Workspace Item</a> (with space)</li>
57
+ <li><a href="/api/resources/Workspace%20Item">/api/resources/Workspace%20Item</a> (URL encoded)</li>
58
+ <li><a href="/api/resources/My%20Resource">/api/resources/My%20Resource</a> (URL encoded with params)</li>
59
+ <li><a href="/api/resources/Doctype%20Permission/01992af8-1c69-7000-9219-9b83c2feb2d6">/api/resources/Doctype%20Permission/01992af8-1c69-7000-9219-9b83c2feb2d6</a> (URL encoded with ID)</li>
60
+ </ul>
61
+
62
+ <h2>Test with JavaScript fetch:</h2>
63
+ <button onclick="testFetch('/api/resources/Workspace Item')">Test with space</button>
64
+ <button onclick="testFetch('/api/resources/Workspace%20Item')">Test URL encoded</button>
65
+ <button onclick="testFetch('/api/resources/My%20Resource')">Test with params</button>
66
+ <button onclick="testFetch('/api/resources/Doctype%20Permission/01992af8-1c69-7000-9219-9b83c2feb2d6')">Test with ID</button>
67
+
68
+ <div id="result"></div>
69
+
70
+ <script>
71
+ async function testFetch(url) {
72
+ try {
73
+ const response = await fetch(url);
74
+ const data = await response.json();
75
+ document.getElementById('result').innerHTML =
76
+ '<h3>Result:</h3><pre>' + JSON.stringify(data, null, 2) + '</pre>';
77
+ } catch (error) {
78
+ document.getElementById('result').innerHTML =
79
+ '<h3>Error:</h3><pre>' + error.message + '</pre>';
80
+ }
81
+ }
82
+ </script>
83
+ `, 200, {
84
+ "Content-Type": "text/html"
85
+ });
86
+ });
87
+
88
+ app.start();
89
+ console.log(`Server is running on http://localhost:${app.server?.port}`);
90
+ console.log(`Test URL encoding at http://localhost:${app.server?.port}/test`);
91
+ }
92
+
93
+ main().catch(console.error);
@@ -0,0 +1,132 @@
1
+ import BXO from "../src";
2
+
3
+ async function main() {
4
+ const app = new BXO({ serve: { port: 3000 } });
5
+
6
+ // HTTP routes
7
+ app.get("/", (ctx) => {
8
+ return ctx.text(`
9
+ <!DOCTYPE html>
10
+ <html>
11
+ <head>
12
+ <title>BXO WebSocket Example</title>
13
+ </head>
14
+ <body>
15
+ <h1>BXO WebSocket Example</h1>
16
+ <div id="messages"></div>
17
+ <input type="text" id="messageInput" placeholder="Type a message...">
18
+ <button onclick="sendMessage()">Send</button>
19
+ <button onclick="connect()">Connect</button>
20
+ <button onclick="disconnect()">Disconnect</button>
21
+
22
+ <script>
23
+ let ws = null;
24
+
25
+ function connect() {
26
+ ws = new WebSocket('ws://localhost:3000/ws');
27
+
28
+ ws.onopen = function() {
29
+ addMessage('Connected to WebSocket');
30
+ };
31
+
32
+ ws.onmessage = function(event) {
33
+ addMessage('Received: ' + event.data);
34
+ };
35
+
36
+ ws.onclose = function() {
37
+ addMessage('Disconnected from WebSocket');
38
+ };
39
+
40
+ ws.onerror = function(error) {
41
+ addMessage('Error: ' + error);
42
+ };
43
+ }
44
+
45
+ function disconnect() {
46
+ if (ws) {
47
+ ws.close();
48
+ ws = null;
49
+ }
50
+ }
51
+
52
+ function sendMessage() {
53
+ const input = document.getElementById('messageInput');
54
+ if (ws && input.value) {
55
+ ws.send(input.value);
56
+ addMessage('Sent: ' + input.value);
57
+ input.value = '';
58
+ }
59
+ }
60
+
61
+ function addMessage(message) {
62
+ const messages = document.getElementById('messages');
63
+ const div = document.createElement('div');
64
+ div.textContent = new Date().toLocaleTimeString() + ': ' + message;
65
+ messages.appendChild(div);
66
+ messages.scrollTop = messages.scrollHeight;
67
+ }
68
+
69
+ // Allow Enter key to send message
70
+ document.getElementById('messageInput').addEventListener('keypress', function(e) {
71
+ if (e.key === 'Enter') {
72
+ sendMessage();
73
+ }
74
+ });
75
+ </script>
76
+ `, 200, {
77
+ "Content-Type": "text/html"
78
+ });
79
+ });
80
+
81
+ // WebSocket route
82
+ app.ws("/ws", {
83
+ open(ws) {
84
+ console.log("WebSocket connection opened");
85
+ ws.send("Welcome to BXO WebSocket!");
86
+ },
87
+
88
+ message(ws, message) {
89
+ console.log("Received message:", message);
90
+ // Echo the message back
91
+ ws.send(`Echo: ${message}`);
92
+ },
93
+
94
+ close(ws, code, reason) {
95
+ console.log(`WebSocket connection closed: ${code} ${reason}`);
96
+ },
97
+
98
+ ping(ws, data) {
99
+ console.log("Ping received:", data);
100
+ },
101
+
102
+ pong(ws, data) {
103
+ console.log("Pong received:", data);
104
+ }
105
+ });
106
+
107
+ // Another WebSocket route with parameters
108
+ app.ws("/chat/:room", {
109
+ open(ws) {
110
+ console.log(`WebSocket connection opened for room: ${ws.data?.room || 'unknown'}`);
111
+ ws.send(`Welcome to chat room: ${ws.data?.room || 'unknown'}`);
112
+ },
113
+
114
+ message(ws, message) {
115
+ const room = ws.data?.room || 'unknown';
116
+ console.log(`Message in room ${room}:`, message);
117
+ ws.send(`[${room}] Echo: ${message}`);
118
+ },
119
+
120
+ close(ws, code, reason) {
121
+ const room = ws.data?.room || 'unknown';
122
+ console.log(`WebSocket connection closed for room ${room}: ${code} ${reason}`);
123
+ }
124
+ });
125
+
126
+ app.start();
127
+ console.log(`Server is running on http://localhost:${app.server?.port}`);
128
+ console.log(`WebSocket available at ws://localhost:${app.server?.port}/ws`);
129
+ console.log(`Chat WebSocket available at ws://localhost:${app.server?.port}/chat/:room`);
130
+ }
131
+
132
+ main().catch(console.error);
package/package.json CHANGED
@@ -1,20 +1,20 @@
1
1
  {
2
2
  "name": "bxo",
3
- "module": "index.ts",
4
- "version": "0.0.5-dev.8",
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.80",
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
  }