azurajs 3.0.2 → 3.0.4

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.
Files changed (120) hide show
  1. package/README.md +32 -0
  2. package/dist/IpResolver-BVgnGnpf.d.mts +5 -0
  3. package/dist/IpResolver-BVgnGnpf.d.ts +5 -0
  4. package/dist/SwaggerPlugin-C0UZTjaZ.d.ts +6 -0
  5. package/dist/SwaggerPlugin-wr9S4SRG.d.mts +6 -0
  6. package/dist/cookies/index.d.mts +7 -0
  7. package/dist/cookies/index.d.ts +7 -0
  8. package/dist/cookies/index.js +38 -0
  9. package/dist/cookies/index.js.map +1 -0
  10. package/dist/cookies/index.mjs +35 -0
  11. package/dist/cookies/index.mjs.map +1 -0
  12. package/dist/core/index.d.mts +18 -27
  13. package/dist/core/index.d.ts +18 -27
  14. package/dist/core/index.js +214 -14
  15. package/dist/core/index.js.map +1 -1
  16. package/dist/core/index.mjs +214 -14
  17. package/dist/core/index.mjs.map +1 -1
  18. package/dist/cors/index.d.mts +7 -0
  19. package/dist/cors/index.d.ts +7 -0
  20. package/dist/cors/index.js +52 -0
  21. package/dist/cors/index.js.map +1 -0
  22. package/dist/cors/index.mjs +50 -0
  23. package/dist/cors/index.mjs.map +1 -0
  24. package/dist/decorators/index.d.mts +2 -0
  25. package/dist/decorators/index.d.ts +2 -0
  26. package/dist/decorators/index.js +25 -0
  27. package/dist/decorators/index.js.map +1 -1
  28. package/dist/decorators/index.mjs +24 -1
  29. package/dist/decorators/index.mjs.map +1 -1
  30. package/dist/decorators-B6l3CbxC.d.ts +13 -0
  31. package/dist/decorators-D5nY109r.d.mts +13 -0
  32. package/dist/http-error/index.d.mts +18 -0
  33. package/dist/http-error/index.d.ts +18 -0
  34. package/dist/http-error/index.js +81 -0
  35. package/dist/http-error/index.js.map +1 -0
  36. package/dist/http-error/index.mjs +79 -0
  37. package/dist/http-error/index.mjs.map +1 -0
  38. package/dist/index-j6QGMhZU.d.mts +30 -0
  39. package/dist/index-tpPZS_UK.d.ts +30 -0
  40. package/dist/index.d.mts +16 -5
  41. package/dist/index.d.ts +16 -5
  42. package/dist/index.js +1178 -14
  43. package/dist/index.js.map +1 -1
  44. package/dist/index.mjs +1176 -15
  45. package/dist/index.mjs.map +1 -1
  46. package/dist/infra/index.d.mts +6 -0
  47. package/dist/infra/index.d.ts +6 -0
  48. package/dist/infra/index.js +162 -0
  49. package/dist/infra/index.js.map +1 -0
  50. package/dist/infra/index.mjs +159 -0
  51. package/dist/infra/index.mjs.map +1 -0
  52. package/dist/{Logger-iEQNVVSc.d.mts → logger/index.d.mts} +2 -1
  53. package/dist/{Logger-iEQNVVSc.d.ts → logger/index.d.ts} +2 -1
  54. package/dist/logger/index.js +123 -0
  55. package/dist/logger/index.js.map +1 -0
  56. package/dist/logger/index.mjs +120 -0
  57. package/dist/logger/index.mjs.map +1 -0
  58. package/dist/plugins/index.d.mts +8 -6
  59. package/dist/plugins/index.d.ts +8 -6
  60. package/dist/plugins/index.js +1101 -0
  61. package/dist/plugins/index.js.map +1 -1
  62. package/dist/plugins/index.mjs +1101 -1
  63. package/dist/plugins/index.mjs.map +1 -1
  64. package/dist/rate-limit/index.d.mts +7 -0
  65. package/dist/rate-limit/index.d.ts +7 -0
  66. package/dist/rate-limit/index.js +81 -0
  67. package/dist/rate-limit/index.js.map +1 -0
  68. package/dist/rate-limit/index.mjs +79 -0
  69. package/dist/rate-limit/index.mjs.map +1 -0
  70. package/dist/router/index.d.mts +4 -0
  71. package/dist/router/index.d.ts +4 -0
  72. package/dist/router/index.js +218 -0
  73. package/dist/router/index.js.map +1 -0
  74. package/dist/router/index.mjs +216 -0
  75. package/dist/router/index.mjs.map +1 -0
  76. package/dist/routes.type-DZO5VBW2.d.mts +58 -0
  77. package/dist/routes.type-DzHNkCag.d.ts +58 -0
  78. package/dist/swagger/index.d.mts +30 -0
  79. package/dist/swagger/index.d.ts +30 -0
  80. package/dist/swagger/index.js +1136 -0
  81. package/dist/swagger/index.js.map +1 -0
  82. package/dist/swagger/index.mjs +1126 -0
  83. package/dist/swagger/index.mjs.map +1 -0
  84. package/dist/swagger/swagger-ui-modern.html +894 -0
  85. package/dist/swagger.type-Bfn5nGR8.d.mts +42 -0
  86. package/dist/swagger.type-CfDbFCZC.d.ts +42 -0
  87. package/dist/types/index.d.mts +5 -58
  88. package/dist/types/index.d.ts +5 -58
  89. package/dist/utils/index.d.mts +7 -72
  90. package/dist/utils/index.d.ts +7 -72
  91. package/dist/validators/index.d.mts +48 -0
  92. package/dist/validators/index.d.ts +48 -0
  93. package/dist/validators/index.js +144 -0
  94. package/dist/validators/index.js.map +1 -0
  95. package/dist/validators/index.mjs +141 -0
  96. package/dist/validators/index.mjs.map +1 -0
  97. package/package.json +86 -2
  98. package/src/cookies/index.ts +1 -0
  99. package/src/core/index.ts +1 -0
  100. package/src/core/router.ts +26 -15
  101. package/src/core/server.ts +64 -14
  102. package/src/cors/index.ts +2 -0
  103. package/src/decorators/index.ts +2 -0
  104. package/src/http-error/index.ts +1 -0
  105. package/src/infra/index.ts +3 -0
  106. package/src/logger/index.ts +1 -0
  107. package/src/plugins/SwaggerPlugin.ts +45 -0
  108. package/src/plugins/index.ts +1 -0
  109. package/src/rate-limit/index.ts +2 -0
  110. package/src/router/index.ts +1 -0
  111. package/src/swagger/constants.ts +8 -0
  112. package/src/swagger/decorators.ts +35 -0
  113. package/src/swagger/index.ts +10 -0
  114. package/src/swagger/openapi-builder.ts +199 -0
  115. package/src/swagger/swagger-ui-html.ts +24 -0
  116. package/src/swagger/swagger-ui-modern.html +894 -0
  117. package/src/swagger/swagger-ui-template.ts +5 -0
  118. package/src/types/index.ts +7 -0
  119. package/src/types/swagger.type.ts +36 -0
  120. package/src/validators/index.ts +4 -0
@@ -0,0 +1,141 @@
1
+ // src/utils/validators/DTOValidator.ts
2
+ var DTOValidator = class {
3
+ static validate(data, schema) {
4
+ const errors = [];
5
+ if (data == null || typeof data !== "object") {
6
+ return {
7
+ valid: false,
8
+ errors: [{ field: "_root", message: "Request body must be an object" }]
9
+ };
10
+ }
11
+ for (const [field, rule] of Object.entries(schema)) {
12
+ const value = data[field];
13
+ if (rule.required && (value === void 0 || value === null || value === "")) {
14
+ errors.push({
15
+ field,
16
+ message: rule.message ?? `${field} is required`,
17
+ value
18
+ });
19
+ continue;
20
+ }
21
+ if (value === void 0 || value === null) continue;
22
+ if (rule.type) {
23
+ const actualType = Array.isArray(value) ? "array" : typeof value;
24
+ if (actualType !== rule.type) {
25
+ errors.push({
26
+ field,
27
+ message: rule.message ?? `${field} must be of type ${rule.type}`,
28
+ value
29
+ });
30
+ continue;
31
+ }
32
+ }
33
+ if (rule.type === "string" || typeof value === "string") {
34
+ if (rule.min !== void 0 && value.length < rule.min) {
35
+ errors.push({
36
+ field,
37
+ message: rule.message ?? `${field} must be at least ${rule.min} characters`,
38
+ value
39
+ });
40
+ }
41
+ if (rule.max !== void 0 && value.length > rule.max) {
42
+ errors.push({
43
+ field,
44
+ message: rule.message ?? `${field} must be at most ${rule.max} characters`,
45
+ value
46
+ });
47
+ }
48
+ if (rule.pattern && !rule.pattern.test(value)) {
49
+ errors.push({
50
+ field,
51
+ message: rule.message ?? `${field} has invalid format`,
52
+ value
53
+ });
54
+ }
55
+ }
56
+ if (rule.type === "number" || typeof value === "number") {
57
+ if (rule.min !== void 0 && value < rule.min) {
58
+ errors.push({
59
+ field,
60
+ message: rule.message ?? `${field} must be at least ${rule.min}`,
61
+ value
62
+ });
63
+ }
64
+ if (rule.max !== void 0 && value > rule.max) {
65
+ errors.push({
66
+ field,
67
+ message: rule.message ?? `${field} must be at most ${rule.max}`,
68
+ value
69
+ });
70
+ }
71
+ }
72
+ if (rule.type === "array" && Array.isArray(value)) {
73
+ if (rule.min !== void 0 && value.length < rule.min) {
74
+ errors.push({
75
+ field,
76
+ message: rule.message ?? `${field} must have at least ${rule.min} items`,
77
+ value
78
+ });
79
+ }
80
+ if (rule.max !== void 0 && value.length > rule.max) {
81
+ errors.push({
82
+ field,
83
+ message: rule.message ?? `${field} must have at most ${rule.max} items`,
84
+ value
85
+ });
86
+ }
87
+ }
88
+ if (rule.enum && !rule.enum.includes(value)) {
89
+ errors.push({
90
+ field,
91
+ message: rule.message ?? `${field} must be one of: ${rule.enum.join(", ")}`,
92
+ value
93
+ });
94
+ }
95
+ if (rule.custom) {
96
+ const customError = rule.custom(value);
97
+ if (customError) {
98
+ errors.push({ field, message: customError, value });
99
+ }
100
+ }
101
+ }
102
+ return {
103
+ valid: errors.length === 0,
104
+ errors,
105
+ data: errors.length === 0 ? data : void 0
106
+ };
107
+ }
108
+ };
109
+
110
+ // src/utils/validators/SchemaValidator.ts
111
+ var SchemaValidator = class {
112
+ static validate(data, schema) {
113
+ if (typeof schema.safeParse === "function") {
114
+ const result = schema.safeParse(data);
115
+ if (result.success) {
116
+ return { valid: true, data: result.data };
117
+ }
118
+ return { valid: false, errors: result.error };
119
+ }
120
+ if (typeof schema.parse === "function") {
121
+ try {
122
+ const parsed = schema.parse(data);
123
+ return { valid: true, data: parsed };
124
+ } catch (err) {
125
+ return { valid: false, errors: err };
126
+ }
127
+ }
128
+ if (typeof schema.validate === "function") {
129
+ const result = schema.validate(data);
130
+ if (result.error) {
131
+ return { valid: false, errors: result.error };
132
+ }
133
+ return { valid: true, data: result.value };
134
+ }
135
+ return { valid: true, data };
136
+ }
137
+ };
138
+
139
+ export { DTOValidator, SchemaValidator };
140
+ //# sourceMappingURL=index.mjs.map
141
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/validators/DTOValidator.ts","../../src/utils/validators/SchemaValidator.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["export interface ValidationError {\r\n field: string;\r\n message: string;\r\n value?: any;\r\n}\r\n\r\nexport interface ValidationResult {\r\n valid: boolean;\r\n errors: ValidationError[];\r\n data?: any;\r\n}\r\n\r\ntype RuleType = \"string\" | \"number\" | \"boolean\" | \"object\" | \"array\";\r\n\r\ninterface FieldRule {\r\n type?: RuleType;\r\n required?: boolean;\r\n min?: number;\r\n max?: number;\r\n pattern?: RegExp;\r\n enum?: any[];\r\n custom?: (value: any) => string | null;\r\n message?: string;\r\n}\r\n\r\nexport type Schema = Record<string, FieldRule>;\r\n\r\nexport class DTOValidator {\r\n static validate(data: any, schema: Schema): ValidationResult {\r\n const errors: ValidationError[] = [];\r\n\r\n if (data == null || typeof data !== \"object\") {\r\n return {\r\n valid: false,\r\n errors: [{ field: \"_root\", message: \"Request body must be an object\" }],\r\n };\r\n }\r\n\r\n for (const [field, rule] of Object.entries(schema)) {\r\n const value = data[field];\r\n\r\n if (rule.required && (value === undefined || value === null || value === \"\")) {\r\n errors.push({\r\n field,\r\n message: rule.message ?? `${field} is required`,\r\n value,\r\n });\r\n continue;\r\n }\r\n\r\n if (value === undefined || value === null) continue;\r\n\r\n if (rule.type) {\r\n const actualType = Array.isArray(value) ? \"array\" : typeof value;\r\n if (actualType !== rule.type) {\r\n errors.push({\r\n field,\r\n message: rule.message ?? `${field} must be of type ${rule.type}`,\r\n value,\r\n });\r\n continue;\r\n }\r\n }\r\n\r\n if (rule.type === \"string\" || typeof value === \"string\") {\r\n if (rule.min !== undefined && value.length < rule.min) {\r\n errors.push({\r\n field,\r\n message: rule.message ?? `${field} must be at least ${rule.min} characters`,\r\n value,\r\n });\r\n }\r\n if (rule.max !== undefined && value.length > rule.max) {\r\n errors.push({\r\n field,\r\n message: rule.message ?? `${field} must be at most ${rule.max} characters`,\r\n value,\r\n });\r\n }\r\n if (rule.pattern && !rule.pattern.test(value)) {\r\n errors.push({\r\n field,\r\n message: rule.message ?? `${field} has invalid format`,\r\n value,\r\n });\r\n }\r\n }\r\n\r\n if (rule.type === \"number\" || typeof value === \"number\") {\r\n if (rule.min !== undefined && value < rule.min) {\r\n errors.push({\r\n field,\r\n message: rule.message ?? `${field} must be at least ${rule.min}`,\r\n value,\r\n });\r\n }\r\n if (rule.max !== undefined && value > rule.max) {\r\n errors.push({\r\n field,\r\n message: rule.message ?? `${field} must be at most ${rule.max}`,\r\n value,\r\n });\r\n }\r\n }\r\n\r\n if (rule.type === \"array\" && Array.isArray(value)) {\r\n if (rule.min !== undefined && value.length < rule.min) {\r\n errors.push({\r\n field,\r\n message: rule.message ?? `${field} must have at least ${rule.min} items`,\r\n value,\r\n });\r\n }\r\n if (rule.max !== undefined && value.length > rule.max) {\r\n errors.push({\r\n field,\r\n message: rule.message ?? `${field} must have at most ${rule.max} items`,\r\n value,\r\n });\r\n }\r\n }\r\n\r\n if (rule.enum && !rule.enum.includes(value)) {\r\n errors.push({\r\n field,\r\n message: rule.message ?? `${field} must be one of: ${rule.enum.join(\", \")}`,\r\n value,\r\n });\r\n }\r\n\r\n if (rule.custom) {\r\n const customError = rule.custom(value);\r\n if (customError) {\r\n errors.push({ field, message: customError, value });\r\n }\r\n }\r\n }\r\n\r\n return {\r\n valid: errors.length === 0,\r\n errors,\r\n data: errors.length === 0 ? data : undefined,\r\n };\r\n }\r\n}\r\n","export interface ExternalSchema {\r\n parse?: (data: any) => any;\r\n safeParse?: (data: any) => { success: boolean; data?: any; error?: any };\r\n validate?: (data: any) => { value?: any; error?: any };\r\n}\r\n\r\nexport interface SchemaValidationResult {\r\n valid: boolean;\r\n data?: any;\r\n errors?: any;\r\n}\r\n\r\nexport class SchemaValidator {\r\n static validate(data: any, schema: ExternalSchema): SchemaValidationResult {\r\n // Zod-style schema\r\n if (typeof schema.safeParse === \"function\") {\r\n const result = schema.safeParse(data);\r\n if (result.success) {\r\n return { valid: true, data: result.data };\r\n }\r\n return { valid: false, errors: result.error };\r\n }\r\n\r\n // Zod parse (throws on error)\r\n if (typeof schema.parse === \"function\") {\r\n try {\r\n const parsed = schema.parse(data);\r\n return { valid: true, data: parsed };\r\n } catch (err) {\r\n return { valid: false, errors: err };\r\n }\r\n }\r\n\r\n // Joi-style schema\r\n if (typeof schema.validate === \"function\") {\r\n const result = schema.validate(data);\r\n if (result.error) {\r\n return { valid: false, errors: result.error };\r\n }\r\n return { valid: true, data: result.value };\r\n }\r\n\r\n return { valid: true, data };\r\n }\r\n}\r\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "azurajs",
3
- "version": "3.0.2",
3
+ "version": "3.0.4",
4
4
  "description": "Ultra-fast TypeScript-first web framework for Node.js and Bun with decorator-based routing, zero dependencies, and built-in plugin system",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
@@ -70,6 +70,79 @@
70
70
  "require": "./dist/middleware/index.js",
71
71
  "default": "./dist/middleware/index.mjs"
72
72
  },
73
+ "./logger": {
74
+ "types": "./dist/logger/index.d.ts",
75
+ "bun": "./dist/logger/index.mjs",
76
+ "import": "./dist/logger/index.mjs",
77
+ "module": "./dist/logger/index.mjs",
78
+ "require": "./dist/logger/index.js",
79
+ "default": "./dist/logger/index.mjs"
80
+ },
81
+ "./validators": {
82
+ "types": "./dist/validators/index.d.ts",
83
+ "bun": "./dist/validators/index.mjs",
84
+ "import": "./dist/validators/index.mjs",
85
+ "module": "./dist/validators/index.mjs",
86
+ "require": "./dist/validators/index.js",
87
+ "default": "./dist/validators/index.mjs"
88
+ },
89
+ "./router": {
90
+ "types": "./dist/router/index.d.ts",
91
+ "bun": "./dist/router/index.mjs",
92
+ "import": "./dist/router/index.mjs",
93
+ "module": "./dist/router/index.mjs",
94
+ "require": "./dist/router/index.js",
95
+ "default": "./dist/router/index.mjs"
96
+ },
97
+ "./cookies": {
98
+ "types": "./dist/cookies/index.d.ts",
99
+ "bun": "./dist/cookies/index.mjs",
100
+ "import": "./dist/cookies/index.mjs",
101
+ "module": "./dist/cookies/index.mjs",
102
+ "require": "./dist/cookies/index.js",
103
+ "default": "./dist/cookies/index.mjs"
104
+ },
105
+ "./http-error": {
106
+ "types": "./dist/http-error/index.d.ts",
107
+ "bun": "./dist/http-error/index.mjs",
108
+ "import": "./dist/http-error/index.mjs",
109
+ "module": "./dist/http-error/index.mjs",
110
+ "require": "./dist/http-error/index.js",
111
+ "default": "./dist/http-error/index.mjs"
112
+ },
113
+ "./cors": {
114
+ "types": "./dist/cors/index.d.ts",
115
+ "bun": "./dist/cors/index.mjs",
116
+ "import": "./dist/cors/index.mjs",
117
+ "module": "./dist/cors/index.mjs",
118
+ "require": "./dist/cors/index.js",
119
+ "default": "./dist/cors/index.mjs"
120
+ },
121
+ "./rate-limit": {
122
+ "types": "./dist/rate-limit/index.d.ts",
123
+ "bun": "./dist/rate-limit/index.mjs",
124
+ "import": "./dist/rate-limit/index.mjs",
125
+ "module": "./dist/rate-limit/index.mjs",
126
+ "require": "./dist/rate-limit/index.js",
127
+ "default": "./dist/rate-limit/index.mjs"
128
+ },
129
+ "./infra": {
130
+ "types": "./dist/infra/index.d.ts",
131
+ "bun": "./dist/infra/index.mjs",
132
+ "import": "./dist/infra/index.mjs",
133
+ "module": "./dist/infra/index.mjs",
134
+ "require": "./dist/infra/index.js",
135
+ "default": "./dist/infra/index.mjs"
136
+ },
137
+ "./swagger": {
138
+ "types": "./dist/swagger/index.d.ts",
139
+ "bun": "./dist/swagger/index.mjs",
140
+ "import": "./dist/swagger/index.mjs",
141
+ "module": "./dist/swagger/index.mjs",
142
+ "require": "./dist/swagger/index.js",
143
+ "default": "./dist/swagger/index.mjs"
144
+ },
145
+ "./swagger-ui": "./dist/swagger/swagger-ui-modern.html",
73
146
  "./package.json": "./package.json"
74
147
  },
75
148
  "typesVersions": {
@@ -80,7 +153,16 @@
80
153
  "types": ["./dist/types/index.d.ts"],
81
154
  "utils": ["./dist/utils/index.d.ts"],
82
155
  "config": ["./dist/config/index.d.ts"],
83
- "middleware": ["./dist/middleware/index.d.ts"]
156
+ "middleware": ["./dist/middleware/index.d.ts"],
157
+ "logger": ["./dist/logger/index.d.ts"],
158
+ "validators": ["./dist/validators/index.d.ts"],
159
+ "router": ["./dist/router/index.d.ts"],
160
+ "cookies": ["./dist/cookies/index.d.ts"],
161
+ "http-error": ["./dist/http-error/index.d.ts"],
162
+ "cors": ["./dist/cors/index.d.ts"],
163
+ "rate-limit": ["./dist/rate-limit/index.d.ts"],
164
+ "infra": ["./dist/infra/index.d.ts"],
165
+ "swagger": ["./dist/swagger/index.d.ts"]
84
166
  }
85
167
  },
86
168
  "files": [
@@ -90,6 +172,8 @@
90
172
  "LICENSE"
91
173
  ],
92
174
  "scripts": {
175
+ "generate:swagger-ui": "node scripts/embed-swagger-ui.mjs",
176
+ "prebuild": "node scripts/embed-swagger-ui.mjs",
93
177
  "build": "tsup",
94
178
  "build:check": "tsc --noEmit",
95
179
  "dev": "tsup --watch",
@@ -0,0 +1 @@
1
+ export { serializeCookie, clearCookieHeader } from "../utils/cookies/CookieManager.js";
package/src/core/index.ts CHANGED
@@ -1,2 +1,3 @@
1
1
  export { AzuraServer } from "./server.js";
2
2
  export { Router } from "./router.js";
3
+ export type { RouteDocument } from "./router.js";
@@ -1,4 +1,12 @@
1
1
  import type { HttpMethod, RouteHandler, MiddlewareHandler, RouteMatch } from "../types/common.type.js";
2
+ import type { RouteMeta } from "../types/routes.type.js";
3
+
4
+ /** Rota registada com metadados opcionais (OpenAPI / documentação). */
5
+ export interface RouteDocument {
6
+ method: HttpMethod;
7
+ path: string;
8
+ meta?: RouteMeta;
9
+ }
2
10
 
3
11
  const PARAM_PREFIX = 58; // ':'
4
12
 
@@ -243,32 +251,35 @@ export class Router {
243
251
  }
244
252
 
245
253
  getAllRoutes(): Array<{ method: HttpMethod; path: string }> {
246
- const routes: Array<{ method: HttpMethod; path: string }> = [];
254
+ return this.getRouteDocuments().map(({ method, path }) => ({ method, path }));
255
+ }
247
256
 
248
- for (const [key] of this.staticRoutes) {
257
+ /**
258
+ * Todas as rotas com metadados (ex.: OpenAPI) associados ao handler.
259
+ */
260
+ getRouteDocuments(): RouteDocument[] {
261
+ const routes: RouteDocument[] = [];
262
+
263
+ for (const [key, stored] of this.staticRoutes) {
249
264
  const [method, path] = key.split(":", 2) as [HttpMethod, string];
250
- routes.push({ method, path });
265
+ routes.push({ method, path, meta: stored.meta });
251
266
  }
252
267
 
253
- this.collectRoutes(this.root, "", routes);
268
+ this.collectRoutesWithMeta(this.root, "", routes);
254
269
  return routes;
255
270
  }
256
271
 
257
- private collectRoutes(
258
- node: RadixNode,
259
- prefix: string,
260
- routes: Array<{ method: HttpMethod; path: string }>,
261
- ): void {
262
- for (const [method] of node.handlers) {
263
- routes.push({ method, path: prefix || "/" });
272
+ private collectRoutesWithMeta(node: RadixNode, prefix: string, routes: RouteDocument[]): void {
273
+ for (const [method, stored] of node.handlers) {
274
+ routes.push({ method, path: prefix || "/", meta: stored.meta });
264
275
  }
265
276
 
266
277
  for (const [seg, child] of node.children) {
267
- this.collectRoutes(child, `${prefix}/${seg}`, routes);
278
+ this.collectRoutesWithMeta(child, `${prefix}/${seg}`, routes);
268
279
  }
269
280
 
270
281
  if (node.paramChild) {
271
- this.collectRoutes(
282
+ this.collectRoutesWithMeta(
272
283
  node.paramChild,
273
284
  `${prefix}/:${node.paramChild.paramName}`,
274
285
  routes,
@@ -276,8 +287,8 @@ export class Router {
276
287
  }
277
288
 
278
289
  if (node.wildcardHandler) {
279
- for (const [method] of node.wildcardHandler) {
280
- routes.push({ method, path: `${prefix}/*` });
290
+ for (const [method, stored] of node.wildcardHandler) {
291
+ routes.push({ method, path: `${prefix}/*`, meta: stored.meta });
281
292
  }
282
293
  }
283
294
  }
@@ -18,7 +18,26 @@ import type {
18
18
  } from "../types/common.type.js";
19
19
  import type { AzuraConfig } from "../types/config.type.js";
20
20
  import type { PluginHandler } from "../types/plugins/plugin.type.js";
21
- import type { ControllerMetadata, RouteMetadata } from "../types/routes.type.js";
21
+ import type { ControllerMetadata, RouteMeta, RouteMetadata } from "../types/routes.type.js";
22
+ import type { BuildOpenApiOptions } from "../types/swagger.type.js";
23
+ import { API_OPERATION_KEY, API_TAGS_KEY, METHOD_TAGS_KEY } from "../swagger/constants.js";
24
+ import { buildOpenApiDocument } from "../swagger/openapi-builder.js";
25
+
26
+ function isRouteDocumentMeta(o: unknown): o is RouteMeta {
27
+ if (!o || typeof o !== "object" || Array.isArray(o)) return false;
28
+ const x = o as Record<string, unknown>;
29
+ return (
30
+ "summary" in x ||
31
+ "description" in x ||
32
+ "tags" in x ||
33
+ "deprecated" in x ||
34
+ "parameters" in x ||
35
+ "responses" in x ||
36
+ "security" in x ||
37
+ "produces" in x ||
38
+ "consumes" in x
39
+ );
40
+ }
22
41
 
23
42
  const CONTROLLER_META_KEY = "azura:controller";
24
43
  const ROUTES_META_KEY = "azura:routes";
@@ -86,35 +105,35 @@ export class AzuraServer {
86
105
  return this;
87
106
  }
88
107
 
89
- get(path: string, ...handlers: (MiddlewareHandler | Function)[]): this {
108
+ get(path: string, ...handlers: (MiddlewareHandler | Function | RouteMeta)[]): this {
90
109
  return this.route("GET", path, handlers);
91
110
  }
92
111
 
93
- post(path: string, ...handlers: (MiddlewareHandler | Function)[]): this {
112
+ post(path: string, ...handlers: (MiddlewareHandler | Function | RouteMeta)[]): this {
94
113
  return this.route("POST", path, handlers);
95
114
  }
96
115
 
97
- put(path: string, ...handlers: (MiddlewareHandler | Function)[]): this {
116
+ put(path: string, ...handlers: (MiddlewareHandler | Function | RouteMeta)[]): this {
98
117
  return this.route("PUT", path, handlers);
99
118
  }
100
119
 
101
- delete(path: string, ...handlers: (MiddlewareHandler | Function)[]): this {
120
+ delete(path: string, ...handlers: (MiddlewareHandler | Function | RouteMeta)[]): this {
102
121
  return this.route("DELETE", path, handlers);
103
122
  }
104
123
 
105
- patch(path: string, ...handlers: (MiddlewareHandler | Function)[]): this {
124
+ patch(path: string, ...handlers: (MiddlewareHandler | Function | RouteMeta)[]): this {
106
125
  return this.route("PATCH", path, handlers);
107
126
  }
108
127
 
109
- head(path: string, ...handlers: (MiddlewareHandler | Function)[]): this {
128
+ head(path: string, ...handlers: (MiddlewareHandler | Function | RouteMeta)[]): this {
110
129
  return this.route("HEAD", path, handlers);
111
130
  }
112
131
 
113
- options(path: string, ...handlers: (MiddlewareHandler | Function)[]): this {
132
+ options(path: string, ...handlers: (MiddlewareHandler | Function | RouteMeta)[]): this {
114
133
  return this.route("OPTIONS", path, handlers);
115
134
  }
116
135
 
117
- all(path: string, ...handlers: (MiddlewareHandler | Function)[]): this {
136
+ all(path: string, ...handlers: (MiddlewareHandler | Function | RouteMeta)[]): this {
118
137
  const methods: HttpMethod[] = ["GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"];
119
138
  for (const method of methods) {
120
139
  this.route(method, path, handlers);
@@ -122,10 +141,20 @@ export class AzuraServer {
122
141
  return this;
123
142
  }
124
143
 
125
- private route(method: HttpMethod, path: string, handlers: (MiddlewareHandler | Function)[]): this {
126
- const routeHandler = handlers[handlers.length - 1] as any;
127
- const middlewares = handlers.slice(0, -1) as MiddlewareHandler[];
128
- this.router.add(method, path, routeHandler, middlewares);
144
+ private route(method: HttpMethod, path: string, handlers: (MiddlewareHandler | Function | RouteMeta)[]): this {
145
+ let meta: RouteMeta | undefined;
146
+ let list = handlers as unknown[];
147
+ const last = list[list.length - 1];
148
+ if (list.length >= 2 && isRouteDocumentMeta(last)) {
149
+ meta = last;
150
+ list = list.slice(0, -1);
151
+ }
152
+ if (list.length === 0) {
153
+ throw new Error(`Route ${method} ${path} requires a handler function`);
154
+ }
155
+ const routeHandler = list[list.length - 1] as any;
156
+ const middlewares = list.slice(0, -1) as MiddlewareHandler[];
157
+ this.router.add(method, path, routeHandler, middlewares, meta);
129
158
  return this;
130
159
  }
131
160
 
@@ -141,6 +170,7 @@ export class AzuraServer {
141
170
  const prefix: string = Reflect.getMetadata?.(CONTROLLER_META_KEY, Controller) ?? "";
142
171
  const controllerMiddlewares: MiddlewareHandler[] =
143
172
  Reflect.getMetadata?.("azura:middlewares", Controller) ?? [];
173
+ const controllerTags: string[] = Reflect.getMetadata?.(API_TAGS_KEY, Controller) ?? [];
144
174
 
145
175
  const prototype = Controller.prototype;
146
176
  const propertyNames = Object.getOwnPropertyNames(prototype).filter(
@@ -163,6 +193,16 @@ export class AzuraServer {
163
193
 
164
194
  const allMiddlewares = [...controllerMiddlewares, ...routeMiddlewares];
165
195
 
196
+ const methodTags: string[] = Reflect.getMetadata?.(METHOD_TAGS_KEY, prototype, propertyKey) ?? [];
197
+ const opExtra = (Reflect.getMetadata?.(API_OPERATION_KEY, prototype, propertyKey) ??
198
+ {}) as Partial<RouteMeta>;
199
+ const tagList = [...controllerTags, ...methodTags, ...(routeMeta.meta?.tags ?? [])];
200
+ const mergedMeta: RouteMeta | undefined = (() => {
201
+ const m = { ...routeMeta.meta, ...opExtra } as RouteMeta;
202
+ if (tagList.length) m.tags = tagList;
203
+ return Object.keys(m).length ? m : undefined;
204
+ })();
205
+
166
206
  const handler = async (req: AzuraRequest, res: AzuraResponse) => {
167
207
  const args = this.resolveParams(paramsMeta, req, res);
168
208
  const result = await instance[propertyKey](...args);
@@ -175,7 +215,7 @@ export class AzuraServer {
175
215
  }
176
216
  };
177
217
 
178
- this.router.add(routeMeta.method, fullPath, handler, allMiddlewares, routeMeta.meta);
218
+ this.router.add(routeMeta.method, fullPath, handler, allMiddlewares, mergedMeta);
179
219
  }
180
220
  }
181
221
 
@@ -587,4 +627,14 @@ export class AzuraServer {
587
627
  getRoutes(): Array<{ method: HttpMethod; path: string }> {
588
628
  return this.router.getAllRoutes();
589
629
  }
630
+
631
+ /** Rotas com metadados OpenAPI (para Swagger ou export manual). */
632
+ getRouteDocuments() {
633
+ return this.router.getRouteDocuments();
634
+ }
635
+
636
+ /** Gera documento OpenAPI 3.0 a partir das rotas registadas. */
637
+ getOpenApiDocument(options: BuildOpenApiOptions = {}) {
638
+ return buildOpenApiDocument(this.getRouteDocuments(), options);
639
+ }
590
640
  }
@@ -0,0 +1,2 @@
1
+ export { CORSPlugin } from "../plugins/CORSPlugin.js";
2
+ export type { CORSOptions } from "../types/plugins/plugin.type.js";
@@ -21,3 +21,5 @@ export {
21
21
  applyDecorators,
22
22
  Meta,
23
23
  } from "./Route.js";
24
+
25
+ export { ApiTags, ApiOperation } from "../swagger/decorators.js";
@@ -0,0 +1 @@
1
+ export { HttpError } from "../utils/HttpError.js";
@@ -0,0 +1,3 @@
1
+ /** Utilitários de infraestrutura (compatível com o subpath `azurajs/infra` da v2). */
2
+ export { ConfigModule } from "../config/ConfigModule.js";
3
+ export { resolveIp } from "../utils/IpResolver.js";
@@ -0,0 +1 @@
1
+ export { Logger, logger } from "../utils/Logger.js";
@@ -0,0 +1,45 @@
1
+ import type { PluginHandler } from "../types/plugins/plugin.type.js";
2
+ import type { SwaggerPluginOptions } from "../types/swagger.type.js";
3
+ import { buildOpenApiDocument } from "../swagger/openapi-builder.js";
4
+ import { renderSwaggerUiPage } from "../swagger/swagger-ui-html.js";
5
+
6
+ export function SwaggerPlugin(options: SwaggerPluginOptions): PluginHandler {
7
+ const {
8
+ getDocuments,
9
+ specPath = "/openapi.json",
10
+ docsPath = "/docs",
11
+ deepLinking = true,
12
+ servers,
13
+ pathPrefix,
14
+ info,
15
+ } = options;
16
+
17
+ const title = info?.title ?? "API";
18
+
19
+ return async (ctx, next) => {
20
+ const { req, res } = ctx;
21
+ const path = req.pathname ?? "";
22
+
23
+ if (path === specPath || path === `${specPath}/`) {
24
+ const doc = buildOpenApiDocument(getDocuments(), {
25
+ info: info ?? { title, version: "1.0.0" },
26
+ servers,
27
+ pathPrefix,
28
+ });
29
+ res.json(doc);
30
+ return;
31
+ }
32
+
33
+ if (path === docsPath || path === `${docsPath}/`) {
34
+ const html = renderSwaggerUiPage({
35
+ title,
36
+ specUrl: specPath,
37
+ deepLinking,
38
+ });
39
+ res.html(html);
40
+ return;
41
+ }
42
+
43
+ next();
44
+ };
45
+ }
@@ -16,3 +16,4 @@ export type { SSEClient } from "./SSEPlugin.js";
16
16
  export { ProxyPlugin } from "./ProxyPlugin.js";
17
17
  export { MultipartPlugin } from "./MultipartPlugin.js";
18
18
  export type { UploadedFile } from "./MultipartPlugin.js";
19
+ export { SwaggerPlugin } from "./SwaggerPlugin.js";
@@ -0,0 +1,2 @@
1
+ export { RateLimitPlugin } from "../plugins/RateLimitPlugin.js";
2
+ export type { RateLimitOptions } from "../types/plugins/plugin.type.js";
@@ -0,0 +1 @@
1
+ export { Router } from "../core/router.js";
@@ -0,0 +1,8 @@
1
+ /** Tags OpenAPI a nível de controller (todas as rotas do class) */
2
+ export const API_TAGS_KEY = "azura:api-tags";
3
+
4
+ /** Tags por método (independente da ordem em relação a @Get/@Post) */
5
+ export const METHOD_TAGS_KEY = "azura:swagger-method-tags";
6
+
7
+ /** Metadados OpenAPI por método (summary, responses, etc.) */
8
+ export const API_OPERATION_KEY = "azura:swagger-operation";
@@ -0,0 +1,35 @@
1
+ import type { RouteMeta } from "../types/routes.type.js";
2
+ import { API_OPERATION_KEY, API_TAGS_KEY, METHOD_TAGS_KEY } from "./constants.js";
3
+
4
+ /**
5
+ * Tags OpenAPI no controller (todas as rotas) ou num método específico.
6
+ * Compatível com `experimentalDecorators` (assinatura legacy de 3 argumentos em métodos).
7
+ */
8
+ export function ApiTags(...tags: string[]): ClassDecorator & MethodDecorator {
9
+ return function (
10
+ target: object,
11
+ propertyKey?: string | symbol,
12
+ _descriptor?: PropertyDescriptor,
13
+ ): void {
14
+ if (propertyKey === undefined) {
15
+ Reflect.defineMetadata(API_TAGS_KEY, tags, target);
16
+ } else {
17
+ Reflect.defineMetadata(METHOD_TAGS_KEY, tags, target, String(propertyKey));
18
+ }
19
+ } as ClassDecorator & MethodDecorator;
20
+ }
21
+
22
+ /**
23
+ * Metadados OpenAPI (summary, description, responses, parameters, etc.).
24
+ */
25
+ export function ApiOperation(meta: Partial<RouteMeta>): MethodDecorator {
26
+ return function (
27
+ target: object,
28
+ propertyKey: string | symbol,
29
+ _descriptor: PropertyDescriptor,
30
+ ): void {
31
+ const key = String(propertyKey);
32
+ const cur = (Reflect.getMetadata?.(API_OPERATION_KEY, target, key) ?? {}) as Partial<RouteMeta>;
33
+ Reflect.defineMetadata(API_OPERATION_KEY, { ...cur, ...meta }, target, key);
34
+ };
35
+ }
@@ -0,0 +1,10 @@
1
+ /** OpenAPI 3 + UI Swagger (tema escuro minimalista, alinhado ao site AzuraJS). */
2
+ export { buildOpenApiDocument, toOpenApiPath } from "./openapi-builder.js";
3
+ export { SwaggerPlugin } from "../plugins/SwaggerPlugin.js";
4
+ export { renderSwaggerUiPage } from "./swagger-ui-html.js";
5
+ export { ApiTags, ApiOperation } from "./decorators.js";
6
+ export {
7
+ API_TAGS_KEY,
8
+ METHOD_TAGS_KEY,
9
+ API_OPERATION_KEY,
10
+ } from "./constants.js";