princejs 1.9.7 โ†’ 2.1.0

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
@@ -1,7 +1,5 @@
1
1
  <div align="center">
2
2
 
3
- <img src="./src/images/og.png" alt="PrinceJS"/>
4
-
5
3
  # ๐Ÿ‘‘ PrinceJS
6
4
 
7
5
  **Ultra-clean, modern & minimal Bun web framework.**
@@ -65,6 +63,7 @@ app.listen(3000);
65
63
  | Routing | `princejs` |
66
64
  | Middleware (CORS, Logger, Rate Limit, Auth, JWT) | `princejs/middleware` |
67
65
  | Zod Validation | `princejs/middleware` |
66
+ | **Cookies & IP Detection** | `princejs` |
68
67
  | File Uploads | `princejs/helpers` |
69
68
  | WebSockets | `princejs` |
70
69
  | Server-Sent Events | `princejs/helpers` |
@@ -77,7 +76,93 @@ app.listen(3000);
77
76
  | SQLite Database | `princejs/db` |
78
77
  | Plugin System | `princejs` |
79
78
  | End-to-End Type Safety | `princejs/client` |
80
- | Deploy Adapters | `princejs/vercel` ยท `princejs/cloudflare` ยท `princejs/deno` |
79
+ | Deploy Adapters | `princejs/vercel` ยท `princejs/cloudflare` ยท `princejs/deno` ยท `princejs/node` |
80
+ | Lifecycle Hooks | `princejs` |
81
+
82
+ ---
83
+
84
+ ## ๐Ÿช Cookies & ๐ŸŒ IP Detection
85
+
86
+ ### Reading Cookies
87
+
88
+ Cookies are automatically parsed from the request:
89
+
90
+ ```ts
91
+ app.get("/profile", (req) => ({
92
+ sessionId: req.cookies?.sessionId,
93
+ theme: req.cookies?.theme,
94
+ allCookies: req.cookies // Record<string, string>
95
+ }));
96
+ ```
97
+
98
+ ### Setting Cookies
99
+
100
+ Use the response builder to set cookies with full control:
101
+
102
+ ```ts
103
+ app.get("/login", (req) =>
104
+ app.response()
105
+ .status(200)
106
+ .json({ ok: true })
107
+ .cookie("sessionId", "abc123", {
108
+ maxAge: 3600, // 1 hour
109
+ path: "/",
110
+ httpOnly: true, // not accessible from JS
111
+ secure: true, // HTTPS only
112
+ sameSite: "Strict" // CSRF protection
113
+ })
114
+ );
115
+
116
+ // Chain multiple cookies
117
+ app.response()
118
+ .json({ ok: true })
119
+ .cookie("session", "xyz")
120
+ .cookie("theme", "dark")
121
+ .cookie("lang", "en");
122
+ ```
123
+
124
+ ### Client IP Detection
125
+
126
+ Automatically detect client IP from request headers:
127
+
128
+ ```ts
129
+ app.get("/api/data", (req) => ({
130
+ clientIp: req.ip,
131
+ data: [...]
132
+ }));
133
+ ```
134
+
135
+ **Supported headers** (in priority order):
136
+ - `X-Forwarded-For` โ€” Load balancers, proxies (first IP in list)
137
+ - `X-Real-IP` โ€” Nginx, Apache reverse proxy
138
+ - `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
147
+
148
+ ```ts
149
+ // Rate limit by IP
150
+ app.use((req, next) => {
151
+ const ip = req.ip;
152
+ const count = ipTracker.getCount(ip) || 0;
153
+ if (count > 100) return new Response("Too many requests", { status: 429 });
154
+ ipTracker.increment(ip);
155
+ return next();
156
+ });
157
+
158
+ // IP-based security
159
+ app.post("/admin", (req) => {
160
+ if (!ALLOWED_IPS.includes(req.ip!)) {
161
+ return new Response("Forbidden", { status: 403 });
162
+ }
163
+ return { authorized: true };
164
+ });
165
+ ```
81
166
 
82
167
  ---
83
168
 
@@ -160,6 +245,57 @@ app.plugin(usersPlugin, { prefix: "/api" });
160
245
 
161
246
  ---
162
247
 
248
+ ## ๐ŸŽฃ Lifecycle Hooks
249
+
250
+ React to key moments in request processing with lifecycle hooks:
251
+
252
+ ```ts
253
+ import { prince } from "princejs";
254
+
255
+ const app = prince();
256
+
257
+ // Called for every incoming request
258
+ app.onRequest((req) => {
259
+ console.log(`๐Ÿ“ฅ Request received: ${req.method} ${req.url}`);
260
+ });
261
+
262
+ // Called before handler execution
263
+ app.onBeforeHandle((req, path, method) => {
264
+ console.log(`๐Ÿ” About to handle: ${method} ${path}`);
265
+ (req as any).startTime = Date.now();
266
+ });
267
+
268
+ // Called after successful handler execution
269
+ 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)`);
272
+ });
273
+
274
+ // Called when handler throws an error
275
+ app.onError((err, req, path, method) => {
276
+ console.error(`โŒ Error in ${method} ${path}:`, err.message);
277
+ // Send alert, log to monitoring service, etc.
278
+ });
279
+
280
+ app.get("/users", () => ({ users: [] }));
281
+ ```
282
+
283
+ **Hook execution order:**
284
+ 1. `onRequest` โ€” early for request-wide setup
285
+ 2. `onBeforeHandle` โ€” just before route handler runs
286
+ 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
296
+
297
+ ---
298
+
163
299
  ## ๐Ÿ”’ End-to-End Type Safety
164
300
 
165
301
  Define a contract once โ€” your client gets full TypeScript autocompletion automatically:
@@ -206,6 +342,25 @@ import { toDeno } from "princejs/deno";
206
342
  Deno.serve(toDeno(app));
207
343
  ```
208
344
 
345
+ **Node Adapter** - `server.ts`
346
+ ```ts
347
+ import { createServer } from "http";
348
+ import { toNode, toExpress } from "princejs/node";
349
+
350
+ const app = prince();
351
+ app.get("/", () => ({ message: "Hello from Node!" }));
352
+
353
+ // Native Node.js http
354
+ const server = createServer(toNode(app));
355
+ server.listen(3000);
356
+
357
+ // Or with Express
358
+ import express from "express";
359
+ const expressApp = express();
360
+ expressApp.all("*", toExpress(app));
361
+ expressApp.listen(3000);
362
+ ```
363
+
209
364
  ---
210
365
 
211
366
  ## ๐ŸŽฏ Full Example
@@ -222,7 +377,29 @@ import { z } from "zod";
222
377
 
223
378
  const app = prince(true);
224
379
 
225
- // Global middleware
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
+ });
390
+
391
+ app.onAfterHandle((req, res, path, method) => {
392
+ const duration = Date.now() - (req as any).startTime;
393
+ console.log(`โœ… ${method} ${path} โ†’ ${res.status} (${duration}ms)`);
394
+ });
395
+
396
+ app.onError((err, req, path, method) => {
397
+ console.error(`โŒ ${method} ${path} failed:`, err.message);
398
+ });
399
+
400
+ // ==========================================
401
+ // GLOBAL MIDDLEWARE
402
+ // ==========================================
226
403
  app.use(cors());
227
404
  app.use(logger());
228
405
  app.use(rateLimit({ max: 100, window: 60 }));
@@ -231,10 +408,31 @@ app.use(jwt(key));
231
408
  app.use(session({ secret: "key" }));
232
409
  app.use(compress());
233
410
 
411
+ // ==========================================
412
+ // ROUTES
413
+ // ==========================================
414
+
234
415
  // JSX
235
416
  const Page = () => Html(Head("Test Page"), Body(H1("Hello World"), P("This is a test")));
236
417
  app.get("/jsx", () => render(Page()));
237
418
 
419
+ // Cookies & IP Detection
420
+ app.post("/login", (req) =>
421
+ app.response()
422
+ .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
428
+ })
429
+ );
430
+
431
+ app.get("/profile", (req) => ({
432
+ sessionId: req.cookies?.sessionId,
433
+ clientIp: req.ip,
434
+ }));
435
+
238
436
  // Database
239
437
  const users = db.sqlite("./db.sqlite", "CREATE TABLE users...");
240
438
  app.get("/users", () => users.query("SELECT * FROM users"));
@@ -247,7 +445,7 @@ app.ws("/chat", {
247
445
 
248
446
  // Auth
249
447
  app.get("/protected", auth(), (req) => ({ user: req.user }));
250
- app.get("/api", apiKey({ keys: ["key_123"] }), handler);
448
+ app.get("/api", apiKey({ keys: ["key_123"] }), (req) => ({ ok: true }));
251
449
 
252
450
  // Helpers
253
451
  app.get("/data", cache(60)(() => ({ time: Date.now() })));
@@ -256,10 +454,14 @@ app.get("/events", sse(), (req) => {
256
454
  setInterval(() => req.sseSend({ time: Date.now() }), 1000);
257
455
  });
258
456
 
259
- // Cron
457
+ // ==========================================
458
+ // CRON JOBS
459
+ // ==========================================
260
460
  cron("*/1 * * * *", () => console.log("PrinceJS heartbeat"));
261
461
 
262
- // OpenAPI + Scalar docs
462
+ // ==========================================
463
+ // OPENAPI + SCALAR DOCS
464
+ // ==========================================
263
465
  const api = app.openapi({ title: "PrinceJS App", version: "1.0.0" }, "/docs");
264
466
  api.route("GET", "/items", {
265
467
  summary: "List items",
@@ -0,0 +1,47 @@
1
+ /**
2
+ * Official Node.js deploy adapter for PrinceJS
3
+ *
4
+ * @example
5
+ * // server.ts
6
+ * import { createServer } from "http";
7
+ * import { prince } from "princejs";
8
+ * import { toNode } from "princejs/node";
9
+ *
10
+ * const app = prince();
11
+ * app.get("/", () => ({ message: "Hello from Node.js!" }));
12
+ *
13
+ * const server = createServer(toNode(app));
14
+ * server.listen(3000, () => {
15
+ * console.log("Listening on http://localhost:3000");
16
+ * });
17
+ *
18
+ * @example
19
+ * // With Express.js
20
+ * import express from "express";
21
+ * import { prince } from "princejs";
22
+ * import { toExpress } from "princejs/node";
23
+ *
24
+ * const app = express();
25
+ * const princeApp = prince();
26
+ * princeApp.get("/", () => ({ message: "Hello!" }));
27
+ *
28
+ * app.all("*", toExpress(princeApp));
29
+ * app.listen(3000);
30
+ */
31
+ export type PrinceApp = {
32
+ fetch(request: Request): Promise<Response>;
33
+ };
34
+ /**
35
+ * Converts a Prince app to a Node.js http.createServer() handler
36
+ * @example
37
+ * const server = createServer(toNode(app));
38
+ * server.listen(3000);
39
+ */
40
+ export declare function toNode(app: PrinceApp): (req: any, res: any) => Promise<void>;
41
+ /**
42
+ * Converts a Prince app to an Express.js middleware handler
43
+ * @example
44
+ * app.all("*", toExpress(princeApp));
45
+ */
46
+ export declare function toExpress(app: PrinceApp): (req: any, res: any, next?: any) => Promise<void>;
47
+ //# sourceMappingURL=node.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node.d.ts","sourceRoot":"","sources":["../../src/adapters/node.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,MAAM,MAAM,SAAS,GAAG;IAAE,KAAK,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;CAAE,CAAC;AAEvE;;;;;GAKG;AACH,wBAAgB,MAAM,CACpB,GAAG,EAAE,SAAS,GACb,CACD,GAAG,EAAE,GAAG,EACR,GAAG,EAAE,GAAG,KACL,OAAO,CAAC,IAAI,CAAC,CAuCjB;AAED;;;;GAIG;AACH,wBAAgB,SAAS,CACvB,GAAG,EAAE,SAAS,GACb,CACD,GAAG,EAAE,GAAG,EACR,GAAG,EAAE,GAAG,EACR,IAAI,CAAC,EAAE,GAAG,KACP,OAAO,CAAC,IAAI,CAAC,CAoCjB"}