@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.cjs
CHANGED
|
@@ -665,10 +665,14 @@ var FluxbaseAuth = class {
|
|
|
665
665
|
|
|
666
666
|
// src/realtime.ts
|
|
667
667
|
var RealtimeChannel = class {
|
|
668
|
-
constructor(url, channelName, token = null) {
|
|
668
|
+
constructor(url, channelName, token = null, config = {}) {
|
|
669
669
|
this.ws = null;
|
|
670
670
|
this.callbacks = /* @__PURE__ */ new Map();
|
|
671
|
+
this.presenceCallbacks = /* @__PURE__ */ new Map();
|
|
672
|
+
this.broadcastCallbacks = /* @__PURE__ */ new Map();
|
|
671
673
|
this.subscriptionConfig = null;
|
|
674
|
+
this._presenceState = {};
|
|
675
|
+
this.myPresenceKey = null;
|
|
672
676
|
this.reconnectAttempts = 0;
|
|
673
677
|
this.maxReconnectAttempts = 10;
|
|
674
678
|
this.reconnectDelay = 1e3;
|
|
@@ -676,6 +680,7 @@ var RealtimeChannel = class {
|
|
|
676
680
|
this.url = url;
|
|
677
681
|
this.channelName = channelName;
|
|
678
682
|
this.token = token;
|
|
683
|
+
this.config = config;
|
|
679
684
|
}
|
|
680
685
|
// Implementation
|
|
681
686
|
on(event, configOrCallback, callback) {
|
|
@@ -688,6 +693,20 @@ var RealtimeChannel = class {
|
|
|
688
693
|
this.callbacks.set(eventType, /* @__PURE__ */ new Set());
|
|
689
694
|
}
|
|
690
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);
|
|
691
710
|
} else {
|
|
692
711
|
const actualEvent = event;
|
|
693
712
|
const actualCallback = configOrCallback;
|
|
@@ -737,7 +756,7 @@ var RealtimeChannel = class {
|
|
|
737
756
|
async unsubscribe(timeout) {
|
|
738
757
|
return new Promise((resolve) => {
|
|
739
758
|
if (this.ws) {
|
|
740
|
-
this.
|
|
759
|
+
this.sendMessage({
|
|
741
760
|
type: "unsubscribe",
|
|
742
761
|
channel: this.channelName
|
|
743
762
|
});
|
|
@@ -760,6 +779,131 @@ var RealtimeChannel = class {
|
|
|
760
779
|
}
|
|
761
780
|
});
|
|
762
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
|
+
}
|
|
763
907
|
/**
|
|
764
908
|
* Internal: Connect to WebSocket
|
|
765
909
|
*/
|
|
@@ -784,7 +928,7 @@ var RealtimeChannel = class {
|
|
|
784
928
|
if (this.subscriptionConfig) {
|
|
785
929
|
subscribeMessage.config = this.subscriptionConfig;
|
|
786
930
|
}
|
|
787
|
-
this.
|
|
931
|
+
this.sendMessage(subscribeMessage);
|
|
788
932
|
this.startHeartbeat();
|
|
789
933
|
};
|
|
790
934
|
this.ws.onmessage = (event) => {
|
|
@@ -817,7 +961,7 @@ var RealtimeChannel = class {
|
|
|
817
961
|
/**
|
|
818
962
|
* Internal: Send a message
|
|
819
963
|
*/
|
|
820
|
-
|
|
964
|
+
sendMessage(message) {
|
|
821
965
|
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
822
966
|
this.ws.send(JSON.stringify(message));
|
|
823
967
|
}
|
|
@@ -828,11 +972,18 @@ var RealtimeChannel = class {
|
|
|
828
972
|
handleMessage(message) {
|
|
829
973
|
switch (message.type) {
|
|
830
974
|
case "heartbeat":
|
|
831
|
-
this.send({ type: "heartbeat" });
|
|
975
|
+
this.ws?.send(JSON.stringify({ type: "heartbeat" }));
|
|
832
976
|
break;
|
|
833
977
|
case "broadcast":
|
|
834
|
-
if (message.
|
|
835
|
-
this.
|
|
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);
|
|
836
987
|
}
|
|
837
988
|
break;
|
|
838
989
|
case "ack":
|
|
@@ -846,7 +997,48 @@ var RealtimeChannel = class {
|
|
|
846
997
|
/**
|
|
847
998
|
* Internal: Handle broadcast message
|
|
848
999
|
*/
|
|
849
|
-
|
|
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) {
|
|
850
1042
|
const supabasePayload = {
|
|
851
1043
|
eventType: payload.type || payload.eventType,
|
|
852
1044
|
schema: payload.schema,
|
|
@@ -870,7 +1062,7 @@ var RealtimeChannel = class {
|
|
|
870
1062
|
*/
|
|
871
1063
|
startHeartbeat() {
|
|
872
1064
|
this.heartbeatInterval = setInterval(() => {
|
|
873
|
-
this.
|
|
1065
|
+
this.sendMessage({ type: "heartbeat" });
|
|
874
1066
|
}, 3e4);
|
|
875
1067
|
}
|
|
876
1068
|
/**
|
|
@@ -907,17 +1099,57 @@ var FluxbaseRealtime = class {
|
|
|
907
1099
|
this.token = token;
|
|
908
1100
|
}
|
|
909
1101
|
/**
|
|
910
|
-
* Create or get a channel
|
|
1102
|
+
* Create or get a channel with optional configuration
|
|
1103
|
+
*
|
|
911
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
|
+
* ```
|
|
912
1115
|
*/
|
|
913
|
-
channel(channelName) {
|
|
914
|
-
|
|
915
|
-
|
|
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);
|
|
916
1121
|
}
|
|
917
|
-
const channel = new RealtimeChannel(
|
|
918
|
-
|
|
1122
|
+
const channel = new RealtimeChannel(
|
|
1123
|
+
this.url,
|
|
1124
|
+
channelName,
|
|
1125
|
+
this.token,
|
|
1126
|
+
config
|
|
1127
|
+
);
|
|
1128
|
+
this.channels.set(key, channel);
|
|
919
1129
|
return channel;
|
|
920
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
|
+
}
|
|
921
1153
|
/**
|
|
922
1154
|
* Remove all channels
|
|
923
1155
|
*/
|
|
@@ -927,8 +1159,9 @@ var FluxbaseRealtime = class {
|
|
|
927
1159
|
}
|
|
928
1160
|
/**
|
|
929
1161
|
* Update auth token for all channels
|
|
1162
|
+
* @param token - The new auth token
|
|
930
1163
|
*/
|
|
931
|
-
|
|
1164
|
+
setAuth(token) {
|
|
932
1165
|
this.token = token;
|
|
933
1166
|
}
|
|
934
1167
|
};
|
|
@@ -4365,7 +4598,7 @@ var FluxbaseClient = class {
|
|
|
4365
4598
|
const originalSetAuthToken = this.fetch.setAuthToken.bind(this.fetch);
|
|
4366
4599
|
this.fetch.setAuthToken = (token) => {
|
|
4367
4600
|
originalSetAuthToken(token);
|
|
4368
|
-
this.realtime.
|
|
4601
|
+
this.realtime.setAuth(token);
|
|
4369
4602
|
};
|
|
4370
4603
|
}
|
|
4371
4604
|
/**
|
|
@@ -4389,37 +4622,48 @@ var FluxbaseClient = class {
|
|
|
4389
4622
|
*/
|
|
4390
4623
|
setAuthToken(token) {
|
|
4391
4624
|
this.fetch.setAuthToken(token);
|
|
4392
|
-
this.realtime.
|
|
4625
|
+
this.realtime.setAuth(token);
|
|
4393
4626
|
}
|
|
4394
4627
|
/**
|
|
4395
|
-
* Create or get a realtime channel (Supabase-compatible
|
|
4396
|
-
*
|
|
4397
|
-
* This is a convenience method that delegates to client.realtime.channel().
|
|
4398
|
-
* Both patterns work identically:
|
|
4399
|
-
* - client.channel('room-1') - Supabase-style
|
|
4400
|
-
* - client.realtime.channel('room-1') - Fluxbase-style
|
|
4628
|
+
* Create or get a realtime channel (Supabase-compatible)
|
|
4401
4629
|
*
|
|
4402
4630
|
* @param name - Channel name
|
|
4631
|
+
* @param config - Optional channel configuration
|
|
4403
4632
|
* @returns RealtimeChannel instance
|
|
4404
4633
|
*
|
|
4405
4634
|
* @example
|
|
4406
4635
|
* ```typescript
|
|
4407
|
-
*
|
|
4408
|
-
*
|
|
4409
|
-
*
|
|
4410
|
-
*
|
|
4411
|
-
*
|
|
4412
|
-
*
|
|
4413
|
-
* }, (payload) => {
|
|
4414
|
-
* 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)
|
|
4415
4642
|
* })
|
|
4416
4643
|
* .subscribe()
|
|
4417
4644
|
* ```
|
|
4418
4645
|
*
|
|
4419
4646
|
* @category Realtime
|
|
4420
4647
|
*/
|
|
4421
|
-
channel(name) {
|
|
4422
|
-
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);
|
|
4423
4667
|
}
|
|
4424
4668
|
/**
|
|
4425
4669
|
* Get the internal HTTP client
|