@uploadista/client-core 0.0.13 → 0.0.14

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.
@@ -0,0 +1,280 @@
1
+ /**
2
+ * Generic event type that the subscription manager can handle
3
+ */
4
+ export interface GenericEvent {
5
+ type: string;
6
+ data?: unknown;
7
+ }
8
+
9
+ /**
10
+ * Event handler callback function
11
+ */
12
+ export type SubscriptionEventHandler<T = GenericEvent> = (event: T) => void;
13
+
14
+ /**
15
+ * Unsubscribe function returned from subscriptions
16
+ */
17
+ export type UnsubscribeFunction = () => void;
18
+
19
+ /**
20
+ * Event source that provides subscription capabilities
21
+ */
22
+ export interface EventSource<T = GenericEvent> {
23
+ /**
24
+ * Subscribe to events from this source
25
+ * @returns Unsubscribe function to clean up the subscription
26
+ */
27
+ subscribe(handler: SubscriptionEventHandler<T>): UnsubscribeFunction;
28
+ }
29
+
30
+ /**
31
+ * Options for event filtering
32
+ */
33
+ export interface EventFilterOptions {
34
+ /**
35
+ * Filter events by type (exact match)
36
+ */
37
+ eventType?: string;
38
+
39
+ /**
40
+ * Filter events by upload/job ID
41
+ * If provided, only events with matching ID will be passed to the handler
42
+ */
43
+ uploadId?: string | null;
44
+
45
+ /**
46
+ * Custom filter function for advanced filtering
47
+ * Return true to pass the event to the handler
48
+ */
49
+ customFilter?: (event: GenericEvent) => boolean;
50
+ }
51
+
52
+ /**
53
+ * Subscription information for tracking
54
+ */
55
+ interface SubscriptionInfo<T extends GenericEvent = GenericEvent> {
56
+ unsubscribe: UnsubscribeFunction;
57
+ handler: SubscriptionEventHandler<T>;
58
+ filter?: EventFilterOptions;
59
+ }
60
+
61
+ /**
62
+ * Platform-agnostic event subscription manager that handles event filtering,
63
+ * subscription tracking, and automatic cleanup.
64
+ *
65
+ * This manager simplifies event handling by:
66
+ * - Filtering events by type and/or ID
67
+ * - Tracking all active subscriptions
68
+ * - Providing cleanup methods to unsubscribe from all events
69
+ * - Supporting custom filter functions for advanced scenarios
70
+ *
71
+ * @example Basic event subscription
72
+ * ```typescript
73
+ * const manager = new EventSubscriptionManager(eventSource);
74
+ *
75
+ * manager.subscribe(
76
+ * (event) => console.log('Upload progress:', event),
77
+ * { eventType: 'UPLOAD_PROGRESS', uploadId: 'abc123' }
78
+ * );
79
+ *
80
+ * // Clean up all subscriptions when done
81
+ * manager.cleanup();
82
+ * ```
83
+ *
84
+ * @example Multiple filtered subscriptions
85
+ * ```typescript
86
+ * const manager = new EventSubscriptionManager(eventSource);
87
+ *
88
+ * // Subscribe to progress events for specific upload
89
+ * manager.subscribe(
90
+ * onProgress,
91
+ * { eventType: 'UPLOAD_PROGRESS', uploadId: currentUploadId }
92
+ * );
93
+ *
94
+ * // Subscribe to error events for any upload
95
+ * manager.subscribe(
96
+ * onError,
97
+ * { eventType: 'UPLOAD_ERROR' }
98
+ * );
99
+ *
100
+ * // Subscribe to all events with custom filtering
101
+ * manager.subscribe(
102
+ * onEvent,
103
+ * { customFilter: (e) => e.data?.priority === 'high' }
104
+ * );
105
+ * ```
106
+ */
107
+ export class EventSubscriptionManager<T extends GenericEvent = GenericEvent> {
108
+ private subscriptions: SubscriptionInfo<T>[] = [];
109
+
110
+ /**
111
+ * Create a new EventSubscriptionManager
112
+ *
113
+ * @param eventSource - Source to subscribe to for events
114
+ */
115
+ constructor(private readonly eventSource: EventSource<T>) {}
116
+
117
+ /**
118
+ * Subscribe to events with optional filtering
119
+ *
120
+ * @param handler - Callback function to invoke when matching events occur
121
+ * @param filter - Optional filter options to narrow down which events trigger the handler
122
+ * @returns Unsubscribe function to remove this specific subscription
123
+ *
124
+ * @example Subscribe to specific event type
125
+ * ```typescript
126
+ * const unsubscribe = manager.subscribe(
127
+ * (event) => console.log('Progress:', event),
128
+ * { eventType: 'UPLOAD_PROGRESS' }
129
+ * );
130
+ *
131
+ * // Later, unsubscribe
132
+ * unsubscribe();
133
+ * ```
134
+ */
135
+ subscribe(
136
+ handler: SubscriptionEventHandler<T>,
137
+ filter?: EventFilterOptions,
138
+ ): UnsubscribeFunction {
139
+ // Create a wrapper handler that applies filtering
140
+ const wrappedHandler: SubscriptionEventHandler<T> = (event: T) => {
141
+ if (this.shouldHandleEvent(event, filter)) {
142
+ handler(event);
143
+ }
144
+ };
145
+
146
+ // Subscribe to the event source with the wrapped handler
147
+ const unsubscribe = this.eventSource.subscribe(wrappedHandler);
148
+
149
+ // Track this subscription
150
+ const subscription: SubscriptionInfo<T> = {
151
+ unsubscribe,
152
+ handler: wrappedHandler,
153
+ filter,
154
+ };
155
+
156
+ this.subscriptions.push(subscription);
157
+
158
+ // Return unsubscribe function that also removes from tracking
159
+ return () => {
160
+ const index = this.subscriptions.indexOf(subscription);
161
+ if (index !== -1) {
162
+ this.subscriptions.splice(index, 1);
163
+ }
164
+ unsubscribe();
165
+ };
166
+ }
167
+
168
+ /**
169
+ * Check if an event matches the filter criteria
170
+ *
171
+ * @param event - Event to check
172
+ * @param filter - Filter options to apply
173
+ * @returns True if the event passes all filters
174
+ */
175
+ private shouldHandleEvent(event: T, filter?: EventFilterOptions): boolean {
176
+ if (!filter) {
177
+ return true;
178
+ }
179
+
180
+ // Check event type filter
181
+ if (filter.eventType && event.type !== filter.eventType) {
182
+ return false;
183
+ }
184
+
185
+ // Check upload ID filter
186
+ if (filter.uploadId !== undefined) {
187
+ const eventData = event.data as { id?: string } | undefined;
188
+ const eventId = eventData?.id;
189
+
190
+ // If filter.uploadId is null, only pass events without an ID
191
+ // If filter.uploadId is a string, only pass events with matching ID
192
+ if (filter.uploadId === null) {
193
+ if (eventId !== undefined) {
194
+ return false;
195
+ }
196
+ } else if (eventId !== filter.uploadId) {
197
+ return false;
198
+ }
199
+ }
200
+
201
+ // Check custom filter
202
+ if (filter.customFilter) {
203
+ // Cast to GenericEvent for custom filter as it operates on the base interface
204
+ return filter.customFilter(event as unknown as GenericEvent);
205
+ }
206
+
207
+ return true;
208
+ }
209
+
210
+ /**
211
+ * Get the number of active subscriptions
212
+ *
213
+ * @returns Number of tracked subscriptions
214
+ */
215
+ getSubscriptionCount(): number {
216
+ return this.subscriptions.length;
217
+ }
218
+
219
+ /**
220
+ * Check if there are any active subscriptions
221
+ *
222
+ * @returns True if at least one subscription is active
223
+ */
224
+ hasSubscriptions(): boolean {
225
+ return this.subscriptions.length > 0;
226
+ }
227
+
228
+ /**
229
+ * Unsubscribe from all tracked subscriptions and clear the subscription list
230
+ *
231
+ * This is typically called when disposing of a component or cleaning up resources.
232
+ *
233
+ * @example Cleanup in framework hooks
234
+ * ```typescript
235
+ * // React
236
+ * useEffect(() => {
237
+ * const manager = new EventSubscriptionManager(eventSource);
238
+ * manager.subscribe(handler, filter);
239
+ *
240
+ * return () => manager.cleanup();
241
+ * }, []);
242
+ *
243
+ * // Vue
244
+ * onUnmounted(() => {
245
+ * manager.cleanup();
246
+ * });
247
+ * ```
248
+ */
249
+ cleanup(): void {
250
+ for (const subscription of this.subscriptions) {
251
+ subscription.unsubscribe();
252
+ }
253
+ this.subscriptions = [];
254
+ }
255
+
256
+ /**
257
+ * Update the upload ID filter for all subscriptions that have an uploadId filter
258
+ *
259
+ * This is useful when the current upload changes and you want to update
260
+ * all subscriptions to listen for the new upload's events.
261
+ *
262
+ * @param newUploadId - New upload ID to filter events by
263
+ *
264
+ * @example Update upload ID when starting new upload
265
+ * ```typescript
266
+ * const manager = new EventSubscriptionManager(eventSource);
267
+ * manager.subscribe(onProgress, { eventType: 'UPLOAD_PROGRESS', uploadId: null });
268
+ *
269
+ * // When upload starts
270
+ * manager.updateUploadIdFilter(uploadId);
271
+ * ```
272
+ */
273
+ updateUploadIdFilter(newUploadId: string | null): void {
274
+ for (const subscription of this.subscriptions) {
275
+ if (subscription.filter && subscription.filter.uploadId !== undefined) {
276
+ subscription.filter.uploadId = newUploadId;
277
+ }
278
+ }
279
+ }
280
+ }