@martel/calyx 1.4.0 → 1.6.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.
Files changed (38) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/package.json +1 -1
  3. package/src/config/config.module.ts +61 -0
  4. package/src/config/config.service.ts +24 -0
  5. package/src/config/index.ts +2 -0
  6. package/src/event-emitter/decorators.ts +10 -0
  7. package/src/event-emitter/event-emitter.module.ts +17 -0
  8. package/src/event-emitter/event-emitter.ts +61 -0
  9. package/src/event-emitter/index.ts +3 -0
  10. package/src/http/application.ts +336 -3
  11. package/src/http/factory.ts +7 -0
  12. package/src/index.ts +6 -0
  13. package/src/lifecycle/context.ts +75 -0
  14. package/src/lifecycle/interfaces.ts +3 -0
  15. package/src/microservices/client-proxy.ts +7 -0
  16. package/src/microservices/client-tcp.ts +127 -0
  17. package/src/microservices/decorators.ts +19 -0
  18. package/src/microservices/index.ts +6 -0
  19. package/src/microservices/interfaces.ts +11 -0
  20. package/src/microservices/microservice.ts +114 -0
  21. package/src/microservices/server-tcp.ts +210 -0
  22. package/src/schedule/cron.matcher.ts +45 -0
  23. package/src/schedule/decorators.ts +28 -0
  24. package/src/schedule/index.ts +3 -0
  25. package/src/schedule/schedule.module.ts +13 -0
  26. package/src/security/cors.middleware.ts +50 -0
  27. package/src/security/hashing.service.ts +12 -0
  28. package/src/security/helmet.middleware.ts +45 -0
  29. package/src/security/index.ts +3 -0
  30. package/src/websockets/decorators.ts +49 -0
  31. package/src/websockets/gateway.ts +11 -0
  32. package/src/websockets/index.ts +2 -0
  33. package/tests/config-event.test.ts +145 -0
  34. package/tests/microservices.test.ts +105 -0
  35. package/tests/rpc-ws-context.test.ts +135 -0
  36. package/tests/schedule.test.ts +64 -0
  37. package/tests/security.test.ts +89 -0
  38. package/tests/websockets.test.ts +125 -0
package/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ # [1.6.0](https://github.com/bmartel/calyx/compare/v1.5.0...v1.6.0) (2026-07-01)
2
+
3
+
4
+ ### Features
5
+
6
+ * **websockets,microservices:** implement WebSocketGateways, TCP Microservices, and context switching for Guards/Interceptors ([a8cde0d](https://github.com/bmartel/calyx/commit/a8cde0d68411a3ab736fe1df00ae04672588b1af))
7
+
8
+ # [1.5.0](https://github.com/bmartel/calyx/compare/v1.4.0...v1.5.0) (2026-07-01)
9
+
10
+
11
+ ### Features
12
+
13
+ * **security,schedule:** implement ConfigModule, EventEmitterModule, HashingService, CORS, Helmet, and Task Scheduler ([6cfa8ca](https://github.com/bmartel/calyx/commit/6cfa8ca9459d73c7a7b1087953991d675d3a55db))
14
+
1
15
  # [1.4.0](https://github.com/bmartel/calyx/compare/v1.3.0...v1.4.0) (2026-07-01)
2
16
 
3
17
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@martel/calyx",
3
- "version": "1.4.0",
3
+ "version": "1.6.0",
4
4
  "description": "High-performance Bun-native NestJS-compatible framework",
5
5
  "main": "src/index.ts",
6
6
  "bin": {
@@ -0,0 +1,61 @@
1
+ import { existsSync, readFileSync } from 'fs';
2
+ import { Module, DynamicModule } from '../core/decorators.ts';
3
+ import { ConfigService } from './config.service.ts';
4
+
5
+ export interface ConfigModuleOptions {
6
+ isGlobal?: boolean;
7
+ load?: (() => Record<string, any>)[];
8
+ envFilePath?: string;
9
+ }
10
+
11
+ @Module({
12
+ providers: [ConfigService],
13
+ exports: [ConfigService],
14
+ })
15
+ export class ConfigModule {
16
+ static forRoot(options: ConfigModuleOptions = {}): DynamicModule {
17
+ const configData: Record<string, string> = { ...(process.env as Record<string, string>) };
18
+
19
+ if (options.envFilePath && existsSync(options.envFilePath)) {
20
+ try {
21
+ const text = readFileSync(options.envFilePath, 'utf-8');
22
+ if (text) {
23
+ const lines = text.split('\n');
24
+ for (const line of lines) {
25
+ const trimmed = line.trim();
26
+ if (!trimmed || trimmed.startsWith('#')) continue;
27
+ const eqIdx = trimmed.indexOf('=');
28
+ if (eqIdx !== -1) {
29
+ const key = trimmed.substring(0, eqIdx).trim();
30
+ const val = trimmed.substring(eqIdx + 1).trim();
31
+ configData[key] = val.replace(/^['"]|['"]$/g, '');
32
+ }
33
+ }
34
+ }
35
+ } catch (err) {
36
+ // ignore read error
37
+ }
38
+ }
39
+
40
+ if (options.load) {
41
+ for (const factory of options.load) {
42
+ const data = factory();
43
+ for (const [key, val] of Object.entries(data)) {
44
+ configData[key] = String(val);
45
+ }
46
+ }
47
+ }
48
+
49
+ const configServiceProvider = {
50
+ provide: ConfigService,
51
+ useValue: new ConfigService(configData),
52
+ };
53
+
54
+ return {
55
+ module: ConfigModule,
56
+ providers: [configServiceProvider],
57
+ exports: [ConfigService],
58
+ global: options.isGlobal ?? false,
59
+ };
60
+ }
61
+ }
@@ -0,0 +1,24 @@
1
+ import { Injectable } from '../core/decorators.ts';
2
+
3
+ @Injectable()
4
+ export class ConfigService {
5
+ private readonly env: Record<string, string> = {};
6
+
7
+ constructor(internalConfig?: Record<string, string>) {
8
+ this.env = internalConfig ?? (process.env as Record<string, string>);
9
+ }
10
+
11
+ get<T = string>(path: string, defaultValue?: T): T {
12
+ const val = this.env[path];
13
+ if (val === undefined) {
14
+ return defaultValue as T;
15
+ }
16
+ if (val === 'true') return true as unknown as T;
17
+ if (val === 'false') return false as unknown as T;
18
+ const num = Number(val);
19
+ if (!isNaN(num) && val.trim() !== '') {
20
+ return num as unknown as T;
21
+ }
22
+ return val as unknown as T;
23
+ }
24
+ }
@@ -0,0 +1,2 @@
1
+ export * from './config.service.ts';
2
+ export * from './config.module.ts';
@@ -0,0 +1,10 @@
1
+ import 'reflect-metadata';
2
+
3
+ export function OnEvent(event: string): MethodDecorator {
4
+ return (target, propertyKey) => {
5
+ const constructor = target.constructor;
6
+ const existing = Reflect.getOwnMetadata('calyx:on_event', constructor) || [];
7
+ existing.push({ event, propertyKey });
8
+ Reflect.defineMetadata('calyx:on_event', existing, constructor);
9
+ };
10
+ }
@@ -0,0 +1,17 @@
1
+ import { Module, DynamicModule } from '../core/decorators.ts';
2
+ import { EventEmitter } from './event-emitter.ts';
3
+
4
+ @Module({
5
+ providers: [EventEmitter],
6
+ exports: [EventEmitter],
7
+ })
8
+ export class EventEmitterModule {
9
+ static forRoot(): DynamicModule {
10
+ return {
11
+ module: EventEmitterModule,
12
+ providers: [EventEmitter],
13
+ exports: [EventEmitter],
14
+ global: true,
15
+ };
16
+ }
17
+ }
@@ -0,0 +1,61 @@
1
+ import { Injectable } from '../core/decorators.ts';
2
+
3
+ @Injectable()
4
+ export class EventEmitter {
5
+ private readonly listeners = new Map<string, Function[]>();
6
+ private readonly wildcardListeners: { pattern: RegExp; fn: Function }[] = [];
7
+
8
+ on(event: string, fn: Function) {
9
+ if (event.includes('*')) {
10
+ const regexStr = '^' + event.replace(/\./g, '\\.').replace(/\*/g, '.*') + '$';
11
+ this.wildcardListeners.push({ pattern: new RegExp(regexStr), fn });
12
+ } else {
13
+ let list = this.listeners.get(event);
14
+ if (!list) {
15
+ list = [];
16
+ this.listeners.set(event, list);
17
+ }
18
+ list.push(fn);
19
+ }
20
+ }
21
+
22
+ emit(event: string, ...args: any[]) {
23
+ const list = this.listeners.get(event);
24
+ if (list) {
25
+ for (const fn of list) {
26
+ fn(...args);
27
+ }
28
+ }
29
+
30
+ for (const item of this.wildcardListeners) {
31
+ if (item.pattern.test(event)) {
32
+ item.fn(...args);
33
+ }
34
+ }
35
+ }
36
+
37
+ async emitAsync(event: string, ...args: any[]): Promise<any[]> {
38
+ const promises: Promise<any>[] = [];
39
+
40
+ const list = this.listeners.get(event);
41
+ if (list) {
42
+ for (const fn of list) {
43
+ const res = fn(...args);
44
+ if (res instanceof Promise) {
45
+ promises.push(res);
46
+ }
47
+ }
48
+ }
49
+
50
+ for (const item of this.wildcardListeners) {
51
+ if (item.pattern.test(event)) {
52
+ const res = item.fn(...args);
53
+ if (res instanceof Promise) {
54
+ promises.push(res);
55
+ }
56
+ }
57
+ }
58
+
59
+ return Promise.all(promises);
60
+ }
61
+ }
@@ -0,0 +1,3 @@
1
+ export * from './event-emitter.ts';
2
+ export * from './decorators.ts';
3
+ export * from './event-emitter.module.ts';
@@ -6,6 +6,10 @@ import { HttpException, NotFoundException } from './exceptions.ts';
6
6
  import { ArgumentsHost, ExecutionContext } from '../lifecycle/interfaces.ts';
7
7
  import { CalyxArgumentsHost, CalyxExecutionContext } from '../lifecycle/context.ts';
8
8
  import { CalyxMiddlewareConsumer, MiddlewareConfiguration, RequestMethodMap, CompiledLifecycleItem as CompiledMiddlewareItem } from './middleware.ts';
9
+ import { EventEmitter } from '../event-emitter/event-emitter.ts';
10
+ import { cors, CorsOptions } from '../security/cors.middleware.ts';
11
+ import { helmet, HelmetOptions } from '../security/helmet.middleware.ts';
12
+ import { CronMatcher } from '../schedule/cron.matcher.ts';
9
13
 
10
14
  class ObjectPool<T> {
11
15
  private pool: T[] = [];
@@ -95,6 +99,9 @@ export class CalyxApplication {
95
99
  private middlewareConfigurations: MiddlewareConfiguration[] = [];
96
100
  private hostPool = new ObjectPool<CalyxArgumentsHost>(() => new CalyxArgumentsHost());
97
101
  private contextPool = new ObjectPool<CalyxExecutionContext>(() => new CalyxExecutionContext());
102
+ private sharedWebSockets: any[] = [];
103
+ private hasWebSockets = false;
104
+ private serverPort = 3000;
98
105
 
99
106
  use(...middlewares: any[]) {
100
107
  this.globalMiddlewares.push(...middlewares);
@@ -109,6 +116,16 @@ export class CalyxApplication {
109
116
  this.globalInterceptors.push(...interceptors);
110
117
  }
111
118
 
119
+ enableCors(options?: CorsOptions) {
120
+ this.use(cors(options));
121
+ return this;
122
+ }
123
+
124
+ useHelmet(options?: HelmetOptions) {
125
+ this.use(helmet(options));
126
+ return this;
127
+ }
128
+
112
129
  useGlobalPipes(...pipes: any[]) {
113
130
  this.globalPipes.push(...pipes);
114
131
  }
@@ -129,6 +146,15 @@ export class CalyxApplication {
129
146
  // Build the routing table from registered controllers
130
147
  this.buildRoutes();
131
148
 
149
+ // Register Event listeners
150
+ this.registerEventListeners();
151
+
152
+ // Register Scheduled tasks
153
+ this.registerScheduledTasks();
154
+
155
+ // Register WebSocket Gateways
156
+ this.registerWebSocketGateways();
157
+
132
158
  // Call OnModuleInit hooks
133
159
  await this.runOnModuleInit();
134
160
 
@@ -908,11 +934,43 @@ export class CalyxApplication {
908
934
  }
909
935
 
910
936
  async listen(port: number): Promise<any> {
937
+ this.serverPort = port;
911
938
  await this.init();
912
- this.server = Bun.serve({
939
+
940
+ const fetchHandler = (req: Request, server: any) => {
941
+ if (this.hasWebSockets && req.headers.get('upgrade') === 'websocket') {
942
+ const success = server.upgrade(req);
943
+ if (success) return undefined;
944
+ }
945
+ return this.handleRequest(req);
946
+ };
947
+
948
+ const serveOptions: any = {
913
949
  port,
914
- fetch: (req) => this.handleRequest(req),
915
- });
950
+ fetch: fetchHandler,
951
+ };
952
+
953
+ if (this.hasWebSockets) {
954
+ serveOptions.websocket = {
955
+ open: (ws: any) => {
956
+ for (const gateway of this.sharedWebSockets) {
957
+ this.dispatchWsConnection(gateway, ws);
958
+ }
959
+ },
960
+ message: (ws: any, message: any) => {
961
+ for (const gateway of this.sharedWebSockets) {
962
+ this.dispatchWsMessage(gateway, ws, message);
963
+ }
964
+ },
965
+ close: (ws: any) => {
966
+ for (const gateway of this.sharedWebSockets) {
967
+ this.dispatchWsDisconnect(gateway, ws);
968
+ }
969
+ }
970
+ };
971
+ }
972
+
973
+ this.server = Bun.serve(serveOptions);
916
974
  return this.server;
917
975
  }
918
976
 
@@ -989,4 +1047,279 @@ export class CalyxApplication {
989
1047
  }
990
1048
  }
991
1049
  }
1050
+
1051
+ private registerEventListeners() {
1052
+ let eventEmitter: EventEmitter;
1053
+ try {
1054
+ eventEmitter = this.container.getGlobalOrAnyInstance(EventEmitter);
1055
+ } catch {
1056
+ return;
1057
+ }
1058
+
1059
+ const instances = this.container.getProviderAndControllerInstances();
1060
+ for (const instance of instances) {
1061
+ if (!instance || !instance.constructor) continue;
1062
+ const listeners: { event: string; propertyKey: string | symbol }[] =
1063
+ Reflect.getMetadata('calyx:on_event', instance.constructor) || [];
1064
+
1065
+ for (const listener of listeners) {
1066
+ eventEmitter.on(listener.event, (...args: any[]) => {
1067
+ return instance[listener.propertyKey](...args);
1068
+ });
1069
+ }
1070
+ }
1071
+ }
1072
+
1073
+ private registerScheduledTasks() {
1074
+ let hasScheduleModule = false;
1075
+ for (const moduleClass of this.container.getModules().keys()) {
1076
+ if (moduleClass.name === 'ScheduleModule') {
1077
+ hasScheduleModule = true;
1078
+ break;
1079
+ }
1080
+ }
1081
+ if (!hasScheduleModule) {
1082
+ return;
1083
+ }
1084
+
1085
+ const instances = this.container.getProviderAndControllerInstances();
1086
+ for (const instance of instances) {
1087
+ if (!instance || !instance.constructor) continue;
1088
+
1089
+ const crons: { expression: string; propertyKey: string | symbol }[] =
1090
+ Reflect.getMetadata('calyx:cron', instance.constructor) || [];
1091
+ for (const cron of crons) {
1092
+ const parts = cron.expression.split(' ');
1093
+ const isSecondLevel = parts.length === 6;
1094
+
1095
+ let lastRan = 0;
1096
+ const tick = () => {
1097
+ const now = new Date();
1098
+ const nowMs = now.getTime();
1099
+ const timeUnit = isSecondLevel ? Math.floor(nowMs / 1000) : Math.floor(nowMs / 60000);
1100
+ if (timeUnit === lastRan) return;
1101
+
1102
+ if (CronMatcher.match(cron.expression, now)) {
1103
+ lastRan = timeUnit;
1104
+ try {
1105
+ instance[cron.propertyKey]();
1106
+ } catch (err) {
1107
+ console.error(`Error executing cron task ${String(cron.propertyKey)}:`, err);
1108
+ }
1109
+ }
1110
+ };
1111
+
1112
+ const intervalMs = isSecondLevel ? 1000 : 20000;
1113
+ const timer = setInterval(tick, intervalMs);
1114
+ this.cleanupListeners.push(() => clearInterval(timer));
1115
+ }
1116
+
1117
+ const intervals: { ms: number; propertyKey: string | symbol }[] =
1118
+ Reflect.getMetadata('calyx:interval', instance.constructor) || [];
1119
+ for (const interval of intervals) {
1120
+ const timer = setInterval(() => {
1121
+ try {
1122
+ instance[interval.propertyKey]();
1123
+ } catch (err) {
1124
+ console.error(`Error executing interval task ${String(interval.propertyKey)}:`, err);
1125
+ }
1126
+ }, interval.ms);
1127
+ this.cleanupListeners.push(() => clearInterval(timer));
1128
+ }
1129
+
1130
+ const timeouts: { ms: number; propertyKey: string | symbol }[] =
1131
+ Reflect.getMetadata('calyx:timeout', instance.constructor) || [];
1132
+ for (const timeout of timeouts) {
1133
+ const timer = setTimeout(() => {
1134
+ try {
1135
+ instance[timeout.propertyKey]();
1136
+ } catch (err) {
1137
+ console.error(`Error executing timeout task ${String(timeout.propertyKey)}:`, err);
1138
+ }
1139
+ }, timeout.ms);
1140
+ this.cleanupListeners.push(() => clearTimeout(timer));
1141
+ }
1142
+ }
1143
+ }
1144
+
1145
+ private registerWebSocketGateways() {
1146
+ const instances = this.container.getProviderAndControllerInstances();
1147
+ for (const instance of instances) {
1148
+ if (!instance || !instance.constructor) continue;
1149
+ const metadata = Reflect.getMetadata('calyx:websocket_gateway', instance.constructor);
1150
+ if (!metadata) continue;
1151
+
1152
+ const handlers = new Map<string, { propertyKey: string | symbol; paramMapping: any[] }>();
1153
+
1154
+ const subMessages: { event: string; propertyKey: string | symbol }[] =
1155
+ Reflect.getMetadata('calyx:subscribe_message', instance.constructor) || [];
1156
+
1157
+ const bodyParams: { propertyKey: string | symbol; parameterIndex: number }[] =
1158
+ Reflect.getMetadata('calyx:message_body', instance.constructor) || [];
1159
+
1160
+ const socketParams: { propertyKey: string | symbol; parameterIndex: number }[] =
1161
+ Reflect.getMetadata('calyx:connected_socket', instance.constructor) || [];
1162
+
1163
+ for (const sub of subMessages) {
1164
+ const paramMapping: any[] = [];
1165
+
1166
+ for (const bp of bodyParams) {
1167
+ if (bp.propertyKey === sub.propertyKey) {
1168
+ paramMapping[bp.parameterIndex] = 'body';
1169
+ }
1170
+ }
1171
+ for (const sp of socketParams) {
1172
+ if (sp.propertyKey === sub.propertyKey) {
1173
+ paramMapping[sp.parameterIndex] = 'socket';
1174
+ }
1175
+ }
1176
+
1177
+ const classGuards = Reflect.getMetadata(METADATA_KEYS.GUARDS, instance.constructor) || [];
1178
+ const methodGuards = Reflect.getMetadata(METADATA_KEYS.GUARDS, instance.constructor.prototype, sub.propertyKey) || [];
1179
+ const guards = this.compileLifecycleItems(this.rootModule, [...this.globalGuards, ...classGuards, ...methodGuards]);
1180
+
1181
+ const classInterceptors = Reflect.getMetadata(METADATA_KEYS.INTERCEPTORS, instance.constructor) || [];
1182
+ const methodInterceptors = Reflect.getMetadata(METADATA_KEYS.INTERCEPTORS, instance.constructor.prototype, sub.propertyKey) || [];
1183
+ const interceptors = this.compileLifecycleItems(this.rootModule, [...this.globalInterceptors, ...classInterceptors, ...methodInterceptors]);
1184
+
1185
+ handlers.set(sub.event, {
1186
+ propertyKey: sub.propertyKey,
1187
+ paramMapping,
1188
+ guards,
1189
+ interceptors,
1190
+ gatewayClass: instance.constructor,
1191
+ });
1192
+ }
1193
+
1194
+ const gatewayInfo = {
1195
+ instance,
1196
+ metadata,
1197
+ handlers,
1198
+ };
1199
+
1200
+ if (metadata.port && metadata.port !== this.serverPort) {
1201
+ this.startDedicatedWebSocketServer(gatewayInfo);
1202
+ } else {
1203
+ this.sharedWebSockets.push(gatewayInfo);
1204
+ this.hasWebSockets = true;
1205
+ }
1206
+ }
1207
+ }
1208
+
1209
+ private startDedicatedWebSocketServer(gateway: any) {
1210
+ const port = gateway.metadata.port;
1211
+ const self = this;
1212
+ const wsServer = Bun.serve({
1213
+ port,
1214
+ fetch(req, server) {
1215
+ if (server.upgrade(req)) {
1216
+ return undefined;
1217
+ }
1218
+ return new Response('Expected upgrade to websocket', { status: 400 });
1219
+ },
1220
+ websocket: {
1221
+ open: (ws: any) => {
1222
+ self.dispatchWsConnection(gateway, ws);
1223
+ },
1224
+ message: (ws: any, message: any) => {
1225
+ self.dispatchWsMessage(gateway, ws, message);
1226
+ },
1227
+ close: (ws: any) => {
1228
+ self.dispatchWsDisconnect(gateway, ws);
1229
+ }
1230
+ }
1231
+ });
1232
+
1233
+ this.cleanupListeners.push(() => wsServer.stop());
1234
+
1235
+ if (typeof gateway.instance.afterInit === 'function') {
1236
+ gateway.instance.afterInit(wsServer);
1237
+ }
1238
+ }
1239
+
1240
+ private dispatchWsConnection(gateway: any, ws: any) {
1241
+ if (typeof gateway.instance.handleConnection === 'function') {
1242
+ gateway.instance.handleConnection(ws);
1243
+ }
1244
+ }
1245
+
1246
+ private dispatchWsDisconnect(gateway: any, ws: any) {
1247
+ if (typeof gateway.instance.handleDisconnect === 'function') {
1248
+ gateway.instance.handleDisconnect(ws);
1249
+ }
1250
+ }
1251
+
1252
+ private async dispatchWsMessage(gateway: any, ws: any, message: any) {
1253
+ let messageStr = '';
1254
+ if (typeof message === 'string') {
1255
+ messageStr = message;
1256
+ } else if (message instanceof Uint8Array || message instanceof ArrayBuffer) {
1257
+ messageStr = new TextDecoder().decode(message);
1258
+ } else {
1259
+ return;
1260
+ }
1261
+
1262
+ try {
1263
+ const parsed = JSON.parse(messageStr);
1264
+ const event = parsed.event;
1265
+ const data = parsed.data;
1266
+
1267
+ const handlerInfo = gateway.handlers.get(event);
1268
+ if (handlerInfo) {
1269
+ const args: any[] = [];
1270
+ for (let i = 0; i < handlerInfo.paramMapping.length; i++) {
1271
+ const type = handlerInfo.paramMapping[i];
1272
+ if (type === 'body') {
1273
+ args[i] = data;
1274
+ } else if (type === 'socket') {
1275
+ args[i] = ws;
1276
+ }
1277
+ }
1278
+
1279
+ const context = this.contextPool.acquire();
1280
+ context.resetContextWs(ws, data, handlerInfo.gatewayClass, gateway.instance[handlerInfo.propertyKey]);
1281
+
1282
+ try {
1283
+ for (const guard of handlerInfo.guards) {
1284
+ const canActive = await guard.instance.canActivate(context);
1285
+ if (!canActive) {
1286
+ return; // Block event
1287
+ }
1288
+ }
1289
+
1290
+ const nextCall = {
1291
+ handle: async () => {
1292
+ return gateway.instance[handlerInfo.propertyKey](...args);
1293
+ }
1294
+ };
1295
+
1296
+ let chain = nextCall.handle;
1297
+ for (let i = handlerInfo.interceptors.length - 1; i >= 0; i--) {
1298
+ const interceptor = handlerInfo.interceptors[i];
1299
+ const currentChain = chain;
1300
+ chain = async () => {
1301
+ return interceptor.instance.intercept(context, {
1302
+ handle: () => currentChain()
1303
+ });
1304
+ };
1305
+ }
1306
+
1307
+ const result = await chain();
1308
+
1309
+ if (result !== undefined) {
1310
+ ws.send(JSON.stringify(result));
1311
+ }
1312
+ } catch (err: any) {
1313
+ // ignore or log
1314
+ } finally {
1315
+ context.clearContext();
1316
+ this.contextPool.release(context);
1317
+ }
1318
+ }
1319
+ } catch {
1320
+ // ignore non-json
1321
+ }
1322
+ }
992
1323
  }
1324
+
1325
+
@@ -1,8 +1,15 @@
1
1
  import { CalyxApplication } from './application.ts';
2
+ import { CalyxMicroservice } from '../microservices/microservice.ts';
3
+ import { MicroserviceOptions } from '../microservices/interfaces.ts';
2
4
 
3
5
  export class CalyxFactory {
4
6
  static async create(rootModule: any): Promise<CalyxApplication> {
5
7
  const app = new CalyxApplication(rootModule);
6
8
  return app;
7
9
  }
10
+
11
+ static async createMicroservice(rootModule: any, options?: MicroserviceOptions): Promise<CalyxMicroservice> {
12
+ const app = new CalyxMicroservice(rootModule, options);
13
+ return app;
14
+ }
8
15
  }
package/src/index.ts CHANGED
@@ -2,3 +2,9 @@ import 'reflect-metadata';
2
2
  export * from './core/index.ts';
3
3
  export * from './http/index.ts';
4
4
  export * from './lifecycle/index.ts';
5
+ export * from './config/index.ts';
6
+ export * from './event-emitter/index.ts';
7
+ export * from './security/index.ts';
8
+ export * from './schedule/index.ts';
9
+ export * from './websockets/index.ts';
10
+ export * from './microservices/index.ts';