princejs 2.1.4 → 2.1.6

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 CHANGED
@@ -20,23 +20,24 @@ Built by a 13-year-old Nigerian developer. Among the top three in performance.
20
20
 
21
21
  Benchmarked with `oha -c 100 -z 30s` on Windows 10:
22
22
 
23
- | Framework | Req/s | Total |
24
- |-----------|------:|------:|
25
- | Elysia | 25,312 | 759k |
26
- | Hono | 22,124 | 664k |
27
- | **PrinceJS** | **21,748** | **653k** |
28
- | Express | 9,325 | 280k |
23
+ | Framework | Avg Req/s | Peak Req/s |
24
+ |-----------|----------:|-----------:|
25
+ | Elysia | 27,606 | 27,834 |
26
+ | **PrinceJS** | **17,985** | **18,507** |
27
+ | Hono | 17,914 | 18,826 |
28
+ | Fastify | 15,519 | 16,434 |
29
+ | Express | 13,138 | 13,458 |
29
30
 
30
- > PrinceJS is **2.3× faster than Express** and sits comfortably in the top 3 at just **4.4kB gzipped**.
31
+ > PrinceJS is **2.3× faster than Express**, matches Hono head-to-head, and sits at just **5.1kB gzipped** — loads in ~101ms on a slow 3G connection.
31
32
 
32
33
  ---
33
34
 
34
35
  ## 🚀 Quick Start
35
36
 
36
37
  ```bash
37
- npm install princejs
38
38
  bun add princejs
39
- yarn add princejs
39
+ # or
40
+ npm install princejs
40
41
  ```
41
42
 
42
43
  ```ts
@@ -49,7 +50,7 @@ app.use(cors());
49
50
  app.use(logger());
50
51
 
51
52
  app.get("/", () => ({ message: "Hello PrinceJS!" }));
52
- app.get("/users/:id", (req) => ({ id: req.params.id }));
53
+ app.get("/users/:id", (req) => ({ id: req.params?.id }));
53
54
 
54
55
  app.listen(3000);
55
56
  ```
@@ -60,24 +61,17 @@ app.listen(3000);
60
61
 
61
62
  | Feature | Import |
62
63
  |---------|--------|
63
- | Routing | `princejs` |
64
- | Middleware (CORS, Logger, Rate Limit, Auth, JWT) | `princejs/middleware` |
65
- | Zod Validation | `princejs/middleware` |
66
- | **Cookies & IP Detection** | `princejs` |
67
- | File Uploads | `princejs/helpers` |
68
- | WebSockets | `princejs` |
69
- | Server-Sent Events | `princejs/helpers` |
70
- | Sessions | `princejs/middleware` |
71
- | Response Compression | `princejs/middleware` |
72
- | In-memory Cache | `princejs/helpers` |
64
+ | Routing, WebSockets, OpenAPI, Plugins, Lifecycle Hooks, Cookies, IP | `princejs` |
65
+ | CORS, Logger, JWT, Auth, Rate Limit, Validate, Compress, Session, API Key | `princejs/middleware` |
66
+ | File Uploads, SSE, In-memory Cache | `princejs/helpers` |
73
67
  | Cron Scheduler | `princejs/scheduler` |
74
- | **OpenAPI + Scalar Docs** | `princejs` |
75
68
  | JSX / SSR | `princejs/jsx` |
76
69
  | SQLite Database | `princejs/db` |
77
- | Plugin System | `princejs` |
78
70
  | End-to-End Type Safety | `princejs/client` |
79
- | Deploy Adapters | `princejs/vercel` · `princejs/cloudflare` · `princejs/deno` · `princejs/node` |
80
- | Lifecycle Hooks | `princejs` |
71
+ | Vercel Edge adapter | `princejs/vercel` |
72
+ | Cloudflare Workers adapter | `princejs/cloudflare` |
73
+ | Deno Deploy adapter | `princejs/deno` |
74
+ | Node.js / Express adapter | `princejs/node` |
81
75
 
82
76
  ---
83
77
 
@@ -85,31 +79,35 @@ app.listen(3000);
85
79
 
86
80
  ### Reading Cookies
87
81
 
88
- Cookies are automatically parsed from the request:
82
+ Cookies are automatically parsed and available on every request:
89
83
 
90
84
  ```ts
85
+ import { prince } from "princejs";
86
+
87
+ const app = prince();
88
+
91
89
  app.get("/profile", (req) => ({
92
90
  sessionId: req.cookies?.sessionId,
93
91
  theme: req.cookies?.theme,
94
- allCookies: req.cookies // Record<string, string>
92
+ allCookies: req.cookies, // Record<string, string>
95
93
  }));
96
94
  ```
97
95
 
98
96
  ### Setting Cookies
99
97
 
100
- Use the response builder to set cookies with full control:
98
+ Use the response builder for full cookie control:
101
99
 
102
100
  ```ts
103
- app.get("/login", (req) =>
101
+ app.get("/login", (req) =>
104
102
  app.response()
105
103
  .status(200)
106
104
  .json({ ok: true })
107
105
  .cookie("sessionId", "abc123", {
108
- maxAge: 3600, // 1 hour
106
+ maxAge: 3600, // 1 hour
109
107
  path: "/",
110
- httpOnly: true, // not accessible from JS
111
- secure: true, // HTTPS only
112
- sameSite: "Strict" // CSRF protection
108
+ httpOnly: true, // not accessible from JS
109
+ secure: true, // HTTPS only
110
+ sameSite: "Strict", // CSRF protection
113
111
  })
114
112
  );
115
113
 
@@ -123,39 +121,30 @@ app.response()
123
121
 
124
122
  ### Client IP Detection
125
123
 
126
- Automatically detect client IP from request headers:
127
-
128
124
  ```ts
129
125
  app.get("/api/data", (req) => ({
130
126
  clientIp: req.ip,
131
- data: [...]
127
+ data: [],
132
128
  }));
133
129
  ```
134
130
 
135
131
  **Supported headers** (in priority order):
136
- - `X-Forwarded-For` — Load balancers, proxies (first IP in list)
132
+ - `X-Forwarded-For` — load balancers, proxies (first IP in list)
137
133
  - `X-Real-IP` — Nginx, Apache reverse proxy
138
134
  - `CF-Connecting-IP` — Cloudflare
139
- - `X-Client-IP` — Other proxy services
140
- - Fallback — `127.0.0.1` (localhost)
141
-
142
- **Use cases:**
143
- - 🔒 Rate limiting per IP
144
- - 📊 Geolocation analytics
145
- - 🚨 IP-based access control
146
- - 👥 User tracking & fraud detection
135
+ - `X-Client-IP` — other proxy services
136
+ - Fallback — `127.0.0.1`
147
137
 
148
138
  ```ts
149
- // Rate limit by IP
139
+ // IP-based rate limiting
150
140
  app.use((req, next) => {
151
- const ip = req.ip;
152
- const count = ipTracker.getCount(ip) || 0;
141
+ const count = ipTracker.getCount(req.ip) || 0;
153
142
  if (count > 100) return new Response("Too many requests", { status: 429 });
154
- ipTracker.increment(ip);
143
+ ipTracker.increment(req.ip);
155
144
  return next();
156
145
  });
157
146
 
158
- // IP-based security
147
+ // IP allowlist
159
148
  app.post("/admin", (req) => {
160
149
  if (!ALLOWED_IPS.includes(req.ip!)) {
161
150
  return new Response("Forbidden", { status: 403 });
@@ -168,7 +157,7 @@ app.post("/admin", (req) => {
168
157
 
169
158
  ## 📖 OpenAPI + Scalar Docs ✨
170
159
 
171
- Auto-generate an OpenAPI 3.0 spec and serve a beautiful [Scalar](https://scalar.com) UI — all from a single `app.openapi()` call. Routes, validation, and docs stay in sync automatically.
160
+ Auto-generate an OpenAPI 3.0 spec and serve a beautiful [Scalar](https://scalar.com) UI — all from a single `app.openapi()` call.
172
161
 
173
162
  ```ts
174
163
  import { prince } from "princejs";
@@ -203,16 +192,16 @@ app.listen(3000);
203
192
  `api.route()` does three things at once:
204
193
 
205
194
  - ✅ Registers the route on PrinceJS
206
- - ✅ Auto-wires `validate(schema.body)` — no separate import needed
195
+ - ✅ Auto-wires body validation — no separate middleware needed
207
196
  - ✅ Writes the full OpenAPI spec entry
208
197
 
209
- | `schema` key | Runtime | Scalar Docs |
198
+ | `schema` key | Runtime effect | Scalar docs |
210
199
  |---|---|---|
211
- | `body` | ✅ Validates request | ✅ requestBody model |
200
+ | `body` | ✅ Validates & rejects bad requests | ✅ requestBody model |
212
201
  | `query` | — | ✅ Typed query params |
213
202
  | `response` | — | ✅ 200 response model |
214
203
 
215
- > Routes on `app.get()` / `app.post()` stay private — never appear in docs.
204
+ > Routes on `app.get()` / `app.post()` stay private — they never appear in the docs.
216
205
 
217
206
  **Themes:** `default` · `moon` · `purple` · `solarized` · `bluePlanet` · `deepSpace` · `saturn` · `kepler` · `mars`
218
207
 
@@ -220,8 +209,6 @@ app.listen(3000);
220
209
 
221
210
  ## 🔌 Plugin System
222
211
 
223
- Share bundles of routes and middleware as reusable plugins:
224
-
225
212
  ```ts
226
213
  import { prince, type PrincePlugin } from "princejs";
227
214
 
@@ -241,66 +228,53 @@ const usersPlugin: PrincePlugin<{ prefix?: string }> = (app, opts) => {
241
228
 
242
229
  const app = prince();
243
230
  app.plugin(usersPlugin, { prefix: "/api" });
231
+ app.listen(3000);
244
232
  ```
245
233
 
246
234
  ---
247
235
 
248
236
  ## 🎣 Lifecycle Hooks
249
237
 
250
- React to key moments in request processing with lifecycle hooks:
251
-
252
238
  ```ts
253
239
  import { prince } from "princejs";
254
240
 
255
241
  const app = prince();
256
242
 
257
- // Called for every incoming request
258
243
  app.onRequest((req) => {
259
- console.log(`📥 Request received: ${req.method} ${req.url}`);
244
+ (req as any).startTime = Date.now();
260
245
  });
261
246
 
262
- // Called before handler execution
263
247
  app.onBeforeHandle((req, path, method) => {
264
- console.log(`🔍 About to handle: ${method} ${path}`);
265
- (req as any).startTime = Date.now();
248
+ console.log(`🔍 ${method} ${path}`);
266
249
  });
267
250
 
268
- // Called after successful handler execution
269
251
  app.onAfterHandle((req, res, path, method) => {
270
- const duration = Date.now() - (req as any).startTime;
271
- console.log(`✅ Response: ${method} ${path} ${res.status} (${duration}ms)`);
252
+ const ms = Date.now() - (req as any).startTime;
253
+ console.log(`✅ ${method} ${path} ${res.status} (${ms}ms)`);
272
254
  });
273
255
 
274
- // Called when handler throws an error
275
256
  app.onError((err, req, path, method) => {
276
- console.error(`❌ Error in ${method} ${path}:`, err.message);
277
- // Send alert, log to monitoring service, etc.
257
+ console.error(`❌ ${method} ${path}:`, err.message);
278
258
  });
279
259
 
280
260
  app.get("/users", () => ({ users: [] }));
261
+ app.listen(3000);
281
262
  ```
282
263
 
283
- **Hook execution order:**
284
- 1. `onRequest` — early for request-wide setup
285
- 2. `onBeforeHandle` — just before route handler runs
264
+ **Execution order:**
265
+ 1. `onRequest` — runs before routing, good for setup
266
+ 2. `onBeforeHandle` — just before the handler
286
267
  3. Handler executes
287
- 4. `onAfterHandle` — after success (on error, skipped)
288
- 5. `onError` — only if handler throws (skips onAfterHandle)
289
-
290
- **Use cases:**
291
- - 📊 Metrics & observability
292
- - 🔍 Request inspection & debugging
293
- - ⏱️ Timing & performance monitoring
294
- - 🚨 Error tracking & alerting
295
- - 🔐 Security audits & compliance logging
268
+ 4. `onAfterHandle` — after success (skipped on error)
269
+ 5. `onError` — only when handler throws
296
270
 
297
271
  ---
298
272
 
299
273
  ## 🔒 End-to-End Type Safety
300
274
 
301
- Define a contract once — your client gets full TypeScript autocompletion automatically:
302
-
303
275
  ```ts
276
+ import { createClient, type PrinceApiContract } from "princejs/client";
277
+
304
278
  type ApiContract = {
305
279
  "GET /users/:id": {
306
280
  params: { id: string };
@@ -312,12 +286,13 @@ type ApiContract = {
312
286
  };
313
287
  };
314
288
 
315
- import { createClient } from "princejs/client";
316
-
317
289
  const client = createClient<ApiContract>("http://localhost:3000");
318
290
 
319
291
  const user = await client.get("/users/:id", { params: { id: "42" } });
320
292
  console.log(user.name); // typed as string ✅
293
+
294
+ const created = await client.post("/users", { body: { name: "Alice" } });
295
+ console.log(created.id); // typed as string ✅
321
296
  ```
322
297
 
323
298
  ---
@@ -342,20 +317,19 @@ import { toDeno } from "princejs/deno";
342
317
  Deno.serve(toDeno(app));
343
318
  ```
344
319
 
345
- **Node Adapter** - `server.ts`
320
+ **Node.js** `server.ts`
346
321
  ```ts
347
322
  import { createServer } from "http";
348
323
  import { toNode, toExpress } from "princejs/node";
324
+ import express from "express";
349
325
 
350
326
  const app = prince();
351
- app.get("/", () => ({ message: "Hello from Node!" }));
327
+ app.get("/", () => ({ message: "Hello!" }));
352
328
 
353
- // Native Node.js http
354
- const server = createServer(toNode(app));
355
- server.listen(3000);
329
+ // Native Node http
330
+ createServer(toNode(app)).listen(3000);
356
331
 
357
- // Or with Express
358
- import express from "express";
332
+ // Or drop into Express
359
333
  const expressApp = express();
360
334
  expressApp.all("*", toExpress(app));
361
335
  expressApp.listen(3000);
@@ -367,102 +341,102 @@ expressApp.listen(3000);
367
341
 
368
342
  ```ts
369
343
  import { prince } from "princejs";
370
- import { cors, logger, rateLimit, auth, apiKey, jwt, session, compress, serve } from "princejs/middleware";
371
- import { validate } from "princejs/validation";
344
+ import {
345
+ cors,
346
+ logger,
347
+ rateLimit,
348
+ auth,
349
+ apiKey,
350
+ jwt,
351
+ signJWT,
352
+ session,
353
+ compress,
354
+ validate,
355
+ } from "princejs/middleware";
372
356
  import { cache, upload, sse } from "princejs/helpers";
373
357
  import { cron } from "princejs/scheduler";
374
358
  import { Html, Head, Body, H1, P, render } from "princejs/jsx";
375
359
  import { db } from "princejs/db";
376
360
  import { z } from "zod";
377
361
 
378
- const app = prince(true);
379
-
380
- // ==========================================
381
- // LIFECYCLE HOOKS - Timing & Observability
382
- // ==========================================
383
- app.onRequest((req) => {
384
- (req as any).startTime = Date.now();
385
- });
386
-
387
- app.onBeforeHandle((req, path, method) => {
388
- console.log(`🔍 Handling: ${method} ${path}`);
389
- });
362
+ const SECRET = new TextEncoder().encode("your-secret");
363
+ const app = prince();
390
364
 
365
+ // ── Lifecycle hooks ───────────────────────────────────────
366
+ app.onRequest((req) => { (req as any).t = Date.now(); });
391
367
  app.onAfterHandle((req, res, path, method) => {
392
- const duration = Date.now() - (req as any).startTime;
393
- console.log(`✅ ${method} ${path} → ${res.status} (${duration}ms)`);
368
+ console.log(`✅ ${method} ${path} ${res.status} (${Date.now() - (req as any).t}ms)`);
394
369
  });
395
-
396
370
  app.onError((err, req, path, method) => {
397
- console.error(`❌ ${method} ${path} failed:`, err.message);
371
+ console.error(`❌ ${method} ${path}:`, err.message);
398
372
  });
399
373
 
400
- // ==========================================
401
- // GLOBAL MIDDLEWARE
402
- // ==========================================
374
+ // ── Global middleware ─────────────────────────────────────
403
375
  app.use(cors());
404
376
  app.use(logger());
405
- app.use(rateLimit({ max: 100, window: 60 }));
406
- app.use(serve({ root: "./public" }));
407
- app.use(jwt(key));
408
- app.use(session({ secret: "key" }));
377
+ app.use(rateLimit(100, 60));
378
+ app.use(jwt(SECRET));
379
+ app.use(session({ secret: "session-secret" }));
409
380
  app.use(compress());
410
381
 
411
- // ==========================================
412
- // ROUTES
413
- // ==========================================
382
+ // ── JSX SSR ───────────────────────────────────────────────
383
+ const Page = () => Html(Head("Home"), Body(H1("Hello World"), P("Welcome!")));
384
+ app.get("/", () => render(Page()));
414
385
 
415
- // JSX
416
- const Page = () => Html(Head("Test Page"), Body(H1("Hello World"), P("This is a test")));
417
- app.get("/jsx", () => render(Page()));
418
-
419
- // Cookies & IP Detection
420
- app.post("/login", (req) =>
386
+ // ── Cookies & IP ──────────────────────────────────────────
387
+ app.post("/login", (req) =>
421
388
  app.response()
422
389
  .json({ ok: true, ip: req.ip })
423
- .cookie("sessionId", "user_123", {
424
- httpOnly: true,
425
- secure: true,
426
- sameSite: "Strict",
427
- maxAge: 86400 // 24 hours
390
+ .cookie("sessionId", "user_123", {
391
+ httpOnly: true, secure: true, sameSite: "Strict", maxAge: 86400,
428
392
  })
429
393
  );
430
-
431
394
  app.get("/profile", (req) => ({
432
395
  sessionId: req.cookies?.sessionId,
433
396
  clientIp: req.ip,
434
397
  }));
435
398
 
436
- // Database
437
- const users = db.sqlite("./db.sqlite", "CREATE TABLE users...");
399
+ // ── Database ──────────────────────────────────────────────
400
+ const users = db.sqlite("./app.sqlite", `
401
+ CREATE TABLE IF NOT EXISTS users (id TEXT PRIMARY KEY, name TEXT NOT NULL)
402
+ `);
438
403
  app.get("/users", () => users.query("SELECT * FROM users"));
439
404
 
440
- // WebSockets
405
+ // ── WebSockets ────────────────────────────────────────────
441
406
  app.ws("/chat", {
442
- open: (ws) => ws.send("Welcome!"),
407
+ open: (ws) => ws.send("Welcome!"),
443
408
  message: (ws, msg) => ws.send(`Echo: ${msg}`),
409
+ close: (ws) => console.log("disconnected"),
444
410
  });
445
411
 
446
- // Auth
412
+ // ── Auth & API keys ───────────────────────────────────────
447
413
  app.get("/protected", auth(), (req) => ({ user: req.user }));
448
- app.get("/api", apiKey({ keys: ["key_123"] }), (req) => ({ ok: true }));
449
-
450
- // Helpers
451
- app.get("/data", cache(60)(() => ({ time: Date.now() })));
452
- app.post("/upload", upload(), (req) => ({ files: Object.keys(req.files || {}) }));
453
- app.get("/events", sse(), (req) => {
454
- setInterval(() => req.sseSend({ time: Date.now() }), 1000);
414
+ app.get("/api", apiKey({ keys: ["key_123"] }), () => ({ ok: true }));
415
+
416
+ // ── Helpers ───────────────────────────────────────────────
417
+ app.get("/cached", cache(60)(() => ({ time: Date.now() })));
418
+ app.post("/upload", upload());
419
+ app.get("/events", sse(), (req) => {
420
+ let i = 0;
421
+ const id = setInterval(() => {
422
+ req.sseSend({ count: i++ });
423
+ if (i >= 10) clearInterval(id);
424
+ }, 1000);
455
425
  });
456
426
 
457
- // ==========================================
458
- // CRON JOBS
459
- // ==========================================
460
- cron("*/1 * * * *", () => console.log("PrinceJS heartbeat"));
427
+ // ── Validation ────────────────────────────────────────────
428
+ app.post(
429
+ "/items",
430
+ validate(z.object({ name: z.string().min(1), price: z.number().positive() })),
431
+ (req) => ({ created: req.parsedBody })
432
+ );
433
+
434
+ // ── Cron ──────────────────────────────────────────────────
435
+ cron("* * * * *", () => console.log("💓 heartbeat"));
461
436
 
462
- // ==========================================
463
- // OPENAPI + SCALAR DOCS
464
- // ==========================================
437
+ // ── OpenAPI + Scalar ──────────────────────────────────────
465
438
  const api = app.openapi({ title: "PrinceJS App", version: "1.0.0" }, "/docs");
439
+
466
440
  api.route("GET", "/items", {
467
441
  summary: "List items",
468
442
  tags: ["items"],
@@ -472,6 +446,15 @@ api.route("GET", "/items", {
472
446
  },
473
447
  }, () => [{ id: "1", name: "Widget" }]);
474
448
 
449
+ api.route("POST", "/items", {
450
+ summary: "Create item",
451
+ tags: ["items"],
452
+ schema: {
453
+ body: z.object({ name: z.string().min(1), price: z.number().positive() }),
454
+ response: z.object({ id: z.string(), name: z.string() }),
455
+ },
456
+ }, (req) => ({ id: crypto.randomUUID(), name: req.parsedBody.name }));
457
+
475
458
  app.listen(3000);
476
459
  ```
477
460
 
@@ -483,8 +466,6 @@ app.listen(3000);
483
466
  bun add princejs
484
467
  # or
485
468
  npm install princejs
486
- # or
487
- yarn add princejs
488
469
  ```
489
470
 
490
471
  ---
@@ -511,7 +492,7 @@ bun test
511
492
 
512
493
  <div align="center">
513
494
 
514
- **PrinceJS: Small in size. Giant in capability. 👑**
495
+ **PrinceJS: 5.1kB. Hono-speed. Everything included. 👑**
515
496
 
516
497
  *Built with ❤️ in Nigeria*
517
498
 
package/dist/prince.d.ts CHANGED
@@ -75,6 +75,7 @@ export declare class Prince {
75
75
  private router;
76
76
  private staticRoutes;
77
77
  private staticMiddlewares;
78
+ private staticComposed;
78
79
  private routeCache;
79
80
  private onRequestHooks;
80
81
  private onBeforeHandleHooks;
@@ -111,8 +112,10 @@ export declare class Prince {
111
112
  private add;
112
113
  private buildRouter;
113
114
  private insertRoute;
115
+ private composeMW;
114
116
  private findRoute;
115
- private matchPath;
117
+ private composeRouterMiddlewares;
118
+ private composeStaticMiddlewares;
116
119
  private matchRoute;
117
120
  private parseBody;
118
121
  private executeHandler;
@@ -1 +1 @@
1
- {"version":3,"file":"prince.d.ts","sourceRoot":"","sources":["../src/prince.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"prince.d.ts","sourceRoot":"","sources":["../src/prince.ts"],"names":[],"mappings":"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"}
package/dist/prince.js CHANGED
@@ -177,27 +177,50 @@ function parseCookies(cookieHeader) {
177
177
  const cookies = {};
178
178
  if (!cookieHeader)
179
179
  return cookies;
180
- cookieHeader.split(";").forEach((pair) => {
181
- const [name, ...value] = pair.split("=");
182
- if (name)
183
- cookies[decodeURIComponent(name.trim())] = decodeURIComponent((value.join("=") || "").trim());
184
- });
180
+ const len = cookieHeader.length;
181
+ let i = 0;
182
+ while (i < len) {
183
+ while (i < len && cookieHeader.charCodeAt(i) === 32)
184
+ i++;
185
+ const eqIdx = cookieHeader.indexOf("=", i);
186
+ if (eqIdx === -1)
187
+ break;
188
+ const semIdx = cookieHeader.indexOf(";", eqIdx);
189
+ const end = semIdx === -1 ? len : semIdx;
190
+ const name = cookieHeader.slice(i, eqIdx).trimEnd();
191
+ const val = cookieHeader.slice(eqIdx + 1, end).trim();
192
+ if (name) {
193
+ try {
194
+ cookies[decodeURIComponent(name)] = decodeURIComponent(val);
195
+ } catch {
196
+ cookies[name] = val;
197
+ }
198
+ }
199
+ i = end + 1;
200
+ }
185
201
  return cookies;
186
202
  }
203
+ function extractPathname(url) {
204
+ const slashSlash = url.indexOf("//");
205
+ if (slashSlash === -1)
206
+ return "/";
207
+ const pathStart = url.indexOf("/", slashSlash + 2);
208
+ if (pathStart === -1)
209
+ return "/";
210
+ const qIdx = url.indexOf("?", pathStart);
211
+ return qIdx === -1 ? url.slice(pathStart) : url.slice(pathStart, qIdx);
212
+ }
213
+ function extractSearch(url) {
214
+ const qIdx = url.indexOf("?");
215
+ return qIdx === -1 ? "" : url.slice(qIdx + 1);
216
+ }
187
217
  function detectIP(req) {
188
218
  const forwarded = req.headers.get("x-forwarded-for");
189
- if (forwarded)
190
- return forwarded.split(",")[0].trim();
191
- const realIp = req.headers.get("x-real-ip");
192
- if (realIp)
193
- return realIp;
194
- const cfIp = req.headers.get("cf-connecting-ip");
195
- if (cfIp)
196
- return cfIp;
197
- const clientIp = req.headers.get("x-client-ip");
198
- if (clientIp)
199
- return clientIp;
200
- return "127.0.0.1";
219
+ if (forwarded) {
220
+ const comma = forwarded.indexOf(",");
221
+ return comma === -1 ? forwarded.trim() : forwarded.slice(0, comma).trim();
222
+ }
223
+ return req.headers.get("x-real-ip") ?? req.headers.get("cf-connecting-ip") ?? req.headers.get("x-client-ip") ?? "127.0.0.1";
201
224
  }
202
225
  function zodToJsonSchema(schema) {
203
226
  const d = schema._def;
@@ -353,6 +376,7 @@ class Prince {
353
376
  router = null;
354
377
  staticRoutes = new Map;
355
378
  staticMiddlewares = new Map;
379
+ staticComposed = new Map;
356
380
  routeCache = new Map;
357
381
  onRequestHooks = [];
358
382
  onBeforeHandleHooks = [];
@@ -363,6 +387,9 @@ class Prince {
363
387
  }
364
388
  use(mw) {
365
389
  this.middlewares.push(mw);
390
+ this.routeCache.clear();
391
+ this.staticComposed.clear();
392
+ this.router = null;
366
393
  return this;
367
394
  }
368
395
  plugin(plugin, options) {
@@ -448,6 +475,8 @@ class Prince {
448
475
  pattern: "",
449
476
  handlers: {},
450
477
  middlewares: {},
478
+ composedMiddlewares: {},
479
+ allowedMethods: new Set,
451
480
  children: []
452
481
  };
453
482
  for (const route of this.rawRoutes) {
@@ -476,6 +505,8 @@ class Prince {
476
505
  pattern: part,
477
506
  handlers: {},
478
507
  middlewares: {},
508
+ composedMiddlewares: {},
509
+ allowedMethods: new Set,
479
510
  children: []
480
511
  };
481
512
  if (part.startsWith(":")) {
@@ -490,76 +521,71 @@ class Prince {
490
521
  }
491
522
  }
492
523
  currentNode.handlers[route.method] = route.handler;
524
+ currentNode.allowedMethods.add(route.method);
493
525
  if (route.middlewares.length > 0) {
494
526
  currentNode.middlewares[route.method] = route.middlewares;
495
527
  }
496
528
  }
529
+ composeMW(routeMW) {
530
+ if (this.middlewares.length === 0)
531
+ return routeMW;
532
+ if (routeMW.length === 0)
533
+ return this.middlewares;
534
+ return [...this.middlewares, ...routeMW];
535
+ }
497
536
  findRoute(method, pathname) {
498
537
  const cacheKey = `${method}:${pathname}`;
499
538
  if (this.routeCache.has(cacheKey)) {
500
539
  return this.routeCache.get(cacheKey);
501
540
  }
502
- const staticKey = `${method}:${pathname}`;
503
- const staticHandler = this.staticRoutes.get(staticKey);
541
+ const staticHandler = this.staticRoutes.get(cacheKey);
504
542
  if (staticHandler) {
505
- const result2 = {
506
- handler: staticHandler,
507
- params: {},
508
- middlewares: this.staticMiddlewares.get(staticKey) || []
509
- };
543
+ const composed = this.staticComposed.get(cacheKey) ?? this.composeMW(this.staticMiddlewares.get(cacheKey) ?? []);
544
+ const result2 = { handler: staticHandler, params: {}, middlewares: composed };
510
545
  this.routeCache.set(cacheKey, result2);
511
546
  return result2;
512
547
  }
548
+ if (this.staticRoutes.size > 0) {
549
+ const methods = ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"];
550
+ const allowed = methods.filter((m) => m !== method && this.staticRoutes.has(`${m}:${pathname}`));
551
+ if (allowed.length > 0) {
552
+ const r = { handler: null, params: {}, middlewares: [], allowedMethods: allowed };
553
+ this.routeCache.set(cacheKey, r);
554
+ return r;
555
+ }
556
+ }
513
557
  const segments = pathname === "/" ? [""] : pathname.split("/").slice(1);
514
558
  const result = this.matchRoute(this.buildRouter(), segments, method);
515
- if (result) {
516
- this.routeCache.set(cacheKey, result);
517
- return result;
518
- }
519
- const allowedMethods = new Set;
520
- for (const route of this.rawRoutes) {
521
- if (this.matchPath(route.path, pathname)) {
522
- allowedMethods.add(route.method);
523
- }
559
+ this.routeCache.set(cacheKey, result);
560
+ return result;
561
+ }
562
+ composeRouterMiddlewares(node) {
563
+ for (const method of Object.keys(node.handlers)) {
564
+ const routeMW = node.middlewares[method] ?? [];
565
+ node.composedMiddlewares[method] = this.middlewares.length === 0 ? routeMW : routeMW.length === 0 ? this.middlewares : [...this.middlewares, ...routeMW];
524
566
  }
525
- if (allowedMethods.size > 0) {
526
- const methodNotAllowed = {
527
- handler: null,
528
- params: {},
529
- middlewares: [],
530
- allowedMethods: Array.from(allowedMethods)
531
- };
532
- this.routeCache.set(cacheKey, methodNotAllowed);
533
- return methodNotAllowed;
567
+ for (const child of node.children) {
568
+ this.composeRouterMiddlewares(child);
534
569
  }
535
- this.routeCache.set(cacheKey, null);
536
- return null;
537
570
  }
538
- matchPath(routePath, requestPath) {
539
- const routeParts = routePath === "/" ? [""] : routePath.split("/").slice(1);
540
- const requestParts = requestPath === "/" ? [""] : requestPath.split("/").slice(1);
541
- if (routeParts.length !== requestParts.length) {
542
- if (routeParts.includes("**"))
543
- return true;
544
- return false;
571
+ composeStaticMiddlewares() {
572
+ this.staticComposed.clear();
573
+ for (const [key, handler] of this.staticRoutes) {
574
+ const routeMW = this.staticMiddlewares.get(key) ?? [];
575
+ this.staticComposed.set(key, this.middlewares.length === 0 ? routeMW : routeMW.length === 0 ? this.middlewares : [...this.middlewares, ...routeMW]);
545
576
  }
546
- for (let i = 0;i < routeParts.length; i++) {
547
- const routePart = routeParts[i];
548
- const requestPart = requestParts[i];
549
- if (routePart.startsWith(":") || routePart === "*" || routePart === "**") {
550
- continue;
551
- }
552
- if (routePart !== requestPart) {
553
- return false;
554
- }
555
- }
556
- return true;
557
577
  }
558
578
  matchRoute(node, segments, method, params = {}, index = 0) {
559
579
  if (index === segments.length) {
560
580
  const handler = node.handlers[method];
561
- const middlewares = node.middlewares[method] || [];
562
- return handler ? { handler, params, middlewares } : null;
581
+ if (handler) {
582
+ const middlewares = node.composedMiddlewares[method] ?? this.composeMW(node.middlewares[method] ?? []);
583
+ return { handler, params, middlewares };
584
+ }
585
+ if (node.allowedMethods.size > 0) {
586
+ return { handler: null, params, middlewares: [], allowedMethods: [...node.allowedMethods] };
587
+ }
588
+ return null;
563
589
  }
564
590
  const segment = segments[index];
565
591
  for (const child of node.children) {
@@ -573,11 +599,15 @@ class Prince {
573
599
  }
574
600
  for (const child of node.children) {
575
601
  if (child.paramName) {
602
+ const saved = params[child.paramName];
576
603
  params[child.paramName] = segment;
577
604
  const result = this.matchRoute(child, segments, method, params, index + 1);
578
605
  if (result)
579
606
  return result;
580
- delete params[child.paramName];
607
+ if (saved === undefined)
608
+ delete params[child.paramName];
609
+ else
610
+ params[child.paramName] = saved;
581
611
  }
582
612
  }
583
613
  for (const child of node.children) {
@@ -590,39 +620,32 @@ class Prince {
590
620
  for (const child of node.children) {
591
621
  if (child.isCatchAll) {
592
622
  const handler = child.handlers[method];
593
- const middlewares = child.middlewares[method] || [];
594
623
  if (handler) {
624
+ const middlewares = child.composedMiddlewares[method] ?? this.composeMW(child.middlewares[method] ?? []);
595
625
  return { handler, params, middlewares };
596
626
  }
627
+ if (child.allowedMethods.size > 0) {
628
+ return { handler: null, params, middlewares: [], allowedMethods: [...child.allowedMethods] };
629
+ }
597
630
  }
598
631
  }
599
632
  return null;
600
633
  }
601
634
  async parseBody(req) {
602
635
  const ct = req.headers.get("content-type") || "";
603
- const clonedReq = req.clone();
604
636
  try {
605
637
  if (ct.includes("application/json")) {
606
- return await clonedReq.json();
638
+ return await req.json();
607
639
  }
608
640
  if (ct.includes("application/x-www-form-urlencoded")) {
609
- const text = await clonedReq.text();
641
+ const text = await req.text();
610
642
  return Object.fromEntries(new URLSearchParams(text));
611
643
  }
612
644
  if (ct.startsWith("multipart/form-data")) {
613
- const fd = await clonedReq.formData();
614
- const files = {};
615
- const fields = {};
616
- for (const [k, v] of fd.entries()) {
617
- if (v instanceof File)
618
- files[k] = v;
619
- else
620
- fields[k] = v;
621
- }
622
- return { files, fields };
645
+ return null;
623
646
  }
624
647
  if (ct.startsWith("text/")) {
625
- return await clonedReq.text();
648
+ return await req.text();
626
649
  }
627
650
  } catch (error) {
628
651
  console.error("Body parsing error:", error);
@@ -630,11 +653,31 @@ class Prince {
630
653
  }
631
654
  return null;
632
655
  }
633
- async executeHandler(req, handler, params, query, routeMiddlewares, method, pathname) {
656
+ async executeHandler(req, handler, params, search, routeMiddlewares, method, pathname) {
634
657
  req.params = params;
635
- req.query = query;
636
- req.cookies = parseCookies(req.headers.get("cookie") || "");
637
- req.ip = detectIP(req);
658
+ let _query;
659
+ Object.defineProperty(req, "query", {
660
+ get() {
661
+ return _query ??= new URLSearchParams(search);
662
+ },
663
+ configurable: true
664
+ });
665
+ Object.defineProperty(req, "cookies", {
666
+ get() {
667
+ const val = parseCookies(req.headers.get("cookie") ?? "");
668
+ Object.defineProperty(req, "cookies", { value: val, configurable: true });
669
+ return val;
670
+ },
671
+ configurable: true
672
+ });
673
+ Object.defineProperty(req, "ip", {
674
+ get() {
675
+ const val = detectIP(req);
676
+ Object.defineProperty(req, "ip", { value: val, configurable: true });
677
+ return val;
678
+ },
679
+ configurable: true
680
+ });
638
681
  if (["POST", "PUT", "PATCH"].includes(req.method) && !req.parsedBody) {
639
682
  const parsed = await this.parseBody(req);
640
683
  if (parsed) {
@@ -649,7 +692,7 @@ class Prince {
649
692
  for (const hook of this.onBeforeHandleHooks) {
650
693
  await hook(req, pathname, method);
651
694
  }
652
- const allMiddlewares = routeMiddlewares.length > 0 ? [...this.middlewares, ...routeMiddlewares] : this.middlewares;
695
+ const allMiddlewares = routeMiddlewares;
653
696
  let i = 0;
654
697
  const next = async () => {
655
698
  while (i < allMiddlewares.length) {
@@ -669,16 +712,18 @@ class Prince {
669
712
  return this.json(res);
670
713
  };
671
714
  const response = await next();
672
- for (const hook of this.onAfterHandleHooks) {
673
- await hook(req, response.clone(), pathname, method);
715
+ if (this.onAfterHandleHooks.length > 0) {
716
+ for (const hook of this.onAfterHandleHooks) {
717
+ await hook(req, response, pathname, method);
718
+ }
674
719
  }
675
720
  return response;
676
721
  }
677
722
  async handleFetch(req) {
678
- const url = new URL(req.url);
723
+ const rawUrl = req.url;
724
+ const pathname = extractPathname(rawUrl);
679
725
  const r = req;
680
726
  const method = req.method;
681
- const pathname = url.pathname;
682
727
  for (const hook of this.onRequestHooks) {
683
728
  await hook(r);
684
729
  }
@@ -695,15 +740,16 @@ class Prince {
695
740
  }
696
741
  });
697
742
  }
698
- return this.executeHandler(r, routeMatch.handler, routeMatch.params, url.searchParams, routeMatch.middlewares, method, pathname);
743
+ const search = extractSearch(rawUrl);
744
+ return this.executeHandler(r, routeMatch.handler, routeMatch.params, search, routeMatch.middlewares, method, pathname);
699
745
  }
700
746
  async fetch(req) {
701
- const url = new URL(req.url);
702
- const pathname = url.pathname;
747
+ const rawUrl = req.url;
703
748
  const method = req.method;
704
749
  try {
705
750
  return await this.handleFetch(req);
706
751
  } catch (err) {
752
+ const pathname = extractPathname(rawUrl);
707
753
  for (const hook of this.onErrorHooks) {
708
754
  await hook(err, req, pathname, method);
709
755
  }
@@ -768,12 +814,15 @@ class Prince {
768
814
  }
769
815
  listen(port = 3000) {
770
816
  const self = this;
817
+ const router = this.buildRouter();
818
+ this.composeRouterMiddlewares(router);
819
+ this.composeStaticMiddlewares();
771
820
  Bun.serve({
772
821
  port,
773
822
  fetch: (req, server) => {
774
- const url = new URL(req.url);
775
- if (self.wsRoutes[url.pathname] && server.upgrade(req, {
776
- data: { path: url.pathname }
823
+ const pathname = extractPathname(req.url);
824
+ if (self.wsRoutes[pathname] && server.upgrade(req, {
825
+ data: { path: pathname }
777
826
  })) {
778
827
  return;
779
828
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "princejs",
3
- "version": "2.1.4",
3
+ "version": "2.1.6",
4
4
  "description": "An easy and fast backend framework that is among the top three — by a 13yo developer, for developers.",
5
5
  "main": "dist/prince.js",
6
6
  "types": "dist/prince.d.ts",