@onebun/core 0.1.2 → 0.1.4
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/package.json +6 -6
- package/src/{application.test.ts → application/application.test.ts} +6 -5
- package/src/{application.ts → application/application.ts} +131 -12
- 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/index.ts +47 -134
- 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} +6 -5
- 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/{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 +34 -5
- package/src/websocket/index.ts +50 -0
- package/src/{ws-decorators.ts → websocket/ws-decorators.ts} +2 -1
- package/src/{ws-integration.test.ts → websocket/ws-integration.test.ts} +3 -2
- package/src/{ws-service-definition.ts → websocket/ws-service-definition.ts} +2 -1
- package/src/{ws-storage-redis.ts → websocket/ws-storage-redis.ts} +1 -1
- /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/{redis-client.ts → redis/redis-client.ts} +0 -0
- /package/src/{shared-redis.ts → redis/shared-redis.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/{ws-base-gateway.test.ts → websocket/ws-base-gateway.test.ts} +0 -0
- /package/src/{ws-base-gateway.ts → websocket/ws-base-gateway.ts} +0 -0
- /package/src/{ws-client.test.ts → websocket/ws-client.test.ts} +0 -0
- /package/src/{ws-client.ts → websocket/ws-client.ts} +0 -0
- /package/src/{ws-client.types.ts → websocket/ws-client.types.ts} +0 -0
- /package/src/{ws-decorators.test.ts → websocket/ws-decorators.test.ts} +0 -0
- /package/src/{ws-guards.test.ts → websocket/ws-guards.test.ts} +0 -0
- /package/src/{ws-guards.ts → websocket/ws-guards.ts} +0 -0
- /package/src/{ws-handler.ts → websocket/ws-handler.ts} +0 -0
- /package/src/{ws-pattern-matcher.test.ts → websocket/ws-pattern-matcher.test.ts} +0 -0
- /package/src/{ws-pattern-matcher.ts → websocket/ws-pattern-matcher.ts} +0 -0
- /package/src/{ws-socketio-protocol.test.ts → websocket/ws-socketio-protocol.test.ts} +0 -0
- /package/src/{ws-socketio-protocol.ts → websocket/ws-socketio-protocol.ts} +0 -0
- /package/src/{ws-storage-memory.test.ts → websocket/ws-storage-memory.test.ts} +0 -0
- /package/src/{ws-storage-memory.ts → websocket/ws-storage-memory.ts} +0 -0
- /package/src/{ws-storage.ts → websocket/ws-storage.ts} +0 -0
- /package/src/{ws.types.ts → websocket/ws.types.ts} +0 -0
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-Memory Queue Adapter
|
|
3
|
+
*
|
|
4
|
+
* A simple in-process message bus. Useful for development, testing,
|
|
5
|
+
* and single-instance deployments where external dependencies are not needed.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
QueueAdapter,
|
|
10
|
+
QueueAdapterType,
|
|
11
|
+
QueueFeature,
|
|
12
|
+
QueueEvents,
|
|
13
|
+
Message,
|
|
14
|
+
MessageMetadata,
|
|
15
|
+
PublishOptions,
|
|
16
|
+
SubscribeOptions,
|
|
17
|
+
Subscription,
|
|
18
|
+
ScheduledJobOptions,
|
|
19
|
+
ScheduledJobInfo,
|
|
20
|
+
MessageHandler,
|
|
21
|
+
} from '../types';
|
|
22
|
+
|
|
23
|
+
import { createQueuePatternMatcher, type QueuePatternMatch } from '../pattern-matcher';
|
|
24
|
+
import { QueueScheduler } from '../scheduler';
|
|
25
|
+
|
|
26
|
+
// ============================================================================
|
|
27
|
+
// Types
|
|
28
|
+
// ============================================================================
|
|
29
|
+
|
|
30
|
+
interface SubscriptionEntry {
|
|
31
|
+
pattern: string;
|
|
32
|
+
handler: MessageHandler;
|
|
33
|
+
options?: SubscribeOptions;
|
|
34
|
+
matcher: (topic: string) => QueuePatternMatch;
|
|
35
|
+
paused: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface DelayedMessage {
|
|
39
|
+
pattern: string;
|
|
40
|
+
data: unknown;
|
|
41
|
+
options?: PublishOptions;
|
|
42
|
+
executeAt: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ============================================================================
|
|
46
|
+
// In-Memory Message Implementation
|
|
47
|
+
// ============================================================================
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* In-memory message implementation
|
|
51
|
+
*/
|
|
52
|
+
class InMemoryMessage<T> implements Message<T> {
|
|
53
|
+
id: string;
|
|
54
|
+
pattern: string;
|
|
55
|
+
data: T;
|
|
56
|
+
timestamp: number;
|
|
57
|
+
redelivered: boolean;
|
|
58
|
+
metadata: MessageMetadata;
|
|
59
|
+
attempt?: number;
|
|
60
|
+
maxAttempts?: number;
|
|
61
|
+
|
|
62
|
+
private acked = false;
|
|
63
|
+
private nacked = false;
|
|
64
|
+
private onAck?: () => void;
|
|
65
|
+
private onNack?: (requeue: boolean) => void;
|
|
66
|
+
|
|
67
|
+
constructor(
|
|
68
|
+
id: string,
|
|
69
|
+
pattern: string,
|
|
70
|
+
data: T,
|
|
71
|
+
metadata: MessageMetadata,
|
|
72
|
+
options?: {
|
|
73
|
+
redelivered?: boolean;
|
|
74
|
+
attempt?: number;
|
|
75
|
+
maxAttempts?: number;
|
|
76
|
+
onAck?: () => void;
|
|
77
|
+
onNack?: (requeue: boolean) => void;
|
|
78
|
+
},
|
|
79
|
+
) {
|
|
80
|
+
this.id = id;
|
|
81
|
+
this.pattern = pattern;
|
|
82
|
+
this.data = data;
|
|
83
|
+
this.timestamp = Date.now();
|
|
84
|
+
this.metadata = metadata;
|
|
85
|
+
this.redelivered = options?.redelivered ?? false;
|
|
86
|
+
this.attempt = options?.attempt;
|
|
87
|
+
this.maxAttempts = options?.maxAttempts;
|
|
88
|
+
this.onAck = options?.onAck;
|
|
89
|
+
this.onNack = options?.onNack;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async ack(): Promise<void> {
|
|
93
|
+
if (this.acked || this.nacked) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
this.acked = true;
|
|
97
|
+
this.onAck?.();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
async nack(requeue = false): Promise<void> {
|
|
101
|
+
if (this.acked || this.nacked) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
this.nacked = true;
|
|
105
|
+
this.onNack?.(requeue);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// ============================================================================
|
|
110
|
+
// In-Memory Subscription Implementation
|
|
111
|
+
// ============================================================================
|
|
112
|
+
|
|
113
|
+
class InMemorySubscription implements Subscription {
|
|
114
|
+
private active = true;
|
|
115
|
+
|
|
116
|
+
constructor(
|
|
117
|
+
private readonly entry: SubscriptionEntry,
|
|
118
|
+
private readonly onUnsubscribe: () => void,
|
|
119
|
+
) {}
|
|
120
|
+
|
|
121
|
+
async unsubscribe(): Promise<void> {
|
|
122
|
+
this.active = false;
|
|
123
|
+
this.onUnsubscribe();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
pause(): void {
|
|
127
|
+
this.entry.paused = true;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
resume(): void {
|
|
131
|
+
this.entry.paused = false;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
get pattern(): string {
|
|
135
|
+
return this.entry.pattern;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
get isActive(): boolean {
|
|
139
|
+
return this.active && !this.entry.paused;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ============================================================================
|
|
144
|
+
// In-Memory Queue Adapter
|
|
145
|
+
// ============================================================================
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* In-Memory Queue Adapter
|
|
149
|
+
*
|
|
150
|
+
* Implements a simple in-process message bus.
|
|
151
|
+
*
|
|
152
|
+
* Supported features:
|
|
153
|
+
* - Pattern subscriptions with wildcards
|
|
154
|
+
* - Delayed messages
|
|
155
|
+
* - Priority (via sorting)
|
|
156
|
+
* - Scheduled jobs
|
|
157
|
+
*
|
|
158
|
+
* Not supported (requires external storage):
|
|
159
|
+
* - Consumer groups (all handlers receive all messages)
|
|
160
|
+
* - Dead letter queues
|
|
161
|
+
* - Retry with persistence
|
|
162
|
+
*
|
|
163
|
+
* @example
|
|
164
|
+
* ```typescript
|
|
165
|
+
* const adapter = new InMemoryQueueAdapter();
|
|
166
|
+
* await adapter.connect();
|
|
167
|
+
*
|
|
168
|
+
* await adapter.subscribe('orders.*', async (message) => {
|
|
169
|
+
* console.log('Received:', message.data);
|
|
170
|
+
* });
|
|
171
|
+
*
|
|
172
|
+
* await adapter.publish('orders.created', { orderId: 123 });
|
|
173
|
+
* ```
|
|
174
|
+
*/
|
|
175
|
+
export class InMemoryQueueAdapter implements QueueAdapter {
|
|
176
|
+
readonly name = 'memory';
|
|
177
|
+
readonly type: QueueAdapterType = 'memory';
|
|
178
|
+
|
|
179
|
+
private subscriptions: SubscriptionEntry[] = [];
|
|
180
|
+
private delayedMessages: DelayedMessage[] = [];
|
|
181
|
+
private messageIdCounter = 0;
|
|
182
|
+
private connected = false;
|
|
183
|
+
private scheduler: QueueScheduler | null = null;
|
|
184
|
+
private delayedInterval?: ReturnType<typeof setInterval>;
|
|
185
|
+
|
|
186
|
+
// Event handlers
|
|
187
|
+
private eventHandlers: Map<keyof QueueEvents, Set<(...args: unknown[]) => void>> = new Map();
|
|
188
|
+
|
|
189
|
+
// ============================================================================
|
|
190
|
+
// Lifecycle
|
|
191
|
+
// ============================================================================
|
|
192
|
+
|
|
193
|
+
async connect(): Promise<void> {
|
|
194
|
+
if (this.connected) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
this.connected = true;
|
|
199
|
+
this.scheduler = new QueueScheduler(this);
|
|
200
|
+
|
|
201
|
+
// Start delayed message processor
|
|
202
|
+
this.delayedInterval = setInterval(() => this.processDelayedMessages(), 100);
|
|
203
|
+
|
|
204
|
+
// Emit ready event
|
|
205
|
+
this.emit('onReady');
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
async disconnect(): Promise<void> {
|
|
209
|
+
if (!this.connected) {
|
|
210
|
+
return;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Stop scheduler
|
|
214
|
+
if (this.scheduler) {
|
|
215
|
+
this.scheduler.stop();
|
|
216
|
+
this.scheduler = null;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Clear delayed interval
|
|
220
|
+
if (this.delayedInterval) {
|
|
221
|
+
clearInterval(this.delayedInterval);
|
|
222
|
+
this.delayedInterval = undefined;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Clear all subscriptions
|
|
226
|
+
this.subscriptions = [];
|
|
227
|
+
this.delayedMessages = [];
|
|
228
|
+
|
|
229
|
+
this.connected = false;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
isConnected(): boolean {
|
|
233
|
+
return this.connected;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ============================================================================
|
|
237
|
+
// Publishing
|
|
238
|
+
// ============================================================================
|
|
239
|
+
|
|
240
|
+
async publish<T>(pattern: string, data: T, options?: PublishOptions): Promise<string> {
|
|
241
|
+
this.ensureConnected();
|
|
242
|
+
|
|
243
|
+
const messageId = options?.messageId ?? this.generateMessageId();
|
|
244
|
+
|
|
245
|
+
// Handle delayed messages
|
|
246
|
+
if (options?.delay && options.delay > 0) {
|
|
247
|
+
this.delayedMessages.push({
|
|
248
|
+
pattern,
|
|
249
|
+
data,
|
|
250
|
+
options: { ...options, messageId },
|
|
251
|
+
executeAt: Date.now() + options.delay,
|
|
252
|
+
});
|
|
253
|
+
// Sort by priority (higher first) and then by time
|
|
254
|
+
this.delayedMessages.sort((a, b) => {
|
|
255
|
+
const priorityA = a.options?.priority ?? 0;
|
|
256
|
+
const priorityB = b.options?.priority ?? 0;
|
|
257
|
+
if (priorityA !== priorityB) {
|
|
258
|
+
return priorityB - priorityA; // Higher priority first
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return a.executeAt - b.executeAt;
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
return messageId;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Dispatch immediately
|
|
268
|
+
await this.dispatch(pattern, data, messageId, options?.metadata);
|
|
269
|
+
|
|
270
|
+
return messageId;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async publishBatch<T>(
|
|
274
|
+
messages: Array<{ pattern: string; data: T; options?: PublishOptions }>,
|
|
275
|
+
): Promise<string[]> {
|
|
276
|
+
const ids: string[] = [];
|
|
277
|
+
|
|
278
|
+
// Sort by priority if any
|
|
279
|
+
const sorted = [...messages].sort((a, b) => {
|
|
280
|
+
const priorityA = a.options?.priority ?? 0;
|
|
281
|
+
const priorityB = b.options?.priority ?? 0;
|
|
282
|
+
|
|
283
|
+
return priorityB - priorityA;
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
for (const msg of sorted) {
|
|
287
|
+
const id = await this.publish(msg.pattern, msg.data, msg.options);
|
|
288
|
+
ids.push(id);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
return ids;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// ============================================================================
|
|
295
|
+
// Subscribing
|
|
296
|
+
// ============================================================================
|
|
297
|
+
|
|
298
|
+
async subscribe<T>(
|
|
299
|
+
pattern: string,
|
|
300
|
+
handler: MessageHandler<T>,
|
|
301
|
+
options?: SubscribeOptions,
|
|
302
|
+
): Promise<Subscription> {
|
|
303
|
+
this.ensureConnected();
|
|
304
|
+
|
|
305
|
+
const entry: SubscriptionEntry = {
|
|
306
|
+
pattern,
|
|
307
|
+
handler: handler as MessageHandler,
|
|
308
|
+
options,
|
|
309
|
+
matcher: createQueuePatternMatcher(pattern),
|
|
310
|
+
paused: false,
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
this.subscriptions.push(entry);
|
|
314
|
+
|
|
315
|
+
const subscription = new InMemorySubscription(entry, () => {
|
|
316
|
+
const index = this.subscriptions.indexOf(entry);
|
|
317
|
+
if (index !== -1) {
|
|
318
|
+
this.subscriptions.splice(index, 1);
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
return subscription;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// ============================================================================
|
|
326
|
+
// Scheduled Jobs
|
|
327
|
+
// ============================================================================
|
|
328
|
+
|
|
329
|
+
async addScheduledJob(name: string, options: ScheduledJobOptions): Promise<void> {
|
|
330
|
+
this.ensureConnected();
|
|
331
|
+
|
|
332
|
+
if (!this.scheduler) {
|
|
333
|
+
throw new Error('Scheduler not initialized');
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (options.schedule.cron) {
|
|
337
|
+
this.scheduler.addCronJob(name, options.schedule.cron, options.pattern, () => options.data, {
|
|
338
|
+
metadata: options.metadata,
|
|
339
|
+
overlapStrategy: options.overlapStrategy,
|
|
340
|
+
});
|
|
341
|
+
} else if (options.schedule.every) {
|
|
342
|
+
this.scheduler.addIntervalJob(name, options.schedule.every, options.pattern, () => options.data, {
|
|
343
|
+
metadata: options.metadata,
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
async removeScheduledJob(name: string): Promise<boolean> {
|
|
349
|
+
this.ensureConnected();
|
|
350
|
+
|
|
351
|
+
if (!this.scheduler) {
|
|
352
|
+
return false;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return this.scheduler.removeJob(name);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
async getScheduledJobs(): Promise<ScheduledJobInfo[]> {
|
|
359
|
+
this.ensureConnected();
|
|
360
|
+
|
|
361
|
+
if (!this.scheduler) {
|
|
362
|
+
return [];
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
return this.scheduler.getJobs();
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// ============================================================================
|
|
369
|
+
// Features
|
|
370
|
+
// ============================================================================
|
|
371
|
+
|
|
372
|
+
supports(feature: QueueFeature): boolean {
|
|
373
|
+
switch (feature) {
|
|
374
|
+
case 'delayed-messages':
|
|
375
|
+
case 'priority':
|
|
376
|
+
case 'scheduled-jobs':
|
|
377
|
+
case 'pattern-subscriptions':
|
|
378
|
+
return true;
|
|
379
|
+
case 'consumer-groups':
|
|
380
|
+
case 'dead-letter-queue':
|
|
381
|
+
case 'retry':
|
|
382
|
+
return false;
|
|
383
|
+
default:
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// ============================================================================
|
|
389
|
+
// Events
|
|
390
|
+
// ============================================================================
|
|
391
|
+
|
|
392
|
+
on<E extends keyof QueueEvents>(event: E, handler: NonNullable<QueueEvents[E]>): void {
|
|
393
|
+
if (!this.eventHandlers.has(event)) {
|
|
394
|
+
this.eventHandlers.set(event, new Set());
|
|
395
|
+
}
|
|
396
|
+
this.eventHandlers.get(event)!.add(handler as (...args: unknown[]) => void);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
off<E extends keyof QueueEvents>(event: E, handler: NonNullable<QueueEvents[E]>): void {
|
|
400
|
+
const handlers = this.eventHandlers.get(event);
|
|
401
|
+
if (handlers) {
|
|
402
|
+
handlers.delete(handler as (...args: unknown[]) => void);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// ============================================================================
|
|
407
|
+
// Private Methods
|
|
408
|
+
// ============================================================================
|
|
409
|
+
|
|
410
|
+
private ensureConnected(): void {
|
|
411
|
+
if (!this.connected) {
|
|
412
|
+
throw new Error('InMemoryQueueAdapter not connected. Call connect() first.');
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
private generateMessageId(): string {
|
|
417
|
+
return `msg-${++this.messageIdCounter}-${Date.now()}`;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
private async dispatch<T>(
|
|
421
|
+
pattern: string,
|
|
422
|
+
data: T,
|
|
423
|
+
messageId: string,
|
|
424
|
+
metadata?: Partial<MessageMetadata>,
|
|
425
|
+
): Promise<void> {
|
|
426
|
+
const fullMetadata: MessageMetadata = {
|
|
427
|
+
headers: {},
|
|
428
|
+
...metadata,
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
// Find all matching subscriptions
|
|
432
|
+
for (const entry of this.subscriptions) {
|
|
433
|
+
if (entry.paused) {
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
const match = entry.matcher(pattern);
|
|
438
|
+
if (!match.matched) {
|
|
439
|
+
continue;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const message = new InMemoryMessage<T>(messageId, pattern, data, fullMetadata, {
|
|
443
|
+
onNack: (requeue) => {
|
|
444
|
+
if (requeue) {
|
|
445
|
+
// Re-dispatch the message
|
|
446
|
+
setImmediate(() => {
|
|
447
|
+
this.dispatch(pattern, data, messageId, metadata);
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
},
|
|
451
|
+
});
|
|
452
|
+
|
|
453
|
+
// Emit received event
|
|
454
|
+
this.emit('onMessageReceived', message);
|
|
455
|
+
|
|
456
|
+
try {
|
|
457
|
+
await entry.handler(message);
|
|
458
|
+
|
|
459
|
+
// Auto-ack if not manual mode
|
|
460
|
+
if (entry.options?.ackMode !== 'manual') {
|
|
461
|
+
await message.ack();
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Emit processed event
|
|
465
|
+
this.emit('onMessageProcessed', message);
|
|
466
|
+
} catch (error) {
|
|
467
|
+
// Emit failed event
|
|
468
|
+
this.emit('onMessageFailed', message, error as Error);
|
|
469
|
+
|
|
470
|
+
// Auto-nack if not manual mode
|
|
471
|
+
if (entry.options?.ackMode !== 'manual') {
|
|
472
|
+
await message.nack(false);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
private processDelayedMessages(): void {
|
|
479
|
+
const now = Date.now();
|
|
480
|
+
|
|
481
|
+
while (this.delayedMessages.length > 0 && this.delayedMessages[0].executeAt <= now) {
|
|
482
|
+
const delayed = this.delayedMessages.shift()!;
|
|
483
|
+
const messageId = delayed.options?.messageId ?? this.generateMessageId();
|
|
484
|
+
|
|
485
|
+
// Dispatch without delay
|
|
486
|
+
this.dispatch(delayed.pattern, delayed.data, messageId, delayed.options?.metadata);
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
private emit<E extends keyof QueueEvents>(event: E, ...args: unknown[]): void {
|
|
491
|
+
const handlers = this.eventHandlers.get(event);
|
|
492
|
+
if (handlers) {
|
|
493
|
+
for (const handler of handlers) {
|
|
494
|
+
try {
|
|
495
|
+
handler(...args);
|
|
496
|
+
} catch {
|
|
497
|
+
// Silently ignore event handler errors
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Create an in-memory queue adapter
|
|
506
|
+
*/
|
|
507
|
+
export function createInMemoryQueueAdapter(): InMemoryQueueAdapter {
|
|
508
|
+
return new InMemoryQueueAdapter();
|
|
509
|
+
}
|