@flightdev/realtime 0.0.2

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 @@
1
+ {"version":3,"sources":["../../src/adapters/sse.ts"],"names":[],"mappings":"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","file":"sse.js","sourcesContent":["/**\r\n * SSE (Server-Sent Events) Adapter for @flightdev/realtime\r\n * \r\n * Edge-compatible transport using SSE for server-to-client\r\n * and fetch POST for client-to-server communication.\r\n * \r\n * @example\r\n * ```typescript\r\n * import { createRealtime } from '@flightdev/realtime';\r\n * import { sse } from '@flightdev/realtime/sse';\r\n * \r\n * const realtime = createRealtime(sse({\r\n * url: '/api/realtime',\r\n * }));\r\n * ```\r\n */\r\n\r\nimport type {\r\n RealtimeAdapter,\r\n RealtimeAdapterFactory,\r\n RealtimeChannel,\r\n RealtimeMessage,\r\n ConnectionState,\r\n ChannelOptions,\r\n SubscriptionCallback,\r\n Unsubscribe,\r\n} from '../index.js';\r\nimport { createMessage, parseMessage } from '../index.js';\r\n\r\n// ============================================================================\r\n// Types\r\n// ============================================================================\r\n\r\nexport interface SSEConfig {\r\n /** URL for SSE endpoint */\r\n url: string;\r\n /** URL for sending messages (defaults to same as url) */\r\n sendUrl?: string;\r\n /** Retry delay on disconnect */\r\n retryDelay?: number;\r\n /** Custom headers */\r\n headers?: Record<string, string>;\r\n}\r\n\r\n// ============================================================================\r\n// SSE Channel\r\n// ============================================================================\r\n\r\nclass SSEChannel<T = unknown> implements RealtimeChannel<T> {\r\n readonly name: string;\r\n private subscribers = new Map<string, Set<SubscriptionCallback<T>>>();\r\n private allSubscribers = new Set<SubscriptionCallback<T>>();\r\n private sendFn: (channel: string, event: string, data: unknown) => void;\r\n\r\n constructor(name: string, sendFn: (channel: string, event: string, data: unknown) => void) {\r\n this.name = name;\r\n this.sendFn = sendFn;\r\n }\r\n\r\n subscribe(callback: SubscriptionCallback<T>): Unsubscribe {\r\n this.allSubscribers.add(callback);\r\n return () => this.allSubscribers.delete(callback);\r\n }\r\n\r\n on(event: string, callback: SubscriptionCallback<T>): Unsubscribe {\r\n if (!this.subscribers.has(event)) {\r\n this.subscribers.set(event, new Set());\r\n }\r\n this.subscribers.get(event)!.add(callback);\r\n return () => this.subscribers.get(event)?.delete(callback);\r\n }\r\n\r\n broadcast(data: T, event = 'message'): void {\r\n this.sendFn(this.name, event, data);\r\n }\r\n\r\n send(data: T, event = 'message'): void {\r\n this.broadcast(data, event);\r\n }\r\n\r\n leave(): void {\r\n this.allSubscribers.clear();\r\n this.subscribers.clear();\r\n }\r\n\r\n _handleMessage(message: RealtimeMessage<T>): void {\r\n this.allSubscribers.forEach(cb => cb(message));\r\n const eventSubs = this.subscribers.get(message.event);\r\n eventSubs?.forEach(cb => cb(message));\r\n }\r\n}\r\n\r\n// ============================================================================\r\n// SSE Adapter\r\n// ============================================================================\r\n\r\n/**\r\n * Create an SSE adapter\r\n * \r\n * SSE provides server-to-client streaming, with fetch POST for\r\n * client-to-server messages. Works on Edge runtimes.\r\n */\r\nexport const sse: RealtimeAdapterFactory<SSEConfig> = (config) => {\r\n if (!config?.url) {\r\n throw new Error('@flightdev/realtime: SSE requires a url configuration');\r\n }\r\n\r\n const {\r\n url,\r\n sendUrl = url,\r\n retryDelay = 3000,\r\n headers = {},\r\n } = config;\r\n\r\n let eventSource: EventSource | null = null;\r\n let state: ConnectionState = 'disconnected';\r\n const channels = new Map<string, SSEChannel>();\r\n const stateCallbacks = new Set<(state: ConnectionState) => void>();\r\n const errorCallbacks = new Set<(error: Error) => void>();\r\n const subscribedChannels = new Set<string>();\r\n\r\n function setState(newState: ConnectionState): void {\r\n state = newState;\r\n stateCallbacks.forEach(cb => cb(state));\r\n }\r\n\r\n async function send(channel: string, event: string, data: unknown): Promise<void> {\r\n const message = createMessage(channel, event, data);\r\n\r\n try {\r\n const response = await fetch(sendUrl, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n ...headers,\r\n },\r\n body: JSON.stringify(message),\r\n });\r\n\r\n if (!response.ok) {\r\n throw new Error(`Failed to send message: ${response.status}`);\r\n }\r\n } catch (error) {\r\n errorCallbacks.forEach(cb => cb(error as Error));\r\n }\r\n }\r\n\r\n function handleMessage(event: MessageEvent): void {\r\n const message = parseMessage(event.data as string);\r\n if (!message) return;\r\n\r\n const channel = channels.get(message.channel);\r\n if (channel) {\r\n channel._handleMessage(message as RealtimeMessage<unknown>);\r\n }\r\n }\r\n\r\n const adapter: RealtimeAdapter = {\r\n name: 'sse',\r\n\r\n get state() {\r\n return state;\r\n },\r\n\r\n async connect(): Promise<void> {\r\n if (state === 'connected' || state === 'connecting') {\r\n return;\r\n }\r\n\r\n setState('connecting');\r\n\r\n return new Promise((resolve, reject) => {\r\n // Build URL with subscribed channels\r\n const sseUrl = new URL(url, globalThis.location?.origin ?? 'http://localhost');\r\n subscribedChannels.forEach(ch => {\r\n sseUrl.searchParams.append('channel', ch);\r\n });\r\n\r\n eventSource = new EventSource(sseUrl.toString());\r\n\r\n eventSource.onopen = () => {\r\n setState('connected');\r\n resolve();\r\n };\r\n\r\n eventSource.onerror = (_event) => {\r\n if (state === 'connecting') {\r\n setState('disconnected');\r\n reject(new Error('SSE connection failed'));\r\n } else {\r\n setState('reconnecting');\r\n setTimeout(() => {\r\n adapter.connect().catch(() => { });\r\n }, retryDelay);\r\n }\r\n errorCallbacks.forEach(cb => cb(new Error('SSE error')));\r\n };\r\n\r\n eventSource.onmessage = handleMessage;\r\n\r\n // Also listen for custom event types\r\n eventSource.addEventListener('realtime', (e) => {\r\n handleMessage(e as MessageEvent);\r\n });\r\n });\r\n },\r\n\r\n disconnect(): void {\r\n eventSource?.close();\r\n eventSource = null;\r\n setState('disconnected');\r\n },\r\n\r\n channel<T = unknown>(name: string, _options?: ChannelOptions): RealtimeChannel<T> {\r\n if (!channels.has(name)) {\r\n channels.set(name, new SSEChannel(name, send));\r\n subscribedChannels.add(name);\r\n\r\n // If already connected, need to reconnect to add channel\r\n if (state === 'connected') {\r\n adapter.disconnect();\r\n adapter.connect().catch(() => { });\r\n }\r\n }\r\n return channels.get(name) as RealtimeChannel<T>;\r\n },\r\n\r\n onStateChange(callback): Unsubscribe {\r\n stateCallbacks.add(callback);\r\n return () => stateCallbacks.delete(callback);\r\n },\r\n\r\n onError(callback): Unsubscribe {\r\n errorCallbacks.add(callback);\r\n return () => errorCallbacks.delete(callback);\r\n },\r\n\r\n send(channel: string, event: string, data: unknown): void {\r\n send(channel, event, data).catch(() => { });\r\n },\r\n };\r\n\r\n return adapter;\r\n};\r\n\r\nexport default sse;\r\n\r\n// ============================================================================\r\n// Server Utilities (for Edge/Node.js)\r\n// ============================================================================\r\n\r\n/**\r\n * Create an SSE response for server-side streaming\r\n * \r\n * @example\r\n * ```typescript\r\n * // In your API route\r\n * export async function GET(request: Request) {\r\n * return createSSEResponse(request, (send) => {\r\n * // Subscribe to updates\r\n * const unsubscribe = mySource.subscribe((data) => {\r\n * send('message', data);\r\n * });\r\n * \r\n * // Cleanup on close\r\n * return () => unsubscribe();\r\n * });\r\n * }\r\n * ```\r\n */\r\nexport function createSSEResponse(\r\n _request: Request,\r\n handler: (send: (event: string, data: unknown) => void) => (() => void) | void\r\n): Response {\r\n const encoder = new TextEncoder();\r\n let cleanup: (() => void) | void;\r\n\r\n const stream = new ReadableStream({\r\n start(controller) {\r\n function send(event: string, data: unknown): void {\r\n const message = `event: ${event}\\ndata: ${JSON.stringify(data)}\\n\\n`;\r\n controller.enqueue(encoder.encode(message));\r\n }\r\n\r\n cleanup = handler(send);\r\n\r\n // Send initial connection event\r\n send('connected', { timestamp: Date.now() });\r\n },\r\n cancel() {\r\n cleanup?.();\r\n },\r\n });\r\n\r\n return new Response(stream, {\r\n headers: {\r\n 'Content-Type': 'text/event-stream',\r\n 'Cache-Control': 'no-cache',\r\n 'Connection': 'keep-alive',\r\n },\r\n });\r\n}\r\n"]}
@@ -0,0 +1,40 @@
1
+ import { RealtimeAdapterFactory } from '../index.js';
2
+
3
+ /**
4
+ * WebSocket Adapter for @flightdev/realtime
5
+ *
6
+ * Native WebSocket transport for Node.js and browser environments.
7
+ *
8
+ * @example
9
+ * ```typescript
10
+ * import { createRealtime } from '@flightdev/realtime';
11
+ * import { websocket } from '@flightdev/realtime/websocket';
12
+ *
13
+ * // Client
14
+ * const client = createRealtime(websocket({ url: 'ws://localhost:3001' }));
15
+ *
16
+ * // Server (Node.js)
17
+ * const server = createRealtime(websocket({ port: 3001, server: true }));
18
+ * ```
19
+ */
20
+
21
+ interface WebSocketConfig {
22
+ /** WebSocket URL (client mode) */
23
+ url?: string;
24
+ /** Port to listen on (server mode) */
25
+ port?: number;
26
+ /** Host to bind to (server mode) */
27
+ host?: string;
28
+ /** Path for WebSocket endpoint */
29
+ path?: string;
30
+ /** Ping interval in ms */
31
+ pingInterval?: number;
32
+ /** Connection timeout in ms */
33
+ timeout?: number;
34
+ }
35
+ /**
36
+ * Create a WebSocket adapter
37
+ */
38
+ declare const websocket: RealtimeAdapterFactory<WebSocketConfig>;
39
+
40
+ export { type WebSocketConfig, websocket as default, websocket };
@@ -0,0 +1,161 @@
1
+ import { createMessage, serializeMessage, parseMessage } from '../chunk-D6OMTDYP.js';
2
+
3
+ // src/adapters/websocket.ts
4
+ var WebSocketChannel = class {
5
+ name;
6
+ subscribers = /* @__PURE__ */ new Map();
7
+ allSubscribers = /* @__PURE__ */ new Set();
8
+ sendFn;
9
+ constructor(name, sendFn) {
10
+ this.name = name;
11
+ this.sendFn = sendFn;
12
+ }
13
+ subscribe(callback) {
14
+ this.allSubscribers.add(callback);
15
+ return () => this.allSubscribers.delete(callback);
16
+ }
17
+ on(event, callback) {
18
+ if (!this.subscribers.has(event)) {
19
+ this.subscribers.set(event, /* @__PURE__ */ new Set());
20
+ }
21
+ this.subscribers.get(event).add(callback);
22
+ return () => this.subscribers.get(event)?.delete(callback);
23
+ }
24
+ broadcast(data, event = "message") {
25
+ this.sendFn(this.name, event, data);
26
+ }
27
+ send(data, event = "message") {
28
+ this.broadcast(data, event);
29
+ }
30
+ leave() {
31
+ this.allSubscribers.clear();
32
+ this.subscribers.clear();
33
+ }
34
+ // Internal: handle incoming message
35
+ _handleMessage(message) {
36
+ this.allSubscribers.forEach((cb) => cb(message));
37
+ const eventSubs = this.subscribers.get(message.event);
38
+ eventSubs?.forEach((cb) => cb(message));
39
+ }
40
+ };
41
+ var websocket = (config = {}) => {
42
+ const {
43
+ url,
44
+ port,
45
+ host = "localhost",
46
+ path = "/ws",
47
+ pingInterval = 3e4,
48
+ timeout = 5e3
49
+ } = config;
50
+ let ws = null;
51
+ let state = "disconnected";
52
+ const channels = /* @__PURE__ */ new Map();
53
+ const stateCallbacks = /* @__PURE__ */ new Set();
54
+ const errorCallbacks = /* @__PURE__ */ new Set();
55
+ let pingTimer = null;
56
+ function setState(newState) {
57
+ state = newState;
58
+ stateCallbacks.forEach((cb) => cb(state));
59
+ }
60
+ function send(channel, event, data) {
61
+ if (ws && ws.readyState === WebSocket.OPEN) {
62
+ const message = createMessage(channel, event, data);
63
+ ws.send(serializeMessage(message));
64
+ }
65
+ }
66
+ function handleMessage(event) {
67
+ const message = parseMessage(event.data);
68
+ if (!message) return;
69
+ const channel = channels.get(message.channel);
70
+ if (channel) {
71
+ channel._handleMessage(message);
72
+ }
73
+ }
74
+ const adapter = {
75
+ name: "websocket",
76
+ get state() {
77
+ return state;
78
+ },
79
+ async connect() {
80
+ if (state === "connected" || state === "connecting") {
81
+ return;
82
+ }
83
+ setState("connecting");
84
+ return new Promise((resolve, reject) => {
85
+ const wsUrl = url ?? `ws://${host}:${port}${path}`;
86
+ try {
87
+ ws = new WebSocket(wsUrl);
88
+ } catch (error) {
89
+ setState("disconnected");
90
+ reject(error);
91
+ return;
92
+ }
93
+ const timeoutId = setTimeout(() => {
94
+ ws?.close();
95
+ setState("disconnected");
96
+ reject(new Error("Connection timeout"));
97
+ }, timeout);
98
+ ws.onopen = () => {
99
+ clearTimeout(timeoutId);
100
+ setState("connected");
101
+ pingTimer = setInterval(() => {
102
+ if (ws?.readyState === WebSocket.OPEN) {
103
+ ws.send(JSON.stringify({ type: "ping" }));
104
+ }
105
+ }, pingInterval);
106
+ resolve();
107
+ };
108
+ ws.onclose = () => {
109
+ clearTimeout(timeoutId);
110
+ if (pingTimer) {
111
+ clearInterval(pingTimer);
112
+ pingTimer = null;
113
+ }
114
+ setState("disconnected");
115
+ };
116
+ ws.onerror = (_event) => {
117
+ clearTimeout(timeoutId);
118
+ const error = new Error("WebSocket error");
119
+ errorCallbacks.forEach((cb) => cb(error));
120
+ if (state === "connecting") {
121
+ reject(error);
122
+ }
123
+ };
124
+ ws.onmessage = handleMessage;
125
+ });
126
+ },
127
+ disconnect() {
128
+ if (pingTimer) {
129
+ clearInterval(pingTimer);
130
+ pingTimer = null;
131
+ }
132
+ ws?.close();
133
+ ws = null;
134
+ setState("disconnected");
135
+ },
136
+ channel(name, options) {
137
+ if (!channels.has(name)) {
138
+ channels.set(name, new WebSocketChannel(name, send));
139
+ if (ws?.readyState === WebSocket.OPEN) {
140
+ send(name, "join", { options });
141
+ }
142
+ }
143
+ return channels.get(name);
144
+ },
145
+ onStateChange(callback) {
146
+ stateCallbacks.add(callback);
147
+ return () => stateCallbacks.delete(callback);
148
+ },
149
+ onError(callback) {
150
+ errorCallbacks.add(callback);
151
+ return () => errorCallbacks.delete(callback);
152
+ },
153
+ send
154
+ };
155
+ return adapter;
156
+ };
157
+ var websocket_default = websocket;
158
+
159
+ export { websocket_default as default, websocket };
160
+ //# sourceMappingURL=websocket.js.map
161
+ //# sourceMappingURL=websocket.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/adapters/websocket.ts"],"names":[],"mappings":"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","file":"websocket.js","sourcesContent":["/**\r\n * WebSocket Adapter for @flightdev/realtime\r\n * \r\n * Native WebSocket transport for Node.js and browser environments.\r\n * \r\n * @example\r\n * ```typescript\r\n * import { createRealtime } from '@flightdev/realtime';\r\n * import { websocket } from '@flightdev/realtime/websocket';\r\n * \r\n * // Client\r\n * const client = createRealtime(websocket({ url: 'ws://localhost:3001' }));\r\n * \r\n * // Server (Node.js)\r\n * const server = createRealtime(websocket({ port: 3001, server: true }));\r\n * ```\r\n */\r\n\r\nimport type {\r\n RealtimeAdapter,\r\n RealtimeAdapterFactory,\r\n RealtimeChannel,\r\n RealtimeMessage,\r\n ConnectionState,\r\n ChannelOptions,\r\n SubscriptionCallback,\r\n Unsubscribe,\r\n} from '../index.js';\r\nimport { createMessage, serializeMessage, parseMessage } from '../index.js';\r\n\r\n// ============================================================================\r\n// Types\r\n// ============================================================================\r\n\r\nexport interface WebSocketConfig {\r\n /** WebSocket URL (client mode) */\r\n url?: string;\r\n /** Port to listen on (server mode) */\r\n port?: number;\r\n /** Host to bind to (server mode) */\r\n host?: string;\r\n /** Path for WebSocket endpoint */\r\n path?: string;\r\n /** Ping interval in ms */\r\n pingInterval?: number;\r\n /** Connection timeout in ms */\r\n timeout?: number;\r\n}\r\n\r\n// ============================================================================\r\n// WebSocket Channel\r\n// ============================================================================\r\n\r\nclass WebSocketChannel<T = unknown> implements RealtimeChannel<T> {\r\n readonly name: string;\r\n private subscribers = new Map<string, Set<SubscriptionCallback<T>>>();\r\n private allSubscribers = new Set<SubscriptionCallback<T>>();\r\n private sendFn: (channel: string, event: string, data: unknown) => void;\r\n\r\n constructor(name: string, sendFn: (channel: string, event: string, data: unknown) => void) {\r\n this.name = name;\r\n this.sendFn = sendFn;\r\n }\r\n\r\n subscribe(callback: SubscriptionCallback<T>): Unsubscribe {\r\n this.allSubscribers.add(callback);\r\n return () => this.allSubscribers.delete(callback);\r\n }\r\n\r\n on(event: string, callback: SubscriptionCallback<T>): Unsubscribe {\r\n if (!this.subscribers.has(event)) {\r\n this.subscribers.set(event, new Set());\r\n }\r\n this.subscribers.get(event)!.add(callback);\r\n return () => this.subscribers.get(event)?.delete(callback);\r\n }\r\n\r\n broadcast(data: T, event = 'message'): void {\r\n this.sendFn(this.name, event, data);\r\n }\r\n\r\n send(data: T, event = 'message'): void {\r\n this.broadcast(data, event);\r\n }\r\n\r\n leave(): void {\r\n this.allSubscribers.clear();\r\n this.subscribers.clear();\r\n }\r\n\r\n // Internal: handle incoming message\r\n _handleMessage(message: RealtimeMessage<T>): void {\r\n // Notify all subscribers\r\n this.allSubscribers.forEach(cb => cb(message));\r\n\r\n // Notify event-specific subscribers\r\n const eventSubs = this.subscribers.get(message.event);\r\n eventSubs?.forEach(cb => cb(message));\r\n }\r\n}\r\n\r\n// ============================================================================\r\n// WebSocket Adapter\r\n// ============================================================================\r\n\r\n/**\r\n * Create a WebSocket adapter\r\n */\r\nexport const websocket: RealtimeAdapterFactory<WebSocketConfig> = (config = {}) => {\r\n const {\r\n url,\r\n port,\r\n host = 'localhost',\r\n path = '/ws',\r\n pingInterval = 30000,\r\n timeout = 5000,\r\n } = config;\r\n\r\n let ws: WebSocket | null = null;\r\n let state: ConnectionState = 'disconnected';\r\n const channels = new Map<string, WebSocketChannel>();\r\n const stateCallbacks = new Set<(state: ConnectionState) => void>();\r\n const errorCallbacks = new Set<(error: Error) => void>();\r\n let pingTimer: ReturnType<typeof setInterval> | null = null;\r\n\r\n function setState(newState: ConnectionState): void {\r\n state = newState;\r\n stateCallbacks.forEach(cb => cb(state));\r\n }\r\n\r\n function send(channel: string, event: string, data: unknown): void {\r\n if (ws && ws.readyState === WebSocket.OPEN) {\r\n const message = createMessage(channel, event, data);\r\n ws.send(serializeMessage(message));\r\n }\r\n }\r\n\r\n function handleMessage(event: MessageEvent): void {\r\n const message = parseMessage(event.data as string);\r\n if (!message) return;\r\n\r\n const channel = channels.get(message.channel);\r\n if (channel) {\r\n channel._handleMessage(message as RealtimeMessage<unknown>);\r\n }\r\n }\r\n\r\n const adapter: RealtimeAdapter = {\r\n name: 'websocket',\r\n\r\n get state() {\r\n return state;\r\n },\r\n\r\n async connect(): Promise<void> {\r\n if (state === 'connected' || state === 'connecting') {\r\n return;\r\n }\r\n\r\n setState('connecting');\r\n\r\n return new Promise((resolve, reject) => {\r\n const wsUrl = url ?? `ws://${host}:${port}${path}`;\r\n\r\n try {\r\n ws = new WebSocket(wsUrl);\r\n } catch (error) {\r\n setState('disconnected');\r\n reject(error);\r\n return;\r\n }\r\n\r\n const timeoutId = setTimeout(() => {\r\n ws?.close();\r\n setState('disconnected');\r\n reject(new Error('Connection timeout'));\r\n }, timeout);\r\n\r\n ws.onopen = () => {\r\n clearTimeout(timeoutId);\r\n setState('connected');\r\n\r\n // Start ping timer\r\n pingTimer = setInterval(() => {\r\n if (ws?.readyState === WebSocket.OPEN) {\r\n ws.send(JSON.stringify({ type: 'ping' }));\r\n }\r\n }, pingInterval);\r\n\r\n resolve();\r\n };\r\n\r\n ws.onclose = () => {\r\n clearTimeout(timeoutId);\r\n if (pingTimer) {\r\n clearInterval(pingTimer);\r\n pingTimer = null;\r\n }\r\n setState('disconnected');\r\n };\r\n\r\n ws.onerror = (_event) => {\r\n clearTimeout(timeoutId);\r\n const error = new Error('WebSocket error');\r\n errorCallbacks.forEach(cb => cb(error));\r\n if (state === 'connecting') {\r\n reject(error);\r\n }\r\n };\r\n\r\n ws.onmessage = handleMessage;\r\n });\r\n },\r\n\r\n disconnect(): void {\r\n if (pingTimer) {\r\n clearInterval(pingTimer);\r\n pingTimer = null;\r\n }\r\n ws?.close();\r\n ws = null;\r\n setState('disconnected');\r\n },\r\n\r\n channel<T = unknown>(name: string, options?: ChannelOptions): RealtimeChannel<T> {\r\n if (!channels.has(name)) {\r\n channels.set(name, new WebSocketChannel(name, send));\r\n\r\n // Send join message\r\n if (ws?.readyState === WebSocket.OPEN) {\r\n send(name, 'join', { options });\r\n }\r\n }\r\n return channels.get(name) as RealtimeChannel<T>;\r\n },\r\n\r\n onStateChange(callback): Unsubscribe {\r\n stateCallbacks.add(callback);\r\n return () => stateCallbacks.delete(callback);\r\n },\r\n\r\n onError(callback): Unsubscribe {\r\n errorCallbacks.add(callback);\r\n return () => errorCallbacks.delete(callback);\r\n },\r\n\r\n send,\r\n };\r\n\r\n return adapter;\r\n};\r\n\r\nexport default websocket;\r\n"]}
@@ -0,0 +1,85 @@
1
+ // src/index.ts
2
+ function createRealtime(adapter, options = {}) {
3
+ const {
4
+ autoConnect = true,
5
+ reconnect = true,
6
+ maxReconnectAttempts = 5,
7
+ reconnectDelay = 1e3
8
+ } = options;
9
+ let reconnectAttempts = 0;
10
+ const stateCallbacks = /* @__PURE__ */ new Set();
11
+ const errorCallbacks = /* @__PURE__ */ new Set();
12
+ if (reconnect) {
13
+ adapter.onStateChange((state) => {
14
+ if (state === "disconnected" && reconnectAttempts < maxReconnectAttempts) {
15
+ reconnectAttempts++;
16
+ setTimeout(() => {
17
+ adapter.connect().catch((err) => {
18
+ errorCallbacks.forEach((cb) => cb(err));
19
+ });
20
+ }, reconnectDelay * reconnectAttempts);
21
+ } else if (state === "connected") {
22
+ reconnectAttempts = 0;
23
+ }
24
+ });
25
+ }
26
+ adapter.onStateChange((state) => {
27
+ stateCallbacks.forEach((cb) => cb(state));
28
+ });
29
+ adapter.onError((error) => {
30
+ errorCallbacks.forEach((cb) => cb(error));
31
+ });
32
+ const service = {
33
+ adapter,
34
+ get state() {
35
+ return adapter.state;
36
+ },
37
+ async connect() {
38
+ await adapter.connect();
39
+ },
40
+ disconnect() {
41
+ adapter.disconnect();
42
+ },
43
+ channel(name, options2) {
44
+ return adapter.channel(name, options2);
45
+ },
46
+ onStateChange(callback) {
47
+ stateCallbacks.add(callback);
48
+ return () => stateCallbacks.delete(callback);
49
+ },
50
+ onError(callback) {
51
+ errorCallbacks.add(callback);
52
+ return () => errorCallbacks.delete(callback);
53
+ }
54
+ };
55
+ if (autoConnect) {
56
+ service.connect().catch((err) => {
57
+ errorCallbacks.forEach((cb) => cb(err));
58
+ });
59
+ }
60
+ return service;
61
+ }
62
+ function createMessage(channel, event, data, senderId) {
63
+ return {
64
+ id: typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,
65
+ channel,
66
+ event,
67
+ data,
68
+ timestamp: Date.now(),
69
+ senderId
70
+ };
71
+ }
72
+ function parseMessage(json) {
73
+ try {
74
+ return JSON.parse(json);
75
+ } catch {
76
+ return null;
77
+ }
78
+ }
79
+ function serializeMessage(message) {
80
+ return JSON.stringify(message);
81
+ }
82
+
83
+ export { createMessage, createRealtime, parseMessage, serializeMessage };
84
+ //# sourceMappingURL=chunk-D6OMTDYP.js.map
85
+ //# sourceMappingURL=chunk-D6OMTDYP.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"names":["options"],"mappings":"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","file":"chunk-D6OMTDYP.js","sourcesContent":["/**\r\n * @flightdev/realtime - Agnostic Real-time Communication\r\n * \r\n * Flight provides real-time primitives, you choose the transport.\r\n * \r\n * @example\r\n * ```typescript\r\n * import { createRealtime } from '@flightdev/realtime';\r\n * import { websocket } from '@flightdev/realtime/websocket';\r\n * \r\n * const realtime = createRealtime(websocket({ port: 3001 }));\r\n * \r\n * // Create a channel\r\n * const chat = realtime.channel('chat');\r\n * \r\n * // Broadcast a message\r\n * chat.broadcast({ message: 'Hello!' });\r\n * \r\n * // Subscribe to messages\r\n * chat.subscribe((message) => console.log(message));\r\n * ```\r\n */\r\n\r\n// ============================================================================\r\n// Types\r\n// ============================================================================\r\n\r\n/** Real-time message payload */\r\nexport interface RealtimeMessage<T = unknown> {\r\n /** Unique message ID */\r\n id: string;\r\n /** Channel name */\r\n channel: string;\r\n /** Event type */\r\n event: string;\r\n /** Message payload */\r\n data: T;\r\n /** Timestamp */\r\n timestamp: number;\r\n /** Sender ID (optional) */\r\n senderId?: string;\r\n}\r\n\r\n/** Connection state */\r\nexport type ConnectionState = 'connecting' | 'connected' | 'disconnected' | 'reconnecting';\r\n\r\n/** Subscription callback */\r\nexport type SubscriptionCallback<T = unknown> = (message: RealtimeMessage<T>) => void;\r\n\r\n/** Unsubscribe function */\r\nexport type Unsubscribe = () => void;\r\n\r\n/** Channel options */\r\nexport interface ChannelOptions {\r\n /** Require authentication */\r\n auth?: boolean;\r\n /** Private channel (single user) */\r\n private?: boolean;\r\n /** Presence tracking */\r\n presence?: boolean;\r\n}\r\n\r\n/** Presence info */\r\nexport interface PresenceInfo<T = unknown> {\r\n /** User ID */\r\n userId: string;\r\n /** User info */\r\n info: T;\r\n /** Joined at timestamp */\r\n joinedAt: number;\r\n}\r\n\r\n// ============================================================================\r\n// Channel Interface\r\n// ============================================================================\r\n\r\n/** Real-time channel */\r\nexport interface RealtimeChannel<T = unknown> {\r\n /** Channel name */\r\n readonly name: string;\r\n\r\n /** Subscribe to all messages on this channel */\r\n subscribe(callback: SubscriptionCallback<T>): Unsubscribe;\r\n\r\n /** Subscribe to a specific event */\r\n on(event: string, callback: SubscriptionCallback<T>): Unsubscribe;\r\n\r\n /** Broadcast a message to all subscribers */\r\n broadcast(data: T, event?: string): void;\r\n\r\n /** Send a message (alias for broadcast) */\r\n send(data: T, event?: string): void;\r\n\r\n /** Leave the channel */\r\n leave(): void;\r\n\r\n /** Get presence information (if enabled) */\r\n getPresence?(): Promise<PresenceInfo[]>;\r\n\r\n /** Track user presence */\r\n trackPresence?(info: unknown): void;\r\n}\r\n\r\n// ============================================================================\r\n// Adapter Interface\r\n// ============================================================================\r\n\r\n/**\r\n * Real-time Adapter Interface\r\n * \r\n * Implement this to create a custom transport.\r\n * Flight provides WebSocket and SSE adapters.\r\n */\r\nexport interface RealtimeAdapter {\r\n /** Adapter name */\r\n readonly name: string;\r\n\r\n /** Current connection state */\r\n readonly state: ConnectionState;\r\n\r\n /** Connect to the server */\r\n connect(): Promise<void>;\r\n\r\n /** Disconnect from the server */\r\n disconnect(): void;\r\n\r\n /** Join a channel */\r\n channel<T = unknown>(name: string, options?: ChannelOptions): RealtimeChannel<T>;\r\n\r\n /** Subscribe to connection state changes */\r\n onStateChange(callback: (state: ConnectionState) => void): Unsubscribe;\r\n\r\n /** Subscribe to errors */\r\n onError(callback: (error: Error) => void): Unsubscribe;\r\n\r\n /** Send a raw message */\r\n send(channel: string, event: string, data: unknown): void;\r\n\r\n /** Handle incoming message (for server-side) */\r\n handleMessage?(handler: (message: RealtimeMessage) => void): void;\r\n}\r\n\r\n/** Adapter factory type */\r\nexport type RealtimeAdapterFactory<TConfig = unknown> = (config?: TConfig) => RealtimeAdapter;\r\n\r\n// ============================================================================\r\n// Service Interface\r\n// ============================================================================\r\n\r\n/** Real-time service options */\r\nexport interface RealtimeServiceOptions {\r\n /** Auto-connect on creation */\r\n autoConnect?: boolean;\r\n /** Reconnect on disconnect */\r\n reconnect?: boolean;\r\n /** Maximum reconnect attempts */\r\n maxReconnectAttempts?: number;\r\n /** Reconnect delay in ms */\r\n reconnectDelay?: number;\r\n}\r\n\r\n/** Real-time service */\r\nexport interface RealtimeService {\r\n /** The underlying adapter */\r\n readonly adapter: RealtimeAdapter;\r\n\r\n /** Current connection state */\r\n readonly state: ConnectionState;\r\n\r\n /** Connect to the server */\r\n connect(): Promise<void>;\r\n\r\n /** Disconnect from the server */\r\n disconnect(): void;\r\n\r\n /** Get or create a channel */\r\n channel<T = unknown>(name: string, options?: ChannelOptions): RealtimeChannel<T>;\r\n\r\n /** Subscribe to connection state changes */\r\n onStateChange(callback: (state: ConnectionState) => void): Unsubscribe;\r\n\r\n /** Subscribe to errors */\r\n onError(callback: (error: Error) => void): Unsubscribe;\r\n}\r\n\r\n// ============================================================================\r\n// Factory\r\n// ============================================================================\r\n\r\n/**\r\n * Create a real-time service\r\n * \r\n * @example\r\n * ```typescript\r\n * import { createRealtime } from '@flightdev/realtime';\r\n * import { websocket } from '@flightdev/realtime/websocket';\r\n * \r\n * const realtime = createRealtime(websocket({ port: 3001 }));\r\n * \r\n * await realtime.connect();\r\n * \r\n * const chat = realtime.channel('chat');\r\n * chat.on('message', (msg) => console.log(msg));\r\n * chat.send({ text: 'Hello!' }, 'message');\r\n * ```\r\n */\r\nexport function createRealtime(\r\n adapter: RealtimeAdapter,\r\n options: RealtimeServiceOptions = {}\r\n): RealtimeService {\r\n const {\r\n autoConnect = true,\r\n reconnect = true,\r\n maxReconnectAttempts = 5,\r\n reconnectDelay = 1000,\r\n } = options;\r\n\r\n let reconnectAttempts = 0;\r\n const stateCallbacks = new Set<(state: ConnectionState) => void>();\r\n const errorCallbacks = new Set<(error: Error) => void>();\r\n\r\n // Auto-reconnect on disconnect\r\n if (reconnect) {\r\n adapter.onStateChange((state) => {\r\n if (state === 'disconnected' && reconnectAttempts < maxReconnectAttempts) {\r\n reconnectAttempts++;\r\n setTimeout(() => {\r\n adapter.connect().catch((err) => {\r\n errorCallbacks.forEach(cb => cb(err));\r\n });\r\n }, reconnectDelay * reconnectAttempts);\r\n } else if (state === 'connected') {\r\n reconnectAttempts = 0;\r\n }\r\n });\r\n }\r\n\r\n // Forward state changes\r\n adapter.onStateChange((state) => {\r\n stateCallbacks.forEach(cb => cb(state));\r\n });\r\n\r\n // Forward errors\r\n adapter.onError((error) => {\r\n errorCallbacks.forEach(cb => cb(error));\r\n });\r\n\r\n const service: RealtimeService = {\r\n adapter,\r\n\r\n get state() {\r\n return adapter.state;\r\n },\r\n\r\n async connect() {\r\n await adapter.connect();\r\n },\r\n\r\n disconnect() {\r\n adapter.disconnect();\r\n },\r\n\r\n channel<T = unknown>(name: string, options?: ChannelOptions) {\r\n return adapter.channel<T>(name, options);\r\n },\r\n\r\n onStateChange(callback) {\r\n stateCallbacks.add(callback);\r\n return () => stateCallbacks.delete(callback);\r\n },\r\n\r\n onError(callback) {\r\n errorCallbacks.add(callback);\r\n return () => errorCallbacks.delete(callback);\r\n },\r\n };\r\n\r\n // Auto-connect if enabled\r\n if (autoConnect) {\r\n service.connect().catch((err) => {\r\n errorCallbacks.forEach(cb => cb(err));\r\n });\r\n }\r\n\r\n return service;\r\n}\r\n\r\n// ============================================================================\r\n// Utilities\r\n// ============================================================================\r\n\r\n/**\r\n * Create a message object\r\n */\r\nexport function createMessage<T>(\r\n channel: string,\r\n event: string,\r\n data: T,\r\n senderId?: string\r\n): RealtimeMessage<T> {\r\n return {\r\n id: typeof crypto !== 'undefined' && crypto.randomUUID\r\n ? crypto.randomUUID()\r\n : `${Date.now()}-${Math.random().toString(36).slice(2, 11)}`,\r\n channel,\r\n event,\r\n data,\r\n timestamp: Date.now(),\r\n senderId,\r\n };\r\n}\r\n\r\n/**\r\n * Parse a message from JSON string\r\n */\r\nexport function parseMessage(json: string): RealtimeMessage | null {\r\n try {\r\n return JSON.parse(json) as RealtimeMessage;\r\n } catch {\r\n return null;\r\n }\r\n}\r\n\r\n/**\r\n * Serialize a message to JSON string\r\n */\r\nexport function serializeMessage(message: RealtimeMessage): string {\r\n return JSON.stringify(message);\r\n}\r\n"]}
@@ -0,0 +1,81 @@
1
+ import { RealtimeService, ConnectionState, RealtimeMessage } from '../index.js';
2
+
3
+ /**
4
+ * React Hooks for @flightdev/realtime
5
+ *
6
+ * @example
7
+ * ```tsx
8
+ * import { useRealtime, useChannel } from '@flightdev/realtime/react';
9
+ *
10
+ * function ChatRoom() {
11
+ * const { state } = useRealtime(realtimeService);
12
+ * const { messages, send } = useChannel(realtimeService, 'chat');
13
+ *
14
+ * return (
15
+ * <div>
16
+ * <div>{state === 'connected' ? '🟢' : '🔴'}</div>
17
+ * {messages.map(msg => <div>{msg.data}</div>)}
18
+ * <button onClick={() => send({ text: 'Hello!' })}>Send</button>
19
+ * </div>
20
+ * );
21
+ * }
22
+ * ```
23
+ */
24
+
25
+ interface UseRealtimeReturn {
26
+ /** Current connection state */
27
+ state: ConnectionState;
28
+ /** Connect to the server */
29
+ connect: () => Promise<void>;
30
+ /** Disconnect from the server */
31
+ disconnect: () => void;
32
+ /** Last error */
33
+ error: Error | null;
34
+ }
35
+ /**
36
+ * Hook for real-time connection state
37
+ */
38
+ declare function useRealtime(realtime: RealtimeService): UseRealtimeReturn;
39
+ interface UseChannelOptions<T> {
40
+ /** Maximum number of messages to keep */
41
+ maxMessages?: number;
42
+ /** Event to filter by */
43
+ event?: string;
44
+ /** Callback on new message */
45
+ onMessage?: (message: RealtimeMessage<T>) => void;
46
+ }
47
+ interface UseChannelReturn<T> {
48
+ /** Received messages */
49
+ messages: RealtimeMessage<T>[];
50
+ /** Send a message */
51
+ send: (data: T, event?: string) => void;
52
+ /** Clear messages */
53
+ clear: () => void;
54
+ /** Latest message */
55
+ latest: RealtimeMessage<T> | null;
56
+ }
57
+ /**
58
+ * Hook for channel subscription
59
+ */
60
+ declare function useChannel<T = unknown>(realtime: RealtimeService, channelName: string, options?: UseChannelOptions<T>): UseChannelReturn<T>;
61
+ interface UsePresenceReturn<T> {
62
+ /** Current members */
63
+ members: Array<{
64
+ userId: string;
65
+ info: T;
66
+ joinedAt: number;
67
+ }>;
68
+ /** Track own presence */
69
+ track: (info: T) => void;
70
+ }
71
+ /**
72
+ * Hook for presence tracking (requires presence-enabled channel)
73
+ */
74
+ declare function usePresence<T = unknown>(realtime: RealtimeService, channelName: string): UsePresenceReturn<T>;
75
+ declare const _default: {
76
+ useRealtime: typeof useRealtime;
77
+ useChannel: typeof useChannel;
78
+ usePresence: typeof usePresence;
79
+ };
80
+
81
+ export { type UseChannelOptions, type UseChannelReturn, type UsePresenceReturn, type UseRealtimeReturn, _default as default, useChannel, usePresence, useRealtime };
@@ -0,0 +1,93 @@
1
+ import * as React from 'react';
2
+
3
+ // src/frameworks/react.tsx
4
+ function useRealtime(realtime) {
5
+ const [state, setState] = React.useState(realtime.state);
6
+ const [error, setError] = React.useState(null);
7
+ React.useEffect(() => {
8
+ const unsubState = realtime.onStateChange(setState);
9
+ const unsubError = realtime.onError(setError);
10
+ setState(realtime.state);
11
+ return () => {
12
+ unsubState();
13
+ unsubError();
14
+ };
15
+ }, [realtime]);
16
+ const connect = React.useCallback(async () => {
17
+ try {
18
+ await realtime.connect();
19
+ setError(null);
20
+ } catch (err) {
21
+ setError(err);
22
+ throw err;
23
+ }
24
+ }, [realtime]);
25
+ const disconnect = React.useCallback(() => {
26
+ realtime.disconnect();
27
+ }, [realtime]);
28
+ return { state, connect, disconnect, error };
29
+ }
30
+ function useChannel(realtime, channelName, options = {}) {
31
+ const { maxMessages = 100, event, onMessage } = options;
32
+ const [messages, setMessages] = React.useState([]);
33
+ const channelRef = React.useRef(realtime.channel(channelName));
34
+ React.useEffect(() => {
35
+ const channel = realtime.channel(channelName);
36
+ channelRef.current = channel;
37
+ const handleMessage = (msg) => {
38
+ setMessages((prev) => {
39
+ const next = [...prev, msg];
40
+ if (next.length > maxMessages) {
41
+ return next.slice(-maxMessages);
42
+ }
43
+ return next;
44
+ });
45
+ onMessage?.(msg);
46
+ };
47
+ const unsubscribe = event ? channel.on(event, handleMessage) : channel.subscribe(handleMessage);
48
+ return () => {
49
+ unsubscribe();
50
+ channel.leave();
51
+ };
52
+ }, [realtime, channelName, event, maxMessages, onMessage]);
53
+ const send = React.useCallback((data, eventType = "message") => {
54
+ channelRef.current.send(data, eventType);
55
+ }, []);
56
+ const clear = React.useCallback(() => {
57
+ setMessages([]);
58
+ }, []);
59
+ const latest = messages.length > 0 ? messages[messages.length - 1] ?? null : null;
60
+ return { messages, send, clear, latest };
61
+ }
62
+ function usePresence(realtime, channelName) {
63
+ const [members, setMembers] = React.useState([]);
64
+ const channelRef = React.useRef(realtime.channel(channelName));
65
+ React.useEffect(() => {
66
+ const channel = realtime.channel(channelName);
67
+ channelRef.current = channel;
68
+ const unsubJoin = channel.on("presence:join", (msg) => {
69
+ const { userId, info, joinedAt } = msg.data;
70
+ setMembers((prev) => [...prev, { userId, info, joinedAt }]);
71
+ });
72
+ const unsubLeave = channel.on("presence:leave", (msg) => {
73
+ const { userId } = msg.data;
74
+ setMembers((prev) => prev.filter((m) => m.userId !== userId));
75
+ });
76
+ channel.getPresence?.().then((presence) => {
77
+ setMembers(presence);
78
+ });
79
+ return () => {
80
+ unsubJoin();
81
+ unsubLeave();
82
+ };
83
+ }, [realtime, channelName]);
84
+ const track = React.useCallback((info) => {
85
+ channelRef.current.trackPresence?.(info);
86
+ }, []);
87
+ return { members, track };
88
+ }
89
+ var react_default = { useRealtime, useChannel, usePresence };
90
+
91
+ export { react_default as default, useChannel, usePresence, useRealtime };
92
+ //# sourceMappingURL=react.js.map
93
+ //# sourceMappingURL=react.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/frameworks/react.tsx"],"names":[],"mappings":"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","file":"react.js","sourcesContent":["/**\r\n * React Hooks for @flightdev/realtime\r\n * \r\n * @example\r\n * ```tsx\r\n * import { useRealtime, useChannel } from '@flightdev/realtime/react';\r\n * \r\n * function ChatRoom() {\r\n * const { state } = useRealtime(realtimeService);\r\n * const { messages, send } = useChannel(realtimeService, 'chat');\r\n * \r\n * return (\r\n * <div>\r\n * <div>{state === 'connected' ? '🟢' : '🔴'}</div>\r\n * {messages.map(msg => <div>{msg.data}</div>)}\r\n * <button onClick={() => send({ text: 'Hello!' })}>Send</button>\r\n * </div>\r\n * );\r\n * }\r\n * ```\r\n */\r\n\r\nimport * as React from 'react';\r\nimport type { RealtimeService, RealtimeMessage, ConnectionState } from '../index.js';\r\n\r\n// ============================================================================\r\n// useRealtime Hook\r\n// ============================================================================\r\n\r\nexport interface UseRealtimeReturn {\r\n /** Current connection state */\r\n state: ConnectionState;\r\n /** Connect to the server */\r\n connect: () => Promise<void>;\r\n /** Disconnect from the server */\r\n disconnect: () => void;\r\n /** Last error */\r\n error: Error | null;\r\n}\r\n\r\n/**\r\n * Hook for real-time connection state\r\n */\r\nexport function useRealtime(realtime: RealtimeService): UseRealtimeReturn {\r\n const [state, setState] = React.useState<ConnectionState>(realtime.state);\r\n const [error, setError] = React.useState<Error | null>(null);\r\n\r\n React.useEffect(() => {\r\n const unsubState = realtime.onStateChange(setState);\r\n const unsubError = realtime.onError(setError);\r\n\r\n // Sync initial state\r\n setState(realtime.state);\r\n\r\n return () => {\r\n unsubState();\r\n unsubError();\r\n };\r\n }, [realtime]);\r\n\r\n const connect = React.useCallback(async () => {\r\n try {\r\n await realtime.connect();\r\n setError(null);\r\n } catch (err) {\r\n setError(err as Error);\r\n throw err;\r\n }\r\n }, [realtime]);\r\n\r\n const disconnect = React.useCallback(() => {\r\n realtime.disconnect();\r\n }, [realtime]);\r\n\r\n return { state, connect, disconnect, error };\r\n}\r\n\r\n// ============================================================================\r\n// useChannel Hook\r\n// ============================================================================\r\n\r\nexport interface UseChannelOptions<T> {\r\n /** Maximum number of messages to keep */\r\n maxMessages?: number;\r\n /** Event to filter by */\r\n event?: string;\r\n /** Callback on new message */\r\n onMessage?: (message: RealtimeMessage<T>) => void;\r\n}\r\n\r\nexport interface UseChannelReturn<T> {\r\n /** Received messages */\r\n messages: RealtimeMessage<T>[];\r\n /** Send a message */\r\n send: (data: T, event?: string) => void;\r\n /** Clear messages */\r\n clear: () => void;\r\n /** Latest message */\r\n latest: RealtimeMessage<T> | null;\r\n}\r\n\r\n/**\r\n * Hook for channel subscription\r\n */\r\nexport function useChannel<T = unknown>(\r\n realtime: RealtimeService,\r\n channelName: string,\r\n options: UseChannelOptions<T> = {}\r\n): UseChannelReturn<T> {\r\n const { maxMessages = 100, event, onMessage } = options;\r\n\r\n const [messages, setMessages] = React.useState<RealtimeMessage<T>[]>([]);\r\n const channelRef = React.useRef(realtime.channel<T>(channelName));\r\n\r\n React.useEffect(() => {\r\n const channel = realtime.channel<T>(channelName);\r\n channelRef.current = channel;\r\n\r\n const handleMessage = (msg: RealtimeMessage<T>) => {\r\n setMessages(prev => {\r\n const next = [...prev, msg];\r\n if (next.length > maxMessages) {\r\n return next.slice(-maxMessages);\r\n }\r\n return next;\r\n });\r\n onMessage?.(msg);\r\n };\r\n\r\n const unsubscribe = event\r\n ? channel.on(event, handleMessage)\r\n : channel.subscribe(handleMessage);\r\n\r\n return () => {\r\n unsubscribe();\r\n channel.leave();\r\n };\r\n }, [realtime, channelName, event, maxMessages, onMessage]);\r\n\r\n const send = React.useCallback((data: T, eventType = 'message') => {\r\n channelRef.current.send(data, eventType);\r\n }, []);\r\n\r\n const clear = React.useCallback(() => {\r\n setMessages([]);\r\n }, []);\r\n\r\n const latest: RealtimeMessage<T> | null = messages.length > 0 ? messages[messages.length - 1] ?? null : null;\r\n\r\n return { messages, send, clear, latest };\r\n}\r\n\r\n// ============================================================================\r\n// usePresence Hook\r\n// ============================================================================\r\n\r\nexport interface UsePresenceReturn<T> {\r\n /** Current members */\r\n members: Array<{ userId: string; info: T; joinedAt: number }>;\r\n /** Track own presence */\r\n track: (info: T) => void;\r\n}\r\n\r\n/**\r\n * Hook for presence tracking (requires presence-enabled channel)\r\n */\r\nexport function usePresence<T = unknown>(\r\n realtime: RealtimeService,\r\n channelName: string\r\n): UsePresenceReturn<T> {\r\n const [members, setMembers] = React.useState<Array<{ userId: string; info: T; joinedAt: number }>>([]);\r\n const channelRef = React.useRef(realtime.channel<T>(channelName));\r\n\r\n React.useEffect(() => {\r\n const channel = realtime.channel<T>(channelName);\r\n channelRef.current = channel;\r\n\r\n // Listen for presence events\r\n const unsubJoin = channel.on('presence:join', (msg) => {\r\n const { userId, info, joinedAt } = msg.data as { userId: string; info: T; joinedAt: number };\r\n setMembers(prev => [...prev, { userId, info, joinedAt }]);\r\n });\r\n\r\n const unsubLeave = channel.on('presence:leave', (msg) => {\r\n const { userId } = msg.data as { userId: string };\r\n setMembers(prev => prev.filter(m => m.userId !== userId));\r\n });\r\n\r\n // Get initial presence\r\n channel.getPresence?.().then((presence) => {\r\n setMembers(presence as Array<{ userId: string; info: T; joinedAt: number }>);\r\n });\r\n\r\n return () => {\r\n unsubJoin();\r\n unsubLeave();\r\n };\r\n }, [realtime, channelName]);\r\n\r\n const track = React.useCallback((info: T) => {\r\n channelRef.current.trackPresence?.(info);\r\n }, []);\r\n\r\n return { members, track };\r\n}\r\n\r\nexport default { useRealtime, useChannel, usePresence };\r\n"]}
@@ -0,0 +1,29 @@
1
+ import { Ref } from 'vue';
2
+ import { RealtimeService, ConnectionState, RealtimeMessage } from '../index.js';
3
+
4
+ /**
5
+ * Vue Composables for @flightdev/realtime
6
+ */
7
+
8
+ interface UseRealtimeReturn {
9
+ state: Ref<ConnectionState>;
10
+ connect: () => Promise<void>;
11
+ disconnect: () => void;
12
+ error: Ref<Error | null>;
13
+ }
14
+ declare function useRealtime(realtime: RealtimeService): UseRealtimeReturn;
15
+ interface UseChannelReturn<T> {
16
+ messages: Ref<RealtimeMessage<T>[]>;
17
+ send: (data: T, event?: string) => void;
18
+ clear: () => void;
19
+ }
20
+ declare function useChannel<T = unknown>(realtime: RealtimeService, channelName: string, options?: {
21
+ maxMessages?: number;
22
+ event?: string;
23
+ }): UseChannelReturn<T>;
24
+ declare const _default: {
25
+ useRealtime: typeof useRealtime;
26
+ useChannel: typeof useChannel;
27
+ };
28
+
29
+ export { type UseChannelReturn, type UseRealtimeReturn, _default as default, useChannel, useRealtime };