vibe-gx 2.0.0 → 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/utils/native.js CHANGED
@@ -1,76 +1,23 @@
1
1
  /**
2
- * Native Module Wrapper
3
- *
4
- * Attempts to load the C++ native module for performance.
5
- * Falls back to pure JavaScript implementations if native module is unavailable.
2
+ * Pure JavaScript implementations for JSON stringify, URL parsing,
3
+ * query string parsing, and URI decoding.
6
4
  */
7
5
 
8
- import { createRequire } from "module";
9
- import { fileURLToPath } from "url";
10
- import path from "path";
11
-
12
- const __filename = fileURLToPath(import.meta.url);
13
- const __dirname = path.dirname(__filename);
14
-
15
- let native = null;
16
- let useNative = false;
17
-
18
- // Try to load native module using createRequire for ESM compatibility
19
- try {
20
- const require = createRequire(import.meta.url);
21
- const nativePath = path.join(
22
- __dirname,
23
- "..",
24
- "build",
25
- "Release",
26
- "vibe_native.node",
27
- );
28
- native = require(nativePath);
29
- useNative = true;
30
- console.log("[VIBE] Native module loaded (C++ optimizations enabled)");
31
- } catch (err) {
32
- // Native module not available, use JS fallback
33
- console.log(
34
- "[VIBE] Native module not available, using JavaScript implementation",
35
- );
36
- }
37
-
38
6
  /**
39
- * Fast JSON stringify
40
- * Uses native C++ implementation if available
41
- *
7
+ * Fast JSON stringify (pure JS)
42
8
  * @param {any} value - Value to stringify
43
9
  * @returns {string} JSON string
44
10
  */
45
11
  export function stringify(value) {
46
- if (useNative) {
47
- try {
48
- return native.stringify(value);
49
- } catch {
50
- // Fallback on error
51
- return JSON.stringify(value);
52
- }
53
- }
54
12
  return JSON.stringify(value);
55
13
  }
56
14
 
57
15
  /**
58
16
  * Parse URL into pathname and query object
59
- * Uses native C++ implementation if available
60
- *
61
17
  * @param {string} url - URL to parse
62
18
  * @returns {{ pathname: string, query: Object }}
63
19
  */
64
20
  export function parseUrl(url) {
65
- if (useNative) {
66
- try {
67
- return native.parseUrl(url);
68
- } catch {
69
- // Fallback
70
- }
71
- }
72
-
73
- // JavaScript fallback
74
21
  const qIdx = url.indexOf("?");
75
22
  if (qIdx < 0) {
76
23
  return { pathname: url, query: {} };
@@ -83,25 +30,13 @@ export function parseUrl(url) {
83
30
 
84
31
  /**
85
32
  * Parse query string into object
86
- * Uses native C++ implementation if available
87
- *
88
33
  * @param {string} queryString - Query string (with or without leading ?)
89
34
  * @returns {Object} Parsed query parameters
90
35
  */
91
36
  export function parseQuery(queryString) {
92
- if (useNative) {
93
- try {
94
- return native.parseQuery(queryString);
95
- } catch {
96
- // Fallback
97
- }
98
- }
99
-
100
- // JavaScript fallback
101
37
  const query = {};
102
38
  if (!queryString) return query;
103
39
 
104
- // Remove leading ?
105
40
  let qs = queryString;
106
41
  if (qs[0] === "?") {
107
42
  qs = qs.slice(1);
@@ -127,28 +62,19 @@ export function parseQuery(queryString) {
127
62
 
128
63
  /**
129
64
  * Decode URI component
130
- * Uses native C++ implementation if available
131
- *
132
65
  * @param {string} encoded - Encoded string
133
66
  * @returns {string} Decoded string
134
67
  */
135
68
  export function decodeURI(encoded) {
136
- if (useNative) {
137
- try {
138
- return native.decodeURI(encoded);
139
- } catch {
140
- // Fallback
141
- }
142
- }
143
69
  return decodeURIComponent(encoded);
144
70
  }
145
71
 
146
72
  /**
147
- * Check if native module is loaded
73
+ * Check if native module is loaded (always false now)
148
74
  * @returns {boolean}
149
75
  */
150
76
  export function isNativeEnabled() {
151
- return useNative;
77
+ return false;
152
78
  }
153
79
 
154
80
  /**
@@ -156,7 +82,7 @@ export function isNativeEnabled() {
156
82
  * @returns {string|null}
157
83
  */
158
84
  export function getNativeVersion() {
159
- return useNative ? native.version : null;
85
+ return null;
160
86
  }
161
87
 
162
88
  export default {
package/vibe.js CHANGED
@@ -3,6 +3,7 @@ import { adapt } from "./utils/helpers/adapt.js";
3
3
  import { color } from "./utils/helpers/colors.js";
4
4
  import { RouteTrie } from "./utils/core/trie.js";
5
5
  import { PathToRegex } from "./utils/core/handler.js";
6
+ import { compileSerializer } from "./utils/core/compile-serializer.js";
6
7
 
7
8
  /**
8
9
  * Helper to generate regex for a path
@@ -195,12 +196,8 @@ const vibe = () => {
195
196
  pathRegex: null,
196
197
  handler: null,
197
198
  intercept: null,
198
- media: {
199
- public: true,
200
- dest: null,
201
- maxSize: 10 * 1024 * 1024,
202
- allowedTypes: null,
203
- },
199
+ serialize: null,
200
+ media: null, // Only set when explicitly configured
204
201
  };
205
202
 
206
203
  // Handle overriding root route
@@ -212,7 +209,18 @@ const vibe = () => {
212
209
  route.intercept = opts.intercept
213
210
  ? wrapIntercepts(opts.intercept)
214
211
  : null;
215
- route.media = { ...route.media, ...opts.media };
212
+ if (opts.media) {
213
+ route.media = {
214
+ public: true,
215
+ dest: null,
216
+ maxSize: 10 * 1024 * 1024,
217
+ allowedTypes: null,
218
+ ...opts.media,
219
+ };
220
+ }
221
+ if (opts.schema?.response) {
222
+ route.serialize = compileSerializer(opts.schema.response);
223
+ }
216
224
  route.handler = handler;
217
225
  } else {
218
226
  route.handler = opts;
@@ -238,7 +246,18 @@ const vibe = () => {
238
246
  throw new Error("Options must be an object when using 3-arg form");
239
247
  }
240
248
  route.intercept = opts.intercept ? wrapIntercepts(opts.intercept) : null;
241
- route.media = { ...route.media, ...opts.media };
249
+ if (opts.media) {
250
+ route.media = {
251
+ public: true,
252
+ dest: null,
253
+ maxSize: 10 * 1024 * 1024,
254
+ allowedTypes: null,
255
+ ...opts.media,
256
+ };
257
+ }
258
+ if (opts.schema?.response) {
259
+ route.serialize = compileSerializer(opts.schema.response);
260
+ }
242
261
  route.handler = handler;
243
262
  } else {
244
263
  route.handler = opts;
package/binding.gyp DELETED
@@ -1,36 +0,0 @@
1
- {
2
- "targets": [
3
- {
4
- "target_name": "vibe_native",
5
- "cflags!": ["-fno-exceptions"],
6
- "cflags_cc!": ["-fno-exceptions"],
7
- "cflags_cc": ["-std=c++17", "-O3"],
8
- "sources": [
9
- "native/vibe_native.cc",
10
- "native/json_stringify.cc",
11
- "native/url_parser.cc"
12
- ],
13
- "include_dirs": [
14
- "<!@(node -p \"require('node-addon-api').include\")"
15
- ],
16
- "defines": ["NAPI_DISABLE_CPP_EXCEPTIONS"],
17
- "conditions": [
18
- ["OS=='mac'", {
19
- "xcode_settings": {
20
- "GCC_ENABLE_CPP_EXCEPTIONS": "YES",
21
- "CLANG_CXX_LANGUAGE_STANDARD": "c++17",
22
- "MACOSX_DEPLOYMENT_TARGET": "10.15"
23
- }
24
- }],
25
- ["OS=='win'", {
26
- "msvs_settings": {
27
- "VCCLCompilerTool": {
28
- "ExceptionHandling": 1,
29
- "AdditionalOptions": ["/std:c++17"]
30
- }
31
- }
32
- }]
33
- ]
34
- }
35
- ]
36
- }
@@ -1,241 +0,0 @@
1
- #include "json_stringify.h"
2
- #include <cmath>
3
- #include <cstring>
4
- #include <vector>
5
-
6
- namespace vibe {
7
-
8
- // Pre-allocated escape table for fast lookup
9
- static const char* ESCAPE_TABLE[256] = {nullptr};
10
- static bool escapeTableInitialized = false;
11
-
12
- static void initEscapeTable() {
13
- if (escapeTableInitialized) return;
14
-
15
- // Control characters
16
- ESCAPE_TABLE['"'] = "\\\"";
17
- ESCAPE_TABLE['\\'] = "\\\\";
18
- ESCAPE_TABLE['\b'] = "\\b";
19
- ESCAPE_TABLE['\f'] = "\\f";
20
- ESCAPE_TABLE['\n'] = "\\n";
21
- ESCAPE_TABLE['\r'] = "\\r";
22
- ESCAPE_TABLE['\t'] = "\\t";
23
-
24
- escapeTableInitialized = true;
25
- }
26
-
27
- // Fast string builder using pre-allocated buffer
28
- class FastStringBuilder {
29
- public:
30
- FastStringBuilder() : buffer_(4096) {
31
- pos_ = 0;
32
- }
33
-
34
- void reserve(size_t size) {
35
- if (buffer_.size() < size) {
36
- buffer_.resize(size * 2);
37
- }
38
- }
39
-
40
- void append(char c) {
41
- ensureCapacity(1);
42
- buffer_[pos_++] = c;
43
- }
44
-
45
- void append(const char* str, size_t len) {
46
- ensureCapacity(len);
47
- memcpy(&buffer_[pos_], str, len);
48
- pos_ += len;
49
- }
50
-
51
- void append(const char* str) {
52
- append(str, strlen(str));
53
- }
54
-
55
- void appendInt(int64_t value) {
56
- char buf[32];
57
- int len = snprintf(buf, sizeof(buf), "%ld", value);
58
- append(buf, len);
59
- }
60
-
61
- void appendDouble(double value) {
62
- char buf[32];
63
- int len = snprintf(buf, sizeof(buf), "%.15g", value);
64
- append(buf, len);
65
- }
66
-
67
- std::string toString() {
68
- return std::string(buffer_.data(), pos_);
69
- }
70
-
71
- private:
72
- void ensureCapacity(size_t needed) {
73
- if (pos_ + needed > buffer_.size()) {
74
- buffer_.resize((pos_ + needed) * 2);
75
- }
76
- }
77
-
78
- std::vector<char> buffer_;
79
- size_t pos_;
80
- };
81
-
82
- // Fast string escape using lookup table
83
- static void escapeStringFast(FastStringBuilder& sb, const std::string& str) {
84
- sb.append('"');
85
-
86
- const char* data = str.data();
87
- size_t len = str.size();
88
- size_t lastPos = 0;
89
-
90
- for (size_t i = 0; i < len; i++) {
91
- unsigned char c = static_cast<unsigned char>(data[i]);
92
-
93
- if (c < 0x20 || c == '"' || c == '\\') {
94
- // Flush unescaped segment
95
- if (i > lastPos) {
96
- sb.append(data + lastPos, i - lastPos);
97
- }
98
-
99
- // Handle escape
100
- if (ESCAPE_TABLE[c]) {
101
- sb.append(ESCAPE_TABLE[c]);
102
- } else {
103
- // Control character - use \u escape
104
- char buf[8];
105
- snprintf(buf, sizeof(buf), "\\u%04x", c);
106
- sb.append(buf, 6);
107
- }
108
-
109
- lastPos = i + 1;
110
- }
111
- }
112
-
113
- // Flush remaining
114
- if (lastPos < len) {
115
- sb.append(data + lastPos, len - lastPos);
116
- }
117
-
118
- sb.append('"');
119
- }
120
-
121
- static void stringifyValueFast(FastStringBuilder& sb, const Napi::Value& value, int depth);
122
-
123
- static void stringifyArrayFast(FastStringBuilder& sb, const Napi::Array& arr, int depth) {
124
- if (depth > 10) {
125
- sb.append("null", 4);
126
- return;
127
- }
128
-
129
- sb.append('[');
130
- uint32_t len = arr.Length();
131
-
132
- for (uint32_t i = 0; i < len; i++) {
133
- if (i > 0) sb.append(',');
134
- stringifyValueFast(sb, arr.Get(i), depth + 1);
135
- }
136
-
137
- sb.append(']');
138
- }
139
-
140
- static void stringifyObjectFast(FastStringBuilder& sb, const Napi::Object& obj, int depth) {
141
- if (depth > 10) {
142
- sb.append("null", 4);
143
- return;
144
- }
145
-
146
- sb.append('{');
147
- Napi::Array keys = obj.GetPropertyNames();
148
- uint32_t len = keys.Length();
149
- bool first = true;
150
-
151
- for (uint32_t i = 0; i < len; i++) {
152
- Napi::Value key = keys.Get(i);
153
- Napi::Value val = obj.Get(key);
154
-
155
- if (val.IsUndefined()) continue;
156
-
157
- if (!first) sb.append(',');
158
- first = false;
159
-
160
- escapeStringFast(sb, key.As<Napi::String>().Utf8Value());
161
- sb.append(':');
162
- stringifyValueFast(sb, val, depth + 1);
163
- }
164
-
165
- sb.append('}');
166
- }
167
-
168
- static void stringifyValueFast(FastStringBuilder& sb, const Napi::Value& value, int depth) {
169
- if (value.IsNull() || value.IsUndefined()) {
170
- sb.append("null", 4);
171
- return;
172
- }
173
-
174
- if (value.IsBoolean()) {
175
- if (value.As<Napi::Boolean>().Value()) {
176
- sb.append("true", 4);
177
- } else {
178
- sb.append("false", 5);
179
- }
180
- return;
181
- }
182
-
183
- if (value.IsNumber()) {
184
- double num = value.As<Napi::Number>().DoubleValue();
185
- if (std::isnan(num) || std::isinf(num)) {
186
- sb.append("null", 4);
187
- } else if (num == static_cast<int64_t>(num) && num >= -9007199254740991.0 && num <= 9007199254740991.0) {
188
- sb.appendInt(static_cast<int64_t>(num));
189
- } else {
190
- sb.appendDouble(num);
191
- }
192
- return;
193
- }
194
-
195
- if (value.IsString()) {
196
- escapeStringFast(sb, value.As<Napi::String>().Utf8Value());
197
- return;
198
- }
199
-
200
- if (value.IsArray()) {
201
- stringifyArrayFast(sb, value.As<Napi::Array>(), depth);
202
- return;
203
- }
204
-
205
- if (value.IsObject()) {
206
- Napi::Object obj = value.As<Napi::Object>();
207
-
208
- // Check for toJSON
209
- if (obj.Has("toJSON") && obj.Get("toJSON").IsFunction()) {
210
- Napi::Function toJSON = obj.Get("toJSON").As<Napi::Function>();
211
- Napi::Value result = toJSON.Call(obj, {});
212
- stringifyValueFast(sb, result, depth);
213
- return;
214
- }
215
-
216
- stringifyObjectFast(sb, obj, depth);
217
- return;
218
- }
219
-
220
- sb.append("null", 4);
221
- }
222
-
223
- Napi::String JsonStringifier::Stringify(const Napi::Env& env, const Napi::Value& value) {
224
- initEscapeTable();
225
-
226
- FastStringBuilder sb;
227
- stringifyValueFast(sb, value, 0);
228
- return Napi::String::New(env, sb.toString());
229
- }
230
-
231
- Napi::Value FastStringify(const Napi::CallbackInfo& info) {
232
- Napi::Env env = info.Env();
233
-
234
- if (info.Length() < 1) {
235
- return Napi::String::New(env, "undefined");
236
- }
237
-
238
- return JsonStringifier::Stringify(env, info[0]);
239
- }
240
-
241
- } // namespace vibe
@@ -1,32 +0,0 @@
1
- #ifndef JSON_STRINGIFY_H
2
- #define JSON_STRINGIFY_H
3
-
4
- #include <napi.h>
5
- #include <string>
6
- #include <sstream>
7
-
8
- namespace vibe {
9
-
10
- /**
11
- * Fast JSON stringifier for common cases
12
- * Handles: strings, numbers, booleans, null, simple objects, arrays
13
- */
14
- class JsonStringifier {
15
- public:
16
- static Napi::String Stringify(const Napi::Env& env, const Napi::Value& value);
17
-
18
- private:
19
- static void StringifyValue(std::ostringstream& ss, const Napi::Value& value, int depth);
20
- static void StringifyObject(std::ostringstream& ss, const Napi::Object& obj, int depth);
21
- static void StringifyArray(std::ostringstream& ss, const Napi::Array& arr, int depth);
22
- static void EscapeString(std::ostringstream& ss, const std::string& str);
23
-
24
- static constexpr int MAX_DEPTH = 10;
25
- };
26
-
27
- // N-API wrapper function
28
- Napi::Value FastStringify(const Napi::CallbackInfo& info);
29
-
30
- } // namespace vibe
31
-
32
- #endif // JSON_STRINGIFY_H
@@ -1,178 +0,0 @@
1
- #include "url_parser.h"
2
- #include <cstring>
3
-
4
- namespace vibe {
5
-
6
- // Pre-computed hex decode table
7
- static int HEX_TABLE[256] = {-1};
8
- static bool hexTableInitialized = false;
9
-
10
- static void initHexTable() {
11
- if (hexTableInitialized) return;
12
-
13
- for (int i = 0; i < 256; i++) HEX_TABLE[i] = -1;
14
-
15
- for (int i = '0'; i <= '9'; i++) HEX_TABLE[i] = i - '0';
16
- for (int i = 'a'; i <= 'f'; i++) HEX_TABLE[i] = i - 'a' + 10;
17
- for (int i = 'A'; i <= 'F'; i++) HEX_TABLE[i] = i - 'A' + 10;
18
-
19
- hexTableInitialized = true;
20
- }
21
-
22
- std::string UrlParser::DecodeURIComponent(const std::string& encoded) {
23
- initHexTable();
24
-
25
- std::string result;
26
- result.reserve(encoded.size());
27
-
28
- const char* data = encoded.data();
29
- size_t len = encoded.size();
30
-
31
- for (size_t i = 0; i < len; i++) {
32
- char c = data[i];
33
-
34
- if (c == '%' && i + 2 < len) {
35
- int hi = HEX_TABLE[static_cast<unsigned char>(data[i + 1])];
36
- int lo = HEX_TABLE[static_cast<unsigned char>(data[i + 2])];
37
-
38
- if (hi >= 0 && lo >= 0) {
39
- result += static_cast<char>((hi << 4) | lo);
40
- i += 2;
41
- continue;
42
- }
43
- } else if (c == '+') {
44
- result += ' ';
45
- continue;
46
- }
47
-
48
- result += c;
49
- }
50
-
51
- return result;
52
- }
53
-
54
- UrlParser::ParseResult UrlParser::Parse(const std::string& url) {
55
- ParseResult result;
56
-
57
- const char* data = url.data();
58
- size_t len = url.size();
59
-
60
- // Find query string start using pointer arithmetic (faster)
61
- const char* qmark = static_cast<const char*>(memchr(data, '?', len));
62
-
63
- if (!qmark) {
64
- result.pathname = url;
65
- return result;
66
- }
67
-
68
- size_t pathLen = qmark - data;
69
- result.pathname = std::string(data, pathLen);
70
-
71
- // Parse query string
72
- const char* queryStart = qmark + 1;
73
- size_t queryLen = len - pathLen - 1;
74
-
75
- const char* pos = queryStart;
76
- const char* end = queryStart + queryLen;
77
-
78
- while (pos < end) {
79
- // Find & or end
80
- const char* amp = static_cast<const char*>(memchr(pos, '&', end - pos));
81
- const char* pairEnd = amp ? amp : end;
82
-
83
- // Find = in pair
84
- size_t pairLen = pairEnd - pos;
85
- const char* eq = static_cast<const char*>(memchr(pos, '=', pairLen));
86
-
87
- if (eq && eq > pos) {
88
- std::string key = DecodeURIComponent(std::string(pos, eq - pos));
89
- std::string value = DecodeURIComponent(std::string(eq + 1, pairEnd - eq - 1));
90
- result.query[key] = value;
91
- }
92
-
93
- pos = pairEnd + 1;
94
- }
95
-
96
- return result;
97
- }
98
-
99
- Napi::Value ParseUrl(const Napi::CallbackInfo& info) {
100
- Napi::Env env = info.Env();
101
-
102
- if (info.Length() < 1 || !info[0].IsString()) {
103
- Napi::TypeError::New(env, "URL string expected").ThrowAsJavaScriptException();
104
- return env.Null();
105
- }
106
-
107
- std::string url = info[0].As<Napi::String>().Utf8Value();
108
- UrlParser::ParseResult parsed = UrlParser::Parse(url);
109
-
110
- Napi::Object result = Napi::Object::New(env);
111
- result.Set("pathname", Napi::String::New(env, parsed.pathname));
112
-
113
- Napi::Object query = Napi::Object::New(env);
114
- for (const auto& pair : parsed.query) {
115
- query.Set(pair.first, Napi::String::New(env, pair.second));
116
- }
117
- result.Set("query", query);
118
-
119
- return result;
120
- }
121
-
122
- Napi::Value ParseQuery(const Napi::CallbackInfo& info) {
123
- Napi::Env env = info.Env();
124
-
125
- if (info.Length() < 1 || !info[0].IsString()) {
126
- return Napi::Object::New(env);
127
- }
128
-
129
- initHexTable();
130
-
131
- std::string queryString = info[0].As<Napi::String>().Utf8Value();
132
- const char* data = queryString.data();
133
- size_t len = queryString.size();
134
-
135
- // Skip leading ?
136
- if (len > 0 && data[0] == '?') {
137
- data++;
138
- len--;
139
- }
140
-
141
- Napi::Object result = Napi::Object::New(env);
142
-
143
- const char* pos = data;
144
- const char* end = data + len;
145
-
146
- while (pos < end) {
147
- const char* amp = static_cast<const char*>(memchr(pos, '&', end - pos));
148
- const char* pairEnd = amp ? amp : end;
149
- size_t pairLen = pairEnd - pos;
150
-
151
- const char* eq = static_cast<const char*>(memchr(pos, '=', pairLen));
152
-
153
- if (eq && eq > pos) {
154
- std::string key = UrlParser::DecodeURIComponent(std::string(pos, eq - pos));
155
- std::string value = UrlParser::DecodeURIComponent(std::string(eq + 1, pairEnd - eq - 1));
156
- result.Set(key, Napi::String::New(env, value));
157
- }
158
-
159
- pos = pairEnd + 1;
160
- }
161
-
162
- return result;
163
- }
164
-
165
- Napi::Value DecodeURI(const Napi::CallbackInfo& info) {
166
- Napi::Env env = info.Env();
167
-
168
- if (info.Length() < 1 || !info[0].IsString()) {
169
- return Napi::String::New(env, "");
170
- }
171
-
172
- std::string encoded = info[0].As<Napi::String>().Utf8Value();
173
- std::string decoded = UrlParser::DecodeURIComponent(encoded);
174
-
175
- return Napi::String::New(env, decoded);
176
- }
177
-
178
- } // namespace vibe