@rebasepro/client 0.9.1-canary.0fce67c → 0.9.1-canary.1d2d8b5

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.
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
package/dist/index.d.ts CHANGED
@@ -74,20 +74,11 @@ export type CreateRebaseClientResult<DB = Record<string, unknown>> = Omit<Rebase
74
74
  apiKeys: ReturnType<typeof createApiKeys>;
75
75
  functions: ReturnType<typeof createFunctionsClient>;
76
76
  ws?: RebaseWebSocketClient;
77
- /**
78
- * Release the realtime socket and its reconnect timer.
79
- *
80
- * An open socket keeps the Node event loop alive, so a script that does not
81
- * call this will not exit on its own. Safe when realtime was never started
82
- * (`realtime: false`), and safe to call twice.
83
- */
84
- close: () => void;
85
77
  storage: StorageSource;
86
78
  storageRegistry: StorageSourceRegistry;
87
79
  createStorageSource: (storageId: string) => StorageSource;
88
80
  fetchStorageSources: () => Promise<StorageSourceDefinition[]>;
89
81
  call: <T = unknown>(endpoint: string, payload?: unknown) => Promise<T>;
90
- collection: <M extends Record<string, unknown> = Record<string, unknown>>(slug: string) => CollectionClient<M>;
91
82
  data: TypedDataLayer<DB>;
92
83
  };
93
84
  export declare function createRebaseClient<DB = Record<string, unknown>>(options: CreateRebaseClientOptions): CreateRebaseClientResult<DB>;
package/dist/index.es.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { DEFAULT_STORAGE_SOURCE_KEY, EntityReference, EntityRelation, GeoPoint, PUBLIC_STORAGE_PREFIX, RebaseApiError, RebaseApiError as RebaseApiError$1, RebaseClientError, Vector, isPublicStoragePath } from "@rebasepro/types";
2
- import { COMPOSITE_ID_SEPARATOR, QueryBuilder, and, buildCompositeId, cond, or, serializeFilter, serializeLogicalCondition, serializeOrderBy } from "@rebasepro/common";
2
+ import { QueryBuilder, and, cond, or, serializeFilter, serializeLogicalCondition, serializeOrderBy } from "@rebasepro/common";
3
3
  import { toSnakeCase } from "@rebasepro/utils";
4
4
  //#region src/reviver.ts
5
5
  function rebaseReviver(_key, value) {
@@ -1035,7 +1035,7 @@ var SDKQueryBuilder = class {
1035
1035
  * Listen to realtime updates matching this query.
1036
1036
  */
1037
1037
  listen(onUpdate, onError) {
1038
- if (!this.collection.listen) throw new Error("Listen is only available when RebaseClient is configured with a websocketUrl, and not when it was created with realtime: false.");
1038
+ if (!this.collection.listen) throw new Error("Listen is only available when RebaseClient is configured with a websocketUrl.");
1039
1039
  return this.collection.listen(this.params, onUpdate, onError);
1040
1040
  }
1041
1041
  };
@@ -1070,17 +1070,6 @@ function createCollectionClient(transport, slug, ws) {
1070
1070
  body: JSON.stringify(body)
1071
1071
  });
1072
1072
  },
1073
- async createMany(data, options) {
1074
- if (!Array.isArray(data)) throw new TypeError("createMany expects an array of records.");
1075
- if (data.length === 0) return [];
1076
- return (await transport.request(`${basePath}/bulk`, {
1077
- method: "POST",
1078
- body: JSON.stringify({
1079
- rows: data,
1080
- ...options?.upsert ? { upsert: true } : {}
1081
- })
1082
- })).data || [];
1083
- },
1084
1073
  async update(id, data) {
1085
1074
  return await transport.request(`${basePath}/${encodeURIComponent(String(id))}`, {
1086
1075
  method: "PUT",
@@ -1397,11 +1386,9 @@ var ClientStorageSourceRegistry = class ClientStorageSourceRegistry {
1397
1386
  function extractMessageError(message) {
1398
1387
  const payload = message.payload;
1399
1388
  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
1389
  return {
1403
- errorMessage: typeof errorMessage === "string" ? errorMessage : errorMessage == null ? "Unknown error" : JSON.stringify(errorMessage),
1404
- errorCode
1390
+ errorMessage: typeof errPayload === "object" ? errPayload.message : payload?.message || (typeof errPayload === "string" ? errPayload : void 0) || message.error || "Unknown error",
1391
+ errorCode: typeof errPayload === "object" ? errPayload.code : payload?.code
1405
1392
  };
1406
1393
  }
1407
1394
  /**
@@ -1437,7 +1424,6 @@ var RebaseWebSocketClient = class {
1437
1424
  isConnected = false;
1438
1425
  messageQueue = [];
1439
1426
  requestTimeoutMs = 3e4;
1440
- subscriptionTimeoutMs = 3e4;
1441
1427
  reconnectTimeout = null;
1442
1428
  isAuthenticated = false;
1443
1429
  authPromise = null;
@@ -1543,7 +1529,6 @@ var RebaseWebSocketClient = class {
1543
1529
  this.emit(wasReconnect ? "reconnect" : "connect");
1544
1530
  this.processMessageQueue();
1545
1531
  if (wasReconnect) this.resubscribeAll();
1546
- this.armPendingSubscribeWatchdogs();
1547
1532
  };
1548
1533
  this.ws.onmessage = (event) => {
1549
1534
  try {
@@ -1558,7 +1543,6 @@ var RebaseWebSocketClient = class {
1558
1543
  this.isConnected = false;
1559
1544
  this.isAuthenticated = false;
1560
1545
  this.authPromise = null;
1561
- this.suspendSubscribeWatchdogs();
1562
1546
  this.emit("disconnect");
1563
1547
  for (const [reqId, request] of this.pendingRequests.entries()) {
1564
1548
  if (reqId.startsWith("auth_")) request.reject(/* @__PURE__ */ new Error("Connection closed during authentication"));
@@ -1590,7 +1574,6 @@ var RebaseWebSocketClient = class {
1590
1574
  attemptReconnect() {
1591
1575
  if (this.reconnectAttempts >= this.maxReconnectAttempts) {
1592
1576
  console.error("Max reconnection attempts reached");
1593
- this.failAllPendingSubscriptions(new RebaseApiError$1("Connection lost", { code: "CONNECTION_LOST" }));
1594
1577
  return;
1595
1578
  }
1596
1579
  this.reconnectAttempts++;
@@ -1645,18 +1628,29 @@ var RebaseWebSocketClient = class {
1645
1628
  subscription.backendSubscriptionId = newBackendId;
1646
1629
  backendKeyMap.delete(oldBackendId);
1647
1630
  backendKeyMap.set(newBackendId, subscriptionKey);
1648
- if (messageType === "subscribe_collection") this.sendCollectionSubscribe(subscriptionKey);
1649
- else this.sendEntitySubscribe(subscriptionKey);
1650
- return;
1631
+ this.sendMessage({
1632
+ type: messageType,
1633
+ payload: {
1634
+ ...subscription.props,
1635
+ subscriptionId: newBackendId
1636
+ }
1637
+ }).catch((error) => {
1638
+ console.error(`[WS] Failed to re-subscribe ${idPrefix} after auth refresh:`, subscriptionKey, error);
1639
+ subscription.callbacks.forEach((callback) => {
1640
+ if (callback.onError) callback.onError(error);
1641
+ });
1642
+ });
1643
+ } else {
1644
+ const { errorMessage, errorCode } = extractMessageError(message);
1645
+ const error = new RebaseApiError$1(errorMessage, { code: errorCode });
1646
+ subscription.callbacks.forEach((callback) => {
1647
+ if (callback.onError) callback.onError(error);
1648
+ });
1651
1649
  }
1652
- const { errorMessage, errorCode } = extractMessageError(message);
1653
- const error = new RebaseApiError$1(errorMessage, { code: errorCode });
1654
- if (messageType === "subscribe_collection") this.failCollectionSubscription(subscriptionKey, error);
1655
- else this.failEntitySubscription(subscriptionKey, error);
1656
1650
  }).catch((err) => {
1657
- const error = err instanceof Error ? err : new Error(String(err));
1658
- if (messageType === "subscribe_collection") this.failCollectionSubscription(subscriptionKey, error);
1659
- else this.failEntitySubscription(subscriptionKey, error);
1651
+ subscription.callbacks.forEach((callback) => {
1652
+ if (callback.onError) callback.onError(err);
1653
+ });
1660
1654
  });
1661
1655
  }
1662
1656
  handleWebSocketMessage(message) {
@@ -1691,15 +1685,10 @@ var RebaseWebSocketClient = class {
1691
1685
  const collectionSub = this.collectionSubscriptions.get(subscriptionKey);
1692
1686
  if (collectionSub) {
1693
1687
  const incomingRows = message.rows || [];
1694
- const updatePks = message.pks;
1695
- if (updatePks) collectionSub.pks = updatePks;
1696
- const rows = this.mergeRows(collectionSub.latestData, incomingRows, collectionSub.pks);
1688
+ const rows = this.mergeRows(collectionSub.latestData, incomingRows);
1697
1689
  collectionSub.latestData = rows;
1698
1690
  collectionSub.lastUpdated = Date.now();
1699
1691
  collectionSub.isInitialDataReceived = true;
1700
- if (collectionSub.subscribeTimeout) clearTimeout(collectionSub.subscribeTimeout);
1701
- collectionSub.subscribeTimeout = void 0;
1702
- collectionSub.subscribeInFlight = false;
1703
1692
  collectionSub.callbacks.forEach((callback) => {
1704
1693
  try {
1705
1694
  callback.onUpdate(rows);
@@ -1718,14 +1707,12 @@ var RebaseWebSocketClient = class {
1718
1707
  const collectionSub = this.collectionSubscriptions.get(subscriptionKey);
1719
1708
  if (collectionSub && collectionSub.isInitialDataReceived && collectionSub.latestData) {
1720
1709
  const patchWireEntity = message.row ?? null;
1721
- const patchMessage = message;
1722
- const patchEntityId = patchMessage.id;
1723
- if (patchMessage.pks) collectionSub.pks = patchMessage.pks;
1710
+ const patchEntityId = message.id;
1724
1711
  const patchRow = patchWireEntity ? patchWireEntity : null;
1725
1712
  let updated;
1726
- if (patchRow === null) updated = collectionSub.latestData.filter((e) => this.rowAddress(e, collectionSub.pks) !== String(patchEntityId));
1713
+ if (patchRow === null) updated = collectionSub.latestData.filter((e) => String(e.id) !== String(patchEntityId));
1727
1714
  else {
1728
- const idx = collectionSub.latestData.findIndex((e) => this.rowAddress(e, collectionSub.pks) === String(patchEntityId));
1715
+ const idx = collectionSub.latestData.findIndex((e) => String(e.id) === String(patchRow.id));
1729
1716
  if (idx >= 0) {
1730
1717
  updated = [...collectionSub.latestData];
1731
1718
  updated[idx] = patchRow;
@@ -1755,9 +1742,6 @@ var RebaseWebSocketClient = class {
1755
1742
  entitySub.latestData = row;
1756
1743
  entitySub.lastUpdated = Date.now();
1757
1744
  entitySub.isInitialDataReceived = true;
1758
- if (entitySub.subscribeTimeout) clearTimeout(entitySub.subscribeTimeout);
1759
- entitySub.subscribeTimeout = void 0;
1760
- entitySub.subscribeInFlight = false;
1761
1745
  entitySub.callbacks.forEach((callback) => {
1762
1746
  try {
1763
1747
  callback.onUpdate(row);
@@ -1779,9 +1763,6 @@ var RebaseWebSocketClient = class {
1779
1763
  this.resubscribeAfterAuthRefresh(message, collectionSub, collectionKey, "collection", this.backendToCollectionKey, "subscribe_collection");
1780
1764
  return;
1781
1765
  }
1782
- if (collectionSub.subscribeTimeout) clearTimeout(collectionSub.subscribeTimeout);
1783
- collectionSub.subscribeTimeout = void 0;
1784
- collectionSub.subscribeInFlight = false;
1785
1766
  const { errorMessage, errorCode } = extractMessageError(message);
1786
1767
  const error = new RebaseApiError$1(errorMessage, { code: errorCode });
1787
1768
  collectionSub.callbacks.forEach((callback) => {
@@ -1798,9 +1779,6 @@ var RebaseWebSocketClient = class {
1798
1779
  this.resubscribeAfterAuthRefresh(message, entitySub, entityKey, "row", this.backendToEntityKey, "subscribe_one");
1799
1780
  return;
1800
1781
  }
1801
- if (entitySub.subscribeTimeout) clearTimeout(entitySub.subscribeTimeout);
1802
- entitySub.subscribeTimeout = void 0;
1803
- entitySub.subscribeInFlight = false;
1804
1782
  const { errorMessage, errorCode } = extractMessageError(message);
1805
1783
  const error = new RebaseApiError$1(errorMessage, { code: errorCode });
1806
1784
  entitySub.callbacks.forEach((callback) => {
@@ -2084,39 +2062,17 @@ var RebaseWebSocketClient = class {
2084
2062
  return val;
2085
2063
  }
2086
2064
  /**
2087
- * The address of a row, for matching it against another copy of itself.
2088
- *
2089
- * A row is exactly its columns and carries no address, so it is derived
2090
- * from the key columns the server named — including the ordinary case where
2091
- * that key is `id`, which the server reports like any other.
2092
- *
2093
- * Undefined when there are no keys, which means the server could not
2094
- * resolve any: such rows genuinely cannot be recognised, and guessing at a
2095
- * column called `id` would be inventing an identity for a table that has
2096
- * none.
2097
- */
2098
- rowAddress(row, pks) {
2099
- if (!pks || pks.length === 0) return void 0;
2100
- const address = buildCompositeId(row, pks);
2101
- if (!address || address.split(COMPOSITE_ID_SEPARATOR).every((part) => part === "")) return void 0;
2102
- return address;
2103
- }
2104
- /**
2105
2065
  * Merge incoming rows with cached data, preserving cached references
2106
2066
  * for rows whose values haven't changed. This avoids unnecessary
2107
2067
  * React re-renders when the server refetches all rows but most
2108
2068
  * haven't actually changed.
2109
2069
  */
2110
- mergeRows(cached, incoming, pks) {
2070
+ mergeRows(cached, incoming) {
2111
2071
  if (!cached || cached.length === 0) return incoming;
2112
2072
  const cachedById = /* @__PURE__ */ new Map();
2113
- for (const row of cached) {
2114
- const address = this.rowAddress(row, pks);
2115
- if (address !== void 0) cachedById.set(address, row);
2116
- }
2073
+ for (const row of cached) cachedById.set(row.id, row);
2117
2074
  return incoming.map((incomingRow) => {
2118
- const address = this.rowAddress(incomingRow, pks);
2119
- const cachedRow = address === void 0 ? void 0 : cachedById.get(address);
2075
+ const cachedRow = cachedById.get(incomingRow.id);
2120
2076
  if (!cachedRow) return incomingRow;
2121
2077
  const normCached = this.normalizeForComparison(cachedRow);
2122
2078
  const normIncoming = this.normalizeForComparison(incomingRow);
@@ -2128,7 +2084,7 @@ var RebaseWebSocketClient = class {
2128
2084
  cached: normCached[key],
2129
2085
  incoming: normIncoming[key]
2130
2086
  };
2131
- console.debug(`[RebaseWS] Row ${address} refetch mismatch:\n`, JSON.stringify(mismatches, null, 2));
2087
+ console.debug(`[RebaseWS] Row ${incomingRow.id} refetch mismatch:\n`, JSON.stringify(mismatches, null, 2));
2132
2088
  }
2133
2089
  return incomingRow;
2134
2090
  });
@@ -2149,12 +2105,9 @@ var RebaseWebSocketClient = class {
2149
2105
  console.error("Error in collection subscription callback:", error);
2150
2106
  if (onError) onError(error instanceof Error ? error : new Error(String(error)));
2151
2107
  }
2152
- else if (!existingSubscription.subscribeInFlight) this.sendCollectionSubscribe(subscriptionKey);
2153
2108
  return () => {
2154
2109
  callbackMap.delete(callbackId);
2155
2110
  if (callbackMap.size === 0) {
2156
- if (this.collectionSubscriptions.get(subscriptionKey) !== existingSubscription) return;
2157
- if (existingSubscription.subscribeTimeout) clearTimeout(existingSubscription.subscribeTimeout);
2158
2111
  this.collectionSubscriptions.delete(subscriptionKey);
2159
2112
  this.backendToCollectionKey.delete(existingSubscription.backendSubscriptionId);
2160
2113
  if (this.isConnected && this.ws) this.sendMessage({
@@ -2176,14 +2129,21 @@ var RebaseWebSocketClient = class {
2176
2129
  props
2177
2130
  });
2178
2131
  this.backendToCollectionKey.set(backendSubscriptionId, subscriptionKey);
2179
- this.sendCollectionSubscribe(subscriptionKey);
2132
+ this.sendMessage({
2133
+ type: "subscribe_collection",
2134
+ payload: {
2135
+ ...props,
2136
+ subscriptionId: backendSubscriptionId
2137
+ }
2138
+ }).catch((error) => {
2139
+ if (onError) onError(error);
2140
+ });
2180
2141
  return () => {
2181
2142
  const subscription = this.collectionSubscriptions.get(subscriptionKey);
2182
2143
  if (subscription) {
2183
2144
  const callbacks = subscription.callbacks;
2184
2145
  callbacks.delete(callbackId);
2185
2146
  if (callbacks.size === 0) {
2186
- if (subscription.subscribeTimeout) clearTimeout(subscription.subscribeTimeout);
2187
2147
  this.collectionSubscriptions.delete(subscriptionKey);
2188
2148
  this.backendToCollectionKey.delete(subscription.backendSubscriptionId);
2189
2149
  if (this.isConnected && this.ws) this.sendMessage({
@@ -2210,12 +2170,9 @@ var RebaseWebSocketClient = class {
2210
2170
  console.error("Error in row subscription callback:", error);
2211
2171
  if (onError) onError(error instanceof Error ? error : new Error(String(error)));
2212
2172
  }
2213
- else if (!existingSubscription.subscribeInFlight) this.sendEntitySubscribe(subscriptionKey);
2214
2173
  return () => {
2215
2174
  callbackMap.delete(callbackId);
2216
2175
  if (callbackMap.size === 0) {
2217
- if (this.singleSubscriptions.get(subscriptionKey) !== existingSubscription) return;
2218
- if (existingSubscription.subscribeTimeout) clearTimeout(existingSubscription.subscribeTimeout);
2219
2176
  this.singleSubscriptions.delete(subscriptionKey);
2220
2177
  this.backendToEntityKey.delete(existingSubscription.backendSubscriptionId);
2221
2178
  if (this.isConnected && this.ws) this.sendMessage({
@@ -2237,7 +2194,15 @@ var RebaseWebSocketClient = class {
2237
2194
  props
2238
2195
  });
2239
2196
  this.backendToEntityKey.set(backendSubscriptionId, subscriptionKey);
2240
- this.sendEntitySubscribe(subscriptionKey);
2197
+ this.sendMessage({
2198
+ type: "subscribe_one",
2199
+ payload: {
2200
+ ...props,
2201
+ subscriptionId: backendSubscriptionId
2202
+ }
2203
+ }).catch((error) => {
2204
+ if (onError) onError(error);
2205
+ });
2241
2206
  return () => {
2242
2207
  const subscription = this.singleSubscriptions.get(subscriptionKey);
2243
2208
  if (subscription) {
@@ -2255,157 +2220,6 @@ var RebaseWebSocketClient = class {
2255
2220
  };
2256
2221
  }
2257
2222
  /**
2258
- * Send a `subscribe_collection` for an already-registered subscription and
2259
- * arm its watchdog.
2260
- *
2261
- * Every path that registers a collection subscription goes through here, so
2262
- * that a subscribe which never lands — a rejected send, or a server that
2263
- * never answers — always ends up in `failCollectionSubscription` rather than
2264
- * leaving the entry parked with `isInitialDataReceived === false` forever.
2265
- */
2266
- sendCollectionSubscribe(subscriptionKey) {
2267
- const subscription = this.collectionSubscriptions.get(subscriptionKey);
2268
- if (!subscription) return;
2269
- const backendSubscriptionId = subscription.backendSubscriptionId;
2270
- subscription.subscribeInFlight = true;
2271
- if (subscription.subscribeTimeout) clearTimeout(subscription.subscribeTimeout);
2272
- subscription.subscribeTimeout = void 0;
2273
- if (this.isConnected) this.sendCollectionSubscribeWatchdog(subscriptionKey);
2274
- this.sendMessage({
2275
- type: "subscribe_collection",
2276
- payload: {
2277
- ...subscription.props,
2278
- subscriptionId: backendSubscriptionId
2279
- }
2280
- }).catch((error) => {
2281
- const current = this.collectionSubscriptions.get(subscriptionKey);
2282
- if (!current || current.backendSubscriptionId !== backendSubscriptionId) return;
2283
- this.failCollectionSubscription(subscriptionKey, error instanceof Error ? error : new Error(String(error)));
2284
- });
2285
- }
2286
- /** The `listenOne` counterpart of {@link sendCollectionSubscribe}. */
2287
- sendEntitySubscribe(subscriptionKey) {
2288
- const subscription = this.singleSubscriptions.get(subscriptionKey);
2289
- if (!subscription) return;
2290
- const backendSubscriptionId = subscription.backendSubscriptionId;
2291
- subscription.subscribeInFlight = true;
2292
- if (subscription.subscribeTimeout) clearTimeout(subscription.subscribeTimeout);
2293
- subscription.subscribeTimeout = void 0;
2294
- if (this.isConnected) this.sendEntitySubscribeWatchdog(subscriptionKey);
2295
- this.sendMessage({
2296
- type: "subscribe_one",
2297
- payload: {
2298
- ...subscription.props,
2299
- subscriptionId: backendSubscriptionId
2300
- }
2301
- }).catch((error) => {
2302
- const current = this.singleSubscriptions.get(subscriptionKey);
2303
- if (!current || current.backendSubscriptionId !== backendSubscriptionId) return;
2304
- this.failEntitySubscription(subscriptionKey, error instanceof Error ? error : new Error(String(error)));
2305
- });
2306
- }
2307
- /**
2308
- * Report a subscribe failure to every listener and drop the registration.
2309
- *
2310
- * Dropping it is the point: the callbacks stay live (their components are
2311
- * still mounted and have been told), but the next `listenCollection` for
2312
- * these params finds no entry and issues a fresh subscribe instead of
2313
- * silently attaching to a dead one.
2314
- */
2315
- failCollectionSubscription(subscriptionKey, error) {
2316
- const subscription = this.collectionSubscriptions.get(subscriptionKey);
2317
- if (!subscription) return;
2318
- if (subscription.subscribeTimeout) clearTimeout(subscription.subscribeTimeout);
2319
- subscription.subscribeInFlight = false;
2320
- this.collectionSubscriptions.delete(subscriptionKey);
2321
- this.backendToCollectionKey.delete(subscription.backendSubscriptionId);
2322
- subscription.callbacks.forEach((callback) => {
2323
- if (callback.onError) try {
2324
- callback.onError(error);
2325
- } catch (callbackError) {
2326
- console.error("Error in collection subscription error callback:", callbackError);
2327
- }
2328
- });
2329
- }
2330
- /** The `listenOne` counterpart of {@link failCollectionSubscription}. */
2331
- failEntitySubscription(subscriptionKey, error) {
2332
- const subscription = this.singleSubscriptions.get(subscriptionKey);
2333
- if (!subscription) return;
2334
- if (subscription.subscribeTimeout) clearTimeout(subscription.subscribeTimeout);
2335
- subscription.subscribeInFlight = false;
2336
- this.singleSubscriptions.delete(subscriptionKey);
2337
- this.backendToEntityKey.delete(subscription.backendSubscriptionId);
2338
- subscription.callbacks.forEach((callback) => {
2339
- if (callback.onError) try {
2340
- callback.onError(error);
2341
- } catch (callbackError) {
2342
- console.error("Error in row subscription error callback:", callbackError);
2343
- }
2344
- });
2345
- }
2346
- /**
2347
- * Stop the watchdogs without failing anything — used when the socket drops,
2348
- * since the reconnect path re-subscribes everything anyway and a watchdog
2349
- * firing mid-reconnect would tear down healthy subscriptions.
2350
- */
2351
- suspendSubscribeWatchdogs() {
2352
- for (const sub of this.collectionSubscriptions.values()) {
2353
- if (sub.subscribeTimeout) clearTimeout(sub.subscribeTimeout);
2354
- sub.subscribeTimeout = void 0;
2355
- sub.subscribeInFlight = false;
2356
- }
2357
- for (const sub of this.singleSubscriptions.values()) {
2358
- if (sub.subscribeTimeout) clearTimeout(sub.subscribeTimeout);
2359
- sub.subscribeTimeout = void 0;
2360
- sub.subscribeInFlight = false;
2361
- }
2362
- }
2363
- /**
2364
- * Arm watchdogs for subscribes that were requested while offline and have
2365
- * just been flushed to the socket. Their timers were deliberately not set at
2366
- * request time, so without this they would have no timeout at all.
2367
- */
2368
- armPendingSubscribeWatchdogs() {
2369
- for (const [key, sub] of this.collectionSubscriptions.entries()) if (sub.subscribeInFlight && !sub.subscribeTimeout) this.sendCollectionSubscribeWatchdog(key);
2370
- for (const [key, sub] of this.singleSubscriptions.entries()) if (sub.subscribeInFlight && !sub.subscribeTimeout) this.sendEntitySubscribeWatchdog(key);
2371
- }
2372
- sendCollectionSubscribeWatchdog(subscriptionKey) {
2373
- const subscription = this.collectionSubscriptions.get(subscriptionKey);
2374
- if (!subscription) return;
2375
- const backendSubscriptionId = subscription.backendSubscriptionId;
2376
- subscription.subscribeTimeout = setTimeout(() => {
2377
- const current = this.collectionSubscriptions.get(subscriptionKey);
2378
- if (!current || current.backendSubscriptionId !== backendSubscriptionId) return;
2379
- if (!current.subscribeInFlight) return;
2380
- this.failCollectionSubscription(subscriptionKey, new RebaseApiError$1("Subscription timed out", { code: "SUBSCRIPTION_TIMEOUT" }));
2381
- }, this.subscriptionTimeoutMs);
2382
- }
2383
- sendEntitySubscribeWatchdog(subscriptionKey) {
2384
- const subscription = this.singleSubscriptions.get(subscriptionKey);
2385
- if (!subscription) return;
2386
- const backendSubscriptionId = subscription.backendSubscriptionId;
2387
- subscription.subscribeTimeout = setTimeout(() => {
2388
- const current = this.singleSubscriptions.get(subscriptionKey);
2389
- if (!current || current.backendSubscriptionId !== backendSubscriptionId) return;
2390
- if (!current.subscribeInFlight) return;
2391
- this.failEntitySubscription(subscriptionKey, new RebaseApiError$1("Subscription timed out", { code: "SUBSCRIPTION_TIMEOUT" }));
2392
- }, this.subscriptionTimeoutMs);
2393
- }
2394
- /**
2395
- * Fail every subscription that never received data. Called when reconnection
2396
- * is given up on, so views surface an error instead of spinning forever.
2397
- */
2398
- failAllPendingSubscriptions(error) {
2399
- for (const key of [...this.collectionSubscriptions.keys()]) {
2400
- const sub = this.collectionSubscriptions.get(key);
2401
- if (sub && !sub.isInitialDataReceived) this.failCollectionSubscription(key, error);
2402
- }
2403
- for (const key of [...this.singleSubscriptions.keys()]) {
2404
- const sub = this.singleSubscriptions.get(key);
2405
- if (sub && !sub.isInitialDataReceived) this.failEntitySubscription(key, error);
2406
- }
2407
- }
2408
- /**
2409
2223
  * Re-send all active subscriptions to the backend after a reconnect.
2410
2224
  * The server wipes subscription state when a client disconnects, so
2411
2225
  * we need to re-register everything to resume receiving updates.
@@ -2418,7 +2232,15 @@ var RebaseWebSocketClient = class {
2418
2232
  sub.backendSubscriptionId = newBackendId;
2419
2233
  this.backendToCollectionKey.delete(oldBackendId);
2420
2234
  this.backendToCollectionKey.set(newBackendId, key);
2421
- this.sendCollectionSubscribe(key);
2235
+ this.sendMessage({
2236
+ type: "subscribe_collection",
2237
+ payload: {
2238
+ ...sub.props,
2239
+ subscriptionId: newBackendId
2240
+ }
2241
+ }).catch((error) => {
2242
+ console.error("[WS] Failed to re-subscribe collection:", key, error);
2243
+ });
2422
2244
  }
2423
2245
  for (const [key, sub] of this.singleSubscriptions.entries()) {
2424
2246
  const oldBackendId = sub.backendSubscriptionId;
@@ -2426,7 +2248,15 @@ var RebaseWebSocketClient = class {
2426
2248
  sub.backendSubscriptionId = newBackendId;
2427
2249
  this.backendToEntityKey.delete(oldBackendId);
2428
2250
  this.backendToEntityKey.set(newBackendId, key);
2429
- this.sendEntitySubscribe(key);
2251
+ this.sendMessage({
2252
+ type: "subscribe_one",
2253
+ payload: {
2254
+ ...sub.props,
2255
+ subscriptionId: newBackendId
2256
+ }
2257
+ }).catch((error) => {
2258
+ console.error("[WS] Failed to re-subscribe row:", key, error);
2259
+ });
2430
2260
  }
2431
2261
  }
2432
2262
  createCollectionSubscriptionKey(props) {
@@ -2501,7 +2331,7 @@ function createRebaseClient(options) {
2501
2331
  });
2502
2332
  return storageSourcesPromise;
2503
2333
  };
2504
- const resolvedWsUrl = options.realtime !== false ? options.websocketUrl ?? deriveWebSocketUrl(options.baseUrl) : void 0;
2334
+ const resolvedWsUrl = options.websocketUrl ?? deriveWebSocketUrl(options.baseUrl);
2505
2335
  let ws;
2506
2336
  if (resolvedWsUrl) {
2507
2337
  ws = new RebaseWebSocketClient({
@@ -2613,16 +2443,6 @@ function createRebaseClient(options) {
2613
2443
  createStorageSource,
2614
2444
  fetchStorageSources,
2615
2445
  ws,
2616
- /**
2617
- * Release the realtime socket and its reconnect timer.
2618
- *
2619
- * Until this returns, the open socket keeps the Node event loop alive
2620
- * and the process will not exit on its own. Safe to call when realtime
2621
- * was never started, and safe to call twice.
2622
- */
2623
- close: () => {
2624
- ws?.disconnect();
2625
- },
2626
2446
  setToken: transport.setToken,
2627
2447
  setAuthTokenGetter: transport.setAuthTokenGetter,
2628
2448
  setOnUnauthorized: transport.setOnUnauthorized,