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/README.md +66 -42
- package/package.json +7 -18
- package/utils/core/compile-serializer.js +171 -0
- package/utils/core/parser.js +16 -2
- package/utils/core/response.js +139 -137
- package/utils/core/server.js +80 -18
- package/utils/native.js +6 -80
- package/vibe.js +27 -8
- package/binding.gyp +0 -36
- package/native/json_stringify.cc +0 -241
- package/native/json_stringify.h +0 -32
- package/native/url_parser.cc +0 -178
- package/native/url_parser.h +0 -34
- package/native/vibe_native.cc +0 -46
package/utils/native.js
CHANGED
|
@@ -1,76 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
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
|
|
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
|
|
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
|
-
|
|
199
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
}
|
package/native/json_stringify.cc
DELETED
|
@@ -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
|
package/native/json_stringify.h
DELETED
|
@@ -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
|
package/native/url_parser.cc
DELETED
|
@@ -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
|