quantum-flow 1.8.1 → 1.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +112 -36
- package/dist/app/http/Application.d.ts +3 -2
- package/dist/app/http/Application.js +27 -19
- package/dist/app/http/index.d.ts +0 -1
- package/dist/app/http/index.js +0 -1
- package/dist/constants.d.ts +3 -0
- package/dist/constants.js +4 -1
- package/dist/core/Controller.d.ts +12 -8
- package/dist/core/Controller.js +41 -12
- package/dist/sse/decorators.d.ts +6 -0
- package/dist/sse/decorators.js +29 -0
- package/dist/sse/index.d.ts +2 -0
- package/dist/sse/index.js +8 -0
- package/dist/sse/server.d.ts +14 -0
- package/dist/sse/server.js +85 -0
- package/dist/sse/service.d.ts +17 -0
- package/dist/sse/service.js +39 -0
- package/dist/types/common.d.ts +1 -1
- package/dist/types/controller.d.ts +40 -4
- package/dist/types/http.d.ts +3 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/sse.d.ts +37 -0
- package/dist/types/sse.js +2 -0
- package/dist/types/websocket.d.ts +1 -0
- package/dist/utils/controller.d.ts +1 -1
- package/dist/utils/controller.js +13 -8
- package/dist/ws/decorators.d.ts +1 -1
- package/dist/ws/decorators.js +2 -5
- package/dist/ws/index.d.ts +1 -0
- package/dist/ws/index.js +1 -0
- package/dist/{app/http/websocket/WebsocketServer.d.ts → ws/server.d.ts} +3 -3
- package/dist/{app/http/websocket/WebsocketServer.js → ws/server.js} +5 -13
- package/dist/{app/http/websocket/WebsocketService.d.ts → ws/service.d.ts} +2 -2
- package/dist/{app/http/Socket.d.ts → ws/socket.d.ts} +1 -1
- package/dist/{app/http/Socket.js → ws/socket.js} +6 -6
- package/package.json +6 -1
- /package/dist/{app/http/websocket/WebsocketService.js → ws/service.js} +0 -0
package/README.md
CHANGED
|
@@ -22,6 +22,7 @@ You can use controllers and server functionality by importing controllers and cr
|
|
|
22
22
|
- `quantum-flow/core` - Core framework components like Controller and Endpoint.
|
|
23
23
|
- `quantum-flow/middlewares` - Core middlewares to use within the application.
|
|
24
24
|
- `quantum-flow/ws` - Websocket decorators.
|
|
25
|
+
- `quantum-flow/sse` - Server side events decorators.
|
|
25
26
|
|
|
26
27
|
---
|
|
27
28
|
|
|
@@ -138,7 +139,8 @@ server.listen().catch(console.error);
|
|
|
138
139
|
- Use `@Multipart` for handling multipart/form-data requests.
|
|
139
140
|
- Use `@Request` to access the original request object.
|
|
140
141
|
- Use `@Response` to access the original object.
|
|
141
|
-
- Use `@InjectWS` to access the
|
|
142
|
+
- Use `@InjectWS` to access the websocket service.
|
|
143
|
+
- Use `@InjectSSE()` to access the server-side-event service.
|
|
142
144
|
|
|
143
145
|
# AWS Lambda Support
|
|
144
146
|
|
|
@@ -168,6 +170,18 @@ export const handler = LambdaAdapter.createHandler(RootController);
|
|
|
168
170
|
|
|
169
171
|
Enable WebSocket in the server configuration and register WebSocket controllers.
|
|
170
172
|
|
|
173
|
+
## Enabling WebSocket Support in Server
|
|
174
|
+
|
|
175
|
+
To enable SSE support, configure your HTTP server with the `sse: true` option and register controllers that use SSE.
|
|
176
|
+
|
|
177
|
+
Example server setup:
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
@Server( {websocket: { enabled: true, path: '/ws' } })
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## Injecting WebSocket events in Controller
|
|
184
|
+
|
|
171
185
|
```typescript
|
|
172
186
|
import { OnConnection, Subscribe, OnMessage } from 'quantum-flow/ws';
|
|
173
187
|
@Controller('socket')
|
|
@@ -231,65 +245,127 @@ export class Socket {
|
|
|
231
245
|
}
|
|
232
246
|
```
|
|
233
247
|
|
|
234
|
-
#
|
|
248
|
+
# Server-Sent Events (SSE) Support
|
|
249
|
+
|
|
250
|
+
The framework supports Server-Sent Events (SSE) to enable real-time, one-way communication from the server to clients over HTTP.
|
|
251
|
+
|
|
252
|
+
## Defining SSE Controllers
|
|
235
253
|
|
|
236
|
-
|
|
254
|
+
Use the `@Controller` decorator to define controllers with a `prefix` and optionally include sub-controllers. This allows modular organization of your API endpoints.
|
|
237
255
|
|
|
238
|
-
|
|
239
|
-
Should be used only with @Server decorator
|
|
256
|
+
Example:
|
|
240
257
|
|
|
241
258
|
```typescript
|
|
242
|
-
@
|
|
243
|
-
|
|
259
|
+
@Controller({
|
|
260
|
+
prefix: 'user',
|
|
261
|
+
controllers: [UserMetadata],
|
|
262
|
+
middlewares: [function UserGlobalUse() {}],
|
|
263
|
+
interceptor: (data, req, res) => {
|
|
264
|
+
return { data, intercepted: true };
|
|
265
|
+
},
|
|
266
|
+
})
|
|
267
|
+
export class User {}
|
|
244
268
|
```
|
|
245
269
|
|
|
246
|
-
|
|
270
|
+
## Injecting SSE Service
|
|
271
|
+
|
|
272
|
+
Use the `@InjectSSE` decorator in your controller methods to create and manage SSE connections. This service allows sending events to connected clients.
|
|
247
273
|
|
|
248
|
-
|
|
274
|
+
Example method using SSE:
|
|
249
275
|
|
|
250
276
|
```typescript
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
class
|
|
277
|
+
import { InjectSSE } from 'quantum-flow/sse';
|
|
278
|
+
|
|
279
|
+
@Controller('user')
|
|
280
|
+
export class UserMetadata {
|
|
281
|
+
@GET('/subscribesse')
|
|
282
|
+
async subscribesse(@InjectSSE() sse) {
|
|
283
|
+
const client = sse.createConnection(res);
|
|
284
|
+
|
|
285
|
+
sse.sendToClient(client.id, {
|
|
286
|
+
event: 'welcome message',
|
|
287
|
+
data: { message: 'Connected to notifications' },
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
return 'hellow';
|
|
291
|
+
}
|
|
292
|
+
}
|
|
255
293
|
```
|
|
256
294
|
|
|
257
|
-
|
|
295
|
+
## SSE Event Decorators
|
|
258
296
|
|
|
259
|
-
|
|
297
|
+
The framework provides decorators to handle SSE connection lifecycle events:
|
|
298
|
+
|
|
299
|
+
- `@OnSSEConnection()`: Decorate a method to handle new SSE connections.
|
|
300
|
+
- `@OnSSEError()`: Decorate a method to handle SSE errors.
|
|
301
|
+
- `@OnSSEClose()`: Decorate a method to handle SSE connection closures.
|
|
302
|
+
|
|
303
|
+
Example usage:
|
|
260
304
|
|
|
261
305
|
```typescript
|
|
262
|
-
|
|
263
|
-
|
|
306
|
+
import { OnSSEConnection, OnSSEError, OnSSEClose } from 'quantum-flow/sse';
|
|
307
|
+
|
|
308
|
+
@Controller('user')
|
|
309
|
+
export class User {
|
|
310
|
+
@OnSSEConnection()
|
|
311
|
+
async onsseconnection(@Request() req: any, @Response() res: any) {
|
|
312
|
+
console.log('SSE connection established');
|
|
313
|
+
return req.body;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
@OnSSEError()
|
|
317
|
+
async onsseerror(@Request() req: any, @Response() res: any) {
|
|
318
|
+
console.log('SSE error occurred');
|
|
319
|
+
return req.body;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
@OnSSEClose()
|
|
323
|
+
async onsseclose(@Request() req: any, @Response() res: any) {
|
|
324
|
+
console.log('SSE connection closed');
|
|
325
|
+
return req.body;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
264
328
|
```
|
|
265
329
|
|
|
266
|
-
|
|
330
|
+
## Sending SSE Events
|
|
267
331
|
|
|
268
|
-
|
|
332
|
+
Use the injected SSE service to send events to clients. You can send events to all clients or specific clients by ID.
|
|
333
|
+
|
|
334
|
+
Example:
|
|
269
335
|
|
|
270
336
|
```typescript
|
|
271
|
-
|
|
272
|
-
|
|
337
|
+
sse.sendToClient(clientId, {
|
|
338
|
+
event: 'eventName',
|
|
339
|
+
data: { key: 'value' },
|
|
340
|
+
});
|
|
273
341
|
```
|
|
274
342
|
|
|
275
|
-
|
|
343
|
+
## Enabling SSE Support in Server
|
|
276
344
|
|
|
277
|
-
|
|
345
|
+
To enable SSE support, configure your HTTP server with the `sse: true` option and register controllers that use SSE.
|
|
346
|
+
|
|
347
|
+
Example server setup:
|
|
278
348
|
|
|
279
349
|
```typescript
|
|
280
|
-
import {
|
|
281
|
-
|
|
282
|
-
@IsEmail()
|
|
283
|
-
email: string;
|
|
350
|
+
import { Server, HttpServer } from 'quantum-flow/http';
|
|
351
|
+
import { User, UserMetadata } from './controllers';
|
|
284
352
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
}
|
|
353
|
+
@Server({
|
|
354
|
+
controllers: [User, UserMetadata],
|
|
355
|
+
sse: { enabled: true },
|
|
356
|
+
})
|
|
357
|
+
class App {}
|
|
288
358
|
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
async createUser(@Body(UserDTO) user: UserDTO, @Query() query) {
|
|
292
|
-
// Your logic here
|
|
293
|
-
}
|
|
294
|
-
}
|
|
359
|
+
const server = new HttpServer(App);
|
|
360
|
+
server.listen().catch(console.error);
|
|
295
361
|
```
|
|
362
|
+
|
|
363
|
+
## Summary
|
|
364
|
+
|
|
365
|
+
- Use `@Controller` with `prefix` and `controllers` to organize your API.
|
|
366
|
+
- Use `@InjectSSE` to create SSE connections in controller methods.
|
|
367
|
+
- Use the SSE service's `send` method to push events to clients.
|
|
368
|
+
- Use `@OnSSEConnection`, `@OnSSEError`, and `@OnSSEClose` decorators to handle SSE lifecycle events.
|
|
369
|
+
- Enable SSE in your server configuration and register SSE controllers.
|
|
370
|
+
|
|
371
|
+
This setup allows you to build real-time, event-driven APIs using Server-Sent Events in a clean and modular way.
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { ServerConfig } from '../../types/index.js';
|
|
2
2
|
import http from 'http';
|
|
3
|
-
|
|
4
|
-
export declare class HttpServer extends Socket {
|
|
3
|
+
export declare class HttpServer {
|
|
5
4
|
private app;
|
|
6
5
|
private config;
|
|
7
6
|
private isRunning;
|
|
7
|
+
private sse?;
|
|
8
|
+
private websocket?;
|
|
8
9
|
constructor(configOrClass: new (...args: any[]) => any);
|
|
9
10
|
private logConfig;
|
|
10
11
|
listen(port?: number, host?: string): Promise<http.Server>;
|
|
@@ -7,21 +7,29 @@ exports.HttpServer = void 0;
|
|
|
7
7
|
const _constants_1 = require("../../constants.js");
|
|
8
8
|
const _utils_1 = require("../../utils/index.js");
|
|
9
9
|
const http_1 = __importDefault(require("http"));
|
|
10
|
-
const
|
|
11
|
-
const
|
|
12
|
-
const
|
|
13
|
-
|
|
10
|
+
const server_1 = require("../../ws/server");
|
|
11
|
+
const service_1 = require("../../ws/service");
|
|
12
|
+
const server_2 = require("../../sse/server");
|
|
13
|
+
const service_2 = require("../../sse/service");
|
|
14
|
+
class HttpServer {
|
|
14
15
|
app;
|
|
15
16
|
config;
|
|
16
17
|
isRunning = false;
|
|
18
|
+
sse;
|
|
19
|
+
websocket;
|
|
17
20
|
constructor(configOrClass) {
|
|
18
|
-
super();
|
|
19
21
|
this.config = (0, _utils_1.resolveConfig)(configOrClass);
|
|
20
22
|
const app = http_1.default.createServer(this.requestHandler.bind(this));
|
|
23
|
+
const controllers = this.getAllControllers(this.config.controllers);
|
|
21
24
|
if (this.config.websocket?.enabled) {
|
|
22
|
-
this.
|
|
23
|
-
this.
|
|
24
|
-
|
|
25
|
+
this.websocket = new server_1.WebSocketServer(app, { path: this.config.websocket.path });
|
|
26
|
+
this.websocket.registerControllers(controllers);
|
|
27
|
+
service_1.WebSocketService.getInstance().initialize(this.websocket);
|
|
28
|
+
}
|
|
29
|
+
if (this.config.sse?.enabled) {
|
|
30
|
+
this.sse = new server_2.SSEServer();
|
|
31
|
+
this.sse.registerControllers(controllers);
|
|
32
|
+
service_2.SSEService.getInstance().initialize(this.sse);
|
|
25
33
|
}
|
|
26
34
|
this.app = app;
|
|
27
35
|
this.logConfig();
|
|
@@ -190,17 +198,17 @@ class HttpServer extends Socket_1.Socket {
|
|
|
190
198
|
}
|
|
191
199
|
async sendResponse(res, data, startTime) {
|
|
192
200
|
const response = data?.data !== undefined ? data.data : data;
|
|
193
|
-
if (
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
}
|
|
203
|
-
}
|
|
201
|
+
if (res.headersSent)
|
|
202
|
+
return;
|
|
203
|
+
if (!res.getHeader('Content-Type')) {
|
|
204
|
+
res.setHeader('Content-Type', 'application/json');
|
|
205
|
+
}
|
|
206
|
+
if (data?.headers) {
|
|
207
|
+
Object.entries(data.headers).forEach(([key, value]) => {
|
|
208
|
+
if (!res.getHeader(key)) {
|
|
209
|
+
res.setHeader(key, value);
|
|
210
|
+
}
|
|
211
|
+
});
|
|
204
212
|
}
|
|
205
213
|
res.setHeader('X-Response-Time', `${Date.now() - startTime}ms`);
|
|
206
214
|
res.statusCode = data.status ?? 200;
|
package/dist/app/http/index.d.ts
CHANGED
package/dist/app/http/index.js
CHANGED
|
@@ -16,4 +16,3 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./Application"), exports);
|
|
18
18
|
__exportStar(require("./decorators"), exports);
|
|
19
|
-
__exportStar(require("./websocket/WebsocketServer"), exports);
|
package/dist/constants.d.ts
CHANGED
|
@@ -21,3 +21,6 @@ export declare const TO_VALIDATE: string[];
|
|
|
21
21
|
export declare const STATISTIC: Record<'controllers' | 'routes', number>;
|
|
22
22
|
export declare const INCREMENT_STATISTIC: (prop: "controllers" | "routes") => void;
|
|
23
23
|
export declare const CORS_METADATA = "cors:config";
|
|
24
|
+
export declare const SSE_METADATA_KEY = "sse:handlers";
|
|
25
|
+
export declare const SSE_TOPIC_KEY = "sse:topics";
|
|
26
|
+
export declare const SSE_SERVICE_KEY = "sse:service";
|
package/dist/constants.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.CORS_METADATA = exports.INCREMENT_STATISTIC = exports.STATISTIC = exports.TO_VALIDATE = exports.OK_STATUSES = exports.STOPPED = exports.SANITIZE = exports.CATCH = exports.INTECEPT = exports.WS_SERVICE_KEY = exports.WS_TOPIC_KEY = exports.WS_HANDLER = exports.USE_MIDDLEWARE = exports.SERVER_CONFIG_KEY = exports.OK_METADATA_KEY = exports.ENDPOINT = exports.INTERCEPTOR = exports.CONTROLLERS = exports.MIDDLEWARES = exports.ROUTE_MIDDLEWARES = exports.ROUTE_PREFIX = exports.APP_METADATA_KEY = exports.PARAM_METADATA_KEY = void 0;
|
|
3
|
+
exports.SSE_SERVICE_KEY = exports.SSE_TOPIC_KEY = exports.SSE_METADATA_KEY = exports.CORS_METADATA = exports.INCREMENT_STATISTIC = exports.STATISTIC = exports.TO_VALIDATE = exports.OK_STATUSES = exports.STOPPED = exports.SANITIZE = exports.CATCH = exports.INTECEPT = exports.WS_SERVICE_KEY = exports.WS_TOPIC_KEY = exports.WS_HANDLER = exports.USE_MIDDLEWARE = exports.SERVER_CONFIG_KEY = exports.OK_METADATA_KEY = exports.ENDPOINT = exports.INTERCEPTOR = exports.CONTROLLERS = exports.MIDDLEWARES = exports.ROUTE_MIDDLEWARES = exports.ROUTE_PREFIX = exports.APP_METADATA_KEY = exports.PARAM_METADATA_KEY = void 0;
|
|
4
4
|
exports.PARAM_METADATA_KEY = 'design:parameters';
|
|
5
5
|
exports.APP_METADATA_KEY = 'app:configuration';
|
|
6
6
|
exports.ROUTE_PREFIX = 'route:prefix';
|
|
@@ -35,3 +35,6 @@ const INCREMENT_STATISTIC = (prop) => {
|
|
|
35
35
|
};
|
|
36
36
|
exports.INCREMENT_STATISTIC = INCREMENT_STATISTIC;
|
|
37
37
|
exports.CORS_METADATA = 'cors:config';
|
|
38
|
+
exports.SSE_METADATA_KEY = 'sse:handlers';
|
|
39
|
+
exports.SSE_TOPIC_KEY = 'sse:topics';
|
|
40
|
+
exports.SSE_SERVICE_KEY = 'sse:service';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AppRequest, ControllerClass, ControllerConfig, InterceptorCB, RouteContext } from '../types/index.js';
|
|
1
|
+
import { AppRequest, ControllerClass, ControllerConfig, InterceptorCB, RouteContext, SeeControllerHandlers, WsControllerHandlers } from '../types/index.js';
|
|
2
2
|
import { ServerResponse } from 'http';
|
|
3
3
|
import 'reflect-metadata';
|
|
4
4
|
/**
|
|
@@ -25,21 +25,25 @@ import 'reflect-metadata';
|
|
|
25
25
|
export declare function Controller(config: string | ControllerConfig, middlewares?: Array<InterceptorCB>): <T extends ControllerClass>(constructor: T) => {
|
|
26
26
|
new (...args: any[]): {
|
|
27
27
|
[x: string]: any;
|
|
28
|
+
ws?: WsControllerHandlers;
|
|
29
|
+
sse?: SeeControllerHandlers;
|
|
28
30
|
executeControllerMethod: (controller: import("../types/index.js").ControllerInstance, propertyName: string, request: AppRequest, response: ServerResponse) => Promise<any>;
|
|
29
31
|
getControllerMethods: (controller: import("../types/index.js").ControllerInstance) => import("../types/index.js").ControllerMethods;
|
|
30
32
|
handleRequest: (request: AppRequest, response: ServerResponse) => Promise<any>;
|
|
31
33
|
routeWalker(context: RouteContext, request: AppRequest, response: ServerResponse): Promise<any>;
|
|
32
|
-
|
|
34
|
+
lookupWS(): void;
|
|
35
|
+
lookupSSE(): void;
|
|
36
|
+
getSSEController(): {
|
|
33
37
|
instance: /*elided*/ any;
|
|
34
38
|
handlers: {
|
|
35
|
-
connection: any;
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
error: any;
|
|
39
|
+
connection: any[];
|
|
40
|
+
close: any[];
|
|
41
|
+
error: any[];
|
|
39
42
|
};
|
|
40
|
-
topics: any;
|
|
41
43
|
};
|
|
42
|
-
getWSHandlers(type: string): any;
|
|
44
|
+
getWSHandlers(type: string): any[];
|
|
45
|
+
getSSEHandlers(type: string): any[];
|
|
43
46
|
getWSTopics(): any;
|
|
47
|
+
typedHandlers: (handlers: any[], type: string) => any[];
|
|
44
48
|
};
|
|
45
49
|
} & T;
|
package/dist/core/Controller.js
CHANGED
|
@@ -64,10 +64,14 @@ function Controller(config, middlewares = []) {
|
|
|
64
64
|
});
|
|
65
65
|
}
|
|
66
66
|
return class extends constructor {
|
|
67
|
+
ws;
|
|
68
|
+
sse;
|
|
67
69
|
executeControllerMethod = _utils_1.executeControllerMethod;
|
|
68
70
|
getControllerMethods = _utils_1.getControllerMethods;
|
|
69
71
|
constructor(...args) {
|
|
70
72
|
super(...args);
|
|
73
|
+
this.lookupWS();
|
|
74
|
+
this.lookupSSE();
|
|
71
75
|
}
|
|
72
76
|
handleRequest = async (request, response) => {
|
|
73
77
|
const middlewares = []
|
|
@@ -200,26 +204,43 @@ function Controller(config, middlewares = []) {
|
|
|
200
204
|
}
|
|
201
205
|
return null;
|
|
202
206
|
}
|
|
203
|
-
|
|
207
|
+
lookupWS() {
|
|
208
|
+
const connection = this.getWSHandlers('connection');
|
|
209
|
+
const message = this.getWSHandlers('message');
|
|
210
|
+
const error = this.getWSHandlers('error');
|
|
211
|
+
const close = this.getWSHandlers('close');
|
|
212
|
+
const topics = this.getWSHandlers('topics');
|
|
213
|
+
if ([...connection, ...message, ...error, ...close, ...topics].length === 0) {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
this.ws = { handlers: { connection, message, close, error }, topics };
|
|
217
|
+
}
|
|
218
|
+
lookupSSE() {
|
|
219
|
+
const connection = this.getSSEHandlers('connection');
|
|
220
|
+
const error = this.getSSEHandlers('error');
|
|
221
|
+
const close = this.getSSEHandlers('close');
|
|
222
|
+
if ([...connection, ...error, ...close].length === 0) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
this.sse = { handlers: { connection, close, error } };
|
|
226
|
+
}
|
|
227
|
+
getSSEController() {
|
|
204
228
|
return {
|
|
205
229
|
instance: this,
|
|
206
230
|
handlers: {
|
|
207
|
-
connection: this.
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
error: this.getWSHandlers('error'),
|
|
231
|
+
connection: this.getSSEHandlers('connection'),
|
|
232
|
+
close: this.getSSEHandlers('close'),
|
|
233
|
+
error: this.getSSEHandlers('error'),
|
|
211
234
|
},
|
|
212
|
-
topics: this.getWSTopics(),
|
|
213
235
|
};
|
|
214
236
|
}
|
|
215
237
|
getWSHandlers(type) {
|
|
216
238
|
const handlers = Reflect.getMetadata(_constants_1.WS_HANDLER, this.constructor) || [];
|
|
217
|
-
return handlers
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
}));
|
|
239
|
+
return this.typedHandlers(handlers, type);
|
|
240
|
+
}
|
|
241
|
+
getSSEHandlers(type) {
|
|
242
|
+
const handlers = Reflect.getMetadata(_constants_1.SSE_METADATA_KEY, this.constructor) || [];
|
|
243
|
+
return this.typedHandlers(handlers, type);
|
|
223
244
|
}
|
|
224
245
|
getWSTopics() {
|
|
225
246
|
const topics = Reflect.getMetadata(_constants_1.WS_TOPIC_KEY, this.constructor) || [];
|
|
@@ -228,6 +249,14 @@ function Controller(config, middlewares = []) {
|
|
|
228
249
|
fn: this[t.method].bind(this),
|
|
229
250
|
}));
|
|
230
251
|
}
|
|
252
|
+
typedHandlers = (handlers, type) => {
|
|
253
|
+
return handlers
|
|
254
|
+
.filter((h) => h.type === type)
|
|
255
|
+
.map((h) => ({
|
|
256
|
+
...h,
|
|
257
|
+
fn: this[h.method].bind(this),
|
|
258
|
+
}));
|
|
259
|
+
};
|
|
231
260
|
};
|
|
232
261
|
};
|
|
233
262
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type SSEHandlerType = 'connection' | 'close' | 'error';
|
|
2
|
+
export declare function OnSSE(type: SSEHandlerType): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
|
|
3
|
+
export declare function OnSSEConnection(): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
|
|
4
|
+
export declare function OnSSEClose(): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
|
|
5
|
+
export declare function OnSSEError(): (target: any, propertyKey: string, descriptor: PropertyDescriptor) => PropertyDescriptor;
|
|
6
|
+
export declare function InjectSSE(): (target: any, propertyKey: string | symbol, parameterIndex: number) => void;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OnSSE = OnSSE;
|
|
4
|
+
exports.OnSSEConnection = OnSSEConnection;
|
|
5
|
+
exports.OnSSEClose = OnSSEClose;
|
|
6
|
+
exports.OnSSEError = OnSSEError;
|
|
7
|
+
exports.InjectSSE = InjectSSE;
|
|
8
|
+
const _constants_1 = require("../constants.js");
|
|
9
|
+
const _utils_1 = require("../utils/index.js");
|
|
10
|
+
function OnSSE(type) {
|
|
11
|
+
return function (target, propertyKey, descriptor) {
|
|
12
|
+
const handlers = Reflect.getMetadata(_constants_1.SSE_METADATA_KEY, target.constructor) || [];
|
|
13
|
+
handlers.push({ type, method: propertyKey });
|
|
14
|
+
Reflect.defineMetadata(_constants_1.SSE_METADATA_KEY, handlers, target.constructor);
|
|
15
|
+
return descriptor;
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function OnSSEConnection() {
|
|
19
|
+
return OnSSE('connection');
|
|
20
|
+
}
|
|
21
|
+
function OnSSEClose() {
|
|
22
|
+
return OnSSE('close');
|
|
23
|
+
}
|
|
24
|
+
function OnSSEError() {
|
|
25
|
+
return OnSSE('error');
|
|
26
|
+
}
|
|
27
|
+
function InjectSSE() {
|
|
28
|
+
return (0, _utils_1.createParamDecorator)('sse');
|
|
29
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OnSSEError = exports.OnSSEConnection = exports.OnSSEClose = exports.InjectSSE = void 0;
|
|
4
|
+
var decorators_1 = require("./decorators");
|
|
5
|
+
Object.defineProperty(exports, "InjectSSE", { enumerable: true, get: function () { return decorators_1.InjectSSE; } });
|
|
6
|
+
Object.defineProperty(exports, "OnSSEClose", { enumerable: true, get: function () { return decorators_1.OnSSEClose; } });
|
|
7
|
+
Object.defineProperty(exports, "OnSSEConnection", { enumerable: true, get: function () { return decorators_1.OnSSEConnection; } });
|
|
8
|
+
Object.defineProperty(exports, "OnSSEError", { enumerable: true, get: function () { return decorators_1.OnSSEError; } });
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { ControllerType, ISSEServer, SSEClient, SSEMessage } from '../types/index.js';
|
|
2
|
+
import { ServerResponse } from 'http';
|
|
3
|
+
export declare class SSEServer implements ISSEServer {
|
|
4
|
+
private clients;
|
|
5
|
+
controllers: any[];
|
|
6
|
+
createConnection(res: ServerResponse): SSEClient;
|
|
7
|
+
sendToClient(clientId: string, message: SSEMessage): boolean;
|
|
8
|
+
broadcast(message: SSEMessage, excludeClientId?: string): void;
|
|
9
|
+
getStats(): {
|
|
10
|
+
clients: number;
|
|
11
|
+
};
|
|
12
|
+
registerControllers(controllers: ControllerType[]): void;
|
|
13
|
+
private triggerHandlers;
|
|
14
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SSEServer = void 0;
|
|
4
|
+
const uuid_1 = require("uuid");
|
|
5
|
+
class SSEServer {
|
|
6
|
+
clients = new Map();
|
|
7
|
+
controllers;
|
|
8
|
+
createConnection(res) {
|
|
9
|
+
const clientId = (0, uuid_1.v4)();
|
|
10
|
+
res.writeHead(200, {
|
|
11
|
+
'Content-Type': 'text/event-stream',
|
|
12
|
+
'Cache-Control': 'no-cache',
|
|
13
|
+
Connection: 'keep-alive',
|
|
14
|
+
'Access-Control-Allow-Origin': '*',
|
|
15
|
+
});
|
|
16
|
+
res.write(': connected\n\n');
|
|
17
|
+
const client = {
|
|
18
|
+
id: clientId,
|
|
19
|
+
response: res,
|
|
20
|
+
topics: new Set(),
|
|
21
|
+
data: {},
|
|
22
|
+
connectedAt: new Date(),
|
|
23
|
+
};
|
|
24
|
+
this.clients.set(clientId, client);
|
|
25
|
+
this.triggerHandlers('connection', {
|
|
26
|
+
type: 'connection',
|
|
27
|
+
client,
|
|
28
|
+
data: res,
|
|
29
|
+
});
|
|
30
|
+
res.on('close', () => {
|
|
31
|
+
this.clients.delete(clientId);
|
|
32
|
+
this.triggerHandlers('close', { type: 'close', client });
|
|
33
|
+
});
|
|
34
|
+
return client;
|
|
35
|
+
}
|
|
36
|
+
sendToClient(clientId, message) {
|
|
37
|
+
const client = this.clients.get(clientId);
|
|
38
|
+
if (!client)
|
|
39
|
+
return false;
|
|
40
|
+
try {
|
|
41
|
+
let sseMessage = '';
|
|
42
|
+
if (message.event)
|
|
43
|
+
sseMessage += `event: ${message.event}\n`;
|
|
44
|
+
if (message.id)
|
|
45
|
+
sseMessage += `id: ${message.id}\n`;
|
|
46
|
+
if (message.retry)
|
|
47
|
+
sseMessage += `retry: ${message.retry}\n`;
|
|
48
|
+
const dataStr = typeof message.data === 'string' ? message.data : JSON.stringify(message.data);
|
|
49
|
+
dataStr.split('\n').forEach((line) => {
|
|
50
|
+
sseMessage += `data: ${line}\n`;
|
|
51
|
+
});
|
|
52
|
+
sseMessage += '\n';
|
|
53
|
+
client.response.write(sseMessage);
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
console.error(`SSE send error to ${clientId}:`, error);
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
broadcast(message, excludeClientId) {
|
|
62
|
+
this.clients.forEach((_, clientId) => {
|
|
63
|
+
if (clientId !== excludeClientId) {
|
|
64
|
+
this.sendToClient(clientId, message);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
getStats() {
|
|
69
|
+
return { clients: this.clients.size };
|
|
70
|
+
}
|
|
71
|
+
registerControllers(controllers) {
|
|
72
|
+
this.controllers = controllers.filter((c) => c.sse);
|
|
73
|
+
}
|
|
74
|
+
async triggerHandlers(eventType, event) {
|
|
75
|
+
for (const controller of this.controllers) {
|
|
76
|
+
if (controller.sse.handlers && controller.sse.handlers[eventType]) {
|
|
77
|
+
for (const handler of controller.sse.handlers[eventType]) {
|
|
78
|
+
console.log(handler);
|
|
79
|
+
await handler.fn(event).catch(console.log);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
exports.SSEServer = SSEServer;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ISSEService, SSEClient, SSEMessage } from '../types/index.js';
|
|
2
|
+
import { ServerResponse } from 'http';
|
|
3
|
+
import { SSEServer } from './server';
|
|
4
|
+
export declare class SSEService implements ISSEService {
|
|
5
|
+
private static instance;
|
|
6
|
+
private sse;
|
|
7
|
+
private constructor();
|
|
8
|
+
static getInstance(): SSEService;
|
|
9
|
+
initialize(sse: SSEServer): void;
|
|
10
|
+
sendToClient(clientId: string, message: SSEMessage): boolean;
|
|
11
|
+
broadcast(message: SSEMessage, excludeClientId?: string): void;
|
|
12
|
+
getStats(): {
|
|
13
|
+
clients: number;
|
|
14
|
+
};
|
|
15
|
+
isAvailable(): boolean;
|
|
16
|
+
createConnection(res: ServerResponse): SSEClient;
|
|
17
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SSEService = void 0;
|
|
4
|
+
class SSEService {
|
|
5
|
+
static instance;
|
|
6
|
+
sse = null;
|
|
7
|
+
constructor() { }
|
|
8
|
+
static getInstance() {
|
|
9
|
+
if (!SSEService.instance) {
|
|
10
|
+
SSEService.instance = new SSEService();
|
|
11
|
+
}
|
|
12
|
+
return SSEService.instance;
|
|
13
|
+
}
|
|
14
|
+
initialize(sse) {
|
|
15
|
+
this.sse = sse;
|
|
16
|
+
}
|
|
17
|
+
sendToClient(clientId, message) {
|
|
18
|
+
if (!this.sse)
|
|
19
|
+
return false;
|
|
20
|
+
return this.sse.sendToClient(clientId, message);
|
|
21
|
+
}
|
|
22
|
+
broadcast(message, excludeClientId) {
|
|
23
|
+
if (!this.sse)
|
|
24
|
+
return;
|
|
25
|
+
this.sse.broadcast(message, excludeClientId);
|
|
26
|
+
}
|
|
27
|
+
getStats() {
|
|
28
|
+
if (!this.sse)
|
|
29
|
+
return { clients: 0 };
|
|
30
|
+
return this.sse.getStats();
|
|
31
|
+
}
|
|
32
|
+
isAvailable() {
|
|
33
|
+
return this.sse !== null;
|
|
34
|
+
}
|
|
35
|
+
createConnection(res) {
|
|
36
|
+
return this.sse.createConnection(res);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
exports.SSEService = SSEService;
|
package/dist/types/common.d.ts
CHANGED
|
@@ -41,7 +41,7 @@ export interface IController {
|
|
|
41
41
|
export type MiddlewareCB = (request: AppRequest, response: ServerResponse, next: (args?: any) => any) => void | Promise<AppRequest> | AppRequest;
|
|
42
42
|
export type InterceptorCB = (data: any, req?: AppRequest, res?: ServerResponse) => Promise<unknown> | unknown;
|
|
43
43
|
export type ErrorCB = (error: HttpError, req?: AppRequest, res?: ServerResponse) => Promise<ResponseWithStatus> | ResponseWithStatus;
|
|
44
|
-
export type ParamDecoratorType = 'body' | 'params' | 'query' | 'request' | 'headers' | 'cookies' | 'response' | 'multipart' | 'event' | 'context';
|
|
44
|
+
export type ParamDecoratorType = 'body' | 'params' | 'query' | 'request' | 'headers' | 'cookies' | 'response' | 'multipart' | 'event' | 'context' | 'sse' | 'ws';
|
|
45
45
|
export interface ParamMetadata {
|
|
46
46
|
index: number;
|
|
47
47
|
type: ParamDecoratorType;
|
|
@@ -1,21 +1,28 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ServerResponse } from 'http';
|
|
2
|
+
import { AppRequest, ErrorCB, HTTP_METHODS, InterceptorCB, MiddlewareCB } from './common';
|
|
2
3
|
import { CORSConfig } from './cors';
|
|
3
4
|
import { SanitizerConfig } from './sanitize';
|
|
4
5
|
export type ControllerClass = {
|
|
5
6
|
new (...args: any[]): any;
|
|
6
7
|
};
|
|
7
|
-
export type ControllerInstance = InstanceType<ControllerClass>;
|
|
8
8
|
export type ControllerMethods = Array<{
|
|
9
9
|
name: string;
|
|
10
10
|
httpMethod: HTTP_METHODS;
|
|
11
11
|
pattern: string;
|
|
12
12
|
middlewares?: MiddlewareCB[];
|
|
13
13
|
}>;
|
|
14
|
+
export type ControllerType = {
|
|
15
|
+
handleRequest?(request: AppRequest, response: ServerResponse): Promise<any>;
|
|
16
|
+
ws?: WsControllerHandlers;
|
|
17
|
+
sse?: SeeControllerHandlers;
|
|
18
|
+
new (...args: any[]): any;
|
|
19
|
+
};
|
|
20
|
+
export type ControllerInstance = InstanceType<ControllerType>;
|
|
14
21
|
export type ControllerMetadata = {
|
|
15
22
|
routePrefix: string;
|
|
16
23
|
middlewares: MiddlewareCB[];
|
|
17
24
|
interceptor?: InterceptorCB;
|
|
18
|
-
subControllers:
|
|
25
|
+
subControllers: ControllerInstance[];
|
|
19
26
|
errorHandler?: ErrorCB;
|
|
20
27
|
cors?: CORSConfig;
|
|
21
28
|
sanitizers: SanitizerConfig[];
|
|
@@ -23,7 +30,7 @@ export type ControllerMetadata = {
|
|
|
23
30
|
export interface ControllerConfig {
|
|
24
31
|
prefix: string;
|
|
25
32
|
middlewares?: Array<MiddlewareCB>;
|
|
26
|
-
controllers?:
|
|
33
|
+
controllers?: ControllerInstance[];
|
|
27
34
|
interceptor?: InterceptorCB;
|
|
28
35
|
}
|
|
29
36
|
export type RouteContext = {
|
|
@@ -38,3 +45,32 @@ export type RouteContext = {
|
|
|
38
45
|
subPath: string;
|
|
39
46
|
sanitizersChain: SanitizerConfig[];
|
|
40
47
|
};
|
|
48
|
+
export type SSE_HANDLER_META = {
|
|
49
|
+
type: string;
|
|
50
|
+
method: string;
|
|
51
|
+
};
|
|
52
|
+
export type HandlerMeta = {
|
|
53
|
+
type: 'connection';
|
|
54
|
+
topic: undefined;
|
|
55
|
+
method: 'onconnect';
|
|
56
|
+
fn: (...args: any[]) => any;
|
|
57
|
+
};
|
|
58
|
+
export type WsHandlerMeta = HandlerMeta & {
|
|
59
|
+
topic?: string;
|
|
60
|
+
};
|
|
61
|
+
export type WsControllerHandlers = {
|
|
62
|
+
handlers: {
|
|
63
|
+
connection: WsHandlerMeta[];
|
|
64
|
+
message: WsHandlerMeta[];
|
|
65
|
+
close: WsHandlerMeta[];
|
|
66
|
+
error: WsHandlerMeta[];
|
|
67
|
+
};
|
|
68
|
+
topics: HandlerMeta[];
|
|
69
|
+
};
|
|
70
|
+
export type SeeControllerHandlers = {
|
|
71
|
+
handlers: {
|
|
72
|
+
connection: HandlerMeta[];
|
|
73
|
+
close: HandlerMeta[];
|
|
74
|
+
error: HandlerMeta[];
|
|
75
|
+
};
|
|
76
|
+
};
|
package/dist/types/http.d.ts
CHANGED
package/dist/types/index.d.ts
CHANGED
package/dist/types/index.js
CHANGED
|
@@ -21,4 +21,5 @@ __exportStar(require("./http"), exports);
|
|
|
21
21
|
__exportStar(require("./lambda"), exports);
|
|
22
22
|
__exportStar(require("./multipart"), exports);
|
|
23
23
|
__exportStar(require("./sanitize"), exports);
|
|
24
|
+
__exportStar(require("./sse"), exports);
|
|
24
25
|
__exportStar(require("./websocket"), exports);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { ServerResponse } from 'http';
|
|
2
|
+
export interface SSEClient {
|
|
3
|
+
id: string;
|
|
4
|
+
response: ServerResponse;
|
|
5
|
+
topics: Set<string>;
|
|
6
|
+
data: Record<string, any>;
|
|
7
|
+
connectedAt: Date;
|
|
8
|
+
}
|
|
9
|
+
export interface SSEMessage {
|
|
10
|
+
event?: string;
|
|
11
|
+
id?: string;
|
|
12
|
+
retry?: number;
|
|
13
|
+
data: any;
|
|
14
|
+
}
|
|
15
|
+
export interface SSEEvent {
|
|
16
|
+
type: 'connection' | 'close';
|
|
17
|
+
client: SSEClient;
|
|
18
|
+
data?: any;
|
|
19
|
+
}
|
|
20
|
+
export interface ISSEServer {
|
|
21
|
+
createConnection(res: ServerResponse): SSEClient;
|
|
22
|
+
sendToClient(clientId: string, message: SSEMessage): boolean;
|
|
23
|
+
broadcast(message: SSEMessage, excludeClientId?: string): void;
|
|
24
|
+
getStats(): {
|
|
25
|
+
clients: number;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export interface ISSEService {
|
|
29
|
+
initialize(sse: ISSEServer): void;
|
|
30
|
+
sendToClient(clientId: string, message: SSEMessage): boolean;
|
|
31
|
+
broadcast(message: SSEMessage, excludeClientId?: string): void;
|
|
32
|
+
getStats(): {
|
|
33
|
+
clients: number;
|
|
34
|
+
};
|
|
35
|
+
isAvailable(): boolean;
|
|
36
|
+
createConnection(res: ServerResponse): SSEClient;
|
|
37
|
+
}
|
|
@@ -21,7 +21,7 @@ export declare const getResponse: (data: {
|
|
|
21
21
|
}) => Promise<{
|
|
22
22
|
status: number;
|
|
23
23
|
data: any;
|
|
24
|
-
}>;
|
|
24
|
+
} | undefined>;
|
|
25
25
|
export declare const applyMiddlewaresVsSanitizers: (request: AppRequest, response: ServerResponse, functions: {
|
|
26
26
|
sanitizers: SanitizerConfig[][];
|
|
27
27
|
middlewares: MiddlewareCB[][];
|
package/dist/utils/controller.js
CHANGED
|
@@ -3,7 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.applyMiddlewaresVsSanitizers = exports.getResponse = exports.NextFN = exports.findRouteInController = exports.getAllMethods = exports.getControllerMethods = exports.executeControllerMethod = void 0;
|
|
4
4
|
const _constants_1 = require("../constants.js");
|
|
5
5
|
const _types_1 = require("../types/index.js");
|
|
6
|
-
const
|
|
6
|
+
const service_1 = require("../sse/service");
|
|
7
|
+
const service_2 = require("../ws/service");
|
|
7
8
|
const helper_1 = require("./helper");
|
|
8
9
|
const multipart_1 = require("./multipart");
|
|
9
10
|
const sanitize_1 = require("./sanitize");
|
|
@@ -41,19 +42,14 @@ const executeControllerMethod = async (controller, propertyName, request, respon
|
|
|
41
42
|
}
|
|
42
43
|
const prototype = Object.getPrototypeOf(controller);
|
|
43
44
|
const paramMetadata = Reflect.getMetadata(_constants_1.PARAM_METADATA_KEY, prototype, propertyName) || [];
|
|
45
|
+
const sse = Reflect.getMetadata(_constants_1.SSE_METADATA_KEY, prototype, propertyName) || [];
|
|
44
46
|
if (paramMetadata.length === 0) {
|
|
45
47
|
return fn.call(controller, request, response);
|
|
46
48
|
}
|
|
47
49
|
const { body, multipart } = getBodyAndMultipart(request);
|
|
48
50
|
const args = [];
|
|
49
|
-
const
|
|
50
|
-
const totalParams = Math.max(paramMetadata.length ? Math.max(...paramMetadata.map((p) => p.index)) + 1 : 0, wsParams.length ? Math.max(...wsParams.map((p) => p.index)) + 1 : 0);
|
|
51
|
+
const totalParams = Math.max(paramMetadata.length ? Math.max(...paramMetadata.map((p) => p.index)) + 1 : 0);
|
|
51
52
|
for (let i = 0; i < totalParams; i++) {
|
|
52
|
-
const wsParam = wsParams.find((p) => p.index === i);
|
|
53
|
-
if (wsParam) {
|
|
54
|
-
args[i] = WebsocketService_1.WebSocketService.getInstance();
|
|
55
|
-
continue;
|
|
56
|
-
}
|
|
57
53
|
const param = paramMetadata.find((p) => p.index === i);
|
|
58
54
|
if (!param) {
|
|
59
55
|
args[i] = undefined;
|
|
@@ -65,6 +61,12 @@ const executeControllerMethod = async (controller, propertyName, request, respon
|
|
|
65
61
|
if (param.type === 'multipart') {
|
|
66
62
|
value = multipart;
|
|
67
63
|
}
|
|
64
|
+
if (param.type === 'ws') {
|
|
65
|
+
value = service_2.WebSocketService.getInstance();
|
|
66
|
+
}
|
|
67
|
+
if (param.type === 'sse') {
|
|
68
|
+
value = service_1.SSEService.getInstance();
|
|
69
|
+
}
|
|
68
70
|
if (param.type === 'request') {
|
|
69
71
|
value = request;
|
|
70
72
|
}
|
|
@@ -173,6 +175,9 @@ exports.NextFN = NextFN;
|
|
|
173
175
|
const getResponse = async (data) => {
|
|
174
176
|
try {
|
|
175
177
|
let appResponse = await (0, exports.executeControllerMethod)(data.controllerInstance, data.name, data.request, data.response);
|
|
178
|
+
if (!appResponse) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
176
181
|
data.response.statusCode = appResponse.status ?? 200;
|
|
177
182
|
const isError = !_constants_1.OK_STATUSES.includes(data.response.statusCode);
|
|
178
183
|
const interceptors = data.interceptors.reverse();
|
package/dist/ws/decorators.d.ts
CHANGED
|
@@ -30,4 +30,4 @@ export declare function Subscribe(topic: string): (target: any, propertyKey: str
|
|
|
30
30
|
/**
|
|
31
31
|
* Parameter decorator to inject WebSocket service instance.
|
|
32
32
|
*/
|
|
33
|
-
export declare function InjectWS(): (target: any, propertyKey: string, parameterIndex: number) => void;
|
|
33
|
+
export declare function InjectWS(): (target: any, propertyKey: string | symbol, parameterIndex: number) => void;
|
package/dist/ws/decorators.js
CHANGED
|
@@ -8,6 +8,7 @@ exports.OnError = OnError;
|
|
|
8
8
|
exports.Subscribe = Subscribe;
|
|
9
9
|
exports.InjectWS = InjectWS;
|
|
10
10
|
const _constants_1 = require("../constants.js");
|
|
11
|
+
const _utils_1 = require("../utils/index.js");
|
|
11
12
|
/**
|
|
12
13
|
* Method decorator to handle WebSocket events.
|
|
13
14
|
* @param type Type of WebSocket event (connection, message, close, error).
|
|
@@ -62,9 +63,5 @@ function Subscribe(topic) {
|
|
|
62
63
|
* Parameter decorator to inject WebSocket service instance.
|
|
63
64
|
*/
|
|
64
65
|
function InjectWS() {
|
|
65
|
-
return
|
|
66
|
-
const existingParams = Reflect.getMetadata(_constants_1.WS_SERVICE_KEY, target, propertyKey) || [];
|
|
67
|
-
existingParams.push({ index: parameterIndex });
|
|
68
|
-
Reflect.defineMetadata(_constants_1.WS_SERVICE_KEY, existingParams, target, propertyKey);
|
|
69
|
-
};
|
|
66
|
+
return (0, _utils_1.createParamDecorator)('ws');
|
|
70
67
|
}
|
package/dist/ws/index.d.ts
CHANGED
package/dist/ws/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { WebSocketClient } from '
|
|
1
|
+
import { ControllerType, IWebSocketServer, WebSocketClient } from '../types/index.js';
|
|
2
2
|
import http from 'http';
|
|
3
|
-
export declare class WebSocketServer {
|
|
3
|
+
export declare class WebSocketServer implements IWebSocketServer {
|
|
4
4
|
private wss;
|
|
5
5
|
private clients;
|
|
6
6
|
private topics;
|
|
@@ -15,7 +15,7 @@ export declare class WebSocketServer {
|
|
|
15
15
|
private handleClose;
|
|
16
16
|
private handleError;
|
|
17
17
|
private triggerHandlers;
|
|
18
|
-
registerControllers(controllers:
|
|
18
|
+
registerControllers(controllers: ControllerType[]): void;
|
|
19
19
|
subscribeToTopic(client: WebSocketClient, topic: string): void;
|
|
20
20
|
unsubscribeFromTopic(client: WebSocketClient, topic: string): void;
|
|
21
21
|
publishToTopic(topic: string, data: any, exclude?: string[]): void;
|
|
@@ -89,6 +89,7 @@ class WebSocketServer {
|
|
|
89
89
|
});
|
|
90
90
|
}
|
|
91
91
|
catch (error) {
|
|
92
|
+
console.log(error);
|
|
92
93
|
client.socket.send(JSON.stringify({
|
|
93
94
|
type: 'error',
|
|
94
95
|
data: { message: 'Invalid message format' },
|
|
@@ -120,7 +121,7 @@ class WebSocketServer {
|
|
|
120
121
|
}
|
|
121
122
|
async triggerHandlers(eventType, event, topic) {
|
|
122
123
|
for (const controller of this.controllers) {
|
|
123
|
-
const controllerHandlers = controller.handlers?.[eventType] ?? [];
|
|
124
|
+
const controllerHandlers = controller.ws?.handlers?.[eventType] ?? [];
|
|
124
125
|
const matchingHandlers = controllerHandlers.filter((h) => {
|
|
125
126
|
if (h.type !== eventType)
|
|
126
127
|
return false;
|
|
@@ -137,7 +138,7 @@ class WebSocketServer {
|
|
|
137
138
|
}
|
|
138
139
|
}
|
|
139
140
|
if (eventType === 'message' && topic) {
|
|
140
|
-
const matchingSubs = controller.topics.filter((s) => s.topic === topic);
|
|
141
|
+
const matchingSubs = controller.ws?.topics.filter((s) => s.topic === topic) ?? [];
|
|
141
142
|
for (const sub of matchingSubs) {
|
|
142
143
|
try {
|
|
143
144
|
await sub.fn(event);
|
|
@@ -150,12 +151,7 @@ class WebSocketServer {
|
|
|
150
151
|
}
|
|
151
152
|
}
|
|
152
153
|
registerControllers(controllers) {
|
|
153
|
-
this.controllers = controllers.
|
|
154
|
-
if (controller.getWebSocketController) {
|
|
155
|
-
return controller.getWebSocketController();
|
|
156
|
-
}
|
|
157
|
-
return controller;
|
|
158
|
-
});
|
|
154
|
+
this.controllers = controllers.filter((c) => !!c.ws);
|
|
159
155
|
}
|
|
160
156
|
subscribeToTopic(client, topic) {
|
|
161
157
|
if (!this.topics.has(topic)) {
|
|
@@ -163,11 +159,7 @@ class WebSocketServer {
|
|
|
163
159
|
}
|
|
164
160
|
this.topics.get(topic).add(client.id);
|
|
165
161
|
client.topics.add(topic);
|
|
166
|
-
client.socket.send(JSON.stringify({
|
|
167
|
-
type: 'subscribed',
|
|
168
|
-
topic,
|
|
169
|
-
data: { success: true },
|
|
170
|
-
}));
|
|
162
|
+
client.socket.send(JSON.stringify({ type: 'subscribed', topic, data: { success: true } }));
|
|
171
163
|
}
|
|
172
164
|
unsubscribeFromTopic(client, topic) {
|
|
173
165
|
const topicClients = this.topics.get(topic);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { IWebSocketService } from '
|
|
2
|
-
import { WebSocketServer } from './
|
|
1
|
+
import { IWebSocketService } from '../types/index.js';
|
|
2
|
+
import { WebSocketServer } from './server';
|
|
3
3
|
export declare class WebSocketService implements IWebSocketService {
|
|
4
4
|
private static instance;
|
|
5
5
|
private wss;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.Socket = void 0;
|
|
4
|
-
const
|
|
4
|
+
const service_1 = require("./service");
|
|
5
5
|
class Socket {
|
|
6
6
|
wss;
|
|
7
7
|
registerWebSocketControllers(controllers) {
|
|
@@ -14,19 +14,19 @@ class Socket {
|
|
|
14
14
|
return this;
|
|
15
15
|
}
|
|
16
16
|
sendToClient(clientId, message) {
|
|
17
|
-
return
|
|
17
|
+
return service_1.WebSocketService.getInstance().sendToClient(clientId, message);
|
|
18
18
|
}
|
|
19
19
|
publishToTopic(topic, data) {
|
|
20
|
-
|
|
20
|
+
service_1.WebSocketService.getInstance().publishToTopic(topic, data);
|
|
21
21
|
}
|
|
22
22
|
broadcast(message, excludeClientId) {
|
|
23
|
-
|
|
23
|
+
service_1.WebSocketService.getInstance().broadcast(message, excludeClientId);
|
|
24
24
|
}
|
|
25
25
|
getWebSocketStats() {
|
|
26
|
-
return
|
|
26
|
+
return service_1.WebSocketService.getInstance().getStats();
|
|
27
27
|
}
|
|
28
28
|
isWebSocketAvailable() {
|
|
29
|
-
return
|
|
29
|
+
return service_1.WebSocketService.getInstance().isAvailable();
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
exports.Socket = Socket;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "quantum-flow",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.10.0",
|
|
4
4
|
"description": "Decorator-based API framework",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -40,6 +40,11 @@
|
|
|
40
40
|
"import": "./dist/ws/index.js",
|
|
41
41
|
"require": "./dist/ws/index.js",
|
|
42
42
|
"types": "./dist/ws/index.d.ts"
|
|
43
|
+
},
|
|
44
|
+
"./sse": {
|
|
45
|
+
"import": "./dist/sse/index.js",
|
|
46
|
+
"require": "./dist/sse/index.js",
|
|
47
|
+
"types": "./dist/sse/index.d.ts"
|
|
43
48
|
}
|
|
44
49
|
},
|
|
45
50
|
"repository": {
|
|
File without changes
|