@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/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 {};
@@ -0,0 +1,7 @@
1
+ /**
2
+ * @signaltree/events/angular
3
+ *
4
+ * Re-export barrel for Angular integration.
5
+ * This file exists for build tooling - import from '@signaltree/events/angular'
6
+ */
7
+ export * from './angular/index';
@@ -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;