@sygnl/subscriptions 1.0.0

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/dist/index.cjs ADDED
@@ -0,0 +1,247 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ SubscriptionManager: () => SubscriptionManager
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+
27
+ // src/SubscriptionManager.ts
28
+ var import_talon = require("@sygnl/talon");
29
+ var import_transports = require("@sygnl/talon/transports");
30
+ var SubscriptionManager = class {
31
+ constructor(options) {
32
+ this.subscriptions = /* @__PURE__ */ new Map();
33
+ this.pendingSubscriptions = /* @__PURE__ */ new Map();
34
+ this.tempIdToServerIdMap = /* @__PURE__ */ new Map();
35
+ this.errorCallbacks = /* @__PURE__ */ new Set();
36
+ this.connectCallbacks = /* @__PURE__ */ new Set();
37
+ this.disconnectCallbacks = /* @__PURE__ */ new Set();
38
+ this.debug = options.debug ?? false;
39
+ this.talon = new import_talon.TalonClient({
40
+ transport: new import_transports.WebSocketTransport({
41
+ url: options.url,
42
+ debug: this.debug
43
+ }),
44
+ autoConnect: options.autoConnect ?? false,
45
+ debug: this.debug
46
+ });
47
+ this.talon.on("_connected", () => {
48
+ this.log("Connected via Talon");
49
+ this.resubscribeAll();
50
+ this.connectCallbacks.forEach((cb) => cb());
51
+ });
52
+ this.talon.on("_disconnected", () => {
53
+ this.log("Disconnected via Talon");
54
+ this.disconnectCallbacks.forEach((cb) => cb());
55
+ for (const sub of this.subscriptions.values()) {
56
+ sub.active = false;
57
+ }
58
+ });
59
+ this.talon.on("_error", (error) => {
60
+ this.emitError(error instanceof Error ? error : new Error(String(error)));
61
+ });
62
+ this.talon.on("subscribed", (data) => this.handleSubscribed(data));
63
+ this.talon.on("event", (data) => this.handleEvent(data));
64
+ this.talon.on("unsubscribed", (data) => this.handleUnsubscribed(data));
65
+ this.talon.on("error", (data) => this.emitError(new Error(data.error || "Unknown error")));
66
+ }
67
+ /**
68
+ * Connect to WebSocket server (via Talon)
69
+ */
70
+ async connect() {
71
+ return this.talon.connect();
72
+ }
73
+ /**
74
+ * Disconnect from WebSocket server (via Talon)
75
+ */
76
+ disconnect() {
77
+ this.talon.disconnect();
78
+ }
79
+ /**
80
+ * Subscribe to events with optional filters
81
+ *
82
+ * @returns Unsubscribe function
83
+ */
84
+ subscribe(callback, options = {}) {
85
+ const tempId = crypto.randomUUID();
86
+ this.pendingSubscriptions.set(tempId, {
87
+ filters: options.filters || {},
88
+ callback
89
+ });
90
+ if (this.talon.connected) {
91
+ this.sendSubscribeRequest(tempId, options.filters || {});
92
+ }
93
+ return () => {
94
+ this.unsubscribe(tempId);
95
+ };
96
+ }
97
+ /**
98
+ * Unsubscribe from events
99
+ */
100
+ async unsubscribe(tempId) {
101
+ const serverSubId = this.tempIdToServerIdMap.get(tempId);
102
+ if (serverSubId) {
103
+ const sub = this.subscriptions.get(serverSubId);
104
+ if (sub) {
105
+ this.log("Unsubscribing:", serverSubId);
106
+ if (this.talon.connected) {
107
+ const request = {
108
+ type: "unsubscribe",
109
+ subscriptionId: serverSubId
110
+ };
111
+ await this.talon.send(request);
112
+ }
113
+ this.subscriptions.delete(serverSubId);
114
+ this.tempIdToServerIdMap.delete(tempId);
115
+ }
116
+ }
117
+ this.pendingSubscriptions.delete(tempId);
118
+ }
119
+ /**
120
+ * Send subscribe request to server (via Talon)
121
+ */
122
+ async sendSubscribeRequest(tempId, filters) {
123
+ if (!this.talon.connected) {
124
+ this.log("Cannot subscribe: not connected");
125
+ return;
126
+ }
127
+ const request = {
128
+ type: "subscribe",
129
+ filters
130
+ };
131
+ this.log("Subscribing with filters:", filters);
132
+ await this.talon.send(request);
133
+ }
134
+ /**
135
+ * Resubscribe to all active subscriptions (after reconnect)
136
+ */
137
+ resubscribeAll() {
138
+ this.log("Resubscribing to", this.pendingSubscriptions.size, "subscriptions");
139
+ for (const [tempId, { filters, callback }] of this.pendingSubscriptions.entries()) {
140
+ this.sendSubscribeRequest(tempId, filters);
141
+ }
142
+ }
143
+ /**
144
+ * Handle subscription confirmation (from Talon)
145
+ */
146
+ handleSubscribed(message) {
147
+ const serverSubId = message.subscriptionId;
148
+ if (!serverSubId) return;
149
+ this.log("Subscription confirmed:", serverSubId);
150
+ const pending = Array.from(this.pendingSubscriptions.entries())[0];
151
+ if (!pending) return;
152
+ const [tempId, { filters, callback }] = pending;
153
+ this.tempIdToServerIdMap.set(tempId, serverSubId);
154
+ this.subscriptions.set(serverSubId, {
155
+ id: serverSubId,
156
+ filters,
157
+ callback,
158
+ active: true
159
+ });
160
+ this.pendingSubscriptions.delete(tempId);
161
+ }
162
+ /**
163
+ * Handle incoming event (from Talon)
164
+ */
165
+ handleEvent(message) {
166
+ const subId = message.subscriptionId;
167
+ if (!subId) return;
168
+ const sub = this.subscriptions.get(subId);
169
+ if (!sub || !sub.active) {
170
+ this.log("Received event for unknown/inactive subscription:", subId);
171
+ return;
172
+ }
173
+ try {
174
+ sub.callback(message.data);
175
+ } catch (error) {
176
+ this.log("Error in subscription callback:", error);
177
+ this.emitError(error instanceof Error ? error : new Error("Callback error"));
178
+ }
179
+ }
180
+ /**
181
+ * Handle unsubscribe confirmation (from Talon)
182
+ */
183
+ handleUnsubscribed(message) {
184
+ const subId = message.subscriptionId;
185
+ if (!subId) return;
186
+ this.log("Unsubscribe confirmed:", subId);
187
+ this.subscriptions.delete(subId);
188
+ }
189
+ /**
190
+ * Register error callback
191
+ */
192
+ onError(callback) {
193
+ this.errorCallbacks.add(callback);
194
+ return () => this.errorCallbacks.delete(callback);
195
+ }
196
+ /**
197
+ * Register connect callback
198
+ */
199
+ onConnect(callback) {
200
+ this.connectCallbacks.add(callback);
201
+ return () => this.connectCallbacks.delete(callback);
202
+ }
203
+ /**
204
+ * Register disconnect callback
205
+ */
206
+ onDisconnect(callback) {
207
+ this.disconnectCallbacks.add(callback);
208
+ return () => this.disconnectCallbacks.delete(callback);
209
+ }
210
+ /**
211
+ * Emit error to all error callbacks
212
+ */
213
+ emitError(error) {
214
+ this.errorCallbacks.forEach((cb) => {
215
+ try {
216
+ cb(error);
217
+ } catch (err) {
218
+ this.log("Error in error callback:", err);
219
+ }
220
+ });
221
+ }
222
+ /**
223
+ * Check if connected (via Talon)
224
+ */
225
+ isConnected() {
226
+ return this.talon.connected;
227
+ }
228
+ /**
229
+ * Get number of active subscriptions
230
+ */
231
+ getSubscriptionCount() {
232
+ return this.subscriptions.size;
233
+ }
234
+ /**
235
+ * Debug logging
236
+ */
237
+ log(...args) {
238
+ if (this.debug) {
239
+ console.log("[SubscriptionManager]", ...args);
240
+ }
241
+ }
242
+ };
243
+ // Annotate the CommonJS export names for ESM import in node:
244
+ 0 && (module.exports = {
245
+ SubscriptionManager
246
+ });
247
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/SubscriptionManager.ts"],"sourcesContent":["/**\n * @sygnl/subscriptions\n * \n * Real-time event subscriptions via WebSocket with filtering and type-safe callbacks.\n * \n * @example\n * ```typescript\n * import { SubscriptionManager } from '@sygnl/subscriptions';\n * \n * const manager = new SubscriptionManager({\n * url: 'wss://your-worker.workers.dev/subscribe',\n * autoConnect: true,\n * debug: true\n * });\n * \n * // Subscribe to purchase events\n * const unsubscribe = manager.subscribe((event) => {\n * console.log('Purchase:', event);\n * }, {\n * filters: {\n * eventTypes: ['purchase']\n * }\n * });\n * \n * // Later: unsubscribe()\n * ```\n */\n\nexport { SubscriptionManager } from './SubscriptionManager';\nexport type {\n SubscriptionManagerOptions,\n SubscriptionOptions,\n SubscriptionFilter,\n SubscriptionCallback,\n ErrorCallback,\n SubscriptionMessage,\n SubscriptionRequest,\n ActiveSubscription,\n} from './types';\n","/**\n * SubscriptionManager - Real-time event subscriptions\n * \n * Built on top of @sygnl/talon for WebSocket management.\n * Provides high-level subscription API with filtering.\n */\n\nimport { TalonClient } from '@sygnl/talon';\nimport { WebSocketTransport } from '@sygnl/talon/transports';\nimport type { AnyEvent } from '@sygnl/event-schema';\n\nimport type {\n SubscriptionManagerOptions,\n SubscriptionOptions,\n SubscriptionFilter,\n SubscriptionCallback,\n ErrorCallback,\n SubscriptionMessage,\n SubscriptionRequest,\n ActiveSubscription,\n} from './types';\n\nexport class SubscriptionManager {\n private talon: TalonClient;\n private subscriptions: Map<string, ActiveSubscription> = new Map();\n private pendingSubscriptions: Map<string, {\n filters: SubscriptionFilter;\n callback: SubscriptionCallback;\n }> = new Map();\n private tempIdToServerIdMap: Map<string, string> = new Map();\n \n private debug: boolean;\n \n private errorCallbacks: Set<ErrorCallback> = new Set();\n private connectCallbacks: Set<() => void> = new Set();\n private disconnectCallbacks: Set<() => void> = new Set();\n\n constructor(options: SubscriptionManagerOptions) {\n this.debug = options.debug ?? false;\n\n // Use Talon for WebSocket management!\n this.talon = new TalonClient({\n transport: new WebSocketTransport({\n url: options.url,\n debug: this.debug,\n }),\n autoConnect: options.autoConnect ?? false,\n debug: this.debug,\n });\n\n // Listen to Talon events\n this.talon.on('_connected', () => {\n this.log('Connected via Talon');\n this.resubscribeAll();\n this.connectCallbacks.forEach((cb) => cb());\n });\n\n this.talon.on('_disconnected', () => {\n this.log('Disconnected via Talon');\n this.disconnectCallbacks.forEach((cb) => cb());\n \n // Mark all subscriptions as inactive\n for (const sub of this.subscriptions.values()) {\n sub.active = false;\n }\n });\n\n this.talon.on('_error', (error: any) => {\n this.emitError(error instanceof Error ? error : new Error(String(error)));\n });\n\n // Listen for subscription-specific messages\n this.talon.on('subscribed', (data: any) => this.handleSubscribed(data));\n this.talon.on('event', (data: any) => this.handleEvent(data));\n this.talon.on('unsubscribed', (data: any) => this.handleUnsubscribed(data));\n this.talon.on('error', (data: any) => this.emitError(new Error(data.error || 'Unknown error')));\n }\n\n /**\n * Connect to WebSocket server (via Talon)\n */\n async connect(): Promise<void> {\n return this.talon.connect();\n }\n\n /**\n * Disconnect from WebSocket server (via Talon)\n */\n disconnect(): void {\n this.talon.disconnect();\n }\n\n /**\n * Subscribe to events with optional filters\n * \n * @returns Unsubscribe function\n */\n subscribe<T = any>(\n callback: SubscriptionCallback<T>,\n options: SubscriptionOptions = {}\n ): () => void {\n const tempId = crypto.randomUUID();\n \n // Store as pending until server confirms\n this.pendingSubscriptions.set(tempId, {\n filters: options.filters || {},\n callback,\n });\n\n // Send subscribe request if connected (via Talon)\n if (this.talon.connected) {\n this.sendSubscribeRequest(tempId, options.filters || {});\n }\n\n // Return unsubscribe function\n return () => {\n this.unsubscribe(tempId);\n };\n }\n\n /**\n * Unsubscribe from events\n */\n private async unsubscribe(tempId: string): Promise<void> {\n // Map temp ID to server ID\n const serverSubId = this.tempIdToServerIdMap.get(tempId);\n \n if (serverSubId) {\n const sub = this.subscriptions.get(serverSubId);\n \n if (sub) {\n this.log('Unsubscribing:', serverSubId);\n \n // Send unsubscribe request via Talon\n if (this.talon.connected) {\n const request: SubscriptionRequest = {\n type: 'unsubscribe',\n subscriptionId: serverSubId,\n };\n await this.talon.send(request as any);\n }\n\n // Remove from active subscriptions\n this.subscriptions.delete(serverSubId);\n this.tempIdToServerIdMap.delete(tempId);\n }\n }\n\n // Also check pending\n this.pendingSubscriptions.delete(tempId);\n }\n\n /**\n * Send subscribe request to server (via Talon)\n */\n private async sendSubscribeRequest(tempId: string, filters: SubscriptionFilter): Promise<void> {\n if (!this.talon.connected) {\n this.log('Cannot subscribe: not connected');\n return;\n }\n\n const request: SubscriptionRequest = {\n type: 'subscribe',\n filters,\n };\n\n this.log('Subscribing with filters:', filters);\n await this.talon.send(request as any);\n }\n\n /**\n * Resubscribe to all active subscriptions (after reconnect)\n */\n private resubscribeAll(): void {\n this.log('Resubscribing to', this.pendingSubscriptions.size, 'subscriptions');\n\n for (const [tempId, { filters, callback }] of this.pendingSubscriptions.entries()) {\n this.sendSubscribeRequest(tempId, filters);\n }\n }\n\n /**\n * Handle subscription confirmation (from Talon)\n */\n private handleSubscribed(message: any): void {\n const serverSubId = message.subscriptionId;\n if (!serverSubId) return;\n\n this.log('Subscription confirmed:', serverSubId);\n\n // Find the pending subscription\n const pending = Array.from(this.pendingSubscriptions.entries())[0];\n if (!pending) return;\n\n const [tempId, { filters, callback }] = pending;\n\n // Map temp ID to server ID\n this.tempIdToServerIdMap.set(tempId, serverSubId);\n\n // Move from pending to active\n this.subscriptions.set(serverSubId, {\n id: serverSubId,\n filters,\n callback,\n active: true,\n });\n this.pendingSubscriptions.delete(tempId);\n }\n\n /**\n * Handle incoming event (from Talon)\n */\n private handleEvent(message: any): void {\n const subId = message.subscriptionId;\n if (!subId) return;\n\n const sub = this.subscriptions.get(subId);\n if (!sub || !sub.active) {\n this.log('Received event for unknown/inactive subscription:', subId);\n return;\n }\n\n // Call the subscription callback\n try {\n sub.callback(message.data);\n } catch (error) {\n this.log('Error in subscription callback:', error);\n this.emitError(error instanceof Error ? error : new Error('Callback error'));\n }\n }\n\n /**\n * Handle unsubscribe confirmation (from Talon)\n */\n private handleUnsubscribed(message: any): void {\n const subId = message.subscriptionId;\n if (!subId) return;\n\n this.log('Unsubscribe confirmed:', subId);\n this.subscriptions.delete(subId);\n }\n\n /**\n * Register error callback\n */\n onError(callback: ErrorCallback): () => void {\n this.errorCallbacks.add(callback);\n return () => this.errorCallbacks.delete(callback);\n }\n\n /**\n * Register connect callback\n */\n onConnect(callback: () => void): () => void {\n this.connectCallbacks.add(callback);\n return () => this.connectCallbacks.delete(callback);\n }\n\n /**\n * Register disconnect callback\n */\n onDisconnect(callback: () => void): () => void {\n this.disconnectCallbacks.add(callback);\n return () => this.disconnectCallbacks.delete(callback);\n }\n\n /**\n * Emit error to all error callbacks\n */\n private emitError(error: Error): void {\n this.errorCallbacks.forEach((cb) => {\n try {\n cb(error);\n } catch (err) {\n this.log('Error in error callback:', err);\n }\n });\n }\n\n /**\n * Check if connected (via Talon)\n */\n isConnected(): boolean {\n return this.talon.connected;\n }\n\n /**\n * Get number of active subscriptions\n */\n getSubscriptionCount(): number {\n return this.subscriptions.size;\n }\n\n /**\n * Debug logging\n */\n private log(...args: any[]): void {\n if (this.debug) {\n console.log('[SubscriptionManager]', ...args);\n }\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,228 @@
1
+ /**
2
+ * Type definitions for subscriptions
3
+ */
4
+ /**
5
+ * Subscription filter
6
+ */
7
+ interface SubscriptionFilter {
8
+ /**
9
+ * Filter by event types
10
+ */
11
+ eventTypes?: string[];
12
+ /**
13
+ * Filter by user ID
14
+ */
15
+ userId?: string;
16
+ /**
17
+ * Filter by session ID
18
+ */
19
+ sessionId?: string;
20
+ /**
21
+ * Filter by custom properties
22
+ */
23
+ properties?: Record<string, any>;
24
+ /**
25
+ * Time range filter (Unix timestamp ms)
26
+ */
27
+ timeRange?: {
28
+ start?: number;
29
+ end?: number;
30
+ };
31
+ }
32
+ /**
33
+ * Subscription message from server
34
+ */
35
+ interface SubscriptionMessage {
36
+ /**
37
+ * Message type
38
+ */
39
+ type: 'subscribed' | 'unsubscribed' | 'event' | 'error';
40
+ /**
41
+ * Subscription ID
42
+ */
43
+ subscriptionId?: string;
44
+ /**
45
+ * Event data (when type is 'event')
46
+ */
47
+ data?: any;
48
+ /**
49
+ * Error message (when type is 'error')
50
+ */
51
+ error?: string;
52
+ }
53
+ /**
54
+ * Client subscription request
55
+ */
56
+ interface SubscriptionRequest {
57
+ /**
58
+ * Request type
59
+ */
60
+ type: 'subscribe' | 'unsubscribe';
61
+ /**
62
+ * Subscription ID (for unsubscribe)
63
+ */
64
+ subscriptionId?: string;
65
+ /**
66
+ * Filters (for subscribe)
67
+ */
68
+ filters?: SubscriptionFilter;
69
+ }
70
+ /**
71
+ * Subscription callback
72
+ */
73
+ type SubscriptionCallback<T = any> = (event: T) => void;
74
+ /**
75
+ * Subscription error callback
76
+ */
77
+ type ErrorCallback = (error: Error) => void;
78
+ /**
79
+ * Subscription options
80
+ */
81
+ interface SubscriptionOptions {
82
+ /**
83
+ * Filters for this subscription
84
+ */
85
+ filters?: SubscriptionFilter;
86
+ /**
87
+ * Auto-reconnect on disconnect
88
+ * @default true
89
+ */
90
+ autoReconnect?: boolean;
91
+ /**
92
+ * Debug mode
93
+ * @default false
94
+ */
95
+ debug?: boolean;
96
+ }
97
+ /**
98
+ * Subscription manager options
99
+ */
100
+ interface SubscriptionManagerOptions {
101
+ /**
102
+ * WebSocket URL
103
+ */
104
+ url: string;
105
+ /**
106
+ * Auto-connect on initialization
107
+ * @default false
108
+ */
109
+ autoConnect?: boolean;
110
+ /**
111
+ * Debug mode
112
+ * @default false
113
+ */
114
+ debug?: boolean;
115
+ /**
116
+ * Connection timeout (ms)
117
+ * @default 5000
118
+ */
119
+ connectionTimeout?: number;
120
+ }
121
+ /**
122
+ * Active subscription
123
+ */
124
+ interface ActiveSubscription {
125
+ /**
126
+ * Subscription ID from server
127
+ */
128
+ id: string;
129
+ /**
130
+ * Filters for this subscription
131
+ */
132
+ filters: SubscriptionFilter;
133
+ /**
134
+ * Callback function
135
+ */
136
+ callback: SubscriptionCallback;
137
+ /**
138
+ * Whether subscription is active
139
+ */
140
+ active: boolean;
141
+ }
142
+
143
+ /**
144
+ * SubscriptionManager - Real-time event subscriptions
145
+ *
146
+ * Built on top of @sygnl/talon for WebSocket management.
147
+ * Provides high-level subscription API with filtering.
148
+ */
149
+
150
+ declare class SubscriptionManager {
151
+ private talon;
152
+ private subscriptions;
153
+ private pendingSubscriptions;
154
+ private tempIdToServerIdMap;
155
+ private debug;
156
+ private errorCallbacks;
157
+ private connectCallbacks;
158
+ private disconnectCallbacks;
159
+ constructor(options: SubscriptionManagerOptions);
160
+ /**
161
+ * Connect to WebSocket server (via Talon)
162
+ */
163
+ connect(): Promise<void>;
164
+ /**
165
+ * Disconnect from WebSocket server (via Talon)
166
+ */
167
+ disconnect(): void;
168
+ /**
169
+ * Subscribe to events with optional filters
170
+ *
171
+ * @returns Unsubscribe function
172
+ */
173
+ subscribe<T = any>(callback: SubscriptionCallback<T>, options?: SubscriptionOptions): () => void;
174
+ /**
175
+ * Unsubscribe from events
176
+ */
177
+ private unsubscribe;
178
+ /**
179
+ * Send subscribe request to server (via Talon)
180
+ */
181
+ private sendSubscribeRequest;
182
+ /**
183
+ * Resubscribe to all active subscriptions (after reconnect)
184
+ */
185
+ private resubscribeAll;
186
+ /**
187
+ * Handle subscription confirmation (from Talon)
188
+ */
189
+ private handleSubscribed;
190
+ /**
191
+ * Handle incoming event (from Talon)
192
+ */
193
+ private handleEvent;
194
+ /**
195
+ * Handle unsubscribe confirmation (from Talon)
196
+ */
197
+ private handleUnsubscribed;
198
+ /**
199
+ * Register error callback
200
+ */
201
+ onError(callback: ErrorCallback): () => void;
202
+ /**
203
+ * Register connect callback
204
+ */
205
+ onConnect(callback: () => void): () => void;
206
+ /**
207
+ * Register disconnect callback
208
+ */
209
+ onDisconnect(callback: () => void): () => void;
210
+ /**
211
+ * Emit error to all error callbacks
212
+ */
213
+ private emitError;
214
+ /**
215
+ * Check if connected (via Talon)
216
+ */
217
+ isConnected(): boolean;
218
+ /**
219
+ * Get number of active subscriptions
220
+ */
221
+ getSubscriptionCount(): number;
222
+ /**
223
+ * Debug logging
224
+ */
225
+ private log;
226
+ }
227
+
228
+ export { type ActiveSubscription, type ErrorCallback, type SubscriptionCallback, type SubscriptionFilter, SubscriptionManager, type SubscriptionManagerOptions, type SubscriptionMessage, type SubscriptionOptions, type SubscriptionRequest };
@@ -0,0 +1,228 @@
1
+ /**
2
+ * Type definitions for subscriptions
3
+ */
4
+ /**
5
+ * Subscription filter
6
+ */
7
+ interface SubscriptionFilter {
8
+ /**
9
+ * Filter by event types
10
+ */
11
+ eventTypes?: string[];
12
+ /**
13
+ * Filter by user ID
14
+ */
15
+ userId?: string;
16
+ /**
17
+ * Filter by session ID
18
+ */
19
+ sessionId?: string;
20
+ /**
21
+ * Filter by custom properties
22
+ */
23
+ properties?: Record<string, any>;
24
+ /**
25
+ * Time range filter (Unix timestamp ms)
26
+ */
27
+ timeRange?: {
28
+ start?: number;
29
+ end?: number;
30
+ };
31
+ }
32
+ /**
33
+ * Subscription message from server
34
+ */
35
+ interface SubscriptionMessage {
36
+ /**
37
+ * Message type
38
+ */
39
+ type: 'subscribed' | 'unsubscribed' | 'event' | 'error';
40
+ /**
41
+ * Subscription ID
42
+ */
43
+ subscriptionId?: string;
44
+ /**
45
+ * Event data (when type is 'event')
46
+ */
47
+ data?: any;
48
+ /**
49
+ * Error message (when type is 'error')
50
+ */
51
+ error?: string;
52
+ }
53
+ /**
54
+ * Client subscription request
55
+ */
56
+ interface SubscriptionRequest {
57
+ /**
58
+ * Request type
59
+ */
60
+ type: 'subscribe' | 'unsubscribe';
61
+ /**
62
+ * Subscription ID (for unsubscribe)
63
+ */
64
+ subscriptionId?: string;
65
+ /**
66
+ * Filters (for subscribe)
67
+ */
68
+ filters?: SubscriptionFilter;
69
+ }
70
+ /**
71
+ * Subscription callback
72
+ */
73
+ type SubscriptionCallback<T = any> = (event: T) => void;
74
+ /**
75
+ * Subscription error callback
76
+ */
77
+ type ErrorCallback = (error: Error) => void;
78
+ /**
79
+ * Subscription options
80
+ */
81
+ interface SubscriptionOptions {
82
+ /**
83
+ * Filters for this subscription
84
+ */
85
+ filters?: SubscriptionFilter;
86
+ /**
87
+ * Auto-reconnect on disconnect
88
+ * @default true
89
+ */
90
+ autoReconnect?: boolean;
91
+ /**
92
+ * Debug mode
93
+ * @default false
94
+ */
95
+ debug?: boolean;
96
+ }
97
+ /**
98
+ * Subscription manager options
99
+ */
100
+ interface SubscriptionManagerOptions {
101
+ /**
102
+ * WebSocket URL
103
+ */
104
+ url: string;
105
+ /**
106
+ * Auto-connect on initialization
107
+ * @default false
108
+ */
109
+ autoConnect?: boolean;
110
+ /**
111
+ * Debug mode
112
+ * @default false
113
+ */
114
+ debug?: boolean;
115
+ /**
116
+ * Connection timeout (ms)
117
+ * @default 5000
118
+ */
119
+ connectionTimeout?: number;
120
+ }
121
+ /**
122
+ * Active subscription
123
+ */
124
+ interface ActiveSubscription {
125
+ /**
126
+ * Subscription ID from server
127
+ */
128
+ id: string;
129
+ /**
130
+ * Filters for this subscription
131
+ */
132
+ filters: SubscriptionFilter;
133
+ /**
134
+ * Callback function
135
+ */
136
+ callback: SubscriptionCallback;
137
+ /**
138
+ * Whether subscription is active
139
+ */
140
+ active: boolean;
141
+ }
142
+
143
+ /**
144
+ * SubscriptionManager - Real-time event subscriptions
145
+ *
146
+ * Built on top of @sygnl/talon for WebSocket management.
147
+ * Provides high-level subscription API with filtering.
148
+ */
149
+
150
+ declare class SubscriptionManager {
151
+ private talon;
152
+ private subscriptions;
153
+ private pendingSubscriptions;
154
+ private tempIdToServerIdMap;
155
+ private debug;
156
+ private errorCallbacks;
157
+ private connectCallbacks;
158
+ private disconnectCallbacks;
159
+ constructor(options: SubscriptionManagerOptions);
160
+ /**
161
+ * Connect to WebSocket server (via Talon)
162
+ */
163
+ connect(): Promise<void>;
164
+ /**
165
+ * Disconnect from WebSocket server (via Talon)
166
+ */
167
+ disconnect(): void;
168
+ /**
169
+ * Subscribe to events with optional filters
170
+ *
171
+ * @returns Unsubscribe function
172
+ */
173
+ subscribe<T = any>(callback: SubscriptionCallback<T>, options?: SubscriptionOptions): () => void;
174
+ /**
175
+ * Unsubscribe from events
176
+ */
177
+ private unsubscribe;
178
+ /**
179
+ * Send subscribe request to server (via Talon)
180
+ */
181
+ private sendSubscribeRequest;
182
+ /**
183
+ * Resubscribe to all active subscriptions (after reconnect)
184
+ */
185
+ private resubscribeAll;
186
+ /**
187
+ * Handle subscription confirmation (from Talon)
188
+ */
189
+ private handleSubscribed;
190
+ /**
191
+ * Handle incoming event (from Talon)
192
+ */
193
+ private handleEvent;
194
+ /**
195
+ * Handle unsubscribe confirmation (from Talon)
196
+ */
197
+ private handleUnsubscribed;
198
+ /**
199
+ * Register error callback
200
+ */
201
+ onError(callback: ErrorCallback): () => void;
202
+ /**
203
+ * Register connect callback
204
+ */
205
+ onConnect(callback: () => void): () => void;
206
+ /**
207
+ * Register disconnect callback
208
+ */
209
+ onDisconnect(callback: () => void): () => void;
210
+ /**
211
+ * Emit error to all error callbacks
212
+ */
213
+ private emitError;
214
+ /**
215
+ * Check if connected (via Talon)
216
+ */
217
+ isConnected(): boolean;
218
+ /**
219
+ * Get number of active subscriptions
220
+ */
221
+ getSubscriptionCount(): number;
222
+ /**
223
+ * Debug logging
224
+ */
225
+ private log;
226
+ }
227
+
228
+ export { type ActiveSubscription, type ErrorCallback, type SubscriptionCallback, type SubscriptionFilter, SubscriptionManager, type SubscriptionManagerOptions, type SubscriptionMessage, type SubscriptionOptions, type SubscriptionRequest };
package/dist/index.js ADDED
@@ -0,0 +1,220 @@
1
+ // src/SubscriptionManager.ts
2
+ import { TalonClient } from "@sygnl/talon";
3
+ import { WebSocketTransport } from "@sygnl/talon/transports";
4
+ var SubscriptionManager = class {
5
+ constructor(options) {
6
+ this.subscriptions = /* @__PURE__ */ new Map();
7
+ this.pendingSubscriptions = /* @__PURE__ */ new Map();
8
+ this.tempIdToServerIdMap = /* @__PURE__ */ new Map();
9
+ this.errorCallbacks = /* @__PURE__ */ new Set();
10
+ this.connectCallbacks = /* @__PURE__ */ new Set();
11
+ this.disconnectCallbacks = /* @__PURE__ */ new Set();
12
+ this.debug = options.debug ?? false;
13
+ this.talon = new TalonClient({
14
+ transport: new WebSocketTransport({
15
+ url: options.url,
16
+ debug: this.debug
17
+ }),
18
+ autoConnect: options.autoConnect ?? false,
19
+ debug: this.debug
20
+ });
21
+ this.talon.on("_connected", () => {
22
+ this.log("Connected via Talon");
23
+ this.resubscribeAll();
24
+ this.connectCallbacks.forEach((cb) => cb());
25
+ });
26
+ this.talon.on("_disconnected", () => {
27
+ this.log("Disconnected via Talon");
28
+ this.disconnectCallbacks.forEach((cb) => cb());
29
+ for (const sub of this.subscriptions.values()) {
30
+ sub.active = false;
31
+ }
32
+ });
33
+ this.talon.on("_error", (error) => {
34
+ this.emitError(error instanceof Error ? error : new Error(String(error)));
35
+ });
36
+ this.talon.on("subscribed", (data) => this.handleSubscribed(data));
37
+ this.talon.on("event", (data) => this.handleEvent(data));
38
+ this.talon.on("unsubscribed", (data) => this.handleUnsubscribed(data));
39
+ this.talon.on("error", (data) => this.emitError(new Error(data.error || "Unknown error")));
40
+ }
41
+ /**
42
+ * Connect to WebSocket server (via Talon)
43
+ */
44
+ async connect() {
45
+ return this.talon.connect();
46
+ }
47
+ /**
48
+ * Disconnect from WebSocket server (via Talon)
49
+ */
50
+ disconnect() {
51
+ this.talon.disconnect();
52
+ }
53
+ /**
54
+ * Subscribe to events with optional filters
55
+ *
56
+ * @returns Unsubscribe function
57
+ */
58
+ subscribe(callback, options = {}) {
59
+ const tempId = crypto.randomUUID();
60
+ this.pendingSubscriptions.set(tempId, {
61
+ filters: options.filters || {},
62
+ callback
63
+ });
64
+ if (this.talon.connected) {
65
+ this.sendSubscribeRequest(tempId, options.filters || {});
66
+ }
67
+ return () => {
68
+ this.unsubscribe(tempId);
69
+ };
70
+ }
71
+ /**
72
+ * Unsubscribe from events
73
+ */
74
+ async unsubscribe(tempId) {
75
+ const serverSubId = this.tempIdToServerIdMap.get(tempId);
76
+ if (serverSubId) {
77
+ const sub = this.subscriptions.get(serverSubId);
78
+ if (sub) {
79
+ this.log("Unsubscribing:", serverSubId);
80
+ if (this.talon.connected) {
81
+ const request = {
82
+ type: "unsubscribe",
83
+ subscriptionId: serverSubId
84
+ };
85
+ await this.talon.send(request);
86
+ }
87
+ this.subscriptions.delete(serverSubId);
88
+ this.tempIdToServerIdMap.delete(tempId);
89
+ }
90
+ }
91
+ this.pendingSubscriptions.delete(tempId);
92
+ }
93
+ /**
94
+ * Send subscribe request to server (via Talon)
95
+ */
96
+ async sendSubscribeRequest(tempId, filters) {
97
+ if (!this.talon.connected) {
98
+ this.log("Cannot subscribe: not connected");
99
+ return;
100
+ }
101
+ const request = {
102
+ type: "subscribe",
103
+ filters
104
+ };
105
+ this.log("Subscribing with filters:", filters);
106
+ await this.talon.send(request);
107
+ }
108
+ /**
109
+ * Resubscribe to all active subscriptions (after reconnect)
110
+ */
111
+ resubscribeAll() {
112
+ this.log("Resubscribing to", this.pendingSubscriptions.size, "subscriptions");
113
+ for (const [tempId, { filters, callback }] of this.pendingSubscriptions.entries()) {
114
+ this.sendSubscribeRequest(tempId, filters);
115
+ }
116
+ }
117
+ /**
118
+ * Handle subscription confirmation (from Talon)
119
+ */
120
+ handleSubscribed(message) {
121
+ const serverSubId = message.subscriptionId;
122
+ if (!serverSubId) return;
123
+ this.log("Subscription confirmed:", serverSubId);
124
+ const pending = Array.from(this.pendingSubscriptions.entries())[0];
125
+ if (!pending) return;
126
+ const [tempId, { filters, callback }] = pending;
127
+ this.tempIdToServerIdMap.set(tempId, serverSubId);
128
+ this.subscriptions.set(serverSubId, {
129
+ id: serverSubId,
130
+ filters,
131
+ callback,
132
+ active: true
133
+ });
134
+ this.pendingSubscriptions.delete(tempId);
135
+ }
136
+ /**
137
+ * Handle incoming event (from Talon)
138
+ */
139
+ handleEvent(message) {
140
+ const subId = message.subscriptionId;
141
+ if (!subId) return;
142
+ const sub = this.subscriptions.get(subId);
143
+ if (!sub || !sub.active) {
144
+ this.log("Received event for unknown/inactive subscription:", subId);
145
+ return;
146
+ }
147
+ try {
148
+ sub.callback(message.data);
149
+ } catch (error) {
150
+ this.log("Error in subscription callback:", error);
151
+ this.emitError(error instanceof Error ? error : new Error("Callback error"));
152
+ }
153
+ }
154
+ /**
155
+ * Handle unsubscribe confirmation (from Talon)
156
+ */
157
+ handleUnsubscribed(message) {
158
+ const subId = message.subscriptionId;
159
+ if (!subId) return;
160
+ this.log("Unsubscribe confirmed:", subId);
161
+ this.subscriptions.delete(subId);
162
+ }
163
+ /**
164
+ * Register error callback
165
+ */
166
+ onError(callback) {
167
+ this.errorCallbacks.add(callback);
168
+ return () => this.errorCallbacks.delete(callback);
169
+ }
170
+ /**
171
+ * Register connect callback
172
+ */
173
+ onConnect(callback) {
174
+ this.connectCallbacks.add(callback);
175
+ return () => this.connectCallbacks.delete(callback);
176
+ }
177
+ /**
178
+ * Register disconnect callback
179
+ */
180
+ onDisconnect(callback) {
181
+ this.disconnectCallbacks.add(callback);
182
+ return () => this.disconnectCallbacks.delete(callback);
183
+ }
184
+ /**
185
+ * Emit error to all error callbacks
186
+ */
187
+ emitError(error) {
188
+ this.errorCallbacks.forEach((cb) => {
189
+ try {
190
+ cb(error);
191
+ } catch (err) {
192
+ this.log("Error in error callback:", err);
193
+ }
194
+ });
195
+ }
196
+ /**
197
+ * Check if connected (via Talon)
198
+ */
199
+ isConnected() {
200
+ return this.talon.connected;
201
+ }
202
+ /**
203
+ * Get number of active subscriptions
204
+ */
205
+ getSubscriptionCount() {
206
+ return this.subscriptions.size;
207
+ }
208
+ /**
209
+ * Debug logging
210
+ */
211
+ log(...args) {
212
+ if (this.debug) {
213
+ console.log("[SubscriptionManager]", ...args);
214
+ }
215
+ }
216
+ };
217
+ export {
218
+ SubscriptionManager
219
+ };
220
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/SubscriptionManager.ts"],"sourcesContent":["/**\n * SubscriptionManager - Real-time event subscriptions\n * \n * Built on top of @sygnl/talon for WebSocket management.\n * Provides high-level subscription API with filtering.\n */\n\nimport { TalonClient } from '@sygnl/talon';\nimport { WebSocketTransport } from '@sygnl/talon/transports';\nimport type { AnyEvent } from '@sygnl/event-schema';\n\nimport type {\n SubscriptionManagerOptions,\n SubscriptionOptions,\n SubscriptionFilter,\n SubscriptionCallback,\n ErrorCallback,\n SubscriptionMessage,\n SubscriptionRequest,\n ActiveSubscription,\n} from './types';\n\nexport class SubscriptionManager {\n private talon: TalonClient;\n private subscriptions: Map<string, ActiveSubscription> = new Map();\n private pendingSubscriptions: Map<string, {\n filters: SubscriptionFilter;\n callback: SubscriptionCallback;\n }> = new Map();\n private tempIdToServerIdMap: Map<string, string> = new Map();\n \n private debug: boolean;\n \n private errorCallbacks: Set<ErrorCallback> = new Set();\n private connectCallbacks: Set<() => void> = new Set();\n private disconnectCallbacks: Set<() => void> = new Set();\n\n constructor(options: SubscriptionManagerOptions) {\n this.debug = options.debug ?? false;\n\n // Use Talon for WebSocket management!\n this.talon = new TalonClient({\n transport: new WebSocketTransport({\n url: options.url,\n debug: this.debug,\n }),\n autoConnect: options.autoConnect ?? false,\n debug: this.debug,\n });\n\n // Listen to Talon events\n this.talon.on('_connected', () => {\n this.log('Connected via Talon');\n this.resubscribeAll();\n this.connectCallbacks.forEach((cb) => cb());\n });\n\n this.talon.on('_disconnected', () => {\n this.log('Disconnected via Talon');\n this.disconnectCallbacks.forEach((cb) => cb());\n \n // Mark all subscriptions as inactive\n for (const sub of this.subscriptions.values()) {\n sub.active = false;\n }\n });\n\n this.talon.on('_error', (error: any) => {\n this.emitError(error instanceof Error ? error : new Error(String(error)));\n });\n\n // Listen for subscription-specific messages\n this.talon.on('subscribed', (data: any) => this.handleSubscribed(data));\n this.talon.on('event', (data: any) => this.handleEvent(data));\n this.talon.on('unsubscribed', (data: any) => this.handleUnsubscribed(data));\n this.talon.on('error', (data: any) => this.emitError(new Error(data.error || 'Unknown error')));\n }\n\n /**\n * Connect to WebSocket server (via Talon)\n */\n async connect(): Promise<void> {\n return this.talon.connect();\n }\n\n /**\n * Disconnect from WebSocket server (via Talon)\n */\n disconnect(): void {\n this.talon.disconnect();\n }\n\n /**\n * Subscribe to events with optional filters\n * \n * @returns Unsubscribe function\n */\n subscribe<T = any>(\n callback: SubscriptionCallback<T>,\n options: SubscriptionOptions = {}\n ): () => void {\n const tempId = crypto.randomUUID();\n \n // Store as pending until server confirms\n this.pendingSubscriptions.set(tempId, {\n filters: options.filters || {},\n callback,\n });\n\n // Send subscribe request if connected (via Talon)\n if (this.talon.connected) {\n this.sendSubscribeRequest(tempId, options.filters || {});\n }\n\n // Return unsubscribe function\n return () => {\n this.unsubscribe(tempId);\n };\n }\n\n /**\n * Unsubscribe from events\n */\n private async unsubscribe(tempId: string): Promise<void> {\n // Map temp ID to server ID\n const serverSubId = this.tempIdToServerIdMap.get(tempId);\n \n if (serverSubId) {\n const sub = this.subscriptions.get(serverSubId);\n \n if (sub) {\n this.log('Unsubscribing:', serverSubId);\n \n // Send unsubscribe request via Talon\n if (this.talon.connected) {\n const request: SubscriptionRequest = {\n type: 'unsubscribe',\n subscriptionId: serverSubId,\n };\n await this.talon.send(request as any);\n }\n\n // Remove from active subscriptions\n this.subscriptions.delete(serverSubId);\n this.tempIdToServerIdMap.delete(tempId);\n }\n }\n\n // Also check pending\n this.pendingSubscriptions.delete(tempId);\n }\n\n /**\n * Send subscribe request to server (via Talon)\n */\n private async sendSubscribeRequest(tempId: string, filters: SubscriptionFilter): Promise<void> {\n if (!this.talon.connected) {\n this.log('Cannot subscribe: not connected');\n return;\n }\n\n const request: SubscriptionRequest = {\n type: 'subscribe',\n filters,\n };\n\n this.log('Subscribing with filters:', filters);\n await this.talon.send(request as any);\n }\n\n /**\n * Resubscribe to all active subscriptions (after reconnect)\n */\n private resubscribeAll(): void {\n this.log('Resubscribing to', this.pendingSubscriptions.size, 'subscriptions');\n\n for (const [tempId, { filters, callback }] of this.pendingSubscriptions.entries()) {\n this.sendSubscribeRequest(tempId, filters);\n }\n }\n\n /**\n * Handle subscription confirmation (from Talon)\n */\n private handleSubscribed(message: any): void {\n const serverSubId = message.subscriptionId;\n if (!serverSubId) return;\n\n this.log('Subscription confirmed:', serverSubId);\n\n // Find the pending subscription\n const pending = Array.from(this.pendingSubscriptions.entries())[0];\n if (!pending) return;\n\n const [tempId, { filters, callback }] = pending;\n\n // Map temp ID to server ID\n this.tempIdToServerIdMap.set(tempId, serverSubId);\n\n // Move from pending to active\n this.subscriptions.set(serverSubId, {\n id: serverSubId,\n filters,\n callback,\n active: true,\n });\n this.pendingSubscriptions.delete(tempId);\n }\n\n /**\n * Handle incoming event (from Talon)\n */\n private handleEvent(message: any): void {\n const subId = message.subscriptionId;\n if (!subId) return;\n\n const sub = this.subscriptions.get(subId);\n if (!sub || !sub.active) {\n this.log('Received event for unknown/inactive subscription:', subId);\n return;\n }\n\n // Call the subscription callback\n try {\n sub.callback(message.data);\n } catch (error) {\n this.log('Error in subscription callback:', error);\n this.emitError(error instanceof Error ? error : new Error('Callback error'));\n }\n }\n\n /**\n * Handle unsubscribe confirmation (from Talon)\n */\n private handleUnsubscribed(message: any): void {\n const subId = message.subscriptionId;\n if (!subId) return;\n\n this.log('Unsubscribe confirmed:', subId);\n this.subscriptions.delete(subId);\n }\n\n /**\n * Register error callback\n */\n onError(callback: ErrorCallback): () => void {\n this.errorCallbacks.add(callback);\n return () => this.errorCallbacks.delete(callback);\n }\n\n /**\n * Register connect callback\n */\n onConnect(callback: () => void): () => void {\n this.connectCallbacks.add(callback);\n return () => this.connectCallbacks.delete(callback);\n }\n\n /**\n * Register disconnect callback\n */\n onDisconnect(callback: () => void): () => void {\n this.disconnectCallbacks.add(callback);\n return () => this.disconnectCallbacks.delete(callback);\n }\n\n /**\n * Emit error to all error callbacks\n */\n private emitError(error: Error): void {\n this.errorCallbacks.forEach((cb) => {\n try {\n cb(error);\n } catch (err) {\n this.log('Error in error callback:', err);\n }\n });\n }\n\n /**\n * Check if connected (via Talon)\n */\n isConnected(): boolean {\n return this.talon.connected;\n }\n\n /**\n * Get number of active subscriptions\n */\n getSubscriptionCount(): number {\n return this.subscriptions.size;\n }\n\n /**\n * Debug logging\n */\n private log(...args: any[]): void {\n if (this.debug) {\n console.log('[SubscriptionManager]', ...args);\n }\n }\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@sygnl/subscriptions",
3
+ "version": "1.0.0",
4
+ "description": "Real-time event subscriptions via WebSocket with filtering and type-safe callbacks",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "keywords": [
20
+ "subscriptions",
21
+ "websocket",
22
+ "real-time",
23
+ "pub-sub",
24
+ "events",
25
+ "durable-objects",
26
+ "cloudflare"
27
+ ],
28
+ "scripts": {
29
+ "build": "tsup",
30
+ "test": "vitest run",
31
+ "test:watch": "vitest",
32
+ "test:coverage": "vitest run --coverage",
33
+ "typecheck": "tsc --noEmit",
34
+ "prepublishOnly": "npm run build"
35
+ },
36
+ "dependencies": {
37
+ "@sygnl/event-schema": "^1.0.0",
38
+ "@sygnl/talon": "^1.0.0"
39
+ },
40
+ "devDependencies": {
41
+ "@types/node": "^20.10.0",
42
+ "@vitest/coverage-v8": "^1.0.4",
43
+ "tsup": "^8.0.1",
44
+ "typescript": "^5.3.3",
45
+ "vite-tsconfig-paths": "^6.0.4",
46
+ "vitest": "^1.0.4"
47
+ },
48
+ "engines": {
49
+ "node": ">=18"
50
+ },
51
+ "author": "Edge Foundry, Inc.",
52
+ "license": "Apache-2.0"
53
+ }