quantum-flow 1.0.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 (102) hide show
  1. package/.prettierrc.json +9 -0
  2. package/README.md +37 -0
  3. package/dist/app/aws/index.d.ts +1 -0
  4. package/dist/app/aws/index.js +17 -0
  5. package/dist/app/aws/lambda.d.ts +13 -0
  6. package/dist/app/aws/lambda.js +241 -0
  7. package/dist/app/http/Application.d.ts +23 -0
  8. package/dist/app/http/Application.js +208 -0
  9. package/dist/app/http/Socket.d.ts +16 -0
  10. package/dist/app/http/Socket.js +32 -0
  11. package/dist/app/http/decorators.d.ts +7 -0
  12. package/dist/app/http/decorators.js +81 -0
  13. package/dist/app/http/index.d.ts +3 -0
  14. package/dist/app/http/index.js +19 -0
  15. package/dist/app/http/websocket/WebsocetService.d.ts +20 -0
  16. package/dist/app/http/websocket/WebsocetService.js +41 -0
  17. package/dist/app/http/websocket/WebsocketServer.d.ts +30 -0
  18. package/dist/app/http/websocket/WebsocketServer.js +221 -0
  19. package/dist/constants.d.ts +16 -0
  20. package/dist/constants.js +24 -0
  21. package/dist/core/Controller.d.ts +43 -0
  22. package/dist/core/Controller.js +159 -0
  23. package/dist/core/Endpoint.d.ts +8 -0
  24. package/dist/core/Endpoint.js +43 -0
  25. package/dist/core/index.d.ts +4 -0
  26. package/dist/core/index.js +19 -0
  27. package/dist/core/utils/extractors.d.ts +15 -0
  28. package/dist/core/utils/extractors.js +29 -0
  29. package/dist/core/utils/helpers.d.ts +4 -0
  30. package/dist/core/utils/helpers.js +22 -0
  31. package/dist/core/utils/index.d.ts +3 -0
  32. package/dist/core/utils/index.js +19 -0
  33. package/dist/core/utils/websocket.d.ts +8 -0
  34. package/dist/core/utils/websocket.js +45 -0
  35. package/dist/types/common.d.ts +47 -0
  36. package/dist/types/common.js +2 -0
  37. package/dist/types/controller.d.ts +4 -0
  38. package/dist/types/controller.js +2 -0
  39. package/dist/types/http.d.ts +18 -0
  40. package/dist/types/http.js +2 -0
  41. package/dist/types/index.d.ts +5 -0
  42. package/dist/types/index.js +21 -0
  43. package/dist/types/lambda.d.ts +26 -0
  44. package/dist/types/lambda.js +2 -0
  45. package/dist/types/websocket.d.ts +55 -0
  46. package/dist/types/websocket.js +2 -0
  47. package/dist/utils/controller.d.ts +9 -0
  48. package/dist/utils/controller.js +111 -0
  49. package/dist/utils/helper.d.ts +1 -0
  50. package/dist/utils/helper.js +24 -0
  51. package/dist/utils/index.d.ts +7 -0
  52. package/dist/utils/index.js +23 -0
  53. package/dist/utils/multipart.d.ts +15 -0
  54. package/dist/utils/multipart.js +109 -0
  55. package/dist/utils/parsers.d.ts +4 -0
  56. package/dist/utils/parsers.js +76 -0
  57. package/dist/utils/server.d.ts +4 -0
  58. package/dist/utils/server.js +46 -0
  59. package/dist/utils/transform.d.ts +1 -0
  60. package/dist/utils/transform.js +23 -0
  61. package/dist/utils/validate.d.ts +1 -0
  62. package/dist/utils/validate.js +46 -0
  63. package/dist/validators/Validate.d.ts +3 -0
  64. package/dist/validators/Validate.js +40 -0
  65. package/dist/validators/index.d.ts +1 -0
  66. package/dist/validators/index.js +17 -0
  67. package/eslint.config.mjs +84 -0
  68. package/nodemon.json +5 -0
  69. package/package.json +70 -0
  70. package/src/app/aws/index.ts +1 -0
  71. package/src/app/aws/lambda.ts +283 -0
  72. package/src/app/http/Application.ts +250 -0
  73. package/src/app/http/Socket.ts +38 -0
  74. package/src/app/http/decorators.ts +115 -0
  75. package/src/app/http/index.ts +3 -0
  76. package/src/app/http/websocket/WebsocetService.ts +44 -0
  77. package/src/app/http/websocket/WebsocketServer.ts +262 -0
  78. package/src/constants.ts +25 -0
  79. package/src/core/Controller.ts +229 -0
  80. package/src/core/Endpoint.ts +39 -0
  81. package/src/core/index.ts +14 -0
  82. package/src/core/utils/extractors.ts +32 -0
  83. package/src/core/utils/helpers.ts +22 -0
  84. package/src/core/utils/index.ts +3 -0
  85. package/src/core/utils/websocket.ts +45 -0
  86. package/src/types/common.ts +60 -0
  87. package/src/types/controller.ts +2 -0
  88. package/src/types/http.ts +19 -0
  89. package/src/types/index.ts +5 -0
  90. package/src/types/lambda.ts +28 -0
  91. package/src/types/websocket.ts +57 -0
  92. package/src/utils/controller.ts +143 -0
  93. package/src/utils/helper.ts +24 -0
  94. package/src/utils/index.ts +7 -0
  95. package/src/utils/multipart.ts +93 -0
  96. package/src/utils/parsers.ts +87 -0
  97. package/src/utils/server.ts +49 -0
  98. package/src/utils/transform.ts +24 -0
  99. package/src/utils/validate.ts +53 -0
  100. package/src/validators/Validate.ts +48 -0
  101. package/src/validators/index.ts +1 -0
  102. package/tsconfig.json +51 -0
@@ -0,0 +1,44 @@
1
+ import { IWebSocketService } from '@types';
2
+ import { WebSocketServer } from './WebsocketServer';
3
+
4
+ export class WebSocketService implements IWebSocketService {
5
+ private static instance: WebSocketService;
6
+ private wss: WebSocketServer | null = null;
7
+
8
+ private constructor() {}
9
+
10
+ static getInstance(): WebSocketService {
11
+ if (!WebSocketService.instance) {
12
+ WebSocketService.instance = new WebSocketService();
13
+ }
14
+ return WebSocketService.instance;
15
+ }
16
+
17
+ initialize(wss: WebSocketServer) {
18
+ this.wss = wss;
19
+ }
20
+
21
+ sendToClient(clientId: string, message: any): boolean {
22
+ if (!this.wss) return false;
23
+ return this.wss.sendToClient(clientId, message);
24
+ }
25
+
26
+ publishToTopic(topic: string, data: any, exclude?: string[]) {
27
+ if (!this.wss) return;
28
+ this.wss.publishToTopic(topic, data, exclude);
29
+ }
30
+
31
+ broadcast(message: any, excludeClientId?: string) {
32
+ if (!this.wss) return;
33
+ this.wss.broadcast(message, excludeClientId);
34
+ }
35
+
36
+ getStats() {
37
+ if (!this.wss) return { clients: 0, topics: [] };
38
+ return this.wss.getStats();
39
+ }
40
+
41
+ isAvailable(): boolean {
42
+ return this.wss !== null;
43
+ }
44
+ }
@@ -0,0 +1,262 @@
1
+ import { WebSocketClient, WebSocketEvent, WebSocketMessage } from '@types';
2
+ import http from 'http';
3
+ import { v4 as uuidv4 } from 'uuid';
4
+ import WebSocket from 'ws';
5
+
6
+ export class WebSocketServer {
7
+ private wss: WebSocket.Server | null = null;
8
+ private clients: Map<string, WebSocketClient> = new Map();
9
+ private topics: Map<string, Set<string>> = new Map();
10
+ private controllers: any[] = [];
11
+ private options: any;
12
+
13
+ constructor(server: http.Server, options?: any) {
14
+ this.options = options;
15
+
16
+ server.on('upgrade', (request, socket, head) => {
17
+ if (this.shouldHandleWebSocket(request.url)) {
18
+ this.ensureServer(server);
19
+ this.wss?.handleUpgrade(request, socket, head, (ws) => {
20
+ this.wss?.emit('connection', ws, request);
21
+ });
22
+ } else {
23
+ socket.destroy();
24
+ }
25
+ });
26
+ }
27
+
28
+ private shouldHandleWebSocket(url: string = ''): boolean {
29
+ const path = this.options?.path || '/ws';
30
+ return url.startsWith(path);
31
+ }
32
+
33
+ private ensureServer(server: http.Server) {
34
+ if (!this.wss) {
35
+ this.wss = new WebSocket.Server({
36
+ server,
37
+ path: this.options?.path || '/ws',
38
+ noServer: true,
39
+ });
40
+
41
+ this.wss.on('connection', (socket, request) => {
42
+ this.handleConnection(socket, request);
43
+ });
44
+ }
45
+ }
46
+
47
+ private async handleConnection(socket: WebSocket, request: http.IncomingMessage) {
48
+ const clientId = uuidv4();
49
+ const client: WebSocketClient = {
50
+ id: clientId,
51
+ socket,
52
+ topics: new Set(),
53
+ data: {},
54
+ connectedAt: new Date(),
55
+ };
56
+
57
+ this.clients.set(clientId, client);
58
+
59
+ socket.on('message', (data: WebSocket.Data) => {
60
+ this.handleMessage(client, data);
61
+ });
62
+
63
+ socket.on('close', () => {
64
+ this.handleClose(client);
65
+ });
66
+
67
+ socket.on('error', (error: any) => {
68
+ this.handleError(client, error);
69
+ });
70
+
71
+ await this.triggerHandlers('connection', { type: 'connection', client });
72
+ }
73
+
74
+ private async handleMessage(client: WebSocketClient, data: WebSocket.Data) {
75
+ try {
76
+ const message = JSON.parse(data.toString()) as WebSocketMessage;
77
+
78
+ if (message.type === 'subscribe' && message.topic) {
79
+ this.subscribeToTopic(client, message.topic);
80
+ return;
81
+ }
82
+
83
+ if (message.type === 'unsubscribe' && message.topic) {
84
+ this.unsubscribeFromTopic(client, message.topic);
85
+ return;
86
+ }
87
+
88
+ if (message.type === 'topic_message' && message.topic) {
89
+ await this.triggerHandlers(
90
+ 'message',
91
+ {
92
+ type: 'message',
93
+ client,
94
+ message,
95
+ },
96
+ message.topic,
97
+ );
98
+
99
+ this.publishToTopic(message.topic, message.data, [client.id]);
100
+ return;
101
+ }
102
+
103
+ await this.triggerHandlers('message', {
104
+ type: 'message',
105
+ client,
106
+ message,
107
+ });
108
+ } catch (error) {
109
+ client.socket.send(
110
+ JSON.stringify({
111
+ type: 'error',
112
+ data: { message: 'Invalid message format' },
113
+ }),
114
+ );
115
+ }
116
+ }
117
+
118
+ private async handleClose(client: WebSocketClient) {
119
+ client.topics.forEach((topic) => {
120
+ const topicClients = this.topics.get(topic);
121
+ if (topicClients) {
122
+ topicClients.delete(client.id);
123
+ if (topicClients.size === 0) {
124
+ this.topics.delete(topic);
125
+ }
126
+ }
127
+ });
128
+
129
+ this.clients.delete(client.id);
130
+
131
+ await this.triggerHandlers('close', {
132
+ type: 'close',
133
+ client,
134
+ });
135
+ }
136
+
137
+ private async handleError(client: WebSocketClient, error: Error) {
138
+ await this.triggerHandlers('error', {
139
+ type: 'error',
140
+ client,
141
+ data: error,
142
+ });
143
+ }
144
+
145
+ private async triggerHandlers(eventType: string, event: WebSocketEvent, topic?: string) {
146
+ for (const controller of this.controllers) {
147
+ const handlers = Reflect.getMetadata('websocket:handler', controller.constructor) || [];
148
+
149
+ const matchingHandlers = handlers.filter(
150
+ (h: any) => h.type === eventType && (!h.topic || h.topic === topic),
151
+ );
152
+
153
+ for (const handler of matchingHandlers) {
154
+ try {
155
+ await controller[handler.method](event);
156
+ } catch (error) {
157
+ console.error(`Error in WebSocket handler ${handler.method}:`, error);
158
+ }
159
+ }
160
+
161
+ const subscriptions = Reflect.getMetadata('websocket:topic', controller.constructor) || [];
162
+
163
+ if (eventType === 'message' && topic) {
164
+ const matchingSubs = subscriptions.filter((s: any) => s.topic === topic);
165
+
166
+ for (const sub of matchingSubs) {
167
+ try {
168
+ await controller[sub.method](event);
169
+ } catch (error) {
170
+ console.error(`Error in subscription ${sub.method}:`, error);
171
+ }
172
+ }
173
+ }
174
+ }
175
+ }
176
+
177
+ public registerControllers(controllers: any[]) {
178
+ this.controllers = controllers;
179
+ }
180
+
181
+ public subscribeToTopic(client: WebSocketClient, topic: string) {
182
+ if (!this.topics.has(topic)) {
183
+ this.topics.set(topic, new Set());
184
+ }
185
+ this.topics.get(topic)!.add(client.id);
186
+ client.topics.add(topic);
187
+
188
+ client.socket.send(
189
+ JSON.stringify({
190
+ type: 'subscribed',
191
+ topic,
192
+ data: { success: true },
193
+ }),
194
+ );
195
+ }
196
+
197
+ public unsubscribeFromTopic(client: WebSocketClient, topic: string) {
198
+ const topicClients = this.topics.get(topic);
199
+ if (topicClients) {
200
+ topicClients.delete(client.id);
201
+ if (topicClients.size === 0) {
202
+ this.topics.delete(topic);
203
+ }
204
+ }
205
+ client.topics.delete(topic);
206
+
207
+ client.socket.send(
208
+ JSON.stringify({
209
+ type: 'unsubscribed',
210
+ topic,
211
+ data: { success: true },
212
+ }),
213
+ );
214
+ }
215
+
216
+ public publishToTopic(topic: string, data: any, exclude?: string[]) {
217
+ const topicClients = this.topics.get(topic);
218
+ if (!topicClients) return;
219
+
220
+ const message = JSON.stringify({
221
+ type: 'message',
222
+ topic,
223
+ data,
224
+ timestamp: new Date().toISOString(),
225
+ });
226
+
227
+ topicClients.forEach((clientId) => {
228
+ const client = this.clients.get(clientId);
229
+ if (client && exclude?.includes(client.id)) {
230
+ client.socket.send(message);
231
+ }
232
+ });
233
+ }
234
+
235
+ public sendToClient(clientId: string, message: any): boolean {
236
+ const client = this.clients.get(clientId);
237
+ if (client) {
238
+ client.socket.send(JSON.stringify(message));
239
+ return true;
240
+ }
241
+ return false;
242
+ }
243
+
244
+ public broadcast(message: any, excludeClientId?: string) {
245
+ const messageStr = JSON.stringify(message);
246
+ this.clients.forEach((client, clientId) => {
247
+ if (clientId !== excludeClientId) {
248
+ client.socket.send(messageStr);
249
+ }
250
+ });
251
+ }
252
+
253
+ public getStats() {
254
+ return {
255
+ clients: this.clients.size,
256
+ topics: Array.from(this.topics.entries()).map(([topic, clients]) => ({
257
+ topic,
258
+ subscribers: clients.size,
259
+ })),
260
+ };
261
+ }
262
+ }
@@ -0,0 +1,25 @@
1
+ export const PARAM_METADATA_KEY = 'design:parameters';
2
+ export const APP_METADATA_KEY = 'app:configuration';
3
+ export const ROUTE_PREFIX = 'route:profix';
4
+ export const MIDDLEWARES = 'route:middlewares';
5
+ export const CONTROLLERS = 'app:controllers';
6
+ export const INTERCEPTORS = 'app:interceptors';
7
+ export const ENDPOINT = 'route:endpoints';
8
+ export const OK_METADATA_KEY = 'custom:ok';
9
+
10
+ export const SERVER_CONFIG_KEY = 'server:config';
11
+ export const SERVER_MODULES_KEY = 'server:modules';
12
+
13
+ export const WS_METADATA_KEY = 'websocket:handler';
14
+ export const WS_TOPIC_KEY = 'websocket:topic';
15
+ export const WS_SERVICE_KEY = 'websocket:service';
16
+
17
+ export const STOPPED = `
18
+ ╔════════════════════════════════════════╗
19
+ ║ 👋 Server stopped ║
20
+ ║ 📊 Status: STOPPED ║
21
+ ╚════════════════════════════════════════╝
22
+ `;
23
+
24
+ export const OK_STATUSES = [200, 201, 202, 203, 204, 205, 206, 207, 208, 226];
25
+ export const TO_VALIDATE = ['headers', 'params', 'multipart', 'query', 'body'];
@@ -0,0 +1,229 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import {
3
+ CONTROLLERS,
4
+ ENDPOINT,
5
+ INTERCEPTORS,
6
+ MIDDLEWARES,
7
+ OK_METADATA_KEY,
8
+ OK_STATUSES,
9
+ ROUTE_PREFIX,
10
+ } from '@constants';
11
+ import { Interceptor, Middleware } from '@types';
12
+ import { executeControllerMethod, getControllerMethods, matchRoute } from '@utils';
13
+ import { ServerResponse } from 'http';
14
+ import 'reflect-metadata';
15
+
16
+ type ControllerClass = { new (...args: any[]): any };
17
+ type ControllerInstance = InstanceType<ControllerClass>;
18
+
19
+ interface ControllerConfig {
20
+ prefix: string;
21
+ middlewares?: Array<Middleware>;
22
+ controllers?: ControllerClass[];
23
+ interceptors?: Array<(...args: any[]) => any> | ((...args: any[]) => any);
24
+ }
25
+
26
+ export function Controller(
27
+ config: string | ControllerConfig,
28
+ middlewares: Array<Interceptor> = [],
29
+ ) {
30
+ // Handle both string and config object
31
+ const routePrefix = typeof config === 'string' ? config : config.prefix;
32
+ const controllers = typeof config === 'object' ? config.controllers : undefined;
33
+ const controllerMiddlewares =
34
+ typeof config === 'object' ? [...(config.middlewares || []), ...middlewares] : middlewares;
35
+
36
+ const interceptors =
37
+ typeof config === 'object'
38
+ ? config.interceptors
39
+ ? ([] as any).concat(config.interceptors)
40
+ : []
41
+ : [];
42
+
43
+ return function <T extends ControllerClass>(constructor: T) {
44
+ const proto = constructor.prototype;
45
+
46
+ Reflect.defineMetadata(ROUTE_PREFIX, routePrefix, proto);
47
+ Reflect.defineMetadata(MIDDLEWARES, controllerMiddlewares, proto);
48
+ Reflect.defineMetadata(CONTROLLERS, controllers || [], proto);
49
+ Reflect.defineMetadata(INTERCEPTORS, interceptors, proto);
50
+
51
+ for (const key of Object.getOwnPropertyNames(proto)) {
52
+ if (key === 'constructor') continue;
53
+
54
+ const descriptor = Object.getOwnPropertyDescriptor(proto, key);
55
+ if (!descriptor || typeof descriptor.value !== 'function') continue;
56
+
57
+ const original = descriptor.value;
58
+
59
+ Object.defineProperty(proto, key, {
60
+ ...descriptor,
61
+ value: async function (...args: any[]) {
62
+ try {
63
+ return await original.apply(this, args);
64
+ } catch (err: any) {
65
+ return {
66
+ status: err.status ?? 400,
67
+ message: err.message,
68
+ data: err,
69
+ };
70
+ }
71
+ },
72
+ });
73
+ }
74
+
75
+ return class extends constructor {
76
+ executeControllerMethod = executeControllerMethod;
77
+ getControllerMethods = getControllerMethods;
78
+ constructor(...args: any[]) {
79
+ super(...args);
80
+ }
81
+
82
+ async getResponse(data: {
83
+ controllerInstance: ControllerInstance;
84
+ name: string;
85
+ payload: any;
86
+ interceptors: Array<(...args: any[]) => any | Promise<any>>;
87
+ response?: ServerResponse;
88
+ }) {
89
+ try {
90
+ let response = await this.executeControllerMethod(
91
+ data.controllerInstance,
92
+ data.name,
93
+ data.payload,
94
+ data.response,
95
+ );
96
+
97
+ let status = response.status ?? 200;
98
+
99
+ const isError = !OK_STATUSES.includes(status);
100
+
101
+ for (let index = 0; index < data.interceptors?.length && !isError; index++) {
102
+ const interceptor = data.interceptors[index];
103
+ response = await interceptor(response);
104
+ }
105
+
106
+ const propertyName = data.name;
107
+ const prototype = Object.getPrototypeOf(data.controllerInstance);
108
+
109
+ const methodOkStatus = Reflect.getMetadata(
110
+ OK_METADATA_KEY,
111
+ data.controllerInstance,
112
+ propertyName,
113
+ );
114
+
115
+ if (methodOkStatus) {
116
+ !isError && (status = methodOkStatus);
117
+ } else {
118
+ const classOkStatus = Reflect.getMetadata(OK_METADATA_KEY, prototype);
119
+ !isError && classOkStatus && (status = classOkStatus);
120
+ }
121
+
122
+ return { status, data: response };
123
+ } catch (err) {
124
+ console.error(err);
125
+ throw err;
126
+ }
127
+ }
128
+
129
+ handleRequest = async (request: any, response?: ServerResponse) => {
130
+ const method = request.method;
131
+ const path = (request.url.path ?? request.url.pathname ?? '').replace(/^\/+/, '');
132
+
133
+ const baseInterceptors = Reflect.getMetadata(INTERCEPTORS, proto);
134
+
135
+ const routePrefix: string = Reflect.getMetadata(ROUTE_PREFIX, proto) || '';
136
+ const middlewares: Array<(Request: any) => any> =
137
+ Reflect.getMetadata(MIDDLEWARES, proto) || [];
138
+ const subControllers: ControllerClass[] = Reflect.getMetadata(CONTROLLERS, proto) || [];
139
+
140
+ // Try sub-controllers
141
+ for (const SubController of subControllers) {
142
+ const controllerInstance = new SubController(SubController);
143
+ if (!controllerInstance) continue;
144
+
145
+ const subInterceptors = Reflect.getMetadata(INTERCEPTORS, controllerInstance);
146
+
147
+ const controllerPrefix: string =
148
+ Reflect.getMetadata(ROUTE_PREFIX, SubController.prototype) || '';
149
+ const controllerMiddlewares: Array<(Request: any) => any> =
150
+ Reflect.getMetadata(MIDDLEWARES, SubController.prototype) || [];
151
+
152
+ const methods = this.getControllerMethods(controllerInstance);
153
+
154
+ for (const methodInfo of methods) {
155
+ if (methodInfo.httpMethod === method || methodInfo.httpMethod === 'USE') {
156
+ // Build full pattern: main prefix + controller prefix + method pattern
157
+ const fullPattern = [routePrefix, controllerPrefix, methodInfo.pattern]
158
+ .filter(Boolean)
159
+ .join('/')
160
+ .replace(/\/+/g, '/');
161
+
162
+ const pathParams = matchRoute(fullPattern, path);
163
+ if (pathParams) {
164
+ let payload = { ...request, params: pathParams };
165
+
166
+ // Apply all middlewares in order: main controller -> sub-controller -> method
167
+ for (const middleware of [
168
+ ...middlewares,
169
+ ...controllerMiddlewares,
170
+ ...(methodInfo.middlewares || []),
171
+ ]) {
172
+ const middlawareResponde = await middleware(payload, response);
173
+ payload = { ...payload, ...middlawareResponde };
174
+ }
175
+
176
+ return this.getResponse({
177
+ interceptors: [...subInterceptors, ...baseInterceptors],
178
+ controllerInstance,
179
+ name: methodInfo.name,
180
+ payload,
181
+ response,
182
+ });
183
+ }
184
+ }
185
+ }
186
+ }
187
+
188
+ const propertyNames = Object.getOwnPropertyNames(proto);
189
+
190
+ for (const propertyName of propertyNames) {
191
+ const fn = (this as any)[propertyName];
192
+ if (typeof fn !== 'function') continue;
193
+
194
+ const endpointMeta = Reflect.getMetadata(ENDPOINT, proto, propertyName) || [];
195
+
196
+ const [httpMethod, routePattern] = endpointMeta;
197
+
198
+ if (httpMethod === method || httpMethod === 'USE') {
199
+ const fullPattern = [routePrefix, routePattern]
200
+ .filter(Boolean)
201
+ .join('/')
202
+ .replace(/\/+/g, '/');
203
+
204
+ const pathParams = matchRoute(fullPattern, path);
205
+ if (pathParams) {
206
+ let payload = { ...request, params: pathParams };
207
+
208
+ // Apply controller-level middlewares
209
+ for (const middleware of middlewares) {
210
+ const middlewareResponse = await middleware(payload);
211
+ payload = { ...payload, middlewareResponse };
212
+ }
213
+
214
+ return this.getResponse({
215
+ interceptors: baseInterceptors,
216
+ controllerInstance: this,
217
+ name: propertyName,
218
+ payload,
219
+ response,
220
+ });
221
+ }
222
+ }
223
+ }
224
+
225
+ return { status: 404, message: 'Method Not Found' };
226
+ };
227
+ };
228
+ };
229
+ }
@@ -0,0 +1,39 @@
1
+ import { ENDPOINT } from '@constants';
2
+ import { Middleware } from '@types';
3
+
4
+ export function Endpoint(method: string, pathPattern?: string, middlewares?: Middleware[]) {
5
+ return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
6
+ const originalMethod = descriptor.value;
7
+
8
+ if (!originalMethod) {
9
+ console.warn('❌ originalMethod is undefined!');
10
+ return descriptor;
11
+ }
12
+
13
+ if (method && pathPattern) {
14
+ Reflect.defineMetadata(ENDPOINT, [method, pathPattern], target, propertyKey);
15
+ Reflect.defineMetadata('middlewares', middlewares || [], target, propertyKey);
16
+ }
17
+
18
+ return descriptor;
19
+ };
20
+ }
21
+
22
+ export const GET = (pathPattern?: string, middelwares?: Middleware[]) => {
23
+ return Endpoint('GET', pathPattern, middelwares);
24
+ };
25
+ export const POST = (pathPattern?: string, middelwares?: Middleware[]) => {
26
+ return Endpoint('POST', pathPattern, middelwares);
27
+ };
28
+ export const PUT = (pathPattern?: string, middelwares?: Middleware[]) => {
29
+ return Endpoint('PUT', pathPattern, middelwares);
30
+ };
31
+ export const PATCH = (pathPattern?: string, middelwares?: Middleware[]) => {
32
+ return Endpoint('PATCH', pathPattern, middelwares);
33
+ };
34
+ export const DELETE = (pathPattern?: string, middelwares?: Middleware[]) => {
35
+ return Endpoint('DELETE', pathPattern, middelwares);
36
+ };
37
+ export const USE = (pathPattern?: string, middelwares?: Middleware[]) => {
38
+ return Endpoint('USE', pathPattern, middelwares);
39
+ };
@@ -0,0 +1,14 @@
1
+ export {
2
+ EndpointResponse,
3
+ IController,
4
+ Interceptor,
5
+ Middleware,
6
+ Request,
7
+ Router,
8
+ WebSocketClient,
9
+ WebSocketEvent,
10
+ WebSocketMessage,
11
+ } from '@types';
12
+ export * from './Controller';
13
+ export * from './Endpoint';
14
+ export * from './utils';
@@ -0,0 +1,32 @@
1
+ import { PARAM_METADATA_KEY } from '@constants';
2
+ import { ParamDecoratorType } from '@types';
3
+
4
+ export interface ParamMetadata {
5
+ index: number;
6
+ type: ParamDecoratorType;
7
+ dto?: any;
8
+ name?: string;
9
+ }
10
+
11
+ function createParamDecorator(type: ParamDecoratorType, dto?: any, name?: string) {
12
+ return function (target: any, propertyKey: string | symbol, parameterIndex: number) {
13
+ const existingParams: ParamMetadata[] =
14
+ Reflect.getMetadata(PARAM_METADATA_KEY, target, propertyKey) || [];
15
+
16
+ existingParams.push({ index: parameterIndex, type, dto, name });
17
+ existingParams.sort((a, b) => a.index - b.index);
18
+
19
+ Reflect.defineMetadata(PARAM_METADATA_KEY, existingParams, target, propertyKey);
20
+
21
+ const saved = Reflect.getMetadata(PARAM_METADATA_KEY, target, propertyKey);
22
+ };
23
+ }
24
+
25
+ export const Body = (dto?: any) => createParamDecorator('body', dto);
26
+ export const Params = (name?: string) => createParamDecorator('params', undefined, name);
27
+ export const Query = (name?: string) => createParamDecorator('query', undefined, name);
28
+ export const Request = () => createParamDecorator('request');
29
+ export const Headers = (name?: string) => createParamDecorator('headers', undefined, name);
30
+ export const Cookies = (name?: string) => createParamDecorator('cookies', undefined, name);
31
+ export const Multipart = (name?: string) => createParamDecorator('multipart', undefined, name);
32
+ export const Response = () => createParamDecorator('response');
@@ -0,0 +1,22 @@
1
+ import { OK_METADATA_KEY } from '@constants';
2
+
3
+ export function Status(status: number) {
4
+ return function (
5
+ target: any,
6
+ propertyKey?: string | symbol,
7
+ descriptor?: TypedPropertyDescriptor<any>,
8
+ ): void {
9
+ if (!propertyKey) {
10
+ Reflect.defineMetadata(OK_METADATA_KEY, status, target.prototype || target);
11
+ return;
12
+ }
13
+
14
+ if (descriptor) {
15
+ Reflect.defineMetadata(OK_METADATA_KEY, status, target, propertyKey);
16
+ }
17
+ };
18
+ }
19
+
20
+ export const Ok200 = () => Status(200);
21
+ export const Ok201 = () => Status(201);
22
+ export const Ok204 = () => Status(204);
@@ -0,0 +1,3 @@
1
+ export * from './extractors';
2
+ export * from './helpers';
3
+ export * from './websocket';