@dangao/bun-server 1.0.1 → 1.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (262) hide show
  1. package/dist/controller/controller.d.ts +1 -1
  2. package/dist/controller/controller.d.ts.map +1 -1
  3. package/dist/core/application.d.ts.map +1 -1
  4. package/dist/database/database-extension.d.ts.map +1 -1
  5. package/dist/database/database-module.d.ts.map +1 -1
  6. package/dist/database/orm/transaction-decorator.d.ts +1 -0
  7. package/dist/database/orm/transaction-decorator.d.ts.map +1 -1
  8. package/dist/database/orm/transaction-interceptor.d.ts +12 -3
  9. package/dist/database/orm/transaction-interceptor.d.ts.map +1 -1
  10. package/dist/index.d.ts +1 -0
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +678 -310
  13. package/dist/interceptor/base-interceptor.d.ts +94 -0
  14. package/dist/interceptor/base-interceptor.d.ts.map +1 -0
  15. package/dist/interceptor/builtin/cache-interceptor.d.ts +69 -0
  16. package/dist/interceptor/builtin/cache-interceptor.d.ts.map +1 -0
  17. package/dist/interceptor/builtin/index.d.ts +4 -0
  18. package/dist/interceptor/builtin/index.d.ts.map +1 -0
  19. package/dist/interceptor/builtin/log-interceptor.d.ts +56 -0
  20. package/dist/interceptor/builtin/log-interceptor.d.ts.map +1 -0
  21. package/dist/interceptor/builtin/permission-interceptor.d.ts +70 -0
  22. package/dist/interceptor/builtin/permission-interceptor.d.ts.map +1 -0
  23. package/dist/interceptor/index.d.ts +7 -0
  24. package/dist/interceptor/index.d.ts.map +1 -0
  25. package/dist/interceptor/interceptor-chain.d.ts +22 -0
  26. package/dist/interceptor/interceptor-chain.d.ts.map +1 -0
  27. package/dist/interceptor/interceptor-registry.d.ts +59 -0
  28. package/dist/interceptor/interceptor-registry.d.ts.map +1 -0
  29. package/dist/interceptor/metadata.d.ts +12 -0
  30. package/dist/interceptor/metadata.d.ts.map +1 -0
  31. package/dist/interceptor/types.d.ts +42 -0
  32. package/dist/interceptor/types.d.ts.map +1 -0
  33. package/dist/middleware/decorators.d.ts +2 -1
  34. package/dist/middleware/decorators.d.ts.map +1 -1
  35. package/dist/router/decorators.d.ts.map +1 -1
  36. package/dist/router/registry.d.ts +2 -1
  37. package/dist/router/registry.d.ts.map +1 -1
  38. package/dist/router/route.d.ts +3 -2
  39. package/dist/router/route.d.ts.map +1 -1
  40. package/dist/router/router.d.ts +2 -1
  41. package/dist/router/router.d.ts.map +1 -1
  42. package/dist/websocket/decorators.d.ts +2 -1
  43. package/dist/websocket/decorators.d.ts.map +1 -1
  44. package/package.json +5 -3
  45. package/readme.md +163 -2
  46. package/src/auth/controller.ts +148 -0
  47. package/src/auth/decorators.ts +81 -0
  48. package/src/auth/index.ts +12 -0
  49. package/src/auth/jwt.ts +169 -0
  50. package/src/auth/oauth2.ts +244 -0
  51. package/src/auth/types.ts +248 -0
  52. package/src/cache/cache-module.ts +67 -0
  53. package/src/cache/decorators.ts +202 -0
  54. package/src/cache/index.ts +27 -0
  55. package/src/cache/service.ts +151 -0
  56. package/src/cache/types.ts +420 -0
  57. package/src/config/config-module.ts +76 -0
  58. package/src/config/index.ts +8 -0
  59. package/src/config/service.ts +93 -0
  60. package/src/config/types.ts +27 -0
  61. package/src/controller/controller.ts +278 -0
  62. package/src/controller/decorators.ts +84 -0
  63. package/src/controller/index.ts +7 -0
  64. package/src/controller/metadata.ts +27 -0
  65. package/src/controller/param-binder.ts +157 -0
  66. package/src/core/application.ts +239 -0
  67. package/src/core/context.ts +228 -0
  68. package/src/core/index.ts +4 -0
  69. package/src/core/server.ts +128 -0
  70. package/src/core/types.ts +2 -0
  71. package/src/database/connection-manager.ts +239 -0
  72. package/src/database/connection-pool.ts +322 -0
  73. package/src/database/database-extension.ts +83 -0
  74. package/src/database/database-module.ts +121 -0
  75. package/src/database/health-indicator.ts +51 -0
  76. package/src/database/index.ts +47 -0
  77. package/src/database/orm/decorators.ts +155 -0
  78. package/src/database/orm/drizzle-repository.ts +39 -0
  79. package/src/database/orm/index.ts +23 -0
  80. package/src/database/orm/repository-decorator.ts +39 -0
  81. package/src/database/orm/repository.ts +103 -0
  82. package/src/database/orm/service.ts +49 -0
  83. package/src/database/orm/transaction-decorator.ts +76 -0
  84. package/src/database/orm/transaction-interceptor.ts +263 -0
  85. package/src/database/orm/transaction-manager.ts +276 -0
  86. package/src/database/orm/transaction-types.ts +140 -0
  87. package/src/database/orm/types.ts +99 -0
  88. package/src/database/service.ts +221 -0
  89. package/src/database/types.ts +171 -0
  90. package/src/di/container.ts +398 -0
  91. package/src/di/decorators.ts +228 -0
  92. package/src/di/index.ts +4 -0
  93. package/src/di/module-registry.ts +188 -0
  94. package/src/di/module.ts +65 -0
  95. package/src/di/types.ts +67 -0
  96. package/src/error/error-codes.ts +222 -0
  97. package/src/error/filter.ts +43 -0
  98. package/src/error/handler.ts +66 -0
  99. package/src/error/http-exception.ts +115 -0
  100. package/src/error/i18n.ts +217 -0
  101. package/src/error/index.ts +16 -0
  102. package/src/extensions/index.ts +5 -0
  103. package/src/extensions/logger-extension.ts +31 -0
  104. package/src/extensions/logger-module.ts +69 -0
  105. package/src/extensions/types.ts +14 -0
  106. package/src/files/index.ts +5 -0
  107. package/src/files/static-middleware.ts +53 -0
  108. package/src/files/storage.ts +67 -0
  109. package/src/files/types.ts +33 -0
  110. package/src/files/upload-middleware.ts +45 -0
  111. package/src/health/controller.ts +76 -0
  112. package/src/health/health-module.ts +51 -0
  113. package/src/health/index.ts +12 -0
  114. package/src/health/types.ts +28 -0
  115. package/src/index.ts +292 -0
  116. package/src/interceptor/base-interceptor.ts +203 -0
  117. package/src/interceptor/builtin/cache-interceptor.ts +169 -0
  118. package/src/interceptor/builtin/index.ts +28 -0
  119. package/src/interceptor/builtin/log-interceptor.ts +178 -0
  120. package/src/interceptor/builtin/permission-interceptor.ts +173 -0
  121. package/src/interceptor/index.ts +26 -0
  122. package/src/interceptor/interceptor-chain.ts +79 -0
  123. package/src/interceptor/interceptor-registry.ts +132 -0
  124. package/src/interceptor/metadata.ts +40 -0
  125. package/src/interceptor/types.ts +52 -0
  126. package/src/metrics/collector.ts +209 -0
  127. package/src/metrics/controller.ts +40 -0
  128. package/src/metrics/index.ts +15 -0
  129. package/src/metrics/metrics-module.ts +58 -0
  130. package/src/metrics/middleware.ts +46 -0
  131. package/src/metrics/prometheus.ts +79 -0
  132. package/src/metrics/types.ts +103 -0
  133. package/src/middleware/builtin/cors.ts +60 -0
  134. package/src/middleware/builtin/error-handler.ts +90 -0
  135. package/src/middleware/builtin/file-upload.ts +42 -0
  136. package/src/middleware/builtin/index.ts +14 -0
  137. package/src/middleware/builtin/logger.ts +91 -0
  138. package/src/middleware/builtin/rate-limit.ts +252 -0
  139. package/src/middleware/builtin/static-file.ts +88 -0
  140. package/src/middleware/decorators.ts +92 -0
  141. package/src/middleware/index.ts +11 -0
  142. package/src/middleware/middleware.ts +13 -0
  143. package/src/middleware/pipeline.ts +93 -0
  144. package/src/queue/decorators.ts +110 -0
  145. package/src/queue/index.ts +26 -0
  146. package/src/queue/queue-module.ts +64 -0
  147. package/src/queue/service.ts +302 -0
  148. package/src/queue/types.ts +341 -0
  149. package/src/request/body-parser.ts +133 -0
  150. package/src/request/file-handler.ts +46 -0
  151. package/src/request/index.ts +5 -0
  152. package/src/request/request.ts +107 -0
  153. package/src/request/response.ts +150 -0
  154. package/src/router/decorators.ts +123 -0
  155. package/src/router/index.ts +6 -0
  156. package/src/router/registry.ts +99 -0
  157. package/src/router/route.ts +141 -0
  158. package/src/router/router.ts +242 -0
  159. package/src/router/types.ts +27 -0
  160. package/src/security/access-decision-manager.ts +34 -0
  161. package/src/security/authentication-manager.ts +47 -0
  162. package/src/security/context.ts +92 -0
  163. package/src/security/filter.ts +162 -0
  164. package/src/security/index.ts +8 -0
  165. package/src/security/providers/index.ts +3 -0
  166. package/src/security/providers/jwt-provider.ts +60 -0
  167. package/src/security/providers/oauth2-provider.ts +70 -0
  168. package/src/security/security-module.ts +145 -0
  169. package/src/security/types.ts +165 -0
  170. package/src/session/decorators.ts +45 -0
  171. package/src/session/index.ts +19 -0
  172. package/src/session/middleware.ts +143 -0
  173. package/src/session/service.ts +218 -0
  174. package/src/session/session-module.ts +69 -0
  175. package/src/session/types.ts +373 -0
  176. package/src/swagger/decorators.ts +133 -0
  177. package/src/swagger/generator.ts +234 -0
  178. package/src/swagger/index.ts +7 -0
  179. package/src/swagger/swagger-extension.ts +41 -0
  180. package/src/swagger/swagger-module.ts +83 -0
  181. package/src/swagger/types.ts +188 -0
  182. package/src/swagger/ui.ts +98 -0
  183. package/src/testing/harness.ts +96 -0
  184. package/src/validation/decorators.ts +95 -0
  185. package/src/validation/errors.ts +28 -0
  186. package/src/validation/index.ts +14 -0
  187. package/src/validation/types.ts +35 -0
  188. package/src/validation/validator.ts +63 -0
  189. package/src/websocket/decorators.ts +53 -0
  190. package/src/websocket/index.ts +12 -0
  191. package/src/websocket/registry.ts +133 -0
  192. package/tests/cache/cache-module.test.ts +212 -0
  193. package/tests/config/config-module.test.ts +151 -0
  194. package/tests/controller/controller.test.ts +189 -0
  195. package/tests/controller/path-combination.test.ts +207 -0
  196. package/tests/core/application.test.ts +57 -0
  197. package/tests/core/context-body.test.ts +44 -0
  198. package/tests/core/context.test.ts +86 -0
  199. package/tests/core/edge-cases.test.ts +432 -0
  200. package/tests/database/database-module.test.ts +385 -0
  201. package/tests/database/orm.test.ts +164 -0
  202. package/tests/database/postgres-mysql-integration.test.ts +395 -0
  203. package/tests/database/transaction.test.ts +238 -0
  204. package/tests/di/container.test.ts +264 -0
  205. package/tests/di/module.test.ts +128 -0
  206. package/tests/error/error-codes.test.ts +121 -0
  207. package/tests/error/error-handler.test.ts +68 -0
  208. package/tests/error/error-handling.test.ts +254 -0
  209. package/tests/error/http-exception.test.ts +37 -0
  210. package/tests/error/i18n-integration.test.ts +175 -0
  211. package/tests/extensions/logger-extension.test.ts +40 -0
  212. package/tests/files/static-middleware.test.ts +67 -0
  213. package/tests/files/upload-middleware.test.ts +43 -0
  214. package/tests/health/health-module.test.ts +116 -0
  215. package/tests/integration/application-router.test.ts +85 -0
  216. package/tests/integration/body-parsing.test.ts +88 -0
  217. package/tests/integration/cache-e2e.test.ts +114 -0
  218. package/tests/integration/oauth2-e2e.test.ts +615 -0
  219. package/tests/integration/session-e2e.test.ts +207 -0
  220. package/tests/interceptor/builtin/cache-interceptor.test.ts +137 -0
  221. package/tests/interceptor/builtin/permission-interceptor.test.ts +182 -0
  222. package/tests/interceptor/interceptor-advanced-integration.test.ts +592 -0
  223. package/tests/interceptor/interceptor-arg-modification.test.ts +76 -0
  224. package/tests/interceptor/interceptor-chain.test.ts +199 -0
  225. package/tests/interceptor/interceptor-integration.test.ts +230 -0
  226. package/tests/interceptor/interceptor-registry.test.ts +200 -0
  227. package/tests/interceptor/perf/interceptor-performance.test.ts +341 -0
  228. package/tests/metrics/metrics-module.test.ts +178 -0
  229. package/tests/middleware/builtin.test.ts +206 -0
  230. package/tests/middleware/file-upload.test.ts +41 -0
  231. package/tests/middleware/middleware.test.ts +120 -0
  232. package/tests/middleware/pipeline.test.ts +72 -0
  233. package/tests/middleware/rate-limit.test.ts +314 -0
  234. package/tests/middleware/static-file.test.ts +62 -0
  235. package/tests/perf/harness.test.ts +48 -0
  236. package/tests/perf/optimization.test.ts +183 -0
  237. package/tests/perf/regression.test.ts +120 -0
  238. package/tests/queue/queue-module.test.ts +217 -0
  239. package/tests/request/body-parser.test.ts +96 -0
  240. package/tests/request/response.test.ts +99 -0
  241. package/tests/router/decorators.test.ts +46 -0
  242. package/tests/router/registry.test.ts +51 -0
  243. package/tests/router/route.test.ts +71 -0
  244. package/tests/router/router-normalization.test.ts +106 -0
  245. package/tests/router/router.test.ts +133 -0
  246. package/tests/security/access-decision-manager.test.ts +84 -0
  247. package/tests/security/authentication-manager.test.ts +81 -0
  248. package/tests/security/context.test.ts +302 -0
  249. package/tests/security/filter.test.ts +225 -0
  250. package/tests/security/jwt-provider.test.ts +106 -0
  251. package/tests/security/oauth2-provider.test.ts +269 -0
  252. package/tests/security/security-module.test.ts +143 -0
  253. package/tests/session/session-module.test.ts +307 -0
  254. package/tests/stress/di-stress.test.ts +30 -0
  255. package/tests/swagger/decorators.test.ts +153 -0
  256. package/tests/swagger/generator.test.ts +202 -0
  257. package/tests/swagger/swagger-extension.test.ts +72 -0
  258. package/tests/swagger/swagger-module.test.ts +79 -0
  259. package/tests/utils/test-port.ts +10 -0
  260. package/tests/validation/controller-validation.test.ts +64 -0
  261. package/tests/validation/validation.test.ts +42 -0
  262. package/tests/websocket/gateway.test.ts +68 -0
@@ -0,0 +1,206 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+
3
+ import { Context } from '../../src/core/context';
4
+ import {
5
+ createLoggerMiddleware,
6
+ createRequestLoggingMiddleware,
7
+ createErrorHandlingMiddleware,
8
+ createCorsMiddleware,
9
+ runMiddlewares,
10
+ } from '../../src/middleware';
11
+ import { ValidationError } from '../../src/validation/errors';
12
+ import { HttpException } from '../../src/error/http-exception';
13
+
14
+ function createContext(url = 'http://localhost:3000/api/test', method: string = 'GET'): Context {
15
+ return new Context(
16
+ new Request(url, {
17
+ method,
18
+ headers: {
19
+ Origin: 'http://localhost:4000',
20
+ },
21
+ }),
22
+ );
23
+ }
24
+
25
+ describe('Builtin Middlewares', () => {
26
+ test('logger middleware should invoke custom logger', async () => {
27
+ const logs: string[] = [];
28
+ const logger = createLoggerMiddleware({
29
+ logger: (message) => logs.push(message),
30
+ prefix: '[TestLogger]',
31
+ });
32
+
33
+ const ctx = createContext();
34
+ const response = await runMiddlewares(
35
+ [logger],
36
+ ctx,
37
+ async () => ctx.createResponse({ message: 'ok' }),
38
+ );
39
+
40
+ expect(await response.json()).toEqual({ message: 'ok' });
41
+ expect(logs).toContain('[TestLogger] GET /api/test 200');
42
+ });
43
+
44
+ test('logger middleware should log downstream status code', async () => {
45
+ const logs: string[] = [];
46
+ const logger = createLoggerMiddleware({
47
+ logger: (message) => logs.push(message),
48
+ prefix: '[TestLogger]',
49
+ });
50
+
51
+ const ctx = createContext();
52
+ const response = await runMiddlewares(
53
+ [logger],
54
+ ctx,
55
+ async () => {
56
+ ctx.setStatus(404);
57
+ return ctx.createResponse({ error: 'not found' });
58
+ },
59
+ );
60
+
61
+ expect(response.status).toBe(404);
62
+ expect(logs).toContain('[TestLogger] GET /api/test 404');
63
+ });
64
+
65
+ test('request logging middleware should set duration header', async () => {
66
+ const ctx = createContext();
67
+ const middleware = createRequestLoggingMiddleware({ setHeader: true });
68
+
69
+ const response = await runMiddlewares(
70
+ [middleware],
71
+ ctx,
72
+ async () => ctx.createResponse({ ok: true }),
73
+ );
74
+
75
+ expect(response.headers.get('x-request-duration')).toBeDefined();
76
+ });
77
+
78
+ test('request logging middleware should capture error details', async () => {
79
+ const logs: Array<{ message: string; details?: Record<string, unknown> }> = [];
80
+ const middleware = createRequestLoggingMiddleware({
81
+ logger: (message, details) => logs.push({ message, details }),
82
+ setHeader: false,
83
+ prefix: '[ReqTest]',
84
+ });
85
+
86
+ const ctx = createContext();
87
+ await expect(
88
+ runMiddlewares([middleware], ctx, async () => {
89
+ throw new Error('request failed');
90
+ }),
91
+ ).rejects.toThrow('request failed');
92
+
93
+ expect(logs[0]?.message).toContain('[ReqTest] GET /api/test error');
94
+ expect(logs[0]?.details?.error).toBe('request failed');
95
+ });
96
+
97
+ test('error handling middleware should capture errors', async () => {
98
+ const errors: string[] = [];
99
+ const middleware = createErrorHandlingMiddleware({
100
+ exposeError: true,
101
+ logger: (error) => {
102
+ errors.push(error instanceof Error ? error.message : String(error));
103
+ },
104
+ });
105
+
106
+ const ctx = createContext();
107
+ const response = await runMiddlewares([middleware], ctx, async () => {
108
+ throw new Error('boom');
109
+ });
110
+
111
+ expect(errors).toContain('boom');
112
+ expect(response.status).toBe(500);
113
+ expect(await response.json()).toEqual({ error: 'boom' });
114
+ });
115
+
116
+ test('error handling middleware should format validation errors', async () => {
117
+ const middleware = createErrorHandlingMiddleware();
118
+ const ctx = createContext();
119
+ const response = await runMiddlewares([middleware], ctx, async () => {
120
+ throw new ValidationError('invalid', [{ index: 0, rule: 'IsString', message: 'bad' }]);
121
+ });
122
+
123
+ expect(response.status).toBe(400);
124
+ expect(await response.json()).toEqual({
125
+ error: 'invalid',
126
+ issues: [{ index: 0, rule: 'IsString', message: 'bad' }],
127
+ });
128
+ });
129
+
130
+ test('error handling middleware should return response when downstream throws Response', async () => {
131
+ const middleware = createErrorHandlingMiddleware();
132
+ const ctx = createContext();
133
+ const downstream = new Response('prebuilt', { status: 418 });
134
+ const response = await runMiddlewares([middleware], ctx, async () => {
135
+ throw downstream;
136
+ });
137
+
138
+ expect(response.status).toBe(418);
139
+ expect(await response.text()).toBe('prebuilt');
140
+ });
141
+
142
+ test('error handling middleware should respect HttpException exposure flag', async () => {
143
+ const middleware = createErrorHandlingMiddleware();
144
+ const ctx = createContext();
145
+ const response = await runMiddlewares([middleware], ctx, async () => {
146
+ throw new HttpException(503, 'Service down', { retry: true });
147
+ });
148
+
149
+ expect(response.status).toBe(503);
150
+ expect(await response.json()).toEqual({
151
+ error: 'Service down',
152
+ details: { retry: true },
153
+ });
154
+ });
155
+
156
+ test('error handling middleware should expose HttpException when configured', async () => {
157
+ const middleware = createErrorHandlingMiddleware({ exposeError: true });
158
+ const ctx = createContext();
159
+ const response = await runMiddlewares([middleware], ctx, async () => {
160
+ throw new HttpException(429, 'Too Many Requests');
161
+ });
162
+
163
+ expect(response.status).toBe(429);
164
+ expect(await response.json()).toEqual({ error: 'Too Many Requests' });
165
+ });
166
+
167
+ test('cors middleware should handle options request', async () => {
168
+ const cors = createCorsMiddleware({
169
+ origin: ['http://localhost:4000'],
170
+ allowedHeaders: ['Content-Type'],
171
+ });
172
+
173
+ const ctx = createContext('http://localhost:3000/api/cors', 'OPTIONS');
174
+ const response = await runMiddlewares([cors], ctx, async () =>
175
+ ctx.createResponse({ ok: true }),
176
+ );
177
+
178
+ expect(response.status).toBe(204);
179
+ expect(response.headers.get('Access-Control-Allow-Origin')).toBe('http://localhost:4000');
180
+ expect(response.headers.get('Access-Control-Allow-Headers')).toContain('Content-Type');
181
+ });
182
+
183
+ test('cors middleware should allow fine grained configuration', async () => {
184
+ const cors = createCorsMiddleware({
185
+ origin: ['http://foo.com', 'http://bar.com'],
186
+ methods: ['GET'],
187
+ allowedHeaders: ['X-Test'],
188
+ exposedHeaders: ['X-Expose'],
189
+ credentials: false,
190
+ maxAge: 10,
191
+ });
192
+
193
+ const ctx = createContext('http://localhost:3000/api/cors', 'GET');
194
+ ctx.headers.set('Origin', 'http://bar.com');
195
+ const response = await runMiddlewares([cors], ctx, async () => ctx.createResponse({}));
196
+
197
+ expect(response).toBeDefined();
198
+ expect(ctx.responseHeaders.get('Access-Control-Allow-Origin')).toBe('http://bar.com');
199
+ expect(ctx.responseHeaders.get('Access-Control-Allow-Credentials')).toBeNull();
200
+ expect(ctx.responseHeaders.get('Access-Control-Allow-Methods')).toBe('GET');
201
+ expect(ctx.responseHeaders.get('Access-Control-Expose-Headers')).toBe('X-Expose');
202
+ expect(ctx.responseHeaders.get('Access-Control-Max-Age')).toBe('10');
203
+ });
204
+ });
205
+
206
+
@@ -0,0 +1,41 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+
3
+ import { Context } from '../../src/core/context';
4
+ import { createFileUploadMiddleware } from '../../src/middleware/builtin/file-upload';
5
+
6
+ function createMultipartRequest(boundary: string, body: string): Request {
7
+ return new Request('http://localhost/upload', {
8
+ method: 'POST',
9
+ headers: {
10
+ 'Content-Type': `multipart/form-data; boundary=${boundary}`,
11
+ },
12
+ body,
13
+ });
14
+ }
15
+
16
+ describe('FileUploadMiddleware', () => {
17
+ test('should parse multipart form data and attach files', async () => {
18
+ const boundary = '----WebKitFormBoundary7MA4YWxkTrZu0gW';
19
+ const body =
20
+ `--${boundary}\r\n` +
21
+ `Content-Disposition: form-data; name="text"\r\n\r\n` +
22
+ `hello\r\n` +
23
+ `--${boundary}\r\n` +
24
+ `Content-Disposition: form-data; name="file"; filename="test.txt"\r\n` +
25
+ `Content-Type: text/plain\r\n\r\n` +
26
+ `file content\r\n` +
27
+ `--${boundary}--`;
28
+
29
+ const ctx = new Context(createMultipartRequest(boundary, body));
30
+ const middleware = createFileUploadMiddleware();
31
+
32
+ const response = await middleware(ctx, async () => ctx.createResponse({ ok: true }));
33
+ expect(response.status).toBe(200);
34
+
35
+ const bodyData = ctx.body as { fields: Record<string, unknown>; files: Record<string, unknown> };
36
+ expect(bodyData.fields.text).toBe('hello');
37
+ expect(bodyData.files.file[0].name).toBe('test.txt');
38
+ });
39
+ });
40
+
41
+
@@ -0,0 +1,120 @@
1
+ import 'reflect-metadata';
2
+ import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
3
+
4
+ import { Application } from '../../src/core/application';
5
+ import { Controller, ControllerRegistry } from '../../src/controller/controller';
6
+ import { GET, POST } from '../../src/router/decorators';
7
+ import { UseMiddleware } from '../../src/middleware';
8
+ import type { Middleware } from '../../src/middleware';
9
+ import { RouteRegistry } from '../../src/router/registry';
10
+ import { Body } from '../../src/controller/decorators';
11
+ import { getTestPort } from '../utils/test-port';
12
+
13
+ describe('Middleware Integration', () => {
14
+ let app: Application;
15
+ let port: number;
16
+
17
+ beforeEach(() => {
18
+ port = getTestPort();
19
+ app = new Application({ port });
20
+ });
21
+
22
+ afterEach(async () => {
23
+ await app.stop();
24
+ RouteRegistry.getInstance().clear();
25
+ ControllerRegistry.getInstance().clear();
26
+ });
27
+
28
+ test('should apply global middleware', async () => {
29
+ const headerMiddleware: Middleware = async (ctx, next) => {
30
+ ctx.setHeader('x-global-middleware', 'enabled');
31
+ return await next();
32
+ };
33
+
34
+ app.use(headerMiddleware);
35
+
36
+ @Controller('/api/middleware/global')
37
+ class GlobalController {
38
+ @GET('/')
39
+ public getData() {
40
+ return { ok: true };
41
+ }
42
+ }
43
+
44
+ app.registerController(GlobalController);
45
+ await app.listen();
46
+
47
+ const response = await fetch(`http://localhost:${port}/api/middleware/global`);
48
+ expect(response.headers.get('x-global-middleware')).toBe('enabled');
49
+ const data = await response.json();
50
+ expect(data).toEqual({ ok: true });
51
+ });
52
+
53
+ test('should apply class and method level middleware', async () => {
54
+ const calls: string[] = [];
55
+
56
+ const classMiddleware: Middleware = async (ctx, next) => {
57
+ calls.push('class');
58
+ return await next();
59
+ };
60
+
61
+ const methodMiddleware: Middleware = async (ctx, next) => {
62
+ calls.push('method');
63
+ return await next();
64
+ };
65
+
66
+ @UseMiddleware(classMiddleware)
67
+ @Controller('/api/middleware/controller')
68
+ class ControllerWithMiddleware {
69
+ @UseMiddleware(methodMiddleware)
70
+ @GET('/')
71
+ public getData() {
72
+ calls.push('handler');
73
+ return { value: 'ok' };
74
+ }
75
+ }
76
+
77
+ app.registerController(ControllerWithMiddleware);
78
+ await app.listen();
79
+
80
+ const response = await fetch(`http://localhost:${port}/api/middleware/controller`);
81
+ const data = await response.json();
82
+
83
+ expect(data).toEqual({ value: 'ok' });
84
+ expect(calls).toEqual(['class', 'method', 'handler']);
85
+ });
86
+
87
+ test('should allow middleware to short-circuit request', async () => {
88
+ const guard: Middleware = async () => {
89
+ return new Response(JSON.stringify({ blocked: true }), {
90
+ status: 403,
91
+ headers: { 'Content-Type': 'application/json' },
92
+ });
93
+ };
94
+
95
+ app.use(guard);
96
+
97
+ @Controller('/api/middleware/blocked')
98
+ class BlockedController {
99
+ @POST('/')
100
+ public createUser(@Body() user: { name: string }) {
101
+ return { id: '1', ...user };
102
+ }
103
+ }
104
+
105
+ app.registerController(BlockedController);
106
+ await app.listen();
107
+
108
+ const response = await fetch(`http://localhost:${port}/api/middleware/blocked`, {
109
+ method: 'POST',
110
+ headers: { 'Content-Type': 'application/json' },
111
+ body: JSON.stringify({ name: 'John' }),
112
+ });
113
+
114
+ expect(response.status).toBe(403);
115
+ const data = await response.json();
116
+ expect(data).toEqual({ blocked: true });
117
+ });
118
+ });
119
+
120
+
@@ -0,0 +1,72 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+
3
+ import { MiddlewarePipeline } from '../../src/middleware/pipeline';
4
+ import type { Middleware } from '../../src/middleware';
5
+ import { Context } from '../../src/core/context';
6
+
7
+ function createContext(): Context {
8
+ const request = new Request('http://localhost:3000/api/test');
9
+ return new Context(request);
10
+ }
11
+
12
+ describe('MiddlewarePipeline', () => {
13
+ test('should execute middlewares in order and call final handler', async () => {
14
+ const pipeline = new MiddlewarePipeline();
15
+ const calls: string[] = [];
16
+
17
+ const first: Middleware = async (ctx, next) => {
18
+ calls.push('first:before');
19
+ const response = await next();
20
+ calls.push('first:after');
21
+ return response;
22
+ };
23
+
24
+ const second: Middleware = async (ctx, next) => {
25
+ calls.push('second:before');
26
+ const response = await next();
27
+ calls.push('second:after');
28
+ return response;
29
+ };
30
+
31
+ pipeline.use(first);
32
+ pipeline.use(second);
33
+
34
+ const context = createContext();
35
+ const response = await pipeline.run(context, async () => {
36
+ calls.push('handler');
37
+ return context.createResponse({ ok: true });
38
+ });
39
+
40
+ const data = await response.json();
41
+ expect(data).toEqual({ ok: true });
42
+ expect(calls).toEqual([
43
+ 'first:before',
44
+ 'second:before',
45
+ 'handler',
46
+ 'second:after',
47
+ 'first:after',
48
+ ]);
49
+ });
50
+
51
+ test('should allow middleware to short circuit the pipeline', async () => {
52
+ const pipeline = new MiddlewarePipeline();
53
+
54
+ const blocker: Middleware = async (ctx) => {
55
+ ctx.setStatus(401);
56
+ return ctx.createResponse({ error: 'Unauthorized' });
57
+ };
58
+
59
+ pipeline.use(blocker);
60
+
61
+ const context = createContext();
62
+ const response = await pipeline.run(context, async () => {
63
+ return context.createResponse({ ok: true });
64
+ });
65
+
66
+ expect(response.status).toBe(401);
67
+ const data = await response.json();
68
+ expect(data).toEqual({ error: 'Unauthorized' });
69
+ });
70
+ });
71
+
72
+