azurajs 1.0.4 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -31,6 +31,37 @@ 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`
@@ -122,6 +153,77 @@ await app.listen();
122
153
  bun run index.ts
123
154
  ```
124
155
 
156
+ ## Alternative: Use with Custom Servers
157
+
158
+ AzuraJS can be used with **any server** that supports the Web Fetch API, just like Hono! This includes Bun, Deno, Cloudflare Workers, and more.
159
+
160
+ ### Using with Bun.serve
161
+
162
+ ```typescript
163
+ import { AzuraClient } from "azurajs";
164
+
165
+ const app = new AzuraClient();
166
+
167
+ app.get("/", (req, res) => {
168
+ res.json({ message: "Hello from Bun!" });
169
+ });
170
+
171
+ // Use with Bun's native server
172
+ const server = Bun.serve({
173
+ port: 3000,
174
+ fetch: app.fetch.bind(app),
175
+ });
176
+
177
+ console.log(`Server running on http://localhost:${server.port}`);
178
+ ```
179
+
180
+ ### Using with Deno
181
+
182
+ ```typescript
183
+ import { AzuraClient } from "azurajs";
184
+
185
+ const app = new AzuraClient();
186
+
187
+ app.get("/", (req, res) => {
188
+ res.json({ message: "Hello from Deno!" });
189
+ });
190
+
191
+ // Use with Deno.serve
192
+ Deno.serve({ port: 3000 }, app.fetch.bind(app));
193
+ ```
194
+
195
+ ### Using with Cloudflare Workers
196
+
197
+ ```typescript
198
+ import { AzuraClient } from "azurajs";
199
+
200
+ const app = new AzuraClient();
201
+
202
+ app.get("/", (req, res) => {
203
+ res.json({ message: "Hello from Cloudflare!" });
204
+ });
205
+
206
+ // Export for Cloudflare Workers
207
+ export default {
208
+ fetch: app.fetch.bind(app),
209
+ };
210
+ ```
211
+
212
+ ### Using with Node.js HTTP
213
+
214
+ ```typescript
215
+ import { AzuraClient } from "azurajs";
216
+
217
+ const app = new AzuraClient();
218
+
219
+ app.get("/", (req, res) => {
220
+ res.json({ message: "Hello from Node.js!" });
221
+ });
222
+
223
+ // Built-in Node.js HTTP server
224
+ await app.listen(3000);
225
+ ```
226
+
125
227
  ## API Reference
126
228
 
127
229
  ### Decorators
@@ -491,7 +593,7 @@ MIT License - see LICENSE file for details
491
593
 
492
594
  - [GitHub Repository](https://github.com/0xviny/azurajs)
493
595
  - [NPM Package](https://www.npmjs.com/package/azurajs)
494
- - [Documentation](https://azurajs.dev)
596
+ - [Documentation](https://azura.js.org/docs/en)
495
597
  - [Examples](https://github.com/0xviny/azurajs/tree/main/examples)
496
598
 
497
599
  ## Support
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "azurajs",
3
- "version": "1.0.4",
3
+ "version": "2.0.0",
4
4
  "description": "Modern TypeScript-first web framework with decorator-based routing, zero dependencies, and built for performance",
5
5
  "main": "src/index.ts",
6
6
  "module": "src/index.ts",
@@ -8,9 +8,79 @@
8
8
  "type": "module",
9
9
  "exports": {
10
10
  ".": {
11
+ "types": "./src/index.ts",
11
12
  "import": "./src/index.ts",
12
- "require": "./src/index.ts",
13
- "types": "./src/index.ts"
13
+ "require": "./src/index.ts"
14
+ },
15
+ "./decorators": {
16
+ "types": "./src/decorators/index.ts",
17
+ "import": "./src/decorators/index.ts",
18
+ "require": "./src/decorators/index.ts"
19
+ },
20
+ "./middleware": {
21
+ "types": "./src/middleware/index.ts",
22
+ "import": "./src/middleware/index.ts",
23
+ "require": "./src/middleware/index.ts"
24
+ },
25
+ "./types": {
26
+ "types": "./src/types/common.type.ts",
27
+ "import": "./src/types/common.type.ts",
28
+ "require": "./src/types/common.type.ts"
29
+ },
30
+ "./router": {
31
+ "types": "./src/infra/Router.ts",
32
+ "import": "./src/infra/Router.ts",
33
+ "require": "./src/infra/Router.ts"
34
+ },
35
+ "./config": {
36
+ "types": "./src/shared/config/ConfigModule.ts",
37
+ "import": "./src/shared/config/ConfigModule.ts",
38
+ "require": "./src/shared/config/ConfigModule.ts"
39
+ },
40
+ "./cors": {
41
+ "types": "./src/shared/plugins/CORSPlugin.ts",
42
+ "import": "./src/shared/plugins/CORSPlugin.ts",
43
+ "require": "./src/shared/plugins/CORSPlugin.ts"
44
+ },
45
+ "./rate-limit": {
46
+ "types": "./src/shared/plugins/RateLimitPlugin.ts",
47
+ "import": "./src/shared/plugins/RateLimitPlugin.ts",
48
+ "require": "./src/shared/plugins/RateLimitPlugin.ts"
49
+ },
50
+ "./cookies": {
51
+ "types": "./src/utils/cookies/ParserCookie.ts",
52
+ "import": "./src/utils/cookies/ParserCookie.ts",
53
+ "require": "./src/utils/cookies/ParserCookie.ts"
54
+ },
55
+ "./validators": {
56
+ "types": "./src/utils/validators/DTOValidator.ts",
57
+ "import": "./src/utils/validators/DTOValidator.ts",
58
+ "require": "./src/utils/validators/DTOValidator.ts"
59
+ },
60
+ "./logger": {
61
+ "types": "./src/utils/Logger.ts",
62
+ "import": "./src/utils/Logger.ts",
63
+ "require": "./src/utils/Logger.ts"
64
+ },
65
+ "./http-error": {
66
+ "types": "./src/infra/utils/HttpError.ts",
67
+ "import": "./src/infra/utils/HttpError.ts",
68
+ "require": "./src/infra/utils/HttpError.ts"
69
+ }
70
+ },
71
+ "typesVersions": {
72
+ "*": {
73
+ "decorators": ["./src/decorators/index.ts"],
74
+ "middleware": ["./src/middleware/index.ts"],
75
+ "types": ["./src/types/common.type.ts"],
76
+ "router": ["./src/infra/Router.ts"],
77
+ "config": ["./src/shared/config/ConfigModule.ts"],
78
+ "cors": ["./src/shared/plugins/CORSPlugin.ts"],
79
+ "rate-limit": ["./src/shared/plugins/RateLimitPlugin.ts"],
80
+ "cookies": ["./src/utils/cookies/ParserCookie.ts"],
81
+ "validators": ["./src/utils/validators/DTOValidator.ts"],
82
+ "logger": ["./src/utils/Logger.ts"],
83
+ "http-error": ["./src/infra/utils/HttpError.ts"]
14
84
  }
15
85
  },
16
86
  "files": [
@@ -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, qs] = url.pathname.split("?");
143
+
144
+ // Parse query
145
+ const rawQuery = parseQS(url.search.slice(1) || "");
146
+ const safeQuery: Record<string, string> = {};
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
+ const cookieHeader = request.headers.get("cookie") || "";
153
+ const cookies = parseCookiesHeader(cookieHeader);
154
+
155
+ let body: any = {};
156
+ if (["POST", "PUT", "PATCH"].includes(request.method.toUpperCase())) {
157
+ const contentType = request.headers.get("content-type") || "";
158
+ try {
159
+ if (contentType.includes("application/json")) {
160
+ body = await request.json();
161
+ } else if (contentType.includes("application/x-www-form-urlencoded")) {
162
+ const text = await request.text();
163
+ const parsed = parseQS(text || "");
164
+ const b: Record<string, string> = {};
165
+ for (const k in parsed) {
166
+ const v = parsed[k];
167
+ b[k] = Array.isArray(v) ? v[0] || "" : (v as string) || "";
168
+ }
169
+ body = b;
170
+ } else {
171
+ body = await request.text();
172
+ }
173
+ } catch {
174
+ body = {};
175
+ }
176
+ }
177
+
178
+ const protocol = url.protocol.slice(0, -1) as "http" | "https";
179
+
180
+ const headersObj: Record<string, string | string[]> = {};
181
+ request.headers.forEach((value, key) => {
182
+ headersObj[key] = value;
183
+ });
184
+
185
+ const rawReq: Partial<RequestServer> = {
186
+ method: request.method,
187
+ url: url.pathname + url.search,
188
+ originalUrl: url.pathname + url.search,
189
+ path: urlPath || "/",
190
+ protocol,
191
+ secure: url.protocol === "https:",
192
+ hostname: url.hostname,
193
+ subdomains: url.hostname ? url.hostname.split(".").slice(0, -2) : [],
194
+ query: safeQuery,
195
+ cookies,
196
+ params: {},
197
+ body,
198
+ headers: headersObj as any,
199
+ get: (name: string) => request.headers.get(name.toLowerCase()) || undefined,
200
+ header: (name: string) => request.headers.get(name.toLowerCase()) || undefined,
201
+ ip: request.headers.get("x-forwarded-for")?.split(",")[0]?.trim() || "",
202
+ ips: request.headers.get("x-forwarded-for")?.split(/\s*,\s*/) || [],
203
+ };
204
+
205
+ // Response builder
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) {