@hazeljs/core 0.2.0-beta.1

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 (194) hide show
  1. package/README.md +522 -0
  2. package/dist/__tests__/container.test.d.ts +2 -0
  3. package/dist/__tests__/container.test.d.ts.map +1 -0
  4. package/dist/__tests__/container.test.js +454 -0
  5. package/dist/__tests__/decorators.test.d.ts +2 -0
  6. package/dist/__tests__/decorators.test.d.ts.map +1 -0
  7. package/dist/__tests__/decorators.test.js +693 -0
  8. package/dist/__tests__/errors/http.error.test.d.ts +2 -0
  9. package/dist/__tests__/errors/http.error.test.d.ts.map +1 -0
  10. package/dist/__tests__/errors/http.error.test.js +117 -0
  11. package/dist/__tests__/filters/exception-filter.test.d.ts +2 -0
  12. package/dist/__tests__/filters/exception-filter.test.d.ts.map +1 -0
  13. package/dist/__tests__/filters/exception-filter.test.js +135 -0
  14. package/dist/__tests__/filters/http-exception.filter.test.d.ts +2 -0
  15. package/dist/__tests__/filters/http-exception.filter.test.d.ts.map +1 -0
  16. package/dist/__tests__/filters/http-exception.filter.test.js +119 -0
  17. package/dist/__tests__/hazel-app.test.d.ts +2 -0
  18. package/dist/__tests__/hazel-app.test.d.ts.map +1 -0
  19. package/dist/__tests__/hazel-app.test.js +682 -0
  20. package/dist/__tests__/hazel-module.test.d.ts +2 -0
  21. package/dist/__tests__/hazel-module.test.d.ts.map +1 -0
  22. package/dist/__tests__/hazel-module.test.js +408 -0
  23. package/dist/__tests__/hazel-response.test.d.ts +2 -0
  24. package/dist/__tests__/hazel-response.test.d.ts.map +1 -0
  25. package/dist/__tests__/hazel-response.test.js +138 -0
  26. package/dist/__tests__/health.test.d.ts +2 -0
  27. package/dist/__tests__/health.test.d.ts.map +1 -0
  28. package/dist/__tests__/health.test.js +147 -0
  29. package/dist/__tests__/index.test.d.ts +2 -0
  30. package/dist/__tests__/index.test.d.ts.map +1 -0
  31. package/dist/__tests__/index.test.js +239 -0
  32. package/dist/__tests__/interceptors/interceptor.test.d.ts +2 -0
  33. package/dist/__tests__/interceptors/interceptor.test.d.ts.map +1 -0
  34. package/dist/__tests__/interceptors/interceptor.test.js +166 -0
  35. package/dist/__tests__/logger.test.d.ts +2 -0
  36. package/dist/__tests__/logger.test.d.ts.map +1 -0
  37. package/dist/__tests__/logger.test.js +141 -0
  38. package/dist/__tests__/middleware/cors.test.d.ts +2 -0
  39. package/dist/__tests__/middleware/cors.test.d.ts.map +1 -0
  40. package/dist/__tests__/middleware/cors.test.js +129 -0
  41. package/dist/__tests__/middleware/csrf.test.d.ts +2 -0
  42. package/dist/__tests__/middleware/csrf.test.d.ts.map +1 -0
  43. package/dist/__tests__/middleware/csrf.test.js +247 -0
  44. package/dist/__tests__/middleware/global-middleware.test.d.ts +2 -0
  45. package/dist/__tests__/middleware/global-middleware.test.d.ts.map +1 -0
  46. package/dist/__tests__/middleware/global-middleware.test.js +259 -0
  47. package/dist/__tests__/middleware/rate-limit.test.d.ts +2 -0
  48. package/dist/__tests__/middleware/rate-limit.test.d.ts.map +1 -0
  49. package/dist/__tests__/middleware/rate-limit.test.js +264 -0
  50. package/dist/__tests__/middleware/security-headers.test.d.ts +2 -0
  51. package/dist/__tests__/middleware/security-headers.test.d.ts.map +1 -0
  52. package/dist/__tests__/middleware/security-headers.test.js +229 -0
  53. package/dist/__tests__/middleware/timeout.test.d.ts +2 -0
  54. package/dist/__tests__/middleware/timeout.test.d.ts.map +1 -0
  55. package/dist/__tests__/middleware/timeout.test.js +132 -0
  56. package/dist/__tests__/middleware.test.d.ts +2 -0
  57. package/dist/__tests__/middleware.test.d.ts.map +1 -0
  58. package/dist/__tests__/middleware.test.js +180 -0
  59. package/dist/__tests__/pipes/pipe.test.d.ts +2 -0
  60. package/dist/__tests__/pipes/pipe.test.d.ts.map +1 -0
  61. package/dist/__tests__/pipes/pipe.test.js +245 -0
  62. package/dist/__tests__/pipes/validation.pipe.test.d.ts +2 -0
  63. package/dist/__tests__/pipes/validation.pipe.test.d.ts.map +1 -0
  64. package/dist/__tests__/pipes/validation.pipe.test.js +297 -0
  65. package/dist/__tests__/request-parser.test.d.ts +2 -0
  66. package/dist/__tests__/request-parser.test.d.ts.map +1 -0
  67. package/dist/__tests__/request-parser.test.js +182 -0
  68. package/dist/__tests__/router.test.d.ts +2 -0
  69. package/dist/__tests__/router.test.d.ts.map +1 -0
  70. package/dist/__tests__/router.test.js +680 -0
  71. package/dist/__tests__/routing/route-matcher.test.d.ts +2 -0
  72. package/dist/__tests__/routing/route-matcher.test.d.ts.map +1 -0
  73. package/dist/__tests__/routing/route-matcher.test.js +219 -0
  74. package/dist/__tests__/routing/version.decorator.test.d.ts +2 -0
  75. package/dist/__tests__/routing/version.decorator.test.d.ts.map +1 -0
  76. package/dist/__tests__/routing/version.decorator.test.js +298 -0
  77. package/dist/__tests__/service.test.d.ts +2 -0
  78. package/dist/__tests__/service.test.d.ts.map +1 -0
  79. package/dist/__tests__/service.test.js +121 -0
  80. package/dist/__tests__/shutdown.test.d.ts +2 -0
  81. package/dist/__tests__/shutdown.test.d.ts.map +1 -0
  82. package/dist/__tests__/shutdown.test.js +250 -0
  83. package/dist/__tests__/testing/testing.module.test.d.ts +2 -0
  84. package/dist/__tests__/testing/testing.module.test.d.ts.map +1 -0
  85. package/dist/__tests__/testing/testing.module.test.js +370 -0
  86. package/dist/__tests__/upload/file-upload.test.d.ts +2 -0
  87. package/dist/__tests__/upload/file-upload.test.d.ts.map +1 -0
  88. package/dist/__tests__/upload/file-upload.test.js +498 -0
  89. package/dist/__tests__/utils/sanitize.test.d.ts +2 -0
  90. package/dist/__tests__/utils/sanitize.test.d.ts.map +1 -0
  91. package/dist/__tests__/utils/sanitize.test.js +291 -0
  92. package/dist/__tests__/validator.test.d.ts +2 -0
  93. package/dist/__tests__/validator.test.d.ts.map +1 -0
  94. package/dist/__tests__/validator.test.js +300 -0
  95. package/dist/container.d.ts +80 -0
  96. package/dist/container.d.ts.map +1 -0
  97. package/dist/container.js +271 -0
  98. package/dist/decorators.d.ts +92 -0
  99. package/dist/decorators.d.ts.map +1 -0
  100. package/dist/decorators.js +343 -0
  101. package/dist/errors/http.error.d.ts +31 -0
  102. package/dist/errors/http.error.d.ts.map +1 -0
  103. package/dist/errors/http.error.js +62 -0
  104. package/dist/filters/exception-filter.d.ts +39 -0
  105. package/dist/filters/exception-filter.d.ts.map +1 -0
  106. package/dist/filters/exception-filter.js +38 -0
  107. package/dist/filters/http-exception.filter.d.ts +9 -0
  108. package/dist/filters/http-exception.filter.d.ts.map +1 -0
  109. package/dist/filters/http-exception.filter.js +42 -0
  110. package/dist/hazel-app.d.ts +78 -0
  111. package/dist/hazel-app.d.ts.map +1 -0
  112. package/dist/hazel-app.js +453 -0
  113. package/dist/hazel-module.d.ts +20 -0
  114. package/dist/hazel-module.d.ts.map +1 -0
  115. package/dist/hazel-module.js +109 -0
  116. package/dist/hazel-response.d.ts +20 -0
  117. package/dist/hazel-response.d.ts.map +1 -0
  118. package/dist/hazel-response.js +68 -0
  119. package/dist/health.d.ts +73 -0
  120. package/dist/health.d.ts.map +1 -0
  121. package/dist/health.js +174 -0
  122. package/dist/index.d.ts +41 -0
  123. package/dist/index.d.ts.map +1 -0
  124. package/dist/index.js +140 -0
  125. package/dist/interceptors/interceptor.d.ts +22 -0
  126. package/dist/interceptors/interceptor.d.ts.map +1 -0
  127. package/dist/interceptors/interceptor.js +46 -0
  128. package/dist/logger.d.ts +8 -0
  129. package/dist/logger.d.ts.map +1 -0
  130. package/dist/logger.js +238 -0
  131. package/dist/middleware/cors.middleware.d.ts +44 -0
  132. package/dist/middleware/cors.middleware.d.ts.map +1 -0
  133. package/dist/middleware/cors.middleware.js +118 -0
  134. package/dist/middleware/csrf.middleware.d.ts +82 -0
  135. package/dist/middleware/csrf.middleware.d.ts.map +1 -0
  136. package/dist/middleware/csrf.middleware.js +183 -0
  137. package/dist/middleware/global-middleware.d.ts +111 -0
  138. package/dist/middleware/global-middleware.d.ts.map +1 -0
  139. package/dist/middleware/global-middleware.js +179 -0
  140. package/dist/middleware/rate-limit.middleware.d.ts +73 -0
  141. package/dist/middleware/rate-limit.middleware.d.ts.map +1 -0
  142. package/dist/middleware/rate-limit.middleware.js +124 -0
  143. package/dist/middleware/security-headers.middleware.d.ts +76 -0
  144. package/dist/middleware/security-headers.middleware.d.ts.map +1 -0
  145. package/dist/middleware/security-headers.middleware.js +123 -0
  146. package/dist/middleware/timeout.middleware.d.ts +25 -0
  147. package/dist/middleware/timeout.middleware.d.ts.map +1 -0
  148. package/dist/middleware/timeout.middleware.js +74 -0
  149. package/dist/middleware.d.ts +13 -0
  150. package/dist/middleware.d.ts.map +1 -0
  151. package/dist/middleware.js +47 -0
  152. package/dist/pipes/pipe.d.ts +50 -0
  153. package/dist/pipes/pipe.d.ts.map +1 -0
  154. package/dist/pipes/pipe.js +96 -0
  155. package/dist/pipes/validation.pipe.d.ts +6 -0
  156. package/dist/pipes/validation.pipe.d.ts.map +1 -0
  157. package/dist/pipes/validation.pipe.js +61 -0
  158. package/dist/request-context.d.ts +17 -0
  159. package/dist/request-context.d.ts.map +1 -0
  160. package/dist/request-context.js +2 -0
  161. package/dist/request-parser.d.ts +7 -0
  162. package/dist/request-parser.d.ts.map +1 -0
  163. package/dist/request-parser.js +60 -0
  164. package/dist/router.d.ts +33 -0
  165. package/dist/router.d.ts.map +1 -0
  166. package/dist/router.js +426 -0
  167. package/dist/routing/route-matcher.d.ts +39 -0
  168. package/dist/routing/route-matcher.d.ts.map +1 -0
  169. package/dist/routing/route-matcher.js +93 -0
  170. package/dist/routing/version.decorator.d.ts +36 -0
  171. package/dist/routing/version.decorator.d.ts.map +1 -0
  172. package/dist/routing/version.decorator.js +89 -0
  173. package/dist/service.d.ts +9 -0
  174. package/dist/service.d.ts.map +1 -0
  175. package/dist/service.js +39 -0
  176. package/dist/shutdown.d.ts +32 -0
  177. package/dist/shutdown.d.ts.map +1 -0
  178. package/dist/shutdown.js +109 -0
  179. package/dist/testing/testing.module.d.ts +83 -0
  180. package/dist/testing/testing.module.d.ts.map +1 -0
  181. package/dist/testing/testing.module.js +164 -0
  182. package/dist/types.d.ts +76 -0
  183. package/dist/types.d.ts.map +1 -0
  184. package/dist/types.js +2 -0
  185. package/dist/upload/file-upload.d.ts +75 -0
  186. package/dist/upload/file-upload.d.ts.map +1 -0
  187. package/dist/upload/file-upload.js +261 -0
  188. package/dist/utils/sanitize.d.ts +45 -0
  189. package/dist/utils/sanitize.d.ts.map +1 -0
  190. package/dist/utils/sanitize.js +165 -0
  191. package/dist/validator.d.ts +7 -0
  192. package/dist/validator.d.ts.map +1 -0
  193. package/dist/validator.js +119 -0
  194. package/package.json +65 -0
@@ -0,0 +1,75 @@
1
+ import { Request } from '../types';
2
+ /**
3
+ * Uploaded file information
4
+ */
5
+ export interface UploadedFile {
6
+ fieldname: string;
7
+ originalname: string;
8
+ encoding: string;
9
+ mimetype: string;
10
+ size: number;
11
+ destination: string;
12
+ filename: string;
13
+ path: string;
14
+ buffer?: Buffer;
15
+ }
16
+ /**
17
+ * File upload options
18
+ */
19
+ export interface FileUploadOptions {
20
+ destination?: string;
21
+ filename?: (file: Partial<UploadedFile>) => string;
22
+ limits?: {
23
+ fileSize?: number;
24
+ files?: number;
25
+ };
26
+ fileFilter?: (file: Partial<UploadedFile>) => boolean;
27
+ storage?: 'disk' | 'memory';
28
+ }
29
+ /**
30
+ * File upload interceptor
31
+ */
32
+ export declare class FileUploadInterceptor {
33
+ private options;
34
+ constructor(options?: FileUploadOptions);
35
+ /**
36
+ * Default filename generator
37
+ */
38
+ private defaultFilename;
39
+ /**
40
+ * Parse multipart form data
41
+ */
42
+ parseMultipart(req: Request): Promise<{
43
+ fields: Record<string, string>;
44
+ files: UploadedFile[];
45
+ }>;
46
+ /**
47
+ * Extract boundary from Content-Type header
48
+ */
49
+ private extractBoundary;
50
+ /**
51
+ * Parse buffer containing multipart data
52
+ */
53
+ private parseBuffer;
54
+ /**
55
+ * Split buffer by boundary
56
+ */
57
+ private splitBuffer;
58
+ /**
59
+ * Parse a single part
60
+ */
61
+ private parsePart;
62
+ /**
63
+ * Process uploaded file
64
+ */
65
+ private processFile;
66
+ }
67
+ /**
68
+ * File decorator for route parameters
69
+ */
70
+ export declare function UploadedFileDecorator(fieldname?: string): ParameterDecorator;
71
+ /**
72
+ * Files decorator for multiple files
73
+ */
74
+ export declare function UploadedFilesDecorator(fieldname?: string): ParameterDecorator;
75
+ //# sourceMappingURL=file-upload.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-upload.d.ts","sourceRoot":"","sources":["../../src/upload/file-upload.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAMnC;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,YAAY,CAAC,KAAK,MAAM,CAAC;IACnD,MAAM,CAAC,EAAE;QACP,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,YAAY,CAAC,KAAK,OAAO,CAAC;IACtD,OAAO,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;CAC7B;AAED;;GAEG;AACH,qBAAa,qBAAqB;IAChC,OAAO,CAAC,OAAO,CAA8B;gBAEjC,OAAO,GAAE,iBAAsB;IAe3C;;OAEG;IACH,OAAO,CAAC,eAAe;IAOvB;;OAEG;IACG,cAAc,CAClB,GAAG,EAAE,OAAO,GACX,OAAO,CAAC;QAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAAC,KAAK,EAAE,YAAY,EAAE,CAAA;KAAE,CAAC;IAkCrE;;OAEG;IACH,OAAO,CAAC,eAAe;IAKvB;;OAEG;YACW,WAAW;IA4CzB;;OAEG;IACH,OAAO,CAAC,WAAW;IAkBnB;;OAEG;IACH,OAAO,CAAC,SAAS;IAwBjB;;OAEG;YACW,WAAW;CAsC1B;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,kBAAkB,CAqB5E;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,kBAAkB,CAqB7E"}
@@ -0,0 +1,261 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.FileUploadInterceptor = void 0;
40
+ exports.UploadedFileDecorator = UploadedFileDecorator;
41
+ exports.UploadedFilesDecorator = UploadedFilesDecorator;
42
+ const logger_1 = __importDefault(require("../logger"));
43
+ const fs = __importStar(require("fs"));
44
+ const path = __importStar(require("path"));
45
+ const crypto = __importStar(require("crypto"));
46
+ /**
47
+ * File upload interceptor
48
+ */
49
+ class FileUploadInterceptor {
50
+ constructor(options = {}) {
51
+ this.options = {
52
+ destination: options.destination || './uploads',
53
+ filename: options.filename || this.defaultFilename,
54
+ limits: options.limits || { fileSize: 10 * 1024 * 1024, files: 10 },
55
+ fileFilter: options.fileFilter || (() => true),
56
+ storage: options.storage || 'disk',
57
+ };
58
+ // Ensure destination directory exists
59
+ if (this.options.storage === 'disk' && !fs.existsSync(this.options.destination)) {
60
+ fs.mkdirSync(this.options.destination, { recursive: true });
61
+ }
62
+ }
63
+ /**
64
+ * Default filename generator
65
+ */
66
+ defaultFilename(file) {
67
+ const timestamp = Date.now();
68
+ const random = crypto.randomBytes(8).toString('hex');
69
+ const ext = path.extname(file.originalname || '');
70
+ return `${timestamp}-${random}${ext}`;
71
+ }
72
+ /**
73
+ * Parse multipart form data
74
+ */
75
+ async parseMultipart(req) {
76
+ const contentType = req.headers?.['content-type'] || '';
77
+ if (!contentType.includes('multipart/form-data')) {
78
+ throw new Error('Content-Type must be multipart/form-data');
79
+ }
80
+ const boundary = this.extractBoundary(contentType);
81
+ if (!boundary) {
82
+ throw new Error('Missing boundary in Content-Type');
83
+ }
84
+ const chunks = [];
85
+ const incomingMessage = req;
86
+ return new Promise((resolve, reject) => {
87
+ incomingMessage.on('data', (chunk) => {
88
+ chunks.push(chunk);
89
+ });
90
+ incomingMessage.on('end', async () => {
91
+ try {
92
+ const buffer = Buffer.concat(chunks);
93
+ const result = await this.parseBuffer(buffer, boundary);
94
+ resolve(result);
95
+ }
96
+ catch (error) {
97
+ reject(error);
98
+ }
99
+ });
100
+ incomingMessage.on('error', reject);
101
+ });
102
+ }
103
+ /**
104
+ * Extract boundary from Content-Type header
105
+ */
106
+ extractBoundary(contentType) {
107
+ const match = contentType.match(/boundary=([^;]+)/);
108
+ return match ? match[1].trim() : null;
109
+ }
110
+ /**
111
+ * Parse buffer containing multipart data
112
+ */
113
+ async parseBuffer(buffer, boundary) {
114
+ const fields = {};
115
+ const files = [];
116
+ const boundaryBuffer = Buffer.from(`--${boundary}`);
117
+ const parts = this.splitBuffer(buffer, boundaryBuffer);
118
+ for (const part of parts) {
119
+ if (part.length === 0)
120
+ continue;
121
+ const { headers, body } = this.parsePart(part);
122
+ const disposition = headers['content-disposition'];
123
+ if (!disposition)
124
+ continue;
125
+ const nameMatch = disposition.match(/name="([^"]+)"/);
126
+ const filenameMatch = disposition.match(/filename="([^"]+)"/);
127
+ if (filenameMatch) {
128
+ // It's a file
129
+ const file = await this.processFile({
130
+ fieldname: nameMatch ? nameMatch[1] : 'file',
131
+ originalname: filenameMatch[1],
132
+ encoding: headers['content-transfer-encoding'] || '7bit',
133
+ mimetype: headers['content-type'] || 'application/octet-stream',
134
+ size: body.length,
135
+ buffer: body,
136
+ });
137
+ if (file) {
138
+ files.push(file);
139
+ }
140
+ }
141
+ else if (nameMatch) {
142
+ // It's a field
143
+ fields[nameMatch[1]] = body.toString('utf-8');
144
+ }
145
+ }
146
+ return { fields, files };
147
+ }
148
+ /**
149
+ * Split buffer by boundary
150
+ */
151
+ splitBuffer(buffer, boundary) {
152
+ const parts = [];
153
+ let start = 0;
154
+ while (start < buffer.length) {
155
+ const index = buffer.indexOf(boundary, start);
156
+ if (index === -1)
157
+ break;
158
+ if (start !== index) {
159
+ parts.push(buffer.slice(start, index));
160
+ }
161
+ start = index + boundary.length;
162
+ }
163
+ return parts;
164
+ }
165
+ /**
166
+ * Parse a single part
167
+ */
168
+ parsePart(part) {
169
+ const headerEnd = part.indexOf('\r\n\r\n');
170
+ if (headerEnd === -1) {
171
+ return { headers: {}, body: part };
172
+ }
173
+ const headerSection = part.slice(0, headerEnd).toString('utf-8');
174
+ const body = part.slice(headerEnd + 4);
175
+ const headers = {};
176
+ const lines = headerSection.split('\r\n');
177
+ for (const line of lines) {
178
+ const colonIndex = line.indexOf(':');
179
+ if (colonIndex !== -1) {
180
+ const key = line.slice(0, colonIndex).trim().toLowerCase();
181
+ const value = line.slice(colonIndex + 1).trim();
182
+ headers[key] = value;
183
+ }
184
+ }
185
+ return { headers, body };
186
+ }
187
+ /**
188
+ * Process uploaded file
189
+ */
190
+ async processFile(file) {
191
+ // Check file filter
192
+ if (!this.options.fileFilter(file)) {
193
+ logger_1.default.warn(`File rejected by filter: ${file.originalname}`);
194
+ return null;
195
+ }
196
+ // Check file size
197
+ if (file.size && file.size > (this.options.limits.fileSize || Infinity)) {
198
+ throw new Error(`File too large: ${file.originalname} (${file.size} bytes)`);
199
+ }
200
+ const filename = this.options.filename(file);
201
+ const destination = this.options.destination;
202
+ const filepath = path.join(destination, filename);
203
+ const uploadedFile = {
204
+ fieldname: file.fieldname,
205
+ originalname: file.originalname,
206
+ encoding: file.encoding,
207
+ mimetype: file.mimetype,
208
+ size: file.size,
209
+ destination,
210
+ filename,
211
+ path: filepath,
212
+ };
213
+ if (this.options.storage === 'disk') {
214
+ // Save to disk
215
+ await fs.promises.writeFile(filepath, file.buffer);
216
+ logger_1.default.info(`File saved: ${filepath}`);
217
+ }
218
+ else {
219
+ // Keep in memory
220
+ uploadedFile.buffer = file.buffer;
221
+ }
222
+ return uploadedFile;
223
+ }
224
+ }
225
+ exports.FileUploadInterceptor = FileUploadInterceptor;
226
+ /**
227
+ * File decorator for route parameters
228
+ */
229
+ function UploadedFileDecorator(fieldname) {
230
+ return (target, propertyKey, parameterIndex) => {
231
+ if (!propertyKey) {
232
+ throw new Error('UploadedFile decorator must be used on a method parameter');
233
+ }
234
+ const constructor = target
235
+ .constructor;
236
+ const injections = Reflect.getMetadata('hazel:inject', constructor, propertyKey) || [];
237
+ injections[parameterIndex] = {
238
+ type: 'file',
239
+ fieldname,
240
+ };
241
+ Reflect.defineMetadata('hazel:inject', injections, constructor, propertyKey);
242
+ };
243
+ }
244
+ /**
245
+ * Files decorator for multiple files
246
+ */
247
+ function UploadedFilesDecorator(fieldname) {
248
+ return (target, propertyKey, parameterIndex) => {
249
+ if (!propertyKey) {
250
+ throw new Error('UploadedFiles decorator must be used on a method parameter');
251
+ }
252
+ const constructor = target
253
+ .constructor;
254
+ const injections = Reflect.getMetadata('hazel:inject', constructor, propertyKey) || [];
255
+ injections[parameterIndex] = {
256
+ type: 'files',
257
+ fieldname,
258
+ };
259
+ Reflect.defineMetadata('hazel:inject', injections, constructor, propertyKey);
260
+ };
261
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Input sanitization utilities
3
+ * Helps prevent XSS and injection attacks
4
+ */
5
+ /**
6
+ * Sanitize HTML string
7
+ * Removes potentially dangerous HTML tags and attributes
8
+ */
9
+ export declare function sanitizeHtml(input: string): string;
10
+ /**
11
+ * Sanitize string input
12
+ * Removes control characters and normalizes whitespace
13
+ */
14
+ export declare function sanitizeString(input: string): string;
15
+ /**
16
+ * Sanitize URL
17
+ * Validates and sanitizes URL input
18
+ */
19
+ export declare function sanitizeUrl(input: string): string;
20
+ /**
21
+ * Sanitize email
22
+ * Basic email validation and sanitization
23
+ */
24
+ export declare function sanitizeEmail(input: string): string;
25
+ /**
26
+ * Sanitize SQL input (for raw queries)
27
+ * Escapes special characters (use parameterized queries instead!)
28
+ */
29
+ export declare function sanitizeSql(input: string): string;
30
+ /**
31
+ * Sanitize object recursively
32
+ * Applies sanitization to all string values in an object
33
+ */
34
+ export declare function sanitizeObject<T extends Record<string, unknown>>(obj: T, options?: {
35
+ sanitizeHtml?: boolean;
36
+ sanitizeStrings?: boolean;
37
+ allowedKeys?: string[];
38
+ maxDepth?: number;
39
+ }): T;
40
+ /**
41
+ * Escape HTML entities
42
+ * Converts special characters to HTML entities
43
+ */
44
+ export declare function escapeHtml(input: string): string;
45
+ //# sourceMappingURL=sanitize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../../src/utils/sanitize.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAYlD;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CASpD;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAejD;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAanD;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAajD;AAED;;;GAGG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9D,GAAG,EAAE,CAAC,EACN,OAAO,GAAE;IACP,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACd,GACL,CAAC,CA2DH;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAchD"}
@@ -0,0 +1,165 @@
1
+ "use strict";
2
+ /**
3
+ * Input sanitization utilities
4
+ * Helps prevent XSS and injection attacks
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.sanitizeHtml = sanitizeHtml;
8
+ exports.sanitizeString = sanitizeString;
9
+ exports.sanitizeUrl = sanitizeUrl;
10
+ exports.sanitizeEmail = sanitizeEmail;
11
+ exports.sanitizeSql = sanitizeSql;
12
+ exports.sanitizeObject = sanitizeObject;
13
+ exports.escapeHtml = escapeHtml;
14
+ /**
15
+ * Sanitize HTML string
16
+ * Removes potentially dangerous HTML tags and attributes
17
+ */
18
+ function sanitizeHtml(input) {
19
+ if (typeof input !== 'string') {
20
+ return String(input);
21
+ }
22
+ // Remove script tags and event handlers
23
+ return input
24
+ .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
25
+ .replace(/<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi, '')
26
+ .replace(/on\w+\s*=\s*["'][^"']*["']/gi, '')
27
+ .replace(/javascript:/gi, '')
28
+ .replace(/data:text\/html/gi, '');
29
+ }
30
+ /**
31
+ * Sanitize string input
32
+ * Removes control characters and normalizes whitespace
33
+ */
34
+ function sanitizeString(input) {
35
+ if (typeof input !== 'string') {
36
+ return String(input);
37
+ }
38
+ return input
39
+ .replace(/[\x00-\x1F\x7F-\x9F]/g, '') // Remove control characters
40
+ .trim()
41
+ .replace(/\s+/g, ' '); // Normalize whitespace
42
+ }
43
+ /**
44
+ * Sanitize URL
45
+ * Validates and sanitizes URL input
46
+ */
47
+ function sanitizeUrl(input) {
48
+ if (typeof input !== 'string') {
49
+ return '';
50
+ }
51
+ try {
52
+ const url = new URL(input);
53
+ // Only allow http and https protocols
54
+ if (!['http:', 'https:'].includes(url.protocol)) {
55
+ return '';
56
+ }
57
+ return url.toString();
58
+ }
59
+ catch {
60
+ return '';
61
+ }
62
+ }
63
+ /**
64
+ * Sanitize email
65
+ * Basic email validation and sanitization
66
+ */
67
+ function sanitizeEmail(input) {
68
+ if (typeof input !== 'string') {
69
+ return '';
70
+ }
71
+ const email = input.trim().toLowerCase();
72
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
73
+ if (!emailRegex.test(email)) {
74
+ return '';
75
+ }
76
+ return email;
77
+ }
78
+ /**
79
+ * Sanitize SQL input (for raw queries)
80
+ * Escapes special characters (use parameterized queries instead!)
81
+ */
82
+ function sanitizeSql(input) {
83
+ if (typeof input !== 'string') {
84
+ return '';
85
+ }
86
+ // WARNING: This is a basic sanitization
87
+ // Always use parameterized queries with Prisma instead!
88
+ return input
89
+ .replace(/'/g, "''")
90
+ .replace(/;/g, '')
91
+ .replace(/--/g, '')
92
+ .replace(/\/\*/g, '')
93
+ .replace(/\*\//g, '');
94
+ }
95
+ /**
96
+ * Sanitize object recursively
97
+ * Applies sanitization to all string values in an object
98
+ */
99
+ function sanitizeObject(obj, options = {}) {
100
+ const { sanitizeHtml: sanitizeHtmlFields = false, sanitizeStrings: sanitizeStringFields = true, allowedKeys, maxDepth = 10, } = options;
101
+ if (maxDepth <= 0) {
102
+ return obj;
103
+ }
104
+ const sanitized = {};
105
+ for (const [key, value] of Object.entries(obj)) {
106
+ // Skip disallowed keys
107
+ if (allowedKeys && !allowedKeys.includes(key)) {
108
+ continue;
109
+ }
110
+ if (typeof value === 'string') {
111
+ if (sanitizeHtmlFields) {
112
+ sanitized[key] = sanitizeHtml(value);
113
+ }
114
+ else if (sanitizeStringFields) {
115
+ sanitized[key] = sanitizeString(value);
116
+ }
117
+ else {
118
+ sanitized[key] = value;
119
+ }
120
+ }
121
+ else if (Array.isArray(value)) {
122
+ sanitized[key] = value.map((item) => typeof item === 'object' && item !== null
123
+ ? sanitizeObject(item, {
124
+ sanitizeHtml: sanitizeHtmlFields,
125
+ sanitizeStrings: sanitizeStringFields,
126
+ maxDepth: maxDepth - 1,
127
+ })
128
+ : typeof item === 'string'
129
+ ? sanitizeHtmlFields
130
+ ? sanitizeHtml(item)
131
+ : sanitizeStringFields
132
+ ? sanitizeString(item)
133
+ : item
134
+ : item);
135
+ }
136
+ else if (typeof value === 'object' && value !== null) {
137
+ sanitized[key] = sanitizeObject(value, {
138
+ sanitizeHtml: sanitizeHtmlFields,
139
+ sanitizeStrings: sanitizeStringFields,
140
+ maxDepth: maxDepth - 1,
141
+ });
142
+ }
143
+ else {
144
+ sanitized[key] = value;
145
+ }
146
+ }
147
+ return sanitized;
148
+ }
149
+ /**
150
+ * Escape HTML entities
151
+ * Converts special characters to HTML entities
152
+ */
153
+ function escapeHtml(input) {
154
+ if (typeof input !== 'string') {
155
+ return String(input);
156
+ }
157
+ const map = {
158
+ '&': '&amp;',
159
+ '<': '&lt;',
160
+ '>': '&gt;',
161
+ '"': '&quot;',
162
+ "'": '&#039;',
163
+ };
164
+ return input.replace(/[&<>"']/g, (m) => map[m]);
165
+ }
@@ -0,0 +1,7 @@
1
+ import { ValidationSchema, ValidationError } from './types';
2
+ type ValidatableData = Record<string, unknown>;
3
+ export declare class Validator {
4
+ static validate(data: ValidatableData, schema: ValidationSchema): ValidationError[];
5
+ }
6
+ export {};
7
+ //# sourceMappingURL=validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validator.d.ts","sourceRoot":"","sources":["../src/validator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAE5D,KAAK,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE/C,qBAAa,SAAS;IACpB,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,eAAe,EAAE,MAAM,EAAE,gBAAgB,GAAG,eAAe,EAAE;CAsHpF"}
@@ -0,0 +1,119 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Validator = void 0;
4
+ class Validator {
5
+ static validate(data, schema) {
6
+ const errors = [];
7
+ for (const [field, rules] of Object.entries(schema)) {
8
+ const value = data[field];
9
+ if (rules.required && (value === undefined || value === null || value === '')) {
10
+ errors.push({
11
+ field,
12
+ message: `${field} is required`,
13
+ });
14
+ continue;
15
+ }
16
+ if (value === undefined || value === null) {
17
+ continue;
18
+ }
19
+ switch (rules.type) {
20
+ case 'string':
21
+ if (typeof value !== 'string') {
22
+ errors.push({
23
+ field,
24
+ message: `${field} must be a string`,
25
+ });
26
+ }
27
+ else {
28
+ if (rules.min !== undefined && value.length < rules.min) {
29
+ errors.push({
30
+ field,
31
+ message: `${field} must be at least ${rules.min} characters`,
32
+ });
33
+ }
34
+ if (rules.max !== undefined && value.length > rules.max) {
35
+ errors.push({
36
+ field,
37
+ message: `${field} must be at most ${rules.max} characters`,
38
+ });
39
+ }
40
+ if (rules.pattern && !rules.pattern.test(value)) {
41
+ errors.push({
42
+ field,
43
+ message: `${field} has invalid format`,
44
+ });
45
+ }
46
+ }
47
+ break;
48
+ case 'number':
49
+ if (typeof value !== 'number') {
50
+ errors.push({
51
+ field,
52
+ message: `${field} must be a number`,
53
+ });
54
+ }
55
+ else {
56
+ if (rules.min !== undefined && value < rules.min) {
57
+ errors.push({
58
+ field,
59
+ message: `${field} must be at least ${rules.min}`,
60
+ });
61
+ }
62
+ if (rules.max !== undefined && value > rules.max) {
63
+ errors.push({
64
+ field,
65
+ message: `${field} must be at most ${rules.max}`,
66
+ });
67
+ }
68
+ }
69
+ break;
70
+ case 'boolean':
71
+ if (typeof value !== 'boolean') {
72
+ errors.push({
73
+ field,
74
+ message: `${field} must be a boolean`,
75
+ });
76
+ }
77
+ break;
78
+ case 'object':
79
+ if (typeof value !== 'object' || Array.isArray(value)) {
80
+ errors.push({
81
+ field,
82
+ message: `${field} must be an object`,
83
+ });
84
+ }
85
+ else if (rules.properties) {
86
+ errors.push(...this.validate(value, rules.properties));
87
+ }
88
+ break;
89
+ case 'array':
90
+ if (!Array.isArray(value)) {
91
+ errors.push({
92
+ field,
93
+ message: `${field} must be an array`,
94
+ });
95
+ }
96
+ else if (rules.items) {
97
+ value.forEach((item, index) => {
98
+ const itemSchema = {
99
+ [`item_${index}`]: {
100
+ type: rules.items.type,
101
+ required: rules.items.required,
102
+ min: rules.items.min,
103
+ max: rules.items.max,
104
+ pattern: rules.items.pattern,
105
+ properties: rules.items.properties,
106
+ items: rules.items.items,
107
+ },
108
+ };
109
+ const itemErrors = this.validate({ [`item_${index}`]: item }, itemSchema);
110
+ errors.push(...itemErrors);
111
+ });
112
+ }
113
+ break;
114
+ }
115
+ }
116
+ return errors;
117
+ }
118
+ }
119
+ exports.Validator = Validator;