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.
- package/README.md +258 -483
- package/example/cookie-example.ts +151 -0
- package/example/cors-example.ts +49 -0
- package/example/index.html +5 -0
- package/example/index.ts +191 -0
- package/example/multipart-example.ts +203 -0
- package/example/openapi-example.ts +132 -0
- package/example/passthrough-validation-example.ts +115 -0
- package/example/url-encoding-example.ts +93 -0
- package/example/websocket-example.ts +132 -0
- package/package.json +8 -8
- package/plugins/cors.ts +123 -73
- package/plugins/index.ts +2 -11
- package/plugins/openapi.ts +204 -0
- package/src/index.ts +960 -0
- package/test-url-encoding.ts +20 -0
- package/tsconfig.json +3 -5
- package/.cursor/rules/use-bun-instead-of-node-vite-npm-pnpm.mdc +0 -111
- package/example.ts +0 -183
- package/index.ts +0 -835
- package/plugins/auth.ts +0 -119
- package/plugins/logger.ts +0 -109
- package/plugins/ratelimit.ts +0 -140
|
@@ -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
|
-
"
|
|
5
|
-
|
|
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.
|
|
17
|
+
"zod": "^4.1.5",
|
|
18
|
+
"zod-openapi": "^5.4.0"
|
|
19
19
|
}
|
|
20
20
|
}
|