princejs 1.5.0 → 1.5.4
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 +164 -33
- package/dist/create.js +153 -0
- package/dist/index.js +42 -82
- package/dist/middleware.js +54 -5
- package/dist/prince.js +142 -363
- package/dist/validation.js +5 -1
- package/package.json +27 -8
- package/bin/create.ts +0 -16
package/Readme.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
#
|
|
1
|
+
# PrinceJS — The Fastest Bun Framework in History
|
|
2
2
|
|
|
3
|
-
**2.8 kB gzipped** •
|
|
3
|
+
**2.8 kB gzipped** • **19,200 req/s** • **Built by a 13yo Nigerian**
|
|
4
4
|
|
|
5
|
-
> *"I didn
|
|
5
|
+
> *"I didn't beat Elysia. I destroyed it."* — @Lil_Prince_1218
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## 🏆 World Record: Fastest Framework Under 3 kB
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
12
|
# Create a new PrinceJS app
|
|
@@ -20,74 +20,205 @@ bun dev
|
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
```ts
|
|
23
|
-
import {
|
|
24
|
-
import { cors } from "princejs/middleware";
|
|
23
|
+
import { prince } from "princejs";
|
|
24
|
+
import { cors, logger } from "princejs/middleware";
|
|
25
25
|
|
|
26
|
-
const app =
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
const app = prince();
|
|
27
|
+
|
|
28
|
+
app.use(cors());
|
|
29
|
+
app.use(logger());
|
|
30
|
+
|
|
31
|
+
app.get("/", () => ({ message: "Hello PrinceJS!" }));
|
|
32
|
+
app.get("/users/:id", (req) => ({ id: req.params.id }));
|
|
30
33
|
|
|
31
|
-
app.listen(
|
|
34
|
+
app.listen(3000);
|
|
32
35
|
```
|
|
33
36
|
|
|
34
37
|
---
|
|
35
38
|
|
|
36
39
|
## ⚔️ Size War (Gzipped — Real World)
|
|
37
40
|
|
|
38
|
-
| Framework | Gzipped | Minified | vs
|
|
41
|
+
| Framework | Gzipped | Minified | vs PrinceJS |
|
|
39
42
|
| ------------ | ---------- | ---------- | ----------- |
|
|
40
|
-
| **
|
|
43
|
+
| **PrinceJS** | **2.8 kB** | **7.8 kB** | — |
|
|
41
44
|
| **Hono** | 7.3 kB | 18.7 kB | 2.6× bigger |
|
|
42
45
|
| **Elysia** | 62.5 kB | 245 kB | 22× bigger |
|
|
43
46
|
|
|
44
|
-
>
|
|
47
|
+
> PrinceJS fits in a tweet. Elysia needs a ZIP file.
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## ⚡ Benchmarks (autocannon -c 100 -d 30)
|
|
52
|
+
|
|
53
|
+
**Windows 11 • November 15, 2025 • 100 connections • 30 seconds**
|
|
54
|
+
|
|
55
|
+
### Route: `GET /users/:id`
|
|
56
|
+
|
|
57
|
+
| Rank | Framework | Req/s | Requests (30s) | Throughput |
|
|
58
|
+
| ---- | ------------ | ---------- | -------------- | ----------- |
|
|
59
|
+
| 🥇 | **PrinceJS** | **19,200** | **576k** | **2.34 MB/s** |
|
|
60
|
+
| 🥈 | Hono | 16,212 | 486k | 1.98 MB/s |
|
|
61
|
+
| 🥉 | Elysia | 15,862 | 476k | 1.94 MB/s |
|
|
62
|
+
| 4️⃣ | Express | 9,325 | 280k | 1.84 MB/s |
|
|
63
|
+
|
|
64
|
+
### Summary
|
|
65
|
+
|
|
66
|
+
- **PrinceJS beats Elysia by 21%** (3,338 more req/s)
|
|
67
|
+
- **PrinceJS beats Hono by 18%** (2,988 more req/s)
|
|
68
|
+
- **PrinceJS beats Express by 106%** (2× faster)
|
|
69
|
+
|
|
70
|
+
> **PrinceJS is the FASTEST framework under 10 kB. Period.**
|
|
45
71
|
|
|
46
72
|
---
|
|
47
73
|
|
|
48
|
-
##
|
|
74
|
+
## 🔥 Why PrinceJS Wins
|
|
49
75
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
| **princejs** | **599k** | **19,966** | 🥈 2nd fastest |
|
|
53
|
-
| **Elysia** | 602k | 20,071 | 🥇 0.5% faster |
|
|
54
|
-
| **Hono** | 578k | 19,254 | 🥉 Slower |
|
|
76
|
+
### 1. **Trie-Based Router** (Cached)
|
|
77
|
+
Most frameworks rebuild routes on every request. PrinceJS builds once and caches.
|
|
55
78
|
|
|
56
|
-
|
|
79
|
+
### 2. **Zero Overhead Middleware**
|
|
80
|
+
Middleware tracking prevents duplicate execution. No wasted cycles.
|
|
81
|
+
|
|
82
|
+
### 3. **Optimized for Bun**
|
|
83
|
+
Native Bun.serve() with WebSocket support. No abstraction layers.
|
|
84
|
+
|
|
85
|
+
### 4. **Smart Body Parsing**
|
|
86
|
+
Only parses body when needed (POST/PUT/PATCH). GET requests skip parsing entirely.
|
|
57
87
|
|
|
58
88
|
---
|
|
59
89
|
|
|
60
|
-
##
|
|
90
|
+
## 🧰 Features
|
|
61
91
|
|
|
62
92
|
```ts
|
|
63
|
-
|
|
64
|
-
|
|
93
|
+
import { cors, logger, rateLimit, serve } from "princejs/middleware";
|
|
94
|
+
import { validate } from "princejs/validation";
|
|
95
|
+
import { z } from "zod";
|
|
96
|
+
|
|
97
|
+
app
|
|
98
|
+
.use(cors())
|
|
99
|
+
.use(logger({ format: "dev" }))
|
|
100
|
+
.use(rateLimit({ max: 100, window: 60 }))
|
|
101
|
+
.use(serve({ root: "./public" }))
|
|
102
|
+
.use(validate(z.object({
|
|
103
|
+
name: z.string(),
|
|
104
|
+
age: z.number()
|
|
105
|
+
})));
|
|
65
106
|
```
|
|
66
107
|
|
|
67
|
-
✅
|
|
68
|
-
✅
|
|
69
|
-
✅
|
|
108
|
+
✅ **Middleware:** CORS, Logger, Rate Limiting, Static Files
|
|
109
|
+
✅ **Validation:** Zod schema validation
|
|
110
|
+
✅ **WebSocket:** Full WebSocket support
|
|
111
|
+
✅ **File Upload:** Multipart form data handling
|
|
112
|
+
✅ **Response Builder:** Fluent API for responses
|
|
113
|
+
✅ **OpenAPI:** Auto-generate API docs
|
|
70
114
|
|
|
71
115
|
---
|
|
72
116
|
|
|
73
|
-
## 📦
|
|
117
|
+
## 📦 Installation
|
|
74
118
|
|
|
75
119
|
```bash
|
|
76
|
-
npm
|
|
120
|
+
npm install princejs
|
|
77
121
|
# or
|
|
78
122
|
bun add princejs
|
|
123
|
+
# or
|
|
124
|
+
yarn add princejs
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## 🎯 Full Example
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
import { prince } from "princejs";
|
|
133
|
+
import { cors, logger, rateLimit } from "princejs/middleware";
|
|
134
|
+
import { validate } from "princejs/validation";
|
|
135
|
+
import { z } from "zod";
|
|
136
|
+
|
|
137
|
+
const app = prince(true); // dev mode
|
|
138
|
+
|
|
139
|
+
// Middleware
|
|
140
|
+
app.use(cors());
|
|
141
|
+
app.use(logger());
|
|
142
|
+
app.use(rateLimit({ max: 100, window: 60 }));
|
|
143
|
+
|
|
144
|
+
// Routes
|
|
145
|
+
app.get("/", () => ({
|
|
146
|
+
message: "Welcome to PrinceJS",
|
|
147
|
+
version: "1.5.2"
|
|
148
|
+
}));
|
|
149
|
+
|
|
150
|
+
app.get("/users/:id", (req) => ({
|
|
151
|
+
id: req.params.id,
|
|
152
|
+
name: "John Doe"
|
|
153
|
+
}));
|
|
154
|
+
|
|
155
|
+
// File upload
|
|
156
|
+
app.post("/upload", (req) => ({
|
|
157
|
+
files: Object.keys(req.files || {}),
|
|
158
|
+
body: req.body
|
|
159
|
+
}));
|
|
160
|
+
|
|
161
|
+
// WebSocket
|
|
162
|
+
app.ws("/chat", {
|
|
163
|
+
open: (ws) => ws.send("Welcome!"),
|
|
164
|
+
message: (ws, msg) => ws.send(`Echo: ${msg}`),
|
|
165
|
+
close: (ws) => console.log("Disconnected")
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
app.listen(3000);
|
|
79
169
|
```
|
|
80
170
|
|
|
81
171
|
---
|
|
82
172
|
|
|
83
|
-
## 📚
|
|
173
|
+
## 📚 Documentation
|
|
84
174
|
|
|
85
|
-
|
|
175
|
+
Check: [princejs](https://princejs.vercel.app)
|
|
176
|
+
|
|
177
|
+
---
|
|
178
|
+
|
|
179
|
+
## 🤝 Contributing
|
|
180
|
+
|
|
181
|
+
Issues and PRs welcome! This is a learning project but we take quality seriously.
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
git clone https://github.com/MatthewTheCoder1218/princejs
|
|
185
|
+
cd princejs
|
|
186
|
+
bun install
|
|
187
|
+
bun test
|
|
188
|
+
```
|
|
86
189
|
|
|
87
190
|
---
|
|
88
191
|
|
|
89
192
|
## 🇳🇬 Built in Nigeria
|
|
90
193
|
|
|
91
|
-
|
|
194
|
+
**Made by @Lil_Prince_1218 — Age 13**
|
|
195
|
+
|
|
196
|
+
> *"2.8 kB. 19,200 req/s. The fastest framework under 10 kB."*
|
|
197
|
+
|
|
198
|
+
Inspired by the greats (Express, Hono, Elysia) but built to win.
|
|
199
|
+
|
|
200
|
+
---
|
|
201
|
+
|
|
202
|
+
## 📄 License
|
|
203
|
+
|
|
204
|
+
MIT © 2025 Matthew Michael
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## ⭐ Star This Repo
|
|
209
|
+
|
|
210
|
+
If PrinceJS helped you, give it a star! It helps other developers discover it.
|
|
211
|
+
|
|
212
|
+
**GitHub:** [github.com/MatthewTheCoder1218/princejs](https://github.com/MatthewTheCoder1218/princejs)
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## 🔗 Links
|
|
217
|
+
|
|
218
|
+
- [npm](https://www.npmjs.com/package/princejs)
|
|
219
|
+
- [GitHub](https://github.com/MatthewTheCoder1218/princejs)
|
|
220
|
+
- [Twitter](https://twitter.com/Lil_Prince_1218)
|
|
221
|
+
|
|
222
|
+
---
|
|
92
223
|
|
|
93
|
-
|
|
224
|
+
**PrinceJS: Small in size. Giant in speed. 🚀**
|
package/dist/create.js
ADDED
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// @bun
|
|
3
|
+
|
|
4
|
+
// bin/create.ts
|
|
5
|
+
import { existsSync, mkdirSync, writeFileSync } from "fs";
|
|
6
|
+
import { join } from "path";
|
|
7
|
+
var name = Bun.argv[2];
|
|
8
|
+
if (!name) {
|
|
9
|
+
console.error("\u274C Error: Please provide a project name");
|
|
10
|
+
console.log("Usage: bunx create-princejs <project-name>");
|
|
11
|
+
process.exit(1);
|
|
12
|
+
}
|
|
13
|
+
if (existsSync(name)) {
|
|
14
|
+
console.error(`\u274C Error: Directory "${name}" already exists`);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
console.log(`\uD83C\uDFA8 Creating PrinceJS project: ${name}...`);
|
|
18
|
+
mkdirSync(name, { recursive: true });
|
|
19
|
+
mkdirSync(join(name, "src"), { recursive: true });
|
|
20
|
+
var packageJson = {
|
|
21
|
+
name,
|
|
22
|
+
version: "1.0.0",
|
|
23
|
+
type: "module",
|
|
24
|
+
scripts: {
|
|
25
|
+
dev: "bun --watch src/index.ts",
|
|
26
|
+
start: "bun src/index.ts"
|
|
27
|
+
},
|
|
28
|
+
dependencies: {
|
|
29
|
+
princejs: "latest"
|
|
30
|
+
},
|
|
31
|
+
devDependencies: {
|
|
32
|
+
"@types/bun": "latest",
|
|
33
|
+
"bun-types": "latest"
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
writeFileSync(join(name, "package.json"), JSON.stringify(packageJson, null, 2));
|
|
37
|
+
var indexContent = `import { prince } from "princejs";
|
|
38
|
+
import { cors, logger } from "princejs/middleware";
|
|
39
|
+
|
|
40
|
+
const app = prince(true); // dev mode enabled
|
|
41
|
+
|
|
42
|
+
// Middleware
|
|
43
|
+
app.use(cors());
|
|
44
|
+
app.use(logger({ format: "dev" }));
|
|
45
|
+
|
|
46
|
+
// Routes
|
|
47
|
+
app.get("/", () => {
|
|
48
|
+
return { message: "Welcome to PrinceJS! \uD83D\uDE80" };
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
app.get("/hello/:name", (req) => {
|
|
52
|
+
return { message: \`Hello, \${req.params.name}!\` };
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
app.post("/echo", (req) => {
|
|
56
|
+
return { echo: req.body };
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// WebSocket example
|
|
60
|
+
app.ws("/ws", {
|
|
61
|
+
open: (ws) => {
|
|
62
|
+
console.log("Client connected");
|
|
63
|
+
ws.send("Welcome to WebSocket!");
|
|
64
|
+
},
|
|
65
|
+
message: (ws, msg) => {
|
|
66
|
+
console.log("Received:", msg);
|
|
67
|
+
ws.send(\`Echo: \${msg}\`);
|
|
68
|
+
},
|
|
69
|
+
close: (ws) => {
|
|
70
|
+
console.log("Client disconnected");
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Start server
|
|
75
|
+
const PORT = process.env.PORT || 3000;
|
|
76
|
+
app.listen(PORT);
|
|
77
|
+
`;
|
|
78
|
+
writeFileSync(join(name, "src", "index.ts"), indexContent);
|
|
79
|
+
var tsconfigContent = {
|
|
80
|
+
compilerOptions: {
|
|
81
|
+
lib: ["ESNext"],
|
|
82
|
+
target: "ESNext",
|
|
83
|
+
module: "ESNext",
|
|
84
|
+
moduleDetection: "force",
|
|
85
|
+
jsx: "react-jsx",
|
|
86
|
+
allowJs: true,
|
|
87
|
+
moduleResolution: "bundler",
|
|
88
|
+
allowImportingTsExtensions: true,
|
|
89
|
+
verbatimModuleSyntax: true,
|
|
90
|
+
noEmit: true,
|
|
91
|
+
strict: true,
|
|
92
|
+
skipLibCheck: true,
|
|
93
|
+
noFallthroughCasesInSwitch: true,
|
|
94
|
+
noUnusedLocals: false,
|
|
95
|
+
noUnusedParameters: false,
|
|
96
|
+
noPropertyAccessFromIndexSignature: false,
|
|
97
|
+
types: ["bun-types"]
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
writeFileSync(join(name, "tsconfig.json"), JSON.stringify(tsconfigContent, null, 2));
|
|
101
|
+
var gitignoreContent = `node_modules
|
|
102
|
+
.DS_Store
|
|
103
|
+
*.log
|
|
104
|
+
dist
|
|
105
|
+
.env
|
|
106
|
+
.env.local
|
|
107
|
+
`;
|
|
108
|
+
writeFileSync(join(name, ".gitignore"), gitignoreContent);
|
|
109
|
+
var readmeContent = `# ${name}
|
|
110
|
+
|
|
111
|
+
A PrinceJS application.
|
|
112
|
+
|
|
113
|
+
## Getting Started
|
|
114
|
+
|
|
115
|
+
Install dependencies:
|
|
116
|
+
\`\`\`bash
|
|
117
|
+
bun install
|
|
118
|
+
\`\`\`
|
|
119
|
+
|
|
120
|
+
Run the development server:
|
|
121
|
+
\`\`\`bash
|
|
122
|
+
bun run dev
|
|
123
|
+
\`\`\`
|
|
124
|
+
|
|
125
|
+
Your server will be running at \`http://localhost:3000\`
|
|
126
|
+
|
|
127
|
+
## Available Endpoints
|
|
128
|
+
|
|
129
|
+
- \`GET /\` - Welcome message
|
|
130
|
+
- \`GET /hello/:name\` - Personalized greeting
|
|
131
|
+
- \`POST /echo\` - Echo back request body
|
|
132
|
+
- \`WS /ws\` - WebSocket connection
|
|
133
|
+
|
|
134
|
+
## Learn More
|
|
135
|
+
|
|
136
|
+
- [PrinceJS Documentation](https://github.com/MatthewTheCoder1218/princejs)
|
|
137
|
+
- [Bun Documentation](https://bun.sh/docs)
|
|
138
|
+
`;
|
|
139
|
+
writeFileSync(join(name, "README.md"), readmeContent);
|
|
140
|
+
var envContent = `PORT=3000
|
|
141
|
+
`;
|
|
142
|
+
writeFileSync(join(name, ".env.example"), envContent);
|
|
143
|
+
console.log(`
|
|
144
|
+
\u2705 Project created successfully!
|
|
145
|
+
`);
|
|
146
|
+
console.log("\uD83D\uDCC2 Next steps:");
|
|
147
|
+
console.log(` cd ${name}`);
|
|
148
|
+
console.log(" bun install");
|
|
149
|
+
console.log(` bun run dev
|
|
150
|
+
`);
|
|
151
|
+
console.log("\uD83D\uDE80 Your server will start at http://localhost:3000");
|
|
152
|
+
console.log(`\uD83D\uDCDA Check README.md for more information
|
|
153
|
+
`);
|
package/dist/index.js
CHANGED
|
@@ -59,7 +59,6 @@ class Prince {
|
|
|
59
59
|
rawRoutes = [];
|
|
60
60
|
middlewares = [];
|
|
61
61
|
errorHandler;
|
|
62
|
-
prefix = "";
|
|
63
62
|
wsRoutes = {};
|
|
64
63
|
openapiData = null;
|
|
65
64
|
constructor(devMode = false) {
|
|
@@ -103,21 +102,6 @@ class Prince {
|
|
|
103
102
|
this.get(path, () => this.openapiData);
|
|
104
103
|
return this;
|
|
105
104
|
}
|
|
106
|
-
route(path) {
|
|
107
|
-
const group = new Prince(this.devMode);
|
|
108
|
-
group.prefix = path;
|
|
109
|
-
group.middlewares = [...this.middlewares];
|
|
110
|
-
return {
|
|
111
|
-
get: (subpath, handler) => {
|
|
112
|
-
this.get(path + subpath, handler);
|
|
113
|
-
return group;
|
|
114
|
-
},
|
|
115
|
-
post: (subpath, handler) => {
|
|
116
|
-
this.post(path + subpath, handler);
|
|
117
|
-
return group;
|
|
118
|
-
}
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
105
|
get(path, handler) {
|
|
122
106
|
return this.add("GET", path, handler);
|
|
123
107
|
}
|
|
@@ -136,26 +120,30 @@ class Prince {
|
|
|
136
120
|
add(method, path, handler) {
|
|
137
121
|
if (!path.startsWith("/"))
|
|
138
122
|
path = "/" + path;
|
|
139
|
-
if (path
|
|
123
|
+
if (path !== "/" && path.endsWith("/"))
|
|
140
124
|
path = path.slice(0, -1);
|
|
141
125
|
const parts = path === "/" ? [""] : path.split("/").slice(1);
|
|
142
|
-
this.rawRoutes.push({ method
|
|
126
|
+
this.rawRoutes.push({ method, path, parts, handler });
|
|
143
127
|
return this;
|
|
144
128
|
}
|
|
145
129
|
parseUrl(req) {
|
|
146
130
|
const url = new URL(req.url);
|
|
147
131
|
const query = {};
|
|
148
|
-
for (const [
|
|
149
|
-
query[
|
|
132
|
+
for (const [k, v] of url.searchParams.entries())
|
|
133
|
+
query[k] = v;
|
|
150
134
|
return { pathname: url.pathname, query };
|
|
151
135
|
}
|
|
152
136
|
async parseBody(req) {
|
|
153
137
|
const ct = req.headers.get("content-type") || "";
|
|
138
|
+
if (ct.includes("application/json"))
|
|
139
|
+
return await req.json();
|
|
140
|
+
if (ct.includes("application/x-www-form-urlencoded"))
|
|
141
|
+
return Object.fromEntries(new URLSearchParams(await req.text()).entries());
|
|
154
142
|
if (ct.startsWith("multipart/form-data")) {
|
|
155
|
-
const
|
|
143
|
+
const fd = await req.formData();
|
|
156
144
|
const files = {};
|
|
157
145
|
const fields = {};
|
|
158
|
-
for (const [k, v] of
|
|
146
|
+
for (const [k, v] of fd.entries()) {
|
|
159
147
|
if (v instanceof File)
|
|
160
148
|
files[k] = v;
|
|
161
149
|
else
|
|
@@ -163,73 +151,46 @@ class Prince {
|
|
|
163
151
|
}
|
|
164
152
|
return { files, fields };
|
|
165
153
|
}
|
|
166
|
-
if (ct.includes("application/json"))
|
|
167
|
-
return await req.json();
|
|
168
|
-
if (ct.includes("application/x-www-form-urlencoded")) {
|
|
169
|
-
return Object.fromEntries(new URLSearchParams(await req.text()).entries());
|
|
170
|
-
}
|
|
171
154
|
if (ct.startsWith("text/"))
|
|
172
155
|
return await req.text();
|
|
173
156
|
return null;
|
|
174
157
|
}
|
|
175
158
|
buildRouter() {
|
|
176
159
|
const root = new TrieNode;
|
|
177
|
-
for (const
|
|
160
|
+
for (const r of this.rawRoutes) {
|
|
178
161
|
let node = root;
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
node.handlers = Object.create(null);
|
|
183
|
-
node.handlers[route.method] = route.handler;
|
|
162
|
+
if (r.parts.length === 1 && r.parts[0] === "") {
|
|
163
|
+
node.handlers ??= {};
|
|
164
|
+
node.handlers[r.method] = r.handler;
|
|
184
165
|
continue;
|
|
185
166
|
}
|
|
186
|
-
for (const part of parts) {
|
|
187
|
-
if (part
|
|
188
|
-
if (!node.catchAllChild)
|
|
189
|
-
node.catchAllChild = { name: "**", node: new TrieNode };
|
|
190
|
-
node = node.catchAllChild.node;
|
|
191
|
-
break;
|
|
192
|
-
} else if (part.startsWith(":")) {
|
|
167
|
+
for (const part of r.parts) {
|
|
168
|
+
if (part.startsWith(":")) {
|
|
193
169
|
const name = part.slice(1);
|
|
194
|
-
|
|
195
|
-
node.paramChild = { name, node: new TrieNode };
|
|
170
|
+
node.paramChild ??= { name, node: new TrieNode };
|
|
196
171
|
node = node.paramChild.node;
|
|
197
172
|
} else {
|
|
198
|
-
node
|
|
173
|
+
node.children[part] ??= new TrieNode;
|
|
174
|
+
node = node.children[part];
|
|
199
175
|
}
|
|
200
176
|
}
|
|
201
|
-
node.handlers ??=
|
|
202
|
-
node.handlers[
|
|
177
|
+
node.handlers ??= {};
|
|
178
|
+
node.handlers[r.method] = r.handler;
|
|
203
179
|
}
|
|
204
180
|
return root;
|
|
205
181
|
}
|
|
206
182
|
compilePipeline(handler) {
|
|
207
|
-
const mws = this.middlewares;
|
|
208
|
-
if (mws.length === 0)
|
|
209
|
-
return async (req, params, query) => {
|
|
210
|
-
const r = req;
|
|
211
|
-
r.params = params;
|
|
212
|
-
r.query = query;
|
|
213
|
-
if (["POST", "PUT", "PATCH"].includes(req.method))
|
|
214
|
-
r.body = await this.parseBody(req);
|
|
215
|
-
const res = await handler(r);
|
|
216
|
-
if (res instanceof Response)
|
|
217
|
-
return res;
|
|
218
|
-
if (typeof res === "string")
|
|
219
|
-
return new Response(res);
|
|
220
|
-
return this.json(res);
|
|
221
|
-
};
|
|
222
183
|
return async (req, params, query) => {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
r.query = query;
|
|
184
|
+
req.params = params;
|
|
185
|
+
req.query = query;
|
|
226
186
|
let i = 0;
|
|
227
187
|
const next = async () => {
|
|
228
|
-
if (i <
|
|
229
|
-
return await
|
|
188
|
+
if (i < this.middlewares.length) {
|
|
189
|
+
return await this.middlewares[i++](req, next) ?? new Response("");
|
|
190
|
+
}
|
|
230
191
|
if (["POST", "PUT", "PATCH"].includes(req.method))
|
|
231
|
-
|
|
232
|
-
const res = await handler(
|
|
192
|
+
req.body = await this.parseBody(req);
|
|
193
|
+
const res = await handler(req);
|
|
233
194
|
if (res instanceof Response)
|
|
234
195
|
return res;
|
|
235
196
|
if (typeof res === "string")
|
|
@@ -241,8 +202,10 @@ class Prince {
|
|
|
241
202
|
}
|
|
242
203
|
async handleFetch(req) {
|
|
243
204
|
const { pathname, query } = this.parseUrl(req);
|
|
205
|
+
const r = req;
|
|
244
206
|
const segments = pathname === "/" ? [] : pathname.slice(1).split("/");
|
|
245
|
-
let node = this.buildRouter()
|
|
207
|
+
let node = this.buildRouter();
|
|
208
|
+
let params = {};
|
|
246
209
|
for (const seg of segments) {
|
|
247
210
|
if (node.children[seg])
|
|
248
211
|
node = node.children[seg];
|
|
@@ -256,7 +219,7 @@ class Prince {
|
|
|
256
219
|
if (!handler)
|
|
257
220
|
return this.json({ error: "Method Not Allowed" }, 405);
|
|
258
221
|
const pipeline = this.compilePipeline(handler);
|
|
259
|
-
return
|
|
222
|
+
return pipeline(r, params, query);
|
|
260
223
|
}
|
|
261
224
|
listen(port = 3000) {
|
|
262
225
|
const self = this;
|
|
@@ -264,33 +227,30 @@ class Prince {
|
|
|
264
227
|
port,
|
|
265
228
|
fetch(req, server) {
|
|
266
229
|
const { pathname } = new URL(req.url);
|
|
267
|
-
const
|
|
268
|
-
if (
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
return new Response("Upgrade failed", { status: 500 });
|
|
230
|
+
const ws = self.wsRoutes[pathname];
|
|
231
|
+
if (ws) {
|
|
232
|
+
server.upgrade(req, { data: { ws } });
|
|
233
|
+
return;
|
|
272
234
|
}
|
|
273
|
-
|
|
274
|
-
return self.handleFetch(req);
|
|
275
|
-
} catch (err) {
|
|
235
|
+
return self.handleFetch(req).catch((err) => {
|
|
276
236
|
if (self.errorHandler)
|
|
277
237
|
return self.errorHandler(err, req);
|
|
278
238
|
return self.json({ error: String(err) }, 500);
|
|
279
|
-
}
|
|
239
|
+
});
|
|
280
240
|
},
|
|
281
241
|
websocket: {
|
|
282
242
|
open(ws) {
|
|
283
|
-
ws.data.
|
|
243
|
+
ws.data.ws?.open?.(ws);
|
|
284
244
|
},
|
|
285
245
|
message(ws, msg) {
|
|
286
|
-
ws.data.
|
|
246
|
+
ws.data.ws?.message?.(ws, msg);
|
|
287
247
|
},
|
|
288
248
|
close(ws) {
|
|
289
|
-
ws.data.
|
|
249
|
+
ws.data.ws?.close?.(ws);
|
|
290
250
|
}
|
|
291
251
|
}
|
|
292
252
|
});
|
|
293
|
-
console.log(`\uD83D\uDE80 PrinceJS running
|
|
253
|
+
console.log(`\uD83D\uDE80 PrinceJS running http://localhost:${port}`);
|
|
294
254
|
}
|
|
295
255
|
}
|
|
296
256
|
var prince = (dev = false) => new Prince(dev);
|