@signaltree/events 7.3.1
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/angular.d.ts +1 -0
- package/angular.esm.js +547 -0
- package/factory.esm.js +178 -0
- package/idempotency.esm.js +701 -0
- package/index.d.ts +1 -0
- package/index.esm.js +167 -0
- package/nestjs.d.ts +1 -0
- package/nestjs.esm.js +944 -0
- package/package.json +110 -0
- package/src/angular/handlers.d.ts +132 -0
- package/src/angular/index.d.ts +12 -0
- package/src/angular/optimistic-updates.d.ts +117 -0
- package/src/angular/websocket.service.d.ts +158 -0
- package/src/angular.d.ts +7 -0
- package/src/core/error-classification.d.ts +100 -0
- package/src/core/factory.d.ts +114 -0
- package/src/core/idempotency.d.ts +209 -0
- package/src/core/registry.d.ts +147 -0
- package/src/core/types.d.ts +127 -0
- package/src/core/validation.d.ts +619 -0
- package/src/index.d.ts +56 -0
- package/src/nestjs/base.subscriber.d.ts +169 -0
- package/src/nestjs/decorators.d.ts +37 -0
- package/src/nestjs/dlq.service.d.ts +117 -0
- package/src/nestjs/event-bus.module.d.ts +117 -0
- package/src/nestjs/event-bus.service.d.ts +114 -0
- package/src/nestjs/index.d.ts +16 -0
- package/src/nestjs/tokens.d.ts +8 -0
- package/src/nestjs.d.ts +7 -0
- package/src/testing/assertions.d.ts +113 -0
- package/src/testing/factories.d.ts +106 -0
- package/src/testing/helpers.d.ts +104 -0
- package/src/testing/index.d.ts +13 -0
- package/src/testing/mock-event-bus.d.ts +144 -0
- package/src/testing.d.ts +7 -0
- package/testing.d.ts +1 -0
- package/testing.esm.js +743 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { Logger, OnModuleDestroy, OnModuleInit } from '@nestjs/common';
|
|
2
|
+
import { ClassificationResult } from '../core/error-classification';
|
|
3
|
+
import { IdempotencyStore } from '../core/idempotency';
|
|
4
|
+
import { BaseEvent, EventPriority } from '../core/types';
|
|
5
|
+
import { DlqService } from './dlq.service';
|
|
6
|
+
import { EventBusModuleConfig } from './event-bus.module';
|
|
7
|
+
/**
|
|
8
|
+
* Base Subscriber - Abstract class for event subscribers
|
|
9
|
+
*
|
|
10
|
+
* Provides:
|
|
11
|
+
* - Automatic idempotency checking
|
|
12
|
+
* - Retry with exponential backoff
|
|
13
|
+
* - Error classification and DLQ routing
|
|
14
|
+
* - Metrics collection
|
|
15
|
+
* - Graceful shutdown
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Subscriber configuration
|
|
19
|
+
*/
|
|
20
|
+
export interface SubscriberConfig {
|
|
21
|
+
/** Unique subscriber name */
|
|
22
|
+
name: string;
|
|
23
|
+
/** Event types to subscribe to */
|
|
24
|
+
eventTypes: string[];
|
|
25
|
+
/** Queue to process from (or use priority to auto-select) */
|
|
26
|
+
queue?: string;
|
|
27
|
+
/** Priority level (used to select queue if queue not specified) */
|
|
28
|
+
priority?: EventPriority;
|
|
29
|
+
/** Number of concurrent jobs to process */
|
|
30
|
+
concurrency?: number;
|
|
31
|
+
/** Lock duration in ms (how long a job is locked while processing) */
|
|
32
|
+
lockDuration?: number;
|
|
33
|
+
/** Stalled check interval in ms */
|
|
34
|
+
stalledInterval?: number;
|
|
35
|
+
/** Enable idempotency checking (default: true) */
|
|
36
|
+
idempotency?: boolean;
|
|
37
|
+
/** Skip DLQ for this subscriber */
|
|
38
|
+
skipDlq?: boolean;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Result of processing an event
|
|
42
|
+
*/
|
|
43
|
+
export interface ProcessingResult {
|
|
44
|
+
/** Whether processing succeeded */
|
|
45
|
+
success: boolean;
|
|
46
|
+
/** Optional result data (stored for idempotent responses) */
|
|
47
|
+
result?: unknown;
|
|
48
|
+
/** Error if failed */
|
|
49
|
+
error?: Error;
|
|
50
|
+
/** Whether to skip DLQ (useful for expected failures) */
|
|
51
|
+
skipDlq?: boolean;
|
|
52
|
+
/** Custom retry delay override */
|
|
53
|
+
retryDelay?: number;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Subscriber metrics
|
|
57
|
+
*/
|
|
58
|
+
export interface SubscriberMetrics {
|
|
59
|
+
processed: number;
|
|
60
|
+
succeeded: number;
|
|
61
|
+
failed: number;
|
|
62
|
+
retried: number;
|
|
63
|
+
dlqSent: number;
|
|
64
|
+
duplicatesSkipped: number;
|
|
65
|
+
avgProcessingTimeMs: number;
|
|
66
|
+
lastProcessedAt?: Date;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Base class for event subscribers
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```typescript
|
|
73
|
+
* @Injectable()
|
|
74
|
+
* export class TradeSubscriber extends BaseSubscriber {
|
|
75
|
+
* protected readonly config: SubscriberConfig = {
|
|
76
|
+
* name: 'trade-subscriber',
|
|
77
|
+
* eventTypes: ['TradeProposalCreated', 'TradeAccepted'],
|
|
78
|
+
* priority: 'high',
|
|
79
|
+
* concurrency: 5,
|
|
80
|
+
* };
|
|
81
|
+
*
|
|
82
|
+
* async handle(event: TradeProposalCreated | TradeAccepted): Promise<ProcessingResult> {
|
|
83
|
+
* switch (event.type) {
|
|
84
|
+
* case 'TradeProposalCreated':
|
|
85
|
+
* await this.handleTradeCreated(event);
|
|
86
|
+
* break;
|
|
87
|
+
* case 'TradeAccepted':
|
|
88
|
+
* await this.handleTradeAccepted(event);
|
|
89
|
+
* break;
|
|
90
|
+
* }
|
|
91
|
+
* return { success: true };
|
|
92
|
+
* }
|
|
93
|
+
* }
|
|
94
|
+
* ```
|
|
95
|
+
*/
|
|
96
|
+
export declare abstract class BaseSubscriber<TEvent extends BaseEvent = BaseEvent> implements OnModuleInit, OnModuleDestroy {
|
|
97
|
+
protected readonly busConfig: EventBusModuleConfig;
|
|
98
|
+
protected readonly idempotencyStore: IdempotencyStore;
|
|
99
|
+
protected readonly errorClassifier: {
|
|
100
|
+
classify: (e: unknown) => ClassificationResult;
|
|
101
|
+
};
|
|
102
|
+
protected readonly dlqService: DlqService;
|
|
103
|
+
protected readonly logger: Logger;
|
|
104
|
+
protected abstract readonly config: SubscriberConfig;
|
|
105
|
+
private worker?;
|
|
106
|
+
private connection;
|
|
107
|
+
private metrics;
|
|
108
|
+
constructor(busConfig: EventBusModuleConfig, idempotencyStore: IdempotencyStore, errorClassifier: {
|
|
109
|
+
classify: (e: unknown) => ClassificationResult;
|
|
110
|
+
}, dlqService: DlqService);
|
|
111
|
+
/**
|
|
112
|
+
* Handle the event - implement in subclass
|
|
113
|
+
*/
|
|
114
|
+
protected abstract handle(event: TEvent): Promise<ProcessingResult>;
|
|
115
|
+
/**
|
|
116
|
+
* Called before processing starts (for setup/validation)
|
|
117
|
+
*/
|
|
118
|
+
protected beforeProcess(_event: TEvent): Promise<void>;
|
|
119
|
+
/**
|
|
120
|
+
* Called after processing completes (for cleanup)
|
|
121
|
+
*/
|
|
122
|
+
protected afterProcess(_event: TEvent, _result: ProcessingResult): Promise<void>;
|
|
123
|
+
onModuleInit(): Promise<void>;
|
|
124
|
+
onModuleDestroy(): Promise<void>;
|
|
125
|
+
/**
|
|
126
|
+
* Get queue name based on config
|
|
127
|
+
*/
|
|
128
|
+
protected getQueueName(): string;
|
|
129
|
+
/**
|
|
130
|
+
* Process a job with idempotency and error handling
|
|
131
|
+
*/
|
|
132
|
+
private processJob;
|
|
133
|
+
/**
|
|
134
|
+
* Handle processing error
|
|
135
|
+
*/
|
|
136
|
+
private handleError;
|
|
137
|
+
/**
|
|
138
|
+
* Check idempotency
|
|
139
|
+
*/
|
|
140
|
+
private checkIdempotency;
|
|
141
|
+
/**
|
|
142
|
+
* Send failed event to DLQ
|
|
143
|
+
*/
|
|
144
|
+
private sendToDlq;
|
|
145
|
+
/**
|
|
146
|
+
* Update metrics
|
|
147
|
+
*/
|
|
148
|
+
private updateMetrics;
|
|
149
|
+
/**
|
|
150
|
+
* Setup worker event listeners
|
|
151
|
+
*/
|
|
152
|
+
private setupWorkerListeners;
|
|
153
|
+
/**
|
|
154
|
+
* Get current metrics
|
|
155
|
+
*/
|
|
156
|
+
getMetrics(): SubscriberMetrics;
|
|
157
|
+
/**
|
|
158
|
+
* Pause processing
|
|
159
|
+
*/
|
|
160
|
+
pause(): Promise<void>;
|
|
161
|
+
/**
|
|
162
|
+
* Resume processing
|
|
163
|
+
*/
|
|
164
|
+
resume(): Promise<void>;
|
|
165
|
+
/**
|
|
166
|
+
* Check if worker is running
|
|
167
|
+
*/
|
|
168
|
+
isRunning(): boolean;
|
|
169
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
import { EventPriority } from '../core/types';
|
|
3
|
+
/**
|
|
4
|
+
* Decorators for event handling
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Metadata key for event handlers
|
|
8
|
+
*/
|
|
9
|
+
export declare const EVENT_HANDLER_METADATA = "EVENT_HANDLER_METADATA";
|
|
10
|
+
/**
|
|
11
|
+
* Event handler metadata
|
|
12
|
+
*/
|
|
13
|
+
export interface EventHandlerMetadata {
|
|
14
|
+
/** Event type to handle */
|
|
15
|
+
eventType: string;
|
|
16
|
+
/** Queue to listen on (derived from priority if not specified) */
|
|
17
|
+
queue?: string;
|
|
18
|
+
/** Priority level */
|
|
19
|
+
priority?: EventPriority;
|
|
20
|
+
/** Consumer group name */
|
|
21
|
+
consumerGroup?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Decorator to mark a method as an event handler
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* @Injectable()
|
|
29
|
+
* export class TradeSubscriber extends BaseSubscriber {
|
|
30
|
+
* @OnEvent('TradeProposalCreated', { priority: 'high' })
|
|
31
|
+
* async handleTradeCreated(event: TradeProposalCreated) {
|
|
32
|
+
* // Handle the event
|
|
33
|
+
* }
|
|
34
|
+
* }
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export declare function OnEvent(eventType: string, options?: Omit<EventHandlerMetadata, 'eventType'>): MethodDecorator;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { OnModuleDestroy, OnModuleInit } from '@nestjs/common';
|
|
2
|
+
import { ErrorClassification } from '../core/error-classification';
|
|
3
|
+
import { BaseEvent } from '../core/types';
|
|
4
|
+
import { EventBusModuleConfig } from './event-bus.module';
|
|
5
|
+
/**
|
|
6
|
+
* DLQ Service - Dead Letter Queue management
|
|
7
|
+
*
|
|
8
|
+
* Provides:
|
|
9
|
+
* - Send failed events to DLQ
|
|
10
|
+
* - Query and inspect DLQ entries
|
|
11
|
+
* - Replay events from DLQ
|
|
12
|
+
* - Purge old entries
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Entry in the Dead Letter Queue
|
|
16
|
+
*/
|
|
17
|
+
export interface DlqEntry<TEvent extends BaseEvent = BaseEvent> {
|
|
18
|
+
/** The failed event */
|
|
19
|
+
event: TEvent;
|
|
20
|
+
/** Error information */
|
|
21
|
+
error: {
|
|
22
|
+
message: string;
|
|
23
|
+
stack?: string;
|
|
24
|
+
classification: ErrorClassification;
|
|
25
|
+
reason: string;
|
|
26
|
+
};
|
|
27
|
+
/** Subscriber that failed */
|
|
28
|
+
subscriber: string;
|
|
29
|
+
/** Number of attempts before giving up */
|
|
30
|
+
attempts: number;
|
|
31
|
+
/** When the event was sent to DLQ */
|
|
32
|
+
failedAt: string;
|
|
33
|
+
/** Original queue the event was in */
|
|
34
|
+
originalQueue?: string;
|
|
35
|
+
/** Additional context */
|
|
36
|
+
context?: Record<string, unknown>;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Options for querying DLQ
|
|
40
|
+
*/
|
|
41
|
+
export interface DlqQueryOptions {
|
|
42
|
+
/** Filter by event type */
|
|
43
|
+
eventType?: string;
|
|
44
|
+
/** Filter by subscriber */
|
|
45
|
+
subscriber?: string;
|
|
46
|
+
/** Filter by error classification */
|
|
47
|
+
classification?: ErrorClassification;
|
|
48
|
+
/** Filter by date range */
|
|
49
|
+
from?: Date;
|
|
50
|
+
to?: Date;
|
|
51
|
+
/** Pagination */
|
|
52
|
+
start?: number;
|
|
53
|
+
end?: number;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* DLQ statistics
|
|
57
|
+
*/
|
|
58
|
+
export interface DlqStats {
|
|
59
|
+
total: number;
|
|
60
|
+
byEventType: Record<string, number>;
|
|
61
|
+
bySubscriber: Record<string, number>;
|
|
62
|
+
byClassification: Record<ErrorClassification, number>;
|
|
63
|
+
oldestEntry?: Date;
|
|
64
|
+
newestEntry?: Date;
|
|
65
|
+
}
|
|
66
|
+
export declare class DlqService implements OnModuleInit, OnModuleDestroy {
|
|
67
|
+
private readonly config;
|
|
68
|
+
private readonly logger;
|
|
69
|
+
private queue?;
|
|
70
|
+
private queueName;
|
|
71
|
+
constructor(config: EventBusModuleConfig);
|
|
72
|
+
onModuleInit(): Promise<void>;
|
|
73
|
+
onModuleDestroy(): Promise<void>;
|
|
74
|
+
/**
|
|
75
|
+
* Send an event to the DLQ
|
|
76
|
+
*/
|
|
77
|
+
send(entry: DlqEntry): Promise<string>;
|
|
78
|
+
/**
|
|
79
|
+
* Get entries from DLQ
|
|
80
|
+
*/
|
|
81
|
+
getEntries(options?: DlqQueryOptions): Promise<DlqEntry[]>;
|
|
82
|
+
/**
|
|
83
|
+
* Get a specific entry by event ID
|
|
84
|
+
*/
|
|
85
|
+
getEntry(eventId: string, subscriber?: string): Promise<DlqEntry | null>;
|
|
86
|
+
/**
|
|
87
|
+
* Get DLQ statistics
|
|
88
|
+
*/
|
|
89
|
+
getStats(): Promise<DlqStats>;
|
|
90
|
+
/**
|
|
91
|
+
* Replay an event from DLQ
|
|
92
|
+
*
|
|
93
|
+
* This removes the event from DLQ and republishes to original queue
|
|
94
|
+
*/
|
|
95
|
+
replay(eventId: string, subscriber: string, targetQueue: string): Promise<boolean>;
|
|
96
|
+
/**
|
|
97
|
+
* Replay all events matching criteria
|
|
98
|
+
*/
|
|
99
|
+
replayBatch(options: DlqQueryOptions & {
|
|
100
|
+
targetQueue: string;
|
|
101
|
+
}): Promise<{
|
|
102
|
+
replayed: number;
|
|
103
|
+
failed: number;
|
|
104
|
+
}>;
|
|
105
|
+
/**
|
|
106
|
+
* Remove an entry from DLQ
|
|
107
|
+
*/
|
|
108
|
+
remove(eventId: string, subscriber: string): Promise<boolean>;
|
|
109
|
+
/**
|
|
110
|
+
* Purge old entries from DLQ
|
|
111
|
+
*/
|
|
112
|
+
purge(olderThan: Date): Promise<number>;
|
|
113
|
+
/**
|
|
114
|
+
* Clear all entries from DLQ
|
|
115
|
+
*/
|
|
116
|
+
clear(): Promise<number>;
|
|
117
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { DynamicModule, InjectionToken, OptionalFactoryDependency, Type } from '@nestjs/common';
|
|
2
|
+
import { ErrorClassifierConfig, EventRegistryConfig, IdempotencyStore } from '..';
|
|
3
|
+
/**
|
|
4
|
+
* EventBus Module - NestJS module for event-driven architecture
|
|
5
|
+
*
|
|
6
|
+
* Provides:
|
|
7
|
+
* - BullMQ queue integration
|
|
8
|
+
* - Event publishing with priority routing
|
|
9
|
+
* - Idempotency and retry handling
|
|
10
|
+
* - DLQ management
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Redis connection configuration
|
|
14
|
+
*/
|
|
15
|
+
export interface RedisConfig {
|
|
16
|
+
host: string;
|
|
17
|
+
port: number;
|
|
18
|
+
password?: string;
|
|
19
|
+
db?: number;
|
|
20
|
+
tls?: boolean;
|
|
21
|
+
maxRetriesPerRequest?: number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Queue configuration
|
|
25
|
+
*/
|
|
26
|
+
export interface QueueConfig {
|
|
27
|
+
/** Queue name */
|
|
28
|
+
name: string;
|
|
29
|
+
/** Priority levels routed to this queue */
|
|
30
|
+
priorities: string[];
|
|
31
|
+
/** Concurrency for this queue */
|
|
32
|
+
concurrency?: number;
|
|
33
|
+
/** Rate limit per interval */
|
|
34
|
+
rateLimit?: {
|
|
35
|
+
max: number;
|
|
36
|
+
duration: number;
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* EventBus module configuration
|
|
41
|
+
*/
|
|
42
|
+
export interface EventBusModuleConfig {
|
|
43
|
+
/** Redis connection config */
|
|
44
|
+
redis: RedisConfig;
|
|
45
|
+
/** Queue configuration (defaults to priority-based queues) */
|
|
46
|
+
queues?: QueueConfig[];
|
|
47
|
+
/** Event registry configuration */
|
|
48
|
+
registry?: EventRegistryConfig;
|
|
49
|
+
/** Custom idempotency store (defaults to in-memory) */
|
|
50
|
+
idempotencyStore?: IdempotencyStore;
|
|
51
|
+
/** Error classifier configuration */
|
|
52
|
+
errorClassifier?: ErrorClassifierConfig;
|
|
53
|
+
/** Enable DLQ (default: true) */
|
|
54
|
+
enableDlq?: boolean;
|
|
55
|
+
/** DLQ queue name (default: 'dead-letter') */
|
|
56
|
+
dlqQueueName?: string;
|
|
57
|
+
/** Global concurrency limit */
|
|
58
|
+
globalConcurrency?: number;
|
|
59
|
+
/** Enable metrics (default: true) */
|
|
60
|
+
enableMetrics?: boolean;
|
|
61
|
+
/** Metrics prefix */
|
|
62
|
+
metricsPrefix?: string;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Async configuration options
|
|
66
|
+
*/
|
|
67
|
+
export interface EventBusModuleAsyncConfig {
|
|
68
|
+
imports?: Type<unknown>[];
|
|
69
|
+
useFactory: (...args: unknown[]) => Promise<EventBusModuleConfig> | EventBusModuleConfig;
|
|
70
|
+
inject?: (InjectionToken | OptionalFactoryDependency)[];
|
|
71
|
+
}
|
|
72
|
+
export declare class EventBusModule {
|
|
73
|
+
/**
|
|
74
|
+
* Register the EventBus module with configuration
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```typescript
|
|
78
|
+
* @Module({
|
|
79
|
+
* imports: [
|
|
80
|
+
* EventBusModule.forRoot({
|
|
81
|
+
* redis: { host: 'localhost', port: 6379 },
|
|
82
|
+
* queues: [
|
|
83
|
+
* { name: 'critical', priorities: ['critical'], concurrency: 10 },
|
|
84
|
+
* { name: 'normal', priorities: ['high', 'normal'], concurrency: 5 },
|
|
85
|
+
* ],
|
|
86
|
+
* }),
|
|
87
|
+
* ],
|
|
88
|
+
* })
|
|
89
|
+
* export class AppModule {}
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
static forRoot(config: EventBusModuleConfig): DynamicModule;
|
|
93
|
+
/**
|
|
94
|
+
* Register the EventBus module with async configuration
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* ```typescript
|
|
98
|
+
* @Module({
|
|
99
|
+
* imports: [
|
|
100
|
+
* EventBusModule.forRootAsync({
|
|
101
|
+
* imports: [ConfigModule],
|
|
102
|
+
* useFactory: (configService: ConfigService) => ({
|
|
103
|
+
* redis: {
|
|
104
|
+
* host: configService.get('REDIS_HOST'),
|
|
105
|
+
* port: configService.get('REDIS_PORT'),
|
|
106
|
+
* },
|
|
107
|
+
* }),
|
|
108
|
+
* inject: [ConfigService],
|
|
109
|
+
* }),
|
|
110
|
+
* ],
|
|
111
|
+
* })
|
|
112
|
+
* export class AppModule {}
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
static forRootAsync(asyncConfig: EventBusModuleAsyncConfig): DynamicModule;
|
|
116
|
+
private static createProviders;
|
|
117
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { OnModuleDestroy, OnModuleInit } from '@nestjs/common';
|
|
2
|
+
import { Queue } from 'bullmq';
|
|
3
|
+
import { EventRegistry } from '../core/registry';
|
|
4
|
+
import { BaseEvent, EventPriority } from '../core/types';
|
|
5
|
+
import { EventBusModuleConfig } from './event-bus.module';
|
|
6
|
+
/**
|
|
7
|
+
* EventBus Service - Publish events to BullMQ queues
|
|
8
|
+
*
|
|
9
|
+
* Provides:
|
|
10
|
+
* - Event publishing with validation
|
|
11
|
+
* - Priority-based routing
|
|
12
|
+
* - Correlation ID propagation
|
|
13
|
+
* - Metrics collection
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Options for publishing an event
|
|
17
|
+
*/
|
|
18
|
+
export interface PublishOptions {
|
|
19
|
+
/** Override event ID */
|
|
20
|
+
id?: string;
|
|
21
|
+
/** Correlation ID for request tracing */
|
|
22
|
+
correlationId?: string;
|
|
23
|
+
/** Causation ID (parent event) */
|
|
24
|
+
causationId?: string;
|
|
25
|
+
/** Delay before processing (ms) */
|
|
26
|
+
delay?: number;
|
|
27
|
+
/** Override priority */
|
|
28
|
+
priority?: EventPriority;
|
|
29
|
+
/** Specific queue to publish to */
|
|
30
|
+
queue?: string;
|
|
31
|
+
/** Job ID for deduplication */
|
|
32
|
+
jobId?: string;
|
|
33
|
+
/** Custom job options */
|
|
34
|
+
jobOptions?: Record<string, unknown>;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Result of publishing an event
|
|
38
|
+
*/
|
|
39
|
+
export interface PublishResult {
|
|
40
|
+
/** Event ID */
|
|
41
|
+
eventId: string;
|
|
42
|
+
/** BullMQ Job ID */
|
|
43
|
+
jobId: string;
|
|
44
|
+
/** Queue the event was published to */
|
|
45
|
+
queue: string;
|
|
46
|
+
/** Correlation ID */
|
|
47
|
+
correlationId: string;
|
|
48
|
+
}
|
|
49
|
+
export declare class EventBusService implements OnModuleInit, OnModuleDestroy {
|
|
50
|
+
private readonly config;
|
|
51
|
+
private readonly registry;
|
|
52
|
+
private readonly logger;
|
|
53
|
+
private queues;
|
|
54
|
+
private priorityToQueue;
|
|
55
|
+
private connection;
|
|
56
|
+
private isReady;
|
|
57
|
+
constructor(config: EventBusModuleConfig, registry: EventRegistry);
|
|
58
|
+
onModuleInit(): Promise<void>;
|
|
59
|
+
onModuleDestroy(): Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* Publish an event
|
|
62
|
+
*
|
|
63
|
+
* @example
|
|
64
|
+
* ```typescript
|
|
65
|
+
* await eventBus.publish({
|
|
66
|
+
* type: 'TradeProposalCreated',
|
|
67
|
+
* data: {
|
|
68
|
+
* tradeId: '123',
|
|
69
|
+
* initiatorId: 'user-1',
|
|
70
|
+
* recipientId: 'user-2',
|
|
71
|
+
* },
|
|
72
|
+
* });
|
|
73
|
+
* ```
|
|
74
|
+
*/
|
|
75
|
+
publish<T extends BaseEvent>(event: Omit<T, 'id' | 'timestamp'> & Partial<Pick<T, 'id' | 'timestamp'>>, options?: PublishOptions): Promise<PublishResult>;
|
|
76
|
+
/**
|
|
77
|
+
* Publish multiple events in a batch
|
|
78
|
+
*/
|
|
79
|
+
publishBatch<T extends BaseEvent>(events: Array<Omit<T, 'id' | 'timestamp'> & Partial<Pick<T, 'id' | 'timestamp'>>>, options?: Omit<PublishOptions, 'id' | 'jobId'>): Promise<PublishResult[]>;
|
|
80
|
+
/**
|
|
81
|
+
* Get queue for a given priority
|
|
82
|
+
*/
|
|
83
|
+
getQueueForPriority(priority: EventPriority): string;
|
|
84
|
+
/**
|
|
85
|
+
* Get queue stats
|
|
86
|
+
*/
|
|
87
|
+
getQueueStats(queueName: string): Promise<{
|
|
88
|
+
waiting: number;
|
|
89
|
+
active: number;
|
|
90
|
+
completed: number;
|
|
91
|
+
failed: number;
|
|
92
|
+
delayed: number;
|
|
93
|
+
}>;
|
|
94
|
+
/**
|
|
95
|
+
* Get all queue names
|
|
96
|
+
*/
|
|
97
|
+
getQueueNames(): string[];
|
|
98
|
+
/**
|
|
99
|
+
* Get underlying BullMQ queue for advanced operations
|
|
100
|
+
*/
|
|
101
|
+
getQueue(name: string): Queue | undefined;
|
|
102
|
+
/**
|
|
103
|
+
* Check if service is ready
|
|
104
|
+
*/
|
|
105
|
+
isServiceReady(): boolean;
|
|
106
|
+
/**
|
|
107
|
+
* Convert priority string to number for BullMQ (lower = higher priority)
|
|
108
|
+
*/
|
|
109
|
+
private getPriorityNumber;
|
|
110
|
+
/**
|
|
111
|
+
* Setup event listeners for monitoring
|
|
112
|
+
*/
|
|
113
|
+
private setupQueueEventListeners;
|
|
114
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @signaltree/events/nestjs
|
|
3
|
+
*
|
|
4
|
+
* NestJS integration for event-driven architecture.
|
|
5
|
+
* Provides EventBusModule, BaseSubscriber, and DLQ handling.
|
|
6
|
+
*/
|
|
7
|
+
export { EventBusModule } from './event-bus.module';
|
|
8
|
+
export type { EventBusModuleConfig, EventBusModuleAsyncConfig, } from './event-bus.module';
|
|
9
|
+
export { EventBusService } from './event-bus.service';
|
|
10
|
+
export type { PublishOptions, PublishResult } from './event-bus.service';
|
|
11
|
+
export { BaseSubscriber } from './base.subscriber';
|
|
12
|
+
export type { SubscriberConfig, ProcessingResult, SubscriberMetrics, } from './base.subscriber';
|
|
13
|
+
export { DlqService } from './dlq.service';
|
|
14
|
+
export type { DlqEntry, DlqQueryOptions, DlqStats } from './dlq.service';
|
|
15
|
+
export { OnEvent, EVENT_HANDLER_METADATA } from './decorators';
|
|
16
|
+
export { EVENT_BUS_CONFIG, EVENT_REGISTRY, IDEMPOTENCY_STORE, ERROR_CLASSIFIER, } from './tokens';
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Injection tokens for the EventBus module
|
|
3
|
+
*/
|
|
4
|
+
export declare const EVENT_BUS_CONFIG: unique symbol;
|
|
5
|
+
export declare const EVENT_REGISTRY: unique symbol;
|
|
6
|
+
export declare const IDEMPOTENCY_STORE: unique symbol;
|
|
7
|
+
export declare const ERROR_CLASSIFIER: unique symbol;
|
|
8
|
+
export declare const DLQ_SERVICE: unique symbol;
|
package/src/nestjs.d.ts
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { BaseEvent } from '../core/types';
|
|
2
|
+
import { PublishedEvent } from './mock-event-bus';
|
|
3
|
+
/**
|
|
4
|
+
* Test Assertions - Assertion helpers for event testing
|
|
5
|
+
*
|
|
6
|
+
* Provides:
|
|
7
|
+
* - Fluent assertion API
|
|
8
|
+
* - Event structure validation
|
|
9
|
+
* - Sequence assertions
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Assertion result
|
|
13
|
+
*/
|
|
14
|
+
export interface AssertionResult {
|
|
15
|
+
passed: boolean;
|
|
16
|
+
message: string;
|
|
17
|
+
expected?: unknown;
|
|
18
|
+
actual?: unknown;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Event assertions class
|
|
22
|
+
*/
|
|
23
|
+
export declare class EventAssertions {
|
|
24
|
+
private publishedEvents;
|
|
25
|
+
constructor(events: PublishedEvent[]);
|
|
26
|
+
/**
|
|
27
|
+
* Assert that an event was published
|
|
28
|
+
*/
|
|
29
|
+
toHavePublished(eventType: string): AssertionResult;
|
|
30
|
+
/**
|
|
31
|
+
* Assert that an event was NOT published
|
|
32
|
+
*/
|
|
33
|
+
toNotHavePublished(eventType: string): AssertionResult;
|
|
34
|
+
/**
|
|
35
|
+
* Assert the number of published events
|
|
36
|
+
*/
|
|
37
|
+
toHavePublishedCount(count: number, eventType?: string): AssertionResult;
|
|
38
|
+
/**
|
|
39
|
+
* Assert event was published with specific data
|
|
40
|
+
*/
|
|
41
|
+
toHavePublishedWith<T extends BaseEvent>(eventType: T['type'], predicate: (event: T) => boolean): AssertionResult;
|
|
42
|
+
/**
|
|
43
|
+
* Assert events were published in order
|
|
44
|
+
*/
|
|
45
|
+
toHavePublishedInOrder(eventTypes: string[]): AssertionResult;
|
|
46
|
+
/**
|
|
47
|
+
* Assert event has valid structure
|
|
48
|
+
*/
|
|
49
|
+
toBeValidEvent<T extends BaseEvent>(event: T): AssertionResult;
|
|
50
|
+
/**
|
|
51
|
+
* Assert all events have same correlation ID
|
|
52
|
+
*/
|
|
53
|
+
toHaveSameCorrelationId(): AssertionResult;
|
|
54
|
+
/**
|
|
55
|
+
* Assert event was published to specific queue
|
|
56
|
+
*/
|
|
57
|
+
toHavePublishedToQueue(eventType: string, queue: string): AssertionResult;
|
|
58
|
+
/**
|
|
59
|
+
* Get all assertions as an array
|
|
60
|
+
*/
|
|
61
|
+
getAllResults(): AssertionResult[];
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Create event assertions helper
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* ```typescript
|
|
68
|
+
* const eventBus = createMockEventBus();
|
|
69
|
+
* // ... publish events ...
|
|
70
|
+
*
|
|
71
|
+
* const assertions = createEventAssertions(eventBus.getPublishedEvents());
|
|
72
|
+
*
|
|
73
|
+
* expect(assertions.toHavePublished('TradeCreated').passed).toBe(true);
|
|
74
|
+
* expect(assertions.toHavePublishedCount(3).passed).toBe(true);
|
|
75
|
+
* expect(assertions.toHavePublishedInOrder(['TradeCreated', 'TradeAccepted']).passed).toBe(true);
|
|
76
|
+
* ```
|
|
77
|
+
*/
|
|
78
|
+
export declare function createEventAssertions(events: PublishedEvent[]): EventAssertions;
|
|
79
|
+
/**
|
|
80
|
+
* Jest/Vitest custom matcher helpers
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```typescript
|
|
84
|
+
* // In your test setup
|
|
85
|
+
* expect.extend({
|
|
86
|
+
* toHavePublishedEvent(received: MockEventBus, eventType: string) {
|
|
87
|
+
* const assertions = createEventAssertions(received.getPublishedEvents());
|
|
88
|
+
* const result = assertions.toHavePublished(eventType);
|
|
89
|
+
* return {
|
|
90
|
+
* pass: result.passed,
|
|
91
|
+
* message: () => result.message,
|
|
92
|
+
* };
|
|
93
|
+
* },
|
|
94
|
+
* });
|
|
95
|
+
*
|
|
96
|
+
* // In your test
|
|
97
|
+
* expect(eventBus).toHavePublishedEvent('TradeCreated');
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
export declare const jestMatchers: {
|
|
101
|
+
toHavePublishedEvent(received: {
|
|
102
|
+
getPublishedEvents: () => PublishedEvent[];
|
|
103
|
+
}, eventType: string): {
|
|
104
|
+
pass: boolean;
|
|
105
|
+
message: () => string;
|
|
106
|
+
};
|
|
107
|
+
toHavePublishedEventCount(received: {
|
|
108
|
+
getPublishedEvents: () => PublishedEvent[];
|
|
109
|
+
}, count: number, eventType?: string): {
|
|
110
|
+
pass: boolean;
|
|
111
|
+
message: () => string;
|
|
112
|
+
};
|
|
113
|
+
};
|