princejs 1.3.6 → 1.4.1

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.
Files changed (3) hide show
  1. package/Readme.md +54 -57
  2. package/dist/index.js +346 -0
  3. package/package.json +22 -6
package/Readme.md CHANGED
@@ -1,93 +1,90 @@
1
- # PrinceJS
1
+ # princejs — The Smallest Bun Framework in History
2
2
 
3
- **The fastest backend framework ever made.**
4
- **Beats Hono and Express • Built in 3 days** \
3
+ **2.8 kB gzipped** **~600k req/30s** • **Built by a 13yo Nigerian**
4
+
5
+ > *"I didn’t beat Elysia. I outsmarted it."* — @Lil_Prince_1218
5
6
 
6
7
  ---
7
8
 
8
- ### Install
9
+ ## 🚀 Get Started
9
10
 
10
11
  ```bash
11
- pnpm add princejs
12
- # or
13
- bun add princejs
14
- # or
15
- npm install princejs
12
+ bun create princejs my-app
13
+ cd my-app
14
+ bun dev
16
15
  ```
17
16
 
18
- ### Quick Start
19
-
20
17
  ```ts
21
- import { Prince } from 'princejs';
18
+ import { Prince } from "princejs";
19
+ import { cors } from "princejs/middleware";
22
20
 
23
- const app = new Prince();
21
+ const app = new Prince()
22
+ .use(cors())
23
+ .get("/", () => "Hello princejs")
24
+ .get("/users/:id", (req) => ({ id: req.params.id }));
24
25
 
25
- // CORS + logging
26
- app.use(async (req, next) => {
27
- console.log(`${req.method} ${req.url}`);
28
- const res = await next();
29
- res.headers.set('Access-Control-Allow-Origin', '*');
30
- return res;
31
- });
26
+ app.listen(5000);
27
+ ```
32
28
 
33
- // Global error handler
34
- app.error((err) => app.json({ error: err.message }, 500));
29
+ ---
35
30
 
36
- // Routes
37
- app.get('/', () => app.json({ hello: 'PrinceJS', age: 13 }));
31
+ ## ⚔️ Size War (Gzipped — Real World)
38
32
 
39
- app.post('/pay', async (req) => {
40
- let body = {};
41
- try { body = await req.json(); } catch {}
42
- return app.json({ url: `https://stripe.com/pay/${body.amount || 999}` });
43
- });
33
+ | Framework | Gzipped | Minified | vs princejs |
34
+ | ------------ | ---------- | ---------- | ----------- |
35
+ | **princejs** | **2.8 kB** | **7.8 kB** | — |
36
+ | **Hono** | 7.3 kB | 18.7 kB | 2. bigger |
37
+ | **Elysia** | 62.5 kB | 245 kB | 22× bigger |
44
38
 
45
- app.listen(3000);
46
- ```
39
+ > princejs fits in a tweet. Elysia needs a ZIP file.
47
40
 
48
41
  ---
49
42
 
50
- ## Features
43
+ ## ⚡ Benchmarks (3×3 — Windows, Nov 11, 2025)
51
44
 
52
- All HTTP methods: **GET, POST, PUT, DELETE, PATCH, OPTIONS, HEAD**
45
+ | Framework | Requests (30s) | Req/s | Notes |
46
+ | ------------ | -------------- | ---------- | -------------- |
47
+ | **princejs** | **599k** | **19,966** | 🥈 2nd fastest |
48
+ | **Elysia** | 602k | 20,071 | 🥇 0.5% faster |
49
+ | **Hono** | 578k | 19,254 | 🥉 Slower |
53
50
 
54
- ```bash
55
- app.json(data, status?) — clean JSON with status
56
- app.error(handler) — global error handling
57
- ```
51
+ > Elysia is only 0.5% faster. But princejs is **22× smaller**.
58
52
 
59
53
  ---
60
54
 
61
- ## 🔥 Benchmark Comparison (real Windows laptop)
62
-
63
- Real-world 30-second load test with `autocannon -c 100 -d 30`.
55
+ ## 🧹 Features
64
56
 
65
- ### **Framework Performance Table**
57
+ ```ts
58
+ .use(rateLimit({ max: 100 }))
59
+ .use(validate(z.object({ name: z.string() })))
60
+ ```
66
61
 
67
- | Framework | Avg Req/sec | Total Requests (30s) | Avg Bytes/sec | Avg Latency |
68
- | -------------------- | ------------ | -------------------- | ------------- | ------------ |
69
- | **PrinceJS** | **8,526.34** | **256,000** | **1.14 MB/s** | **11.22 ms** |
70
- | Hono | 8,044.8 | 241,000 | 1.08 MB/s | 11.22 ms |
71
- | Elysia | 9,531.21 | 286,000 | 1.28 MB/s | 10 ms |
62
+ Zod Validation
63
+ CORS + Logger
64
+ Rate Limit Middleware
72
65
 
73
66
  ---
74
67
 
75
- ### Author
76
-
77
- Matthew Micheal — Nigeria
68
+ ## 📦 Install
78
69
 
79
- Made in PowerShell on a school laptop.
70
+ ```bash
71
+ npm i princejs
72
+ # or
73
+ bun add princejs
74
+ ```
80
75
 
81
- ### Links
76
+ ---
82
77
 
83
- npm: [https://www.npmjs.com/package/princejs](https://www.npmjs.com/package/princejs)
78
+ ## 📚 Docs
84
79
 
85
- github: [https://github.com/MatthewTheCoder1218/princejs](https://github.com/MatthewTheCoder1218/princejs)
80
+ **coming soon →** [princejs.vercel.app](https://princejs.vercel.app)
86
81
 
87
82
  ---
88
83
 
89
- PrinceJS Small. Fast. Unbeatable.
84
+ ## 🇳🇬 Built in Nigeria
90
85
 
91
- ```bash
92
- pnpm add princejs
93
- ```
86
+ **@Lil_Prince_1218 — 13 years old**
87
+ Lagos, Nigeria
88
+ **November 11, 2025**
89
+
90
+ > *“2.8 kB. 600k req. No excuses.”*
package/dist/index.js ADDED
@@ -0,0 +1,346 @@
1
+ // @bun
2
+ // src/prince.ts
3
+ class TrieNode {
4
+ children = Object.create(null);
5
+ paramChild;
6
+ wildcardChild;
7
+ catchAllChild;
8
+ handlers = null;
9
+ }
10
+
11
+ class ResponseBuilder {
12
+ _status = 200;
13
+ _headers = {};
14
+ _body = null;
15
+ status(code) {
16
+ this._status = code;
17
+ return this;
18
+ }
19
+ header(key, value) {
20
+ this._headers[key] = value;
21
+ return this;
22
+ }
23
+ json(data) {
24
+ this._headers["Content-Type"] = "application/json";
25
+ this._body = JSON.stringify(data);
26
+ return this.build();
27
+ }
28
+ text(data) {
29
+ this._headers["Content-Type"] = "text/plain";
30
+ this._body = data;
31
+ return this.build();
32
+ }
33
+ html(data) {
34
+ this._headers["Content-Type"] = "text/html";
35
+ this._body = data;
36
+ return this.build();
37
+ }
38
+ redirect(url, status = 302) {
39
+ this._status = status;
40
+ this._headers["Location"] = url;
41
+ return this.build();
42
+ }
43
+ build() {
44
+ return new Response(this._body, {
45
+ status: this._status,
46
+ headers: this._headers
47
+ });
48
+ }
49
+ }
50
+
51
+ class Prince {
52
+ devMode;
53
+ rawRoutes = [];
54
+ middlewares = [];
55
+ errorHandler;
56
+ prefix = "";
57
+ constructor(devMode = false) {
58
+ this.devMode = devMode;
59
+ }
60
+ use(mw) {
61
+ this.middlewares.push(mw);
62
+ return this;
63
+ }
64
+ error(fn) {
65
+ this.errorHandler = fn;
66
+ return this;
67
+ }
68
+ json(data, status = 200) {
69
+ return new Response(JSON.stringify(data), {
70
+ status,
71
+ headers: { "Content-Type": "application/json" }
72
+ });
73
+ }
74
+ response() {
75
+ return new ResponseBuilder;
76
+ }
77
+ route(path) {
78
+ const group = new Prince(this.devMode);
79
+ group.prefix = path;
80
+ group.middlewares = [...this.middlewares];
81
+ return {
82
+ get: (subpath, handler) => {
83
+ this.get(path + subpath, handler);
84
+ return group;
85
+ },
86
+ post: (subpath, handler) => {
87
+ this.post(path + subpath, handler);
88
+ return group;
89
+ },
90
+ put: (subpath, handler) => {
91
+ this.put(path + subpath, handler);
92
+ return group;
93
+ },
94
+ delete: (subpath, handler) => {
95
+ this.delete(path + subpath, handler);
96
+ return group;
97
+ },
98
+ patch: (subpath, handler) => {
99
+ this.patch(path + subpath, handler);
100
+ return group;
101
+ }
102
+ };
103
+ }
104
+ get(path, handler) {
105
+ return this.add("GET", path, handler);
106
+ }
107
+ post(path, handler) {
108
+ return this.add("POST", path, handler);
109
+ }
110
+ put(path, handler) {
111
+ return this.add("PUT", path, handler);
112
+ }
113
+ delete(path, handler) {
114
+ return this.add("DELETE", path, handler);
115
+ }
116
+ patch(path, handler) {
117
+ return this.add("PATCH", path, handler);
118
+ }
119
+ options(path, handler) {
120
+ return this.add("OPTIONS", path, handler);
121
+ }
122
+ head(path, handler) {
123
+ return this.add("HEAD", path, handler);
124
+ }
125
+ add(method, path, handler) {
126
+ if (!path.startsWith("/"))
127
+ path = "/" + path;
128
+ if (path.length > 1 && path.endsWith("/"))
129
+ path = path.slice(0, -1);
130
+ const parts = path === "/" ? [""] : path.split("/").slice(1);
131
+ this.rawRoutes.push({ method: method.toUpperCase(), path, parts, handler });
132
+ return this;
133
+ }
134
+ parseUrl(req) {
135
+ const url = new URL(req.url);
136
+ const query = {};
137
+ for (const [key, value] of url.searchParams.entries()) {
138
+ query[key] = value;
139
+ }
140
+ return {
141
+ pathname: url.pathname,
142
+ query
143
+ };
144
+ }
145
+ async parseBody(req) {
146
+ const ct = req.headers.get("content-type") || "";
147
+ if (ct.includes("application/json")) {
148
+ return await req.json();
149
+ }
150
+ if (ct.includes("application/x-www-form-urlencoded")) {
151
+ const text = await req.text();
152
+ const params = {};
153
+ const pairs = text.split("&");
154
+ for (const pair of pairs) {
155
+ const [key, val] = pair.split("=");
156
+ params[decodeURIComponent(key)] = decodeURIComponent(val || "");
157
+ }
158
+ return params;
159
+ }
160
+ if (ct.includes("text/")) {
161
+ return await req.text();
162
+ }
163
+ return null;
164
+ }
165
+ buildRouter() {
166
+ const root = new TrieNode;
167
+ for (const route of this.rawRoutes) {
168
+ let node = root;
169
+ const parts = route.parts;
170
+ if (parts.length === 1 && parts[0] === "") {
171
+ if (!node.handlers)
172
+ node.handlers = Object.create(null);
173
+ node.handlers[route.method] = route.handler;
174
+ continue;
175
+ }
176
+ for (let i = 0;i < parts.length; i++) {
177
+ const part = parts[i];
178
+ if (part === "**") {
179
+ if (!node.catchAllChild) {
180
+ node.catchAllChild = { name: "**", node: new TrieNode };
181
+ }
182
+ node = node.catchAllChild.node;
183
+ break;
184
+ } else if (part === "*") {
185
+ if (!node.wildcardChild)
186
+ node.wildcardChild = new TrieNode;
187
+ node = node.wildcardChild;
188
+ } else if (part.startsWith(":")) {
189
+ const name = part.slice(1);
190
+ if (!node.paramChild)
191
+ node.paramChild = { name, node: new TrieNode };
192
+ node = node.paramChild.node;
193
+ } else {
194
+ if (!node.children[part])
195
+ node.children[part] = new TrieNode;
196
+ node = node.children[part];
197
+ }
198
+ }
199
+ if (!node.handlers)
200
+ node.handlers = Object.create(null);
201
+ node.handlers[route.method] = route.handler;
202
+ }
203
+ return root;
204
+ }
205
+ compilePipeline(handler, paramsGetter) {
206
+ const mws = this.middlewares.slice();
207
+ const hasMiddleware = mws.length > 0;
208
+ if (!hasMiddleware) {
209
+ return async (req, params, query) => {
210
+ const princeReq = req;
211
+ princeReq.params = params;
212
+ princeReq.query = query;
213
+ if (req.method === "POST" || req.method === "PUT" || req.method === "PATCH") {
214
+ princeReq.body = await this.parseBody(req);
215
+ }
216
+ const res = await handler(princeReq);
217
+ if (res instanceof Response)
218
+ return res;
219
+ if (typeof res === "string")
220
+ return new Response(res, { status: 200 });
221
+ if (res instanceof Uint8Array || res instanceof ArrayBuffer)
222
+ return new Response(res, { status: 200 });
223
+ return this.json(res);
224
+ };
225
+ }
226
+ return async (req, params, query) => {
227
+ const princeReq = req;
228
+ princeReq.params = params;
229
+ princeReq.query = query;
230
+ let idx = 0;
231
+ const runNext = async () => {
232
+ if (idx >= mws.length) {
233
+ if (req.method === "POST" || req.method === "PUT" || req.method === "PATCH") {
234
+ princeReq.body = await this.parseBody(req);
235
+ }
236
+ const res = await handler(princeReq);
237
+ if (res instanceof Response)
238
+ return res;
239
+ if (typeof res === "string")
240
+ return new Response(res, { status: 200 });
241
+ if (res instanceof Uint8Array || res instanceof ArrayBuffer)
242
+ return new Response(res, { status: 200 });
243
+ return this.json(res);
244
+ }
245
+ const mw = mws[idx++];
246
+ return await mw(req, runNext);
247
+ };
248
+ const out = await runNext();
249
+ if (out instanceof Response)
250
+ return out;
251
+ if (out !== undefined) {
252
+ if (typeof out === "string")
253
+ return new Response(out, { status: 200 });
254
+ if (out instanceof Uint8Array || out instanceof ArrayBuffer)
255
+ return new Response(out, { status: 200 });
256
+ return this.json(out);
257
+ }
258
+ return new Response(null, { status: 204 });
259
+ };
260
+ }
261
+ listen(port = 3000) {
262
+ const root = this.buildRouter();
263
+ const handlerMap = new Map;
264
+ Bun.serve({
265
+ port,
266
+ fetch: async (req) => {
267
+ try {
268
+ const { pathname, query } = this.parseUrl(req);
269
+ const segments = pathname === "/" ? [] : pathname.slice(1).split("/");
270
+ let node = root;
271
+ const params = {};
272
+ let matched = true;
273
+ if (segments.length === 0) {
274
+ if (!node.handlers)
275
+ return this.json({ error: "Route not found" }, 404);
276
+ const handler2 = node.handlers[req.method];
277
+ if (!handler2)
278
+ return this.json({ error: "Method not allowed" }, 405);
279
+ let methodMap2 = handlerMap.get(node);
280
+ if (!methodMap2) {
281
+ methodMap2 = {};
282
+ handlerMap.set(node, methodMap2);
283
+ }
284
+ if (!methodMap2[req.method]) {
285
+ methodMap2[req.method] = this.compilePipeline(handler2, (_) => params);
286
+ }
287
+ return await methodMap2[req.method](req, params, query);
288
+ }
289
+ for (let i = 0;i < segments.length; i++) {
290
+ const seg = segments[i];
291
+ if (node.children[seg]) {
292
+ node = node.children[seg];
293
+ continue;
294
+ }
295
+ if (node.paramChild) {
296
+ params[node.paramChild.name] = seg;
297
+ node = node.paramChild.node;
298
+ continue;
299
+ }
300
+ if (node.wildcardChild) {
301
+ node = node.wildcardChild;
302
+ continue;
303
+ }
304
+ if (node.catchAllChild) {
305
+ const remaining = segments.slice(i).join("/");
306
+ if (node.catchAllChild.name)
307
+ params[node.catchAllChild.name] = remaining;
308
+ node = node.catchAllChild.node;
309
+ break;
310
+ }
311
+ matched = false;
312
+ break;
313
+ }
314
+ if (!matched || !node || !node.handlers) {
315
+ return this.json({ error: "Route not found" }, 404);
316
+ }
317
+ const handler = node.handlers[req.method];
318
+ if (!handler)
319
+ return this.json({ error: "Method not allowed" }, 405);
320
+ let methodMap = handlerMap.get(node);
321
+ if (!methodMap) {
322
+ methodMap = {};
323
+ handlerMap.set(node, methodMap);
324
+ }
325
+ if (!methodMap[req.method]) {
326
+ methodMap[req.method] = this.compilePipeline(handler, (_) => params);
327
+ }
328
+ return await methodMap[req.method](req, params, query);
329
+ } catch (err) {
330
+ if (this.errorHandler) {
331
+ try {
332
+ return this.errorHandler(err, req);
333
+ } catch {}
334
+ }
335
+ return this.json({ error: String(err) }, 500);
336
+ }
337
+ }
338
+ });
339
+ console.log(`\uD83D\uDE80 PrinceJS running at http://localhost:${port}`);
340
+ }
341
+ }
342
+ var prince = (dev = false) => new Prince(dev);
343
+ export {
344
+ prince,
345
+ Prince
346
+ };
package/package.json CHANGED
@@ -1,9 +1,23 @@
1
1
  {
2
2
  "name": "princejs",
3
- "version": "1.3.6",
3
+ "version": "1.4.1",
4
4
  "description": "An easy and fast backend framework — by a 13yo developer, for developers.",
5
5
  "main": "dist/prince.js",
6
6
  "types": "dist/prince.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./dist/prince.js",
10
+ "types": "./dist/prince.d.ts"
11
+ },
12
+ "./middleware": {
13
+ "import": "./dist/middleware.js",
14
+ "types": "./dist/middleware.d.ts"
15
+ },
16
+ "./validation": {
17
+ "import": "./dist/validation.js",
18
+ "types": "./dist/validation.d.ts"
19
+ }
20
+ },
7
21
  "files": [
8
22
  "dist"
9
23
  ],
@@ -13,7 +27,10 @@
13
27
  "framework",
14
28
  "bun",
15
29
  "easy",
16
- "princejs"
30
+ "princejs",
31
+ "rest",
32
+ "server",
33
+ "typescript"
17
34
  ],
18
35
  "author": "Matthew Michael (MatthewTheCoder1218)",
19
36
  "license": "MIT",
@@ -23,12 +40,11 @@
23
40
  },
24
41
  "devDependencies": {
25
42
  "@types/bun": "^1.3.2",
26
- "bun-types": "latest"
27
- },
28
- "dependencies": {
43
+ "bun-types": "latest",
44
+ "typescript": "^5.9.3",
29
45
  "zod": "^4.1.12"
30
46
  },
31
47
  "scripts": {
32
- "build": "bun build ./src/prince.ts --outdir dist --target bun"
48
+ "build": "bun build src/index.ts --outdir dist --target bun && bun build src/middleware.ts --outdir dist --target bun && bun build src/validation.ts --outdir dist --target bun"
33
49
  }
34
50
  }