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