vibe-gx 1.0.7 โ 3.0.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 +470 -117
- package/package.json +8 -9
- package/utils/core/compile-serializer.js +171 -0
- package/utils/core/parser.js +16 -2
- package/utils/core/response.js +139 -134
- package/utils/core/server.js +80 -31
- package/utils/helpers/adapt.js +113 -29
- package/utils/native.js +95 -0
- package/vibe.d.ts +150 -0
- package/vibe.js +27 -8
package/README.md
CHANGED
|
@@ -2,159 +2,305 @@
|
|
|
2
2
|
<img src="./assets/vlogo.png" alt="Vibe Logo" width="180" />
|
|
3
3
|
<h1>Vibe</h1>
|
|
4
4
|
<p>
|
|
5
|
-
<b>
|
|
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-14,687_RPS-brightgreen" alt="Performance" />
|
|
9
|
+
<img src="https://img.shields.io/badge/vs_Express-2.2x_faster-blue" alt="vs Express" />
|
|
10
|
+
<img src="https://img.shields.io/badge/vs_Fastify-30%25_faster-orange" alt="vs Fastify" />
|
|
11
|
+
<img src="https://img.shields.io/badge/license-MIT-green" alt="License" />
|
|
6
12
|
</p>
|
|
7
13
|
</div>
|
|
8
14
|
|
|
9
15
|
---
|
|
10
16
|
|
|
11
|
-
|
|
17
|
+
## ๐ฆ Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install vibe-gx
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Optional: Build C++ Native Module
|
|
24
|
+
|
|
25
|
+
For maximum performance, build the optional C++ native module:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
npm run build:native
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
> If the build fails, Vibe automatically falls back to pure JavaScript with zero configuration.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## ๐ Why Vibe?
|
|
36
|
+
|
|
37
|
+
| Metric | Vibe | Express | Fastify |
|
|
38
|
+
| :------------------------ | :------------: | :-------: | :--------: |
|
|
39
|
+
| **JSON Performance** | **14,687 RPS** | 6,629 RPS | 11,289 RPS |
|
|
40
|
+
| **Install Size** | **~280 KB** | ~5 MB | ~4 MB |
|
|
41
|
+
| **Lines for Hello World** | 3 | 5 | 6 |
|
|
42
|
+
| **Dependencies** | 2 | 30+ | 15+ |
|
|
43
|
+
| **Built-in Clustering** | โ
| โ | โ |
|
|
44
|
+
| **Built-in Caching** | โ
| โ | โ |
|
|
45
|
+
| **C++ Optimizations** | โ
| โ | โ |
|
|
12
46
|
|
|
13
|
-
> **
|
|
47
|
+
> **Vibe is faster than Fastify, simpler than Express, and 14-18x smaller than both.**
|
|
48
|
+
|
|
49
|
+
---
|
|
14
50
|
|
|
15
51
|
## โก Features
|
|
16
52
|
|
|
17
|
-
| Feature | Description
|
|
18
|
-
| :----------------------- |
|
|
19
|
-
| ๐ **
|
|
20
|
-
|
|
|
21
|
-
|
|
|
22
|
-
|
|
|
23
|
-
|
|
|
24
|
-
|
|
|
25
|
-
|
|
|
53
|
+
| Feature | Description |
|
|
54
|
+
| :----------------------- | :--------------------------------------------------------- |
|
|
55
|
+
| ๐ **C++ Native Module** | JSON stringify & URL parsing in C++ |
|
|
56
|
+
| ๐ฏ **Hybrid Router** | O(1) static + O(log n) Trie routing |
|
|
57
|
+
| ๐ **Plugin System** | Fastify-style `register()` with encapsulation |
|
|
58
|
+
| ๐จ **Decorators** | Extend app, request, and response |
|
|
59
|
+
| โก **Cluster Mode** | Built-in multi-process scaling |
|
|
60
|
+
| ๐พ **LRU Cache** | Built-in response caching with ETag |
|
|
61
|
+
| ๐ **Connection Pool** | Generic pool for databases |
|
|
62
|
+
| ๐ **File Uploads** | Multipart uploads with size/type validation |
|
|
63
|
+
| ๐ **Streaming** | Large file uploads without buffering |
|
|
64
|
+
| ๐ **Security** | Path traversal protection, body limits, error sanitization |
|
|
65
|
+
| ๐ **Express Adapter** | Use any Express middleware with `adapt()` |
|
|
66
|
+
|
|
67
|
+
---
|
|
26
68
|
|
|
27
69
|
## ๐ Quick Start
|
|
28
70
|
|
|
29
71
|
```javascript
|
|
30
|
-
import vibe from "
|
|
72
|
+
import vibe from "vibe-gx";
|
|
31
73
|
|
|
32
74
|
const app = vibe();
|
|
33
75
|
|
|
76
|
+
// Direct value - no callback needed!
|
|
34
77
|
app.get("/", "Hello Vibe!");
|
|
35
|
-
|
|
78
|
+
|
|
79
|
+
// Auto JSON response - just return an object
|
|
80
|
+
app.get("/users/:id", (req) => ({ userId: req.params.id }));
|
|
36
81
|
|
|
37
82
|
app.listen(3000);
|
|
38
83
|
```
|
|
39
84
|
|
|
85
|
+
**That's it.** No `res.send()`, no `res.json()` - just return data.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
40
89
|
## ๐ Core API
|
|
41
90
|
|
|
42
91
|
### Routes
|
|
43
92
|
|
|
93
|
+
Vibe supports all standard HTTP methods with a clean, flexible syntax:
|
|
94
|
+
|
|
44
95
|
```javascript
|
|
45
|
-
|
|
46
|
-
app.
|
|
47
|
-
|
|
96
|
+
// String response
|
|
97
|
+
app.get("/", "Hello World");
|
|
98
|
+
|
|
99
|
+
// JSON response (just return an object)
|
|
100
|
+
app.get("/json", { message: "Hello" });
|
|
101
|
+
|
|
102
|
+
// Handler function with request access
|
|
103
|
+
app.get("/users/:id", (req) => ({ id: req.params.id }));
|
|
104
|
+
|
|
105
|
+
// Multiple route parameters
|
|
106
|
+
app.get("/posts/:postId/comments/:commentId", (req) => ({
|
|
107
|
+
postId: req.params.postId,
|
|
108
|
+
commentId: req.params.commentId,
|
|
109
|
+
}));
|
|
110
|
+
|
|
111
|
+
// With options (interceptors, file uploads)
|
|
112
|
+
app.post("/protected", { intercept: authCheck }, handler);
|
|
113
|
+
|
|
114
|
+
// All HTTP methods
|
|
115
|
+
app.get("/");
|
|
116
|
+
app.post("/");
|
|
117
|
+
app.put("/");
|
|
118
|
+
app.del("/"); // DELETE
|
|
119
|
+
app.patch("/");
|
|
120
|
+
app.head("/");
|
|
48
121
|
```
|
|
49
122
|
|
|
50
|
-
###
|
|
123
|
+
### Query Parameters
|
|
51
124
|
|
|
52
125
|
```javascript
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
);
|
|
126
|
+
// GET /search?q=hello&page=2
|
|
127
|
+
app.get("/search", (req) => ({
|
|
128
|
+
query: req.query.q, // "hello"
|
|
129
|
+
page: req.query.page, // "2"
|
|
130
|
+
}));
|
|
59
131
|
```
|
|
60
132
|
|
|
61
|
-
###
|
|
133
|
+
### Request Body
|
|
62
134
|
|
|
63
135
|
```javascript
|
|
64
|
-
app.
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
this.success(d);
|
|
136
|
+
app.post("/users", (req) => {
|
|
137
|
+
const { name, email } = req.body;
|
|
138
|
+
return { created: { name, email } };
|
|
68
139
|
});
|
|
69
140
|
```
|
|
70
141
|
|
|
71
142
|
---
|
|
72
143
|
|
|
73
|
-
##
|
|
144
|
+
## ๐ Plugins (Fastify-style)
|
|
74
145
|
|
|
75
|
-
|
|
146
|
+
Plugins provide encapsulated route groups with optional prefixes:
|
|
76
147
|
|
|
77
148
|
```javascript
|
|
78
|
-
|
|
149
|
+
// Register a plugin with prefix
|
|
150
|
+
await app.register(
|
|
151
|
+
async (api) => {
|
|
152
|
+
api.get("/status", { status: "ok" }); // GET /api/status
|
|
153
|
+
api.get("/health", { healthy: true }); // GET /api/health
|
|
154
|
+
|
|
155
|
+
// Plugins can have their own interceptors
|
|
156
|
+
api.plugin((req, res) => {
|
|
157
|
+
console.log(`[API] ${req.method} ${req.url}`);
|
|
158
|
+
});
|
|
159
|
+
},
|
|
160
|
+
{ prefix: "/api" },
|
|
161
|
+
);
|
|
79
162
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
app.listen(3000);
|
|
163
|
+
// Nested plugins
|
|
164
|
+
await app.register(
|
|
165
|
+
async (v1) => {
|
|
166
|
+
v1.get("/users", { version: 1 }); // GET /api/v1/users
|
|
85
167
|
},
|
|
86
|
-
{
|
|
168
|
+
{ prefix: "/api/v1" },
|
|
87
169
|
);
|
|
88
170
|
```
|
|
89
171
|
|
|
90
|
-
|
|
172
|
+
---
|
|
91
173
|
|
|
92
|
-
|
|
93
|
-
import vibe, { LRUCache, cacheMiddleware } from "./vibe.js";
|
|
174
|
+
## ๐ก๏ธ Interceptors (Middleware)
|
|
94
175
|
|
|
95
|
-
|
|
96
|
-
const cache = new LRUCache({ max: 1000, ttl: 60000 });
|
|
176
|
+
Interceptors run before your handler. Return `false` to stop execution.
|
|
97
177
|
|
|
98
|
-
|
|
99
|
-
|
|
178
|
+
### Single Interceptor
|
|
179
|
+
|
|
180
|
+
```javascript
|
|
181
|
+
const authCheck = (req, res) => {
|
|
182
|
+
if (!req.headers.authorization) {
|
|
183
|
+
res.unauthorized("Token required");
|
|
184
|
+
return false; // Stop execution
|
|
185
|
+
}
|
|
186
|
+
req.user = { id: 1 };
|
|
187
|
+
return true; // Continue to handler
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
app.get("/protected", { intercept: authCheck }, (req) => {
|
|
191
|
+
return { user: req.user };
|
|
100
192
|
});
|
|
101
193
|
```
|
|
102
194
|
|
|
103
|
-
###
|
|
195
|
+
### Multiple Interceptors
|
|
104
196
|
|
|
105
197
|
```javascript
|
|
106
|
-
|
|
198
|
+
app.get(
|
|
199
|
+
"/admin",
|
|
200
|
+
{
|
|
201
|
+
intercept: [authCheck, adminCheck, rateLimiter],
|
|
202
|
+
},
|
|
203
|
+
handler,
|
|
204
|
+
);
|
|
205
|
+
```
|
|
107
206
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
207
|
+
### Global Interceptors
|
|
208
|
+
|
|
209
|
+
```javascript
|
|
210
|
+
// Applies to ALL routes
|
|
211
|
+
app.plugin((req, res) => {
|
|
212
|
+
console.log(`${req.method} ${req.url}`);
|
|
112
213
|
});
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
---
|
|
113
217
|
|
|
114
|
-
|
|
218
|
+
## ๐จ Decorators
|
|
115
219
|
|
|
116
|
-
app
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
220
|
+
Extend app, request, or response with custom properties:
|
|
221
|
+
|
|
222
|
+
```javascript
|
|
223
|
+
// App decorator - shared config
|
|
224
|
+
app.decorate("config", { env: "production", version: "1.0.0" });
|
|
225
|
+
|
|
226
|
+
// Access via app.decorators in main app
|
|
227
|
+
app.get("/version", () => ({ version: app.decorators.config.version }));
|
|
228
|
+
|
|
229
|
+
// In plugins, decorators are spread directly (no .decorators)
|
|
230
|
+
app.register(
|
|
231
|
+
async (api) => {
|
|
232
|
+
api.get("/env", () => ({ env: api.config.env })); // Direct access
|
|
233
|
+
},
|
|
234
|
+
{ prefix: "/api" },
|
|
235
|
+
);
|
|
236
|
+
|
|
237
|
+
// Request decorator - add to all requests
|
|
238
|
+
app.decorateRequest("timestamp", () => Date.now());
|
|
239
|
+
|
|
240
|
+
app.get("/time", (req) => ({ timestamp: req.timestamp }));
|
|
241
|
+
|
|
242
|
+
// Reply decorator - add methods to response
|
|
243
|
+
app.decorateReply("sendSuccess", function (data) {
|
|
244
|
+
this.success(data);
|
|
120
245
|
});
|
|
121
246
|
```
|
|
122
247
|
|
|
123
|
-
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## ๐ File Uploads
|
|
124
251
|
|
|
125
|
-
Vibe supports multipart file uploads with built-in validation.
|
|
252
|
+
Vibe supports multipart file uploads with built-in validation and security.
|
|
126
253
|
|
|
127
|
-
|
|
254
|
+
> **๐ Security**: File uploads are **disabled by default**. You must explicitly configure `media` options to accept uploads.
|
|
255
|
+
|
|
256
|
+
### Basic Upload
|
|
128
257
|
|
|
129
258
|
```javascript
|
|
130
|
-
app.post("/upload", { media: { dest: "uploads" } }, (req
|
|
259
|
+
app.post("/upload", { media: { dest: "uploads" } }, (req) => {
|
|
131
260
|
return { files: req.files, body: req.body };
|
|
132
261
|
});
|
|
133
262
|
```
|
|
134
263
|
|
|
135
|
-
|
|
264
|
+
### Media Options
|
|
136
265
|
|
|
137
266
|
```javascript
|
|
138
267
|
app.post(
|
|
139
268
|
"/upload",
|
|
140
269
|
{
|
|
141
270
|
media: {
|
|
142
|
-
dest: "uploads", //
|
|
271
|
+
dest: "uploads", // Subfolder destination
|
|
143
272
|
public: true, // Save inside public folder (default: true)
|
|
144
273
|
maxSize: 5 * 1024 * 1024, // Max file size: 5MB
|
|
145
|
-
allowedTypes: ["image/jpeg", "image/png", "
|
|
274
|
+
allowedTypes: ["image/jpeg", "image/png", "image/*"], // Wildcards supported
|
|
146
275
|
},
|
|
147
276
|
},
|
|
148
277
|
handler,
|
|
149
278
|
);
|
|
150
279
|
```
|
|
151
280
|
|
|
152
|
-
|
|
281
|
+
### Uploaded File Object
|
|
282
|
+
|
|
283
|
+
```javascript
|
|
284
|
+
// req.files contains:
|
|
285
|
+
[
|
|
286
|
+
{
|
|
287
|
+
filename: "image-a7x92b.png", // Saved filename (safe)
|
|
288
|
+
originalName: "photo.png", // Original filename
|
|
289
|
+
type: "image/png", // MIME type
|
|
290
|
+
filePath: "/uploads/image-a7x92b.png", // Full path
|
|
291
|
+
size: 102400, // Size in bytes
|
|
292
|
+
},
|
|
293
|
+
];
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Streaming Uploads (Large Files)
|
|
297
|
+
|
|
298
|
+
For large files, use streaming mode to avoid buffering in memory:
|
|
153
299
|
|
|
154
300
|
```javascript
|
|
155
301
|
import fs from "fs";
|
|
156
302
|
|
|
157
|
-
app.post("/upload", { media: { streaming: true } }, (req
|
|
303
|
+
app.post("/upload-large", { media: { streaming: true } }, (req) => {
|
|
158
304
|
req.on("file", (name, stream, info) => {
|
|
159
305
|
stream.pipe(fs.createWriteStream(`/uploads/${info.filename}`));
|
|
160
306
|
});
|
|
@@ -162,65 +308,186 @@ app.post("/upload", { media: { streaming: true } }, (req, res) => {
|
|
|
162
308
|
});
|
|
163
309
|
```
|
|
164
310
|
|
|
165
|
-
|
|
311
|
+
### Error Handling
|
|
312
|
+
|
|
313
|
+
- **413 Payload Too Large** - File exceeds `maxSize`
|
|
314
|
+
- **415 Unsupported Media Type** - File type not in `allowedTypes`
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
## ๐ฅ Scalability
|
|
319
|
+
|
|
320
|
+
### Cluster Mode
|
|
321
|
+
|
|
322
|
+
Scale across all CPU cores automatically:
|
|
166
323
|
|
|
167
324
|
```javascript
|
|
168
|
-
|
|
169
|
-
|
|
325
|
+
import vibe, { clusterize, isPrimary, getWorkerId } from "vibe-gx";
|
|
326
|
+
|
|
327
|
+
clusterize(
|
|
328
|
+
() => {
|
|
329
|
+
const app = vibe();
|
|
330
|
+
app.get("/", `Hello from worker ${getWorkerId()}!`);
|
|
331
|
+
app.listen(3000);
|
|
332
|
+
},
|
|
170
333
|
{
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
filePath: "/uploads/image.png", // Full path
|
|
175
|
-
size: 102400, // Size in bytes
|
|
334
|
+
workers: 4, // Number of workers (default: CPU count)
|
|
335
|
+
restart: true, // Auto-restart crashed workers
|
|
336
|
+
restartDelay: 1000, // Delay before restart (ms)
|
|
176
337
|
},
|
|
177
|
-
|
|
338
|
+
);
|
|
178
339
|
```
|
|
179
340
|
|
|
180
|
-
|
|
341
|
+
### LRU Cache
|
|
181
342
|
|
|
182
|
-
|
|
343
|
+
Built-in response caching with ETag support:
|
|
183
344
|
|
|
184
|
-
|
|
345
|
+
```javascript
|
|
346
|
+
import vibe, { LRUCache, cacheMiddleware } from "vibe-gx";
|
|
185
347
|
|
|
186
|
-
|
|
348
|
+
const cache = new LRUCache({
|
|
349
|
+
max: 1000, // Maximum entries
|
|
350
|
+
ttl: 60000, // TTL in milliseconds (60 seconds)
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
app.get("/expensive", { intercept: cacheMiddleware(cache) }, async () => {
|
|
354
|
+
// This only runs on cache MISS
|
|
355
|
+
return await expensiveOperation();
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
// Manual cache operations
|
|
359
|
+
cache.set("key", { data: "value" });
|
|
360
|
+
cache.get("key"); // { value, expires, etag }
|
|
361
|
+
cache.delete("key");
|
|
362
|
+
cache.clear();
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
### Connection Pool
|
|
366
|
+
|
|
367
|
+
Generic connection pool for databases:
|
|
187
368
|
|
|
188
369
|
```javascript
|
|
189
|
-
|
|
190
|
-
if (!req.headers.authorization) {
|
|
191
|
-
res.unauthorized("Token required");
|
|
192
|
-
return false;
|
|
193
|
-
}
|
|
194
|
-
req.user = { id: 1 };
|
|
195
|
-
return true;
|
|
196
|
-
};
|
|
370
|
+
import vibe, { createPool } from "vibe-gx";
|
|
197
371
|
|
|
198
|
-
|
|
199
|
-
|
|
372
|
+
const dbPool = createPool({
|
|
373
|
+
create: async () => await connectToDatabase(),
|
|
374
|
+
destroy: async (conn) => await conn.close(),
|
|
375
|
+
validate: (conn) => conn.isAlive(),
|
|
376
|
+
min: 2, // Minimum connections
|
|
377
|
+
max: 10, // Maximum connections
|
|
378
|
+
acquireTimeout: 30000, // Timeout to acquire (ms)
|
|
379
|
+
idleTimeout: 60000, // Idle timeout (ms)
|
|
380
|
+
});
|
|
381
|
+
|
|
382
|
+
app.get("/users", async () => {
|
|
383
|
+
return await dbPool.use(async (conn) => {
|
|
384
|
+
return await conn.query("SELECT * FROM users");
|
|
385
|
+
});
|
|
200
386
|
});
|
|
387
|
+
|
|
388
|
+
// Pool statistics
|
|
389
|
+
console.log(dbPool.stats);
|
|
390
|
+
// { available: 5, inUse: 2, waiting: 0, max: 10 }
|
|
391
|
+
|
|
392
|
+
// Cleanup on shutdown
|
|
393
|
+
process.on("SIGTERM", () => dbPool.close());
|
|
201
394
|
```
|
|
202
395
|
|
|
203
|
-
|
|
396
|
+
---
|
|
397
|
+
|
|
398
|
+
## ๐ Express Middleware Adapter
|
|
399
|
+
|
|
400
|
+
Use any Express middleware with the adapter:
|
|
204
401
|
|
|
205
402
|
```javascript
|
|
206
|
-
|
|
207
|
-
|
|
403
|
+
import { adapt } from "vibe-gx/utils/helpers/adapt.js";
|
|
404
|
+
import cors from "cors";
|
|
405
|
+
import helmet from "helmet";
|
|
406
|
+
import compression from "compression";
|
|
407
|
+
|
|
408
|
+
app.plugin(adapt(cors()));
|
|
409
|
+
app.plugin(adapt(helmet()));
|
|
410
|
+
app.plugin(adapt(compression()));
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
---
|
|
414
|
+
|
|
415
|
+
## ๐ Security
|
|
416
|
+
|
|
417
|
+
Built-in protections:
|
|
418
|
+
|
|
419
|
+
| Feature | Status |
|
|
420
|
+
| :--------------------------------------- | :----: |
|
|
421
|
+
| **File upload protection** (opt-in only) | โ
|
|
|
422
|
+
| Path traversal protection | โ
|
|
|
423
|
+
| File type validation | โ
|
|
|
424
|
+
| Body size limits (1MB JSON, 10MB files) | โ
|
|
|
425
|
+
| Error sanitization (production mode) | โ
|
|
|
426
|
+
| Safe filename generation | โ
|
|
|
427
|
+
| Port validation | โ
|
|
|
428
|
+
|
|
429
|
+
### File Upload Security
|
|
430
|
+
|
|
431
|
+
Routes **reject multipart uploads by default** unless `media` is explicitly configured:
|
|
432
|
+
|
|
433
|
+
```javascript
|
|
434
|
+
// โ This will reject file uploads with 400 Bad Request
|
|
435
|
+
app.post("/api/data", (req) => ({ data: req.body }));
|
|
436
|
+
|
|
437
|
+
// โ
This accepts file uploads (explicit opt-in)
|
|
438
|
+
app.post(
|
|
439
|
+
"/upload",
|
|
208
440
|
{
|
|
209
|
-
|
|
441
|
+
media: {
|
|
442
|
+
dest: "uploads",
|
|
443
|
+
maxSize: 5 * 1024 * 1024,
|
|
444
|
+
allowedTypes: ["image/*", "application/pdf"],
|
|
445
|
+
},
|
|
210
446
|
},
|
|
211
447
|
handler,
|
|
212
448
|
);
|
|
213
449
|
```
|
|
214
450
|
|
|
215
|
-
|
|
451
|
+
This prevents attackers from uploading malicious files to unintended routes.
|
|
452
|
+
|
|
453
|
+
Set `NODE_ENV=production` for secure error handling (stack traces hidden).
|
|
454
|
+
|
|
455
|
+
---
|
|
456
|
+
|
|
457
|
+
## โก Schema-Based Serialization
|
|
458
|
+
|
|
459
|
+
**Optional** performance boost: Pre-compile JSON serializers for 2-3x faster responses.
|
|
216
460
|
|
|
217
461
|
```javascript
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
462
|
+
app.get(
|
|
463
|
+
"/users/:id",
|
|
464
|
+
{
|
|
465
|
+
schema: {
|
|
466
|
+
response: {
|
|
467
|
+
type: "object",
|
|
468
|
+
properties: {
|
|
469
|
+
id: { type: "number" },
|
|
470
|
+
name: { type: "string" },
|
|
471
|
+
email: { type: "string" },
|
|
472
|
+
active: { type: "boolean" },
|
|
473
|
+
},
|
|
474
|
+
},
|
|
475
|
+
},
|
|
476
|
+
},
|
|
477
|
+
async (req) => {
|
|
478
|
+
const user = await db.getUser(req.params.id);
|
|
479
|
+
return user; // Uses pre-compiled serializer (2-3x faster than JSON.stringify)
|
|
480
|
+
},
|
|
481
|
+
);
|
|
222
482
|
```
|
|
223
483
|
|
|
484
|
+
**Benefits:**
|
|
485
|
+
|
|
486
|
+
- โ
2-3x faster JSON serialization
|
|
487
|
+
- โ
No `Object.keys()` enumeration
|
|
488
|
+
- โ
Zero runtime type checking
|
|
489
|
+
- โ
Completely optional (routes work without schemas)
|
|
490
|
+
|
|
224
491
|
---
|
|
225
492
|
|
|
226
493
|
### Route Options
|
|
@@ -245,34 +512,120 @@ app.post(
|
|
|
245
512
|
|
|
246
513
|
### Application
|
|
247
514
|
|
|
248
|
-
| Method
|
|
249
|
-
|
|
|
250
|
-
| `app.
|
|
251
|
-
| `app.
|
|
252
|
-
| `app.
|
|
253
|
-
| `app.plugin(fn)`
|
|
515
|
+
| Method | Description |
|
|
516
|
+
| :----------------------------------------------- | :------------------- |
|
|
517
|
+
| `app.get/post/put/del/patch/head(path, handler)` | Register route |
|
|
518
|
+
| `app.listen(port, host?, callback?)` | Start server |
|
|
519
|
+
| `app.register(fn, { prefix })` | Register plugin |
|
|
520
|
+
| `app.plugin(fn)` | Global interceptor |
|
|
521
|
+
| `app.decorate(name, value)` | Add app property |
|
|
522
|
+
| `app.decorateRequest(name, value)` | Add to all requests |
|
|
523
|
+
| `app.decorateReply(name, value)` | Add to all responses |
|
|
524
|
+
| `app.setPublicFolder(path)` | Set static folder |
|
|
525
|
+
| `app.logRoutes()` | Log all routes |
|
|
254
526
|
|
|
255
527
|
### Request (`req`)
|
|
256
528
|
|
|
257
|
-
| Property
|
|
258
|
-
|
|
|
259
|
-
| `req.params`
|
|
260
|
-
| `req.query`
|
|
261
|
-
| `req.body`
|
|
262
|
-
| `req.files`
|
|
529
|
+
| Property | Description |
|
|
530
|
+
| :------------ | :------------------------- |
|
|
531
|
+
| `req.params` | Route parameters (`:id`) |
|
|
532
|
+
| `req.query` | Query string (`?page=1`) |
|
|
533
|
+
| `req.body` | Parsed JSON/form body |
|
|
534
|
+
| `req.files` | Uploaded files (multipart) |
|
|
535
|
+
| `req.ip` | Client IP address |
|
|
536
|
+
| `req.method` | HTTP method |
|
|
537
|
+
| `req.url` | Request URL |
|
|
538
|
+
| `req.headers` | Request headers |
|
|
263
539
|
|
|
264
540
|
### Response (`res`)
|
|
265
541
|
|
|
266
|
-
| Method
|
|
267
|
-
|
|
|
268
|
-
| `res.json(data)`
|
|
269
|
-
| `res.send(data)`
|
|
270
|
-
| `res.status(code)`
|
|
271
|
-
| `res.
|
|
272
|
-
| `res.
|
|
542
|
+
| Method | Description |
|
|
543
|
+
| :---------------------------------- | :--------------------------- |
|
|
544
|
+
| `res.json(data)` | Send JSON |
|
|
545
|
+
| `res.send(data)` | Send any response |
|
|
546
|
+
| `res.status(code)` | Set status (chainable) |
|
|
547
|
+
| `res.redirect(url, code?)` | Redirect (302) |
|
|
548
|
+
| `res.sendFile(path)` | Send file from public folder |
|
|
549
|
+
| `res.sendAbsoluteFile(path, opts?)` | Send file from any path |
|
|
550
|
+
| `res.sendHtml(filename)` | Send HTML file |
|
|
551
|
+
| `res.success(data?, msg?)` | 200 OK |
|
|
552
|
+
| `res.created(data?, msg?)` | 201 Created |
|
|
553
|
+
| `res.badRequest(msg?, errors?)` | 400 Bad Request |
|
|
554
|
+
| `res.unauthorized(msg?)` | 401 Unauthorized |
|
|
555
|
+
| `res.forbidden(msg?)` | 403 Forbidden |
|
|
556
|
+
| `res.notFound(msg?)` | 404 Not Found |
|
|
557
|
+
| `res.conflict(msg?)` | 409 Conflict |
|
|
558
|
+
| `res.serverError(err?)` | 500 Server Error |
|
|
559
|
+
|
|
560
|
+
### Cluster Utilities
|
|
561
|
+
|
|
562
|
+
| Function | Description |
|
|
563
|
+
| :--------------------- | :---------------------------- |
|
|
564
|
+
| `clusterize(fn, opts)` | Start in cluster mode |
|
|
565
|
+
| `isPrimary()` | Check if primary process |
|
|
566
|
+
| `isWorker()` | Check if worker process |
|
|
567
|
+
| `getWorkerId()` | Get worker ID (0 for primary) |
|
|
568
|
+
| `getWorkerCount()` | Get number of active workers |
|
|
569
|
+
|
|
570
|
+
### Cache Utilities
|
|
571
|
+
|
|
572
|
+
| Class/Function | Description |
|
|
573
|
+
| :-------------------------- | :------------------------ |
|
|
574
|
+
| `new LRUCache(opts)` | Create LRU cache instance |
|
|
575
|
+
| `cacheMiddleware(cache)` | Create cache interceptor |
|
|
576
|
+
| `LRUCache.key(method, url)` | Generate cache key |
|
|
577
|
+
| `LRUCache.etag(value)` | Generate ETag |
|
|
578
|
+
|
|
579
|
+
### Pool Utilities
|
|
580
|
+
|
|
581
|
+
| Class/Function | Description |
|
|
582
|
+
| :----------------- | :--------------------- |
|
|
583
|
+
| `createPool(opts)` | Create connection pool |
|
|
584
|
+
| `pool.acquire()` | Acquire resource |
|
|
585
|
+
| `pool.release(r)` | Release resource |
|
|
586
|
+
| `pool.use(fn)` | Use with auto-release |
|
|
587
|
+
| `pool.close()` | Close pool |
|
|
588
|
+
| `pool.stats` | Get pool statistics |
|
|
589
|
+
|
|
590
|
+
---
|
|
591
|
+
|
|
592
|
+
## ๐ Benchmarks
|
|
593
|
+
|
|
594
|
+
Run benchmarks yourself:
|
|
595
|
+
|
|
596
|
+
```bash
|
|
597
|
+
npm run benchmark
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
Tested with 5,000 requests, 50 concurrency:
|
|
601
|
+
|
|
602
|
+
```
|
|
603
|
+
Framework | JSON RPS | vs Express | vs Fastify
|
|
604
|
+
-------------|-------------|------------|------------
|
|
605
|
+
Vibe | 14,687 | 2.2x โ
| 1.3x โ
|
|
606
|
+
Fastify | 11,289 | 1.7x | baseline
|
|
607
|
+
Express | 6,629 | baseline | 0.6x
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
---
|
|
611
|
+
|
|
612
|
+
## ๐งช Testing
|
|
613
|
+
|
|
614
|
+
```bash
|
|
615
|
+
# Run all tests
|
|
616
|
+
npm test
|
|
617
|
+
|
|
618
|
+
# Run comprehensive tests
|
|
619
|
+
npm run test:all
|
|
620
|
+
|
|
621
|
+
# Run benchmarks
|
|
622
|
+
npm run benchmark
|
|
623
|
+
```
|
|
273
624
|
|
|
274
625
|
---
|
|
275
626
|
|
|
276
627
|
## ๐ License
|
|
277
628
|
|
|
278
|
-
Part of the **GeNeSix** brand. Created by **Nnamdi "Joe" Amaga**.
|
|
629
|
+
Part of the **GeNeSix** brand. Created by **Nnamdi "Joe" Amaga**.
|
|
630
|
+
|
|
631
|
+
MIT License.
|