@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,405 @@
1
+ /**
2
+ * In-Memory Queue Adapter Tests
3
+ */
4
+
5
+ import {
6
+ describe,
7
+ it,
8
+ expect,
9
+ beforeEach,
10
+ afterEach,
11
+ } from 'bun:test';
12
+
13
+ import type { Message } from '../types';
14
+
15
+ import { InMemoryQueueAdapter, createInMemoryQueueAdapter } from './memory.adapter';
16
+
17
+ describe('InMemoryQueueAdapter', () => {
18
+ let adapter: InMemoryQueueAdapter;
19
+
20
+ beforeEach(() => {
21
+ adapter = new InMemoryQueueAdapter();
22
+ });
23
+
24
+ afterEach(async () => {
25
+ await adapter.disconnect();
26
+ });
27
+
28
+ describe('lifecycle', () => {
29
+ it('should connect successfully', async () => {
30
+ expect(adapter.isConnected()).toBe(false);
31
+ await adapter.connect();
32
+ expect(adapter.isConnected()).toBe(true);
33
+ });
34
+
35
+ it('should disconnect successfully', async () => {
36
+ await adapter.connect();
37
+ expect(adapter.isConnected()).toBe(true);
38
+ await adapter.disconnect();
39
+ expect(adapter.isConnected()).toBe(false);
40
+ });
41
+
42
+ it('should handle multiple connect calls', async () => {
43
+ await adapter.connect();
44
+ await adapter.connect();
45
+ expect(adapter.isConnected()).toBe(true);
46
+ });
47
+
48
+ it('should have correct name and type', () => {
49
+ expect(adapter.name).toBe('memory');
50
+ expect(adapter.type).toBe('memory');
51
+ });
52
+ });
53
+
54
+ describe('publish/subscribe', () => {
55
+ it('should publish and receive messages', async () => {
56
+ await adapter.connect();
57
+
58
+ const received: Message[] = [];
59
+ await adapter.subscribe('orders.created', async (message) => {
60
+ received.push(message);
61
+ });
62
+
63
+ await adapter.publish('orders.created', { orderId: 123 });
64
+
65
+ expect(received.length).toBe(1);
66
+ expect(received[0].data).toEqual({ orderId: 123 });
67
+ expect(received[0].pattern).toBe('orders.created');
68
+ });
69
+
70
+ it('should match wildcard patterns', async () => {
71
+ await adapter.connect();
72
+
73
+ const received: Message[] = [];
74
+ await adapter.subscribe('orders.*', async (message) => {
75
+ received.push(message);
76
+ });
77
+
78
+ await adapter.publish('orders.created', { type: 'created' });
79
+ await adapter.publish('orders.updated', { type: 'updated' });
80
+ await adapter.publish('users.created', { type: 'user' }); // Should not match
81
+
82
+ expect(received.length).toBe(2);
83
+ expect(received[0].data).toEqual({ type: 'created' });
84
+ expect(received[1].data).toEqual({ type: 'updated' });
85
+ });
86
+
87
+ it('should match multi-level wildcard (#)', async () => {
88
+ await adapter.connect();
89
+
90
+ const received: Message[] = [];
91
+ await adapter.subscribe('events.#', async (message) => {
92
+ received.push(message);
93
+ });
94
+
95
+ await adapter.publish('events.user.created', { type: 'user' });
96
+ await adapter.publish('events.order.payment.completed', { type: 'payment' });
97
+
98
+ expect(received.length).toBe(2);
99
+ });
100
+
101
+ it('should return message ID on publish', async () => {
102
+ await adapter.connect();
103
+
104
+ const messageId = await adapter.publish('test', { data: 'test' });
105
+ expect(messageId).toMatch(/^msg-\d+-\d+$/);
106
+ });
107
+
108
+ it('should use custom message ID when provided', async () => {
109
+ await adapter.connect();
110
+
111
+ const messageId = await adapter.publish('test', { data: 'test' }, { messageId: 'custom-id' });
112
+ expect(messageId).toBe('custom-id');
113
+ });
114
+ });
115
+
116
+ describe('delayed messages', () => {
117
+ it('should delay message delivery', async () => {
118
+ await adapter.connect();
119
+
120
+ const received: Message[] = [];
121
+ await adapter.subscribe('delayed', async (message) => {
122
+ received.push(message);
123
+ });
124
+
125
+ await adapter.publish('delayed', { data: 'delayed' }, { delay: 50 });
126
+
127
+ // Message should not be received immediately
128
+ expect(received.length).toBe(0);
129
+
130
+ // Wait for delay + processing
131
+ await new Promise((resolve) => setTimeout(resolve, 200));
132
+
133
+ expect(received.length).toBe(1);
134
+ expect(received[0].data).toEqual({ data: 'delayed' });
135
+ });
136
+ });
137
+
138
+ describe('subscription management', () => {
139
+ it('should unsubscribe successfully', async () => {
140
+ await adapter.connect();
141
+
142
+ const received: Message[] = [];
143
+ const subscription = await adapter.subscribe('test', async (message) => {
144
+ received.push(message);
145
+ });
146
+
147
+ await adapter.publish('test', { count: 1 });
148
+ expect(received.length).toBe(1);
149
+
150
+ await subscription.unsubscribe();
151
+
152
+ await adapter.publish('test', { count: 2 });
153
+ expect(received.length).toBe(1); // Still 1, no new messages
154
+ });
155
+
156
+ it('should pause and resume subscription', async () => {
157
+ await adapter.connect();
158
+
159
+ const received: Message[] = [];
160
+ const subscription = await adapter.subscribe('test', async (message) => {
161
+ received.push(message);
162
+ });
163
+
164
+ await adapter.publish('test', { count: 1 });
165
+ expect(received.length).toBe(1);
166
+
167
+ subscription.pause();
168
+
169
+ await adapter.publish('test', { count: 2 });
170
+ expect(received.length).toBe(1); // Paused
171
+
172
+ subscription.resume();
173
+
174
+ await adapter.publish('test', { count: 3 });
175
+ expect(received.length).toBe(2);
176
+ });
177
+
178
+ it('should report subscription state', async () => {
179
+ await adapter.connect();
180
+
181
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
182
+ const subscription = await adapter.subscribe('test', async () => {});
183
+
184
+ expect(subscription.isActive).toBe(true);
185
+ expect(subscription.pattern).toBe('test');
186
+
187
+ subscription.pause();
188
+ expect(subscription.isActive).toBe(false);
189
+
190
+ subscription.resume();
191
+ expect(subscription.isActive).toBe(true);
192
+
193
+ await subscription.unsubscribe();
194
+ expect(subscription.isActive).toBe(false);
195
+ });
196
+ });
197
+
198
+ describe('batch publishing', () => {
199
+ it('should publish multiple messages', async () => {
200
+ await adapter.connect();
201
+
202
+ const received: Message[] = [];
203
+ await adapter.subscribe('batch.*', async (message) => {
204
+ received.push(message);
205
+ });
206
+
207
+ const ids = await adapter.publishBatch([
208
+ { pattern: 'batch.1', data: { index: 1 } },
209
+ { pattern: 'batch.2', data: { index: 2 } },
210
+ { pattern: 'batch.3', data: { index: 3 } },
211
+ ]);
212
+
213
+ expect(ids.length).toBe(3);
214
+ expect(received.length).toBe(3);
215
+ });
216
+ });
217
+
218
+ describe('message acknowledgment', () => {
219
+ it('should auto-ack by default', async () => {
220
+ await adapter.connect();
221
+
222
+ let messageRef: Message | null = null;
223
+ await adapter.subscribe('test', async (message) => {
224
+ messageRef = message;
225
+ });
226
+
227
+ await adapter.publish('test', { data: 'test' });
228
+
229
+ expect(messageRef).not.toBeNull();
230
+ // In auto mode, ack is called automatically
231
+ });
232
+
233
+ it('should support manual ack mode', async () => {
234
+ await adapter.connect();
235
+
236
+ let messageRef: Message | null = null;
237
+ await adapter.subscribe(
238
+ 'test',
239
+ async (message) => {
240
+ messageRef = message;
241
+ // In manual mode, we need to ack
242
+ await message.ack();
243
+ },
244
+ { ackMode: 'manual' },
245
+ );
246
+
247
+ await adapter.publish('test', { data: 'test' });
248
+
249
+ expect(messageRef).not.toBeNull();
250
+ });
251
+
252
+ it('should support nack with requeue', async () => {
253
+ await adapter.connect();
254
+
255
+ let callCount = 0;
256
+ await adapter.subscribe(
257
+ 'test',
258
+ async (message) => {
259
+ callCount++;
260
+ if (callCount === 1) {
261
+ await message.nack(true); // Requeue
262
+ } else {
263
+ await message.ack();
264
+ }
265
+ },
266
+ { ackMode: 'manual' },
267
+ );
268
+
269
+ await adapter.publish('test', { data: 'test' });
270
+
271
+ // Wait for requeue processing (setImmediate is used for requeue)
272
+ await new Promise((resolve) => setTimeout(resolve, 50));
273
+
274
+ expect(callCount).toBeGreaterThanOrEqual(2); // Should be called at least twice due to requeue
275
+ });
276
+ });
277
+
278
+ describe('events', () => {
279
+ it('should emit onReady event on connect', async () => {
280
+ let readyEmitted = false;
281
+ adapter.on('onReady', () => {
282
+ readyEmitted = true;
283
+ });
284
+
285
+ await adapter.connect();
286
+ expect(readyEmitted).toBe(true);
287
+ });
288
+
289
+ it('should emit onMessageReceived event', async () => {
290
+ await adapter.connect();
291
+
292
+ let receivedMessage: Message | null = null;
293
+ adapter.on('onMessageReceived', (message) => {
294
+ receivedMessage = message as Message;
295
+ });
296
+
297
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
298
+ await adapter.subscribe('test', async () => {});
299
+ await adapter.publish('test', { data: 'test' });
300
+
301
+ expect(receivedMessage).not.toBeNull();
302
+ expect(receivedMessage!.pattern).toBe('test');
303
+ });
304
+
305
+ it('should emit onMessageProcessed event', async () => {
306
+ await adapter.connect();
307
+
308
+ let processed = false;
309
+ adapter.on('onMessageProcessed', () => {
310
+ processed = true;
311
+ });
312
+
313
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
314
+ await adapter.subscribe('test', async () => {});
315
+ await adapter.publish('test', { data: 'test' });
316
+
317
+ expect(processed).toBe(true);
318
+ });
319
+
320
+ it('should emit onMessageFailed event on error', async () => {
321
+ await adapter.connect();
322
+
323
+ let failedMessage: Message | null = null;
324
+ let failedError: Error | null = null;
325
+ adapter.on('onMessageFailed', (message, error) => {
326
+ failedMessage = message as Message;
327
+ failedError = error as Error;
328
+ });
329
+
330
+ await adapter.subscribe('test', async () => {
331
+ throw new Error('Handler error');
332
+ });
333
+ await adapter.publish('test', { data: 'test' });
334
+
335
+ expect(failedMessage).not.toBeNull();
336
+ expect(failedError).not.toBeNull();
337
+ expect(failedError!.message).toBe('Handler error');
338
+ });
339
+
340
+ it('should unregister event handlers', async () => {
341
+ let callCount = 0;
342
+ const handler = () => {
343
+ callCount++;
344
+ };
345
+
346
+ adapter.on('onReady', handler);
347
+ await adapter.connect();
348
+ expect(callCount).toBe(1);
349
+
350
+ await adapter.disconnect();
351
+ adapter.off('onReady', handler);
352
+
353
+ await adapter.connect();
354
+ expect(callCount).toBe(1); // Should still be 1
355
+ });
356
+ });
357
+
358
+ describe('feature support', () => {
359
+ it('should support pattern-subscriptions', () => {
360
+ expect(adapter.supports('pattern-subscriptions')).toBe(true);
361
+ });
362
+
363
+ it('should support delayed-messages', () => {
364
+ expect(adapter.supports('delayed-messages')).toBe(true);
365
+ });
366
+
367
+ it('should support priority', () => {
368
+ expect(adapter.supports('priority')).toBe(true);
369
+ });
370
+
371
+ it('should support scheduled-jobs', () => {
372
+ expect(adapter.supports('scheduled-jobs')).toBe(true);
373
+ });
374
+
375
+ it('should not support consumer-groups', () => {
376
+ expect(adapter.supports('consumer-groups')).toBe(false);
377
+ });
378
+
379
+ it('should not support dead-letter-queue', () => {
380
+ expect(adapter.supports('dead-letter-queue')).toBe(false);
381
+ });
382
+
383
+ it('should not support retry', () => {
384
+ expect(adapter.supports('retry')).toBe(false);
385
+ });
386
+ });
387
+
388
+ describe('error handling', () => {
389
+ it('should throw when publishing without connecting', async () => {
390
+ await expect(adapter.publish('test', { data: 'test' })).rejects.toThrow();
391
+ });
392
+
393
+ it('should throw when subscribing without connecting', async () => {
394
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
395
+ await expect(adapter.subscribe('test', async () => {})).rejects.toThrow();
396
+ });
397
+ });
398
+
399
+ describe('createInMemoryQueueAdapter', () => {
400
+ it('should create adapter instance', () => {
401
+ const created = createInMemoryQueueAdapter();
402
+ expect(created).toBeInstanceOf(InMemoryQueueAdapter);
403
+ });
404
+ });
405
+ });