@rebasepro/client 0.9.1-canary.ed943fa → 0.9.1-canary.f2f61da
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.d.ts +0 -2
- package/dist/index.es.js +60 -429
- package/dist/index.es.js.map +1 -1
- package/dist/websocket.d.ts +1 -54
- package/package.json +5 -7
- package/src/index.ts +0 -33
- package/src/websocket.ts +60 -340
- package/dist/realtime-channel.d.ts +0 -89
- package/src/realtime-channel.test.ts +0 -241
- package/src/realtime-channel.ts +0 -238
package/dist/index.d.ts
CHANGED
|
@@ -27,8 +27,6 @@ export type { CreateBackupsOptions } from "./backups";
|
|
|
27
27
|
export type { ApiKeyMasked, ApiKeyPermission, ApiKeyWithSecret, CreateApiKeyRequest, CreateApiKeysOptions, UpdateApiKeyRequest } from "./api-keys";
|
|
28
28
|
export type { FunctionInvokeOptions, FunctionsClient } from "./functions";
|
|
29
29
|
export { RebaseWebSocketClient } from "./websocket";
|
|
30
|
-
export { RebaseRealtimeChannel } from "./realtime-channel";
|
|
31
|
-
export type { PresenceState, PresenceDiff, BroadcastEvent, ChannelTransport } from "./realtime-channel";
|
|
32
30
|
export interface CreateRebaseClientOptions extends RebaseClientConfig {
|
|
33
31
|
auth?: CreateAuthOptions;
|
|
34
32
|
admin?: CreateAdminOptions;
|
package/dist/index.es.js
CHANGED
|
@@ -1397,11 +1397,9 @@ var ClientStorageSourceRegistry = class ClientStorageSourceRegistry {
|
|
|
1397
1397
|
function extractMessageError(message) {
|
|
1398
1398
|
const payload = message.payload;
|
|
1399
1399
|
const errPayload = payload?.error;
|
|
1400
|
-
const errorMessage = typeof errPayload === "object" ? errPayload.message : payload?.message || (typeof errPayload === "string" ? errPayload : void 0) || message.error || "Unknown error";
|
|
1401
|
-
const errorCode = typeof errPayload === "object" ? errPayload.code : payload?.code;
|
|
1402
1400
|
return {
|
|
1403
|
-
errorMessage: typeof
|
|
1404
|
-
errorCode
|
|
1401
|
+
errorMessage: typeof errPayload === "object" ? errPayload.message : payload?.message || (typeof errPayload === "string" ? errPayload : void 0) || message.error || "Unknown error",
|
|
1402
|
+
errorCode: typeof errPayload === "object" ? errPayload.code : payload?.code
|
|
1405
1403
|
};
|
|
1406
1404
|
}
|
|
1407
1405
|
/**
|
|
@@ -1419,23 +1417,6 @@ var RebaseWebSocketClient = class {
|
|
|
1419
1417
|
getAuthToken;
|
|
1420
1418
|
subscriptions = /* @__PURE__ */ new Map();
|
|
1421
1419
|
listeners = /* @__PURE__ */ new Map();
|
|
1422
|
-
/** Channel-name → handlers, for broadcast and presence frames. */
|
|
1423
|
-
channelHandlers = /* @__PURE__ */ new Map();
|
|
1424
|
-
/** Subscribe to broadcast/presence frames for one channel. */
|
|
1425
|
-
onChannelMessage(channel, handler) {
|
|
1426
|
-
if (!this.channelHandlers.has(channel)) this.channelHandlers.set(channel, /* @__PURE__ */ new Set());
|
|
1427
|
-
this.channelHandlers.get(channel).add(handler);
|
|
1428
|
-
return () => {
|
|
1429
|
-
const handlers = this.channelHandlers.get(channel);
|
|
1430
|
-
if (!handlers) return;
|
|
1431
|
-
handlers.delete(handler);
|
|
1432
|
-
if (handlers.size === 0) this.channelHandlers.delete(channel);
|
|
1433
|
-
};
|
|
1434
|
-
}
|
|
1435
|
-
/** Notified after the socket comes back, so channels can re-join. */
|
|
1436
|
-
onReconnect(handler) {
|
|
1437
|
-
return this.on("reconnect", handler);
|
|
1438
|
-
}
|
|
1439
1420
|
on(event, cb) {
|
|
1440
1421
|
if (!this.listeners.has(event)) this.listeners.set(event, /* @__PURE__ */ new Set());
|
|
1441
1422
|
this.listeners.get(event).add(cb);
|
|
@@ -1454,7 +1435,6 @@ var RebaseWebSocketClient = class {
|
|
|
1454
1435
|
isConnected = false;
|
|
1455
1436
|
messageQueue = [];
|
|
1456
1437
|
requestTimeoutMs = 3e4;
|
|
1457
|
-
subscriptionTimeoutMs = 3e4;
|
|
1458
1438
|
reconnectTimeout = null;
|
|
1459
1439
|
isAuthenticated = false;
|
|
1460
1440
|
authPromise = null;
|
|
@@ -1560,7 +1540,6 @@ var RebaseWebSocketClient = class {
|
|
|
1560
1540
|
this.emit(wasReconnect ? "reconnect" : "connect");
|
|
1561
1541
|
this.processMessageQueue();
|
|
1562
1542
|
if (wasReconnect) this.resubscribeAll();
|
|
1563
|
-
this.armPendingSubscribeWatchdogs();
|
|
1564
1543
|
};
|
|
1565
1544
|
this.ws.onmessage = (event) => {
|
|
1566
1545
|
try {
|
|
@@ -1575,7 +1554,6 @@ var RebaseWebSocketClient = class {
|
|
|
1575
1554
|
this.isConnected = false;
|
|
1576
1555
|
this.isAuthenticated = false;
|
|
1577
1556
|
this.authPromise = null;
|
|
1578
|
-
this.suspendSubscribeWatchdogs();
|
|
1579
1557
|
this.emit("disconnect");
|
|
1580
1558
|
for (const [reqId, request] of this.pendingRequests.entries()) {
|
|
1581
1559
|
if (reqId.startsWith("auth_")) request.reject(/* @__PURE__ */ new Error("Connection closed during authentication"));
|
|
@@ -1607,7 +1585,6 @@ var RebaseWebSocketClient = class {
|
|
|
1607
1585
|
attemptReconnect() {
|
|
1608
1586
|
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
|
1609
1587
|
console.error("Max reconnection attempts reached");
|
|
1610
|
-
this.failAllPendingSubscriptions(new RebaseApiError$1("Connection lost", { code: "CONNECTION_LOST" }));
|
|
1611
1588
|
return;
|
|
1612
1589
|
}
|
|
1613
1590
|
this.reconnectAttempts++;
|
|
@@ -1662,18 +1639,29 @@ var RebaseWebSocketClient = class {
|
|
|
1662
1639
|
subscription.backendSubscriptionId = newBackendId;
|
|
1663
1640
|
backendKeyMap.delete(oldBackendId);
|
|
1664
1641
|
backendKeyMap.set(newBackendId, subscriptionKey);
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1642
|
+
this.sendMessage({
|
|
1643
|
+
type: messageType,
|
|
1644
|
+
payload: {
|
|
1645
|
+
...subscription.props,
|
|
1646
|
+
subscriptionId: newBackendId
|
|
1647
|
+
}
|
|
1648
|
+
}).catch((error) => {
|
|
1649
|
+
console.error(`[WS] Failed to re-subscribe ${idPrefix} after auth refresh:`, subscriptionKey, error);
|
|
1650
|
+
subscription.callbacks.forEach((callback) => {
|
|
1651
|
+
if (callback.onError) callback.onError(error);
|
|
1652
|
+
});
|
|
1653
|
+
});
|
|
1654
|
+
} else {
|
|
1655
|
+
const { errorMessage, errorCode } = extractMessageError(message);
|
|
1656
|
+
const error = new RebaseApiError$1(errorMessage, { code: errorCode });
|
|
1657
|
+
subscription.callbacks.forEach((callback) => {
|
|
1658
|
+
if (callback.onError) callback.onError(error);
|
|
1659
|
+
});
|
|
1668
1660
|
}
|
|
1669
|
-
const { errorMessage, errorCode } = extractMessageError(message);
|
|
1670
|
-
const error = new RebaseApiError$1(errorMessage, { code: errorCode });
|
|
1671
|
-
if (messageType === "subscribe_collection") this.failCollectionSubscription(subscriptionKey, error);
|
|
1672
|
-
else this.failEntitySubscription(subscriptionKey, error);
|
|
1673
1661
|
}).catch((err) => {
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1662
|
+
subscription.callbacks.forEach((callback) => {
|
|
1663
|
+
if (callback.onError) callback.onError(err);
|
|
1664
|
+
});
|
|
1677
1665
|
});
|
|
1678
1666
|
}
|
|
1679
1667
|
handleWebSocketMessage(message) {
|
|
@@ -1702,15 +1690,6 @@ var RebaseWebSocketClient = class {
|
|
|
1702
1690
|
}
|
|
1703
1691
|
return;
|
|
1704
1692
|
}
|
|
1705
|
-
if (typeof message.channel === "string" && (type === "broadcast" || type === "presence_state" || type === "presence_diff")) {
|
|
1706
|
-
const handlers = this.channelHandlers.get(message.channel);
|
|
1707
|
-
if (handlers) for (const handler of [...handlers]) try {
|
|
1708
|
-
handler(message);
|
|
1709
|
-
} catch (error) {
|
|
1710
|
-
console.error("Error in channel handler:", error);
|
|
1711
|
-
}
|
|
1712
|
-
return;
|
|
1713
|
-
}
|
|
1714
1693
|
if (subscriptionId && type === "collection_update") {
|
|
1715
1694
|
const subscriptionKey = this.backendToCollectionKey.get(subscriptionId);
|
|
1716
1695
|
if (subscriptionKey) {
|
|
@@ -1723,9 +1702,6 @@ var RebaseWebSocketClient = class {
|
|
|
1723
1702
|
collectionSub.latestData = rows;
|
|
1724
1703
|
collectionSub.lastUpdated = Date.now();
|
|
1725
1704
|
collectionSub.isInitialDataReceived = true;
|
|
1726
|
-
if (collectionSub.subscribeTimeout) clearTimeout(collectionSub.subscribeTimeout);
|
|
1727
|
-
collectionSub.subscribeTimeout = void 0;
|
|
1728
|
-
collectionSub.subscribeInFlight = false;
|
|
1729
1705
|
collectionSub.callbacks.forEach((callback) => {
|
|
1730
1706
|
try {
|
|
1731
1707
|
callback.onUpdate(rows);
|
|
@@ -1781,9 +1757,6 @@ var RebaseWebSocketClient = class {
|
|
|
1781
1757
|
entitySub.latestData = row;
|
|
1782
1758
|
entitySub.lastUpdated = Date.now();
|
|
1783
1759
|
entitySub.isInitialDataReceived = true;
|
|
1784
|
-
if (entitySub.subscribeTimeout) clearTimeout(entitySub.subscribeTimeout);
|
|
1785
|
-
entitySub.subscribeTimeout = void 0;
|
|
1786
|
-
entitySub.subscribeInFlight = false;
|
|
1787
1760
|
entitySub.callbacks.forEach((callback) => {
|
|
1788
1761
|
try {
|
|
1789
1762
|
callback.onUpdate(row);
|
|
@@ -1805,9 +1778,6 @@ var RebaseWebSocketClient = class {
|
|
|
1805
1778
|
this.resubscribeAfterAuthRefresh(message, collectionSub, collectionKey, "collection", this.backendToCollectionKey, "subscribe_collection");
|
|
1806
1779
|
return;
|
|
1807
1780
|
}
|
|
1808
|
-
if (collectionSub.subscribeTimeout) clearTimeout(collectionSub.subscribeTimeout);
|
|
1809
|
-
collectionSub.subscribeTimeout = void 0;
|
|
1810
|
-
collectionSub.subscribeInFlight = false;
|
|
1811
1781
|
const { errorMessage, errorCode } = extractMessageError(message);
|
|
1812
1782
|
const error = new RebaseApiError$1(errorMessage, { code: errorCode });
|
|
1813
1783
|
collectionSub.callbacks.forEach((callback) => {
|
|
@@ -1824,9 +1794,6 @@ var RebaseWebSocketClient = class {
|
|
|
1824
1794
|
this.resubscribeAfterAuthRefresh(message, entitySub, entityKey, "row", this.backendToEntityKey, "subscribe_one");
|
|
1825
1795
|
return;
|
|
1826
1796
|
}
|
|
1827
|
-
if (entitySub.subscribeTimeout) clearTimeout(entitySub.subscribeTimeout);
|
|
1828
|
-
entitySub.subscribeTimeout = void 0;
|
|
1829
|
-
entitySub.subscribeInFlight = false;
|
|
1830
1797
|
const { errorMessage, errorCode } = extractMessageError(message);
|
|
1831
1798
|
const error = new RebaseApiError$1(errorMessage, { code: errorCode });
|
|
1832
1799
|
entitySub.callbacks.forEach((callback) => {
|
|
@@ -1899,10 +1866,6 @@ var RebaseWebSocketClient = class {
|
|
|
1899
1866
|
throw error;
|
|
1900
1867
|
}
|
|
1901
1868
|
}
|
|
1902
|
-
/**
|
|
1903
|
-
* Public because `RebaseRealtimeChannel` sends channel frames through it.
|
|
1904
|
-
* Not part of the stable surface — prefer `client.realtime.channel(name)`.
|
|
1905
|
-
*/
|
|
1906
1869
|
sendMessage(message) {
|
|
1907
1870
|
const queuedMsg = message;
|
|
1908
1871
|
if (queuedMsg._queuedResolve && queuedMsg._queuedReject) return this.doSendMessage(message, queuedMsg._queuedResolve, queuedMsg._queuedReject);
|
|
@@ -2179,12 +2142,9 @@ var RebaseWebSocketClient = class {
|
|
|
2179
2142
|
console.error("Error in collection subscription callback:", error);
|
|
2180
2143
|
if (onError) onError(error instanceof Error ? error : new Error(String(error)));
|
|
2181
2144
|
}
|
|
2182
|
-
else if (!existingSubscription.subscribeInFlight) this.sendCollectionSubscribe(subscriptionKey);
|
|
2183
2145
|
return () => {
|
|
2184
2146
|
callbackMap.delete(callbackId);
|
|
2185
2147
|
if (callbackMap.size === 0) {
|
|
2186
|
-
if (this.collectionSubscriptions.get(subscriptionKey) !== existingSubscription) return;
|
|
2187
|
-
if (existingSubscription.subscribeTimeout) clearTimeout(existingSubscription.subscribeTimeout);
|
|
2188
2148
|
this.collectionSubscriptions.delete(subscriptionKey);
|
|
2189
2149
|
this.backendToCollectionKey.delete(existingSubscription.backendSubscriptionId);
|
|
2190
2150
|
if (this.isConnected && this.ws) this.sendMessage({
|
|
@@ -2206,14 +2166,21 @@ var RebaseWebSocketClient = class {
|
|
|
2206
2166
|
props
|
|
2207
2167
|
});
|
|
2208
2168
|
this.backendToCollectionKey.set(backendSubscriptionId, subscriptionKey);
|
|
2209
|
-
this.
|
|
2169
|
+
this.sendMessage({
|
|
2170
|
+
type: "subscribe_collection",
|
|
2171
|
+
payload: {
|
|
2172
|
+
...props,
|
|
2173
|
+
subscriptionId: backendSubscriptionId
|
|
2174
|
+
}
|
|
2175
|
+
}).catch((error) => {
|
|
2176
|
+
if (onError) onError(error);
|
|
2177
|
+
});
|
|
2210
2178
|
return () => {
|
|
2211
2179
|
const subscription = this.collectionSubscriptions.get(subscriptionKey);
|
|
2212
2180
|
if (subscription) {
|
|
2213
2181
|
const callbacks = subscription.callbacks;
|
|
2214
2182
|
callbacks.delete(callbackId);
|
|
2215
2183
|
if (callbacks.size === 0) {
|
|
2216
|
-
if (subscription.subscribeTimeout) clearTimeout(subscription.subscribeTimeout);
|
|
2217
2184
|
this.collectionSubscriptions.delete(subscriptionKey);
|
|
2218
2185
|
this.backendToCollectionKey.delete(subscription.backendSubscriptionId);
|
|
2219
2186
|
if (this.isConnected && this.ws) this.sendMessage({
|
|
@@ -2240,12 +2207,9 @@ var RebaseWebSocketClient = class {
|
|
|
2240
2207
|
console.error("Error in row subscription callback:", error);
|
|
2241
2208
|
if (onError) onError(error instanceof Error ? error : new Error(String(error)));
|
|
2242
2209
|
}
|
|
2243
|
-
else if (!existingSubscription.subscribeInFlight) this.sendEntitySubscribe(subscriptionKey);
|
|
2244
2210
|
return () => {
|
|
2245
2211
|
callbackMap.delete(callbackId);
|
|
2246
2212
|
if (callbackMap.size === 0) {
|
|
2247
|
-
if (this.singleSubscriptions.get(subscriptionKey) !== existingSubscription) return;
|
|
2248
|
-
if (existingSubscription.subscribeTimeout) clearTimeout(existingSubscription.subscribeTimeout);
|
|
2249
2213
|
this.singleSubscriptions.delete(subscriptionKey);
|
|
2250
2214
|
this.backendToEntityKey.delete(existingSubscription.backendSubscriptionId);
|
|
2251
2215
|
if (this.isConnected && this.ws) this.sendMessage({
|
|
@@ -2267,7 +2231,15 @@ var RebaseWebSocketClient = class {
|
|
|
2267
2231
|
props
|
|
2268
2232
|
});
|
|
2269
2233
|
this.backendToEntityKey.set(backendSubscriptionId, subscriptionKey);
|
|
2270
|
-
this.
|
|
2234
|
+
this.sendMessage({
|
|
2235
|
+
type: "subscribe_one",
|
|
2236
|
+
payload: {
|
|
2237
|
+
...props,
|
|
2238
|
+
subscriptionId: backendSubscriptionId
|
|
2239
|
+
}
|
|
2240
|
+
}).catch((error) => {
|
|
2241
|
+
if (onError) onError(error);
|
|
2242
|
+
});
|
|
2271
2243
|
return () => {
|
|
2272
2244
|
const subscription = this.singleSubscriptions.get(subscriptionKey);
|
|
2273
2245
|
if (subscription) {
|
|
@@ -2285,157 +2257,6 @@ var RebaseWebSocketClient = class {
|
|
|
2285
2257
|
};
|
|
2286
2258
|
}
|
|
2287
2259
|
/**
|
|
2288
|
-
* Send a `subscribe_collection` for an already-registered subscription and
|
|
2289
|
-
* arm its watchdog.
|
|
2290
|
-
*
|
|
2291
|
-
* Every path that registers a collection subscription goes through here, so
|
|
2292
|
-
* that a subscribe which never lands — a rejected send, or a server that
|
|
2293
|
-
* never answers — always ends up in `failCollectionSubscription` rather than
|
|
2294
|
-
* leaving the entry parked with `isInitialDataReceived === false` forever.
|
|
2295
|
-
*/
|
|
2296
|
-
sendCollectionSubscribe(subscriptionKey) {
|
|
2297
|
-
const subscription = this.collectionSubscriptions.get(subscriptionKey);
|
|
2298
|
-
if (!subscription) return;
|
|
2299
|
-
const backendSubscriptionId = subscription.backendSubscriptionId;
|
|
2300
|
-
subscription.subscribeInFlight = true;
|
|
2301
|
-
if (subscription.subscribeTimeout) clearTimeout(subscription.subscribeTimeout);
|
|
2302
|
-
subscription.subscribeTimeout = void 0;
|
|
2303
|
-
if (this.isConnected) this.sendCollectionSubscribeWatchdog(subscriptionKey);
|
|
2304
|
-
this.sendMessage({
|
|
2305
|
-
type: "subscribe_collection",
|
|
2306
|
-
payload: {
|
|
2307
|
-
...subscription.props,
|
|
2308
|
-
subscriptionId: backendSubscriptionId
|
|
2309
|
-
}
|
|
2310
|
-
}).catch((error) => {
|
|
2311
|
-
const current = this.collectionSubscriptions.get(subscriptionKey);
|
|
2312
|
-
if (!current || current.backendSubscriptionId !== backendSubscriptionId) return;
|
|
2313
|
-
this.failCollectionSubscription(subscriptionKey, error instanceof Error ? error : new Error(String(error)));
|
|
2314
|
-
});
|
|
2315
|
-
}
|
|
2316
|
-
/** The `listenOne` counterpart of {@link sendCollectionSubscribe}. */
|
|
2317
|
-
sendEntitySubscribe(subscriptionKey) {
|
|
2318
|
-
const subscription = this.singleSubscriptions.get(subscriptionKey);
|
|
2319
|
-
if (!subscription) return;
|
|
2320
|
-
const backendSubscriptionId = subscription.backendSubscriptionId;
|
|
2321
|
-
subscription.subscribeInFlight = true;
|
|
2322
|
-
if (subscription.subscribeTimeout) clearTimeout(subscription.subscribeTimeout);
|
|
2323
|
-
subscription.subscribeTimeout = void 0;
|
|
2324
|
-
if (this.isConnected) this.sendEntitySubscribeWatchdog(subscriptionKey);
|
|
2325
|
-
this.sendMessage({
|
|
2326
|
-
type: "subscribe_one",
|
|
2327
|
-
payload: {
|
|
2328
|
-
...subscription.props,
|
|
2329
|
-
subscriptionId: backendSubscriptionId
|
|
2330
|
-
}
|
|
2331
|
-
}).catch((error) => {
|
|
2332
|
-
const current = this.singleSubscriptions.get(subscriptionKey);
|
|
2333
|
-
if (!current || current.backendSubscriptionId !== backendSubscriptionId) return;
|
|
2334
|
-
this.failEntitySubscription(subscriptionKey, error instanceof Error ? error : new Error(String(error)));
|
|
2335
|
-
});
|
|
2336
|
-
}
|
|
2337
|
-
/**
|
|
2338
|
-
* Report a subscribe failure to every listener and drop the registration.
|
|
2339
|
-
*
|
|
2340
|
-
* Dropping it is the point: the callbacks stay live (their components are
|
|
2341
|
-
* still mounted and have been told), but the next `listenCollection` for
|
|
2342
|
-
* these params finds no entry and issues a fresh subscribe instead of
|
|
2343
|
-
* silently attaching to a dead one.
|
|
2344
|
-
*/
|
|
2345
|
-
failCollectionSubscription(subscriptionKey, error) {
|
|
2346
|
-
const subscription = this.collectionSubscriptions.get(subscriptionKey);
|
|
2347
|
-
if (!subscription) return;
|
|
2348
|
-
if (subscription.subscribeTimeout) clearTimeout(subscription.subscribeTimeout);
|
|
2349
|
-
subscription.subscribeInFlight = false;
|
|
2350
|
-
this.collectionSubscriptions.delete(subscriptionKey);
|
|
2351
|
-
this.backendToCollectionKey.delete(subscription.backendSubscriptionId);
|
|
2352
|
-
subscription.callbacks.forEach((callback) => {
|
|
2353
|
-
if (callback.onError) try {
|
|
2354
|
-
callback.onError(error);
|
|
2355
|
-
} catch (callbackError) {
|
|
2356
|
-
console.error("Error in collection subscription error callback:", callbackError);
|
|
2357
|
-
}
|
|
2358
|
-
});
|
|
2359
|
-
}
|
|
2360
|
-
/** The `listenOne` counterpart of {@link failCollectionSubscription}. */
|
|
2361
|
-
failEntitySubscription(subscriptionKey, error) {
|
|
2362
|
-
const subscription = this.singleSubscriptions.get(subscriptionKey);
|
|
2363
|
-
if (!subscription) return;
|
|
2364
|
-
if (subscription.subscribeTimeout) clearTimeout(subscription.subscribeTimeout);
|
|
2365
|
-
subscription.subscribeInFlight = false;
|
|
2366
|
-
this.singleSubscriptions.delete(subscriptionKey);
|
|
2367
|
-
this.backendToEntityKey.delete(subscription.backendSubscriptionId);
|
|
2368
|
-
subscription.callbacks.forEach((callback) => {
|
|
2369
|
-
if (callback.onError) try {
|
|
2370
|
-
callback.onError(error);
|
|
2371
|
-
} catch (callbackError) {
|
|
2372
|
-
console.error("Error in row subscription error callback:", callbackError);
|
|
2373
|
-
}
|
|
2374
|
-
});
|
|
2375
|
-
}
|
|
2376
|
-
/**
|
|
2377
|
-
* Stop the watchdogs without failing anything — used when the socket drops,
|
|
2378
|
-
* since the reconnect path re-subscribes everything anyway and a watchdog
|
|
2379
|
-
* firing mid-reconnect would tear down healthy subscriptions.
|
|
2380
|
-
*/
|
|
2381
|
-
suspendSubscribeWatchdogs() {
|
|
2382
|
-
for (const sub of this.collectionSubscriptions.values()) {
|
|
2383
|
-
if (sub.subscribeTimeout) clearTimeout(sub.subscribeTimeout);
|
|
2384
|
-
sub.subscribeTimeout = void 0;
|
|
2385
|
-
sub.subscribeInFlight = false;
|
|
2386
|
-
}
|
|
2387
|
-
for (const sub of this.singleSubscriptions.values()) {
|
|
2388
|
-
if (sub.subscribeTimeout) clearTimeout(sub.subscribeTimeout);
|
|
2389
|
-
sub.subscribeTimeout = void 0;
|
|
2390
|
-
sub.subscribeInFlight = false;
|
|
2391
|
-
}
|
|
2392
|
-
}
|
|
2393
|
-
/**
|
|
2394
|
-
* Arm watchdogs for subscribes that were requested while offline and have
|
|
2395
|
-
* just been flushed to the socket. Their timers were deliberately not set at
|
|
2396
|
-
* request time, so without this they would have no timeout at all.
|
|
2397
|
-
*/
|
|
2398
|
-
armPendingSubscribeWatchdogs() {
|
|
2399
|
-
for (const [key, sub] of this.collectionSubscriptions.entries()) if (sub.subscribeInFlight && !sub.subscribeTimeout) this.sendCollectionSubscribeWatchdog(key);
|
|
2400
|
-
for (const [key, sub] of this.singleSubscriptions.entries()) if (sub.subscribeInFlight && !sub.subscribeTimeout) this.sendEntitySubscribeWatchdog(key);
|
|
2401
|
-
}
|
|
2402
|
-
sendCollectionSubscribeWatchdog(subscriptionKey) {
|
|
2403
|
-
const subscription = this.collectionSubscriptions.get(subscriptionKey);
|
|
2404
|
-
if (!subscription) return;
|
|
2405
|
-
const backendSubscriptionId = subscription.backendSubscriptionId;
|
|
2406
|
-
subscription.subscribeTimeout = setTimeout(() => {
|
|
2407
|
-
const current = this.collectionSubscriptions.get(subscriptionKey);
|
|
2408
|
-
if (!current || current.backendSubscriptionId !== backendSubscriptionId) return;
|
|
2409
|
-
if (!current.subscribeInFlight) return;
|
|
2410
|
-
this.failCollectionSubscription(subscriptionKey, new RebaseApiError$1("Subscription timed out", { code: "SUBSCRIPTION_TIMEOUT" }));
|
|
2411
|
-
}, this.subscriptionTimeoutMs);
|
|
2412
|
-
}
|
|
2413
|
-
sendEntitySubscribeWatchdog(subscriptionKey) {
|
|
2414
|
-
const subscription = this.singleSubscriptions.get(subscriptionKey);
|
|
2415
|
-
if (!subscription) return;
|
|
2416
|
-
const backendSubscriptionId = subscription.backendSubscriptionId;
|
|
2417
|
-
subscription.subscribeTimeout = setTimeout(() => {
|
|
2418
|
-
const current = this.singleSubscriptions.get(subscriptionKey);
|
|
2419
|
-
if (!current || current.backendSubscriptionId !== backendSubscriptionId) return;
|
|
2420
|
-
if (!current.subscribeInFlight) return;
|
|
2421
|
-
this.failEntitySubscription(subscriptionKey, new RebaseApiError$1("Subscription timed out", { code: "SUBSCRIPTION_TIMEOUT" }));
|
|
2422
|
-
}, this.subscriptionTimeoutMs);
|
|
2423
|
-
}
|
|
2424
|
-
/**
|
|
2425
|
-
* Fail every subscription that never received data. Called when reconnection
|
|
2426
|
-
* is given up on, so views surface an error instead of spinning forever.
|
|
2427
|
-
*/
|
|
2428
|
-
failAllPendingSubscriptions(error) {
|
|
2429
|
-
for (const key of [...this.collectionSubscriptions.keys()]) {
|
|
2430
|
-
const sub = this.collectionSubscriptions.get(key);
|
|
2431
|
-
if (sub && !sub.isInitialDataReceived) this.failCollectionSubscription(key, error);
|
|
2432
|
-
}
|
|
2433
|
-
for (const key of [...this.singleSubscriptions.keys()]) {
|
|
2434
|
-
const sub = this.singleSubscriptions.get(key);
|
|
2435
|
-
if (sub && !sub.isInitialDataReceived) this.failEntitySubscription(key, error);
|
|
2436
|
-
}
|
|
2437
|
-
}
|
|
2438
|
-
/**
|
|
2439
2260
|
* Re-send all active subscriptions to the backend after a reconnect.
|
|
2440
2261
|
* The server wipes subscription state when a client disconnects, so
|
|
2441
2262
|
* we need to re-register everything to resume receiving updates.
|
|
@@ -2448,7 +2269,15 @@ var RebaseWebSocketClient = class {
|
|
|
2448
2269
|
sub.backendSubscriptionId = newBackendId;
|
|
2449
2270
|
this.backendToCollectionKey.delete(oldBackendId);
|
|
2450
2271
|
this.backendToCollectionKey.set(newBackendId, key);
|
|
2451
|
-
this.
|
|
2272
|
+
this.sendMessage({
|
|
2273
|
+
type: "subscribe_collection",
|
|
2274
|
+
payload: {
|
|
2275
|
+
...sub.props,
|
|
2276
|
+
subscriptionId: newBackendId
|
|
2277
|
+
}
|
|
2278
|
+
}).catch((error) => {
|
|
2279
|
+
console.error("[WS] Failed to re-subscribe collection:", key, error);
|
|
2280
|
+
});
|
|
2452
2281
|
}
|
|
2453
2282
|
for (const [key, sub] of this.singleSubscriptions.entries()) {
|
|
2454
2283
|
const oldBackendId = sub.backendSubscriptionId;
|
|
@@ -2456,7 +2285,15 @@ var RebaseWebSocketClient = class {
|
|
|
2456
2285
|
sub.backendSubscriptionId = newBackendId;
|
|
2457
2286
|
this.backendToEntityKey.delete(oldBackendId);
|
|
2458
2287
|
this.backendToEntityKey.set(newBackendId, key);
|
|
2459
|
-
this.
|
|
2288
|
+
this.sendMessage({
|
|
2289
|
+
type: "subscribe_one",
|
|
2290
|
+
payload: {
|
|
2291
|
+
...sub.props,
|
|
2292
|
+
subscriptionId: newBackendId
|
|
2293
|
+
}
|
|
2294
|
+
}).catch((error) => {
|
|
2295
|
+
console.error("[WS] Failed to re-subscribe row:", key, error);
|
|
2296
|
+
});
|
|
2460
2297
|
}
|
|
2461
2298
|
}
|
|
2462
2299
|
createCollectionSubscriptionKey(props) {
|
|
@@ -2483,190 +2320,6 @@ var RebaseWebSocketClient = class {
|
|
|
2483
2320
|
}
|
|
2484
2321
|
};
|
|
2485
2322
|
//#endregion
|
|
2486
|
-
//#region src/realtime-channel.ts
|
|
2487
|
-
/**
|
|
2488
|
-
* Re-send presence comfortably inside the server's 30s expiry.
|
|
2489
|
-
*
|
|
2490
|
-
* Two-thirds of the window: one lost heartbeat still leaves time for the next
|
|
2491
|
-
* before the entry is reaped, so a single dropped frame is not a disappearance.
|
|
2492
|
-
*/
|
|
2493
|
-
var PRESENCE_HEARTBEAT_MS = 2e4;
|
|
2494
|
-
var RebaseRealtimeChannel = class {
|
|
2495
|
-
name;
|
|
2496
|
-
transport;
|
|
2497
|
-
presenceHandlers = /* @__PURE__ */ new Set();
|
|
2498
|
-
broadcastHandlers = /* @__PURE__ */ new Set();
|
|
2499
|
-
unsubscribers = [];
|
|
2500
|
-
/** Last known roster, kept so handlers always get a full picture. */
|
|
2501
|
-
presences = {};
|
|
2502
|
-
/** What this client last tracked, replayed on reconnect and heartbeat. */
|
|
2503
|
-
trackedState = null;
|
|
2504
|
-
heartbeat = null;
|
|
2505
|
-
joined = false;
|
|
2506
|
-
constructor(name, transport) {
|
|
2507
|
-
this.name = name;
|
|
2508
|
-
this.transport = transport;
|
|
2509
|
-
}
|
|
2510
|
-
/**
|
|
2511
|
-
* Join the channel and ask for the current roster.
|
|
2512
|
-
*
|
|
2513
|
-
* Called automatically by `track`, `broadcast`, `onPresence` and
|
|
2514
|
-
* `onBroadcast`; calling it directly is only needed to start receiving
|
|
2515
|
-
* before there is anything to send.
|
|
2516
|
-
*/
|
|
2517
|
-
async join() {
|
|
2518
|
-
if (this.joined) return;
|
|
2519
|
-
this.joined = true;
|
|
2520
|
-
this.unsubscribers.push(this.transport.onChannelMessage(this.name, (message) => this.handle(message)));
|
|
2521
|
-
this.unsubscribers.push(this.transport.onReconnect(() => {
|
|
2522
|
-
this.rejoin();
|
|
2523
|
-
}));
|
|
2524
|
-
await this.transport.sendMessage({
|
|
2525
|
-
type: "join_channel",
|
|
2526
|
-
channel: this.name
|
|
2527
|
-
});
|
|
2528
|
-
await this.transport.sendMessage({
|
|
2529
|
-
type: "presence_state",
|
|
2530
|
-
channel: this.name
|
|
2531
|
-
});
|
|
2532
|
-
}
|
|
2533
|
-
async rejoin() {
|
|
2534
|
-
try {
|
|
2535
|
-
await this.transport.sendMessage({
|
|
2536
|
-
type: "join_channel",
|
|
2537
|
-
channel: this.name
|
|
2538
|
-
});
|
|
2539
|
-
await this.transport.sendMessage({
|
|
2540
|
-
type: "presence_state",
|
|
2541
|
-
channel: this.name
|
|
2542
|
-
});
|
|
2543
|
-
if (this.trackedState) await this.transport.sendMessage({
|
|
2544
|
-
type: "presence_track",
|
|
2545
|
-
channel: this.name,
|
|
2546
|
-
state: this.trackedState
|
|
2547
|
-
});
|
|
2548
|
-
} catch {}
|
|
2549
|
-
}
|
|
2550
|
-
/**
|
|
2551
|
-
* Publish this client's presence state, and keep publishing it.
|
|
2552
|
-
*
|
|
2553
|
-
* Calling `track` again replaces the state (and restarts the heartbeat),
|
|
2554
|
-
* which is how you update e.g. a cursor position.
|
|
2555
|
-
*/
|
|
2556
|
-
async track(state) {
|
|
2557
|
-
await this.join();
|
|
2558
|
-
this.trackedState = state;
|
|
2559
|
-
await this.transport.sendMessage({
|
|
2560
|
-
type: "presence_track",
|
|
2561
|
-
channel: this.name,
|
|
2562
|
-
state
|
|
2563
|
-
});
|
|
2564
|
-
if (!this.heartbeat) {
|
|
2565
|
-
this.heartbeat = setInterval(() => {
|
|
2566
|
-
if (!this.trackedState) return;
|
|
2567
|
-
this.transport.sendMessage({
|
|
2568
|
-
type: "presence_track",
|
|
2569
|
-
channel: this.name,
|
|
2570
|
-
state: this.trackedState
|
|
2571
|
-
}).catch(() => {});
|
|
2572
|
-
}, PRESENCE_HEARTBEAT_MS);
|
|
2573
|
-
this.heartbeat.unref?.();
|
|
2574
|
-
}
|
|
2575
|
-
}
|
|
2576
|
-
/** Stop publishing presence, without leaving the channel. */
|
|
2577
|
-
async untrack() {
|
|
2578
|
-
this.stopHeartbeat();
|
|
2579
|
-
this.trackedState = null;
|
|
2580
|
-
if (this.joined) await this.transport.sendMessage({
|
|
2581
|
-
type: "presence_untrack",
|
|
2582
|
-
channel: this.name
|
|
2583
|
-
});
|
|
2584
|
-
}
|
|
2585
|
-
/**
|
|
2586
|
-
* Observe the roster. The handler fires immediately with what is already
|
|
2587
|
-
* known, then on every change.
|
|
2588
|
-
*/
|
|
2589
|
-
onPresence(handler) {
|
|
2590
|
-
this.presenceHandlers.add(handler);
|
|
2591
|
-
this.join();
|
|
2592
|
-
if (Object.keys(this.presences).length > 0) handler({ ...this.presences });
|
|
2593
|
-
return () => this.presenceHandlers.delete(handler);
|
|
2594
|
-
}
|
|
2595
|
-
/** Send a broadcast. The sender does not receive its own message. */
|
|
2596
|
-
async broadcast(event, payload) {
|
|
2597
|
-
await this.join();
|
|
2598
|
-
await this.transport.sendMessage({
|
|
2599
|
-
type: "broadcast",
|
|
2600
|
-
channel: this.name,
|
|
2601
|
-
event,
|
|
2602
|
-
payload
|
|
2603
|
-
});
|
|
2604
|
-
}
|
|
2605
|
-
onBroadcast(eventOrHandler, maybeHandler) {
|
|
2606
|
-
const wrapped = typeof eventOrHandler === "string" ? (e) => {
|
|
2607
|
-
if (e.event === eventOrHandler) maybeHandler(e.payload);
|
|
2608
|
-
} : eventOrHandler;
|
|
2609
|
-
this.broadcastHandlers.add(wrapped);
|
|
2610
|
-
this.join();
|
|
2611
|
-
return () => this.broadcastHandlers.delete(wrapped);
|
|
2612
|
-
}
|
|
2613
|
-
/** Leave the channel and release every listener and timer. */
|
|
2614
|
-
async leave() {
|
|
2615
|
-
this.stopHeartbeat();
|
|
2616
|
-
this.trackedState = null;
|
|
2617
|
-
this.presences = {};
|
|
2618
|
-
this.presenceHandlers.clear();
|
|
2619
|
-
this.broadcastHandlers.clear();
|
|
2620
|
-
for (const off of this.unsubscribers) off();
|
|
2621
|
-
this.unsubscribers = [];
|
|
2622
|
-
if (this.joined) {
|
|
2623
|
-
this.joined = false;
|
|
2624
|
-
await this.transport.sendMessage({
|
|
2625
|
-
type: "leave_channel",
|
|
2626
|
-
channel: this.name
|
|
2627
|
-
});
|
|
2628
|
-
}
|
|
2629
|
-
}
|
|
2630
|
-
stopHeartbeat() {
|
|
2631
|
-
if (this.heartbeat) {
|
|
2632
|
-
clearInterval(this.heartbeat);
|
|
2633
|
-
this.heartbeat = null;
|
|
2634
|
-
}
|
|
2635
|
-
}
|
|
2636
|
-
/** Fold an incoming frame into the roster and fan it out. */
|
|
2637
|
-
handle(message) {
|
|
2638
|
-
switch (message.type) {
|
|
2639
|
-
case "presence_state":
|
|
2640
|
-
this.presences = message.presences ?? {};
|
|
2641
|
-
this.emitPresence();
|
|
2642
|
-
break;
|
|
2643
|
-
case "presence_diff": {
|
|
2644
|
-
const joins = message.joins ?? {};
|
|
2645
|
-
const leaves = message.leaves ?? {};
|
|
2646
|
-
for (const [id, state] of Object.entries(joins)) this.presences[id] = state;
|
|
2647
|
-
for (const id of Object.keys(leaves)) delete this.presences[id];
|
|
2648
|
-
this.emitPresence({
|
|
2649
|
-
joins,
|
|
2650
|
-
leaves
|
|
2651
|
-
});
|
|
2652
|
-
break;
|
|
2653
|
-
}
|
|
2654
|
-
case "broadcast": {
|
|
2655
|
-
const event = {
|
|
2656
|
-
event: message.event,
|
|
2657
|
-
payload: message.payload
|
|
2658
|
-
};
|
|
2659
|
-
for (const handler of this.broadcastHandlers) handler(event);
|
|
2660
|
-
break;
|
|
2661
|
-
}
|
|
2662
|
-
}
|
|
2663
|
-
}
|
|
2664
|
-
emitPresence(diff) {
|
|
2665
|
-
const snapshot = { ...this.presences };
|
|
2666
|
-
for (const handler of this.presenceHandlers) handler(snapshot, diff);
|
|
2667
|
-
}
|
|
2668
|
-
};
|
|
2669
|
-
//#endregion
|
|
2670
2323
|
//#region src/index.ts
|
|
2671
2324
|
/**
|
|
2672
2325
|
* Derive a WebSocket URL from an HTTP base URL.
|
|
@@ -2717,8 +2370,6 @@ function createRebaseClient(options) {
|
|
|
2717
2370
|
};
|
|
2718
2371
|
const resolvedWsUrl = options.realtime !== false ? options.websocketUrl ?? deriveWebSocketUrl(options.baseUrl) : void 0;
|
|
2719
2372
|
let ws;
|
|
2720
|
-
/** One channel object per name — see `realtime.channel`. */
|
|
2721
|
-
const realtimeChannels = /* @__PURE__ */ new Map();
|
|
2722
2373
|
if (resolvedWsUrl) {
|
|
2723
2374
|
ws = new RebaseWebSocketClient({
|
|
2724
2375
|
websocketUrl: resolvedWsUrl,
|
|
@@ -2829,24 +2480,6 @@ function createRebaseClient(options) {
|
|
|
2829
2480
|
createStorageSource,
|
|
2830
2481
|
fetchStorageSources,
|
|
2831
2482
|
ws,
|
|
2832
|
-
realtime: {
|
|
2833
|
-
/**
|
|
2834
|
-
* Join a broadcast/presence channel.
|
|
2835
|
-
*
|
|
2836
|
-
* Repeated calls with the same name return the same channel, so
|
|
2837
|
-
* separate components can attach handlers without each opening its
|
|
2838
|
-
* own membership — and `leave()` from one would otherwise silently
|
|
2839
|
-
* cut off the others.
|
|
2840
|
-
*/
|
|
2841
|
-
channel: (name) => {
|
|
2842
|
-
if (!ws) throw new RebaseClientError("Realtime is disabled on this client (realtime: false), so channels are unavailable.");
|
|
2843
|
-
let existing = realtimeChannels.get(name);
|
|
2844
|
-
if (!existing) {
|
|
2845
|
-
existing = new RebaseRealtimeChannel(name, ws);
|
|
2846
|
-
realtimeChannels.set(name, existing);
|
|
2847
|
-
}
|
|
2848
|
-
return existing;
|
|
2849
|
-
} },
|
|
2850
2483
|
/**
|
|
2851
2484
|
* Release the realtime socket and its reconnect timer.
|
|
2852
2485
|
*
|
|
@@ -2855,8 +2488,6 @@ channel: (name) => {
|
|
|
2855
2488
|
* was never started, and safe to call twice.
|
|
2856
2489
|
*/
|
|
2857
2490
|
close: () => {
|
|
2858
|
-
for (const channel of realtimeChannels.values()) channel.leave();
|
|
2859
|
-
realtimeChannels.clear();
|
|
2860
2491
|
ws?.disconnect();
|
|
2861
2492
|
},
|
|
2862
2493
|
setToken: transport.setToken,
|
|
@@ -2877,6 +2508,6 @@ channel: (name) => {
|
|
|
2877
2508
|
};
|
|
2878
2509
|
}
|
|
2879
2510
|
//#endregion
|
|
2880
|
-
export { QueryBuilder, RebaseApiError, RebaseClientError,
|
|
2511
|
+
export { QueryBuilder, RebaseApiError, RebaseClientError, RebaseWebSocketClient, and, cond, createBackups, createCookieStorage, createMemoryStorage, createRebaseClient, or };
|
|
2881
2512
|
|
|
2882
2513
|
//# sourceMappingURL=index.es.js.map
|