@onebun/core 0.1.2 → 0.1.3

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 (79) hide show
  1. package/package.json +1 -1
  2. package/src/{application.test.ts → application/application.test.ts} +6 -5
  3. package/src/{application.ts → application/application.ts} +131 -12
  4. package/src/application/index.ts +9 -0
  5. package/src/{multi-service-application.test.ts → application/multi-service-application.test.ts} +2 -1
  6. package/src/{multi-service-application.ts → application/multi-service-application.ts} +2 -1
  7. package/src/{multi-service.types.ts → application/multi-service.types.ts} +1 -1
  8. package/src/{decorators.test.ts → decorators/decorators.test.ts} +2 -1
  9. package/src/{decorators.ts → decorators/decorators.ts} +3 -2
  10. package/src/decorators/index.ts +15 -0
  11. package/src/index.ts +47 -134
  12. package/src/module/index.ts +12 -0
  13. package/src/{module.test.ts → module/module.test.ts} +3 -2
  14. package/src/{module.ts → module/module.ts} +6 -5
  15. package/src/queue/adapters/index.ts +8 -0
  16. package/src/queue/adapters/memory.adapter.test.ts +405 -0
  17. package/src/queue/adapters/memory.adapter.ts +509 -0
  18. package/src/queue/adapters/redis.adapter.ts +673 -0
  19. package/src/queue/cron-expression.test.ts +145 -0
  20. package/src/queue/cron-expression.ts +115 -0
  21. package/src/queue/cron-parser.test.ts +185 -0
  22. package/src/queue/cron-parser.ts +287 -0
  23. package/src/queue/decorators.test.ts +292 -0
  24. package/src/queue/decorators.ts +493 -0
  25. package/src/queue/docs-examples.test.ts +449 -0
  26. package/src/queue/guards.test.ts +309 -0
  27. package/src/queue/guards.ts +307 -0
  28. package/src/queue/index.ts +118 -0
  29. package/src/queue/pattern-matcher.test.ts +191 -0
  30. package/src/queue/pattern-matcher.ts +252 -0
  31. package/src/queue/queue.service.ts +421 -0
  32. package/src/queue/scheduler.test.ts +235 -0
  33. package/src/queue/scheduler.ts +379 -0
  34. package/src/queue/types.ts +502 -0
  35. package/src/redis/index.ts +8 -0
  36. package/src/{env-resolver.ts → service-client/env-resolver.ts} +1 -1
  37. package/src/service-client/index.ts +10 -0
  38. package/src/{service-client.test.ts → service-client/service-client.test.ts} +3 -2
  39. package/src/{service-client.ts → service-client/service-client.ts} +1 -1
  40. package/src/{service-definition.test.ts → service-client/service-definition.test.ts} +3 -2
  41. package/src/{service-definition.ts → service-client/service-definition.ts} +2 -2
  42. package/src/testing/index.ts +7 -0
  43. package/src/types.ts +34 -5
  44. package/src/websocket/index.ts +50 -0
  45. package/src/{ws-decorators.ts → websocket/ws-decorators.ts} +2 -1
  46. package/src/{ws-integration.test.ts → websocket/ws-integration.test.ts} +3 -2
  47. package/src/{ws-service-definition.ts → websocket/ws-service-definition.ts} +2 -1
  48. package/src/{ws-storage-redis.ts → websocket/ws-storage-redis.ts} +1 -1
  49. /package/src/{metadata.test.ts → decorators/metadata.test.ts} +0 -0
  50. /package/src/{metadata.ts → decorators/metadata.ts} +0 -0
  51. /package/src/{config.service.test.ts → module/config.service.test.ts} +0 -0
  52. /package/src/{config.service.ts → module/config.service.ts} +0 -0
  53. /package/src/{controller.test.ts → module/controller.test.ts} +0 -0
  54. /package/src/{controller.ts → module/controller.ts} +0 -0
  55. /package/src/{service.test.ts → module/service.test.ts} +0 -0
  56. /package/src/{service.ts → module/service.ts} +0 -0
  57. /package/src/{redis-client.ts → redis/redis-client.ts} +0 -0
  58. /package/src/{shared-redis.ts → redis/shared-redis.ts} +0 -0
  59. /package/src/{env-resolver.test.ts → service-client/env-resolver.test.ts} +0 -0
  60. /package/src/{service-client.types.ts → service-client/service-client.types.ts} +0 -0
  61. /package/src/{test-utils.test.ts → testing/test-utils.test.ts} +0 -0
  62. /package/src/{test-utils.ts → testing/test-utils.ts} +0 -0
  63. /package/src/{ws-base-gateway.test.ts → websocket/ws-base-gateway.test.ts} +0 -0
  64. /package/src/{ws-base-gateway.ts → websocket/ws-base-gateway.ts} +0 -0
  65. /package/src/{ws-client.test.ts → websocket/ws-client.test.ts} +0 -0
  66. /package/src/{ws-client.ts → websocket/ws-client.ts} +0 -0
  67. /package/src/{ws-client.types.ts → websocket/ws-client.types.ts} +0 -0
  68. /package/src/{ws-decorators.test.ts → websocket/ws-decorators.test.ts} +0 -0
  69. /package/src/{ws-guards.test.ts → websocket/ws-guards.test.ts} +0 -0
  70. /package/src/{ws-guards.ts → websocket/ws-guards.ts} +0 -0
  71. /package/src/{ws-handler.ts → websocket/ws-handler.ts} +0 -0
  72. /package/src/{ws-pattern-matcher.test.ts → websocket/ws-pattern-matcher.test.ts} +0 -0
  73. /package/src/{ws-pattern-matcher.ts → websocket/ws-pattern-matcher.ts} +0 -0
  74. /package/src/{ws-socketio-protocol.test.ts → websocket/ws-socketio-protocol.test.ts} +0 -0
  75. /package/src/{ws-socketio-protocol.ts → websocket/ws-socketio-protocol.ts} +0 -0
  76. /package/src/{ws-storage-memory.test.ts → websocket/ws-storage-memory.test.ts} +0 -0
  77. /package/src/{ws-storage-memory.ts → websocket/ws-storage-memory.ts} +0 -0
  78. /package/src/{ws-storage.ts → websocket/ws-storage.ts} +0 -0
  79. /package/src/{ws.types.ts → websocket/ws.types.ts} +0 -0
@@ -0,0 +1,309 @@
1
+ /**
2
+ * Queue Guards Tests
3
+ */
4
+
5
+ import {
6
+ describe,
7
+ it,
8
+ expect,
9
+ } from 'bun:test';
10
+
11
+ import type {
12
+ Message,
13
+ MessageMetadata,
14
+ MessageGuard,
15
+ MessageExecutionContext,
16
+ } from './types';
17
+
18
+ import {
19
+ MessageExecutionContextImpl,
20
+ MessageAuthGuard,
21
+ MessageServiceGuard,
22
+ MessageHeaderGuard,
23
+ MessageTraceGuard,
24
+ MessageAllGuards,
25
+ MessageAnyGuard,
26
+ executeMessageGuards,
27
+ createMessageGuard,
28
+ } from './guards';
29
+
30
+ // Helper to create a mock message
31
+ function createMockMessage(metadata: MessageMetadata = {}): Message {
32
+ return {
33
+ id: 'test-id',
34
+ pattern: 'test.pattern',
35
+ data: { test: true },
36
+ timestamp: Date.now(),
37
+ metadata,
38
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
39
+ async ack() {},
40
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
41
+ async nack() {},
42
+ };
43
+ }
44
+
45
+ // Helper to create execution context
46
+ function createContext(metadata: MessageMetadata = {}): MessageExecutionContext {
47
+ const message = createMockMessage(metadata);
48
+
49
+ return new MessageExecutionContextImpl(
50
+ message,
51
+ 'test.pattern',
52
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
53
+ () => {},
54
+ class TestHandler {},
55
+ );
56
+ }
57
+
58
+ describe('queue-guards', () => {
59
+ describe('MessageExecutionContextImpl', () => {
60
+ it('should provide message access', () => {
61
+ const message = createMockMessage({ authorization: 'Bearer token' });
62
+ const context = new MessageExecutionContextImpl(
63
+ message,
64
+ 'test.pattern',
65
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
66
+ () => {},
67
+ class TestHandler {},
68
+ );
69
+
70
+ expect(context.getMessage()).toBe(message);
71
+ });
72
+
73
+ it('should provide metadata access', () => {
74
+ const context = createContext({ authorization: 'Bearer token' });
75
+ const metadata = context.getMetadata();
76
+
77
+ expect(metadata.authorization).toBe('Bearer token');
78
+ });
79
+
80
+ it('should provide pattern access', () => {
81
+ const context = createContext();
82
+ expect(context.getPattern()).toBe('test.pattern');
83
+ });
84
+
85
+ it('should provide handler access', () => {
86
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
87
+ const handler = () => {};
88
+ const message = createMockMessage();
89
+ const context = new MessageExecutionContextImpl(
90
+ message,
91
+ 'test.pattern',
92
+ handler,
93
+ class TestHandler {},
94
+ );
95
+
96
+ expect(context.getHandler()).toBe(handler);
97
+ });
98
+
99
+ it('should provide class access', () => {
100
+ class TestHandler {}
101
+ const message = createMockMessage();
102
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
103
+ const context = new MessageExecutionContextImpl(message, 'test.pattern', () => {}, TestHandler);
104
+
105
+ expect(context.getClass()).toBe(TestHandler);
106
+ });
107
+ });
108
+
109
+ describe('MessageAuthGuard', () => {
110
+ it('should pass when authorization is present', () => {
111
+ const guard = new MessageAuthGuard();
112
+ const context = createContext({ authorization: 'Bearer token123' });
113
+
114
+ expect(guard.canActivate(context)).toBe(true);
115
+ });
116
+
117
+ it('should fail when authorization is missing', () => {
118
+ const guard = new MessageAuthGuard();
119
+ const context = createContext({});
120
+
121
+ expect(guard.canActivate(context)).toBe(false);
122
+ });
123
+
124
+ it('should fail when authorization is empty', () => {
125
+ const guard = new MessageAuthGuard();
126
+ const context = createContext({ authorization: '' });
127
+
128
+ expect(guard.canActivate(context)).toBe(false);
129
+ });
130
+ });
131
+
132
+ describe('MessageServiceGuard', () => {
133
+ it('should pass for allowed service', () => {
134
+ const guard = new MessageServiceGuard(['payment-service', 'order-service']);
135
+ const context = createContext({ serviceId: 'payment-service' });
136
+
137
+ expect(guard.canActivate(context)).toBe(true);
138
+ });
139
+
140
+ it('should fail for disallowed service', () => {
141
+ const guard = new MessageServiceGuard(['payment-service']);
142
+ const context = createContext({ serviceId: 'unknown-service' });
143
+
144
+ expect(guard.canActivate(context)).toBe(false);
145
+ });
146
+
147
+ it('should fail when serviceId is missing', () => {
148
+ const guard = new MessageServiceGuard(['payment-service']);
149
+ const context = createContext({});
150
+
151
+ expect(guard.canActivate(context)).toBe(false);
152
+ });
153
+ });
154
+
155
+ /* eslint-disable @typescript-eslint/naming-convention */
156
+ describe('MessageHeaderGuard', () => {
157
+ it('should pass when header is present', () => {
158
+ const guard = new MessageHeaderGuard('x-api-key');
159
+ const context = createContext({ headers: { 'x-api-key': 'secret' } });
160
+
161
+ expect(guard.canActivate(context)).toBe(true);
162
+ });
163
+
164
+ it('should fail when header is missing', () => {
165
+ const guard = new MessageHeaderGuard('x-api-key');
166
+ const context = createContext({ headers: {} });
167
+
168
+ expect(guard.canActivate(context)).toBe(false);
169
+ });
170
+
171
+ it('should fail when headers object is missing', () => {
172
+ const guard = new MessageHeaderGuard('x-api-key');
173
+ const context = createContext({});
174
+
175
+ expect(guard.canActivate(context)).toBe(false);
176
+ });
177
+
178
+ it('should check expected value when provided', () => {
179
+ const guard = new MessageHeaderGuard('x-api-key', 'expected-value');
180
+ const contextMatch = createContext({ headers: { 'x-api-key': 'expected-value' } });
181
+ const contextNoMatch = createContext({ headers: { 'x-api-key': 'wrong-value' } });
182
+
183
+ expect(guard.canActivate(contextMatch)).toBe(true);
184
+ expect(guard.canActivate(contextNoMatch)).toBe(false);
185
+ });
186
+ /* eslint-enable @typescript-eslint/naming-convention */
187
+ });
188
+
189
+ describe('MessageTraceGuard', () => {
190
+ it('should pass when traceId is present', () => {
191
+ const guard = new MessageTraceGuard();
192
+ const context = createContext({ traceId: 'trace-123' });
193
+
194
+ expect(guard.canActivate(context)).toBe(true);
195
+ });
196
+
197
+ it('should fail when traceId is missing', () => {
198
+ const guard = new MessageTraceGuard();
199
+ const context = createContext({});
200
+
201
+ expect(guard.canActivate(context)).toBe(false);
202
+ });
203
+ });
204
+
205
+ describe('MessageAllGuards', () => {
206
+ it('should pass when all guards pass', async () => {
207
+ const guard = new MessageAllGuards([MessageAuthGuard, MessageTraceGuard]);
208
+ const context = createContext({ authorization: 'Bearer token', traceId: 'trace-123' });
209
+
210
+ const result = await guard.canActivate(context);
211
+ expect(result).toBe(true);
212
+ });
213
+
214
+ it('should fail when any guard fails', async () => {
215
+ const guard = new MessageAllGuards([MessageAuthGuard, MessageTraceGuard]);
216
+ const context = createContext({ authorization: 'Bearer token' }); // No traceId
217
+
218
+ const result = await guard.canActivate(context);
219
+ expect(result).toBe(false);
220
+ });
221
+
222
+ it('should accept guard instances', async () => {
223
+ const serviceGuard = new MessageServiceGuard(['allowed-service']);
224
+ const guard = new MessageAllGuards([serviceGuard, MessageAuthGuard]);
225
+ const context = createContext({ serviceId: 'allowed-service', authorization: 'Bearer token' });
226
+
227
+ const result = await guard.canActivate(context);
228
+ expect(result).toBe(true);
229
+ });
230
+ });
231
+
232
+ describe('MessageAnyGuard', () => {
233
+ it('should pass when any guard passes', async () => {
234
+ const guard = new MessageAnyGuard([MessageAuthGuard, MessageTraceGuard]);
235
+ const context = createContext({ authorization: 'Bearer token' }); // Only auth, no trace
236
+
237
+ const result = await guard.canActivate(context);
238
+ expect(result).toBe(true);
239
+ });
240
+
241
+ it('should fail when all guards fail', async () => {
242
+ const guard = new MessageAnyGuard([MessageAuthGuard, MessageTraceGuard]);
243
+ const context = createContext({}); // No auth, no trace
244
+
245
+ const result = await guard.canActivate(context);
246
+ expect(result).toBe(false);
247
+ });
248
+ });
249
+
250
+ describe('executeMessageGuards', () => {
251
+ it('should execute all guards and return true when all pass', async () => {
252
+ const context = createContext({ authorization: 'Bearer token' });
253
+ const result = await executeMessageGuards([MessageAuthGuard], context);
254
+
255
+ expect(result).toBe(true);
256
+ });
257
+
258
+ it('should return false when any guard fails', async () => {
259
+ const context = createContext({});
260
+ const result = await executeMessageGuards([MessageAuthGuard], context);
261
+
262
+ expect(result).toBe(false);
263
+ });
264
+
265
+ it('should short-circuit on first failure', async () => {
266
+ let secondGuardCalled = false;
267
+
268
+ class SecondGuard implements MessageGuard {
269
+ canActivate(): boolean {
270
+ secondGuardCalled = true;
271
+
272
+ return true;
273
+ }
274
+ }
275
+
276
+ const context = createContext({}); // Will fail auth guard
277
+ await executeMessageGuards([MessageAuthGuard, SecondGuard], context);
278
+
279
+ expect(secondGuardCalled).toBe(false);
280
+ });
281
+ });
282
+
283
+ describe('createMessageGuard', () => {
284
+ it('should create guard from function', () => {
285
+ const guard = createMessageGuard((context) => {
286
+ return context.getMetadata().authorization === 'secret';
287
+ });
288
+
289
+ const passContext = createContext({ authorization: 'secret' });
290
+ const failContext = createContext({ authorization: 'wrong' });
291
+
292
+ expect(guard.canActivate(passContext)).toBe(true);
293
+ expect(guard.canActivate(failContext)).toBe(false);
294
+ });
295
+
296
+ it('should support async functions', async () => {
297
+ const guard = createMessageGuard(async (context) => {
298
+ await Promise.resolve(); // Simulate async operation
299
+
300
+ return !!context.getMetadata().authorization;
301
+ });
302
+
303
+ const context = createContext({ authorization: 'token' });
304
+ const result = await guard.canActivate(context);
305
+
306
+ expect(result).toBe(true);
307
+ });
308
+ });
309
+ });
@@ -0,0 +1,307 @@
1
+ /**
2
+ * Message Guards
3
+ *
4
+ * Guards for message handlers, similar to WebSocket guards.
5
+ * Guards can be used to authorize message processing.
6
+ */
7
+
8
+ import type {
9
+ Message,
10
+ MessageMetadata,
11
+ MessageGuard,
12
+ MessageExecutionContext,
13
+ MessageGuardConstructor,
14
+ } from './types';
15
+
16
+ // ============================================================================
17
+ // Execution Context Implementation
18
+ // ============================================================================
19
+
20
+ /**
21
+ * Implementation of MessageExecutionContext
22
+ */
23
+ export class MessageExecutionContextImpl implements MessageExecutionContext {
24
+ constructor(
25
+ private readonly message: Message,
26
+ private readonly pattern: string,
27
+ private readonly handler: (...args: unknown[]) => unknown,
28
+ private readonly targetClass: new (...args: unknown[]) => unknown,
29
+ ) {}
30
+
31
+ getMessage<T>(): Message<T> {
32
+ return this.message as Message<T>;
33
+ }
34
+
35
+ getMetadata(): MessageMetadata {
36
+ return this.message.metadata;
37
+ }
38
+
39
+ getPattern(): string {
40
+ return this.pattern;
41
+ }
42
+
43
+ getHandler(): (...args: unknown[]) => unknown {
44
+ return this.handler;
45
+ }
46
+
47
+ getClass(): new (...args: unknown[]) => unknown {
48
+ return this.targetClass;
49
+ }
50
+ }
51
+
52
+ // ============================================================================
53
+ // Built-in Guards
54
+ // ============================================================================
55
+
56
+ /**
57
+ * Guard that checks for the presence of an authorization token
58
+ *
59
+ * @example
60
+ * ```typescript
61
+ * @UseMessageGuards(MessageAuthGuard)
62
+ * @Subscribe('orders.*')
63
+ * async handleOrder(message: Message<OrderData>) {
64
+ * // Only messages with authorization token will be processed
65
+ * }
66
+ * ```
67
+ */
68
+ export class MessageAuthGuard implements MessageGuard {
69
+ canActivate(context: MessageExecutionContext): boolean {
70
+ const metadata = context.getMetadata();
71
+
72
+ return !!metadata.authorization;
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Guard that checks if the message comes from an allowed service
78
+ *
79
+ * @example
80
+ * ```typescript
81
+ * const serviceGuard = new MessageServiceGuard(['payment-service', 'order-service']);
82
+ *
83
+ * @UseMessageGuards(serviceGuard)
84
+ * @Subscribe('events.internal.*')
85
+ * async handleInternalEvent(message: Message<EventData>) {
86
+ * // Only messages from allowed services will be processed
87
+ * }
88
+ * ```
89
+ */
90
+ export class MessageServiceGuard implements MessageGuard {
91
+ constructor(private readonly allowedServices: string[]) {}
92
+
93
+ canActivate(context: MessageExecutionContext): boolean {
94
+ const metadata = context.getMetadata();
95
+ const serviceId = metadata.serviceId;
96
+
97
+ if (!serviceId) {
98
+ return false;
99
+ }
100
+
101
+ return this.allowedServices.includes(serviceId);
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Guard that requires a specific header to be present
107
+ *
108
+ * @example
109
+ * ```typescript
110
+ * const headerGuard = new MessageHeaderGuard('x-api-key');
111
+ *
112
+ * @UseMessageGuards(headerGuard)
113
+ * @Subscribe('api.*')
114
+ * async handleApiRequest(message: Message<RequestData>) {
115
+ * // Only messages with x-api-key header will be processed
116
+ * }
117
+ * ```
118
+ */
119
+ export class MessageHeaderGuard implements MessageGuard {
120
+ constructor(
121
+ private readonly headerName: string,
122
+ private readonly expectedValue?: string,
123
+ ) {}
124
+
125
+ canActivate(context: MessageExecutionContext): boolean {
126
+ const metadata = context.getMetadata();
127
+ const headers = metadata.headers;
128
+
129
+ if (!headers) {
130
+ return false;
131
+ }
132
+
133
+ const value = headers[this.headerName];
134
+
135
+ if (value === undefined) {
136
+ return false;
137
+ }
138
+
139
+ if (this.expectedValue !== undefined) {
140
+ return value === this.expectedValue;
141
+ }
142
+
143
+ return true;
144
+ }
145
+ }
146
+
147
+ /**
148
+ * Guard that checks for trace context (for distributed tracing requirements)
149
+ *
150
+ * @example
151
+ * ```typescript
152
+ * @UseMessageGuards(MessageTraceGuard)
153
+ * @Subscribe('traced.*')
154
+ * async handleTracedEvent(message: Message<EventData>) {
155
+ * // Only messages with trace context will be processed
156
+ * }
157
+ * ```
158
+ */
159
+ export class MessageTraceGuard implements MessageGuard {
160
+ canActivate(context: MessageExecutionContext): boolean {
161
+ const metadata = context.getMetadata();
162
+
163
+ return !!metadata.traceId;
164
+ }
165
+ }
166
+
167
+ // ============================================================================
168
+ // Composite Guards
169
+ // ============================================================================
170
+
171
+ /**
172
+ * Guard that passes if ALL of the provided guards pass
173
+ *
174
+ * @example
175
+ * ```typescript
176
+ * const allGuards = new MessageAllGuards([
177
+ * MessageAuthGuard,
178
+ * new MessageServiceGuard(['payment-service']),
179
+ * ]);
180
+ *
181
+ * @UseMessageGuards(allGuards)
182
+ * @Subscribe('secure.*')
183
+ * async handleSecureEvent(message: Message<EventData>) {
184
+ * // Requires both auth and service check to pass
185
+ * }
186
+ * ```
187
+ */
188
+ export class MessageAllGuards implements MessageGuard {
189
+ private readonly guards: MessageGuard[];
190
+
191
+ constructor(guards: Array<MessageGuard | MessageGuardConstructor>) {
192
+ this.guards = guards.map((guard) => {
193
+ if (typeof guard === 'function') {
194
+ return new guard();
195
+ }
196
+
197
+ return guard;
198
+ });
199
+ }
200
+
201
+ async canActivate(context: MessageExecutionContext): Promise<boolean> {
202
+ for (const guard of this.guards) {
203
+ const result = await guard.canActivate(context);
204
+ if (!result) {
205
+ return false;
206
+ }
207
+ }
208
+
209
+ return true;
210
+ }
211
+ }
212
+
213
+ /**
214
+ * Guard that passes if ANY of the provided guards passes
215
+ *
216
+ * @example
217
+ * ```typescript
218
+ * const anyGuard = new MessageAnyGuard([
219
+ * new MessageServiceGuard(['internal-service']),
220
+ * MessageAuthGuard,
221
+ * ]);
222
+ *
223
+ * @UseMessageGuards(anyGuard)
224
+ * @Subscribe('events.*')
225
+ * async handleEvent(message: Message<EventData>) {
226
+ * // Requires either service check OR auth to pass
227
+ * }
228
+ * ```
229
+ */
230
+ export class MessageAnyGuard implements MessageGuard {
231
+ private readonly guards: MessageGuard[];
232
+
233
+ constructor(guards: Array<MessageGuard | MessageGuardConstructor>) {
234
+ this.guards = guards.map((guard) => {
235
+ if (typeof guard === 'function') {
236
+ return new guard();
237
+ }
238
+
239
+ return guard;
240
+ });
241
+ }
242
+
243
+ async canActivate(context: MessageExecutionContext): Promise<boolean> {
244
+ for (const guard of this.guards) {
245
+ const result = await guard.canActivate(context);
246
+ if (result) {
247
+ return true;
248
+ }
249
+ }
250
+
251
+ return false;
252
+ }
253
+ }
254
+
255
+ // ============================================================================
256
+ // Guard Execution
257
+ // ============================================================================
258
+
259
+ /**
260
+ * Execute an array of guards and return whether all passed
261
+ *
262
+ * @param guards - Array of guard instances or constructors
263
+ * @param context - Message execution context
264
+ * @returns Whether all guards passed
265
+ */
266
+ export async function executeMessageGuards(
267
+ guards: Array<MessageGuard | MessageGuardConstructor>,
268
+ context: MessageExecutionContext,
269
+ ): Promise<boolean> {
270
+ for (const guard of guards) {
271
+ const guardInstance = typeof guard === 'function' ? new guard() : guard;
272
+ const result = await guardInstance.canActivate(context);
273
+ if (!result) {
274
+ return false;
275
+ }
276
+ }
277
+
278
+ return true;
279
+ }
280
+
281
+ /**
282
+ * Create a guard from a simple check function
283
+ *
284
+ * @param checkFn - Function that returns whether the message should be processed
285
+ * @returns MessageGuard instance
286
+ *
287
+ * @example
288
+ * ```typescript
289
+ * const customGuard = createMessageGuard((context) => {
290
+ * const metadata = context.getMetadata();
291
+ * return metadata.headers?.['x-custom-header'] === 'expected-value';
292
+ * });
293
+ *
294
+ * @UseMessageGuards(customGuard)
295
+ * @Subscribe('custom.*')
296
+ * async handleCustomEvent(message: Message<EventData>) {
297
+ * // Custom guard logic
298
+ * }
299
+ * ```
300
+ */
301
+ export function createMessageGuard(
302
+ checkFn: (context: MessageExecutionContext) => boolean | Promise<boolean>,
303
+ ): MessageGuard {
304
+ return {
305
+ canActivate: checkFn,
306
+ };
307
+ }