azurajs 3.0.1 → 3.0.2
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/dist/config/index.js +128 -6
- package/dist/config/index.js.map +1 -1
- package/dist/config/index.mjs +130 -1
- package/dist/config/index.mjs.map +1 -1
- package/dist/core/index.js +1100 -11
- package/dist/core/index.js.map +1 -1
- package/dist/core/index.mjs +1102 -3
- package/dist/core/index.mjs.map +1 -1
- package/dist/decorators/index.js +117 -87
- package/dist/decorators/index.js.map +1 -1
- package/dist/decorators/index.mjs +98 -1
- package/dist/decorators/index.mjs.map +1 -1
- package/dist/index.js +2592 -236
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +2537 -9
- package/dist/index.mjs.map +1 -1
- package/dist/middleware/index.js +16 -7
- package/dist/middleware/index.js.map +1 -1
- package/dist/middleware/index.mjs +17 -1
- package/dist/middleware/index.mjs.map +1 -1
- package/dist/plugins/index.js +1056 -73
- package/dist/plugins/index.js.map +1 -1
- package/dist/plugins/index.mjs +1042 -1
- package/dist/plugins/index.mjs.map +1 -1
- package/dist/types/index.js +49 -12
- package/dist/types/index.js.map +1 -1
- package/dist/types/index.mjs +49 -2
- package/dist/types/index.mjs.map +1 -1
- package/dist/utils/index.js +551 -50
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/index.mjs +541 -3
- package/dist/utils/index.mjs.map +1 -1
- package/package.json +35 -17
- package/{dist/chunk-DR254CWJ.mjs → src/config/ConfigModule.ts} +169 -132
- package/src/config/index.ts +1 -0
- package/src/core/index.ts +2 -0
- package/src/core/router.ts +284 -0
- package/{dist/chunk-EYAHUNC7.mjs → src/core/server.ts} +590 -699
- package/src/decorators/Route.ts +110 -0
- package/src/decorators/index.ts +23 -0
- package/src/index.ts +12 -0
- package/src/middleware/LoggingMiddleware.ts +20 -0
- package/src/middleware/index.ts +1 -0
- package/src/plugins/CORSPlugin.ts +56 -0
- package/src/plugins/CircuitBreakerPlugin.ts +84 -0
- package/src/plugins/CompressionPlugin.ts +80 -0
- package/src/plugins/ETagPlugin.ts +31 -0
- package/src/plugins/HealthCheckPlugin.ts +57 -0
- package/src/plugins/HelmetPlugin.ts +89 -0
- package/src/plugins/JWTPlugin.ts +132 -0
- package/src/plugins/MultipartPlugin.ts +168 -0
- package/src/plugins/ProxyPlugin.ts +89 -0
- package/src/plugins/RateLimitPlugin.ts +96 -0
- package/src/plugins/RequestIdPlugin.ts +21 -0
- package/src/plugins/SSEPlugin.ts +114 -0
- package/src/plugins/SessionPlugin.ts +98 -0
- package/src/plugins/StaticPlugin.ts +152 -0
- package/src/plugins/TimeoutPlugin.ts +33 -0
- package/src/plugins/index.ts +18 -0
- package/src/types/common.type.ts +82 -0
- package/src/types/config.type.ts +57 -0
- package/{dist/chunk-OWUGAI5V.mjs → src/types/http/status.ts} +49 -51
- package/src/types/index.ts +55 -0
- package/src/types/plugins/plugin.type.ts +170 -0
- package/src/types/reflect.d.ts +14 -0
- package/src/types/routes.type.ts +70 -0
- package/src/utils/HttpError.ts +62 -0
- package/src/utils/IpResolver.ts +30 -0
- package/src/utils/Logger.ts +144 -0
- package/src/utils/Parser.ts +182 -0
- package/src/utils/cookies/CookieManager.ts +48 -0
- package/src/utils/index.ts +9 -0
- package/{dist/chunk-UWIFSGSQ.mjs → src/utils/validators/DTOValidator.ts} +145 -141
- package/src/utils/validators/SchemaValidator.ts +45 -0
- package/dist/chunk-3UFAWS2V.js +0 -392
- package/dist/chunk-3UFAWS2V.js.map +0 -1
- package/dist/chunk-4LSFAAZW.js +0 -4
- package/dist/chunk-4LSFAAZW.js.map +0 -1
- package/dist/chunk-7NSRIVZM.js +0 -54
- package/dist/chunk-7NSRIVZM.js.map +0 -1
- package/dist/chunk-AOG6NYAM.js +0 -144
- package/dist/chunk-AOG6NYAM.js.map +0 -1
- package/dist/chunk-DR254CWJ.mjs.map +0 -1
- package/dist/chunk-EYAHUNC7.mjs.map +0 -1
- package/dist/chunk-HHDQPIJN.mjs +0 -19
- package/dist/chunk-HHDQPIJN.mjs.map +0 -1
- package/dist/chunk-HHZNAGGI.js +0 -702
- package/dist/chunk-HHZNAGGI.js.map +0 -1
- package/dist/chunk-KJM5XCAY.js +0 -21
- package/dist/chunk-KJM5XCAY.js.map +0 -1
- package/dist/chunk-NLSZKAPA.mjs +0 -1044
- package/dist/chunk-NLSZKAPA.mjs.map +0 -1
- package/dist/chunk-OWUGAI5V.mjs.map +0 -1
- package/dist/chunk-POPNQEOK.js +0 -1063
- package/dist/chunk-POPNQEOK.js.map +0 -1
- package/dist/chunk-QPRW4YU4.js +0 -134
- package/dist/chunk-QPRW4YU4.js.map +0 -1
- package/dist/chunk-REJDZUZ5.mjs +0 -382
- package/dist/chunk-REJDZUZ5.mjs.map +0 -1
- package/dist/chunk-TC6N6TJZ.mjs +0 -100
- package/dist/chunk-TC6N6TJZ.mjs.map +0 -1
- package/dist/chunk-TEUXKMXP.js +0 -122
- package/dist/chunk-TEUXKMXP.js.map +0 -1
- package/dist/chunk-UWIFSGSQ.mjs.map +0 -1
- package/dist/chunk-YPBKY4KY.mjs +0 -3
- package/dist/chunk-YPBKY4KY.mjs.map +0 -1
package/dist/core/index.mjs
CHANGED
|
@@ -1,5 +1,1104 @@
|
|
|
1
|
-
|
|
2
|
-
import '
|
|
3
|
-
import '
|
|
1
|
+
import { createServer as createServer$1 } from 'http';
|
|
2
|
+
import { createServer } from 'https';
|
|
3
|
+
import { readFileSync } from 'fs';
|
|
4
|
+
|
|
5
|
+
// src/core/server.ts
|
|
6
|
+
|
|
7
|
+
// src/core/router.ts
|
|
8
|
+
var PARAM_PREFIX = 58;
|
|
9
|
+
function createNode(segment = "") {
|
|
10
|
+
return {
|
|
11
|
+
segment,
|
|
12
|
+
children: /* @__PURE__ */ new Map(),
|
|
13
|
+
paramChild: null,
|
|
14
|
+
paramName: "",
|
|
15
|
+
handlers: /* @__PURE__ */ new Map(),
|
|
16
|
+
wildcardHandler: null
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
var LRUCache = class {
|
|
20
|
+
capacity;
|
|
21
|
+
map;
|
|
22
|
+
head;
|
|
23
|
+
tail;
|
|
24
|
+
constructor(capacity) {
|
|
25
|
+
this.capacity = capacity;
|
|
26
|
+
this.map = /* @__PURE__ */ new Map();
|
|
27
|
+
this.head = { prev: null, next: null };
|
|
28
|
+
this.tail = { prev: null, next: null };
|
|
29
|
+
this.head.next = this.tail;
|
|
30
|
+
this.tail.prev = this.head;
|
|
31
|
+
}
|
|
32
|
+
get(key) {
|
|
33
|
+
const node = this.map.get(key);
|
|
34
|
+
if (!node) return void 0;
|
|
35
|
+
this.moveToHead(node);
|
|
36
|
+
return node.value;
|
|
37
|
+
}
|
|
38
|
+
set(key, value) {
|
|
39
|
+
const existing = this.map.get(key);
|
|
40
|
+
if (existing) {
|
|
41
|
+
existing.value = value;
|
|
42
|
+
this.moveToHead(existing);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
const node = { value, key, prev: null, next: null };
|
|
46
|
+
this.map.set(key, node);
|
|
47
|
+
this.addToHead(node);
|
|
48
|
+
if (this.map.size > this.capacity) {
|
|
49
|
+
const removed = this.removeTail();
|
|
50
|
+
if (removed) this.map.delete(removed.key);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
addToHead(node) {
|
|
54
|
+
node.prev = this.head;
|
|
55
|
+
node.next = this.head.next;
|
|
56
|
+
this.head.next.prev = node;
|
|
57
|
+
this.head.next = node;
|
|
58
|
+
}
|
|
59
|
+
removeNode(node) {
|
|
60
|
+
node.prev.next = node.next;
|
|
61
|
+
node.next.prev = node.prev;
|
|
62
|
+
}
|
|
63
|
+
moveToHead(node) {
|
|
64
|
+
this.removeNode(node);
|
|
65
|
+
this.addToHead(node);
|
|
66
|
+
}
|
|
67
|
+
removeTail() {
|
|
68
|
+
const node = this.tail.prev;
|
|
69
|
+
if (node === this.head) return null;
|
|
70
|
+
this.removeNode(node);
|
|
71
|
+
return node;
|
|
72
|
+
}
|
|
73
|
+
clear() {
|
|
74
|
+
this.map.clear();
|
|
75
|
+
this.head.next = this.tail;
|
|
76
|
+
this.tail.prev = this.head;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
var Router = class {
|
|
80
|
+
root = createNode();
|
|
81
|
+
cache;
|
|
82
|
+
staticRoutes = /* @__PURE__ */ new Map();
|
|
83
|
+
constructor(cacheSize = 1024) {
|
|
84
|
+
this.cache = new LRUCache(cacheSize);
|
|
85
|
+
}
|
|
86
|
+
add(method, path, handler, middlewares = [], meta) {
|
|
87
|
+
this.cache.clear();
|
|
88
|
+
const normalizedPath = this.normalizePath(path);
|
|
89
|
+
const stored = { handler, middlewares, meta };
|
|
90
|
+
if (!normalizedPath.includes(":") && !normalizedPath.includes("*")) {
|
|
91
|
+
const key = `${method}:${normalizedPath}`;
|
|
92
|
+
this.staticRoutes.set(key, stored);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const segments = normalizedPath.split("/").filter(Boolean);
|
|
96
|
+
let node = this.root;
|
|
97
|
+
for (let i = 0; i < segments.length; i++) {
|
|
98
|
+
const seg = segments[i];
|
|
99
|
+
if (seg === "*") {
|
|
100
|
+
if (!node.wildcardHandler) {
|
|
101
|
+
node.wildcardHandler = /* @__PURE__ */ new Map();
|
|
102
|
+
}
|
|
103
|
+
node.wildcardHandler.set(method, stored);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
if (seg.charCodeAt(0) === PARAM_PREFIX) {
|
|
107
|
+
if (!node.paramChild) {
|
|
108
|
+
node.paramChild = createNode(seg);
|
|
109
|
+
node.paramChild.paramName = seg.slice(1);
|
|
110
|
+
}
|
|
111
|
+
node = node.paramChild;
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
let child = node.children.get(seg);
|
|
115
|
+
if (!child) {
|
|
116
|
+
child = createNode(seg);
|
|
117
|
+
node.children.set(seg, child);
|
|
118
|
+
}
|
|
119
|
+
node = child;
|
|
120
|
+
}
|
|
121
|
+
node.handlers.set(method, stored);
|
|
122
|
+
}
|
|
123
|
+
find(method, path) {
|
|
124
|
+
const normalizedPath = this.normalizePath(path);
|
|
125
|
+
const staticKey = `${method}:${normalizedPath}`;
|
|
126
|
+
const staticRoute = this.staticRoutes.get(staticKey);
|
|
127
|
+
if (staticRoute) {
|
|
128
|
+
return {
|
|
129
|
+
handler: staticRoute.handler,
|
|
130
|
+
params: {},
|
|
131
|
+
middlewares: staticRoute.middlewares,
|
|
132
|
+
meta: staticRoute.meta
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
const cacheKey = staticKey;
|
|
136
|
+
const cached = this.cache.get(cacheKey);
|
|
137
|
+
if (cached !== void 0) {
|
|
138
|
+
return cached ? { ...cached, params: { ...cached.params } } : null;
|
|
139
|
+
}
|
|
140
|
+
const segments = normalizedPath.split("/").filter(Boolean);
|
|
141
|
+
const params = {};
|
|
142
|
+
const result = this.matchNode(this.root, segments, 0, params, method);
|
|
143
|
+
if (result) {
|
|
144
|
+
const match = {
|
|
145
|
+
handler: result.handler,
|
|
146
|
+
params,
|
|
147
|
+
middlewares: result.middlewares,
|
|
148
|
+
meta: result.meta
|
|
149
|
+
};
|
|
150
|
+
this.cache.set(cacheKey, match);
|
|
151
|
+
return { ...match, params: { ...params } };
|
|
152
|
+
}
|
|
153
|
+
this.cache.set(cacheKey, null);
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
matchNode(node, segments, idx, params, method) {
|
|
157
|
+
if (idx === segments.length) {
|
|
158
|
+
return node.handlers.get(method) ?? null;
|
|
159
|
+
}
|
|
160
|
+
const seg = segments[idx];
|
|
161
|
+
const child = node.children.get(seg);
|
|
162
|
+
if (child) {
|
|
163
|
+
const result = this.matchNode(child, segments, idx + 1, params, method);
|
|
164
|
+
if (result) return result;
|
|
165
|
+
}
|
|
166
|
+
if (node.paramChild) {
|
|
167
|
+
params[node.paramChild.paramName] = seg;
|
|
168
|
+
const result = this.matchNode(node.paramChild, segments, idx + 1, params, method);
|
|
169
|
+
if (result) return result;
|
|
170
|
+
delete params[node.paramChild.paramName];
|
|
171
|
+
}
|
|
172
|
+
if (node.wildcardHandler) {
|
|
173
|
+
const route = node.wildcardHandler.get(method);
|
|
174
|
+
if (route) return route;
|
|
175
|
+
}
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
normalizePath(path) {
|
|
179
|
+
if (path === "/") return "/";
|
|
180
|
+
if (path.endsWith("/") && path.length > 1) return path.slice(0, -1);
|
|
181
|
+
return path;
|
|
182
|
+
}
|
|
183
|
+
getAllRoutes() {
|
|
184
|
+
const routes = [];
|
|
185
|
+
for (const [key] of this.staticRoutes) {
|
|
186
|
+
const [method, path] = key.split(":", 2);
|
|
187
|
+
routes.push({ method, path });
|
|
188
|
+
}
|
|
189
|
+
this.collectRoutes(this.root, "", routes);
|
|
190
|
+
return routes;
|
|
191
|
+
}
|
|
192
|
+
collectRoutes(node, prefix, routes) {
|
|
193
|
+
for (const [method] of node.handlers) {
|
|
194
|
+
routes.push({ method, path: prefix || "/" });
|
|
195
|
+
}
|
|
196
|
+
for (const [seg, child] of node.children) {
|
|
197
|
+
this.collectRoutes(child, `${prefix}/${seg}`, routes);
|
|
198
|
+
}
|
|
199
|
+
if (node.paramChild) {
|
|
200
|
+
this.collectRoutes(
|
|
201
|
+
node.paramChild,
|
|
202
|
+
`${prefix}/:${node.paramChild.paramName}`,
|
|
203
|
+
routes
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
if (node.wildcardHandler) {
|
|
207
|
+
for (const [method] of node.wildcardHandler) {
|
|
208
|
+
routes.push({ method, path: `${prefix}/*` });
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
// src/utils/Logger.ts
|
|
215
|
+
var LEVEL_PRIORITY = {
|
|
216
|
+
debug: 0,
|
|
217
|
+
info: 1,
|
|
218
|
+
warn: 2,
|
|
219
|
+
error: 3,
|
|
220
|
+
silent: 4
|
|
221
|
+
};
|
|
222
|
+
var COLORS = {
|
|
223
|
+
reset: "\x1B[0m",
|
|
224
|
+
bold: "\x1B[1m",
|
|
225
|
+
dim: "\x1B[2m",
|
|
226
|
+
red: "\x1B[31m",
|
|
227
|
+
green: "\x1B[32m",
|
|
228
|
+
yellow: "\x1B[33m",
|
|
229
|
+
blue: "\x1B[34m",
|
|
230
|
+
magenta: "\x1B[35m",
|
|
231
|
+
cyan: "\x1B[36m",
|
|
232
|
+
white: "\x1B[37m",
|
|
233
|
+
gray: "\x1B[90m"};
|
|
234
|
+
var METHOD_COLORS = {
|
|
235
|
+
GET: COLORS.green,
|
|
236
|
+
POST: COLORS.blue,
|
|
237
|
+
PUT: COLORS.yellow,
|
|
238
|
+
DELETE: COLORS.red,
|
|
239
|
+
PATCH: COLORS.magenta,
|
|
240
|
+
HEAD: COLORS.cyan,
|
|
241
|
+
OPTIONS: COLORS.gray
|
|
242
|
+
};
|
|
243
|
+
function statusColor(code) {
|
|
244
|
+
if (code < 200) return COLORS.gray;
|
|
245
|
+
if (code < 300) return COLORS.green;
|
|
246
|
+
if (code < 400) return COLORS.cyan;
|
|
247
|
+
if (code < 500) return COLORS.yellow;
|
|
248
|
+
return COLORS.red;
|
|
249
|
+
}
|
|
250
|
+
function formatDuration(ns) {
|
|
251
|
+
const us = Number(ns) / 1e3;
|
|
252
|
+
if (us < 1e3) return `${us.toFixed(0)}\xB5s`;
|
|
253
|
+
const ms = us / 1e3;
|
|
254
|
+
if (ms < 1e3) return `${ms.toFixed(1)}ms`;
|
|
255
|
+
return `${(ms / 1e3).toFixed(2)}s`;
|
|
256
|
+
}
|
|
257
|
+
var Logger = class {
|
|
258
|
+
level;
|
|
259
|
+
useColors;
|
|
260
|
+
showTimestamp;
|
|
261
|
+
prefix;
|
|
262
|
+
constructor(options = {}) {
|
|
263
|
+
this.level = options.level ?? "info";
|
|
264
|
+
this.useColors = options.colors ?? process.stdout.isTTY !== false;
|
|
265
|
+
this.showTimestamp = options.timestamp ?? true;
|
|
266
|
+
this.prefix = options.prefix ?? "azura";
|
|
267
|
+
}
|
|
268
|
+
shouldLog(level) {
|
|
269
|
+
return LEVEL_PRIORITY[level] >= LEVEL_PRIORITY[this.level];
|
|
270
|
+
}
|
|
271
|
+
timestamp() {
|
|
272
|
+
if (!this.showTimestamp) return "";
|
|
273
|
+
const now = /* @__PURE__ */ new Date();
|
|
274
|
+
return `${COLORS.gray}${now.toISOString().slice(11, 23)}${COLORS.reset} `;
|
|
275
|
+
}
|
|
276
|
+
tag(level) {
|
|
277
|
+
if (!this.useColors) return `[${level.toUpperCase()}]`;
|
|
278
|
+
const colorMap = {
|
|
279
|
+
debug: COLORS.gray,
|
|
280
|
+
info: COLORS.blue,
|
|
281
|
+
warn: COLORS.yellow,
|
|
282
|
+
error: COLORS.red
|
|
283
|
+
};
|
|
284
|
+
return `${colorMap[level] ?? ""}[${level.toUpperCase()}]${COLORS.reset}`;
|
|
285
|
+
}
|
|
286
|
+
debug(message, ...args) {
|
|
287
|
+
if (!this.shouldLog("debug")) return;
|
|
288
|
+
console.debug(`${this.timestamp()}${this.tag("debug")} ${message}`, ...args);
|
|
289
|
+
}
|
|
290
|
+
info(message, ...args) {
|
|
291
|
+
if (!this.shouldLog("info")) return;
|
|
292
|
+
console.info(`${this.timestamp()}${this.tag("info")} ${message}`, ...args);
|
|
293
|
+
}
|
|
294
|
+
warn(message, ...args) {
|
|
295
|
+
if (!this.shouldLog("warn")) return;
|
|
296
|
+
console.warn(`${this.timestamp()}${this.tag("warn")} ${message}`, ...args);
|
|
297
|
+
}
|
|
298
|
+
error(message, ...args) {
|
|
299
|
+
if (!this.shouldLog("error")) return;
|
|
300
|
+
console.error(`${this.timestamp()}${this.tag("error")} ${message}`, ...args);
|
|
301
|
+
}
|
|
302
|
+
request(method, path, statusCode, duration) {
|
|
303
|
+
if (!this.shouldLog("info")) return;
|
|
304
|
+
const mc = this.useColors ? METHOD_COLORS[method] ?? COLORS.white : "";
|
|
305
|
+
const sc = this.useColors ? statusColor(statusCode) : "";
|
|
306
|
+
const r = this.useColors ? COLORS.reset : "";
|
|
307
|
+
const dur = formatDuration(duration);
|
|
308
|
+
console.info(
|
|
309
|
+
`${this.timestamp()}${mc}${method.padEnd(7)}${r} ${path} ${sc}${statusCode}${r} ${COLORS.dim}${dur}${r}`
|
|
310
|
+
);
|
|
311
|
+
}
|
|
312
|
+
banner(port, host) {
|
|
313
|
+
if (!this.shouldLog("info")) return;
|
|
314
|
+
const c = this.useColors;
|
|
315
|
+
const lines = [
|
|
316
|
+
"",
|
|
317
|
+
`${c ? COLORS.bold + COLORS.cyan : ""} \u26A1 AzuraJS v3.0.0${c ? COLORS.reset : ""}`,
|
|
318
|
+
"",
|
|
319
|
+
`${c ? COLORS.green : ""} \u279C Local: ${c ? COLORS.bold : ""}http://${host}:${port}/${c ? COLORS.reset : ""}`,
|
|
320
|
+
`${c ? COLORS.dim : ""} \u279C Press Ctrl+C to stop${c ? COLORS.reset : ""}`,
|
|
321
|
+
""
|
|
322
|
+
];
|
|
323
|
+
console.info(lines.join("\n"));
|
|
324
|
+
}
|
|
325
|
+
setLevel(level) {
|
|
326
|
+
this.level = level;
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
new Logger();
|
|
330
|
+
|
|
331
|
+
// src/types/http/status.ts
|
|
332
|
+
var HttpStatusText = {
|
|
333
|
+
200: "OK",
|
|
334
|
+
201: "Created",
|
|
335
|
+
202: "Accepted",
|
|
336
|
+
204: "No Content",
|
|
337
|
+
301: "Moved Permanently",
|
|
338
|
+
302: "Found",
|
|
339
|
+
304: "Not Modified",
|
|
340
|
+
400: "Bad Request",
|
|
341
|
+
401: "Unauthorized",
|
|
342
|
+
403: "Forbidden",
|
|
343
|
+
404: "Not Found",
|
|
344
|
+
405: "Method Not Allowed",
|
|
345
|
+
409: "Conflict",
|
|
346
|
+
410: "Gone",
|
|
347
|
+
422: "Unprocessable Entity",
|
|
348
|
+
429: "Too Many Requests",
|
|
349
|
+
500: "Internal Server Error",
|
|
350
|
+
501: "Not Implemented",
|
|
351
|
+
502: "Bad Gateway",
|
|
352
|
+
503: "Service Unavailable",
|
|
353
|
+
504: "Gateway Timeout"
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
// src/utils/HttpError.ts
|
|
357
|
+
var HttpError = class _HttpError extends Error {
|
|
358
|
+
statusCode;
|
|
359
|
+
details;
|
|
360
|
+
isOperational;
|
|
361
|
+
constructor(statusCode, message, details) {
|
|
362
|
+
super(message ?? HttpStatusText[statusCode] ?? "Unknown Error");
|
|
363
|
+
this.statusCode = statusCode;
|
|
364
|
+
this.details = details;
|
|
365
|
+
this.isOperational = true;
|
|
366
|
+
Object.setPrototypeOf(this, _HttpError.prototype);
|
|
367
|
+
}
|
|
368
|
+
toJSON() {
|
|
369
|
+
const obj = {
|
|
370
|
+
error: {
|
|
371
|
+
statusCode: this.statusCode,
|
|
372
|
+
message: this.message
|
|
373
|
+
}
|
|
374
|
+
};
|
|
375
|
+
if (this.details) obj.error.details = this.details;
|
|
376
|
+
return obj;
|
|
377
|
+
}
|
|
378
|
+
static badRequest(message, details) {
|
|
379
|
+
return new _HttpError(400, message ?? "Bad Request", details);
|
|
380
|
+
}
|
|
381
|
+
static unauthorized(message) {
|
|
382
|
+
return new _HttpError(401, message ?? "Unauthorized");
|
|
383
|
+
}
|
|
384
|
+
static forbidden(message) {
|
|
385
|
+
return new _HttpError(403, message ?? "Forbidden");
|
|
386
|
+
}
|
|
387
|
+
static notFound(message) {
|
|
388
|
+
return new _HttpError(404, message ?? "Not Found");
|
|
389
|
+
}
|
|
390
|
+
static methodNotAllowed(message) {
|
|
391
|
+
return new _HttpError(405, message ?? "Method Not Allowed");
|
|
392
|
+
}
|
|
393
|
+
static conflict(message, details) {
|
|
394
|
+
return new _HttpError(409, message ?? "Conflict", details);
|
|
395
|
+
}
|
|
396
|
+
static unprocessableEntity(message, details) {
|
|
397
|
+
return new _HttpError(422, message ?? "Unprocessable Entity", details);
|
|
398
|
+
}
|
|
399
|
+
static tooManyRequests(message) {
|
|
400
|
+
return new _HttpError(429, message ?? "Too Many Requests");
|
|
401
|
+
}
|
|
402
|
+
static internal(message) {
|
|
403
|
+
return new _HttpError(500, message ?? "Internal Server Error");
|
|
404
|
+
}
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
// src/utils/Parser.ts
|
|
408
|
+
function parseQueryString(qs) {
|
|
409
|
+
const result = /* @__PURE__ */ Object.create(null);
|
|
410
|
+
if (!qs || qs.length === 0) return result;
|
|
411
|
+
let key = "";
|
|
412
|
+
let value = "";
|
|
413
|
+
let startingKey = true;
|
|
414
|
+
let i = qs.charCodeAt(0) === 63 ? 1 : 0;
|
|
415
|
+
for (; i < qs.length; i++) {
|
|
416
|
+
const ch = qs.charCodeAt(i);
|
|
417
|
+
if (ch === 61 && startingKey) {
|
|
418
|
+
startingKey = false;
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
if (ch === 38) {
|
|
422
|
+
if (key.length > 0) {
|
|
423
|
+
const decodedKey = fastDecode(key);
|
|
424
|
+
const decodedVal = fastDecode(value);
|
|
425
|
+
const existing = result[decodedKey];
|
|
426
|
+
if (existing === void 0) {
|
|
427
|
+
result[decodedKey] = decodedVal;
|
|
428
|
+
} else if (typeof existing === "string") {
|
|
429
|
+
result[decodedKey] = [existing, decodedVal];
|
|
430
|
+
} else {
|
|
431
|
+
existing.push(decodedVal);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
key = "";
|
|
435
|
+
value = "";
|
|
436
|
+
startingKey = true;
|
|
437
|
+
continue;
|
|
438
|
+
}
|
|
439
|
+
if (startingKey) {
|
|
440
|
+
key += qs[i];
|
|
441
|
+
} else {
|
|
442
|
+
value += qs[i];
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
if (key.length > 0) {
|
|
446
|
+
const decodedKey = fastDecode(key);
|
|
447
|
+
const decodedVal = fastDecode(value);
|
|
448
|
+
const existing = result[decodedKey];
|
|
449
|
+
if (existing === void 0) {
|
|
450
|
+
result[decodedKey] = decodedVal;
|
|
451
|
+
} else if (typeof existing === "string") {
|
|
452
|
+
result[decodedKey] = [existing, decodedVal];
|
|
453
|
+
} else {
|
|
454
|
+
existing.push(decodedVal);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
return result;
|
|
458
|
+
}
|
|
459
|
+
function fastDecode(str) {
|
|
460
|
+
if (str.indexOf("%") === -1 && str.indexOf("+") === -1) return str;
|
|
461
|
+
try {
|
|
462
|
+
return decodeURIComponent(str.replace(/\+/g, " "));
|
|
463
|
+
} catch {
|
|
464
|
+
return str;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
function parseCookies(header) {
|
|
468
|
+
const cookies = /* @__PURE__ */ Object.create(null);
|
|
469
|
+
if (!header) return cookies;
|
|
470
|
+
let i = 0;
|
|
471
|
+
const len = header.length;
|
|
472
|
+
while (i < len) {
|
|
473
|
+
while (i < len && header.charCodeAt(i) === 32) i++;
|
|
474
|
+
let eqIdx = -1;
|
|
475
|
+
let semiIdx = -1;
|
|
476
|
+
for (let j = i; j < len; j++) {
|
|
477
|
+
const ch = header.charCodeAt(j);
|
|
478
|
+
if (ch === 61 && eqIdx === -1) eqIdx = j;
|
|
479
|
+
if (ch === 59) {
|
|
480
|
+
semiIdx = j;
|
|
481
|
+
break;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
if (eqIdx === -1) {
|
|
485
|
+
i = semiIdx === -1 ? len : semiIdx + 1;
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
const end = semiIdx === -1 ? len : semiIdx;
|
|
489
|
+
const name = header.slice(i, eqIdx).trim();
|
|
490
|
+
let val = header.slice(eqIdx + 1, end).trim();
|
|
491
|
+
if (val.charCodeAt(0) === 34 && val.charCodeAt(val.length - 1) === 34) {
|
|
492
|
+
val = val.slice(1, -1);
|
|
493
|
+
}
|
|
494
|
+
if (cookies[name] === void 0) {
|
|
495
|
+
cookies[name] = fastDecode(val);
|
|
496
|
+
}
|
|
497
|
+
i = end + 1;
|
|
498
|
+
}
|
|
499
|
+
return cookies;
|
|
500
|
+
}
|
|
501
|
+
var CONTENT_TYPE_JSON = "application/json";
|
|
502
|
+
var CONTENT_TYPE_FORM = "application/x-www-form-urlencoded";
|
|
503
|
+
async function parseBody(req) {
|
|
504
|
+
const contentType = req.headers["content-type"] ?? "";
|
|
505
|
+
const contentLength = req.headers["content-length"];
|
|
506
|
+
if (req.method === "GET" || req.method === "HEAD" || req.method === "OPTIONS" || contentLength !== void 0 && contentLength === "0") {
|
|
507
|
+
return void 0;
|
|
508
|
+
}
|
|
509
|
+
const chunks = [];
|
|
510
|
+
let totalSize = 0;
|
|
511
|
+
const maxSize = 10 * 1024 * 1024;
|
|
512
|
+
return new Promise((resolve, reject) => {
|
|
513
|
+
req.on("data", (chunk) => {
|
|
514
|
+
totalSize += chunk.length;
|
|
515
|
+
if (totalSize > maxSize) {
|
|
516
|
+
req.destroy();
|
|
517
|
+
reject(new Error("Request body too large"));
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
chunks.push(chunk);
|
|
521
|
+
});
|
|
522
|
+
req.on("end", () => {
|
|
523
|
+
if (chunks.length === 0) {
|
|
524
|
+
resolve(void 0);
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
const raw = Buffer.concat(chunks).toString("utf-8");
|
|
528
|
+
if (contentType.startsWith(CONTENT_TYPE_JSON)) {
|
|
529
|
+
try {
|
|
530
|
+
resolve(JSON.parse(raw));
|
|
531
|
+
} catch {
|
|
532
|
+
resolve(raw);
|
|
533
|
+
}
|
|
534
|
+
} else if (contentType.startsWith(CONTENT_TYPE_FORM)) {
|
|
535
|
+
resolve(parseQueryString(raw));
|
|
536
|
+
} else {
|
|
537
|
+
resolve(raw);
|
|
538
|
+
}
|
|
539
|
+
});
|
|
540
|
+
req.on("error", reject);
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
function parseUrl(url) {
|
|
544
|
+
let pathname = "";
|
|
545
|
+
let search = "";
|
|
546
|
+
let i = 0;
|
|
547
|
+
for (; i < url.length; i++) {
|
|
548
|
+
if (url.charCodeAt(i) === 63) {
|
|
549
|
+
pathname = url.slice(0, i);
|
|
550
|
+
search = url.slice(i);
|
|
551
|
+
return { pathname, search };
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
return { pathname: url, search: "" };
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
// src/utils/IpResolver.ts
|
|
558
|
+
var PROXY_HEADERS = [
|
|
559
|
+
"x-forwarded-for",
|
|
560
|
+
"x-real-ip",
|
|
561
|
+
"cf-connecting-ip",
|
|
562
|
+
"x-client-ip",
|
|
563
|
+
"x-cluster-client-ip",
|
|
564
|
+
"fastly-client-ip",
|
|
565
|
+
"true-client-ip"
|
|
566
|
+
];
|
|
567
|
+
function resolveIp(req) {
|
|
568
|
+
for (const header of PROXY_HEADERS) {
|
|
569
|
+
const val = req.headers[header];
|
|
570
|
+
if (val) {
|
|
571
|
+
const ip = typeof val === "string" ? val.split(",")[0].trim() : val[0];
|
|
572
|
+
if (ip && ip.length > 0) return normalizeIp(ip);
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
const remoteAddr = req.socket?.remoteAddress ?? "127.0.0.1";
|
|
576
|
+
return normalizeIp(remoteAddr);
|
|
577
|
+
}
|
|
578
|
+
function normalizeIp(ip) {
|
|
579
|
+
if (ip === "::1" || ip === "::ffff:127.0.0.1") return "127.0.0.1";
|
|
580
|
+
if (ip.startsWith("::ffff:")) return ip.slice(7);
|
|
581
|
+
return ip;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
// src/utils/cookies/CookieManager.ts
|
|
585
|
+
function serializeCookie(name, value, options = {}) {
|
|
586
|
+
let cookie = `${encodeURIComponent(name)}=${encodeURIComponent(value)}`;
|
|
587
|
+
if (options.maxAge != null) {
|
|
588
|
+
cookie += `; Max-Age=${Math.floor(options.maxAge)}`;
|
|
589
|
+
}
|
|
590
|
+
if (options.expires) {
|
|
591
|
+
cookie += `; Expires=${options.expires.toUTCString()}`;
|
|
592
|
+
}
|
|
593
|
+
if (options.domain) {
|
|
594
|
+
cookie += `; Domain=${options.domain}`;
|
|
595
|
+
}
|
|
596
|
+
cookie += `; Path=${options.path ?? "/"}`;
|
|
597
|
+
if (options.secure) {
|
|
598
|
+
cookie += "; Secure";
|
|
599
|
+
}
|
|
600
|
+
if (options.httpOnly !== false) {
|
|
601
|
+
cookie += "; HttpOnly";
|
|
602
|
+
}
|
|
603
|
+
if (options.sameSite) {
|
|
604
|
+
cookie += `; SameSite=${options.sameSite}`;
|
|
605
|
+
}
|
|
606
|
+
return cookie;
|
|
607
|
+
}
|
|
608
|
+
function clearCookieHeader(name, options = {}) {
|
|
609
|
+
return serializeCookie(name, "", {
|
|
610
|
+
...options,
|
|
611
|
+
maxAge: 0,
|
|
612
|
+
expires: /* @__PURE__ */ new Date(0)
|
|
613
|
+
});
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// src/core/server.ts
|
|
617
|
+
var CONTROLLER_META_KEY = "azura:controller";
|
|
618
|
+
var ROUTES_META_KEY = "azura:routes";
|
|
619
|
+
var PARAMS_META_KEY = "azura:params";
|
|
620
|
+
var AzuraServer = class {
|
|
621
|
+
server = null;
|
|
622
|
+
router;
|
|
623
|
+
logger;
|
|
624
|
+
middlewares = [];
|
|
625
|
+
plugins = [];
|
|
626
|
+
errorHandler = null;
|
|
627
|
+
config;
|
|
628
|
+
compiledChain = null;
|
|
629
|
+
constructor(config = {}) {
|
|
630
|
+
this.config = {
|
|
631
|
+
environment: config.environment ?? process.env.NODE_ENV ?? "development",
|
|
632
|
+
server: {
|
|
633
|
+
port: config.server?.port ?? 3e3,
|
|
634
|
+
host: config.server?.host ?? "0.0.0.0",
|
|
635
|
+
keepAliveTimeout: config.server?.keepAliveTimeout ?? 72e3,
|
|
636
|
+
headersTimeout: config.server?.headersTimeout ?? 75e3,
|
|
637
|
+
requestTimeout: config.server?.requestTimeout ?? 3e4,
|
|
638
|
+
...config.server
|
|
639
|
+
},
|
|
640
|
+
logging: {
|
|
641
|
+
enabled: config.logging?.enabled ?? true,
|
|
642
|
+
level: config.logging?.level ?? "info",
|
|
643
|
+
timestamp: config.logging?.timestamp ?? true,
|
|
644
|
+
colors: config.logging?.colors ?? true,
|
|
645
|
+
requestLogging: config.logging?.requestLogging ?? true,
|
|
646
|
+
...config.logging
|
|
647
|
+
},
|
|
648
|
+
plugins: config.plugins ?? {},
|
|
649
|
+
debug: config.debug ?? false
|
|
650
|
+
};
|
|
651
|
+
this.router = new Router();
|
|
652
|
+
this.logger = new Logger({
|
|
653
|
+
level: this.config.logging?.enabled === false ? "silent" : this.config.logging?.level,
|
|
654
|
+
colors: this.config.logging?.colors,
|
|
655
|
+
timestamp: this.config.logging?.timestamp
|
|
656
|
+
});
|
|
657
|
+
}
|
|
658
|
+
use(handler) {
|
|
659
|
+
this.compiledChain = null;
|
|
660
|
+
if (handler.length <= 2) {
|
|
661
|
+
this.plugins.push(handler);
|
|
662
|
+
} else {
|
|
663
|
+
this.middlewares.push(handler);
|
|
664
|
+
}
|
|
665
|
+
return this;
|
|
666
|
+
}
|
|
667
|
+
plugin(handler) {
|
|
668
|
+
this.compiledChain = null;
|
|
669
|
+
this.plugins.push(handler);
|
|
670
|
+
return this;
|
|
671
|
+
}
|
|
672
|
+
onError(handler) {
|
|
673
|
+
this.errorHandler = handler;
|
|
674
|
+
return this;
|
|
675
|
+
}
|
|
676
|
+
get(path, ...handlers) {
|
|
677
|
+
return this.route("GET", path, handlers);
|
|
678
|
+
}
|
|
679
|
+
post(path, ...handlers) {
|
|
680
|
+
return this.route("POST", path, handlers);
|
|
681
|
+
}
|
|
682
|
+
put(path, ...handlers) {
|
|
683
|
+
return this.route("PUT", path, handlers);
|
|
684
|
+
}
|
|
685
|
+
delete(path, ...handlers) {
|
|
686
|
+
return this.route("DELETE", path, handlers);
|
|
687
|
+
}
|
|
688
|
+
patch(path, ...handlers) {
|
|
689
|
+
return this.route("PATCH", path, handlers);
|
|
690
|
+
}
|
|
691
|
+
head(path, ...handlers) {
|
|
692
|
+
return this.route("HEAD", path, handlers);
|
|
693
|
+
}
|
|
694
|
+
options(path, ...handlers) {
|
|
695
|
+
return this.route("OPTIONS", path, handlers);
|
|
696
|
+
}
|
|
697
|
+
all(path, ...handlers) {
|
|
698
|
+
const methods = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"];
|
|
699
|
+
for (const method of methods) {
|
|
700
|
+
this.route(method, path, handlers);
|
|
701
|
+
}
|
|
702
|
+
return this;
|
|
703
|
+
}
|
|
704
|
+
route(method, path, handlers) {
|
|
705
|
+
const routeHandler = handlers[handlers.length - 1];
|
|
706
|
+
const middlewares = handlers.slice(0, -1);
|
|
707
|
+
this.router.add(method, path, routeHandler, middlewares);
|
|
708
|
+
return this;
|
|
709
|
+
}
|
|
710
|
+
register(...controllers) {
|
|
711
|
+
for (const Controller of controllers) {
|
|
712
|
+
this.registerController(Controller);
|
|
713
|
+
}
|
|
714
|
+
return this;
|
|
715
|
+
}
|
|
716
|
+
registerController(Controller) {
|
|
717
|
+
const instance = new Controller();
|
|
718
|
+
const prefix = Reflect.getMetadata?.(CONTROLLER_META_KEY, Controller) ?? "";
|
|
719
|
+
const controllerMiddlewares = Reflect.getMetadata?.("azura:middlewares", Controller) ?? [];
|
|
720
|
+
const prototype = Controller.prototype;
|
|
721
|
+
const propertyNames = Object.getOwnPropertyNames(prototype).filter(
|
|
722
|
+
(p) => p !== "constructor"
|
|
723
|
+
);
|
|
724
|
+
for (const propertyKey of propertyNames) {
|
|
725
|
+
const routeMeta = Reflect.getMetadata?.(
|
|
726
|
+
ROUTES_META_KEY,
|
|
727
|
+
prototype,
|
|
728
|
+
propertyKey
|
|
729
|
+
);
|
|
730
|
+
if (!routeMeta) continue;
|
|
731
|
+
const fullPath = this.joinPaths(prefix, routeMeta.path);
|
|
732
|
+
const paramsMeta = Reflect.getMetadata?.(PARAMS_META_KEY, prototype, propertyKey) ?? [];
|
|
733
|
+
const routeMiddlewares = Reflect.getMetadata?.("azura:middlewares", prototype, propertyKey) ?? [];
|
|
734
|
+
const allMiddlewares = [...controllerMiddlewares, ...routeMiddlewares];
|
|
735
|
+
const handler = async (req, res) => {
|
|
736
|
+
const args = this.resolveParams(paramsMeta, req, res);
|
|
737
|
+
const result = await instance[propertyKey](...args);
|
|
738
|
+
if (result !== void 0 && !res.sent) {
|
|
739
|
+
if (typeof result === "object") {
|
|
740
|
+
res.json(result);
|
|
741
|
+
} else {
|
|
742
|
+
res.send(String(result));
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
};
|
|
746
|
+
this.router.add(routeMeta.method, fullPath, handler, allMiddlewares, routeMeta.meta);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
resolveParams(paramsMeta, req, res) {
|
|
750
|
+
if (!paramsMeta || paramsMeta.length === 0) return [req, res];
|
|
751
|
+
const sorted = [...paramsMeta].sort((a, b) => a.index - b.index);
|
|
752
|
+
const args = new Array(sorted.length);
|
|
753
|
+
for (const meta of sorted) {
|
|
754
|
+
let value;
|
|
755
|
+
switch (meta.type) {
|
|
756
|
+
case "body":
|
|
757
|
+
value = meta.name ? req.body?.[meta.name] : req.body;
|
|
758
|
+
break;
|
|
759
|
+
case "query":
|
|
760
|
+
value = meta.name ? req.query[meta.name] : req.query;
|
|
761
|
+
break;
|
|
762
|
+
case "param":
|
|
763
|
+
value = meta.name ? req.params[meta.name] : req.params;
|
|
764
|
+
break;
|
|
765
|
+
case "header":
|
|
766
|
+
value = meta.name ? req.headers[meta.name.toLowerCase()] : req.headers;
|
|
767
|
+
break;
|
|
768
|
+
case "cookie":
|
|
769
|
+
value = meta.name ? req.cookies[meta.name] : req.cookies;
|
|
770
|
+
break;
|
|
771
|
+
case "req":
|
|
772
|
+
value = req;
|
|
773
|
+
break;
|
|
774
|
+
case "res":
|
|
775
|
+
value = res;
|
|
776
|
+
break;
|
|
777
|
+
case "next":
|
|
778
|
+
value = () => {
|
|
779
|
+
};
|
|
780
|
+
break;
|
|
781
|
+
case "ip":
|
|
782
|
+
value = req.ip;
|
|
783
|
+
break;
|
|
784
|
+
case "session":
|
|
785
|
+
value = req.session;
|
|
786
|
+
break;
|
|
787
|
+
default:
|
|
788
|
+
value = void 0;
|
|
789
|
+
}
|
|
790
|
+
if (meta.pipes) {
|
|
791
|
+
for (const pipe of meta.pipes) {
|
|
792
|
+
value = pipe(value);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
args[meta.index] = value;
|
|
796
|
+
}
|
|
797
|
+
return args;
|
|
798
|
+
}
|
|
799
|
+
joinPaths(prefix, path) {
|
|
800
|
+
const p = prefix.endsWith("/") ? prefix.slice(0, -1) : prefix;
|
|
801
|
+
const s = path.startsWith("/") ? path : `/${path}`;
|
|
802
|
+
return `${p}${s}`;
|
|
803
|
+
}
|
|
804
|
+
extendRequest(req, pathname, query) {
|
|
805
|
+
const azReq = req;
|
|
806
|
+
azReq.pathname = pathname;
|
|
807
|
+
azReq.query = query;
|
|
808
|
+
azReq.params = {};
|
|
809
|
+
azReq.cookies = parseCookies(req.headers.cookie);
|
|
810
|
+
azReq.ip = resolveIp(req);
|
|
811
|
+
azReq.protocol = req.socket.encrypted ? "https" : "http";
|
|
812
|
+
azReq.secure = azReq.protocol === "https";
|
|
813
|
+
azReq.hostname = (req.headers.host ?? "localhost").split(":")[0];
|
|
814
|
+
azReq.startTime = process.hrtime.bigint();
|
|
815
|
+
azReq.raw = req;
|
|
816
|
+
return azReq;
|
|
817
|
+
}
|
|
818
|
+
extendResponse(res) {
|
|
819
|
+
const azRes = res;
|
|
820
|
+
azRes.locals = {};
|
|
821
|
+
azRes.sent = false;
|
|
822
|
+
azRes.status = function(code) {
|
|
823
|
+
this.statusCode = code;
|
|
824
|
+
return this;
|
|
825
|
+
};
|
|
826
|
+
azRes.json = function(data) {
|
|
827
|
+
if (this.sent) return;
|
|
828
|
+
this.sent = true;
|
|
829
|
+
const body = JSON.stringify(data);
|
|
830
|
+
this.setHeader("Content-Type", "application/json; charset=utf-8");
|
|
831
|
+
this.setHeader("Content-Length", Buffer.byteLength(body));
|
|
832
|
+
this.end(body);
|
|
833
|
+
};
|
|
834
|
+
azRes.send = function(body) {
|
|
835
|
+
if (this.sent) return;
|
|
836
|
+
this.sent = true;
|
|
837
|
+
if (!this.getHeader("Content-Type")) {
|
|
838
|
+
this.setHeader("Content-Type", typeof body === "string" ? "text/plain; charset=utf-8" : "application/octet-stream");
|
|
839
|
+
}
|
|
840
|
+
const buf = typeof body === "string" ? Buffer.from(body) : body;
|
|
841
|
+
this.setHeader("Content-Length", buf.length);
|
|
842
|
+
this.end(buf);
|
|
843
|
+
};
|
|
844
|
+
azRes.html = function(body) {
|
|
845
|
+
if (this.sent) return;
|
|
846
|
+
this.sent = true;
|
|
847
|
+
this.setHeader("Content-Type", "text/html; charset=utf-8");
|
|
848
|
+
this.setHeader("Content-Length", Buffer.byteLength(body));
|
|
849
|
+
this.end(body);
|
|
850
|
+
};
|
|
851
|
+
azRes.redirect = function(url, code = 302) {
|
|
852
|
+
if (this.sent) return;
|
|
853
|
+
this.sent = true;
|
|
854
|
+
this.statusCode = code;
|
|
855
|
+
this.setHeader("Location", url);
|
|
856
|
+
this.end();
|
|
857
|
+
};
|
|
858
|
+
azRes.cookie = function(name, value, options) {
|
|
859
|
+
const header = serializeCookie(name, value, options);
|
|
860
|
+
const existing = this.getHeader("Set-Cookie");
|
|
861
|
+
if (existing) {
|
|
862
|
+
const arr = Array.isArray(existing) ? existing : [String(existing)];
|
|
863
|
+
arr.push(header);
|
|
864
|
+
this.setHeader("Set-Cookie", arr);
|
|
865
|
+
} else {
|
|
866
|
+
this.setHeader("Set-Cookie", header);
|
|
867
|
+
}
|
|
868
|
+
return this;
|
|
869
|
+
};
|
|
870
|
+
azRes.clearCookie = function(name, options) {
|
|
871
|
+
const header = clearCookieHeader(name, options);
|
|
872
|
+
const existing = this.getHeader("Set-Cookie");
|
|
873
|
+
if (existing) {
|
|
874
|
+
const arr = Array.isArray(existing) ? existing : [String(existing)];
|
|
875
|
+
arr.push(header);
|
|
876
|
+
this.setHeader("Set-Cookie", arr);
|
|
877
|
+
} else {
|
|
878
|
+
this.setHeader("Set-Cookie", header);
|
|
879
|
+
}
|
|
880
|
+
return this;
|
|
881
|
+
};
|
|
882
|
+
azRes.header = function(name, value) {
|
|
883
|
+
this.setHeader(name, value);
|
|
884
|
+
return this;
|
|
885
|
+
};
|
|
886
|
+
azRes.type = function(contentType) {
|
|
887
|
+
this.setHeader("Content-Type", contentType);
|
|
888
|
+
return this;
|
|
889
|
+
};
|
|
890
|
+
azRes.attachment = function(filename) {
|
|
891
|
+
if (filename) {
|
|
892
|
+
this.setHeader("Content-Disposition", `attachment; filename="${filename}"`);
|
|
893
|
+
} else {
|
|
894
|
+
this.setHeader("Content-Disposition", "attachment");
|
|
895
|
+
}
|
|
896
|
+
return this;
|
|
897
|
+
};
|
|
898
|
+
azRes.download = function(_path, _filename) {
|
|
899
|
+
};
|
|
900
|
+
return azRes;
|
|
901
|
+
}
|
|
902
|
+
compileMiddlewareChain() {
|
|
903
|
+
if (this.compiledChain) return this.compiledChain;
|
|
904
|
+
const self = this;
|
|
905
|
+
const globalMiddlewares = [...this.middlewares];
|
|
906
|
+
this.compiledChain = async (req, res) => {
|
|
907
|
+
for (const plugin of self.plugins) {
|
|
908
|
+
if (res.sent) return;
|
|
909
|
+
await new Promise((resolve, reject) => {
|
|
910
|
+
try {
|
|
911
|
+
const result = plugin({ req, res }, (err) => {
|
|
912
|
+
if (err) reject(err);
|
|
913
|
+
else resolve();
|
|
914
|
+
});
|
|
915
|
+
if (result && typeof result.then === "function") {
|
|
916
|
+
result.then(resolve, reject);
|
|
917
|
+
}
|
|
918
|
+
} catch (e) {
|
|
919
|
+
reject(e);
|
|
920
|
+
}
|
|
921
|
+
});
|
|
922
|
+
}
|
|
923
|
+
if (res.sent) return;
|
|
924
|
+
let idx = 0;
|
|
925
|
+
const runMiddleware = async () => {
|
|
926
|
+
if (idx >= globalMiddlewares.length || res.sent) return;
|
|
927
|
+
const mw = globalMiddlewares[idx++];
|
|
928
|
+
await new Promise((resolve, reject) => {
|
|
929
|
+
try {
|
|
930
|
+
const result = mw(req, res, (err) => {
|
|
931
|
+
if (err) reject(err);
|
|
932
|
+
else resolve();
|
|
933
|
+
});
|
|
934
|
+
if (result && typeof result.then === "function") {
|
|
935
|
+
result.then(resolve, reject);
|
|
936
|
+
}
|
|
937
|
+
} catch (e) {
|
|
938
|
+
reject(e);
|
|
939
|
+
}
|
|
940
|
+
});
|
|
941
|
+
await runMiddleware();
|
|
942
|
+
};
|
|
943
|
+
await runMiddleware();
|
|
944
|
+
};
|
|
945
|
+
return this.compiledChain;
|
|
946
|
+
}
|
|
947
|
+
async handleRequest(req, res) {
|
|
948
|
+
const { pathname, search } = parseUrl(req.url ?? "/");
|
|
949
|
+
const query = parseQueryString(search);
|
|
950
|
+
const azReq = this.extendRequest(req, pathname, query);
|
|
951
|
+
const azRes = this.extendResponse(res);
|
|
952
|
+
try {
|
|
953
|
+
const chain = this.compileMiddlewareChain();
|
|
954
|
+
await chain(azReq, azRes);
|
|
955
|
+
if (azRes.sent) return;
|
|
956
|
+
const method = (req.method ?? "GET").toUpperCase();
|
|
957
|
+
const match = this.router.find(method, pathname);
|
|
958
|
+
if (!match) {
|
|
959
|
+
if (pathname === "/favicon.ico") {
|
|
960
|
+
azRes.status(204).send("");
|
|
961
|
+
return;
|
|
962
|
+
}
|
|
963
|
+
throw HttpError.notFound(`Route ${method} ${pathname} not found`);
|
|
964
|
+
}
|
|
965
|
+
azReq.params = match.params;
|
|
966
|
+
if (method !== "GET" && method !== "HEAD" && method !== "OPTIONS") {
|
|
967
|
+
azReq.body = await parseBody(req);
|
|
968
|
+
}
|
|
969
|
+
for (const mw of match.middlewares) {
|
|
970
|
+
if (azRes.sent) return;
|
|
971
|
+
await new Promise((resolve, reject) => {
|
|
972
|
+
try {
|
|
973
|
+
const result2 = mw(azReq, azRes, (err) => {
|
|
974
|
+
if (err) reject(err);
|
|
975
|
+
else resolve();
|
|
976
|
+
});
|
|
977
|
+
if (result2 && typeof result2.then === "function") {
|
|
978
|
+
result2.then(resolve, reject);
|
|
979
|
+
}
|
|
980
|
+
} catch (e) {
|
|
981
|
+
reject(e);
|
|
982
|
+
}
|
|
983
|
+
});
|
|
984
|
+
}
|
|
985
|
+
if (azRes.sent) return;
|
|
986
|
+
const result = await match.handler(azReq, azRes);
|
|
987
|
+
if (result !== void 0 && !azRes.sent) {
|
|
988
|
+
if (typeof result === "object" && result !== null) {
|
|
989
|
+
azRes.json(result);
|
|
990
|
+
} else {
|
|
991
|
+
azRes.send(String(result));
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
} catch (err) {
|
|
995
|
+
this.handleError(err, azReq, azRes);
|
|
996
|
+
} finally {
|
|
997
|
+
if (this.config.logging?.requestLogging) {
|
|
998
|
+
const duration = process.hrtime.bigint() - azReq.startTime;
|
|
999
|
+
this.logger.request(
|
|
1000
|
+
req.method ?? "GET",
|
|
1001
|
+
pathname,
|
|
1002
|
+
res.statusCode,
|
|
1003
|
+
duration
|
|
1004
|
+
);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
handleError(err, req, res) {
|
|
1009
|
+
if (res.sent) return;
|
|
1010
|
+
if (this.errorHandler) {
|
|
1011
|
+
try {
|
|
1012
|
+
this.errorHandler(err, req, res, () => {
|
|
1013
|
+
});
|
|
1014
|
+
return;
|
|
1015
|
+
} catch (handlerErr) {
|
|
1016
|
+
this.logger.error("Error handler threw:", handlerErr);
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
if (err instanceof HttpError) {
|
|
1020
|
+
res.status(err.statusCode).json(err.toJSON());
|
|
1021
|
+
return;
|
|
1022
|
+
}
|
|
1023
|
+
const statusCode = err?.statusCode ?? err?.status ?? 500;
|
|
1024
|
+
const message = this.config.environment === "production" ? "Internal Server Error" : err?.message ?? "Internal Server Error";
|
|
1025
|
+
this.logger.error(`Unhandled error: ${err?.message ?? err}`);
|
|
1026
|
+
if (this.config.debug && err?.stack) {
|
|
1027
|
+
this.logger.error(err.stack);
|
|
1028
|
+
}
|
|
1029
|
+
res.status(statusCode).json({
|
|
1030
|
+
error: {
|
|
1031
|
+
statusCode,
|
|
1032
|
+
message,
|
|
1033
|
+
...this.config.debug ? { stack: err?.stack } : {}
|
|
1034
|
+
}
|
|
1035
|
+
});
|
|
1036
|
+
}
|
|
1037
|
+
async listen(port, callback) {
|
|
1038
|
+
const serverPort = port ?? this.config.server?.port ?? 3e3;
|
|
1039
|
+
const host = this.config.server?.host ?? "0.0.0.0";
|
|
1040
|
+
const handler = (req, res) => {
|
|
1041
|
+
this.handleRequest(req, res).catch((err) => {
|
|
1042
|
+
this.logger.error("Fatal request error:", err);
|
|
1043
|
+
if (!res.headersSent) {
|
|
1044
|
+
res.statusCode = 500;
|
|
1045
|
+
res.end('{"error":{"statusCode":500,"message":"Internal Server Error"}}');
|
|
1046
|
+
}
|
|
1047
|
+
});
|
|
1048
|
+
};
|
|
1049
|
+
if (this.config.server?.https) {
|
|
1050
|
+
const httpsConfig = this.config.server.https;
|
|
1051
|
+
this.server = createServer(
|
|
1052
|
+
{
|
|
1053
|
+
key: readFileSync(httpsConfig.key),
|
|
1054
|
+
cert: readFileSync(httpsConfig.cert),
|
|
1055
|
+
...httpsConfig.ca ? { ca: readFileSync(httpsConfig.ca) } : {}
|
|
1056
|
+
},
|
|
1057
|
+
handler
|
|
1058
|
+
);
|
|
1059
|
+
} else {
|
|
1060
|
+
this.server = createServer$1(handler);
|
|
1061
|
+
}
|
|
1062
|
+
if (this.config.server?.keepAliveTimeout) {
|
|
1063
|
+
this.server.keepAliveTimeout = this.config.server.keepAliveTimeout;
|
|
1064
|
+
}
|
|
1065
|
+
if (this.config.server?.headersTimeout) {
|
|
1066
|
+
this.server.headersTimeout = this.config.server.headersTimeout;
|
|
1067
|
+
}
|
|
1068
|
+
return new Promise((resolve) => {
|
|
1069
|
+
this.server.listen(serverPort, host, () => {
|
|
1070
|
+
this.logger.banner(serverPort, host);
|
|
1071
|
+
callback?.();
|
|
1072
|
+
resolve(this.server);
|
|
1073
|
+
});
|
|
1074
|
+
});
|
|
1075
|
+
}
|
|
1076
|
+
async close() {
|
|
1077
|
+
return new Promise((resolve, reject) => {
|
|
1078
|
+
if (!this.server) {
|
|
1079
|
+
resolve();
|
|
1080
|
+
return;
|
|
1081
|
+
}
|
|
1082
|
+
this.server.close((err) => {
|
|
1083
|
+
if (err) reject(err);
|
|
1084
|
+
else resolve();
|
|
1085
|
+
});
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1088
|
+
getRouter() {
|
|
1089
|
+
return this.router;
|
|
1090
|
+
}
|
|
1091
|
+
getLogger() {
|
|
1092
|
+
return this.logger;
|
|
1093
|
+
}
|
|
1094
|
+
getConfig() {
|
|
1095
|
+
return this.config;
|
|
1096
|
+
}
|
|
1097
|
+
getRoutes() {
|
|
1098
|
+
return this.router.getAllRoutes();
|
|
1099
|
+
}
|
|
1100
|
+
};
|
|
1101
|
+
|
|
1102
|
+
export { AzuraServer, Router };
|
|
4
1103
|
//# sourceMappingURL=index.mjs.map
|
|
5
1104
|
//# sourceMappingURL=index.mjs.map
|