@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
@@ -0,0 +1,89 @@
1
+ import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
2
+ import {
3
+ Module,
4
+ Controller,
5
+ Get,
6
+ CalyxFactory,
7
+ HashingService,
8
+ } from '../src/index.ts';
9
+
10
+ @Controller('test-security')
11
+ class SecurityController {
12
+ constructor(private readonly hashing: HashingService) {}
13
+
14
+ @Get('hash')
15
+ async testHash() {
16
+ const password = 'my_secure_password';
17
+ const hash = await this.hashing.hash(password);
18
+ const valid = await this.hashing.verify(password, hash);
19
+ const invalid = await this.hashing.verify('wrong_password', hash);
20
+ return { valid, invalid };
21
+ }
22
+
23
+ @Get('hello')
24
+ sayHello() {
25
+ return { hello: 'world' };
26
+ }
27
+ }
28
+
29
+ @Module({
30
+ controllers: [SecurityController],
31
+ providers: [HashingService],
32
+ })
33
+ class SecurityTestApp {}
34
+
35
+ describe('Security Features (Hashing, CORS, Helmet)', () => {
36
+ let app: any;
37
+ let baseUrl: string;
38
+ const PORT = 3878;
39
+
40
+ beforeAll(async () => {
41
+ app = await CalyxFactory.create(SecurityTestApp);
42
+ app.enableCors({ origin: 'http://example.com', credentials: true });
43
+ app.useHelmet({ contentSecurityPolicy: false }); // Disable CSP to make tests simpler
44
+ await app.listen(PORT);
45
+ baseUrl = `http://localhost:${PORT}`;
46
+ });
47
+
48
+ afterAll(async () => {
49
+ await app.close();
50
+ });
51
+
52
+ test('should hash and verify passwords using native Bun.password Zig bindings', async () => {
53
+ const res = await fetch(`${baseUrl}/test-security/hash`);
54
+ expect(res.status).toBe(200);
55
+ const body = await res.json();
56
+ expect(body.valid).toBe(true);
57
+ expect(body.invalid).toBe(false);
58
+ });
59
+
60
+ test('should apply CORS headers and respond to preflight OPTIONS request', async () => {
61
+ // 1. Regular GET request with Origin
62
+ const res = await fetch(`${baseUrl}/test-security/hello`, {
63
+ headers: { Origin: 'http://example.com' },
64
+ });
65
+ expect(res.status).toBe(200);
66
+ expect(res.headers.get('access-control-allow-origin')).toBe('http://example.com');
67
+ expect(res.headers.get('access-control-allow-credentials')).toBe('true');
68
+
69
+ // 2. Preflight OPTIONS request
70
+ const preflight = await fetch(`${baseUrl}/test-security/hello`, {
71
+ method: 'OPTIONS',
72
+ headers: {
73
+ Origin: 'http://example.com',
74
+ 'Access-Control-Request-Method': 'GET',
75
+ },
76
+ });
77
+ expect(preflight.status).toBe(204);
78
+ expect(preflight.headers.get('access-control-allow-origin')).toBe('http://example.com');
79
+ expect(preflight.headers.get('access-control-allow-methods')).toContain('GET');
80
+ });
81
+
82
+ test('should set Helmet secure headers', async () => {
83
+ const res = await fetch(`${baseUrl}/test-security/hello`);
84
+ expect(res.status).toBe(200);
85
+ expect(res.headers.get('x-content-type-options')).toBe('nosniff');
86
+ expect(res.headers.get('x-frame-options')).toBe('SAMEORIGIN');
87
+ expect(res.headers.get('referrer-policy')).toBe('no-referrer');
88
+ });
89
+ });
@@ -0,0 +1,125 @@
1
+ import { describe, test, expect, beforeAll, afterAll } from 'bun:test';
2
+ import {
3
+ Module,
4
+ CalyxFactory,
5
+ WebSocketGateway,
6
+ SubscribeMessage,
7
+ MessageBody,
8
+ ConnectedSocket,
9
+ } from '../src/index.ts';
10
+
11
+ let clientConnected = false;
12
+ let clientDisconnected = false;
13
+ let messageReceived: any = null;
14
+
15
+ // 1. Shared Port Gateway
16
+ @WebSocketGateway()
17
+ class SharedEventsGateway {
18
+ handleConnection(client: any) {
19
+ clientConnected = true;
20
+ }
21
+
22
+ handleDisconnect(client: any) {
23
+ clientDisconnected = true;
24
+ }
25
+
26
+ @SubscribeMessage('events')
27
+ onEvents(@MessageBody() data: any, @ConnectedSocket() client: any) {
28
+ messageReceived = data;
29
+ return { event: 'events', data: `echo: ${data}` };
30
+ }
31
+ }
32
+
33
+ // 2. Dedicated Port Gateway
34
+ let dedicatedConnected = false;
35
+ @WebSocketGateway(3912)
36
+ class DedicatedEventsGateway {
37
+ handleConnection(client: any) {
38
+ dedicatedConnected = true;
39
+ }
40
+
41
+ @SubscribeMessage('dedicated_event')
42
+ onDedicatedEvent(@MessageBody() data: any) {
43
+ return { event: 'dedicated_response', data: `dedicated echo: ${data}` };
44
+ }
45
+ }
46
+
47
+ @Module({
48
+ providers: [SharedEventsGateway, DedicatedEventsGateway],
49
+ })
50
+ class TestApp {}
51
+
52
+ describe('WebSocket Gateways (Bun.serve, SubscribeMessage, MessageBody, ConnectedSocket)', () => {
53
+ let app: any;
54
+ const PORT = 3889;
55
+
56
+ beforeAll(async () => {
57
+ app = await CalyxFactory.create(TestApp);
58
+ await app.listen(PORT);
59
+ });
60
+
61
+ afterAll(async () => {
62
+ await app.close();
63
+ });
64
+
65
+ test('should connect and handle message exchange on shared port', async () => {
66
+ clientConnected = false;
67
+ clientDisconnected = false;
68
+ messageReceived = null;
69
+
70
+ const ws = new WebSocket(`ws://localhost:${PORT}`);
71
+
72
+ // Wait for connection to open
73
+ await new Promise<void>((resolve, reject) => {
74
+ ws.onopen = () => resolve();
75
+ ws.onerror = (err) => reject(err);
76
+ });
77
+
78
+ expect(clientConnected).toBe(true);
79
+
80
+ // Send a message
81
+ ws.send(JSON.stringify({ event: 'events', data: 'hello calyx' }));
82
+
83
+ // Wait for response message
84
+ const response = await new Promise<any>((resolve) => {
85
+ ws.onmessage = (event) => {
86
+ resolve(JSON.parse(event.data));
87
+ };
88
+ });
89
+
90
+ expect(messageReceived).toBe('hello calyx');
91
+ expect(response).toEqual({ event: 'events', data: 'echo: hello calyx' });
92
+
93
+ // Close connection
94
+ ws.close();
95
+
96
+ // Wait a tick for disconnect event
97
+ await new Promise((resolve) => setTimeout(resolve, 50));
98
+ expect(clientDisconnected).toBe(true);
99
+ });
100
+
101
+ test('should connect and handle message exchange on dedicated port', async () => {
102
+ dedicatedConnected = false;
103
+
104
+ const ws = new WebSocket('ws://localhost:3912');
105
+
106
+ await new Promise<void>((resolve, reject) => {
107
+ ws.onopen = () => resolve();
108
+ ws.onerror = (err) => reject(err);
109
+ });
110
+
111
+ expect(dedicatedConnected).toBe(true);
112
+
113
+ ws.send(JSON.stringify({ event: 'dedicated_event', data: 'secure channel' }));
114
+
115
+ const response = await new Promise<any>((resolve) => {
116
+ ws.onmessage = (event) => {
117
+ resolve(JSON.parse(event.data));
118
+ };
119
+ });
120
+
121
+ expect(response).toEqual({ event: 'dedicated_response', data: 'dedicated echo: secure channel' });
122
+
123
+ ws.close();
124
+ });
125
+ });