@fluxbase/sdk 0.0.1-rc.26 → 0.0.1-rc.28

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 CHANGED
@@ -165,16 +165,19 @@ var FluxbaseAuth = class {
165
165
  }
166
166
  }
167
167
  /**
168
- * Get the current session
168
+ * Get the current session (Supabase-compatible)
169
+ * Returns the session from the client-side cache without making a network request
169
170
  */
170
- getSession() {
171
- return this.session;
171
+ async getSession() {
172
+ return { data: { session: this.session }, error: null };
172
173
  }
173
174
  /**
174
- * Get the current user
175
+ * Get the current user (Supabase-compatible)
176
+ * Returns the user from the client-side session without making a network request
177
+ * For server-side validation, use getCurrentUser() instead
175
178
  */
176
- getUser() {
177
- return this.session?.user ?? null;
179
+ async getUser() {
180
+ return { data: { user: this.session?.user ?? null }, error: null };
178
181
  }
179
182
  /**
180
183
  * Get the current access token
@@ -183,9 +186,9 @@ var FluxbaseAuth = class {
183
186
  return this.session?.access_token ?? null;
184
187
  }
185
188
  /**
186
- * Listen to auth state changes
189
+ * Listen to auth state changes (Supabase-compatible)
187
190
  * @param callback - Function called when auth state changes
188
- * @returns Subscription object with unsubscribe method
191
+ * @returns Object containing subscription data
189
192
  *
190
193
  * @example
191
194
  * ```typescript
@@ -199,11 +202,12 @@ var FluxbaseAuth = class {
199
202
  */
200
203
  onAuthStateChange(callback) {
201
204
  this.stateChangeListeners.add(callback);
202
- return {
205
+ const subscription = {
203
206
  unsubscribe: () => {
204
207
  this.stateChangeListeners.delete(callback);
205
208
  }
206
209
  };
210
+ return { data: { subscription } };
207
211
  }
208
212
  /**
209
213
  * Sign in with email and password
@@ -220,7 +224,7 @@ var FluxbaseAuth = class {
220
224
  ...authResponse,
221
225
  expires_at: Date.now() + authResponse.expires_in * 1e3
222
226
  };
223
- this.setSession(session);
227
+ this.setSessionInternal(session);
224
228
  return session;
225
229
  });
226
230
  }
@@ -245,7 +249,7 @@ var FluxbaseAuth = class {
245
249
  ...response,
246
250
  expires_at: Date.now() + response.expires_in * 1e3
247
251
  };
248
- this.setSession(session);
252
+ this.setSessionInternal(session);
249
253
  return { user: session.user, session };
250
254
  });
251
255
  }
@@ -262,9 +266,10 @@ var FluxbaseAuth = class {
262
266
  });
263
267
  }
264
268
  /**
265
- * Refresh the access token
269
+ * Refresh the session (Supabase-compatible)
270
+ * Returns a new session with refreshed tokens
266
271
  */
267
- async refreshToken() {
272
+ async refreshSession() {
268
273
  return wrapAsync(async () => {
269
274
  if (!this.session?.refresh_token) {
270
275
  throw new Error("No refresh token available");
@@ -279,8 +284,8 @@ var FluxbaseAuth = class {
279
284
  ...response,
280
285
  expires_at: Date.now() + response.expires_in * 1e3
281
286
  };
282
- this.setSession(session, "TOKEN_REFRESHED");
283
- return { session };
287
+ this.setSessionInternal(session, "TOKEN_REFRESHED");
288
+ return { session, user: session.user };
284
289
  });
285
290
  }
286
291
  /**
@@ -313,10 +318,28 @@ var FluxbaseAuth = class {
313
318
  });
314
319
  }
315
320
  /**
316
- * Set the auth token manually
321
+ * Set the session manually (Supabase-compatible)
322
+ * Useful for restoring a session from storage or SSR scenarios
323
+ * @param session - Object containing access_token and refresh_token
324
+ * @returns Promise with session data
317
325
  */
318
- setToken(token) {
319
- this.fetch.setAuthToken(token);
326
+ async setSession(session) {
327
+ return wrapAsync(async () => {
328
+ const authSession = {
329
+ access_token: session.access_token,
330
+ refresh_token: session.refresh_token,
331
+ user: null,
332
+ // Will be populated by getCurrentUser
333
+ expires_in: 3600,
334
+ // Default, will be updated on refresh
335
+ expires_at: Date.now() + 3600 * 1e3
336
+ };
337
+ this.fetch.setAuthToken(session.access_token);
338
+ const user = await this.fetch.get("/api/v1/auth/user");
339
+ authSession.user = user;
340
+ this.setSessionInternal(authSession, "SIGNED_IN");
341
+ return { user, session: authSession };
342
+ });
320
343
  }
321
344
  /**
322
345
  * Setup 2FA for the current user
@@ -389,7 +412,7 @@ var FluxbaseAuth = class {
389
412
  ...response,
390
413
  expires_at: Date.now() + response.expires_in * 1e3
391
414
  };
392
- this.setSession(session, "MFA_CHALLENGE_VERIFIED");
415
+ this.setSessionInternal(session, "MFA_CHALLENGE_VERIFIED");
393
416
  return { user: session.user, session };
394
417
  });
395
418
  }
@@ -478,7 +501,7 @@ var FluxbaseAuth = class {
478
501
  ...response,
479
502
  expires_at: Date.now() + response.expires_in * 1e3
480
503
  };
481
- this.setSession(session);
504
+ this.setSessionInternal(session);
482
505
  return { user: session.user, session };
483
506
  });
484
507
  }
@@ -495,7 +518,7 @@ var FluxbaseAuth = class {
495
518
  ...response,
496
519
  expires_at: Date.now() + response.expires_in * 1e3
497
520
  };
498
- this.setSession(session);
521
+ this.setSessionInternal(session);
499
522
  return { user: session.user, session };
500
523
  });
501
524
  }
@@ -544,7 +567,7 @@ var FluxbaseAuth = class {
544
567
  ...response,
545
568
  expires_at: Date.now() + response.expires_in * 1e3
546
569
  };
547
- this.setSession(session);
570
+ this.setSessionInternal(session);
548
571
  return { user: session.user, session };
549
572
  });
550
573
  }
@@ -574,7 +597,7 @@ var FluxbaseAuth = class {
574
597
  /**
575
598
  * Internal: Set the session and persist it
576
599
  */
577
- setSession(session, event = "SIGNED_IN") {
600
+ setSessionInternal(session, event = "SIGNED_IN") {
578
601
  this.session = session;
579
602
  this.fetch.setAuthToken(session.access_token);
580
603
  this.saveSession();
@@ -618,7 +641,7 @@ var FluxbaseAuth = class {
618
641
  const delay = refreshAt - Date.now();
619
642
  if (delay > 0) {
620
643
  this.refreshTimer = setTimeout(async () => {
621
- const result = await this.refreshToken();
644
+ const result = await this.refreshSession();
622
645
  if (result.error) {
623
646
  console.error("Failed to refresh token:", result.error);
624
647
  this.clearSession();
@@ -642,10 +665,14 @@ var FluxbaseAuth = class {
642
665
 
643
666
  // src/realtime.ts
644
667
  var RealtimeChannel = class {
645
- constructor(url, channelName, token = null) {
668
+ constructor(url, channelName, token = null, config = {}) {
646
669
  this.ws = null;
647
670
  this.callbacks = /* @__PURE__ */ new Map();
671
+ this.presenceCallbacks = /* @__PURE__ */ new Map();
672
+ this.broadcastCallbacks = /* @__PURE__ */ new Map();
648
673
  this.subscriptionConfig = null;
674
+ this._presenceState = {};
675
+ this.myPresenceKey = null;
649
676
  this.reconnectAttempts = 0;
650
677
  this.maxReconnectAttempts = 10;
651
678
  this.reconnectDelay = 1e3;
@@ -653,6 +680,7 @@ var RealtimeChannel = class {
653
680
  this.url = url;
654
681
  this.channelName = channelName;
655
682
  this.token = token;
683
+ this.config = config;
656
684
  }
657
685
  // Implementation
658
686
  on(event, configOrCallback, callback) {
@@ -665,6 +693,20 @@ var RealtimeChannel = class {
665
693
  this.callbacks.set(eventType, /* @__PURE__ */ new Set());
666
694
  }
667
695
  this.callbacks.get(eventType).add(actualCallback);
696
+ } else if (event === "broadcast" && typeof configOrCallback !== "function") {
697
+ const config = configOrCallback;
698
+ const actualCallback = callback;
699
+ if (!this.broadcastCallbacks.has(config.event)) {
700
+ this.broadcastCallbacks.set(config.event, /* @__PURE__ */ new Set());
701
+ }
702
+ this.broadcastCallbacks.get(config.event).add(actualCallback);
703
+ } else if (event === "presence" && typeof configOrCallback !== "function") {
704
+ const config = configOrCallback;
705
+ const actualCallback = callback;
706
+ if (!this.presenceCallbacks.has(config.event)) {
707
+ this.presenceCallbacks.set(config.event, /* @__PURE__ */ new Set());
708
+ }
709
+ this.presenceCallbacks.get(config.event).add(actualCallback);
668
710
  } else {
669
711
  const actualEvent = event;
670
712
  const actualCallback = configOrCallback;
@@ -714,7 +756,7 @@ var RealtimeChannel = class {
714
756
  async unsubscribe(timeout) {
715
757
  return new Promise((resolve) => {
716
758
  if (this.ws) {
717
- this.send({
759
+ this.sendMessage({
718
760
  type: "unsubscribe",
719
761
  channel: this.channelName
720
762
  });
@@ -737,6 +779,131 @@ var RealtimeChannel = class {
737
779
  }
738
780
  });
739
781
  }
782
+ /**
783
+ * Send a broadcast message to all subscribers on this channel
784
+ *
785
+ * @param message - Broadcast message with type, event, and payload
786
+ * @returns Promise resolving to status
787
+ *
788
+ * @example
789
+ * ```typescript
790
+ * await channel.send({
791
+ * type: 'broadcast',
792
+ * event: 'cursor-pos',
793
+ * payload: { x: 100, y: 200 }
794
+ * })
795
+ * ```
796
+ */
797
+ async send(message) {
798
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
799
+ return "error";
800
+ }
801
+ try {
802
+ this.ws.send(
803
+ JSON.stringify({
804
+ type: "broadcast",
805
+ channel: this.channelName,
806
+ event: message.event,
807
+ payload: message.payload
808
+ })
809
+ );
810
+ if (this.config.broadcast?.ack) {
811
+ return "ok";
812
+ }
813
+ return "ok";
814
+ } catch (error) {
815
+ console.error("[Fluxbase Realtime] Failed to send broadcast:", error);
816
+ return "error";
817
+ }
818
+ }
819
+ /**
820
+ * Track user presence on this channel
821
+ *
822
+ * @param state - Presence state to track
823
+ * @returns Promise resolving to status
824
+ *
825
+ * @example
826
+ * ```typescript
827
+ * await channel.track({
828
+ * user_id: 123,
829
+ * status: 'online'
830
+ * })
831
+ * ```
832
+ */
833
+ async track(state) {
834
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
835
+ return "error";
836
+ }
837
+ try {
838
+ if (!this.myPresenceKey) {
839
+ this.myPresenceKey = this.config.presence?.key || `presence-${Math.random().toString(36).substr(2, 9)}`;
840
+ }
841
+ this.ws.send(
842
+ JSON.stringify({
843
+ type: "presence",
844
+ channel: this.channelName,
845
+ event: "track",
846
+ payload: {
847
+ key: this.myPresenceKey,
848
+ state
849
+ }
850
+ })
851
+ );
852
+ return "ok";
853
+ } catch (error) {
854
+ console.error("[Fluxbase Realtime] Failed to track presence:", error);
855
+ return "error";
856
+ }
857
+ }
858
+ /**
859
+ * Stop tracking presence on this channel
860
+ *
861
+ * @returns Promise resolving to status
862
+ *
863
+ * @example
864
+ * ```typescript
865
+ * await channel.untrack()
866
+ * ```
867
+ */
868
+ async untrack() {
869
+ if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
870
+ return "error";
871
+ }
872
+ if (!this.myPresenceKey) {
873
+ return "ok";
874
+ }
875
+ try {
876
+ this.ws.send(
877
+ JSON.stringify({
878
+ type: "presence",
879
+ channel: this.channelName,
880
+ event: "untrack",
881
+ payload: {
882
+ key: this.myPresenceKey
883
+ }
884
+ })
885
+ );
886
+ this.myPresenceKey = null;
887
+ return "ok";
888
+ } catch (error) {
889
+ console.error("[Fluxbase Realtime] Failed to untrack presence:", error);
890
+ return "error";
891
+ }
892
+ }
893
+ /**
894
+ * Get current presence state for all users on this channel
895
+ *
896
+ * @returns Current presence state
897
+ *
898
+ * @example
899
+ * ```typescript
900
+ * const state = channel.presenceState()
901
+ * console.log('Online users:', Object.keys(state).length)
902
+ * ```
903
+ */
904
+ presenceState() {
905
+ return { ...this._presenceState };
906
+ }
740
907
  /**
741
908
  * Internal: Connect to WebSocket
742
909
  */
@@ -761,7 +928,7 @@ var RealtimeChannel = class {
761
928
  if (this.subscriptionConfig) {
762
929
  subscribeMessage.config = this.subscriptionConfig;
763
930
  }
764
- this.send(subscribeMessage);
931
+ this.sendMessage(subscribeMessage);
765
932
  this.startHeartbeat();
766
933
  };
767
934
  this.ws.onmessage = (event) => {
@@ -794,7 +961,7 @@ var RealtimeChannel = class {
794
961
  /**
795
962
  * Internal: Send a message
796
963
  */
797
- send(message) {
964
+ sendMessage(message) {
798
965
  if (this.ws && this.ws.readyState === WebSocket.OPEN) {
799
966
  this.ws.send(JSON.stringify(message));
800
967
  }
@@ -805,11 +972,18 @@ var RealtimeChannel = class {
805
972
  handleMessage(message) {
806
973
  switch (message.type) {
807
974
  case "heartbeat":
808
- this.send({ type: "heartbeat" });
975
+ this.ws?.send(JSON.stringify({ type: "heartbeat" }));
809
976
  break;
810
977
  case "broadcast":
811
- if (message.payload) {
812
- this.handleBroadcast(message.payload);
978
+ if (message.broadcast) {
979
+ this.handleBroadcastMessage(message.broadcast);
980
+ } else if (message.payload) {
981
+ this.handlePostgresChanges(message.payload);
982
+ }
983
+ break;
984
+ case "presence":
985
+ if (message.presence) {
986
+ this.handlePresenceMessage(message.presence);
813
987
  }
814
988
  break;
815
989
  case "ack":
@@ -823,7 +997,48 @@ var RealtimeChannel = class {
823
997
  /**
824
998
  * Internal: Handle broadcast message
825
999
  */
826
- handleBroadcast(payload) {
1000
+ handleBroadcastMessage(message) {
1001
+ const event = message.event;
1002
+ const payload = {
1003
+ event,
1004
+ payload: message.payload
1005
+ };
1006
+ if (!this.config.broadcast?.self && message.self) {
1007
+ return;
1008
+ }
1009
+ const callbacks = this.broadcastCallbacks.get(event);
1010
+ if (callbacks) {
1011
+ callbacks.forEach((callback) => callback(payload));
1012
+ }
1013
+ const wildcardCallbacks = this.broadcastCallbacks.get("*");
1014
+ if (wildcardCallbacks) {
1015
+ wildcardCallbacks.forEach((callback) => callback(payload));
1016
+ }
1017
+ }
1018
+ /**
1019
+ * Internal: Handle presence message
1020
+ */
1021
+ handlePresenceMessage(message) {
1022
+ const event = message.event;
1023
+ const payload = {
1024
+ event,
1025
+ key: message.key,
1026
+ newPresences: message.newPresences,
1027
+ leftPresences: message.leftPresences,
1028
+ currentPresences: message.currentPresences || this._presenceState
1029
+ };
1030
+ if (message.currentPresences) {
1031
+ this._presenceState = message.currentPresences;
1032
+ }
1033
+ const callbacks = this.presenceCallbacks.get(event);
1034
+ if (callbacks) {
1035
+ callbacks.forEach((callback) => callback(payload));
1036
+ }
1037
+ }
1038
+ /**
1039
+ * Internal: Handle postgres_changes message
1040
+ */
1041
+ handlePostgresChanges(payload) {
827
1042
  const supabasePayload = {
828
1043
  eventType: payload.type || payload.eventType,
829
1044
  schema: payload.schema,
@@ -847,7 +1062,7 @@ var RealtimeChannel = class {
847
1062
  */
848
1063
  startHeartbeat() {
849
1064
  this.heartbeatInterval = setInterval(() => {
850
- this.send({ type: "heartbeat" });
1065
+ this.sendMessage({ type: "heartbeat" });
851
1066
  }, 3e4);
852
1067
  }
853
1068
  /**
@@ -884,17 +1099,57 @@ var FluxbaseRealtime = class {
884
1099
  this.token = token;
885
1100
  }
886
1101
  /**
887
- * Create or get a channel
1102
+ * Create or get a channel with optional configuration
1103
+ *
888
1104
  * @param channelName - Channel name (e.g., 'table:public.products')
1105
+ * @param config - Optional channel configuration
1106
+ * @returns RealtimeChannel instance
1107
+ *
1108
+ * @example
1109
+ * ```typescript
1110
+ * const channel = realtime.channel('room-1', {
1111
+ * broadcast: { self: true, ack: true },
1112
+ * presence: { key: 'user-123' }
1113
+ * })
1114
+ * ```
889
1115
  */
890
- channel(channelName) {
891
- if (this.channels.has(channelName)) {
892
- return this.channels.get(channelName);
1116
+ channel(channelName, config) {
1117
+ const configKey = config ? JSON.stringify(config) : "";
1118
+ const key = `${channelName}:${configKey}`;
1119
+ if (this.channels.has(key)) {
1120
+ return this.channels.get(key);
893
1121
  }
894
- const channel = new RealtimeChannel(this.url, channelName, this.token);
895
- this.channels.set(channelName, channel);
1122
+ const channel = new RealtimeChannel(
1123
+ this.url,
1124
+ channelName,
1125
+ this.token,
1126
+ config
1127
+ );
1128
+ this.channels.set(key, channel);
896
1129
  return channel;
897
1130
  }
1131
+ /**
1132
+ * Remove a specific channel
1133
+ *
1134
+ * @param channel - The channel to remove
1135
+ * @returns Promise resolving to status
1136
+ *
1137
+ * @example
1138
+ * ```typescript
1139
+ * const channel = realtime.channel('room-1')
1140
+ * await realtime.removeChannel(channel)
1141
+ * ```
1142
+ */
1143
+ async removeChannel(channel) {
1144
+ await channel.unsubscribe();
1145
+ for (const [key, ch] of this.channels.entries()) {
1146
+ if (ch === channel) {
1147
+ this.channels.delete(key);
1148
+ return "ok";
1149
+ }
1150
+ }
1151
+ return "error";
1152
+ }
898
1153
  /**
899
1154
  * Remove all channels
900
1155
  */
@@ -904,8 +1159,9 @@ var FluxbaseRealtime = class {
904
1159
  }
905
1160
  /**
906
1161
  * Update auth token for all channels
1162
+ * @param token - The new auth token
907
1163
  */
908
- setToken(token) {
1164
+ setAuth(token) {
909
1165
  this.token = token;
910
1166
  }
911
1167
  };
@@ -939,15 +1195,20 @@ var StorageBucket = class {
939
1195
  if (options?.upsert !== void 0) {
940
1196
  formData.append("upsert", String(options.upsert));
941
1197
  }
942
- const response = await this.fetch.request(
943
- `/api/v1/storage/${this.bucketName}/${path}`,
944
- {
945
- method: "POST",
946
- body: formData,
947
- headers: {}
948
- // Let browser set Content-Type for FormData
949
- }
950
- );
1198
+ let response;
1199
+ if (options?.onUploadProgress) {
1200
+ response = await this.uploadWithProgress(path, formData, options.onUploadProgress);
1201
+ } else {
1202
+ response = await this.fetch.request(
1203
+ `/api/v1/storage/${this.bucketName}/${path}`,
1204
+ {
1205
+ method: "POST",
1206
+ body: formData,
1207
+ headers: {}
1208
+ // Let browser set Content-Type for FormData
1209
+ }
1210
+ );
1211
+ }
951
1212
  return {
952
1213
  data: {
953
1214
  id: response.id || response.key || path,
@@ -960,6 +1221,57 @@ var StorageBucket = class {
960
1221
  return { data: null, error };
961
1222
  }
962
1223
  }
1224
+ /**
1225
+ * Upload with progress tracking using XMLHttpRequest
1226
+ * @private
1227
+ */
1228
+ uploadWithProgress(path, formData, onProgress) {
1229
+ return new Promise((resolve, reject) => {
1230
+ const xhr = new XMLHttpRequest();
1231
+ const url = `${this.fetch["baseUrl"]}/api/v1/storage/${this.bucketName}/${path}`;
1232
+ xhr.upload.addEventListener("progress", (event) => {
1233
+ if (event.lengthComputable) {
1234
+ const percentage = Math.round(event.loaded / event.total * 100);
1235
+ onProgress({
1236
+ loaded: event.loaded,
1237
+ total: event.total,
1238
+ percentage
1239
+ });
1240
+ }
1241
+ });
1242
+ xhr.addEventListener("load", () => {
1243
+ if (xhr.status >= 200 && xhr.status < 300) {
1244
+ try {
1245
+ const response = JSON.parse(xhr.responseText);
1246
+ resolve(response);
1247
+ } catch (e) {
1248
+ resolve(xhr.responseText);
1249
+ }
1250
+ } else {
1251
+ try {
1252
+ const errorData = JSON.parse(xhr.responseText);
1253
+ reject(new Error(errorData.error || xhr.statusText));
1254
+ } catch (e) {
1255
+ reject(new Error(xhr.statusText));
1256
+ }
1257
+ }
1258
+ });
1259
+ xhr.addEventListener("error", () => {
1260
+ reject(new Error("Upload failed"));
1261
+ });
1262
+ xhr.addEventListener("abort", () => {
1263
+ reject(new Error("Upload aborted"));
1264
+ });
1265
+ xhr.open("POST", url);
1266
+ const headers = this.fetch["defaultHeaders"];
1267
+ for (const [key, value] of Object.entries(headers)) {
1268
+ if (key.toLowerCase() !== "content-type") {
1269
+ xhr.setRequestHeader(key, value);
1270
+ }
1271
+ }
1272
+ xhr.send(formData);
1273
+ });
1274
+ }
963
1275
  /**
964
1276
  * Download a file from the bucket
965
1277
  * @param path - The path/key of the file
@@ -4286,7 +4598,7 @@ var FluxbaseClient = class {
4286
4598
  const originalSetAuthToken = this.fetch.setAuthToken.bind(this.fetch);
4287
4599
  this.fetch.setAuthToken = (token) => {
4288
4600
  originalSetAuthToken(token);
4289
- this.realtime.setToken(token);
4601
+ this.realtime.setAuth(token);
4290
4602
  };
4291
4603
  }
4292
4604
  /**
@@ -4310,37 +4622,48 @@ var FluxbaseClient = class {
4310
4622
  */
4311
4623
  setAuthToken(token) {
4312
4624
  this.fetch.setAuthToken(token);
4313
- this.realtime.setToken(token);
4625
+ this.realtime.setAuth(token);
4314
4626
  }
4315
4627
  /**
4316
- * Create or get a realtime channel (Supabase-compatible alias)
4317
- *
4318
- * This is a convenience method that delegates to client.realtime.channel().
4319
- * Both patterns work identically:
4320
- * - client.channel('room-1') - Supabase-style
4321
- * - client.realtime.channel('room-1') - Fluxbase-style
4628
+ * Create or get a realtime channel (Supabase-compatible)
4322
4629
  *
4323
4630
  * @param name - Channel name
4631
+ * @param config - Optional channel configuration
4324
4632
  * @returns RealtimeChannel instance
4325
4633
  *
4326
4634
  * @example
4327
4635
  * ```typescript
4328
- * // Supabase-compatible usage
4329
- * const channel = client.channel('room-1')
4330
- * .on('postgres_changes', {
4331
- * event: '*',
4332
- * schema: 'public',
4333
- * table: 'messages'
4334
- * }, (payload) => {
4335
- * console.log('Change:', payload)
4636
+ * const channel = client.channel('room-1', {
4637
+ * broadcast: { self: true },
4638
+ * presence: { key: 'user-123' }
4639
+ * })
4640
+ * .on('broadcast', { event: 'message' }, (payload) => {
4641
+ * console.log('Message:', payload)
4336
4642
  * })
4337
4643
  * .subscribe()
4338
4644
  * ```
4339
4645
  *
4340
4646
  * @category Realtime
4341
4647
  */
4342
- channel(name) {
4343
- return this.realtime.channel(name);
4648
+ channel(name, config) {
4649
+ return this.realtime.channel(name, config);
4650
+ }
4651
+ /**
4652
+ * Remove a realtime channel (Supabase-compatible)
4653
+ *
4654
+ * @param channel - The channel to remove
4655
+ * @returns Promise resolving to status
4656
+ *
4657
+ * @example
4658
+ * ```typescript
4659
+ * const channel = client.channel('room-1')
4660
+ * await client.removeChannel(channel)
4661
+ * ```
4662
+ *
4663
+ * @category Realtime
4664
+ */
4665
+ removeChannel(channel) {
4666
+ return this.realtime.removeChannel(channel);
4344
4667
  }
4345
4668
  /**
4346
4669
  * Get the internal HTTP client