vibe-gx 4.2.0 → 4.2.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.
package/README.md CHANGED
@@ -1,770 +1,24 @@
1
- <div align="center">
2
- <img src="https://github.com/thesixers/vibe/blob/808b45722a0b3ca0d266215bbe4cc074f62283e5/assets/vlogo.png?raw=true" alt="Vibe Logo" width="180" />
3
- <h1>Vibe</h1>
4
- <p>
5
- <b>The fastest Node.js web framework with the simplest syntax.</b>
6
- </p>
7
- <p>
8
- <img src="https://img.shields.io/badge/performance-11,472_RPS-brightgreen" alt="Performance" />
9
- <img src="https://img.shields.io/badge/vs_Express-4.7x_faster-blue" alt="vs Express" />
10
- <img src="https://img.shields.io/badge/vs_Fastify-faster-orange" alt="vs Fastify" />
11
- <img src="https://img.shields.io/badge/license-MIT-green" alt="License" />
12
- </p>
13
- </div>
1
+ # Vibe
14
2
 
15
- ---
16
-
17
- ## 📦 Installation
18
-
19
- ```bash
20
- npm install vibe-gx
21
- ```
22
-
23
- > Pure JavaScript — no native dependencies, no build steps, just install and go.
24
-
25
- ---
26
-
27
- ## 🏆 Why Vibe?
28
-
29
- | Metric | Vibe | Express | Fastify |
30
- | :------------------------- | :------------: | :-------: | :--------: |
31
- | **JSON Performance** | **11,472 RPS** | 2,421 RPS | 11,334 RPS |
32
- | **Install Size** | **~280 KB** | ~5 MB | ~4 MB |
33
- | **Lines for Hello World** | 3 | 5 | 6 |
34
- | **Dependencies** | 1 | 30+ | 15+ |
35
- | **Built-in Clustering** | ✅ | ❌ | ❌ |
36
- | **Built-in Caching** | ✅ | ❌ | ❌ |
37
- | **Code-Gen Serialization** | ✅ | ❌ | ✅ |
38
-
39
- > **Vibe is faster than Fastify, simpler than Express, and 14-18x smaller than both.**
40
-
41
- ---
42
-
43
- ## ⚡ Features
44
-
45
- | Feature | Description |
46
- | :---------------------------- | :--------------------------------------------------------- |
47
- | 🚀 **Code-Gen Serialization** | Schema-compiled JSON serializers via `new Function()` |
48
- | 🎯 **Hybrid Router** | O(1) static + O(log n) Trie routing |
49
- | 🔌 **Plugin System** | Encapsulated `register()` with optional route prefixes |
50
- | 🎨 **Decorators** | Extend app, request, and response |
51
- | ⚡ **Cluster Mode** | Built-in multi-process scaling |
52
- | 💾 **LRU Cache** | Built-in response caching with ETag |
53
- | 🛡️ **Rate Limiting** | Built-in sliding window rate limiter — no dependencies |
54
- | 🌐 **CORS** | Built-in CORS with preflight handling — no dependencies |
55
- | 🔗 **Connection Pool** | Generic pool for databases |
56
- | 📂 **File Uploads** | Multipart uploads with size/type validation |
57
- | 🌊 **Streaming** | Large file uploads without buffering |
58
- | 🔒 **Security** | Path traversal protection, body limits, error sanitization |
59
- | 🔄 **Express Adapter** | Use any Express middleware with `adapt()` |
60
-
61
- ---
62
-
63
- ## 🚀 Quick Start
64
-
65
- ```javascript
66
- import vibe from "vibe-gx";
67
-
68
- const app = vibe();
69
-
70
- // Direct value - no callback needed!
71
- app.get("/", "Hello Vibe!");
72
-
73
- // Auto JSON response - just return an object
74
- app.get("/users/:id", (req) => ({ userId: req.params.id }));
75
-
76
- app.listen(3000);
77
- ```
78
-
79
- **That's it.** No `res.send()`, no `res.json()` - just return data.
80
-
81
- ---
82
-
83
- ## 📖 Core API
84
-
85
- ### Routes
86
-
87
- Vibe supports all standard HTTP methods with a clean, flexible syntax:
88
-
89
- ```javascript
90
- // String response
91
- app.get("/", "Hello World");
92
-
93
- // JSON response (just return an object)
94
- app.get("/json", { message: "Hello" });
95
-
96
- // Handler function with request access
97
- app.get("/users/:id", (req) => ({ id: req.params.id }));
98
-
99
- // Multiple route parameters
100
- app.get("/posts/:postId/comments/:commentId", (req) => ({
101
- postId: req.params.postId,
102
- commentId: req.params.commentId,
103
- }));
104
-
105
- // With options (interceptors, file uploads)
106
- app.post("/protected", { intercept: authCheck }, handler);
107
-
108
- // All HTTP methods
109
- app.get("/");
110
- app.post("/");
111
- app.put("/");
112
- app.del("/"); // DELETE
113
- app.patch("/");
114
- app.head("/");
115
- ```
116
-
117
- ### Query Parameters
118
-
119
- ```javascript
120
- // GET /search?q=hello&page=2
121
- app.get("/search", (req) => ({
122
- query: req.query.q, // "hello"
123
- page: req.query.page, // "2"
124
- }));
125
- ```
126
-
127
- ### Request Body
128
-
129
- ```javascript
130
- app.post("/users", (req) => {
131
- const { name, email } = req.body;
132
- return { created: { name, email } };
133
- });
134
- ```
135
-
136
- ---
137
-
138
- ## 📝 Logging & Error Handling
139
-
140
- Vibe ships with a structured JSON logger (Pino-compatible) and a powerful error interception system. Errors thrown, returned, or sent from any route are automatically caught and routed through a central error handler.
141
-
142
- ### JSON Structured Logging
143
-
144
- Initialize the app with `logger: { lifecycle: true }` or add `prettyPrint: true` for development to get beautiful, human-readable terminal output.
145
-
146
- ```javascript
147
- const app = vibe({
148
- logger: {
149
- lifecycle: true,
150
- prettyPrint: process.env.NODE_ENV !== "production",
151
- },
152
- });
153
-
154
- // JSON native bindings
155
- app.log.info({ database: "online" }, "System booting...");
156
- ```
157
-
158
- ### Contextual Sub-Loggers
159
-
160
- Every incoming request dynamically extracts a fast UUID exposed securely on `req.id` natively piping through to `req.log`.
161
-
162
- ```javascript
163
- app.get("/users/:id", (req) => {
164
- req.log.warn("Database lookup constraint fired");
165
- // Production Output -> {"level":40,"time":123,"reqId":"abcd-123", "msg":"..."}
166
-
167
- return { success: true };
168
- });
169
- ```
170
-
171
- ### Central Error Abstraction
172
-
173
- To route an error into the central handler without halting execution via `throw`, simply return an `Error` object from your handler — Vibe intercepts it automatically:
174
-
175
- ```javascript
176
- app.get("/test", (req, res) => {
177
- return new Error("Something went wrong");
178
- });
179
- ```
180
-
181
- ---
182
-
183
- ## 🔌 Plugin System
184
-
185
- Plugins provide encapsulated route groups with optional prefixes:
186
-
187
- ```javascript
188
- // Register a plugin with prefix
189
- await app.register(
190
- async (api) => {
191
- api.get("/status", { status: "ok" }); // GET /api/status
192
- api.get("/health", { healthy: true }); // GET /api/health
193
-
194
- // Plugins can have their own interceptors
195
- api.plugin((req, res) => {
196
- console.log(`[API] ${req.method} ${req.url}`);
197
- });
198
- },
199
- { prefix: "/api" },
200
- );
201
-
202
- // Nested plugins
203
- await app.register(
204
- async (v1) => {
205
- v1.get("/users", { version: 1 }); // GET /api/v1/users
206
- },
207
- { prefix: "/api/v1" },
208
- );
209
- ```
210
-
211
- ---
212
-
213
- ## 🛡️ Interceptors (Middleware)
214
-
215
- Interceptors run before your handler. Return `false` to stop execution.
216
-
217
- ### Single Interceptor
218
-
219
- ```javascript
220
- const authCheck = (req, res) => {
221
- if (!req.headers.authorization) {
222
- res.unauthorized("Token required");
223
- return false; // Stop execution
224
- }
225
- req.user = { id: 1 };
226
- return true; // Continue to handler
227
- };
228
-
229
- app.get("/protected", { intercept: authCheck }, (req) => {
230
- return { user: req.user };
231
- });
232
- ```
233
-
234
- ### Multiple Interceptors
235
-
236
- ```javascript
237
- app.get(
238
- "/admin",
239
- {
240
- intercept: [authCheck, adminCheck, rateLimiter],
241
- },
242
- handler,
243
- );
244
- ```
245
-
246
- ### Global Interceptors
247
-
248
- ```javascript
249
- // Applies to ALL routes
250
- app.plugin((req, res) => {
251
- console.log(`${req.method} ${req.url}`);
252
- });
253
- ```
254
-
255
- ---
256
-
257
- ## 🎨 Decorators
258
-
259
- Extend app, request, or response with custom properties:
260
-
261
- ```javascript
262
- // App decorator - shared config
263
- app.decorate("config", { env: "production", version: "1.0.0" });
264
-
265
- // Access directly on the app
266
- app.get("/version", () => ({ version: app.config.version }));
267
-
268
- // Same in plugins — decorators are spread directly
269
- app.register(
270
- async (api) => {
271
- api.get("/env", () => ({ env: api.config.env }));
272
- },
273
- { prefix: "/api" },
274
- );
275
-
276
- // Request decorator - add to all requests
277
- app.decorateRequest("timestamp", () => Date.now());
278
-
279
- app.get("/time", (req) => ({ timestamp: req.timestamp }));
280
-
281
- // Reply decorator - add methods to response
282
- app.decorateReply("sendSuccess", function (data) {
283
- this.success(data);
284
- });
285
- ```
286
-
287
- ---
288
-
289
- ## 📂 File Uploads
290
-
291
- Vibe supports multipart file uploads with built-in validation and security.
292
-
293
- > **🔒 Security**: File uploads are **disabled by default**. You must explicitly configure `media` options to accept uploads.
294
-
295
- ### Basic Upload
296
-
297
- ```javascript
298
- app.post("/upload", { media: { dest: "uploads" } }, (req) => {
299
- return { files: req.files, body: req.body };
300
- });
301
- ```
302
-
303
- ### Media Options
304
-
305
- ```javascript
306
- app.post(
307
- "/upload",
308
- {
309
- media: {
310
- dest: "uploads", // Subfolder destination
311
- public: true, // Save in public folder (default: true)
312
- maxSize: 5 * 1024 * 1024, // Max file size: 5MB
313
- allowedTypes: ["image/jpeg", "image/png", "image/*"], // Wildcards supported
314
- },
315
- },
316
- handler,
317
- );
318
- ```
319
-
320
- ### Public vs Private Uploads
321
-
322
- **Public uploads** (web-accessible):
323
-
324
- ```javascript
325
- app.post(
326
- "/upload/avatar",
327
- {
328
- media: {
329
- public: true, // ✅ Files accessible via HTTP
330
- dest: "avatars", // Saved to: public/avatars/
331
- },
332
- },
333
- handler,
334
- );
335
-
336
- // Files accessible at: http://yourapp.com/avatars/filename.jpg
337
- ```
338
-
339
- **Private uploads** (server-only access):
340
-
341
- ```javascript
342
- app.post(
343
- "/upload/documents",
344
- {
345
- media: {
346
- public: false, // 🔒 Files NOT web-accessible
347
- dest: "documents", // Saved to: private/documents/
348
- },
349
- },
350
- handler,
351
- );
352
-
353
- // Files only accessible via your backend code (e.g., sendAbsoluteFile)
354
- ```
355
-
356
- ### Uploaded File Object
3
+ **The fastest Node.js web framework with the simplest syntax.**
357
4
 
358
- ```javascript
359
- // req.files contains:
360
- [
361
- {
362
- filename: "image-a7x92b.png", // Saved filename (safe)
363
- originalName: "photo.png", // Original filename
364
- type: "image/png", // MIME type
365
- filePath: "/uploads/image-a7x92b.png", // Full path
366
- size: 102400, // Size in bytes
367
- },
368
- ];
369
- ```
370
-
371
- ### Streaming Uploads (Large Files)
372
-
373
- For large files, use streaming mode to avoid buffering in memory:
374
-
375
- ```javascript
376
- import fs from "fs";
377
-
378
- app.post("/upload-large", { media: { streaming: true } }, (req) => {
379
- req.on("file", (name, stream, info) => {
380
- stream.pipe(fs.createWriteStream(`/uploads/${info.filename}`));
381
- });
382
- return { status: "uploading" };
383
- });
384
- ```
385
-
386
- ### Error Handling
387
-
388
- - **413 Payload Too Large** - File exceeds `maxSize`
389
- - **415 Unsupported Media Type** - File type not in `allowedTypes`
5
+ ![Performance](https://img.shields.io/badge/performance-11,472_RPS-brightgreen)
6
+ ![vs Express](https://img.shields.io/badge/vs_Express-4.7x_faster-blue)
7
+ ![vs Fastify](https://img.shields.io/badge/vs_Fastify-faster-orange)
8
+ ![License](https://img.shields.io/badge/license-MIT-green)
390
9
 
391
10
  ---
392
11
 
393
- ## 🔥 Scalability
394
-
395
- ### Cluster Mode
396
-
397
- Scale across all CPU cores automatically:
398
-
399
- ```javascript
400
- import vibe, { clusterize, isPrimary, getWorkerId } from "vibe-gx";
401
-
402
- clusterize(
403
- () => {
404
- const app = vibe();
405
- app.get("/", `Hello from worker ${getWorkerId()}!`);
406
- app.listen(3000);
407
- },
408
- {
409
- workers: 4, // Number of workers (default: CPU count)
410
- restart: true, // Auto-restart crashed workers
411
- restartDelay: 1000, // Delay before restart (ms)
412
- },
413
- );
414
- ```
415
-
416
- ### LRU Cache
417
-
418
- Built-in response caching with ETag support:
419
-
420
- ```javascript
421
- import vibe, { LRUCache, cacheMiddleware } from "vibe-gx";
422
-
423
- const cache = new LRUCache({
424
- max: 1000, // Maximum entries
425
- ttl: 60000, // TTL in milliseconds (60 seconds)
426
- });
427
-
428
- app.get("/expensive", { intercept: cacheMiddleware(cache) }, async () => {
429
- // This only runs on cache MISS
430
- return await expensiveOperation();
431
- });
432
-
433
- // Manual cache operations
434
- cache.set("key", { data: "value" });
435
- cache.get("key"); // { value, expires, etag }
436
- cache.delete("key");
437
- cache.clear();
438
- ```
439
-
440
- ### Connection Pool
441
-
442
- Generic connection pool for databases:
443
-
444
- ```javascript
445
- import vibe, { createPool } from "vibe-gx";
446
-
447
- const dbPool = createPool({
448
- create: async () => await connectToDatabase(),
449
- destroy: async (conn) => await conn.close(),
450
- validate: (conn) => conn.isAlive(),
451
- min: 2, // Minimum connections
452
- max: 10, // Maximum connections
453
- acquireTimeout: 30000, // Timeout to acquire (ms)
454
- idleTimeout: 60000, // Idle timeout (ms)
455
- });
456
-
457
- app.get("/users", async () => {
458
- return await dbPool.use(async (conn) => {
459
- return await conn.query("SELECT * FROM users");
460
- });
461
- });
462
-
463
- // Pool statistics
464
- console.log(dbPool.stats);
465
- // { available: 5, inUse: 2, waiting: 0, max: 10 }
466
-
467
- // Cleanup on shutdown
468
- process.on("SIGTERM", () => dbPool.close());
469
- ```
470
-
471
- ---
472
-
473
- ## 🔄 Express Middleware Adapter
474
-
475
- Use any Express middleware with the adapter:
476
-
477
- ```javascript
478
- import vibe, { adapt } from "vibe-gx";
479
- import helmet from "helmet";
480
- import compression from "compression";
481
-
482
- app.plugin(adapt(helmet()));
483
- app.plugin(adapt(compression()));
484
- ```
485
-
486
- > **Note:** Vibe ships with a native `cors()` helper. No need to adapt the `cors` npm package.
487
-
488
- ---
489
-
490
- ## 🛡️ Rate Limiting
491
-
492
- Built-in sliding window rate limiter — no external dependencies:
493
-
494
- ```javascript
495
- import vibe, { rateLimit } from "vibe-gx";
496
-
497
- const app = vibe();
498
-
499
- // Global limit: 100 requests per minute per IP
500
- app.plugin(rateLimit({ max: 100, window: 60_000 }));
501
-
502
- // Per-route: tight limit on login (brute force protection)
503
- app.post(
504
- "/auth/login",
505
- { intercept: rateLimit({ max: 5, window: 60_000 }) },
506
- handler,
507
- );
508
- ```
509
-
510
- Sets `X-RateLimit-Limit`, `X-RateLimit-Remaining`, `X-RateLimit-Reset`, and `Retry-After` headers automatically.
511
-
512
- ---
513
-
514
- ## 🌐 CORS
515
-
516
- Built-in CORS with automatic preflight handling — no external dependencies:
517
-
518
- ```javascript
519
- import vibe, { cors } from "vibe-gx";
520
-
521
- const app = vibe();
522
-
523
- app.plugin(
524
- cors({
525
- origin: "https://myapp.com",
526
- credentials: true,
527
- maxAge: 86_400, // cache preflight for 24 hours
528
- }),
529
- );
530
- ```
531
-
532
- Supports wildcard, single origin, array of origins, and dynamic origin functions.
533
-
534
- ---
535
-
536
- ## 🔒 Security
537
-
538
- Built-in protections:
539
-
540
- | Feature | Status |
541
- | :--------------------------------------- | :----: |
542
- | **File upload protection** (opt-in only) | ✅ |
543
- | Path traversal protection | ✅ |
544
- | File type validation | ✅ |
545
- | Body size limits (1MB JSON, 10MB files) | ✅ |
546
- | Error sanitization (production mode) | ✅ |
547
- | Safe filename generation | ✅ |
548
- | Port validation | ✅ |
549
-
550
- ### File Upload Security
551
-
552
- Routes **reject multipart uploads by default** unless `media` is explicitly configured:
553
-
554
- ```javascript
555
- // ❌ This will reject file uploads with 400 Bad Request
556
- app.post("/api/data", (req) => ({ data: req.body }));
557
-
558
- // ✅ This accepts file uploads (explicit opt-in)
559
- app.post(
560
- "/upload",
561
- {
562
- media: {
563
- dest: "uploads",
564
- maxSize: 5 * 1024 * 1024,
565
- allowedTypes: ["image/*", "application/pdf"],
566
- },
567
- },
568
- handler,
569
- );
570
- ```
571
-
572
- This prevents attackers from uploading malicious files to unintended routes.
573
-
574
- Set `NODE_ENV=production` for secure error handling (stack traces hidden).
575
-
576
- ---
577
-
578
- ## ⚡ Schema-Based Serialization
579
-
580
- **Optional** performance boost: Pre-compile JSON serializers for 2-3x faster responses.
581
-
582
- ```javascript
583
- app.get(
584
- "/users/:id",
585
- {
586
- schema: {
587
- response: {
588
- type: "object",
589
- properties: {
590
- id: { type: "number" },
591
- name: { type: "string" },
592
- email: { type: "string" },
593
- active: { type: "boolean" },
594
- },
595
- },
596
- },
597
- },
598
- async (req) => {
599
- const user = await db.getUser(req.params.id);
600
- return user; // Uses pre-compiled serializer (2-3x faster than JSON.stringify)
601
- },
602
- );
603
- ```
604
-
605
- **Benefits:**
606
-
607
- - ✅ 2-3x faster JSON serialization
608
- - ✅ Zero-loop code generation via `new Function()`
609
- - ✅ No `Object.keys()` enumeration
610
- - ✅ Zero runtime type checking
611
- - ✅ Completely optional (routes work without schemas)
612
-
613
- ---
614
-
615
- ### Route Options
616
-
617
- ```javascript
618
- app.post(
619
- "/path",
620
- {
621
- intercept: authMiddleware, // Middleware function(s)
622
- media: {
623
- // File upload config
624
- dest: "uploads",
625
- maxSize: 10 * 1024 * 1024,
626
- allowedTypes: ["image/*"],
627
- },
628
- },
629
- handler,
630
- );
631
- ```
632
-
633
- ## 🛠️ API Reference
634
-
635
- ### Application
636
-
637
- | Method | Description |
638
- | :----------------------------------------------- | :--------------------- |
639
- | `vibe({ logger?: LoggerConfig })` | Initialize app |
640
- | `app.setErrorHandler(fn)` | Override error handler |
641
- | `app.get/post/put/del/patch/head(path, handler)` | Register route |
642
- | `app.listen(port, host?, callback?)` | Start server |
643
- | `app.register(fn, { prefix })` | Register plugin |
644
- | `app.plugin(fn)` | Global interceptor |
645
- | `app.decorate(name, value)` | Add app property |
646
- | `app.decorateRequest(name, value)` | Add to all requests |
647
- | `app.decorateReply(name, value)` | Add to all responses |
648
- | `app.setPublicFolder(path)` | Set static folder |
649
- | `app.logRoutes()` | Log all routes |
650
-
651
- ### Request (`req`)
652
-
653
- | Property | Description |
654
- | :------------ | :------------------------------------------------------- |
655
- | `req.id` | Lazy UUID — generated only on first access |
656
- | `req.log` | Lazy context-bound logger — created only on first access |
657
- | `req.params` | Route parameters (`:id`) |
658
- | `req.query` | Query string (`?page=1`) |
659
- | `req.body` | Parsed JSON/form body |
660
- | `req.files` | Uploaded files (multipart) |
661
- | `req.ip` | Real client IP — proxy-aware (`x-forwarded-for` first) |
662
- | `req.method` | HTTP method |
663
- | `req.url` | Request URL (pathname only, query stripped) |
664
- | `req.headers` | Request headers |
665
-
666
- ### Response (`res`)
667
-
668
- | Method | Description |
669
- | :---------------------------------- | :--------------------------- |
670
- | `res.json(data)` | Send JSON |
671
- | `res.send(data)` | Send any response |
672
- | `res.status(code)` | Set status (chainable) |
673
- | `res.redirect(url, code?)` | Redirect (302) |
674
- | `res.sendFile(path)` | Send file from public folder |
675
- | `res.sendAbsoluteFile(path, opts?)` | Send file from any path |
676
- | `res.sendHtml(filename)` | Send HTML file |
677
- | `res.success(data?, msg?)` | 200 OK |
678
- | `res.created(data?, msg?)` | 201 Created |
679
- | `res.badRequest(msg?, errors?)` | 400 Bad Request |
680
- | `res.unauthorized(msg?)` | 401 Unauthorized |
681
- | `res.forbidden(msg?)` | 403 Forbidden |
682
- | `res.notFound(msg?)` | 404 Not Found |
683
- | `res.conflict(msg?)` | 409 Conflict |
684
- | `res.serverError(err?)` | 500 Server Error |
685
-
686
- ### Cluster Utilities
687
-
688
- | Function | Description |
689
- | :--------------------- | :---------------------------- |
690
- | `clusterize(fn, opts)` | Start in cluster mode |
691
- | `isPrimary()` | Check if primary process |
692
- | `isWorker()` | Check if worker process |
693
- | `getWorkerId()` | Get worker ID (0 for primary) |
694
- | `getWorkerCount()` | Get number of active workers |
695
-
696
- ### Cache Utilities
697
-
698
- | Class/Function | Description |
699
- | :-------------------------- | :------------------------ |
700
- | `new LRUCache(opts)` | Create LRU cache instance |
701
- | `cacheMiddleware(cache)` | Create cache interceptor |
702
- | `LRUCache.key(method, url)` | Generate cache key |
703
- | `LRUCache.etag(value)` | Generate ETag |
704
-
705
- ### Pool Utilities
706
-
707
- | Class/Function | Description |
708
- | :----------------- | :--------------------- |
709
- | `createPool(opts)` | Create connection pool |
710
- | `pool.acquire()` | Acquire resource |
711
- | `pool.release(r)` | Release resource |
712
- | `pool.use(fn)` | Use with auto-release |
713
- | `pool.close()` | Close pool |
714
- | `pool.stats` | Get pool statistics |
715
-
716
- ### Rate Limit Utilities
717
-
718
- | Function | Description |
719
- | :---------------- | :----------------------------------- |
720
- | `rateLimit(opts)` | Create a sliding window rate limiter |
721
-
722
- ### CORS Utilities
723
-
724
- | Function | Description |
725
- | :------------ | :------------------------ |
726
- | `cors(opts?)` | Create a CORS interceptor |
727
-
728
- ---
729
-
730
- ## 📊 Benchmarks
731
-
732
- Run benchmarks yourself:
733
-
734
- ```bash
735
- npm run benchmark
736
- ```
737
-
738
- Tested under overload (20,000 requests × 200 concurrent):
739
-
740
- ```
741
- Framework | JSON RPS | vs Express | vs Fastify
742
- -------------|-------------|------------|------------
743
- Vibe | 11,472 | 4.7x ✅ | 1.01x ✅
744
- Fastify | 11,334 | 4.7x | baseline
745
- Hono | 7,351 | 3.0x | 0.6x
746
- Express | 2,421 | baseline | 0.2x
747
- ```
748
-
749
- ---
750
-
751
- ## 🧪 Testing
752
-
753
- ```bash
754
- # Run all tests
755
- npm test
12
+ ## 📖 Complete Documentation
756
13
 
757
- # Run comprehensive tests
758
- npm run test:all
14
+ Vibe-GX features, API references, tutorials, and guidelines are available on the official documentation website:
759
15
 
760
- # Run benchmarks
761
- npm run benchmark
762
- ```
16
+ 👉 **[genesix.hkai.site/vibegx](https://genesix.hkai.site/vibegx)**
763
17
 
764
18
  ---
765
19
 
766
20
  ## 📝 License
767
21
 
768
- Part of the **GeNeSix** brand. Created by **Nnamdi "Joe" Amaga**.
22
+ Part of the **GeNeSix** brand. Created by **NA**.
769
23
 
770
24
  MIT License.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibe-gx",
3
- "version": "4.2.0",
3
+ "version": "4.2.1",
4
4
  "description": "A lightweight, high-performance Node.js web framework.",
5
5
  "type": "module",
6
6
  "main": "vibe.js",
@@ -253,6 +253,48 @@ const vibeResponseMethods = {
253
253
  this.end(RESPONSES.serverError);
254
254
  },
255
255
 
256
+ /**
257
+ * Sets a cookie on the response.
258
+ * Chainable — supports multiple cookies: res.setCookie("a","1").setCookie("b","2")
259
+ * @param {string} name - Cookie name
260
+ * @param {string} value - Cookie value (will be URI-encoded)
261
+ * @param {Object} [options]
262
+ * @param {number} [options.maxAge] - Max age in seconds
263
+ * @param {Date} [options.expires] - Expiry date
264
+ * @param {string} [options.path="/"] - Cookie path
265
+ * @param {string} [options.domain] - Cookie domain
266
+ * @param {boolean} [options.secure] - HTTPS only
267
+ * @param {boolean} [options.httpOnly] - Inaccessible to JS
268
+ * @param {"Strict"|"Lax"|"None"} [options.sameSite] - SameSite policy
269
+ * @returns {this}
270
+ */
271
+ setCookie(name, value, options = {}) {
272
+ let cookie = `${name}=${encodeURIComponent(value)}`;
273
+ if (options.maxAge != null) cookie += `; Max-Age=${options.maxAge}`;
274
+ if (options.expires instanceof Date) cookie += `; Expires=${options.expires.toUTCString()}`;
275
+ cookie += `; Path=${options.path ?? "/"}`;
276
+ if (options.domain) cookie += `; Domain=${options.domain}`;
277
+ if (options.secure) cookie += "; Secure";
278
+ if (options.httpOnly) cookie += "; HttpOnly";
279
+ if (options.sameSite) cookie += `; SameSite=${options.sameSite}`;
280
+
281
+ const existing = this.getHeader("Set-Cookie");
282
+ if (Array.isArray(existing)) this.setHeader("Set-Cookie", [...existing, cookie]);
283
+ else if (existing) this.setHeader("Set-Cookie", [existing, cookie]);
284
+ else this.setHeader("Set-Cookie", cookie);
285
+ return this;
286
+ },
287
+
288
+ /**
289
+ * Clears a cookie by immediately expiring it.
290
+ * @param {string} name - Cookie name
291
+ * @param {Object} [options] - Same options as setCookie (except maxAge/expires)
292
+ * @returns {this}
293
+ */
294
+ clearCookie(name, options = {}) {
295
+ return this.setCookie(name, "", { ...options, maxAge: 0, expires: new Date(0) });
296
+ },
297
+
256
298
  /**
257
299
  * Redirects the client to another URL.
258
300
  * @param {string} url
@@ -110,6 +110,25 @@ async function server(options, port, host, callback) {
110
110
  configurable: true,
111
111
  });
112
112
 
113
+ // Lazy req.cookies — parsed once on first access, zero cost if unused
114
+ Object.defineProperty(req, "cookies", {
115
+ get() {
116
+ if (this._parsedCookies !== undefined) return this._parsedCookies;
117
+ const header = this.headers["cookie"];
118
+ if (!header) return (this._parsedCookies = {});
119
+ const cookies = {};
120
+ for (const pair of header.split(";")) {
121
+ const idx = pair.indexOf("=");
122
+ if (idx < 0) continue;
123
+ const key = pair.slice(0, idx).trim();
124
+ const val = pair.slice(idx + 1).trim();
125
+ if (key) cookies[key] = decodeURIComponent(val);
126
+ }
127
+ return (this._parsedCookies = cookies);
128
+ },
129
+ configurable: true,
130
+ });
131
+
113
132
  if (options.loggerConfig && options.loggerConfig.lifecycle) {
114
133
  req.startTime = Date.now();
115
134
 
package/vibe.d.ts CHANGED
@@ -162,6 +162,26 @@ export interface RouteOptions {
162
162
  schema?: SchemaOptions;
163
163
  }
164
164
 
165
+ /**
166
+ * Options for cookie serialization.
167
+ */
168
+ export interface CookieOptions {
169
+ /** Max age in seconds. Takes priority over expires. */
170
+ maxAge?: number;
171
+ /** Expiry date */
172
+ expires?: Date;
173
+ /** Cookie path. Default: "/" */
174
+ path?: string;
175
+ /** Cookie domain */
176
+ domain?: string;
177
+ /** Send only over HTTPS */
178
+ secure?: boolean;
179
+ /** Inaccessible to client-side JavaScript */
180
+ httpOnly?: boolean;
181
+ /** SameSite policy */
182
+ sameSite?: "Strict" | "Lax" | "None";
183
+ }
184
+
165
185
  /**
166
186
  * Options for registering a plugin.
167
187
  */
@@ -239,6 +259,8 @@ export interface VibeRequest extends IncomingMessage {
239
259
  files?: UploadedFile[];
240
260
  /** Real client IP — first entry from x-forwarded-for, x-real-ip, or socket address */
241
261
  ip?: string;
262
+ /** Parsed cookies from the Cookie header (lazily evaluated on first access) */
263
+ cookies: Record<string, string>;
242
264
  /** Automatically generated UUID for the request lifecycle */
243
265
  id: string;
244
266
  /** Context-bound logger automatically stamped with the req.id constraint */
@@ -264,6 +286,21 @@ export interface VibeResponse extends ServerResponse {
264
286
  sendHtml: (filename: string) => void;
265
287
  redirect: (url: string, code?: number) => void;
266
288
 
289
+ /**
290
+ * Sets a cookie on the response. Chainable.
291
+ * @example
292
+ * res.setCookie("token", "abc", { httpOnly: true, secure: true, maxAge: 3600 });
293
+ * res.setCookie("a", "1").setCookie("b", "2"); // multiple cookies
294
+ */
295
+ setCookie(name: string, value: string, options?: CookieOptions): VibeResponse;
296
+
297
+ /**
298
+ * Clears a cookie by expiring it immediately.
299
+ * @example
300
+ * res.clearCookie("token");
301
+ */
302
+ clearCookie(name: string, options?: Omit<CookieOptions, "maxAge" | "expires">): VibeResponse;
303
+
267
304
  /** Sends a 200 OK response with a success message */
268
305
  success: (data?: any, message?: string) => void;
269
306
  /** Sends a 201 Created response */