@ooneex/pub-sub 0.0.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/LICENSE +21 -0
- package/README.md +648 -0
- package/dist/index.d.ts +78 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +13 -0
- package/package.json +47 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Ooneex
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,648 @@
|
|
|
1
|
+
# @ooneex/pub-sub
|
|
2
|
+
|
|
3
|
+
A publish-subscribe messaging pattern implementation for TypeScript applications with Redis support. This package provides an abstract base class for creating pub/sub handlers and a Redis client for distributed messaging across application components.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+

|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
✅ **Redis Integration** - Built-in Redis pub/sub client for distributed messaging
|
|
14
|
+
|
|
15
|
+
✅ **Abstract Base Class** - Create custom pub/sub handlers with consistent interface
|
|
16
|
+
|
|
17
|
+
✅ **Channel Management** - Subscribe, unsubscribe, and publish to channels
|
|
18
|
+
|
|
19
|
+
✅ **Type-Safe** - Full TypeScript support with generic data types
|
|
20
|
+
|
|
21
|
+
✅ **Container Integration** - Works seamlessly with dependency injection
|
|
22
|
+
|
|
23
|
+
✅ **Auto Reconnection** - Redis client with automatic reconnection support
|
|
24
|
+
|
|
25
|
+
✅ **Key Support** - Optional message keys for routing and filtering
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
### Bun
|
|
30
|
+
```bash
|
|
31
|
+
bun add @ooneex/pub-sub
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### pnpm
|
|
35
|
+
```bash
|
|
36
|
+
pnpm add @ooneex/pub-sub
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Yarn
|
|
40
|
+
```bash
|
|
41
|
+
yarn add @ooneex/pub-sub
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### npm
|
|
45
|
+
```bash
|
|
46
|
+
npm install @ooneex/pub-sub
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Usage
|
|
50
|
+
|
|
51
|
+
### Basic Pub/Sub Handler
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
import { PubSub, RedisPubSub } from '@ooneex/pub-sub';
|
|
55
|
+
import type { ScalarType } from '@ooneex/types';
|
|
56
|
+
|
|
57
|
+
interface NotificationData extends Record<string, ScalarType> {
|
|
58
|
+
userId: string;
|
|
59
|
+
title: string;
|
|
60
|
+
body: string;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
class NotificationPubSub extends PubSub<NotificationData> {
|
|
64
|
+
public getChannel(): string {
|
|
65
|
+
return 'notifications';
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
public async handler(context: {
|
|
69
|
+
data: NotificationData;
|
|
70
|
+
channel: string;
|
|
71
|
+
key?: string
|
|
72
|
+
}): Promise<void> {
|
|
73
|
+
const { data, channel, key } = context;
|
|
74
|
+
|
|
75
|
+
console.log(`Received on ${channel}:`, data);
|
|
76
|
+
|
|
77
|
+
// Handle the notification
|
|
78
|
+
await this.sendPushNotification(data.userId, data.title, data.body);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private async sendPushNotification(
|
|
82
|
+
userId: string,
|
|
83
|
+
title: string,
|
|
84
|
+
body: string
|
|
85
|
+
): Promise<void> {
|
|
86
|
+
// Push notification logic
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Create Redis client
|
|
91
|
+
const redisClient = new RedisPubSub({
|
|
92
|
+
connectionString: 'redis://localhost:6379'
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// Create pub/sub handler
|
|
96
|
+
const notificationPubSub = new NotificationPubSub(redisClient);
|
|
97
|
+
|
|
98
|
+
// Subscribe to channel
|
|
99
|
+
await notificationPubSub.subscribe();
|
|
100
|
+
|
|
101
|
+
// Publish a message
|
|
102
|
+
await notificationPubSub.publish({
|
|
103
|
+
userId: 'user-123',
|
|
104
|
+
title: 'New Message',
|
|
105
|
+
body: 'You have a new message!'
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Using Environment Variables
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
import { RedisPubSub } from '@ooneex/pub-sub';
|
|
113
|
+
|
|
114
|
+
// Automatically uses CACHE_REDIS_URL environment variable
|
|
115
|
+
const client = new RedisPubSub();
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**Environment Variables:**
|
|
119
|
+
- `CACHE_REDIS_URL` - Redis connection string
|
|
120
|
+
|
|
121
|
+
### Publishing with Keys
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
import { PubSub, RedisPubSub } from '@ooneex/pub-sub';
|
|
125
|
+
|
|
126
|
+
interface OrderEvent extends Record<string, ScalarType> {
|
|
127
|
+
orderId: string;
|
|
128
|
+
status: string;
|
|
129
|
+
amount: number;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
class OrderPubSub extends PubSub<OrderEvent> {
|
|
133
|
+
public getChannel(): string {
|
|
134
|
+
return 'orders';
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
public async handler(context: {
|
|
138
|
+
data: OrderEvent;
|
|
139
|
+
channel: string;
|
|
140
|
+
key?: string
|
|
141
|
+
}): Promise<void> {
|
|
142
|
+
const { data, key } = context;
|
|
143
|
+
|
|
144
|
+
// Route based on key
|
|
145
|
+
switch (key) {
|
|
146
|
+
case 'created':
|
|
147
|
+
await this.handleOrderCreated(data);
|
|
148
|
+
break;
|
|
149
|
+
case 'completed':
|
|
150
|
+
await this.handleOrderCompleted(data);
|
|
151
|
+
break;
|
|
152
|
+
case 'cancelled':
|
|
153
|
+
await this.handleOrderCancelled(data);
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const orderPubSub = new OrderPubSub(new RedisPubSub());
|
|
160
|
+
|
|
161
|
+
// Subscribe
|
|
162
|
+
await orderPubSub.subscribe();
|
|
163
|
+
|
|
164
|
+
// Publish with keys
|
|
165
|
+
await orderPubSub.publish({
|
|
166
|
+
orderId: 'order-456',
|
|
167
|
+
status: 'created',
|
|
168
|
+
amount: 99.99
|
|
169
|
+
}, 'created');
|
|
170
|
+
|
|
171
|
+
await orderPubSub.publish({
|
|
172
|
+
orderId: 'order-456',
|
|
173
|
+
status: 'completed',
|
|
174
|
+
amount: 99.99
|
|
175
|
+
}, 'completed');
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Dynamic Channels
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
import { PubSub, RedisPubSub } from '@ooneex/pub-sub';
|
|
182
|
+
|
|
183
|
+
interface ChatMessage extends Record<string, ScalarType> {
|
|
184
|
+
userId: string;
|
|
185
|
+
message: string;
|
|
186
|
+
timestamp: number;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
class ChatRoomPubSub extends PubSub<ChatMessage> {
|
|
190
|
+
constructor(
|
|
191
|
+
client: RedisPubSub,
|
|
192
|
+
private readonly roomId: string
|
|
193
|
+
) {
|
|
194
|
+
super(client);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
public getChannel(): string {
|
|
198
|
+
return `chat:room:${this.roomId}`;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
public async handler(context: {
|
|
202
|
+
data: ChatMessage;
|
|
203
|
+
channel: string
|
|
204
|
+
}): Promise<void> {
|
|
205
|
+
const { data, channel } = context;
|
|
206
|
+
console.log(`[${channel}] ${data.userId}: ${data.message}`);
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Create room-specific pub/sub
|
|
211
|
+
const room1 = new ChatRoomPubSub(new RedisPubSub(), 'room-1');
|
|
212
|
+
const room2 = new ChatRoomPubSub(new RedisPubSub(), 'room-2');
|
|
213
|
+
|
|
214
|
+
await room1.subscribe();
|
|
215
|
+
await room2.subscribe();
|
|
216
|
+
|
|
217
|
+
// Publish to specific room
|
|
218
|
+
await room1.publish({
|
|
219
|
+
userId: 'user-123',
|
|
220
|
+
message: 'Hello Room 1!',
|
|
221
|
+
timestamp: Date.now()
|
|
222
|
+
});
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
### Async Channel Names
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
import { PubSub, RedisPubSub } from '@ooneex/pub-sub';
|
|
229
|
+
|
|
230
|
+
interface UserEvent extends Record<string, ScalarType> {
|
|
231
|
+
action: string;
|
|
232
|
+
data: string;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
class UserEventPubSub extends PubSub<UserEvent> {
|
|
236
|
+
constructor(
|
|
237
|
+
client: RedisPubSub,
|
|
238
|
+
private readonly getUserId: () => Promise<string>
|
|
239
|
+
) {
|
|
240
|
+
super(client);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
public async getChannel(): Promise<string> {
|
|
244
|
+
const userId = await this.getUserId();
|
|
245
|
+
return `user:${userId}:events`;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
public async handler(context: {
|
|
249
|
+
data: UserEvent;
|
|
250
|
+
channel: string
|
|
251
|
+
}): Promise<void> {
|
|
252
|
+
console.log('User event:', context.data);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## API Reference
|
|
258
|
+
|
|
259
|
+
### Classes
|
|
260
|
+
|
|
261
|
+
#### `PubSub<Data>` (Abstract)
|
|
262
|
+
|
|
263
|
+
Abstract base class for creating pub/sub handlers.
|
|
264
|
+
|
|
265
|
+
**Type Parameter:**
|
|
266
|
+
- `Data` - Data type extending `Record<string, ScalarType>`
|
|
267
|
+
|
|
268
|
+
**Constructor:**
|
|
269
|
+
```typescript
|
|
270
|
+
new PubSub(client: IPubSubClient<Data>)
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
**Abstract Methods:**
|
|
274
|
+
|
|
275
|
+
##### `getChannel(): string | Promise<string>`
|
|
276
|
+
|
|
277
|
+
Returns the channel name to subscribe/publish to.
|
|
278
|
+
|
|
279
|
+
**Returns:** Channel name (sync or async)
|
|
280
|
+
|
|
281
|
+
##### `handler(context: { data: Data; channel: string; key?: string }): Promise<void> | void`
|
|
282
|
+
|
|
283
|
+
Handle incoming messages on the channel.
|
|
284
|
+
|
|
285
|
+
**Parameters:**
|
|
286
|
+
- `context.data` - The message data
|
|
287
|
+
- `context.channel` - The channel name
|
|
288
|
+
- `context.key` - Optional message key
|
|
289
|
+
|
|
290
|
+
**Concrete Methods:**
|
|
291
|
+
|
|
292
|
+
##### `publish(data: Data, key?: string): Promise<void>`
|
|
293
|
+
|
|
294
|
+
Publish a message to the channel.
|
|
295
|
+
|
|
296
|
+
**Parameters:**
|
|
297
|
+
- `data` - The data to publish
|
|
298
|
+
- `key` - Optional key for message routing
|
|
299
|
+
|
|
300
|
+
##### `subscribe(): Promise<void>`
|
|
301
|
+
|
|
302
|
+
Subscribe to the channel and start receiving messages.
|
|
303
|
+
|
|
304
|
+
##### `unsubscribe(): Promise<void>`
|
|
305
|
+
|
|
306
|
+
Unsubscribe from the channel.
|
|
307
|
+
|
|
308
|
+
##### `unsubscribeAll(): Promise<void>`
|
|
309
|
+
|
|
310
|
+
Unsubscribe from all channels.
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
#### `RedisPubSub`
|
|
315
|
+
|
|
316
|
+
Redis-based pub/sub client implementation.
|
|
317
|
+
|
|
318
|
+
**Constructor:**
|
|
319
|
+
```typescript
|
|
320
|
+
new RedisPubSub(options?: RedisPubSubOptionsType)
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
**Parameters:**
|
|
324
|
+
- `options.connectionString` - Redis URL (default: `CACHE_REDIS_URL` env var)
|
|
325
|
+
- `options.connectionTimeout` - Connection timeout in ms (default: 10000)
|
|
326
|
+
- `options.idleTimeout` - Idle timeout in ms (default: 30000)
|
|
327
|
+
- `options.autoReconnect` - Enable auto reconnection (default: true)
|
|
328
|
+
- `options.maxRetries` - Maximum retry attempts (default: 3)
|
|
329
|
+
- `options.enableOfflineQueue` - Queue commands when offline (default: true)
|
|
330
|
+
- `options.enableAutoPipelining` - Enable auto pipelining (default: true)
|
|
331
|
+
- `options.tls` - TLS configuration (optional)
|
|
332
|
+
|
|
333
|
+
**Methods:**
|
|
334
|
+
|
|
335
|
+
##### `publish(config: { channel: string; data: Data; key?: string }): Promise<void>`
|
|
336
|
+
|
|
337
|
+
Publish a message to a channel.
|
|
338
|
+
|
|
339
|
+
##### `subscribe(channel: string, handler: PubSubMessageHandlerType<Data>): Promise<void>`
|
|
340
|
+
|
|
341
|
+
Subscribe to a channel with a message handler.
|
|
342
|
+
|
|
343
|
+
##### `unsubscribe(channel: string): Promise<void>`
|
|
344
|
+
|
|
345
|
+
Unsubscribe from a specific channel.
|
|
346
|
+
|
|
347
|
+
##### `unsubscribeAll(): Promise<void>`
|
|
348
|
+
|
|
349
|
+
Unsubscribe from all channels.
|
|
350
|
+
|
|
351
|
+
### Types
|
|
352
|
+
|
|
353
|
+
#### `PubSubMessageHandlerType<Data>`
|
|
354
|
+
|
|
355
|
+
Handler function type for incoming messages.
|
|
356
|
+
|
|
357
|
+
```typescript
|
|
358
|
+
type PubSubMessageHandlerType<Data> = (context: {
|
|
359
|
+
data: Data;
|
|
360
|
+
channel: string;
|
|
361
|
+
key?: string;
|
|
362
|
+
}) => Promise<void> | void;
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
#### `RedisPubSubOptionsType`
|
|
366
|
+
|
|
367
|
+
Configuration options for Redis pub/sub client.
|
|
368
|
+
|
|
369
|
+
```typescript
|
|
370
|
+
type RedisPubSubOptionsType = {
|
|
371
|
+
connectionString?: string;
|
|
372
|
+
connectionTimeout?: number;
|
|
373
|
+
idleTimeout?: number;
|
|
374
|
+
autoReconnect?: boolean;
|
|
375
|
+
maxRetries?: number;
|
|
376
|
+
enableOfflineQueue?: boolean;
|
|
377
|
+
enableAutoPipelining?: boolean;
|
|
378
|
+
tls?: boolean | object;
|
|
379
|
+
};
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
#### `IPubSubClient<Data>`
|
|
383
|
+
|
|
384
|
+
Interface for pub/sub client implementations.
|
|
385
|
+
|
|
386
|
+
```typescript
|
|
387
|
+
interface IPubSubClient<Data> {
|
|
388
|
+
publish: (config: { channel: string; data: Data; key?: string }) => Promise<void>;
|
|
389
|
+
subscribe: (channel: string, handler: PubSubMessageHandlerType<Data>) => Promise<void>;
|
|
390
|
+
unsubscribe: (channel: string) => Promise<void>;
|
|
391
|
+
unsubscribeAll: () => Promise<void>;
|
|
392
|
+
}
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
#### `IPubSub<Data>`
|
|
396
|
+
|
|
397
|
+
Interface for pub/sub handler implementations.
|
|
398
|
+
|
|
399
|
+
```typescript
|
|
400
|
+
interface IPubSub<Data> {
|
|
401
|
+
getChannel: () => Promise<string> | string;
|
|
402
|
+
handler: (context: { data: Data; channel: string; key?: string }) => Promise<void> | void;
|
|
403
|
+
publish: (data: Data, key?: string) => Promise<void> | void;
|
|
404
|
+
subscribe: () => Promise<void> | void;
|
|
405
|
+
unsubscribe: () => Promise<void> | void;
|
|
406
|
+
unsubscribeAll: () => Promise<void> | void;
|
|
407
|
+
}
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
#### `PubSubClassType`
|
|
411
|
+
|
|
412
|
+
Type for pub/sub class constructors.
|
|
413
|
+
|
|
414
|
+
```typescript
|
|
415
|
+
type PubSubClassType = new (...args: any[]) => IPubSub;
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### Exceptions
|
|
419
|
+
|
|
420
|
+
#### `PubSubException`
|
|
421
|
+
|
|
422
|
+
Thrown when pub/sub operations fail.
|
|
423
|
+
|
|
424
|
+
```typescript
|
|
425
|
+
import { PubSub, PubSubException, RedisPubSub } from '@ooneex/pub-sub';
|
|
426
|
+
|
|
427
|
+
try {
|
|
428
|
+
const client = new RedisPubSub();
|
|
429
|
+
await client.publish({ channel: 'test', data: { message: 'hello' } });
|
|
430
|
+
} catch (error) {
|
|
431
|
+
if (error instanceof PubSubException) {
|
|
432
|
+
console.error('PubSub error:', error.message);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
### Decorators
|
|
438
|
+
|
|
439
|
+
#### `@decorator.pubsub()`
|
|
440
|
+
|
|
441
|
+
Decorator to register pub/sub classes with the DI container.
|
|
442
|
+
|
|
443
|
+
```typescript
|
|
444
|
+
import { PubSub, decorator, RedisPubSub } from '@ooneex/pub-sub';
|
|
445
|
+
|
|
446
|
+
@decorator.pubsub()
|
|
447
|
+
class MyEventPubSub extends PubSub {
|
|
448
|
+
// Implementation
|
|
449
|
+
}
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
## Advanced Usage
|
|
453
|
+
|
|
454
|
+
### Multiple Subscribers
|
|
455
|
+
|
|
456
|
+
```typescript
|
|
457
|
+
import { PubSub, RedisPubSub } from '@ooneex/pub-sub';
|
|
458
|
+
|
|
459
|
+
interface EventData extends Record<string, ScalarType> {
|
|
460
|
+
type: string;
|
|
461
|
+
payload: string;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// Subscriber 1: Log events
|
|
465
|
+
class EventLoggerPubSub extends PubSub<EventData> {
|
|
466
|
+
public getChannel(): string {
|
|
467
|
+
return 'events';
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
public async handler(context: { data: EventData }): Promise<void> {
|
|
471
|
+
console.log('Event logged:', context.data);
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Subscriber 2: Process events
|
|
476
|
+
class EventProcessorPubSub extends PubSub<EventData> {
|
|
477
|
+
public getChannel(): string {
|
|
478
|
+
return 'events';
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
public async handler(context: { data: EventData }): Promise<void> {
|
|
482
|
+
await this.processEvent(context.data);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Both subscribe to the same channel
|
|
487
|
+
const client = new RedisPubSub();
|
|
488
|
+
const logger = new EventLoggerPubSub(client);
|
|
489
|
+
const processor = new EventProcessorPubSub(client);
|
|
490
|
+
|
|
491
|
+
await logger.subscribe();
|
|
492
|
+
await processor.subscribe();
|
|
493
|
+
|
|
494
|
+
// Both receive this message
|
|
495
|
+
await logger.publish({ type: 'user.created', payload: '{"id":"123"}' });
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
### Error Handling
|
|
499
|
+
|
|
500
|
+
```typescript
|
|
501
|
+
import { PubSub, RedisPubSub, PubSubException } from '@ooneex/pub-sub';
|
|
502
|
+
|
|
503
|
+
class ResilientPubSub extends PubSub<Record<string, ScalarType>> {
|
|
504
|
+
public getChannel(): string {
|
|
505
|
+
return 'resilient';
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
public async handler(context: { data: Record<string, ScalarType> }): Promise<void> {
|
|
509
|
+
try {
|
|
510
|
+
await this.processMessage(context.data);
|
|
511
|
+
} catch (error) {
|
|
512
|
+
console.error('Failed to process message:', error);
|
|
513
|
+
// Optionally republish to dead letter channel
|
|
514
|
+
await this.publishToDeadLetter(context.data, error);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
private async publishToDeadLetter(
|
|
519
|
+
data: Record<string, ScalarType>,
|
|
520
|
+
error: unknown
|
|
521
|
+
): Promise<void> {
|
|
522
|
+
// Dead letter queue logic
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
```
|
|
526
|
+
|
|
527
|
+
### Integration with WebSocket
|
|
528
|
+
|
|
529
|
+
```typescript
|
|
530
|
+
import { Route } from '@ooneex/routing';
|
|
531
|
+
import { PubSub, RedisPubSub } from '@ooneex/pub-sub';
|
|
532
|
+
import type { IController, ContextType } from '@ooneex/socket';
|
|
533
|
+
|
|
534
|
+
interface ChatData extends Record<string, ScalarType> {
|
|
535
|
+
roomId: string;
|
|
536
|
+
userId: string;
|
|
537
|
+
message: string;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// Pub/Sub for distributing messages across server instances
|
|
541
|
+
class ChatPubSub extends PubSub<ChatData> {
|
|
542
|
+
public getChannel(): string {
|
|
543
|
+
return 'chat:messages';
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
public async handler(context: { data: ChatData }): Promise<void> {
|
|
547
|
+
// Broadcast to all connected WebSocket clients
|
|
548
|
+
await this.broadcastToRoom(context.data.roomId, context.data);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// WebSocket controller uses pub/sub for distributed messaging
|
|
553
|
+
@Route.socket({
|
|
554
|
+
name: 'api.chat.send',
|
|
555
|
+
path: '/ws/chat/:roomId',
|
|
556
|
+
description: 'Send chat message'
|
|
557
|
+
})
|
|
558
|
+
class ChatController implements IController {
|
|
559
|
+
private readonly chatPubSub = new ChatPubSub(new RedisPubSub());
|
|
560
|
+
|
|
561
|
+
public async index(context: ContextType): Promise<IResponse> {
|
|
562
|
+
const { roomId } = context.params;
|
|
563
|
+
const { message } = context.payload;
|
|
564
|
+
|
|
565
|
+
// Publish through Redis for distribution to all server instances
|
|
566
|
+
await this.chatPubSub.publish({
|
|
567
|
+
roomId,
|
|
568
|
+
userId: context.user?.id ?? 'anonymous',
|
|
569
|
+
message
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
return context.response.json({ sent: true });
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
### Pattern-Based Subscriptions
|
|
578
|
+
|
|
579
|
+
```typescript
|
|
580
|
+
import { PubSub, RedisPubSub } from '@ooneex/pub-sub';
|
|
581
|
+
|
|
582
|
+
interface SystemEvent extends Record<string, ScalarType> {
|
|
583
|
+
service: string;
|
|
584
|
+
event: string;
|
|
585
|
+
severity: string;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
class SystemMonitorPubSub extends PubSub<SystemEvent> {
|
|
589
|
+
constructor(
|
|
590
|
+
client: RedisPubSub,
|
|
591
|
+
private readonly servicePattern: string
|
|
592
|
+
) {
|
|
593
|
+
super(client);
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
public getChannel(): string {
|
|
597
|
+
return `system:${this.servicePattern}:events`;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
public async handler(context: {
|
|
601
|
+
data: SystemEvent;
|
|
602
|
+
channel: string
|
|
603
|
+
}): Promise<void> {
|
|
604
|
+
const { data, channel } = context;
|
|
605
|
+
|
|
606
|
+
if (data.severity === 'critical') {
|
|
607
|
+
await this.alertOncall(channel, data);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
await this.logToMonitoring(data);
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
// Monitor specific services
|
|
615
|
+
const apiMonitor = new SystemMonitorPubSub(new RedisPubSub(), 'api');
|
|
616
|
+
const dbMonitor = new SystemMonitorPubSub(new RedisPubSub(), 'database');
|
|
617
|
+
const cacheMonitor = new SystemMonitorPubSub(new RedisPubSub(), 'cache');
|
|
618
|
+
|
|
619
|
+
await apiMonitor.subscribe();
|
|
620
|
+
await dbMonitor.subscribe();
|
|
621
|
+
await cacheMonitor.subscribe();
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
## License
|
|
625
|
+
|
|
626
|
+
This project is licensed under the MIT License - see the [LICENSE](./LICENSE) file for details.
|
|
627
|
+
|
|
628
|
+
## Contributing
|
|
629
|
+
|
|
630
|
+
Contributions are welcome! Please feel free to submit a Pull Request. For major changes, please open an issue first to discuss what you would like to change.
|
|
631
|
+
|
|
632
|
+
### Development Setup
|
|
633
|
+
|
|
634
|
+
1. Clone the repository
|
|
635
|
+
2. Install dependencies: `bun install`
|
|
636
|
+
3. Run tests: `bun run test`
|
|
637
|
+
4. Build the project: `bun run build`
|
|
638
|
+
|
|
639
|
+
### Guidelines
|
|
640
|
+
|
|
641
|
+
- Write tests for new features
|
|
642
|
+
- Follow the existing code style
|
|
643
|
+
- Update documentation for API changes
|
|
644
|
+
- Ensure all tests pass before submitting PR
|
|
645
|
+
|
|
646
|
+
---
|
|
647
|
+
|
|
648
|
+
Made with ❤️ by the Ooneex team
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { EContainerScope } from "@ooneex/container";
|
|
2
|
+
import { ScalarType } from "@ooneex/types";
|
|
3
|
+
type PubSubMessageHandlerType<Data extends Record<string, ScalarType> = Record<string, ScalarType>> = (context: {
|
|
4
|
+
data: Data;
|
|
5
|
+
channel: string;
|
|
6
|
+
key?: string;
|
|
7
|
+
}) => Promise<void> | void;
|
|
8
|
+
type RedisPubSubOptionsType = {
|
|
9
|
+
connectionString?: string;
|
|
10
|
+
connectionTimeout?: number;
|
|
11
|
+
idleTimeout?: number;
|
|
12
|
+
autoReconnect?: boolean;
|
|
13
|
+
maxRetries?: number;
|
|
14
|
+
enableOfflineQueue?: boolean;
|
|
15
|
+
enableAutoPipelining?: boolean;
|
|
16
|
+
tls?: boolean | object;
|
|
17
|
+
};
|
|
18
|
+
interface IPubSubClient<Data extends Record<string, ScalarType> = Record<string, ScalarType>> {
|
|
19
|
+
publish: (config: {
|
|
20
|
+
channel: string;
|
|
21
|
+
data: Data;
|
|
22
|
+
key?: string;
|
|
23
|
+
}) => Promise<void>;
|
|
24
|
+
subscribe: (channel: string, handler: PubSubMessageHandlerType<Data>) => Promise<void>;
|
|
25
|
+
unsubscribe: (channel: string) => Promise<void>;
|
|
26
|
+
unsubscribeAll: () => Promise<void>;
|
|
27
|
+
}
|
|
28
|
+
interface IPubSub<Data extends Record<string, ScalarType> = Record<string, ScalarType>> {
|
|
29
|
+
getChannel: () => Promise<string> | string;
|
|
30
|
+
handler: (context: {
|
|
31
|
+
data: Data;
|
|
32
|
+
channel: string;
|
|
33
|
+
key?: string;
|
|
34
|
+
}) => Promise<void> | void;
|
|
35
|
+
publish: (data: Data, key?: string) => Promise<void> | void;
|
|
36
|
+
subscribe: () => Promise<void> | void;
|
|
37
|
+
unsubscribe: () => Promise<void> | void;
|
|
38
|
+
unsubscribeAll: () => Promise<void> | void;
|
|
39
|
+
}
|
|
40
|
+
type PubSubClassType = new (...args: any[]) => IPubSub;
|
|
41
|
+
declare const decorator: {
|
|
42
|
+
pubSub: (scope?: EContainerScope) => (target: PubSubClassType) => void;
|
|
43
|
+
};
|
|
44
|
+
import { ScalarType as ScalarType2 } from "@ooneex/types";
|
|
45
|
+
declare abstract class PubSub<Data extends Record<string, ScalarType2> = Record<string, ScalarType2>> implements IPubSub<Data> {
|
|
46
|
+
protected readonly client: IPubSubClient<Data>;
|
|
47
|
+
constructor(client: IPubSubClient<Data>);
|
|
48
|
+
abstract handler(context: {
|
|
49
|
+
data: Data;
|
|
50
|
+
channel: string;
|
|
51
|
+
key?: string;
|
|
52
|
+
}): Promise<void> | void;
|
|
53
|
+
publish(data: Data, key?: string): Promise<void>;
|
|
54
|
+
subscribe(): Promise<void>;
|
|
55
|
+
unsubscribe(): Promise<void>;
|
|
56
|
+
unsubscribeAll(): Promise<void>;
|
|
57
|
+
}
|
|
58
|
+
import { Exception } from "@ooneex/exception";
|
|
59
|
+
declare class PubSubException extends Exception {
|
|
60
|
+
constructor(message: string, data?: Record<string, unknown>);
|
|
61
|
+
}
|
|
62
|
+
import { ScalarType as ScalarType3 } from "@ooneex/types";
|
|
63
|
+
declare class RedisPubSubClient<Data extends Record<string, ScalarType3>> implements IPubSubClient<Data> {
|
|
64
|
+
private client;
|
|
65
|
+
private subscriber;
|
|
66
|
+
constructor(options?: RedisPubSubOptionsType);
|
|
67
|
+
private connect;
|
|
68
|
+
private connectSubscriber;
|
|
69
|
+
publish(config: {
|
|
70
|
+
channel: string;
|
|
71
|
+
data: Data;
|
|
72
|
+
key?: string;
|
|
73
|
+
}): Promise<void>;
|
|
74
|
+
subscribe(channel: string, handler: PubSubMessageHandlerType<Data>): Promise<void>;
|
|
75
|
+
unsubscribe(channel: string): Promise<void>;
|
|
76
|
+
unsubscribeAll(): Promise<void>;
|
|
77
|
+
}
|
|
78
|
+
export { decorator, RedisPubSubOptionsType, RedisPubSubClient as RedisPubSub, PubSubMessageHandlerType, PubSubException, PubSubClassType, PubSub, IPubSubClient, IPubSub };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
var b=function(e,t,i,n){var o=arguments.length,s=o<3?t:n===null?n=Object.getOwnPropertyDescriptor(t,i):n,a;if(typeof Reflect==="object"&&typeof Reflect.decorate==="function")s=Reflect.decorate(e,t,i,n);else for(var u=e.length-1;u>=0;u--)if(a=e[u])s=(o<3?a(s):o>3?a(t,i,s):a(t,i))||s;return o>3&&s&&Object.defineProperty(t,i,s),s};var l=(e,t)=>{if(typeof Reflect==="object"&&typeof Reflect.metadata==="function")return Reflect.metadata(e,t)};import{container as p,EContainerScope as d}from"@ooneex/container";var m={pubSub:(e=d.Singleton)=>{return(t)=>{p.add(t,e),p.get(t).subscribe()}}};class h{client;constructor(e){this.client=e}async publish(e,t){await this.client.publish({channel:await this.getChannel(),data:e,...t!==void 0&&{key:t}})}async subscribe(){await this.client.subscribe(await this.getChannel(),this.handler.bind(this))}async unsubscribe(){await this.client.unsubscribe(await this.getChannel())}async unsubscribeAll(){await this.client.unsubscribeAll()}}import{Exception as S}from"@ooneex/exception";import{HttpStatus as y}from"@ooneex/http-status";class r extends S{constructor(e,t={}){super(e,{status:y.Code.InternalServerError,data:t});this.name="PubSubException"}}import{injectable as P}from"@ooneex/container";class c{client;subscriber=null;constructor(e={}){let t=e.connectionString||Bun.env.PUBSUB_REDIS_URL;if(!t)throw new r("Redis connection string is required. Please provide a connection string either through the constructor options or set the PUBSUB_REDIS_URL environment variable.");let{connectionString:i,...n}=e,s={...{connectionTimeout:1e4,idleTimeout:30000,autoReconnect:!0,maxRetries:3,enableOfflineQueue:!0,enableAutoPipelining:!0},...n};this.client=new Bun.RedisClient(t,s)}async connect(){if(!this.client.connected)await this.client.connect()}async connectSubscriber(){if(!this.subscriber)this.subscriber=await this.client.duplicate();if(!this.subscriber.connected)await this.subscriber.connect()}async publish(e){try{await this.connect();let t=JSON.stringify(e.data);await this.client.publish(e.channel,t)}catch(t){throw new r(`Failed to publish message to channel "${e.channel}": ${t}`)}finally{this.client.close()}}async subscribe(e,t){try{await this.connectSubscriber(),await this.subscriber?.subscribe(e,(i,n)=>{let o=JSON.parse(i);t({data:o,channel:n})})}catch(i){throw new r(`Failed to subscribe to channel "${e}": ${i}`)}}async unsubscribe(e){try{if(!this.subscriber)return;await this.subscriber.unsubscribe(e)}catch(t){throw new r(`Failed to unsubscribe from channel "${e}": ${t}`)}}async unsubscribeAll(){try{if(!this.subscriber)return;await this.subscriber.unsubscribe()}catch(e){throw new r(`Failed to unsubscribe from all channels: ${e}`)}}}c=b([P(),l("design:paramtypes",[typeof RedisPubSubOptionsType==="undefined"?Object:RedisPubSubOptionsType])],c);export{m as decorator,c as RedisPubSub,r as PubSubException,h as PubSub};
|
|
3
|
+
|
|
4
|
+
//# debugId=71EA4C698ED7AF9C64756E2164756E21
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["src/decorators.ts", "src/PubSub.ts", "src/PubSubException.ts", "src/RedisPubSubClient.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import { container, EContainerScope } from \"@ooneex/container\";\nimport type { IPubSub, PubSubClassType } from \"./types\";\n\nexport const decorator = {\n pubSub: (scope: EContainerScope = EContainerScope.Singleton) => {\n return (target: PubSubClassType): void => {\n container.add(target, scope);\n const pubsub = container.get<IPubSub>(target);\n pubsub.subscribe();\n };\n },\n};\n",
|
|
6
|
+
"import type { ScalarType } from \"@ooneex/types\";\nimport type { IPubSub, IPubSubClient } from \"./types\";\n\nexport abstract class PubSub<Data extends Record<string, ScalarType> = Record<string, ScalarType>>\n implements IPubSub<Data>\n{\n constructor(protected readonly client: IPubSubClient<Data>) {}\n\n public abstract getChannel(): string | Promise<string>;\n public abstract handler(context: { data: Data; channel: string; key?: string }): Promise<void> | void;\n\n public async publish(data: Data, key?: string): Promise<void> {\n await this.client.publish({\n channel: await this.getChannel(),\n data,\n ...(key !== undefined && { key }),\n });\n }\n\n public async subscribe(): Promise<void> {\n await this.client.subscribe(await this.getChannel(), this.handler.bind(this));\n }\n\n public async unsubscribe(): Promise<void> {\n await this.client.unsubscribe(await this.getChannel());\n }\n\n public async unsubscribeAll(): Promise<void> {\n await this.client.unsubscribeAll();\n }\n}\n",
|
|
7
|
+
"import { Exception } from \"@ooneex/exception\";\nimport { HttpStatus } from \"@ooneex/http-status\";\n\nexport class PubSubException extends Exception {\n constructor(message: string, data: Record<string, unknown> = {}) {\n super(message, {\n status: HttpStatus.Code.InternalServerError,\n data,\n });\n this.name = \"PubSubException\";\n }\n}\n",
|
|
8
|
+
"import { injectable } from \"@ooneex/container\";\nimport type { ScalarType } from \"@ooneex/types\";\nimport { PubSubException } from \"./PubSubException\";\nimport type { IPubSubClient, PubSubMessageHandlerType, RedisPubSubOptionsType } from \"./types\";\n\n@injectable()\nexport class RedisPubSubClient<Data extends Record<string, ScalarType>> implements IPubSubClient<Data> {\n private client: Bun.RedisClient;\n private subscriber: Bun.RedisClient | null = null;\n\n constructor(options: RedisPubSubOptionsType = {}) {\n const connectionString = options.connectionString || Bun.env.PUBSUB_REDIS_URL;\n\n if (!connectionString) {\n throw new PubSubException(\n \"Redis connection string is required. Please provide a connection string either through the constructor options or set the PUBSUB_REDIS_URL environment variable.\",\n );\n }\n\n const { connectionString: _, ...userOptions } = options;\n\n const defaultOptions = {\n connectionTimeout: 10_000,\n idleTimeout: 30_000,\n autoReconnect: true,\n maxRetries: 3,\n enableOfflineQueue: true,\n enableAutoPipelining: true,\n };\n\n const clientOptions = { ...defaultOptions, ...userOptions };\n\n this.client = new Bun.RedisClient(connectionString, clientOptions);\n }\n\n private async connect(): Promise<void> {\n if (!this.client.connected) {\n await this.client.connect();\n }\n }\n\n private async connectSubscriber(): Promise<void> {\n if (!this.subscriber) {\n this.subscriber = await this.client.duplicate();\n }\n\n if (!this.subscriber.connected) {\n await this.subscriber.connect();\n }\n }\n\n public async publish(config: { channel: string; data: Data; key?: string }): Promise<void> {\n try {\n await this.connect();\n const message = JSON.stringify(config.data);\n await this.client.publish(config.channel, message);\n } catch (error) {\n throw new PubSubException(`Failed to publish message to channel \"${config.channel}\": ${error}`);\n } finally {\n this.client.close();\n }\n }\n\n public async subscribe(channel: string, handler: PubSubMessageHandlerType<Data>): Promise<void> {\n try {\n await this.connectSubscriber();\n await this.subscriber?.subscribe(channel, (message: string, ch: string) => {\n const data = JSON.parse(message) as Data;\n handler({ data, channel: ch });\n });\n } catch (error) {\n throw new PubSubException(`Failed to subscribe to channel \"${channel}\": ${error}`);\n }\n }\n\n public async unsubscribe(channel: string): Promise<void> {\n try {\n if (!this.subscriber) {\n return;\n }\n\n await this.subscriber.unsubscribe(channel);\n } catch (error) {\n throw new PubSubException(`Failed to unsubscribe from channel \"${channel}\": ${error}`);\n }\n }\n\n public async unsubscribeAll(): Promise<void> {\n try {\n if (!this.subscriber) {\n return;\n }\n\n await this.subscriber.unsubscribe();\n } catch (error) {\n throw new PubSubException(`Failed to unsubscribe from all channels: ${error}`);\n }\n }\n}\n"
|
|
9
|
+
],
|
|
10
|
+
"mappings": ";ybAAA,oBAAS,qBAAW,0BAGb,IAAM,EAAY,CACvB,OAAQ,CAAC,EAAyB,EAAgB,YAAc,CAC9D,MAAO,CAAC,IAAkC,CACxC,EAAU,IAAI,EAAQ,CAAK,EACZ,EAAU,IAAa,CAAM,EACrC,UAAU,GAGvB,ECRO,MAAe,CAEtB,CACiC,OAA/B,WAAW,CAAoB,EAA6B,CAA7B,mBAKlB,QAAO,CAAC,EAAY,EAA6B,CAC5D,MAAM,KAAK,OAAO,QAAQ,CACxB,QAAS,MAAM,KAAK,WAAW,EAC/B,UACI,IAAQ,QAAa,CAAE,KAAI,CACjC,CAAC,OAGU,UAAS,EAAkB,CACtC,MAAM,KAAK,OAAO,UAAU,MAAM,KAAK,WAAW,EAAG,KAAK,QAAQ,KAAK,IAAI,CAAC,OAGjE,YAAW,EAAkB,CACxC,MAAM,KAAK,OAAO,YAAY,MAAM,KAAK,WAAW,CAAC,OAG1C,eAAc,EAAkB,CAC3C,MAAM,KAAK,OAAO,eAAe,EAErC,CC9BA,oBAAS,0BACT,qBAAS,4BAEF,MAAM,UAAwB,CAAU,CAC7C,WAAW,CAAC,EAAiB,EAAgC,CAAC,EAAG,CAC/D,MAAM,EAAS,CACb,OAAQ,EAAW,KAAK,oBACxB,MACF,CAAC,EACD,KAAK,KAAO,kBAEhB,CCXA,qBAAS,0BAMF,MAAM,CAA0F,CAC7F,OACA,WAAqC,KAE7C,WAAW,CAAC,EAAkC,CAAC,EAAG,CAChD,IAAM,EAAmB,EAAQ,kBAAoB,IAAI,IAAI,iBAE7D,GAAI,CAAC,EACH,MAAM,IAAI,EACR,kKACF,EAGF,IAAQ,iBAAkB,KAAM,GAAgB,EAW1C,EAAgB,IATC,CACrB,kBAAmB,IACnB,YAAa,MACb,cAAe,GACf,WAAY,EACZ,mBAAoB,GACpB,qBAAsB,EACxB,KAE8C,CAAY,EAE1D,KAAK,OAAS,IAAI,IAAI,YAAY,EAAkB,CAAa,OAGrD,QAAO,EAAkB,CACrC,GAAI,CAAC,KAAK,OAAO,UACf,MAAM,KAAK,OAAO,QAAQ,OAIhB,kBAAiB,EAAkB,CAC/C,GAAI,CAAC,KAAK,WACR,KAAK,WAAa,MAAM,KAAK,OAAO,UAAU,EAGhD,GAAI,CAAC,KAAK,WAAW,UACnB,MAAM,KAAK,WAAW,QAAQ,OAIrB,QAAO,CAAC,EAAsE,CACzF,GAAI,CACF,MAAM,KAAK,QAAQ,EACnB,IAAM,EAAU,KAAK,UAAU,EAAO,IAAI,EAC1C,MAAM,KAAK,OAAO,QAAQ,EAAO,QAAS,CAAO,EACjD,MAAO,EAAO,CACd,MAAM,IAAI,EAAgB,yCAAyC,EAAO,aAAa,GAAO,SAC9F,CACA,KAAK,OAAO,MAAM,QAIT,UAAS,CAAC,EAAiB,EAAwD,CAC9F,GAAI,CACF,MAAM,KAAK,kBAAkB,EAC7B,MAAM,KAAK,YAAY,UAAU,EAAS,CAAC,EAAiB,IAAe,CACzE,IAAM,EAAO,KAAK,MAAM,CAAO,EAC/B,EAAQ,CAAE,OAAM,QAAS,CAAG,CAAC,EAC9B,EACD,MAAO,EAAO,CACd,MAAM,IAAI,EAAgB,mCAAmC,OAAa,GAAO,QAIxE,YAAW,CAAC,EAAgC,CACvD,GAAI,CACF,GAAI,CAAC,KAAK,WACR,OAGF,MAAM,KAAK,WAAW,YAAY,CAAO,EACzC,MAAO,EAAO,CACd,MAAM,IAAI,EAAgB,uCAAuC,OAAa,GAAO,QAI5E,eAAc,EAAkB,CAC3C,GAAI,CACF,GAAI,CAAC,KAAK,WACR,OAGF,MAAM,KAAK,WAAW,YAAY,EAClC,MAAO,EAAO,CACd,MAAM,IAAI,EAAgB,4CAA4C,GAAO,GAGnF,CA5Fa,EAAN,GADN,EAAW,EACL,oGAAM",
|
|
11
|
+
"debugId": "71EA4C698ED7AF9C64756E2164756E21",
|
|
12
|
+
"names": []
|
|
13
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ooneex/pub-sub",
|
|
3
|
+
"description": "Publish-subscribe messaging pattern implementation for event-driven communication between application components",
|
|
4
|
+
"version": "0.0.4",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"files": [
|
|
7
|
+
"dist",
|
|
8
|
+
"LICENSE",
|
|
9
|
+
"README.md",
|
|
10
|
+
"package.json"
|
|
11
|
+
],
|
|
12
|
+
"module": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"import": {
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
|
+
"default": "./dist/index.js"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"./package.json": "./package.json"
|
|
22
|
+
},
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "bunup",
|
|
26
|
+
"lint": "tsgo --noEmit && bunx biome lint",
|
|
27
|
+
"publish": "bun publish --access public || true",
|
|
28
|
+
"test": "bun test tests"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@ooneex/container": "0.0.2",
|
|
32
|
+
"@ooneex/exception": "0.0.1",
|
|
33
|
+
"@ooneex/http-status": "0.0.1"
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"@ooneex/types": "0.0.1"
|
|
37
|
+
},
|
|
38
|
+
"keywords": [
|
|
39
|
+
"bun",
|
|
40
|
+
"events",
|
|
41
|
+
"messaging",
|
|
42
|
+
"ooneex",
|
|
43
|
+
"pub-sub",
|
|
44
|
+
"pubsub",
|
|
45
|
+
"typescript"
|
|
46
|
+
]
|
|
47
|
+
}
|