@onebun/core 0.1.1 → 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.
- package/README.md +233 -0
- package/package.json +1 -1
- package/src/{application.test.ts → application/application.test.ts} +125 -5
- package/src/{application.ts → application/application.ts} +239 -13
- package/src/application/index.ts +9 -0
- package/src/{multi-service-application.test.ts → application/multi-service-application.test.ts} +2 -1
- package/src/{multi-service-application.ts → application/multi-service-application.ts} +2 -1
- package/src/{multi-service.types.ts → application/multi-service.types.ts} +1 -1
- package/src/{decorators.test.ts → decorators/decorators.test.ts} +2 -1
- package/src/{decorators.ts → decorators/decorators.ts} +3 -2
- package/src/decorators/index.ts +15 -0
- package/src/docs-examples.test.ts +753 -0
- package/src/index.ts +50 -41
- package/src/module/index.ts +12 -0
- package/src/{module.test.ts → module/module.test.ts} +3 -2
- package/src/{module.ts → module/module.ts} +15 -8
- package/src/queue/adapters/index.ts +8 -0
- package/src/queue/adapters/memory.adapter.test.ts +405 -0
- package/src/queue/adapters/memory.adapter.ts +509 -0
- package/src/queue/adapters/redis.adapter.ts +673 -0
- package/src/queue/cron-expression.test.ts +145 -0
- package/src/queue/cron-expression.ts +115 -0
- package/src/queue/cron-parser.test.ts +185 -0
- package/src/queue/cron-parser.ts +287 -0
- package/src/queue/decorators.test.ts +292 -0
- package/src/queue/decorators.ts +493 -0
- package/src/queue/docs-examples.test.ts +449 -0
- package/src/queue/guards.test.ts +309 -0
- package/src/queue/guards.ts +307 -0
- package/src/queue/index.ts +118 -0
- package/src/queue/pattern-matcher.test.ts +191 -0
- package/src/queue/pattern-matcher.ts +252 -0
- package/src/queue/queue.service.ts +421 -0
- package/src/queue/scheduler.test.ts +235 -0
- package/src/queue/scheduler.ts +379 -0
- package/src/queue/types.ts +502 -0
- package/src/redis/index.ts +8 -0
- package/src/redis/redis-client.ts +502 -0
- package/src/redis/shared-redis.ts +231 -0
- package/src/{env-resolver.ts → service-client/env-resolver.ts} +1 -1
- package/src/service-client/index.ts +10 -0
- package/src/{service-client.test.ts → service-client/service-client.test.ts} +3 -2
- package/src/{service-client.ts → service-client/service-client.ts} +1 -1
- package/src/{service-definition.test.ts → service-client/service-definition.test.ts} +3 -2
- package/src/{service-definition.ts → service-client/service-definition.ts} +2 -2
- package/src/testing/index.ts +7 -0
- package/src/types.ts +84 -5
- package/src/websocket/index.ts +50 -0
- package/src/websocket/ws-base-gateway.test.ts +479 -0
- package/src/websocket/ws-base-gateway.ts +514 -0
- package/src/websocket/ws-client.test.ts +511 -0
- package/src/websocket/ws-client.ts +628 -0
- package/src/websocket/ws-client.types.ts +129 -0
- package/src/websocket/ws-decorators.test.ts +331 -0
- package/src/websocket/ws-decorators.ts +418 -0
- package/src/websocket/ws-guards.test.ts +334 -0
- package/src/websocket/ws-guards.ts +298 -0
- package/src/websocket/ws-handler.ts +658 -0
- package/src/websocket/ws-integration.test.ts +518 -0
- package/src/websocket/ws-pattern-matcher.test.ts +152 -0
- package/src/websocket/ws-pattern-matcher.ts +240 -0
- package/src/websocket/ws-service-definition.ts +224 -0
- package/src/websocket/ws-socketio-protocol.test.ts +344 -0
- package/src/websocket/ws-socketio-protocol.ts +567 -0
- package/src/websocket/ws-storage-memory.test.ts +246 -0
- package/src/websocket/ws-storage-memory.ts +222 -0
- package/src/websocket/ws-storage-redis.ts +302 -0
- package/src/websocket/ws-storage.ts +210 -0
- package/src/websocket/ws.types.ts +342 -0
- /package/src/{metadata.test.ts → decorators/metadata.test.ts} +0 -0
- /package/src/{metadata.ts → decorators/metadata.ts} +0 -0
- /package/src/{config.service.test.ts → module/config.service.test.ts} +0 -0
- /package/src/{config.service.ts → module/config.service.ts} +0 -0
- /package/src/{controller.test.ts → module/controller.test.ts} +0 -0
- /package/src/{controller.ts → module/controller.ts} +0 -0
- /package/src/{service.test.ts → module/service.test.ts} +0 -0
- /package/src/{service.ts → module/service.ts} +0 -0
- /package/src/{env-resolver.test.ts → service-client/env-resolver.test.ts} +0 -0
- /package/src/{service-client.types.ts → service-client/service-client.types.ts} +0 -0
- /package/src/{test-utils.test.ts → testing/test-utils.test.ts} +0 -0
- /package/src/{test-utils.ts → testing/test-utils.ts} +0 -0
package/src/index.ts
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
// export
|
|
2
|
-
// export * from './types';
|
|
3
|
-
// export * from './config.service';
|
|
4
|
-
// Re-export envs from @onebun/envs
|
|
1
|
+
// Re-export from external packages
|
|
5
2
|
export { Env, type EnvSchema, EnvValidationError } from '@onebun/envs';
|
|
6
3
|
export type { SyncLogger } from '@onebun/logger';
|
|
7
4
|
export {
|
|
@@ -15,51 +12,63 @@ export {
|
|
|
15
12
|
type SuccessResponse,
|
|
16
13
|
} from '@onebun/requests';
|
|
17
14
|
export { Span } from '@onebun/trace';
|
|
15
|
+
|
|
18
16
|
// Re-export Effect and Layer from effect
|
|
19
17
|
export { Effect, Layer } from 'effect';
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
export * from './decorators';
|
|
23
|
-
export { defineMetadata, getMetadata } from './metadata';
|
|
24
|
-
export { OneBunModule } from './module';
|
|
25
|
-
export { BaseService, getServiceTag, Service } from './service';
|
|
26
|
-
export {
|
|
27
|
-
FakeTimers,
|
|
28
|
-
fakeTimers,
|
|
29
|
-
useFakeTimers,
|
|
30
|
-
makeMockLoggerLayer,
|
|
31
|
-
} from './test-utils';
|
|
18
|
+
|
|
19
|
+
// Types (excluding WebSocket types that are re-exported from ./websocket)
|
|
32
20
|
export {
|
|
33
21
|
HttpMethod,
|
|
34
|
-
|
|
35
|
-
type
|
|
22
|
+
ParamType,
|
|
23
|
+
type ServiceInterface,
|
|
24
|
+
type ModuleProviders,
|
|
25
|
+
type ModuleInstance,
|
|
26
|
+
type TypedEnvSchema,
|
|
27
|
+
type ApplicationOptions,
|
|
36
28
|
type ParamMetadata,
|
|
37
29
|
type ResponseSchemaMetadata,
|
|
38
|
-
|
|
30
|
+
type RouteMetadata,
|
|
31
|
+
type ControllerMetadata,
|
|
32
|
+
// WebSocket types are exported from ./websocket
|
|
33
|
+
type WsStorageType,
|
|
34
|
+
type WsStorageOptions,
|
|
35
|
+
type WebSocketApplicationOptions,
|
|
39
36
|
} from './types';
|
|
40
|
-
export * from './validation';
|
|
41
37
|
|
|
42
|
-
//
|
|
43
|
-
export
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
38
|
+
// Decorators and Metadata (exports Controller decorator, Module decorator, etc.)
|
|
39
|
+
export * from './decorators';
|
|
40
|
+
|
|
41
|
+
// Module System - explicitly re-export to avoid Controller conflict
|
|
42
|
+
export {
|
|
43
|
+
OneBunModule,
|
|
44
|
+
Controller as BaseController,
|
|
45
|
+
BaseService,
|
|
46
|
+
Service,
|
|
47
|
+
getServiceMetadata,
|
|
48
|
+
getServiceTag,
|
|
49
|
+
createServiceLayer,
|
|
50
|
+
ConfigServiceImpl,
|
|
51
|
+
ConfigServiceTag,
|
|
52
|
+
ConfigService,
|
|
53
|
+
} from './module';
|
|
54
|
+
|
|
55
|
+
// Application
|
|
56
|
+
export * from './application';
|
|
52
57
|
|
|
53
|
-
// Service
|
|
54
|
-
export
|
|
55
|
-
export type {
|
|
56
|
-
ServiceDefinition,
|
|
57
|
-
EndpointMetadata,
|
|
58
|
-
ControllerDefinition,
|
|
59
|
-
} from './service-definition';
|
|
58
|
+
// Service Client
|
|
59
|
+
export * from './service-client';
|
|
60
60
|
|
|
61
|
-
|
|
62
|
-
export
|
|
61
|
+
// Redis
|
|
62
|
+
export * from './redis';
|
|
63
|
+
|
|
64
|
+
// WebSocket
|
|
65
|
+
export * from './websocket';
|
|
66
|
+
|
|
67
|
+
// Queue System
|
|
68
|
+
export * from './queue';
|
|
69
|
+
|
|
70
|
+
// Validation
|
|
71
|
+
export * from './validation';
|
|
63
72
|
|
|
64
|
-
//
|
|
65
|
-
export
|
|
73
|
+
// Testing Utilities
|
|
74
|
+
export * from './testing';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module System
|
|
3
|
+
*
|
|
4
|
+
* Core module, controller, and service abstractions.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export * from './module';
|
|
8
|
+
export * from './controller';
|
|
9
|
+
// Re-export Controller as BaseController for backward compatibility
|
|
10
|
+
export { Controller as BaseController } from './controller';
|
|
11
|
+
export * from './service';
|
|
12
|
+
export * from './config.service';
|
|
@@ -11,9 +11,10 @@ import {
|
|
|
11
11
|
} from 'bun:test';
|
|
12
12
|
import { Context } from 'effect';
|
|
13
13
|
|
|
14
|
-
import { makeDevLogger } from '
|
|
14
|
+
import { makeDevLogger } from '@onebun/logger';
|
|
15
|
+
|
|
16
|
+
import { Module } from '../decorators/decorators';
|
|
15
17
|
|
|
16
|
-
import { Module } from './decorators';
|
|
17
18
|
import { OneBunModule } from './module';
|
|
18
19
|
import { Service } from './service';
|
|
19
20
|
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
} from 'effect';
|
|
6
6
|
|
|
7
7
|
import type { Controller } from './controller';
|
|
8
|
-
import type {
|
|
8
|
+
import type { ModuleInstance } from '../types';
|
|
9
9
|
|
|
10
10
|
import {
|
|
11
11
|
createSyncLogger,
|
|
@@ -21,13 +21,15 @@ import {
|
|
|
21
21
|
getConstructorParamTypes,
|
|
22
22
|
getModuleMetadata,
|
|
23
23
|
registerControllerDependencies,
|
|
24
|
-
} from '
|
|
24
|
+
} from '../decorators/decorators';
|
|
25
|
+
import { isWebSocketGateway } from '../websocket/ws-decorators';
|
|
26
|
+
|
|
25
27
|
import { getServiceMetadata, getServiceTag } from './service';
|
|
26
28
|
|
|
27
29
|
/**
|
|
28
30
|
* OneBun Module implementation
|
|
29
31
|
*/
|
|
30
|
-
export class OneBunModule implements
|
|
32
|
+
export class OneBunModule implements ModuleInstance {
|
|
31
33
|
private rootLayer: Layer.Layer<never, never, unknown>;
|
|
32
34
|
private controllers: Function[] = [];
|
|
33
35
|
private controllerInstances: Map<Function, Controller> = new Map();
|
|
@@ -331,14 +333,19 @@ export class OneBunModule implements Module {
|
|
|
331
333
|
const controllerConstructor = controllerClass as new (...args: unknown[]) => Controller;
|
|
332
334
|
const controller = new controllerConstructor(...dependencies);
|
|
333
335
|
|
|
334
|
-
// Initialize controller with logger and config
|
|
335
|
-
controller.initializeController
|
|
336
|
+
// Initialize controller with logger and config (skip for WebSocket gateways)
|
|
337
|
+
if (!isWebSocketGateway(controllerClass) && typeof controller.initializeController === 'function') {
|
|
338
|
+
controller.initializeController(this.logger, this.config);
|
|
339
|
+
}
|
|
336
340
|
|
|
337
341
|
this.controllerInstances.set(controllerClass, controller);
|
|
338
342
|
|
|
339
343
|
// Inject all services into controller (for legacy compatibility)
|
|
340
|
-
for
|
|
341
|
-
|
|
344
|
+
// Skip for WebSocket gateways which don't have setService
|
|
345
|
+
if (!isWebSocketGateway(controllerClass) && typeof controller.setService === 'function') {
|
|
346
|
+
for (const [tag, serviceInstance] of this.serviceInstances.entries()) {
|
|
347
|
+
controller.setService(tag, serviceInstance);
|
|
348
|
+
}
|
|
342
349
|
}
|
|
343
350
|
|
|
344
351
|
if (paramTypes && paramTypes.length > 0) {
|
|
@@ -432,7 +439,7 @@ export class OneBunModule implements Module {
|
|
|
432
439
|
moduleClass: Function,
|
|
433
440
|
loggerLayer?: Layer.Layer<never, never, unknown>,
|
|
434
441
|
config?: unknown,
|
|
435
|
-
):
|
|
442
|
+
): ModuleInstance {
|
|
436
443
|
// Using console.log here because we don't have access to the logger instance yet
|
|
437
444
|
// The instance will create its own logger in the constructor
|
|
438
445
|
return new OneBunModule(moduleClass, loggerLayer, config);
|
|
@@ -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
|
+
});
|