azurajs 1.0.4 → 2.1.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 +115 -24
- package/package.json +126 -6
- package/src/infra/Router.ts +19 -11
- package/src/infra/Server.ts +207 -0
- package/src/infra/index.ts +3 -0
- package/src/shared/config/index.ts +2 -0
- package/src/shared/plugins/index.ts +2 -0
- package/src/types/index.ts +5 -0
- package/src/utils/Parser.ts +20 -8
- package/src/utils/cookies/index.ts +2 -0
- package/src/utils/index.ts +4 -0
- package/src/utils/validators/index.ts +2 -0
package/README.md
CHANGED
|
@@ -31,12 +31,43 @@ or with Bun:
|
|
|
31
31
|
bun add azurajs
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
+
## Modular Imports
|
|
35
|
+
|
|
36
|
+
AzuraJS supports modular imports for tree-shaking and better organization:
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
// Main package
|
|
40
|
+
import { AzuraClient, applyDecorators } from "azurajs";
|
|
41
|
+
|
|
42
|
+
// Decorators
|
|
43
|
+
import { Controller, Get, Post, Body, Param } from "azurajs/decorators";
|
|
44
|
+
|
|
45
|
+
// Middleware
|
|
46
|
+
import { createLoggingMiddleware } from "azurajs/middleware";
|
|
47
|
+
|
|
48
|
+
// Plugins
|
|
49
|
+
import { cors } from "azurajs/cors";
|
|
50
|
+
import { rateLimit } from "azurajs/rate-limit";
|
|
51
|
+
|
|
52
|
+
// Utilities
|
|
53
|
+
import { logger } from "azurajs/logger";
|
|
54
|
+
import { HttpError } from "azurajs/http-error";
|
|
55
|
+
import { validateDto } from "azurajs/validators";
|
|
56
|
+
import { parseCookiesHeader } from "azurajs/cookies";
|
|
57
|
+
|
|
58
|
+
// Config
|
|
59
|
+
import type { ConfigTypes } from "azurajs/config";
|
|
60
|
+
|
|
61
|
+
// Router
|
|
62
|
+
import { Router } from "azurajs/router";
|
|
63
|
+
```
|
|
64
|
+
|
|
34
65
|
## Quick Start
|
|
35
66
|
|
|
36
67
|
### 1. Create `azura.config.ts`
|
|
37
68
|
|
|
38
69
|
```typescript
|
|
39
|
-
import type { ConfigTypes } from "azurajs";
|
|
70
|
+
import type { ConfigTypes } from "azurajs/config";
|
|
40
71
|
|
|
41
72
|
const config: ConfigTypes = {
|
|
42
73
|
environment: "development",
|
|
@@ -69,19 +100,10 @@ export default config;
|
|
|
69
100
|
### 2. Create your server
|
|
70
101
|
|
|
71
102
|
```typescript
|
|
72
|
-
import {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
Post,
|
|
77
|
-
Body,
|
|
78
|
-
Param,
|
|
79
|
-
Query,
|
|
80
|
-
Res,
|
|
81
|
-
applyDecorators,
|
|
82
|
-
createLoggingMiddleware
|
|
83
|
-
} from "azurajs";
|
|
84
|
-
import type { ResponseServer } from "azurajs";
|
|
103
|
+
import { AzuraClient, applyDecorators } from "azurajs";
|
|
104
|
+
import { Controller, Get, Post, Body, Param, Query, Res } from "azurajs/decorators";
|
|
105
|
+
import { createLoggingMiddleware } from "azurajs/middleware";
|
|
106
|
+
import type { ResponseServer } from "azurajs/types";
|
|
85
107
|
|
|
86
108
|
@Controller("/api")
|
|
87
109
|
class UserController {
|
|
@@ -122,6 +144,77 @@ await app.listen();
|
|
|
122
144
|
bun run index.ts
|
|
123
145
|
```
|
|
124
146
|
|
|
147
|
+
## Alternative: Use with Custom Servers
|
|
148
|
+
|
|
149
|
+
AzuraJS can be used with **any server** that supports the Web Fetch API, just like Hono! This includes Bun, Deno, Cloudflare Workers, and more.
|
|
150
|
+
|
|
151
|
+
### Using with Bun.serve
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
import { AzuraClient } from "azurajs";
|
|
155
|
+
|
|
156
|
+
const app = new AzuraClient();
|
|
157
|
+
|
|
158
|
+
app.get("/", (req, res) => {
|
|
159
|
+
res.json({ message: "Hello from Bun!" });
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Use with Bun's native server
|
|
163
|
+
const server = Bun.serve({
|
|
164
|
+
port: 3000,
|
|
165
|
+
fetch: app.fetch.bind(app),
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
console.log(`Server running on http://localhost:${server.port}`);
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### Using with Deno
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
import { AzuraClient } from "azurajs";
|
|
175
|
+
|
|
176
|
+
const app = new AzuraClient();
|
|
177
|
+
|
|
178
|
+
app.get("/", (req, res) => {
|
|
179
|
+
res.json({ message: "Hello from Deno!" });
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// Use with Deno.serve
|
|
183
|
+
Deno.serve({ port: 3000 }, app.fetch.bind(app));
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### Using with Cloudflare Workers
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
import { AzuraClient } from "azurajs";
|
|
190
|
+
|
|
191
|
+
const app = new AzuraClient();
|
|
192
|
+
|
|
193
|
+
app.get("/", (req, res) => {
|
|
194
|
+
res.json({ message: "Hello from Cloudflare!" });
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Export for Cloudflare Workers
|
|
198
|
+
export default {
|
|
199
|
+
fetch: app.fetch.bind(app),
|
|
200
|
+
};
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Using with Node.js HTTP
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
import { AzuraClient } from "azurajs";
|
|
207
|
+
|
|
208
|
+
const app = new AzuraClient();
|
|
209
|
+
|
|
210
|
+
app.get("/", (req, res) => {
|
|
211
|
+
res.json({ message: "Hello from Node.js!" });
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// Built-in Node.js HTTP server
|
|
215
|
+
await app.listen(3000);
|
|
216
|
+
```
|
|
217
|
+
|
|
125
218
|
## API Reference
|
|
126
219
|
|
|
127
220
|
### Decorators
|
|
@@ -247,7 +340,7 @@ res.location(url: string)
|
|
|
247
340
|
### Middleware
|
|
248
341
|
|
|
249
342
|
```typescript
|
|
250
|
-
import { createLoggingMiddleware } from "azurajs";
|
|
343
|
+
import { createLoggingMiddleware } from "azurajs/middleware";
|
|
251
344
|
|
|
252
345
|
const app = new AzuraClient();
|
|
253
346
|
|
|
@@ -314,8 +407,9 @@ type ConfigTypes = {
|
|
|
314
407
|
### Complete CRUD API
|
|
315
408
|
|
|
316
409
|
```typescript
|
|
317
|
-
import { AzuraClient,
|
|
318
|
-
import
|
|
410
|
+
import { AzuraClient, applyDecorators } from "azurajs";
|
|
411
|
+
import { Controller, Get, Post, Put, Delete, Body, Param, Res } from "azurajs/decorators";
|
|
412
|
+
import type { ResponseServer } from "azurajs/types";
|
|
319
413
|
|
|
320
414
|
interface User {
|
|
321
415
|
id: number;
|
|
@@ -461,12 +555,9 @@ AzuraJS is designed for high performance:
|
|
|
461
555
|
Full TypeScript support with complete type definitions:
|
|
462
556
|
|
|
463
557
|
```typescript
|
|
464
|
-
import type {
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
ConfigTypes,
|
|
468
|
-
RequestHandler
|
|
469
|
-
} from "azurajs";
|
|
558
|
+
import type { RequestServer, ResponseServer } from "azurajs/types";
|
|
559
|
+
import type { ConfigTypes } from "azurajs/config";
|
|
560
|
+
import type { RequestHandler } from "azurajs/types";
|
|
470
561
|
```
|
|
471
562
|
|
|
472
563
|
> ⚠️ Azura is TypeScript-only.
|
|
@@ -491,7 +582,7 @@ MIT License - see LICENSE file for details
|
|
|
491
582
|
|
|
492
583
|
- [GitHub Repository](https://github.com/0xviny/azurajs)
|
|
493
584
|
- [NPM Package](https://www.npmjs.com/package/azurajs)
|
|
494
|
-
- [Documentation](https://
|
|
585
|
+
- [Documentation](https://azura.js.org/docs/en)
|
|
495
586
|
- [Examples](https://github.com/0xviny/azurajs/tree/main/examples)
|
|
496
587
|
|
|
497
588
|
## Support
|
package/package.json
CHANGED
|
@@ -1,16 +1,136 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "azurajs",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Modern TypeScript-first web framework with decorator-based routing, zero dependencies, and built for performance",
|
|
5
|
-
"main": "src/index.ts",
|
|
6
|
-
"module": "src/index.ts",
|
|
7
|
-
"types": "src/index.ts",
|
|
5
|
+
"main": "./src/index.ts",
|
|
6
|
+
"module": "./src/index.ts",
|
|
7
|
+
"types": "./src/index.ts",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"exports": {
|
|
10
10
|
".": {
|
|
11
|
+
"types": "./src/index.ts",
|
|
11
12
|
"import": "./src/index.ts",
|
|
12
|
-
"
|
|
13
|
-
|
|
13
|
+
"default": "./src/index.ts"
|
|
14
|
+
},
|
|
15
|
+
"./decorators": {
|
|
16
|
+
"types": "./src/decorators/index.ts",
|
|
17
|
+
"import": "./src/decorators/index.ts",
|
|
18
|
+
"default": "./src/decorators/index.ts"
|
|
19
|
+
},
|
|
20
|
+
"./middleware": {
|
|
21
|
+
"types": "./src/middleware/index.ts",
|
|
22
|
+
"import": "./src/middleware/index.ts",
|
|
23
|
+
"default": "./src/middleware/index.ts"
|
|
24
|
+
},
|
|
25
|
+
"./types": {
|
|
26
|
+
"types": "./src/types/index.ts",
|
|
27
|
+
"import": "./src/types/index.ts",
|
|
28
|
+
"default": "./src/types/index.ts"
|
|
29
|
+
},
|
|
30
|
+
"./infra": {
|
|
31
|
+
"types": "./src/infra/index.ts",
|
|
32
|
+
"import": "./src/infra/index.ts",
|
|
33
|
+
"default": "./src/infra/index.ts"
|
|
34
|
+
},
|
|
35
|
+
"./router": {
|
|
36
|
+
"types": "./src/infra/Router.ts",
|
|
37
|
+
"import": "./src/infra/Router.ts",
|
|
38
|
+
"default": "./src/infra/Router.ts"
|
|
39
|
+
},
|
|
40
|
+
"./config": {
|
|
41
|
+
"types": "./src/shared/config/index.ts",
|
|
42
|
+
"import": "./src/shared/config/index.ts",
|
|
43
|
+
"default": "./src/shared/config/index.ts"
|
|
44
|
+
},
|
|
45
|
+
"./plugins": {
|
|
46
|
+
"types": "./src/shared/plugins/index.ts",
|
|
47
|
+
"import": "./src/shared/plugins/index.ts",
|
|
48
|
+
"default": "./src/shared/plugins/index.ts"
|
|
49
|
+
},
|
|
50
|
+
"./cors": {
|
|
51
|
+
"types": "./src/shared/plugins/CORSPlugin.ts",
|
|
52
|
+
"import": "./src/shared/plugins/CORSPlugin.ts",
|
|
53
|
+
"default": "./src/shared/plugins/CORSPlugin.ts"
|
|
54
|
+
},
|
|
55
|
+
"./rate-limit": {
|
|
56
|
+
"types": "./src/shared/plugins/RateLimitPlugin.ts",
|
|
57
|
+
"import": "./src/shared/plugins/RateLimitPlugin.ts",
|
|
58
|
+
"default": "./src/shared/plugins/RateLimitPlugin.ts"
|
|
59
|
+
},
|
|
60
|
+
"./utils": {
|
|
61
|
+
"types": "./src/utils/index.ts",
|
|
62
|
+
"import": "./src/utils/index.ts",
|
|
63
|
+
"default": "./src/utils/index.ts"
|
|
64
|
+
},
|
|
65
|
+
"./cookies": {
|
|
66
|
+
"types": "./src/utils/cookies/index.ts",
|
|
67
|
+
"import": "./src/utils/cookies/index.ts",
|
|
68
|
+
"default": "./src/utils/cookies/index.ts"
|
|
69
|
+
},
|
|
70
|
+
"./validators": {
|
|
71
|
+
"types": "./src/utils/validators/index.ts",
|
|
72
|
+
"import": "./src/utils/validators/index.ts",
|
|
73
|
+
"default": "./src/utils/validators/index.ts"
|
|
74
|
+
},
|
|
75
|
+
"./logger": {
|
|
76
|
+
"types": "./src/utils/Logger.ts",
|
|
77
|
+
"import": "./src/utils/Logger.ts",
|
|
78
|
+
"default": "./src/utils/Logger.ts"
|
|
79
|
+
},
|
|
80
|
+
"./http-error": {
|
|
81
|
+
"types": "./src/infra/utils/HttpError.ts",
|
|
82
|
+
"import": "./src/infra/utils/HttpError.ts",
|
|
83
|
+
"default": "./src/infra/utils/HttpError.ts"
|
|
84
|
+
},
|
|
85
|
+
"./package.json": "./package.json"
|
|
86
|
+
},
|
|
87
|
+
"typesVersions": {
|
|
88
|
+
"*": {
|
|
89
|
+
"*": [
|
|
90
|
+
"./src/*"
|
|
91
|
+
],
|
|
92
|
+
"decorators": [
|
|
93
|
+
"./src/decorators/index.ts"
|
|
94
|
+
],
|
|
95
|
+
"middleware": [
|
|
96
|
+
"./src/middleware/index.ts"
|
|
97
|
+
],
|
|
98
|
+
"types": [
|
|
99
|
+
"./src/types/index.ts"
|
|
100
|
+
],
|
|
101
|
+
"infra": [
|
|
102
|
+
"./src/infra/index.ts"
|
|
103
|
+
],
|
|
104
|
+
"router": [
|
|
105
|
+
"./src/infra/Router.ts"
|
|
106
|
+
],
|
|
107
|
+
"config": [
|
|
108
|
+
"./src/shared/config/index.ts"
|
|
109
|
+
],
|
|
110
|
+
"plugins": [
|
|
111
|
+
"./src/shared/plugins/index.ts"
|
|
112
|
+
],
|
|
113
|
+
"cors": [
|
|
114
|
+
"./src/shared/plugins/CORSPlugin.ts"
|
|
115
|
+
],
|
|
116
|
+
"rate-limit": [
|
|
117
|
+
"./src/shared/plugins/RateLimitPlugin.ts"
|
|
118
|
+
],
|
|
119
|
+
"utils": [
|
|
120
|
+
"./src/utils/index.ts"
|
|
121
|
+
],
|
|
122
|
+
"cookies": [
|
|
123
|
+
"./src/utils/cookies/index.ts"
|
|
124
|
+
],
|
|
125
|
+
"validators": [
|
|
126
|
+
"./src/utils/validators/index.ts"
|
|
127
|
+
],
|
|
128
|
+
"logger": [
|
|
129
|
+
"./src/utils/Logger.ts"
|
|
130
|
+
],
|
|
131
|
+
"http-error": [
|
|
132
|
+
"./src/infra/utils/HttpError.ts"
|
|
133
|
+
]
|
|
14
134
|
}
|
|
15
135
|
},
|
|
16
136
|
"files": [
|
package/src/infra/Router.ts
CHANGED
|
@@ -34,23 +34,31 @@ export class Router {
|
|
|
34
34
|
find(method: string, path: string): MatchResult {
|
|
35
35
|
const segments = path.split("/").filter(Boolean);
|
|
36
36
|
let node = this.root;
|
|
37
|
-
|
|
38
37
|
const params: Record<string, string> = {};
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
params[node.paramName] = seg;
|
|
47
|
-
}
|
|
38
|
+
|
|
39
|
+
for (let i = 0; i < segments.length; i++) {
|
|
40
|
+
const seg = segments[i];
|
|
41
|
+
let child = node.children.get(seg);
|
|
42
|
+
|
|
43
|
+
if (child) {
|
|
44
|
+
node = child;
|
|
48
45
|
} else {
|
|
49
|
-
|
|
46
|
+
child = node.children.get(":");
|
|
47
|
+
if (child) {
|
|
48
|
+
node = child;
|
|
49
|
+
if (node.paramName) {
|
|
50
|
+
params[node.paramName] = seg;
|
|
51
|
+
}
|
|
52
|
+
} else {
|
|
53
|
+
throw new HttpError(404, "Route not found");
|
|
54
|
+
}
|
|
50
55
|
}
|
|
51
56
|
}
|
|
52
57
|
|
|
53
58
|
const handlers = node.handlers.get(method.toUpperCase()) as Handler[];
|
|
59
|
+
if (!handlers) {
|
|
60
|
+
throw new HttpError(404, "Route not found");
|
|
61
|
+
}
|
|
54
62
|
return { handlers, params };
|
|
55
63
|
}
|
|
56
64
|
}
|
package/src/infra/Server.ts
CHANGED
|
@@ -114,6 +114,213 @@ export class AzuraClient {
|
|
|
114
114
|
logger("info", `[${who}] listening on http://localhost:${port}`);
|
|
115
115
|
if (this.opts.server?.ipHost) getIP(port);
|
|
116
116
|
});
|
|
117
|
+
|
|
118
|
+
return this.server;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Fetch handler compatible with Web API (Bun, Deno, Cloudflare Workers, etc.)
|
|
123
|
+
* @example
|
|
124
|
+
* ```typescript
|
|
125
|
+
* const app = new AzuraClient();
|
|
126
|
+
* app.get('/', (req, res) => res.text('Hello World!'));
|
|
127
|
+
*
|
|
128
|
+
* // Use with Bun
|
|
129
|
+
* Bun.serve({
|
|
130
|
+
* port: 3000,
|
|
131
|
+
* fetch: app.fetch.bind(app),
|
|
132
|
+
* });
|
|
133
|
+
*
|
|
134
|
+
* // Use with Deno
|
|
135
|
+
* Deno.serve({ port: 3000 }, app.fetch.bind(app));
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
public async fetch(request: Request): Promise<Response> {
|
|
139
|
+
await this.initPromise;
|
|
140
|
+
|
|
141
|
+
const url = new URL(request.url);
|
|
142
|
+
const urlPath = url.pathname;
|
|
143
|
+
|
|
144
|
+
const safeQuery: Record<string, string> = {};
|
|
145
|
+
if (url.search) {
|
|
146
|
+
const rawQuery = parseQS(url.search.slice(1));
|
|
147
|
+
for (const k in rawQuery) {
|
|
148
|
+
const v = rawQuery[k];
|
|
149
|
+
safeQuery[k] = Array.isArray(v) ? v[0] || "" : v as string;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const cookieHeader = request.headers.get("cookie") || "";
|
|
154
|
+
const cookies = parseCookiesHeader(cookieHeader);
|
|
155
|
+
|
|
156
|
+
let body: any = {};
|
|
157
|
+
if (["POST", "PUT", "PATCH"].includes(request.method.toUpperCase())) {
|
|
158
|
+
const contentType = request.headers.get("content-type") || "";
|
|
159
|
+
try {
|
|
160
|
+
if (contentType.includes("application/json")) {
|
|
161
|
+
body = await request.json();
|
|
162
|
+
} else if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
163
|
+
const text = await request.text();
|
|
164
|
+
const parsed = parseQS(text || "");
|
|
165
|
+
const b: Record<string, string> = {};
|
|
166
|
+
for (const k in parsed) {
|
|
167
|
+
const v = parsed[k];
|
|
168
|
+
b[k] = Array.isArray(v) ? v[0] || "" : (v as string) || "";
|
|
169
|
+
}
|
|
170
|
+
body = b;
|
|
171
|
+
} else {
|
|
172
|
+
body = await request.text();
|
|
173
|
+
}
|
|
174
|
+
} catch {
|
|
175
|
+
body = {};
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const protocol = url.protocol.slice(0, -1) as "http" | "https";
|
|
180
|
+
|
|
181
|
+
const headersObj: Record<string, string | string[]> = {};
|
|
182
|
+
request.headers.forEach((value, key) => {
|
|
183
|
+
headersObj[key] = value;
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
const rawReq: Partial<RequestServer> = {
|
|
187
|
+
method: request.method,
|
|
188
|
+
url: url.pathname + url.search,
|
|
189
|
+
originalUrl: url.pathname + url.search,
|
|
190
|
+
path: urlPath || "/",
|
|
191
|
+
protocol,
|
|
192
|
+
secure: url.protocol === "https:",
|
|
193
|
+
hostname: url.hostname,
|
|
194
|
+
subdomains: url.hostname ? url.hostname.split(".").slice(0, -2) : [],
|
|
195
|
+
query: safeQuery,
|
|
196
|
+
cookies,
|
|
197
|
+
params: {},
|
|
198
|
+
body,
|
|
199
|
+
headers: headersObj as any,
|
|
200
|
+
get: (name: string) => request.headers.get(name.toLowerCase()) || undefined,
|
|
201
|
+
header: (name: string) => request.headers.get(name.toLowerCase()) || undefined,
|
|
202
|
+
ip: request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || "",
|
|
203
|
+
ips: request.headers.get("x-forwarded-for")?.split(/\s*,\s*/) || [],
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
let statusCode = 200;
|
|
207
|
+
const responseHeaders = new Headers();
|
|
208
|
+
let responseBody: any = null;
|
|
209
|
+
|
|
210
|
+
const rawRes: Partial<ResponseServer> = {
|
|
211
|
+
statusCode,
|
|
212
|
+
status: (code: number) => {
|
|
213
|
+
statusCode = code;
|
|
214
|
+
return rawRes as ResponseServer;
|
|
215
|
+
},
|
|
216
|
+
set: (field: string, value: string | number | string[]) => {
|
|
217
|
+
responseHeaders.set(field, String(value));
|
|
218
|
+
return rawRes as ResponseServer;
|
|
219
|
+
},
|
|
220
|
+
header: (field: string, value: string | number | string[]) => {
|
|
221
|
+
responseHeaders.set(field, String(value));
|
|
222
|
+
return rawRes as ResponseServer;
|
|
223
|
+
},
|
|
224
|
+
get: (field: string) => responseHeaders.get(field) || undefined,
|
|
225
|
+
type: (t: string) => {
|
|
226
|
+
responseHeaders.set("Content-Type", t);
|
|
227
|
+
return rawRes as ResponseServer;
|
|
228
|
+
},
|
|
229
|
+
contentType: (t: string) => {
|
|
230
|
+
responseHeaders.set("Content-Type", t);
|
|
231
|
+
return rawRes as ResponseServer;
|
|
232
|
+
},
|
|
233
|
+
location: (u: string) => {
|
|
234
|
+
responseHeaders.set("Location", u);
|
|
235
|
+
return rawRes as ResponseServer;
|
|
236
|
+
},
|
|
237
|
+
redirect: ((a: number | string, b?: string) => {
|
|
238
|
+
if (typeof a === "number") {
|
|
239
|
+
statusCode = a;
|
|
240
|
+
responseHeaders.set("Location", b!);
|
|
241
|
+
} else {
|
|
242
|
+
statusCode = 302;
|
|
243
|
+
responseHeaders.set("Location", a);
|
|
244
|
+
}
|
|
245
|
+
return rawRes as ResponseServer;
|
|
246
|
+
}) as ResponseServer["redirect"],
|
|
247
|
+
cookie: (name: string, val: string, opts: CookieOptions = {}) => {
|
|
248
|
+
const s = serializeCookie(name, val, opts);
|
|
249
|
+
const prev = responseHeaders.get("Set-Cookie");
|
|
250
|
+
if (prev) {
|
|
251
|
+
responseHeaders.append("Set-Cookie", s);
|
|
252
|
+
} else {
|
|
253
|
+
responseHeaders.set("Set-Cookie", s);
|
|
254
|
+
}
|
|
255
|
+
return rawRes as ResponseServer;
|
|
256
|
+
},
|
|
257
|
+
clearCookie: (name: string, opts: CookieOptions = {}) => {
|
|
258
|
+
return rawRes.cookie!(name, "", { ...opts, expires: new Date(1), maxAge: 0 });
|
|
259
|
+
},
|
|
260
|
+
send: (b: any) => {
|
|
261
|
+
if (b === undefined || b === null) {
|
|
262
|
+
responseBody = "";
|
|
263
|
+
} else if (typeof b === "object") {
|
|
264
|
+
responseHeaders.set("Content-Type", "application/json");
|
|
265
|
+
responseBody = JSON.stringify(b);
|
|
266
|
+
} else {
|
|
267
|
+
responseBody = String(b);
|
|
268
|
+
}
|
|
269
|
+
return rawRes as ResponseServer;
|
|
270
|
+
},
|
|
271
|
+
json: (b: any) => {
|
|
272
|
+
responseHeaders.set("Content-Type", "application/json");
|
|
273
|
+
responseBody = JSON.stringify(b);
|
|
274
|
+
return rawRes as ResponseServer;
|
|
275
|
+
},
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
const errorHandler = (err: any) => {
|
|
279
|
+
statusCode = err instanceof HttpError ? err.status : 500;
|
|
280
|
+
responseHeaders.set("Content-Type", "application/json");
|
|
281
|
+
responseBody = JSON.stringify(
|
|
282
|
+
err instanceof HttpError
|
|
283
|
+
? err.payload ?? { error: err.message || "Internal Server Error" }
|
|
284
|
+
: { error: err?.message || "Internal Server Error" }
|
|
285
|
+
);
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
try {
|
|
289
|
+
const { handlers, params } = this.router.find(request.method, urlPath || "/");
|
|
290
|
+
rawReq.params = params || {};
|
|
291
|
+
|
|
292
|
+
const chain = [
|
|
293
|
+
...this.middlewares.map(adaptRequestHandler),
|
|
294
|
+
...handlers.map(adaptRequestHandler),
|
|
295
|
+
];
|
|
296
|
+
|
|
297
|
+
let idx = 0;
|
|
298
|
+
const next = async (err?: any) => {
|
|
299
|
+
if (err) return errorHandler(err);
|
|
300
|
+
if (idx >= chain.length) return;
|
|
301
|
+
const fn = chain[idx++];
|
|
302
|
+
try {
|
|
303
|
+
await fn({
|
|
304
|
+
request: rawReq as RequestServer,
|
|
305
|
+
response: rawRes as ResponseServer,
|
|
306
|
+
req: rawReq as RequestServer,
|
|
307
|
+
res: rawRes as ResponseServer,
|
|
308
|
+
next,
|
|
309
|
+
});
|
|
310
|
+
} catch (e) {
|
|
311
|
+
return errorHandler(e);
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
await next();
|
|
316
|
+
} catch (err) {
|
|
317
|
+
errorHandler(err);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return new Response(responseBody, {
|
|
321
|
+
status: statusCode,
|
|
322
|
+
headers: responseHeaders,
|
|
323
|
+
});
|
|
117
324
|
}
|
|
118
325
|
|
|
119
326
|
private async handle(rawReq: RequestServer, rawRes: ResponseServer) {
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type { RequestServer } from "./http/request.type";
|
|
2
|
+
export type { ResponseServer } from "./http/response.type";
|
|
3
|
+
export type { RequestHandler, HttpContext } from "./common.type";
|
|
4
|
+
export type { RouteDefinition, ParamDefinition, ParamSource } from "./routes.type";
|
|
5
|
+
export type { Schema as ValidationSchema } from "./validations.type";
|
package/src/utils/Parser.ts
CHANGED
|
@@ -1,16 +1,28 @@
|
|
|
1
1
|
export function parseQS(qs: string): Record<string, string | string[]> {
|
|
2
2
|
const out: Record<string, string | string[]> = {};
|
|
3
3
|
if (!qs) return out;
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
|
|
5
|
+
const parts = qs.split("&");
|
|
6
|
+
for (let i = 0; i < parts.length; i++) {
|
|
7
|
+
const p = parts[i];
|
|
6
8
|
if (!p) continue;
|
|
9
|
+
|
|
7
10
|
const idx = p.indexOf("=");
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
if (idx === -1) {
|
|
12
|
+
out[decodeURIComponent(p)] = "";
|
|
13
|
+
continue;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const k = decodeURIComponent(p.slice(0, idx));
|
|
17
|
+
const v = decodeURIComponent(p.slice(idx + 1));
|
|
18
|
+
|
|
19
|
+
const existing = out[k];
|
|
20
|
+
if (existing !== undefined) {
|
|
21
|
+
if (Array.isArray(existing)) {
|
|
22
|
+
existing.push(v);
|
|
23
|
+
} else {
|
|
24
|
+
out[k] = [existing as string, v];
|
|
25
|
+
}
|
|
14
26
|
} else {
|
|
15
27
|
out[k] = v;
|
|
16
28
|
}
|