@fluxbase/sdk 0.0.1-rc.27 → 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 +278 -34
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +213 -23
- package/dist/index.d.ts +213 -23
- package/dist/index.js +278 -34
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -663,10 +663,14 @@ var FluxbaseAuth = class {
|
|
|
663
663
|
|
|
664
664
|
// src/realtime.ts
|
|
665
665
|
var RealtimeChannel = class {
|
|
666
|
-
constructor(url, channelName, token = null) {
|
|
666
|
+
constructor(url, channelName, token = null, config = {}) {
|
|
667
667
|
this.ws = null;
|
|
668
668
|
this.callbacks = /* @__PURE__ */ new Map();
|
|
669
|
+
this.presenceCallbacks = /* @__PURE__ */ new Map();
|
|
670
|
+
this.broadcastCallbacks = /* @__PURE__ */ new Map();
|
|
669
671
|
this.subscriptionConfig = null;
|
|
672
|
+
this._presenceState = {};
|
|
673
|
+
this.myPresenceKey = null;
|
|
670
674
|
this.reconnectAttempts = 0;
|
|
671
675
|
this.maxReconnectAttempts = 10;
|
|
672
676
|
this.reconnectDelay = 1e3;
|
|
@@ -674,6 +678,7 @@ var RealtimeChannel = class {
|
|
|
674
678
|
this.url = url;
|
|
675
679
|
this.channelName = channelName;
|
|
676
680
|
this.token = token;
|
|
681
|
+
this.config = config;
|
|
677
682
|
}
|
|
678
683
|
// Implementation
|
|
679
684
|
on(event, configOrCallback, callback) {
|
|
@@ -686,6 +691,20 @@ var RealtimeChannel = class {
|
|
|
686
691
|
this.callbacks.set(eventType, /* @__PURE__ */ new Set());
|
|
687
692
|
}
|
|
688
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);
|
|
689
708
|
} else {
|
|
690
709
|
const actualEvent = event;
|
|
691
710
|
const actualCallback = configOrCallback;
|
|
@@ -735,7 +754,7 @@ var RealtimeChannel = class {
|
|
|
735
754
|
async unsubscribe(timeout) {
|
|
736
755
|
return new Promise((resolve) => {
|
|
737
756
|
if (this.ws) {
|
|
738
|
-
this.
|
|
757
|
+
this.sendMessage({
|
|
739
758
|
type: "unsubscribe",
|
|
740
759
|
channel: this.channelName
|
|
741
760
|
});
|
|
@@ -758,6 +777,131 @@ var RealtimeChannel = class {
|
|
|
758
777
|
}
|
|
759
778
|
});
|
|
760
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
|
+
}
|
|
761
905
|
/**
|
|
762
906
|
* Internal: Connect to WebSocket
|
|
763
907
|
*/
|
|
@@ -782,7 +926,7 @@ var RealtimeChannel = class {
|
|
|
782
926
|
if (this.subscriptionConfig) {
|
|
783
927
|
subscribeMessage.config = this.subscriptionConfig;
|
|
784
928
|
}
|
|
785
|
-
this.
|
|
929
|
+
this.sendMessage(subscribeMessage);
|
|
786
930
|
this.startHeartbeat();
|
|
787
931
|
};
|
|
788
932
|
this.ws.onmessage = (event) => {
|
|
@@ -815,7 +959,7 @@ var RealtimeChannel = class {
|
|
|
815
959
|
/**
|
|
816
960
|
* Internal: Send a message
|
|
817
961
|
*/
|
|
818
|
-
|
|
962
|
+
sendMessage(message) {
|
|
819
963
|
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
820
964
|
this.ws.send(JSON.stringify(message));
|
|
821
965
|
}
|
|
@@ -826,11 +970,18 @@ var RealtimeChannel = class {
|
|
|
826
970
|
handleMessage(message) {
|
|
827
971
|
switch (message.type) {
|
|
828
972
|
case "heartbeat":
|
|
829
|
-
this.send({ type: "heartbeat" });
|
|
973
|
+
this.ws?.send(JSON.stringify({ type: "heartbeat" }));
|
|
830
974
|
break;
|
|
831
975
|
case "broadcast":
|
|
832
|
-
if (message.
|
|
833
|
-
this.
|
|
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);
|
|
834
985
|
}
|
|
835
986
|
break;
|
|
836
987
|
case "ack":
|
|
@@ -844,7 +995,48 @@ var RealtimeChannel = class {
|
|
|
844
995
|
/**
|
|
845
996
|
* Internal: Handle broadcast message
|
|
846
997
|
*/
|
|
847
|
-
|
|
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) {
|
|
848
1040
|
const supabasePayload = {
|
|
849
1041
|
eventType: payload.type || payload.eventType,
|
|
850
1042
|
schema: payload.schema,
|
|
@@ -868,7 +1060,7 @@ var RealtimeChannel = class {
|
|
|
868
1060
|
*/
|
|
869
1061
|
startHeartbeat() {
|
|
870
1062
|
this.heartbeatInterval = setInterval(() => {
|
|
871
|
-
this.
|
|
1063
|
+
this.sendMessage({ type: "heartbeat" });
|
|
872
1064
|
}, 3e4);
|
|
873
1065
|
}
|
|
874
1066
|
/**
|
|
@@ -905,17 +1097,57 @@ var FluxbaseRealtime = class {
|
|
|
905
1097
|
this.token = token;
|
|
906
1098
|
}
|
|
907
1099
|
/**
|
|
908
|
-
* Create or get a channel
|
|
1100
|
+
* Create or get a channel with optional configuration
|
|
1101
|
+
*
|
|
909
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
|
+
* ```
|
|
910
1113
|
*/
|
|
911
|
-
channel(channelName) {
|
|
912
|
-
|
|
913
|
-
|
|
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);
|
|
914
1119
|
}
|
|
915
|
-
const channel = new RealtimeChannel(
|
|
916
|
-
|
|
1120
|
+
const channel = new RealtimeChannel(
|
|
1121
|
+
this.url,
|
|
1122
|
+
channelName,
|
|
1123
|
+
this.token,
|
|
1124
|
+
config
|
|
1125
|
+
);
|
|
1126
|
+
this.channels.set(key, channel);
|
|
917
1127
|
return channel;
|
|
918
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
|
+
}
|
|
919
1151
|
/**
|
|
920
1152
|
* Remove all channels
|
|
921
1153
|
*/
|
|
@@ -925,8 +1157,9 @@ var FluxbaseRealtime = class {
|
|
|
925
1157
|
}
|
|
926
1158
|
/**
|
|
927
1159
|
* Update auth token for all channels
|
|
1160
|
+
* @param token - The new auth token
|
|
928
1161
|
*/
|
|
929
|
-
|
|
1162
|
+
setAuth(token) {
|
|
930
1163
|
this.token = token;
|
|
931
1164
|
}
|
|
932
1165
|
};
|
|
@@ -4363,7 +4596,7 @@ var FluxbaseClient = class {
|
|
|
4363
4596
|
const originalSetAuthToken = this.fetch.setAuthToken.bind(this.fetch);
|
|
4364
4597
|
this.fetch.setAuthToken = (token) => {
|
|
4365
4598
|
originalSetAuthToken(token);
|
|
4366
|
-
this.realtime.
|
|
4599
|
+
this.realtime.setAuth(token);
|
|
4367
4600
|
};
|
|
4368
4601
|
}
|
|
4369
4602
|
/**
|
|
@@ -4387,37 +4620,48 @@ var FluxbaseClient = class {
|
|
|
4387
4620
|
*/
|
|
4388
4621
|
setAuthToken(token) {
|
|
4389
4622
|
this.fetch.setAuthToken(token);
|
|
4390
|
-
this.realtime.
|
|
4623
|
+
this.realtime.setAuth(token);
|
|
4391
4624
|
}
|
|
4392
4625
|
/**
|
|
4393
|
-
* Create or get a realtime channel (Supabase-compatible
|
|
4394
|
-
*
|
|
4395
|
-
* This is a convenience method that delegates to client.realtime.channel().
|
|
4396
|
-
* Both patterns work identically:
|
|
4397
|
-
* - client.channel('room-1') - Supabase-style
|
|
4398
|
-
* - client.realtime.channel('room-1') - Fluxbase-style
|
|
4626
|
+
* Create or get a realtime channel (Supabase-compatible)
|
|
4399
4627
|
*
|
|
4400
4628
|
* @param name - Channel name
|
|
4629
|
+
* @param config - Optional channel configuration
|
|
4401
4630
|
* @returns RealtimeChannel instance
|
|
4402
4631
|
*
|
|
4403
4632
|
* @example
|
|
4404
4633
|
* ```typescript
|
|
4405
|
-
*
|
|
4406
|
-
*
|
|
4407
|
-
*
|
|
4408
|
-
*
|
|
4409
|
-
*
|
|
4410
|
-
*
|
|
4411
|
-
* }, (payload) => {
|
|
4412
|
-
* 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)
|
|
4413
4640
|
* })
|
|
4414
4641
|
* .subscribe()
|
|
4415
4642
|
* ```
|
|
4416
4643
|
*
|
|
4417
4644
|
* @category Realtime
|
|
4418
4645
|
*/
|
|
4419
|
-
channel(name) {
|
|
4420
|
-
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);
|
|
4421
4665
|
}
|
|
4422
4666
|
/**
|
|
4423
4667
|
* Get the internal HTTP client
|