@rebasepro/client 0.3.0 → 0.4.0

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/admin.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Transport } from "./transport";
1
+ import type { Transport } from "./transport";
2
2
  export interface AdminUser {
3
3
  uid: string;
4
4
  email: string;
@@ -9,12 +9,6 @@ export interface AdminUser {
9
9
  createdAt: string;
10
10
  updatedAt: string;
11
11
  }
12
- export interface RebaseRole {
13
- id: string;
14
- name: string;
15
- isAdmin: boolean;
16
- defaultPermissions: Record<string, unknown> | null;
17
- }
18
12
  export interface CreateAdminOptions {
19
13
  adminPath?: string;
20
14
  }
@@ -56,30 +50,6 @@ export declare function createAdmin(transport: Transport, options?: CreateAdminO
56
50
  deleteUser: (userId: string) => Promise<{
57
51
  success: boolean;
58
52
  }>;
59
- listRoles: () => Promise<{
60
- roles: RebaseRole[];
61
- }>;
62
- getRole: (roleId: string) => Promise<{
63
- role: RebaseRole;
64
- }>;
65
- createRole: (data: {
66
- id: string;
67
- name: string;
68
- isAdmin?: boolean;
69
- defaultPermissions?: Record<string, unknown>;
70
- }) => Promise<{
71
- role: RebaseRole;
72
- }>;
73
- updateRole: (roleId: string, data: {
74
- name?: string;
75
- isAdmin?: boolean;
76
- defaultPermissions?: Record<string, unknown>;
77
- }) => Promise<{
78
- role: RebaseRole;
79
- }>;
80
- deleteRole: (roleId: string) => Promise<{
81
- success: boolean;
82
- }>;
83
53
  bootstrap: () => Promise<{
84
54
  success: boolean;
85
55
  message: string;
package/dist/auth.d.ts CHANGED
@@ -168,3 +168,11 @@ export declare function createAuth(transport: Transport, options?: CreateAuthOpt
168
168
  getSession: () => RebaseSession | null;
169
169
  onAuthStateChange: (callback: (event: AuthChangeEvent, session: RebaseSession | null) => void) => () => boolean;
170
170
  };
171
+ export interface CookieStorageOptions {
172
+ path?: string;
173
+ domain?: string;
174
+ secure?: boolean;
175
+ sameSite?: "Lax" | "Strict" | "None";
176
+ maxAge?: number;
177
+ }
178
+ export declare function createCookieStorage(options?: CookieStorageOptions): AuthStorage;
@@ -1,6 +1,6 @@
1
- import { Transport, FindParams } from "./transport";
1
+ import { FindParams, Transport } from "./transport";
2
2
  import { RebaseWebSocketClient } from "./websocket";
3
- import { CollectionAccessor, FilterOperator } from "@rebasepro/types";
3
+ import { CollectionAccessor, FilterOperator, LogicalCondition, WhereValue } from "@rebasepro/types";
4
4
  import { QueryBuilder } from "./query_builder";
5
5
  /**
6
6
  * CollectionClient extends `CollectionAccessor` from `@rebasepro/types` so that
@@ -9,7 +9,8 @@ import { QueryBuilder } from "./query_builder";
9
9
  * Additionally it exposes fluent query builder methods like `.where()`, `.orderBy()`.
10
10
  */
11
11
  export interface CollectionClient<M extends Record<string, unknown> = Record<string, unknown>> extends CollectionAccessor<M> {
12
- where(column: keyof M & string, operator: FilterOperator, value: unknown): QueryBuilder<M>;
12
+ where<K extends keyof M & string>(column: K, operator: FilterOperator, value: WhereValue<M[K]>): QueryBuilder<M>;
13
+ where(logicalCondition: LogicalCondition): QueryBuilder<M>;
13
14
  orderBy(column: keyof M & string, ascending?: "asc" | "desc"): QueryBuilder<M>;
14
15
  limit(count: number): QueryBuilder<M>;
15
16
  offset(count: number): QueryBuilder<M>;
package/dist/index.es.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Vector, GeoPoint, EntityRelation, EntityReference } from "@rebasepro/types";
2
2
  import { QueryBuilder } from "@rebasepro/common";
3
- import { QueryBuilder as QueryBuilder2 } from "@rebasepro/common";
3
+ import { QueryBuilder as QueryBuilder2, and, cond, or } from "@rebasepro/common";
4
4
  import { toSnakeCase } from "@rebasepro/utils";
5
5
  function rebaseReviver(_key, value) {
6
6
  if (value && typeof value === "object" && "__type" in value) {
@@ -66,15 +66,33 @@ function normalizeWhereValue(value) {
66
66
  if (value === null) return "eq.null";
67
67
  if (typeof value === "boolean") return `eq.${value}`;
68
68
  if (typeof value === "number") return String(value);
69
- if (Array.isArray(value) && value.length === 2) {
70
- const [rawOp, val] = value;
71
- const op = OP_MAP[rawOp] ?? rawOp;
72
- if (val === null) return `${op}.null`;
73
- if (Array.isArray(val)) return `${op}.(${val.join(",")})`;
74
- return `${op}.${val}`;
69
+ if (Array.isArray(value)) {
70
+ const conditions = Array.isArray(value[0]) ? value : [value];
71
+ const [rawOp, val] = conditions[0] || [];
72
+ if (rawOp) {
73
+ const op = OP_MAP[rawOp] ?? rawOp;
74
+ if (val === null) return `${op}.null`;
75
+ if (Array.isArray(val)) return `${op}.(${val.join(",")})`;
76
+ return `${op}.${val}`;
77
+ }
75
78
  }
76
79
  return String(value);
77
80
  }
81
+ function serializeLogicalCondition(cond2) {
82
+ if ("type" in cond2) {
83
+ const sub = cond2.conditions.map(serializeLogicalCondition).join(",");
84
+ return `${cond2.type}(${sub})`;
85
+ } else {
86
+ const op = OP_MAP[cond2.operator] ?? cond2.operator;
87
+ let formattedValue = cond2.value;
88
+ if (Array.isArray(cond2.value)) {
89
+ formattedValue = `(${cond2.value.join(",")})`;
90
+ } else if (cond2.value === null) {
91
+ formattedValue = "null";
92
+ }
93
+ return `${cond2.column}.${op}.${formattedValue}`;
94
+ }
95
+ }
78
96
  function buildQueryString(params) {
79
97
  if (!params) return "";
80
98
  const parts = [];
@@ -90,10 +108,22 @@ function buildQueryString(params) {
90
108
  if (params.include && params.include.length > 0) {
91
109
  parts.push(`include=${encodeURIComponent(params.include.join(","))}`);
92
110
  }
111
+ if (params.logical) {
112
+ const root = params.logical;
113
+ const serialized = root.conditions.map(serializeLogicalCondition).join(",");
114
+ parts.push(`${root.type}=${encodeURIComponent(`(${serialized})`)}`);
115
+ }
93
116
  if (params.where) {
94
117
  for (const [field, value] of Object.entries(params.where)) {
95
- const normalized = normalizeWhereValue(value);
96
- parts.push(`${encodeURIComponent(field)}=${encodeURIComponent(normalized)}`);
118
+ if (Array.isArray(value) && value.length > 0 && Array.isArray(value[0])) {
119
+ for (const subVal of value) {
120
+ const normalized = normalizeWhereValue(subVal);
121
+ parts.push(`${encodeURIComponent(field)}=${encodeURIComponent(normalized)}`);
122
+ }
123
+ } else {
124
+ const normalized = normalizeWhereValue(value);
125
+ parts.push(`${encodeURIComponent(field)}=${encodeURIComponent(normalized)}`);
126
+ }
97
127
  }
98
128
  }
99
129
  return parts.length > 0 ? "?" + parts.join("&") : "";
@@ -703,6 +733,58 @@ function createAuth(transport, options) {
703
733
  onAuthStateChange
704
734
  };
705
735
  }
736
+ function createCookieStorage(options = {}) {
737
+ const defaultOptions = {
738
+ path: "/",
739
+ sameSite: "Lax",
740
+ ...options
741
+ };
742
+ return {
743
+ getItem(key) {
744
+ if (typeof document === "undefined") return null;
745
+ const nameEQ = encodeURIComponent(key) + "=";
746
+ const ca = document.cookie.split(";");
747
+ for (let i = 0; i < ca.length; i++) {
748
+ let c = ca[i];
749
+ while (c.charAt(0) === " ") c = c.substring(1, c.length);
750
+ if (c.indexOf(nameEQ) === 0) {
751
+ return decodeURIComponent(c.substring(nameEQ.length, c.length));
752
+ }
753
+ }
754
+ return null;
755
+ },
756
+ setItem(key, value) {
757
+ if (typeof document === "undefined") return;
758
+ let cookieStr = `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
759
+ if (defaultOptions.path) {
760
+ cookieStr += `; path=${defaultOptions.path}`;
761
+ }
762
+ if (defaultOptions.domain) {
763
+ cookieStr += `; domain=${defaultOptions.domain}`;
764
+ }
765
+ if (defaultOptions.maxAge !== void 0) {
766
+ cookieStr += `; max-age=${defaultOptions.maxAge}`;
767
+ } else {
768
+ cookieStr += `; max-age=${365 * 24 * 60 * 60}`;
769
+ }
770
+ if (defaultOptions.secure) {
771
+ cookieStr += "; secure";
772
+ }
773
+ if (defaultOptions.sameSite) {
774
+ cookieStr += `; samesite=${defaultOptions.sameSite}`;
775
+ }
776
+ document.cookie = cookieStr;
777
+ },
778
+ removeItem(key) {
779
+ if (typeof document === "undefined") return;
780
+ let cookieStr = `${encodeURIComponent(key)}=; path=${defaultOptions.path || "/"}; max-age=-1`;
781
+ if (defaultOptions.domain) {
782
+ cookieStr += `; domain=${defaultOptions.domain}`;
783
+ }
784
+ document.cookie = cookieStr;
785
+ }
786
+ };
787
+ }
706
788
  function createAdmin(transport, options) {
707
789
  const opts = options || {};
708
790
  const adminPath = opts.adminPath || "/admin";
@@ -742,29 +824,6 @@ function createAdmin(transport, options) {
742
824
  method: "DELETE"
743
825
  });
744
826
  }
745
- async function listRoles() {
746
- return transport.request(adminPath + "/roles", { method: "GET" });
747
- }
748
- async function getRole(roleId) {
749
- return transport.request(adminPath + "/roles/" + encodeURIComponent(roleId), { method: "GET" });
750
- }
751
- async function createRole(data) {
752
- return transport.request(adminPath + "/roles", {
753
- method: "POST",
754
- body: JSON.stringify(data)
755
- });
756
- }
757
- async function updateRole(roleId, data) {
758
- return transport.request(adminPath + "/roles/" + encodeURIComponent(roleId), {
759
- method: "PUT",
760
- body: JSON.stringify(data)
761
- });
762
- }
763
- async function deleteRole(roleId) {
764
- return transport.request(adminPath + "/roles/" + encodeURIComponent(roleId), {
765
- method: "DELETE"
766
- });
767
- }
768
827
  async function bootstrap() {
769
828
  return transport.request(adminPath + "/bootstrap", {
770
829
  method: "POST"
@@ -777,11 +836,6 @@ function createAdmin(transport, options) {
777
836
  createUser,
778
837
  updateUser,
779
838
  deleteUser,
780
- listRoles,
781
- getRole,
782
- createRole,
783
- updateRole,
784
- deleteRole,
785
839
  bootstrap
786
840
  };
787
841
  }
@@ -831,44 +885,34 @@ function createCron(transport, options) {
831
885
  function parseWhereFilter(where) {
832
886
  if (!where) return void 0;
833
887
  const filters = {};
834
- for (const [key, rawValue] of Object.entries(where)) {
835
- if (rawValue === null) {
836
- filters[key] = ["==", null];
837
- continue;
838
- }
839
- if (typeof rawValue === "boolean") {
840
- filters[key] = ["==", rawValue];
841
- continue;
842
- }
843
- if (typeof rawValue === "number") {
844
- filters[key] = ["==", rawValue];
845
- continue;
846
- }
847
- if (Array.isArray(rawValue) && rawValue.length === 2) {
888
+ const OP_TO_FILTER = {
889
+ "eq": "==",
890
+ "neq": "!=",
891
+ "gt": ">",
892
+ "gte": ">=",
893
+ "lt": "<",
894
+ "lte": "<=",
895
+ "==": "==",
896
+ "!=": "!=",
897
+ ">": ">",
898
+ ">=": ">=",
899
+ "<": "<",
900
+ "<=": "<=",
901
+ "in": "in",
902
+ "nin": "not-in",
903
+ "not-in": "not-in",
904
+ "cs": "array-contains",
905
+ "csa": "array-contains-any",
906
+ "array-contains": "array-contains",
907
+ "array-contains-any": "array-contains-any"
908
+ };
909
+ const parseSingle = (rawValue, fieldKey) => {
910
+ if (rawValue === null) return ["==", null];
911
+ if (typeof rawValue === "boolean") return ["==", rawValue];
912
+ if (typeof rawValue === "number") return ["==", rawValue];
913
+ if (Array.isArray(rawValue) && rawValue.length === 2 && typeof rawValue[0] === "string") {
848
914
  const [rawOp, val] = rawValue;
849
- const OP_TO_FILTER = {
850
- "eq": "==",
851
- "neq": "!=",
852
- "gt": ">",
853
- "gte": ">=",
854
- "lt": "<",
855
- "lte": "<=",
856
- "==": "==",
857
- "!=": "!=",
858
- ">": ">",
859
- ">=": ">=",
860
- "<": "<",
861
- "<=": "<=",
862
- "in": "in",
863
- "nin": "not-in",
864
- "not-in": "not-in",
865
- "cs": "array-contains",
866
- "csa": "array-contains-any",
867
- "array-contains": "array-contains",
868
- "array-contains-any": "array-contains-any"
869
- };
870
- filters[key] = [OP_TO_FILTER[rawOp] ?? "==", val];
871
- continue;
915
+ return [OP_TO_FILTER[rawOp] ?? "==", val];
872
916
  }
873
917
  const value = String(rawValue);
874
918
  const dotIndex = value.indexOf(".");
@@ -918,10 +962,17 @@ function parseWhereFilter(where) {
918
962
  if (val === "true") val = true;
919
963
  else if (val === "false") val = false;
920
964
  else if (val === "null") val = null;
921
- else if (typeof val === "string" && /^[0-9]+(\.[0-9]+)?$/.test(val) && key !== "id" && !key.endsWith("_id")) val = Number(val);
922
- filters[key] = [op, val];
965
+ else if (typeof val === "string" && /^[0-9]+(\.[0-9]+)?$/.test(val) && fieldKey !== "id" && !fieldKey.endsWith("_id")) val = Number(val);
966
+ return [op, val];
923
967
  } else {
924
- filters[key] = ["==", value];
968
+ return ["==", value];
969
+ }
970
+ };
971
+ for (const [key, rawValue] of Object.entries(where)) {
972
+ if (Array.isArray(rawValue) && rawValue.length > 0 && Array.isArray(rawValue[0])) {
973
+ filters[key] = rawValue.map((r) => parseSingle(r, key));
974
+ } else {
975
+ filters[key] = parseSingle(rawValue, key);
925
976
  }
926
977
  }
927
978
  return filters;
@@ -945,9 +996,16 @@ function createCollectionClient(transport, slug, ws) {
945
996
  };
946
997
  },
947
998
  async findById(id) {
948
- const raw = await transport.request(`${basePath}/${encodeURIComponent(String(id))}`, { method: "GET" });
949
- if (!raw) return void 0;
950
- return rowToEntity(raw, slug);
999
+ try {
1000
+ const raw = await transport.request(`${basePath}/${encodeURIComponent(String(id))}`, { method: "GET" });
1001
+ if (!raw) return void 0;
1002
+ return rowToEntity(raw, slug);
1003
+ } catch (err) {
1004
+ if (err instanceof RebaseApiError && err.status === 404) {
1005
+ return void 0;
1006
+ }
1007
+ throw err;
1008
+ }
951
1009
  },
952
1010
  async create(data, id) {
953
1011
  const body = { ...data };
@@ -973,14 +1031,22 @@ function createCollectionClient(transport, slug, ws) {
973
1031
  });
974
1032
  },
975
1033
  async count(params) {
976
- const countParams = { ...params, limit: void 0, offset: void 0 };
1034
+ const countParams = {
1035
+ ...params,
1036
+ limit: void 0,
1037
+ offset: void 0
1038
+ };
977
1039
  const qs = buildQueryString(countParams);
978
1040
  const raw = await transport.request(basePath + "/count" + qs, { method: "GET" });
979
1041
  return raw.count ?? 0;
980
1042
  },
981
1043
  // Fluent builder instantiation
982
- where(column, operator, value) {
983
- return new QueryBuilder(client).where(column, operator, value);
1044
+ where(columnOrCondition, operator, value) {
1045
+ const builder = new QueryBuilder(client);
1046
+ if (typeof columnOrCondition === "object") {
1047
+ return builder.where(columnOrCondition);
1048
+ }
1049
+ return builder.where(columnOrCondition, operator, value);
984
1050
  },
985
1051
  orderBy(column, ascending) {
986
1052
  return new QueryBuilder(client).orderBy(column, ascending);
@@ -1117,9 +1183,12 @@ class RebaseWebSocketClient {
1117
1183
  isAuthenticated = false;
1118
1184
  authPromise = null;
1119
1185
  WebSocketConstructor;
1186
+ onUnauthorized;
1187
+ refreshInProgress = null;
1120
1188
  constructor(config) {
1121
1189
  this.websocketUrl = config.websocketUrl;
1122
1190
  this.getAuthToken = config.getAuthToken;
1191
+ this.onUnauthorized = config.onUnauthorized;
1123
1192
  this.WebSocketConstructor = config.WebSocket || (typeof WebSocket !== "undefined" ? WebSocket : void 0);
1124
1193
  if (!this.WebSocketConstructor) {
1125
1194
  console.warn("WebSocket is not defined in this environment. Realtime subscriptions will not work unless you provide a WebSocket implementation in the config.");
@@ -1284,6 +1353,42 @@ class RebaseWebSocketClient {
1284
1353
  this.initWebSocket();
1285
1354
  }, delay);
1286
1355
  }
1356
+ isAuthError(message) {
1357
+ if (message.type === "AUTH_ERROR") return true;
1358
+ const { errorMessage, errorCode } = extractMessageError(message);
1359
+ if (errorCode === "UNAUTHORIZED" || errorCode === "JWT_EXPIRED" || errorCode === "AUTH_ERROR") return true;
1360
+ const lowerMessage = errorMessage.toLowerCase();
1361
+ return lowerMessage.includes("unauthorized") || lowerMessage.includes("token expired") || lowerMessage.includes("token is expired") || lowerMessage.includes("invalid token") || lowerMessage.includes("session expired") || lowerMessage.includes("auth error");
1362
+ }
1363
+ async handleAuthFailure() {
1364
+ if (this.refreshInProgress) {
1365
+ return this.refreshInProgress;
1366
+ }
1367
+ this.refreshInProgress = (async () => {
1368
+ this.isAuthenticated = false;
1369
+ this.authPromise = null;
1370
+ if (this.onUnauthorized) {
1371
+ try {
1372
+ const refreshed = await this.onUnauthorized();
1373
+ if (refreshed && this.getAuthToken) {
1374
+ const token = await this.getAuthToken();
1375
+ if (token) {
1376
+ await this.authenticate(token);
1377
+ return true;
1378
+ }
1379
+ }
1380
+ } catch (error) {
1381
+ console.error("WebSocket auth refresh failed:", error);
1382
+ }
1383
+ }
1384
+ return false;
1385
+ })();
1386
+ try {
1387
+ return await this.refreshInProgress;
1388
+ } finally {
1389
+ this.refreshInProgress = null;
1390
+ }
1391
+ }
1287
1392
  handleWebSocketMessage(message) {
1288
1393
  const {
1289
1394
  type,
@@ -1291,16 +1396,28 @@ class RebaseWebSocketClient {
1291
1396
  subscriptionId
1292
1397
  } = message;
1293
1398
  if (requestId && this.pendingRequests.has(requestId)) {
1294
- const {
1295
- resolve,
1296
- reject
1297
- } = this.pendingRequests.get(requestId);
1298
- this.pendingRequests.delete(requestId);
1399
+ const pendingReq = this.pendingRequests.get(requestId);
1299
1400
  if (type === "ERROR" || type === "AUTH_ERROR" || message.error) {
1300
- const { errorMessage, errorCode } = extractMessageError(message);
1301
- reject(new ApiError(errorMessage, errorMessage, errorCode));
1401
+ if (this.isAuthError(message)) {
1402
+ this.pendingRequests.delete(requestId);
1403
+ this.handleAuthFailure().then((refreshed) => {
1404
+ if (refreshed && pendingReq.message) {
1405
+ this.doSendMessage(pendingReq.message, pendingReq.resolve, pendingReq.reject).catch(pendingReq.reject);
1406
+ } else {
1407
+ const { errorMessage, errorCode } = extractMessageError(message);
1408
+ pendingReq.reject(new ApiError(errorMessage, errorMessage, errorCode));
1409
+ }
1410
+ }).catch((err) => {
1411
+ pendingReq.reject(err);
1412
+ });
1413
+ } else {
1414
+ this.pendingRequests.delete(requestId);
1415
+ const { errorMessage, errorCode } = extractMessageError(message);
1416
+ pendingReq.reject(new ApiError(errorMessage, errorMessage, errorCode));
1417
+ }
1302
1418
  } else {
1303
- resolve(message.payload || message);
1419
+ this.pendingRequests.delete(requestId);
1420
+ pendingReq.resolve(message.payload || message);
1304
1421
  }
1305
1422
  return;
1306
1423
  }
@@ -1391,6 +1508,40 @@ class RebaseWebSocketClient {
1391
1508
  if (collectionKey) {
1392
1509
  const collectionSub = this.collectionSubscriptions.get(collectionKey);
1393
1510
  if (collectionSub) {
1511
+ if (this.isAuthError(message)) {
1512
+ this.handleAuthFailure().then((refreshed) => {
1513
+ if (refreshed) {
1514
+ const oldBackendId = collectionSub.backendSubscriptionId;
1515
+ const newBackendId = `collection_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
1516
+ collectionSub.backendSubscriptionId = newBackendId;
1517
+ this.backendToCollectionKey.delete(oldBackendId);
1518
+ this.backendToCollectionKey.set(newBackendId, collectionKey);
1519
+ this.sendMessage({
1520
+ type: "subscribe_collection",
1521
+ payload: {
1522
+ ...collectionSub.props,
1523
+ subscriptionId: newBackendId
1524
+ }
1525
+ }).catch((error2) => {
1526
+ console.error("[WS] Failed to re-subscribe collection after auth refresh:", collectionKey, error2);
1527
+ collectionSub.callbacks.forEach((callback) => {
1528
+ if (callback.onError) callback.onError(error2);
1529
+ });
1530
+ });
1531
+ } else {
1532
+ const { errorMessage: errorMessage2, errorCode: errorCode2 } = extractMessageError(message);
1533
+ const error2 = new ApiError(errorMessage2, errorMessage2, errorCode2);
1534
+ collectionSub.callbacks.forEach((callback) => {
1535
+ if (callback.onError) callback.onError(error2);
1536
+ });
1537
+ }
1538
+ }).catch((err) => {
1539
+ collectionSub.callbacks.forEach((callback) => {
1540
+ if (callback.onError) callback.onError(err);
1541
+ });
1542
+ });
1543
+ return;
1544
+ }
1394
1545
  const { errorMessage, errorCode } = extractMessageError(message);
1395
1546
  const error = new ApiError(errorMessage, errorMessage, errorCode);
1396
1547
  collectionSub.callbacks.forEach((callback) => {
@@ -1405,6 +1556,40 @@ class RebaseWebSocketClient {
1405
1556
  if (entityKey) {
1406
1557
  const entitySub = this.entitySubscriptions.get(entityKey);
1407
1558
  if (entitySub) {
1559
+ if (this.isAuthError(message)) {
1560
+ this.handleAuthFailure().then((refreshed) => {
1561
+ if (refreshed) {
1562
+ const oldBackendId = entitySub.backendSubscriptionId;
1563
+ const newBackendId = `entity_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
1564
+ entitySub.backendSubscriptionId = newBackendId;
1565
+ this.backendToEntityKey.delete(oldBackendId);
1566
+ this.backendToEntityKey.set(newBackendId, entityKey);
1567
+ this.sendMessage({
1568
+ type: "subscribe_entity",
1569
+ payload: {
1570
+ ...entitySub.props,
1571
+ subscriptionId: newBackendId
1572
+ }
1573
+ }).catch((error2) => {
1574
+ console.error("[WS] Failed to re-subscribe entity after auth refresh:", entityKey, error2);
1575
+ entitySub.callbacks.forEach((callback) => {
1576
+ if (callback.onError) callback.onError(error2);
1577
+ });
1578
+ });
1579
+ } else {
1580
+ const { errorMessage: errorMessage2, errorCode: errorCode2 } = extractMessageError(message);
1581
+ const error2 = new ApiError(errorMessage2, errorMessage2, errorCode2);
1582
+ entitySub.callbacks.forEach((callback) => {
1583
+ if (callback.onError) callback.onError(error2);
1584
+ });
1585
+ }
1586
+ }).catch((err) => {
1587
+ entitySub.callbacks.forEach((callback) => {
1588
+ if (callback.onError) callback.onError(err);
1589
+ });
1590
+ });
1591
+ return;
1592
+ }
1408
1593
  const { errorMessage, errorCode } = extractMessageError(message);
1409
1594
  const error = new ApiError(errorMessage, errorMessage, errorCode);
1410
1595
  entitySub.callbacks.forEach((callback) => {
@@ -2085,14 +2270,27 @@ function createStorage(transport) {
2085
2270
  };
2086
2271
  }
2087
2272
  function deriveWebSocketUrl(baseUrl) {
2088
- if (!baseUrl) {
2089
- if (typeof window !== "undefined") {
2090
- const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
2091
- return `${protocol}//${window.location.host}`;
2273
+ if (typeof window !== "undefined") {
2274
+ let absoluteUrl = "";
2275
+ if (!baseUrl) {
2276
+ absoluteUrl = window.location.origin;
2277
+ } else if (/^https?:\/\//i.test(baseUrl) || /^wss?:\/\//i.test(baseUrl)) {
2278
+ absoluteUrl = baseUrl;
2279
+ } else {
2280
+ try {
2281
+ absoluteUrl = new URL(baseUrl, window.location.href).origin;
2282
+ } catch {
2283
+ absoluteUrl = window.location.origin;
2284
+ }
2092
2285
  }
2286
+ const protocol = absoluteUrl.startsWith("https:") || absoluteUrl.startsWith("wss:") ? "wss:" : "ws:";
2287
+ return absoluteUrl.replace(/^https?:\/\//i, `${protocol}//`).replace(/^wss?:\/\//i, `${protocol}//`).replace(/\/$/, "");
2288
+ }
2289
+ if (!baseUrl) return "";
2290
+ if (!/^https?:\/\//i.test(baseUrl) && !/^wss?:\/\//i.test(baseUrl)) {
2093
2291
  return "";
2094
2292
  }
2095
- return baseUrl.replace(/^https:\/\//, "wss://").replace(/^http:\/\//, "ws://").replace(/\/$/, "");
2293
+ return baseUrl.replace(/^https?:\/\//i, (match) => match.toLowerCase() === "https://" ? "wss://" : "ws://").replace(/\/$/, "");
2096
2294
  }
2097
2295
  function createRebaseClient(options) {
2098
2296
  const transport = createTransport(options);
@@ -2104,12 +2302,27 @@ function createRebaseClient(options) {
2104
2302
  const resolvedWsUrl = options.websocketUrl ?? deriveWebSocketUrl(options.baseUrl);
2105
2303
  let ws;
2106
2304
  if (resolvedWsUrl) {
2305
+ const wsOnUnauthorized = options.onUnauthorized || (async () => {
2306
+ try {
2307
+ await auth.refreshSession();
2308
+ return true;
2309
+ } catch (e) {
2310
+ return false;
2311
+ }
2312
+ });
2107
2313
  ws = new RebaseWebSocketClient({
2108
2314
  websocketUrl: resolvedWsUrl,
2109
2315
  getAuthToken: async () => {
2110
- const session = await auth.getSession();
2316
+ let session = auth.getSession();
2317
+ if (session && session.expiresAt <= Date.now() + 1e4) {
2318
+ try {
2319
+ session = await auth.refreshSession();
2320
+ } catch (e) {
2321
+ }
2322
+ }
2111
2323
  return session?.accessToken || options.token || "";
2112
- }
2324
+ },
2325
+ onUnauthorized: wsOnUnauthorized
2113
2326
  });
2114
2327
  auth.onAuthStateChange((event, session) => {
2115
2328
  if (!ws) return;
@@ -2184,16 +2397,20 @@ export {
2184
2397
  QueryBuilder2 as QueryBuilder,
2185
2398
  RebaseApiError,
2186
2399
  RebaseWebSocketClient,
2400
+ and,
2187
2401
  buildQueryString,
2402
+ cond,
2188
2403
  createAdmin,
2189
2404
  createAuth,
2190
2405
  createCollectionClient,
2406
+ createCookieStorage,
2191
2407
  createCron,
2192
2408
  createFunctionsClient,
2193
2409
  createMemoryStorage,
2194
2410
  createRebaseClient,
2195
2411
  createStorage,
2196
2412
  createTransport,
2413
+ or,
2197
2414
  rebaseReviver
2198
2415
  };
2199
2416
  //# sourceMappingURL=index.es.js.map