vibe-gx 3.1.0 → 4.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 CHANGED
@@ -1,13 +1,13 @@
1
1
  <div align="center">
2
- <img src="./assets/vlogo.png" alt="Vibe Logo" width="180" />
2
+ <img src="https://github.com/thesixers/vibe/blob/808b45722a0b3ca0d266215bbe4cc074f62283e5/assets/vlogo.png?raw=true" alt="Vibe Logo" width="180" />
3
3
  <h1>Vibe</h1>
4
4
  <p>
5
5
  <b>The fastest Node.js web framework with the simplest syntax.</b>
6
6
  </p>
7
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" />
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
11
  <img src="https://img.shields.io/badge/license-MIT-green" alt="License" />
12
12
  </p>
13
13
  </div>
@@ -20,29 +20,21 @@
20
20
  npm install vibe-gx
21
21
  ```
22
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.
23
+ > Pure JavaScript no native dependencies, no build steps, just install and go.
32
24
 
33
25
  ---
34
26
 
35
27
  ## 🏆 Why Vibe?
36
28
 
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** | ✅ | ❌ | |
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** | ✅ | ❌ | |
46
38
 
47
39
  > **Vibe is faster than Fastify, simpler than Express, and 14-18x smaller than both.**
48
40
 
@@ -50,19 +42,19 @@ npm run build:native
50
42
 
51
43
  ## ⚡ Features
52
44
 
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()` |
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** | Fastify-style `register()` with encapsulation |
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
+ | 🔗 **Connection Pool** | Generic pool for databases |
54
+ | 📂 **File Uploads** | Multipart uploads with size/type validation |
55
+ | 🌊 **Streaming** | Large file uploads without buffering |
56
+ | 🔒 **Security** | Path traversal protection, body limits, error sanitization |
57
+ | 🔄 **Express Adapter** | Use any Express middleware with `adapt()` |
66
58
 
67
59
  ---
68
60
 
@@ -520,6 +512,7 @@ app.get(
520
512
  **Benefits:**
521
513
 
522
514
  - ✅ 2-3x faster JSON serialization
515
+ - ✅ Zero-loop code generation via `new Function()`
523
516
  - ✅ No `Object.keys()` enumeration
524
517
  - ✅ Zero runtime type checking
525
518
  - ✅ Completely optional (routes work without schemas)
@@ -633,14 +626,15 @@ Run benchmarks yourself:
633
626
  npm run benchmark
634
627
  ```
635
628
 
636
- Tested with 5,000 requests, 50 concurrency:
629
+ Tested under overload (20,000 requests × 200 concurrent):
637
630
 
638
631
  ```
639
632
  Framework | JSON RPS | vs Express | vs Fastify
640
633
  -------------|-------------|------------|------------
641
- Vibe | 14,687 | 2.2x ✅ | 1.3x
642
- Fastify | 11,289 | 1.7x | baseline
643
- Express | 6,629 | baseline | 0.6x
634
+ Vibe | 11,472 | 4.7x ✅ | 1.01x
635
+ Fastify | 11,334 | 4.7x | baseline
636
+ Hono | 7,351 | 3.0x | 0.6x
637
+ Express | 2,421 | baseline | 0.2x
644
638
  ```
645
639
 
646
640
  ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vibe-gx",
3
- "version": "3.1.0",
3
+ "version": "4.0.0",
4
4
  "description": "A lightweight, high-performance Node.js web framework.",
5
5
  "type": "module",
6
6
  "main": "vibe.js",
@@ -1,16 +1,68 @@
1
1
  /**
2
2
  * Schema-based JSON serializer compiler.
3
3
  *
4
- * Pre-compiles a specialized stringify function from a JSON schema
5
- * at route registration time. The generated function knows the exact
6
- * object shape, avoiding Object.keys() enumeration and type-checking
7
- * at request time.
4
+ * Uses `new Function()` code generation to produce zero-loop,
5
+ * straight-line serializer functions from JSON schemas.
6
+ * Each generated function directly accesses known properties
7
+ * by name — no loops, no dynamic dispatch, no Object.keys().
8
+ *
9
+ * This is the same technique used by Fastify's fast-json-stringify.
8
10
  *
9
11
  * @module compile-serializer
10
12
  */
11
13
 
12
14
  /**
13
- * Compiles a JSON schema into a specialized serializer function.
15
+ * Escapes a string for JSON output.
16
+ * Handles: " \ \b \f \n \r \t and control chars.
17
+ * This function is passed into generated serializers.
18
+ */
19
+ function escapeString(str) {
20
+ if (str.length === 0) return '""';
21
+
22
+ let result = '"';
23
+ let last = 0;
24
+
25
+ for (let i = 0; i < str.length; i++) {
26
+ const code = str.charCodeAt(i);
27
+ if (code < 32 || code === 34 || code === 92) {
28
+ if (i > last) result += str.slice(last, i);
29
+ switch (code) {
30
+ case 34:
31
+ result += '\\"';
32
+ break;
33
+ case 92:
34
+ result += "\\\\";
35
+ break;
36
+ case 8:
37
+ result += "\\b";
38
+ break;
39
+ case 12:
40
+ result += "\\f";
41
+ break;
42
+ case 10:
43
+ result += "\\n";
44
+ break;
45
+ case 13:
46
+ result += "\\r";
47
+ break;
48
+ case 9:
49
+ result += "\\t";
50
+ break;
51
+ default:
52
+ result += "\\u" + code.toString(16).padStart(4, "0");
53
+ }
54
+ last = i + 1;
55
+ }
56
+ }
57
+
58
+ if (last === 0) return '"' + str + '"';
59
+ if (last < str.length) result += str.slice(last);
60
+ return result + '"';
61
+ }
62
+
63
+ /**
64
+ * Compiles a JSON schema into a specialized serializer function
65
+ * using code generation for maximum V8 optimization.
14
66
  *
15
67
  * @param {Object} schema - JSON schema (subset)
16
68
  * @returns {(data: any) => string} Compiled serializer
@@ -20,47 +72,144 @@ export function compileSerializer(schema) {
20
72
  return JSON.stringify;
21
73
  }
22
74
 
23
- const fn = compileType(schema);
24
- return fn;
75
+ return compileType(schema);
25
76
  }
26
77
 
27
78
  /**
28
- * Escapes a string for JSON output.
29
- * Handles: " \ \b \f \n \r \t and control chars
79
+ * Generates a code-gen serializer for an object schema.
80
+ * Produces a single straight-line function with no loops.
30
81
  */
31
- const escapeChar = {
32
- '"': '\\"',
33
- "\\": "\\\\",
34
- "\b": "\\b",
35
- "\f": "\\f",
36
- "\n": "\\n",
37
- "\r": "\\r",
38
- "\t": "\\t",
39
- };
82
+ function compileObject(schema) {
83
+ const props = schema.properties;
84
+ if (!props) return JSON.stringify;
40
85
 
41
- function escapeString(str) {
42
- let result = '"';
43
- let last = 0;
86
+ const keys = Object.keys(props);
87
+ if (keys.length === 0) return () => "{}";
44
88
 
45
- for (let i = 0; i < str.length; i++) {
46
- const code = str.charCodeAt(i);
47
- // Check for characters that need escaping
48
- if (code === 34 || code === 92 || code < 32) {
49
- const char = str[i];
50
- if (i > last) result += str.slice(last, i);
51
- result += escapeChar[char] || "\\u" + code.toString(16).padStart(4, "0");
52
- last = i + 1;
89
+ // Build the function body as a single expression
90
+ const parts = [];
91
+
92
+ for (let i = 0; i < keys.length; i++) {
93
+ const key = keys[i];
94
+ const prop = props[key];
95
+ const comma = i > 0 ? "," : "";
96
+ const jsonKey = `${comma}"${key}":`;
97
+
98
+ // Generate inline serialization code per property type
99
+ parts.push({ key, jsonKey, type: prop.type || "unknown", schema: prop });
100
+ }
101
+
102
+ // Check if all string — ultra-fast codegen path
103
+ const allSimple = parts.every(
104
+ (p) =>
105
+ p.type === "string" ||
106
+ p.type === "number" ||
107
+ p.type === "integer" ||
108
+ p.type === "boolean",
109
+ );
110
+
111
+ if (allSimple && keys.length <= 16) {
112
+ // Generate a straight-line function via new Function()
113
+ // This avoids all loops and dynamic dispatch
114
+ let body = 'if(o===null||o===undefined)return"null";\n';
115
+ body += "return '{' + ";
116
+
117
+ const segments = [];
118
+ for (let i = 0; i < parts.length; i++) {
119
+ const p = parts[i];
120
+ const accessor = `o[${JSON.stringify(p.key)}]`;
121
+ let valExpr;
122
+
123
+ switch (p.type) {
124
+ case "string":
125
+ valExpr = `(${accessor}===null||${accessor}===undefined?"null":e(""+${accessor}))`;
126
+ break;
127
+ case "number":
128
+ case "integer":
129
+ valExpr = `(${accessor}!=${accessor}||${accessor}===1/0||${accessor}===-1/0?"null":""+${accessor})`;
130
+ break;
131
+ case "boolean":
132
+ valExpr = `(${accessor}?"true":"false")`;
133
+ break;
134
+ }
135
+
136
+ segments.push(`'${p.jsonKey}'+${valExpr}`);
137
+ }
138
+
139
+ body += segments.join("+") + "+'}';";
140
+
141
+ try {
142
+ return new Function("e", "o", body).bind(null, escapeString);
143
+ } catch {
144
+ // Fallback if code-gen fails
145
+ return buildFallbackSerializer(parts);
53
146
  }
54
147
  }
55
148
 
56
- if (last === 0) return '"' + str + '"';
57
- if (last < str.length) result += str.slice(last);
58
- return result + '"';
149
+ // Complex types (nested objects, arrays) use pre-compiled sub-serializers
150
+ return buildComplexSerializer(parts);
151
+ }
152
+
153
+ /**
154
+ * Builds a serializer for complex schemas with nested objects/arrays.
155
+ * Uses pre-compiled child serializers (no code-gen).
156
+ */
157
+ function buildComplexSerializer(parts) {
158
+ // Pre-compile child serializers
159
+ const entries = parts.map((p) => ({
160
+ key: p.key,
161
+ jsonKey: p.jsonKey,
162
+ serializer: compileType(p.schema),
163
+ }));
164
+
165
+ return function serializeComplex(obj) {
166
+ if (obj === null || obj === undefined) return "null";
167
+ let result = "{";
168
+ for (let i = 0; i < entries.length; i++) {
169
+ const e = entries[i];
170
+ const v = obj[e.key];
171
+ result += e.jsonKey;
172
+ if (v === null || v === undefined) {
173
+ result += "null";
174
+ } else {
175
+ result += e.serializer(v);
176
+ }
177
+ }
178
+ return result + "}";
179
+ };
180
+ }
181
+
182
+ /**
183
+ * Fallback serializer when code-gen fails.
184
+ */
185
+ function buildFallbackSerializer(parts) {
186
+ const entries = parts.map((p) => ({
187
+ key: p.key,
188
+ jsonKey: p.jsonKey,
189
+ type: p.type,
190
+ }));
191
+
192
+ return function serializeFallback(obj) {
193
+ if (obj === null || obj === undefined) return "null";
194
+ let result = "{";
195
+ for (let i = 0; i < entries.length; i++) {
196
+ const e = entries[i];
197
+ const v = obj[e.key];
198
+ result += e.jsonKey;
199
+ if (v === null || v === undefined) {
200
+ result += "null";
201
+ } else if (e.type === "string") {
202
+ result += escapeString("" + v);
203
+ } else {
204
+ result += "" + v;
205
+ }
206
+ }
207
+ return result + "}";
208
+ };
59
209
  }
60
210
 
61
211
  /**
62
212
  * Compiles a type-specific serializer from schema.
63
- * Returns a function (value) => string.
64
213
  */
65
214
  function compileType(schema) {
66
215
  switch (schema.type) {
@@ -83,7 +232,7 @@ function compileType(schema) {
83
232
  }
84
233
 
85
234
  function serializeNumber(v) {
86
- if (v !== v || v === Infinity || v === -Infinity) return "null"; // NaN, Inf
235
+ if (v !== v || v === Infinity || v === -Infinity) return "null";
87
236
  return "" + v;
88
237
  }
89
238
 
@@ -91,63 +240,6 @@ function serializeBoolean(v) {
91
240
  return v ? "true" : "false";
92
241
  }
93
242
 
94
- /**
95
- * Compiles an object serializer from schema.properties.
96
- *
97
- * Generates a function that directly accesses known properties
98
- * by name, avoiding Object.keys() enumeration entirely.
99
- */
100
- function compileObject(schema) {
101
- const props = schema.properties;
102
- if (!props) return JSON.stringify;
103
-
104
- const keys = Object.keys(props);
105
- if (keys.length === 0) return () => "{}";
106
-
107
- // Pre-compute property serializers and JSON key prefixes
108
- const entries = keys.map((key, i) => {
109
- const serializer = compileType(props[key]);
110
- // Pre-stringify the key + colon (and comma for non-first)
111
- const prefix = (i > 0 ? "," : "") + '"' + key + '":';
112
- return { key, serializer, prefix };
113
- });
114
-
115
- // Check if all properties are simple strings — ultra-fast path
116
- const allStrings = keys.every((k) => props[k].type === "string");
117
-
118
- if (allStrings && keys.length <= 8) {
119
- // Ultra-fast path for small all-string objects (most common API response)
120
- return function serializeStringObject(obj) {
121
- if (obj === null || obj === undefined) return "null";
122
- let result = "{";
123
- for (let i = 0; i < entries.length; i++) {
124
- const e = entries[i];
125
- const v = obj[e.key];
126
- result += e.prefix;
127
- result += v === undefined || v === null ? "null" : escapeString("" + v);
128
- }
129
- return result + "}";
130
- };
131
- }
132
-
133
- // General path for mixed-type objects
134
- return function serializeObject(obj) {
135
- if (obj === null || obj === undefined) return "null";
136
- let result = "{";
137
- for (let i = 0; i < entries.length; i++) {
138
- const e = entries[i];
139
- const v = obj[e.key];
140
- result += e.prefix;
141
- if (v === undefined || v === null) {
142
- result += "null";
143
- } else {
144
- result += e.serializer(v);
145
- }
146
- }
147
- return result + "}";
148
- };
149
- }
150
-
151
243
  /**
152
244
  * Compiles an array serializer from schema.items.
153
245
  */
@@ -158,10 +250,11 @@ function compileArray(schema) {
158
250
 
159
251
  return function serializeArray(arr) {
160
252
  if (!Array.isArray(arr)) return "null";
161
- if (arr.length === 0) return "[]";
253
+ const len = arr.length;
254
+ if (len === 0) return "[]";
162
255
 
163
256
  let result = "[" + itemSerializer(arr[0]);
164
- for (let i = 1; i < arr.length; i++) {
257
+ for (let i = 1; i < len; i++) {
165
258
  result += "," + itemSerializer(arr[i]);
166
259
  }
167
260
  return result + "]";
@@ -2,6 +2,11 @@ import fs from "fs";
2
2
  import path from "path";
3
3
  import { mimeTypes } from "../helpers/mime.js";
4
4
 
5
+ // Pre-allocated headers (avoid per-request object creation)
6
+ const JSON_CT = { "content-type": "application/json" };
7
+ const TEXT_CT = { "content-type": "text/plain" };
8
+ const HTML_CT = { "Content-Type": "text/html" };
9
+
5
10
  // Pre-allocated response templates (stringified once at startup)
6
11
  const RESPONSES = {
7
12
  notFound: JSON.stringify({ success: false, message: "Resource not found" }),
@@ -31,12 +36,12 @@ const vibeResponseMethods = {
31
36
  }
32
37
 
33
38
  if (typeof data === "object" && data !== null) {
34
- this.setHeader("Content-Type", "application/json");
39
+ if (!this.headersSent) this.writeHead(this.statusCode || 200, JSON_CT);
35
40
  this.end(JSON.stringify(data));
36
41
  return;
37
42
  }
38
43
 
39
- this.setHeader("Content-Type", "text/plain");
44
+ if (!this.headersSent) this.writeHead(this.statusCode || 200, TEXT_CT);
40
45
  this.end(String(data));
41
46
  },
42
47
 
@@ -45,7 +50,7 @@ const vibeResponseMethods = {
45
50
  * @param {Object} data
46
51
  */
47
52
  json(data) {
48
- this.setHeader("Content-Type", "application/json");
53
+ if (!this.headersSent) this.writeHead(this.statusCode || 200, JSON_CT);
49
54
  this.end(JSON.stringify(data));
50
55
  },
51
56
 
@@ -146,8 +151,7 @@ const vibeResponseMethods = {
146
151
  * @param {string} message
147
152
  */
148
153
  success(data = null, message = "Success") {
149
- this.statusCode = 200;
150
- this.setHeader("Content-Type", "application/json");
154
+ this.writeHead(200, JSON_CT);
151
155
  this.end(JSON.stringify({ success: true, message, data }));
152
156
  },
153
157
 
@@ -157,8 +161,7 @@ const vibeResponseMethods = {
157
161
  * @param {string} message
158
162
  */
159
163
  created(data = null, message = "Resource created") {
160
- this.statusCode = 201;
161
- this.setHeader("Content-Type", "application/json");
164
+ this.writeHead(201, JSON_CT);
162
165
  this.end(JSON.stringify({ success: true, message, data }));
163
166
  },
164
167
 
@@ -168,8 +171,7 @@ const vibeResponseMethods = {
168
171
  * @param {any} errors
169
172
  */
170
173
  badRequest(message = "Bad Request", errors = null) {
171
- this.statusCode = 400;
172
- this.setHeader("Content-Type", "application/json");
174
+ this.writeHead(400, JSON_CT);
173
175
  this.end(
174
176
  errors
175
177
  ? JSON.stringify({ success: false, message, errors })
@@ -182,8 +184,7 @@ const vibeResponseMethods = {
182
184
  * @param {string} message
183
185
  */
184
186
  unauthorized(message) {
185
- this.statusCode = 401;
186
- this.setHeader("Content-Type", "application/json");
187
+ this.writeHead(401, JSON_CT);
187
188
  this.end(
188
189
  message
189
190
  ? JSON.stringify({ success: false, message })
@@ -196,8 +197,7 @@ const vibeResponseMethods = {
196
197
  * @param {string} message
197
198
  */
198
199
  forbidden(message) {
199
- this.statusCode = 403;
200
- this.setHeader("Content-Type", "application/json");
200
+ this.writeHead(403, JSON_CT);
201
201
  this.end(
202
202
  message
203
203
  ? JSON.stringify({ success: false, message })
@@ -210,8 +210,7 @@ const vibeResponseMethods = {
210
210
  * @param {string} message
211
211
  */
212
212
  notFound(message) {
213
- this.statusCode = 404;
214
- this.setHeader("Content-Type", "application/json");
213
+ this.writeHead(404, JSON_CT);
215
214
  this.end(
216
215
  message
217
216
  ? JSON.stringify({ success: false, message })
@@ -224,8 +223,7 @@ const vibeResponseMethods = {
224
223
  * @param {string} message
225
224
  */
226
225
  conflict(message) {
227
- this.statusCode = 409;
228
- this.setHeader("Content-Type", "application/json");
226
+ this.writeHead(409, JSON_CT);
229
227
  this.end(
230
228
  message
231
229
  ? JSON.stringify({ success: false, message })
@@ -239,8 +237,7 @@ const vibeResponseMethods = {
239
237
  */
240
238
  serverError(error) {
241
239
  console.error(error);
242
- this.statusCode = 500;
243
- this.setHeader("Content-Type", "application/json");
240
+ this.writeHead(500, JSON_CT);
244
241
  this.end(RESPONSES.serverError);
245
242
  },
246
243
 
@@ -1,19 +1,20 @@
1
1
  import http from "http";
2
- import {
3
- error,
4
- getNetworkIP,
5
- handleError,
6
- isSendAble,
7
- matchPath,
8
- } from "./handler.js";
2
+ import { error, getNetworkIP, handleError, isSendAble } from "./handler.js";
9
3
  import bodyParser from "./parser.js";
10
4
  import { installResponseMethods, initResponse } from "./response.js";
11
5
  import dns from "node:dns/promises";
12
6
  import { parseQuery } from "../native.js";
13
7
 
8
+ // Pre-allocated headers (frozen for V8 optimization)
9
+ const JSON_HEADERS = { "content-type": "application/json" };
10
+ const TEXT_HEADERS = { "content-type": "text/plain" };
11
+
14
12
  // Pre-allocated 404 response
15
13
  const NOT_FOUND_BODY = "Not Found";
16
14
 
15
+ // Shared frozen empty params (avoids per-request object allocation)
16
+ const EMPTY_PARAMS = Object.freeze(Object.create(null));
17
+
17
18
  /**
18
19
  * Creates and starts the Vibe HTTP server.
19
20
  * HEAVILY OPTIMIZED for performance
@@ -22,6 +23,20 @@ async function server(options, port, host, callback) {
22
23
  // Install response methods on prototype ONCE (zero per-request cost)
23
24
  installResponseMethods(http.ServerResponse);
24
25
 
26
+ // Install lazy query getter on IncomingMessage prototype ONCE
27
+ if (!http.IncomingMessage.prototype._vibeQueryInstalled) {
28
+ Object.defineProperty(http.IncomingMessage.prototype, "query", {
29
+ get() {
30
+ if (this._parsedQuery !== undefined) return this._parsedQuery;
31
+ this._parsedQuery =
32
+ this._qIdx < 0 ? {} : parseQuery(this._rawUrl.slice(this._qIdx + 1));
33
+ return this._parsedQuery;
34
+ },
35
+ configurable: true,
36
+ });
37
+ http.IncomingMessage.prototype._vibeQueryInstalled = true;
38
+ }
39
+
25
40
  // Pre-compute everything we can
26
41
  const useTrieMatching = options.routeCount > options.trieThreshold;
27
42
  const staticRoutes = options.staticRoutes || new Map();
@@ -73,29 +88,22 @@ async function server(options, port, host, callback) {
73
88
  return null;
74
89
  }
75
90
 
76
- // Main request handler - OPTIMIZED
91
+ // Main request handler - ULTRA OPTIMIZED
77
92
  function reqListener(req, res) {
78
93
  // Fast pathname extraction
79
94
  const url = req.url;
80
95
  const qIdx = url.indexOf("?");
81
96
  const pathname = qIdx < 0 ? url : url.slice(0, qIdx);
82
97
 
83
- // Lazy query via Object.defineProperty (replaces deprecated __defineGetter__)
84
- let _query;
85
- Object.defineProperty(req, "query", {
86
- get() {
87
- if (_query === undefined) {
88
- _query = qIdx < 0 ? {} : parseQuery(url.slice(qIdx + 1));
89
- }
90
- return _query;
91
- },
92
- configurable: true,
93
- });
98
+ // Store raw values for lazy query parsing (prototype getter handles the rest)
99
+ req._qIdx = qIdx;
100
+ req._rawUrl = url;
101
+ req._parsedQuery = undefined;
94
102
 
95
103
  req.url = pathname;
96
104
 
97
105
  // Stamp response with options ref (ONLY per-request cost for response methods)
98
- initResponse(res, options);
106
+ res._vibeOptions = options;
99
107
 
100
108
  // Apply decorators (only if exist)
101
109
  if (requestDecoratorEntries) {
@@ -117,41 +125,44 @@ async function server(options, port, host, callback) {
117
125
  const staticMatch = staticRoutes.get(routeKey);
118
126
 
119
127
  if (staticMatch && !staticMatch.intercept) {
120
- const { handler, serialize } = staticMatch;
121
- req.params = {};
128
+ req.params = EMPTY_PARAMS;
129
+
130
+ // Pre-built response (string/object/number/boolean registered at route time)
131
+ if (staticMatch._handlerType === 2) {
132
+ const pb = staticMatch._prebuilt;
133
+ // Check if it looks like JSON (starts with { or [)
134
+ const c = pb.charCodeAt(0);
135
+ if (c === 123 || c === 91) {
136
+ res.writeHead(200, JSON_HEADERS);
137
+ } else {
138
+ res.writeHead(200, TEXT_HEADERS);
139
+ }
140
+ res.end(pb);
141
+ return;
142
+ }
143
+
144
+ // Function handler
145
+ const handler = staticMatch.handler;
146
+ const serialize = staticMatch.serialize;
122
147
 
123
148
  try {
124
- if (typeof handler === "function") {
125
- const result = handler(req, res);
126
- // Check if handler returned a Promise (async handler)
127
- if (result !== undefined && !res.writableEnded) {
128
- if (result && typeof result.then === "function") {
129
- // Async handler fall through to async path
130
- result
131
- .then((val) => {
132
- if (val !== undefined && !res.writableEnded) {
133
- if (serialize) {
134
- res.setHeader("Content-Type", "application/json");
135
- res.end(serialize(val));
136
- } else {
137
- res.json(val);
138
- }
139
- }
140
- })
141
- .catch((err) => handleError(err, req, res));
142
- } else if (serialize) {
143
- res.setHeader("Content-Type", "application/json");
144
- res.end(serialize(result));
145
- } else {
146
- res.json(result);
147
- }
148
- }
149
- } else if (isSendAble(handler)) {
150
- if (serialize && typeof handler === "object" && handler !== null) {
151
- res.setHeader("Content-Type", "application/json");
152
- res.end(serialize(handler));
149
+ const result = handler(req, res);
150
+ if (result !== undefined && !res.writableEnded) {
151
+ if (result && typeof result.then === "function") {
152
+ result
153
+ .then((val) => {
154
+ if (val !== undefined && !res.writableEnded) {
155
+ res.writeHead(200, JSON_HEADERS);
156
+ res.end(serialize ? serialize(val) : JSON.stringify(val));
157
+ }
158
+ })
159
+ .catch((err) => handleError(err, req, res));
160
+ } else if (typeof result === "object" && result !== null) {
161
+ res.writeHead(200, JSON_HEADERS);
162
+ res.end(serialize ? serialize(result) : JSON.stringify(result));
153
163
  } else {
154
- res.send(handler);
164
+ res.writeHead(200, TEXT_HEADERS);
165
+ res.end(String(result));
155
166
  }
156
167
  }
157
168
  } catch (err) {
@@ -190,7 +201,7 @@ async function server(options, port, host, callback) {
190
201
  }
191
202
 
192
203
  if (!match) {
193
- res.writeHead(404, { "content-type": "text/plain" });
204
+ res.writeHead(404, TEXT_HEADERS);
194
205
  res.end(NOT_FOUND_BODY);
195
206
  return;
196
207
  }
@@ -218,15 +229,16 @@ async function server(options, port, host, callback) {
218
229
  if (result !== undefined && !res.writableEnded) {
219
230
  if (serialize) {
220
231
  // Pre-compiled schema serializer — fastest path
221
- res.setHeader("Content-Type", "application/json");
232
+ res.writeHead(200, JSON_HEADERS);
222
233
  res.end(serialize(result));
223
234
  } else {
224
- res.json(result);
235
+ res.writeHead(200, JSON_HEADERS);
236
+ res.end(JSON.stringify(result));
225
237
  }
226
238
  }
227
239
  } else if (isSendAble(handler)) {
228
240
  if (serialize && typeof handler === "object" && handler !== null) {
229
- res.setHeader("Content-Type", "application/json");
241
+ res.writeHead(200, JSON_HEADERS);
230
242
  res.end(serialize(handler));
231
243
  } else {
232
244
  res.send(handler);
package/vibe.d.ts CHANGED
@@ -51,6 +51,62 @@ export interface MediaOptions {
51
51
  streaming?: boolean;
52
52
  }
53
53
 
54
+ /**
55
+ * JSON Schema property definition for schema-based serialization.
56
+ */
57
+ export interface JsonSchemaProperty {
58
+ /** The type of the property */
59
+ type:
60
+ | "string"
61
+ | "number"
62
+ | "integer"
63
+ | "boolean"
64
+ | "object"
65
+ | "array"
66
+ | "null";
67
+ /** Nested properties (for type: "object") */
68
+ properties?: Record<string, JsonSchemaProperty>;
69
+ /** Item schema (for type: "array") */
70
+ items?: JsonSchemaProperty;
71
+ }
72
+
73
+ /**
74
+ * JSON Schema definition for response serialization.
75
+ *
76
+ * @example
77
+ * {
78
+ * type: "object",
79
+ * properties: {
80
+ * id: { type: "number" },
81
+ * name: { type: "string" },
82
+ * active: { type: "boolean" }
83
+ * }
84
+ * }
85
+ */
86
+ export interface JsonSchema extends JsonSchemaProperty {
87
+ type: "object" | "array";
88
+ }
89
+
90
+ /**
91
+ * Schema options for a route.
92
+ * Used for pre-compiled response serialization (2-3x faster than JSON.stringify).
93
+ *
94
+ * @example
95
+ * {
96
+ * response: {
97
+ * type: "object",
98
+ * properties: {
99
+ * id: { type: "number" },
100
+ * name: { type: "string" }
101
+ * }
102
+ * }
103
+ * }
104
+ */
105
+ export interface SchemaOptions {
106
+ /** Response schema for pre-compiled JSON serialization */
107
+ response?: JsonSchema;
108
+ }
109
+
54
110
  /**
55
111
  * Options for registering a route.
56
112
  *
@@ -88,6 +144,22 @@ export interface RouteOptions {
88
144
  * Files will be available in req.files array.
89
145
  */
90
146
  media?: MediaOptions;
147
+ /**
148
+ * JSON Schema for pre-compiled response serialization.
149
+ * Generates a zero-overhead serializer at route registration time.
150
+ *
151
+ * @example
152
+ * schema: {
153
+ * response: {
154
+ * type: "object",
155
+ * properties: {
156
+ * id: { type: "number" },
157
+ * name: { type: "string" }
158
+ * }
159
+ * }
160
+ * }
161
+ */
162
+ schema?: SchemaOptions;
91
163
  }
92
164
 
93
165
  /**
package/vibe.js CHANGED
@@ -85,6 +85,7 @@ function pathToRegex(path) {
85
85
  * @typedef {Object} RouteOptions
86
86
  * @property {Interceptor | Interceptor[]} [intercept]
87
87
  * @property {MediaOptions} [media]
88
+ * @property {{ response?: Object }} [schema] Schema for pre-compiled serialization
88
89
  */
89
90
 
90
91
  /**
@@ -92,9 +93,14 @@ function pathToRegex(path) {
92
93
  * @typedef {Object} VibeRoute
93
94
  * @property {string} method
94
95
  * @property {string} path
96
+ * @property {RegExp | null} pathRegex
95
97
  * @property {Handler | string | number | object} handler
96
98
  * @property {Interceptor | Interceptor[] | null} intercept
97
- * @property {MediaOptions} media
99
+ * @property {((data: any) => string) | null} serialize
100
+ * @property {MediaOptions | null} media
101
+ * @property {boolean} [isStatic]
102
+ * @property {number} [_handlerType]
103
+ * @property {string | null} [_prebuilt]
98
104
  */
99
105
 
100
106
  /**
@@ -198,6 +204,9 @@ const vibe = () => {
198
204
  intercept: null,
199
205
  serialize: null,
200
206
  media: null, // Only set when explicitly configured
207
+ // Pre-computed handler metadata (avoids typeof checks on hot path)
208
+ _handlerType: 0, // 0=unknown, 1=function, 2=prebuilt-string
209
+ _prebuilt: null, // Pre-stringified response for static handlers
201
210
  };
202
211
 
203
212
  // Handle overriding root route
@@ -227,6 +236,7 @@ const vibe = () => {
227
236
  }
228
237
  route.pathRegex = /^\/$/;
229
238
  route.isStatic = true;
239
+ finalizeRoute(route);
230
240
  trie.insert(method, "/", route);
231
241
  staticRoutes.set(method + "/", route);
232
242
  // Update existing root route in routes array
@@ -270,6 +280,9 @@ const vibe = () => {
270
280
  const isStatic = !fullPath.includes(":") && !fullPath.includes("*");
271
281
  route.isStatic = isStatic;
272
282
 
283
+ // Pre-compute handler type for fast-path dispatch
284
+ finalizeRoute(route);
285
+
273
286
  // Add to static routes Map for O(1) lookup
274
287
  if (isStatic) {
275
288
  staticRoutes.set(method + fullPath, route);
@@ -281,6 +294,28 @@ const vibe = () => {
281
294
  options.routeCount++;
282
295
  }
283
296
 
297
+ /**
298
+ * Pre-computes handler type and pre-stringifies static values.
299
+ * This moves work from request-time to registration-time.
300
+ */
301
+ function finalizeRoute(route) {
302
+ const h = route.handler;
303
+ if (typeof h === "function") {
304
+ route._handlerType = 1; // function
305
+ } else if (typeof h === "string") {
306
+ route._handlerType = 2; // pre-built string
307
+ route._prebuilt = h;
308
+ } else if (typeof h === "object" && h !== null) {
309
+ route._handlerType = 2; // pre-built JSON
310
+ route._prebuilt = route.serialize
311
+ ? route.serialize(h)
312
+ : JSON.stringify(h);
313
+ } else if (typeof h === "number" || typeof h === "boolean") {
314
+ route._handlerType = 2; // pre-built primitive
315
+ route._prebuilt = String(h);
316
+ }
317
+ }
318
+
284
319
  /**
285
320
  * Wraps interceptors with adapt() for consistent behavior
286
321
  * @param {Interceptor | Interceptor[]} intercept
@@ -492,6 +527,8 @@ const vibe = () => {
492
527
  media: { public: true, dest: null },
493
528
  };
494
529
  trie.insert("GET", routePath, route);
530
+ routes.push(route);
531
+ options.routeCount++;
495
532
  }
496
533
 
497
534
  /**