@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
package/package.json
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@signaltree/events",
|
|
3
|
+
"version": "7.3.1",
|
|
4
|
+
"description": "Event-driven architecture infrastructure for SignalTree - event bus, subscribers, validation, and real-time sync",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"main": "./index.esm.js",
|
|
9
|
+
"module": "./index.esm.js",
|
|
10
|
+
"types": "./index.d.ts",
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"import": "./index.esm.js",
|
|
14
|
+
"types": "./index.d.ts"
|
|
15
|
+
},
|
|
16
|
+
"./nestjs": {
|
|
17
|
+
"import": "./nestjs.esm.js",
|
|
18
|
+
"types": "./nestjs.d.ts"
|
|
19
|
+
},
|
|
20
|
+
"./angular": {
|
|
21
|
+
"import": "./angular.esm.js",
|
|
22
|
+
"types": "./angular.d.ts"
|
|
23
|
+
},
|
|
24
|
+
"./testing": {
|
|
25
|
+
"import": "./testing.esm.js",
|
|
26
|
+
"types": "./testing.d.ts"
|
|
27
|
+
},
|
|
28
|
+
"./package.json": "./package.json"
|
|
29
|
+
},
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"zod": "^3.0.0",
|
|
32
|
+
"@angular/core": "^18.0.0 || ^19.0.0 || ^20.0.0",
|
|
33
|
+
"rxjs": "^7.0.0",
|
|
34
|
+
"@nestjs/common": "^10.0.0 || ^11.0.0",
|
|
35
|
+
"bullmq": "^5.0.0",
|
|
36
|
+
"reflect-metadata": "^0.1.13 || ^0.2.0"
|
|
37
|
+
},
|
|
38
|
+
"peerDependenciesMeta": {
|
|
39
|
+
"@nestjs/common": {
|
|
40
|
+
"optional": true
|
|
41
|
+
},
|
|
42
|
+
"@nestjs/core": {
|
|
43
|
+
"optional": true
|
|
44
|
+
},
|
|
45
|
+
"bullmq": {
|
|
46
|
+
"optional": true
|
|
47
|
+
},
|
|
48
|
+
"ioredis": {
|
|
49
|
+
"optional": true
|
|
50
|
+
},
|
|
51
|
+
"@angular/core": {
|
|
52
|
+
"optional": true
|
|
53
|
+
},
|
|
54
|
+
"rxjs": {
|
|
55
|
+
"optional": true
|
|
56
|
+
},
|
|
57
|
+
"reflect-metadata": {
|
|
58
|
+
"optional": true
|
|
59
|
+
},
|
|
60
|
+
"@signaltree/core": {
|
|
61
|
+
"optional": true
|
|
62
|
+
},
|
|
63
|
+
"socket.io-client": {
|
|
64
|
+
"optional": true
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
"devDependencies": {
|
|
68
|
+
"@nestjs/common": "^11.0.0",
|
|
69
|
+
"@nestjs/core": "^11.0.0",
|
|
70
|
+
"@angular/core": "^20.0.0",
|
|
71
|
+
"@signaltree/core": "workspace:*",
|
|
72
|
+
"bullmq": "^5.0.0",
|
|
73
|
+
"ioredis": "^5.0.0",
|
|
74
|
+
"socket.io-client": "^4.0.0",
|
|
75
|
+
"rxjs": "^7.8.0",
|
|
76
|
+
"reflect-metadata": "^0.2.0",
|
|
77
|
+
"zod": "^3.24.0",
|
|
78
|
+
"vitest": "^3.0.5"
|
|
79
|
+
},
|
|
80
|
+
"publishConfig": {
|
|
81
|
+
"access": "public"
|
|
82
|
+
},
|
|
83
|
+
"files": [
|
|
84
|
+
"*.esm.js",
|
|
85
|
+
"*.d.ts",
|
|
86
|
+
"src/**/*.d.ts",
|
|
87
|
+
"README.md"
|
|
88
|
+
],
|
|
89
|
+
"keywords": [
|
|
90
|
+
"signaltree",
|
|
91
|
+
"events",
|
|
92
|
+
"event-driven",
|
|
93
|
+
"event-bus",
|
|
94
|
+
"cqrs",
|
|
95
|
+
"bullmq",
|
|
96
|
+
"nestjs",
|
|
97
|
+
"angular",
|
|
98
|
+
"websocket",
|
|
99
|
+
"real-time"
|
|
100
|
+
],
|
|
101
|
+
"repository": {
|
|
102
|
+
"type": "git",
|
|
103
|
+
"url": "https://github.com/JBorgia/signaltree.git",
|
|
104
|
+
"directory": "packages/events"
|
|
105
|
+
},
|
|
106
|
+
"bugs": {
|
|
107
|
+
"url": "https://github.com/JBorgia/signaltree/issues"
|
|
108
|
+
},
|
|
109
|
+
"homepage": "https://github.com/JBorgia/signaltree/tree/main/packages/events#readme"
|
|
110
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { BaseEvent } from '../core/types';
|
|
2
|
+
/**
|
|
3
|
+
* Event Handlers - Utilities for handling events in Angular
|
|
4
|
+
*
|
|
5
|
+
* Provides:
|
|
6
|
+
* - Type-safe event handlers
|
|
7
|
+
* - Handler composition
|
|
8
|
+
* - Store integration helpers
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Event handler function type
|
|
12
|
+
*/
|
|
13
|
+
export type EventHandler<T extends BaseEvent = BaseEvent> = (event: T) => void | Promise<void>;
|
|
14
|
+
/**
|
|
15
|
+
* Typed event handler with metadata
|
|
16
|
+
*/
|
|
17
|
+
export interface TypedEventHandler<T extends BaseEvent = BaseEvent> {
|
|
18
|
+
/** Event type this handler processes */
|
|
19
|
+
eventType: T['type'];
|
|
20
|
+
/** Handler function */
|
|
21
|
+
handle: EventHandler<T>;
|
|
22
|
+
/** Optional priority (lower = higher priority) */
|
|
23
|
+
priority?: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Create a simple event handler
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* const handler = createEventHandler<TradeProposalCreated>(event => {
|
|
31
|
+
* store.$.trades.entities.upsertOne(event.data);
|
|
32
|
+
* });
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
export declare function createEventHandler<T extends BaseEvent>(handler: EventHandler<T>): EventHandler<T>;
|
|
36
|
+
/**
|
|
37
|
+
* Create a typed event handler with metadata
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```typescript
|
|
41
|
+
* const handler = createTypedHandler('TradeProposalCreated', {
|
|
42
|
+
* handle: (event) => {
|
|
43
|
+
* store.$.trades.entities.upsertOne(event.data);
|
|
44
|
+
* },
|
|
45
|
+
* priority: 1,
|
|
46
|
+
* });
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
export declare function createTypedHandler<T extends BaseEvent>(eventType: T['type'], options: {
|
|
50
|
+
handle: EventHandler<T>;
|
|
51
|
+
priority?: number;
|
|
52
|
+
}): TypedEventHandler<T>;
|
|
53
|
+
/**
|
|
54
|
+
* Create a handler registry for managing multiple handlers
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```typescript
|
|
58
|
+
* const registry = createHandlerRegistry();
|
|
59
|
+
*
|
|
60
|
+
* registry.register('TradeProposalCreated', (event) => {
|
|
61
|
+
* store.$.trades.entities.upsertOne(event.data);
|
|
62
|
+
* });
|
|
63
|
+
*
|
|
64
|
+
* registry.register('TradeAccepted', (event) => {
|
|
65
|
+
* store.$.trades.entities.update(event.data.tradeId, { status: 'accepted' });
|
|
66
|
+
* });
|
|
67
|
+
*
|
|
68
|
+
* // In WebSocket service
|
|
69
|
+
* protected onEventReceived(event: BaseEvent): void {
|
|
70
|
+
* registry.dispatch(event);
|
|
71
|
+
* }
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
export declare function createHandlerRegistry(): {
|
|
75
|
+
register: <T extends BaseEvent>(eventType: T['type'], handler: EventHandler<T>) => void;
|
|
76
|
+
unregister: (eventType: string, handler?: EventHandler) => void;
|
|
77
|
+
dispatch: (event: BaseEvent) => Promise<void>;
|
|
78
|
+
getHandlers: (eventType: string) => EventHandler[];
|
|
79
|
+
clear: () => void;
|
|
80
|
+
};
|
|
81
|
+
/**
|
|
82
|
+
* Compose multiple handlers into one
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```typescript
|
|
86
|
+
* const composedHandler = composeHandlers(
|
|
87
|
+
* logHandler,
|
|
88
|
+
* metricsHandler,
|
|
89
|
+
* storeHandler,
|
|
90
|
+
* );
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
export declare function composeHandlers<T extends BaseEvent>(...handlers: EventHandler<T>[]): EventHandler<T>;
|
|
94
|
+
/**
|
|
95
|
+
* Create a conditional handler that only runs if predicate is true
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```typescript
|
|
99
|
+
* const handler = conditionalHandler(
|
|
100
|
+
* (event) => event.data.status === 'pending',
|
|
101
|
+
* (event) => processPendingTrade(event),
|
|
102
|
+
* );
|
|
103
|
+
* ```
|
|
104
|
+
*/
|
|
105
|
+
export declare function conditionalHandler<T extends BaseEvent>(predicate: (event: T) => boolean, handler: EventHandler<T>): EventHandler<T>;
|
|
106
|
+
/**
|
|
107
|
+
* Create a handler that debounces rapid events
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* ```typescript
|
|
111
|
+
* const handler = debouncedHandler(
|
|
112
|
+
* (event) => updateUI(event),
|
|
113
|
+
* 100, // 100ms debounce
|
|
114
|
+
* );
|
|
115
|
+
* ```
|
|
116
|
+
*/
|
|
117
|
+
export declare function debouncedHandler<T extends BaseEvent>(handler: EventHandler<T>, delayMs: number): EventHandler<T>;
|
|
118
|
+
/**
|
|
119
|
+
* Create a handler that batches events and processes them together
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```typescript
|
|
123
|
+
* const handler = batchedHandler(
|
|
124
|
+
* (events) => {
|
|
125
|
+
* store.$.trades.entities.upsertMany(events.map(e => e.data));
|
|
126
|
+
* },
|
|
127
|
+
* 50, // Process batch every 50ms
|
|
128
|
+
* 100, // Or when batch reaches 100 events
|
|
129
|
+
* );
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
export declare function batchedHandler<T extends BaseEvent>(handler: (events: T[]) => void | Promise<void>, flushIntervalMs: number, maxBatchSize?: number): EventHandler<T>;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @signaltree/events/angular
|
|
3
|
+
*
|
|
4
|
+
* Angular integration for real-time event synchronization.
|
|
5
|
+
* Provides WebSocket service base for SignalTree integration.
|
|
6
|
+
*/
|
|
7
|
+
export { WebSocketService } from './websocket.service';
|
|
8
|
+
export type { WebSocketConfig, ConnectionState, WebSocketMessage, } from './websocket.service';
|
|
9
|
+
export { OptimisticUpdateManager } from './optimistic-updates';
|
|
10
|
+
export type { OptimisticUpdate, UpdateResult } from './optimistic-updates';
|
|
11
|
+
export { createEventHandler, createTypedHandler } from './handlers';
|
|
12
|
+
export type { EventHandler, TypedEventHandler } from './handlers';
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { Signal } from '@angular/core';
|
|
2
|
+
/**
|
|
3
|
+
* Optimistic Update Manager - Handle optimistic UI updates with rollback
|
|
4
|
+
*
|
|
5
|
+
* Provides:
|
|
6
|
+
* - Track pending updates
|
|
7
|
+
* - Automatic rollback on failure
|
|
8
|
+
* - Correlation with server events
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Pending optimistic update
|
|
12
|
+
*/
|
|
13
|
+
export interface OptimisticUpdate<T = unknown> {
|
|
14
|
+
/** Unique ID for tracking */
|
|
15
|
+
id: string;
|
|
16
|
+
/** Correlation ID to match with server response */
|
|
17
|
+
correlationId: string;
|
|
18
|
+
/** Type of update */
|
|
19
|
+
type: string;
|
|
20
|
+
/** Optimistic data applied to UI */
|
|
21
|
+
data: T;
|
|
22
|
+
/** Previous data for rollback */
|
|
23
|
+
previousData: T;
|
|
24
|
+
/** When the update was applied */
|
|
25
|
+
appliedAt: Date;
|
|
26
|
+
/** Timeout for automatic rollback (ms) */
|
|
27
|
+
timeoutMs: number;
|
|
28
|
+
/** Rollback function */
|
|
29
|
+
rollback: () => void;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Result of an optimistic update
|
|
33
|
+
*/
|
|
34
|
+
export interface UpdateResult {
|
|
35
|
+
success: boolean;
|
|
36
|
+
correlationId: string;
|
|
37
|
+
error?: Error;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Optimistic Update Manager
|
|
41
|
+
*
|
|
42
|
+
* Tracks optimistic updates and handles confirmation/rollback.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* ```typescript
|
|
46
|
+
* const manager = new OptimisticUpdateManager();
|
|
47
|
+
*
|
|
48
|
+
* // Apply optimistic update
|
|
49
|
+
* manager.apply({
|
|
50
|
+
* id: 'update-1',
|
|
51
|
+
* correlationId: 'corr-123',
|
|
52
|
+
* type: 'UpdateTradeStatus',
|
|
53
|
+
* data: { status: 'accepted' },
|
|
54
|
+
* previousData: { status: 'pending' },
|
|
55
|
+
* timeoutMs: 5000,
|
|
56
|
+
* rollback: () => store.$.trade.status.set('pending'),
|
|
57
|
+
* });
|
|
58
|
+
*
|
|
59
|
+
* // When server confirms
|
|
60
|
+
* manager.confirm('corr-123');
|
|
61
|
+
*
|
|
62
|
+
* // Or when server rejects
|
|
63
|
+
* manager.rollback('corr-123', new Error('Server rejected'));
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
export declare class OptimisticUpdateManager {
|
|
67
|
+
private readonly _updates;
|
|
68
|
+
private timeouts;
|
|
69
|
+
/**
|
|
70
|
+
* Number of pending updates
|
|
71
|
+
*/
|
|
72
|
+
readonly pendingCount: Signal<number>;
|
|
73
|
+
/**
|
|
74
|
+
* Whether there are any pending updates
|
|
75
|
+
*/
|
|
76
|
+
readonly hasPending: Signal<boolean>;
|
|
77
|
+
/**
|
|
78
|
+
* Get all pending updates
|
|
79
|
+
*/
|
|
80
|
+
readonly pending: Signal<OptimisticUpdate[]>;
|
|
81
|
+
/**
|
|
82
|
+
* Apply an optimistic update
|
|
83
|
+
*/
|
|
84
|
+
apply<T>(update: OptimisticUpdate<T>): void;
|
|
85
|
+
/**
|
|
86
|
+
* Confirm an optimistic update (server accepted)
|
|
87
|
+
*/
|
|
88
|
+
confirm(correlationId: string): boolean;
|
|
89
|
+
/**
|
|
90
|
+
* Rollback an optimistic update (server rejected or timeout)
|
|
91
|
+
*/
|
|
92
|
+
rollback(correlationId: string, error?: Error): boolean;
|
|
93
|
+
/**
|
|
94
|
+
* Rollback all pending updates
|
|
95
|
+
*/
|
|
96
|
+
rollbackAll(error?: Error): number;
|
|
97
|
+
/**
|
|
98
|
+
* Get update by correlation ID
|
|
99
|
+
*/
|
|
100
|
+
get(correlationId: string): OptimisticUpdate | undefined;
|
|
101
|
+
/**
|
|
102
|
+
* Check if an update is pending
|
|
103
|
+
*/
|
|
104
|
+
isPending(correlationId: string): boolean;
|
|
105
|
+
/**
|
|
106
|
+
* Clear all updates without rollback (use with caution)
|
|
107
|
+
*/
|
|
108
|
+
clear(): void;
|
|
109
|
+
/**
|
|
110
|
+
* Dispose the manager
|
|
111
|
+
*/
|
|
112
|
+
dispose(): void;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Create an optimistic update manager instance
|
|
116
|
+
*/
|
|
117
|
+
export declare function createOptimisticUpdateManager(): OptimisticUpdateManager;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { OnDestroy, Signal } from '@angular/core';
|
|
2
|
+
import { Observable } from 'rxjs';
|
|
3
|
+
import { BaseEvent } from '../core/types';
|
|
4
|
+
/**
|
|
5
|
+
* WebSocket Service - Base class for real-time event synchronization
|
|
6
|
+
*
|
|
7
|
+
* Provides:
|
|
8
|
+
* - WebSocket connection management
|
|
9
|
+
* - Automatic reconnection with exponential backoff
|
|
10
|
+
* - Presence tracking
|
|
11
|
+
* - SignalTree integration
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* WebSocket connection states
|
|
15
|
+
*/
|
|
16
|
+
export type ConnectionState = 'disconnected' | 'connecting' | 'connected' | 'reconnecting' | 'error';
|
|
17
|
+
/**
|
|
18
|
+
* WebSocket configuration
|
|
19
|
+
*/
|
|
20
|
+
export interface WebSocketConfig {
|
|
21
|
+
/** WebSocket URL */
|
|
22
|
+
url: string;
|
|
23
|
+
/** Reconnection settings */
|
|
24
|
+
reconnect?: {
|
|
25
|
+
enabled: boolean;
|
|
26
|
+
initialDelayMs: number;
|
|
27
|
+
maxDelayMs: number;
|
|
28
|
+
maxAttempts: number;
|
|
29
|
+
};
|
|
30
|
+
/** Heartbeat settings */
|
|
31
|
+
heartbeat?: {
|
|
32
|
+
enabled: boolean;
|
|
33
|
+
intervalMs: number;
|
|
34
|
+
timeoutMs: number;
|
|
35
|
+
};
|
|
36
|
+
/** Auth token getter */
|
|
37
|
+
getAuthToken?: () => string | null | Promise<string | null>;
|
|
38
|
+
/** Protocols */
|
|
39
|
+
protocols?: string[];
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* WebSocket message wrapper
|
|
43
|
+
*/
|
|
44
|
+
export interface WebSocketMessage<T = unknown> {
|
|
45
|
+
type: 'event' | 'ack' | 'error' | 'ping' | 'pong' | 'subscribe' | 'unsubscribe';
|
|
46
|
+
payload?: T;
|
|
47
|
+
eventType?: string;
|
|
48
|
+
correlationId?: string;
|
|
49
|
+
timestamp?: string;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Default configuration
|
|
53
|
+
*/
|
|
54
|
+
declare const DEFAULT_CONFIG: Required<Pick<WebSocketConfig, 'reconnect' | 'heartbeat'>>;
|
|
55
|
+
/**
|
|
56
|
+
* Base WebSocket service for real-time event synchronization
|
|
57
|
+
*
|
|
58
|
+
* Extend this class in your application and wire it to your SignalTree store.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```typescript
|
|
62
|
+
* @Injectable({ providedIn: 'root' })
|
|
63
|
+
* export class AppWebSocketService extends WebSocketService {
|
|
64
|
+
* private readonly store = inject(AppStore);
|
|
65
|
+
*
|
|
66
|
+
* constructor() {
|
|
67
|
+
* super({
|
|
68
|
+
* url: environment.wsUrl,
|
|
69
|
+
* getAuthToken: () => this.store.$.session.token(),
|
|
70
|
+
* });
|
|
71
|
+
*
|
|
72
|
+
* // Subscribe to events
|
|
73
|
+
* this.onEvent<TradeProposalCreated>('TradeProposalCreated').subscribe(event => {
|
|
74
|
+
* this.store.$.trades.entities.upsertOne(event.data);
|
|
75
|
+
* });
|
|
76
|
+
* }
|
|
77
|
+
* }
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
export declare abstract class WebSocketService implements OnDestroy {
|
|
81
|
+
private readonly destroyRef;
|
|
82
|
+
private readonly _connectionState;
|
|
83
|
+
private readonly _lastError;
|
|
84
|
+
private readonly _reconnectAttempts;
|
|
85
|
+
private readonly _lastMessageTime;
|
|
86
|
+
readonly connectionState: Signal<ConnectionState>;
|
|
87
|
+
readonly lastError: Signal<Error | null>;
|
|
88
|
+
readonly isConnected: Signal<boolean>;
|
|
89
|
+
readonly isReconnecting: Signal<boolean>;
|
|
90
|
+
private socket$?;
|
|
91
|
+
private readonly messageSubject;
|
|
92
|
+
private readonly subscribedEvents;
|
|
93
|
+
private heartbeatInterval?;
|
|
94
|
+
private reconnectTimer?;
|
|
95
|
+
protected readonly config: WebSocketConfig & typeof DEFAULT_CONFIG;
|
|
96
|
+
constructor(config: WebSocketConfig);
|
|
97
|
+
/**
|
|
98
|
+
* Connect to WebSocket server
|
|
99
|
+
*/
|
|
100
|
+
connect(): Promise<void>;
|
|
101
|
+
/**
|
|
102
|
+
* Disconnect from WebSocket server
|
|
103
|
+
*/
|
|
104
|
+
disconnect(): void;
|
|
105
|
+
/**
|
|
106
|
+
* Send a message
|
|
107
|
+
*/
|
|
108
|
+
send(message: WebSocketMessage): void;
|
|
109
|
+
/**
|
|
110
|
+
* Subscribe to a specific event type
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* ```typescript
|
|
114
|
+
* this.onEvent<TradeProposalCreated>('TradeProposalCreated').subscribe(event => {
|
|
115
|
+
* console.log('Trade created:', event);
|
|
116
|
+
* });
|
|
117
|
+
* ```
|
|
118
|
+
*/
|
|
119
|
+
onEvent<T extends BaseEvent>(eventType: T['type']): Observable<T>;
|
|
120
|
+
/**
|
|
121
|
+
* Unsubscribe from an event type
|
|
122
|
+
*/
|
|
123
|
+
offEvent(eventType: string): void;
|
|
124
|
+
/**
|
|
125
|
+
* Get all messages (for debugging/logging)
|
|
126
|
+
*/
|
|
127
|
+
get messages$(): Observable<WebSocketMessage>;
|
|
128
|
+
/**
|
|
129
|
+
* Wait for connection to be established
|
|
130
|
+
*/
|
|
131
|
+
waitForConnection(timeoutMs?: number): Promise<void>;
|
|
132
|
+
ngOnDestroy(): void;
|
|
133
|
+
/**
|
|
134
|
+
* Called when connection is established
|
|
135
|
+
* Override in subclass to perform initialization
|
|
136
|
+
*/
|
|
137
|
+
protected onConnected(): void;
|
|
138
|
+
/**
|
|
139
|
+
* Called when connection is lost
|
|
140
|
+
* Override in subclass to handle cleanup
|
|
141
|
+
*/
|
|
142
|
+
protected onDisconnected(): void;
|
|
143
|
+
/**
|
|
144
|
+
* Called when an event is received
|
|
145
|
+
* Override in subclass to dispatch to store
|
|
146
|
+
*/
|
|
147
|
+
protected onEventReceived(_event: BaseEvent): void;
|
|
148
|
+
private handleOpen;
|
|
149
|
+
private handleClose;
|
|
150
|
+
private handleMessage;
|
|
151
|
+
private handleError;
|
|
152
|
+
private handleComplete;
|
|
153
|
+
private scheduleReconnect;
|
|
154
|
+
private clearReconnectTimer;
|
|
155
|
+
private startHeartbeat;
|
|
156
|
+
private stopHeartbeat;
|
|
157
|
+
}
|
|
158
|
+
export {};
|
package/src/angular.d.ts
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Classification - Determine retry behavior for errors
|
|
3
|
+
*
|
|
4
|
+
* Provides:
|
|
5
|
+
* - Retryable vs non-retryable error classification
|
|
6
|
+
* - Error categories (transient, permanent, poison)
|
|
7
|
+
* - Retry configuration per error type
|
|
8
|
+
* - Custom error classifiers
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Error classification result
|
|
12
|
+
*/
|
|
13
|
+
export type ErrorClassification = 'transient' | 'permanent' | 'poison' | 'unknown';
|
|
14
|
+
/**
|
|
15
|
+
* Retry configuration for classified errors
|
|
16
|
+
*/
|
|
17
|
+
export interface RetryConfig {
|
|
18
|
+
/** Maximum number of retry attempts */
|
|
19
|
+
maxAttempts: number;
|
|
20
|
+
/** Initial delay in milliseconds */
|
|
21
|
+
initialDelayMs: number;
|
|
22
|
+
/** Maximum delay in milliseconds */
|
|
23
|
+
maxDelayMs: number;
|
|
24
|
+
/** Backoff multiplier */
|
|
25
|
+
backoffMultiplier: number;
|
|
26
|
+
/** Jitter factor (0-1) to prevent thundering herd */
|
|
27
|
+
jitter: number;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Error classification result with retry config
|
|
31
|
+
*/
|
|
32
|
+
export interface ClassificationResult {
|
|
33
|
+
classification: ErrorClassification;
|
|
34
|
+
retryConfig?: RetryConfig;
|
|
35
|
+
sendToDlq: boolean;
|
|
36
|
+
reason: string;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Default retry configurations by classification
|
|
40
|
+
*/
|
|
41
|
+
export declare const DEFAULT_RETRY_CONFIGS: Record<ErrorClassification, RetryConfig>;
|
|
42
|
+
/**
|
|
43
|
+
* Custom error classifier function
|
|
44
|
+
*/
|
|
45
|
+
export type ErrorClassifier = (error: unknown) => ErrorClassification | null;
|
|
46
|
+
/**
|
|
47
|
+
* Error classifier configuration
|
|
48
|
+
*/
|
|
49
|
+
export interface ErrorClassifierConfig {
|
|
50
|
+
/** Custom classifiers (checked first) */
|
|
51
|
+
customClassifiers?: ErrorClassifier[];
|
|
52
|
+
/** Override default retry configs */
|
|
53
|
+
retryConfigs?: Partial<Record<ErrorClassification, Partial<RetryConfig>>>;
|
|
54
|
+
/** Default classification for unknown errors */
|
|
55
|
+
defaultClassification?: ErrorClassification;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Create an error classifier
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```typescript
|
|
62
|
+
* const classifier = createErrorClassifier({
|
|
63
|
+
* customClassifiers: [
|
|
64
|
+
* (error) => {
|
|
65
|
+
* if (error instanceof MyCustomTransientError) return 'transient';
|
|
66
|
+
* return null; // Let default classification handle it
|
|
67
|
+
* }
|
|
68
|
+
* ],
|
|
69
|
+
* retryConfigs: {
|
|
70
|
+
* transient: { maxAttempts: 10 }, // Override max attempts
|
|
71
|
+
* },
|
|
72
|
+
* });
|
|
73
|
+
*
|
|
74
|
+
* const result = classifier.classify(error);
|
|
75
|
+
* if (result.sendToDlq) {
|
|
76
|
+
* await dlqService.send(event, error, result.reason);
|
|
77
|
+
* }
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
export declare function createErrorClassifier(config?: ErrorClassifierConfig): {
|
|
81
|
+
classify: (error: unknown) => ClassificationResult;
|
|
82
|
+
isRetryable: (error: unknown) => boolean;
|
|
83
|
+
calculateDelay: (attempt: number, config: RetryConfig) => number;
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Pre-configured error classifier instance
|
|
87
|
+
*/
|
|
88
|
+
export declare const defaultErrorClassifier: {
|
|
89
|
+
classify: (error: unknown) => ClassificationResult;
|
|
90
|
+
isRetryable: (error: unknown) => boolean;
|
|
91
|
+
calculateDelay: (attempt: number, config: RetryConfig) => number;
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Quick helper to check if error is retryable
|
|
95
|
+
*/
|
|
96
|
+
export declare function isRetryableError(error: unknown): boolean;
|
|
97
|
+
/**
|
|
98
|
+
* Quick helper to classify error
|
|
99
|
+
*/
|
|
100
|
+
export declare function classifyError(error: unknown): ClassificationResult;
|