@rebasepro/client 0.0.1-canary.4d4fb3e → 0.0.1-canary.ca2cb6e

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.es.js CHANGED
@@ -1,3 +1,42 @@
1
+ import { Vector, GeoPoint, EntityRelation, EntityReference } from "@rebasepro/types";
2
+ import { toSnakeCase } from "@rebasepro/utils";
3
+ function rebaseReviver(_key, value) {
4
+ if (value && typeof value === "object" && "__type" in value) {
5
+ const record = value;
6
+ switch (record.__type) {
7
+ case "date":
8
+ case "Date": {
9
+ if (typeof record.value !== "string") {
10
+ return value;
11
+ }
12
+ const date = new Date(record.value);
13
+ return isNaN(date.getTime()) ? null : date;
14
+ }
15
+ case "reference":
16
+ case "EntityReference":
17
+ return new EntityReference({
18
+ id: String(record.id),
19
+ path: record.path,
20
+ driver: record.driver,
21
+ databaseId: record.databaseId
22
+ });
23
+ case "relation":
24
+ case "EntityRelation":
25
+ return new EntityRelation(
26
+ record.id,
27
+ record.path,
28
+ record.data
29
+ );
30
+ case "GeoPoint":
31
+ return new GeoPoint(record.latitude, record.longitude);
32
+ case "Vector":
33
+ return new Vector(record.value);
34
+ default:
35
+ return value;
36
+ }
37
+ }
38
+ return value;
39
+ }
1
40
  class RebaseApiError extends Error {
2
41
  status;
3
42
  code;
@@ -10,6 +49,30 @@ class RebaseApiError extends Error {
10
49
  this.details = details;
11
50
  }
12
51
  }
52
+ const OP_MAP = {
53
+ "==": "eq",
54
+ "!=": "neq",
55
+ ">": "gt",
56
+ ">=": "gte",
57
+ "<": "lt",
58
+ "<=": "lte",
59
+ "not-in": "nin",
60
+ "array-contains": "cs",
61
+ "array-contains-any": "csa"
62
+ };
63
+ function normalizeWhereValue(value) {
64
+ if (value === null) return "eq.null";
65
+ if (typeof value === "boolean") return `eq.${value}`;
66
+ if (typeof value === "number") return String(value);
67
+ if (Array.isArray(value) && value.length === 2) {
68
+ const [rawOp, val] = value;
69
+ const op = OP_MAP[rawOp] ?? rawOp;
70
+ if (val === null) return `${op}.null`;
71
+ if (Array.isArray(val)) return `${op}.(${val.join(",")})`;
72
+ return `${op}.${val}`;
73
+ }
74
+ return String(value);
75
+ }
13
76
  function buildQueryString(params) {
14
77
  if (!params) return "";
15
78
  const parts = [];
@@ -19,12 +82,16 @@ function buildQueryString(params) {
19
82
  if (params.orderBy) {
20
83
  parts.push(`orderBy=${encodeURIComponent(params.orderBy)}`);
21
84
  }
85
+ if (params.searchString) {
86
+ parts.push(`searchString=${encodeURIComponent(params.searchString)}`);
87
+ }
22
88
  if (params.include && params.include.length > 0) {
23
89
  parts.push(`include=${encodeURIComponent(params.include.join(","))}`);
24
90
  }
25
91
  if (params.where) {
26
92
  for (const [field, value] of Object.entries(params.where)) {
27
- parts.push(`${encodeURIComponent(field)}=${encodeURIComponent(String(value))}`);
93
+ const normalized = normalizeWhereValue(value);
94
+ parts.push(`${encodeURIComponent(field)}=${encodeURIComponent(normalized)}`);
28
95
  }
29
96
  }
30
97
  return parts.length > 0 ? "?" + parts.join("&") : "";
@@ -43,7 +110,8 @@ function createTransport(config) {
43
110
  };
44
111
  }
45
112
  async function request(path, init) {
46
- const url = config.baseUrl.replace(/\/$/, "") + apiPath + path;
113
+ const base = config.baseUrl ? config.baseUrl.replace(/\/$/, "") : "";
114
+ const url = base + apiPath + path;
47
115
  let activeToken = token;
48
116
  if (tokenGetter) {
49
117
  try {
@@ -58,9 +126,19 @@ function createTransport(config) {
58
126
  if (init?.body instanceof FormData) {
59
127
  delete headers["Content-Type"];
60
128
  }
61
- const res = await fetchFn(url, { ...init, headers });
129
+ const res = await fetchFn(url, {
130
+ ...init,
131
+ headers
132
+ });
62
133
  if (res.status === 204) return void 0;
63
- const body = await res.json().catch(() => ({}));
134
+ const text = await res.text().catch(() => "");
135
+ let body = {};
136
+ if (text) {
137
+ try {
138
+ body = JSON.parse(text, rebaseReviver);
139
+ } catch (e) {
140
+ }
141
+ }
64
142
  if (res.status === 401 && onUnauthorizedHandler) {
65
143
  const retried = await onUnauthorizedHandler();
66
144
  if (retried) {
@@ -75,13 +153,28 @@ function createTransport(config) {
75
153
  }
76
154
  }
77
155
  const retryHeaders = getHeaders(retryToken, init);
78
- const retryRes = await fetchFn(url, { ...init, headers: retryHeaders });
156
+ const retryRes = await fetchFn(url, {
157
+ ...init,
158
+ headers: retryHeaders
159
+ });
79
160
  if (retryRes.status === 204) return void 0;
80
- const retryBody = await retryRes.json().catch(() => ({}));
161
+ const retryText = await retryRes.text().catch(() => "");
162
+ let retryBody = {};
163
+ if (retryText) {
164
+ try {
165
+ retryBody = JSON.parse(retryText, rebaseReviver);
166
+ } catch (e) {
167
+ }
168
+ }
81
169
  if (!retryRes.ok) {
170
+ let fallbackMessage = retryRes.statusText;
171
+ if (retryRes.status === 404 && !fallbackMessage) {
172
+ const method = init?.method || "GET";
173
+ fallbackMessage = `Endpoint not found (${method} ${path}). This usually means the collection is not registered on the backend, or the frontend API URL configuration (e.g. VITE_API_URL) is missing or pointing to the wrong host.`;
174
+ }
82
175
  throw new RebaseApiError(
83
176
  retryRes.status,
84
- retryBody?.error?.message || retryBody?.message || retryRes.statusText,
177
+ retryBody?.error?.message || retryBody?.message || fallbackMessage || `Request failed with status ${retryRes.status}`,
85
178
  retryBody?.error?.code || retryBody?.code,
86
179
  retryBody?.error?.details || retryBody?.details
87
180
  );
@@ -90,9 +183,14 @@ function createTransport(config) {
90
183
  }
91
184
  }
92
185
  if (!res.ok) {
186
+ let fallbackMessage = res.statusText;
187
+ if (res.status === 404 && !fallbackMessage) {
188
+ const method = init?.method || "GET";
189
+ fallbackMessage = `Endpoint not found (${method} ${path}). This usually means the collection is not registered on the backend, or the frontend API URL configuration (e.g. VITE_API_URL) is missing or pointing to the wrong host.`;
190
+ }
93
191
  throw new RebaseApiError(
94
192
  res.status,
95
- body?.error?.message || body?.message || res.statusText,
193
+ body?.error?.message || body?.message || fallbackMessage || `Request failed with status ${res.status}`,
96
194
  body?.error?.code || body?.code,
97
195
  body?.error?.details || body?.details
98
196
  );
@@ -111,7 +209,7 @@ function createTransport(config) {
111
209
  onUnauthorizedHandler = handler;
112
210
  },
113
211
  get baseUrl() {
114
- return config.baseUrl.replace(/\/$/, "");
212
+ return config.baseUrl ? config.baseUrl.replace(/\/$/, "") : "";
115
213
  },
116
214
  get apiPath() {
117
215
  return apiPath;
@@ -248,16 +346,26 @@ function createAuth(transport, options) {
248
346
  const res = await fetchFn(authUrl("/login"), {
249
347
  method: "POST",
250
348
  headers: { "Content-Type": "application/json" },
251
- body: JSON.stringify({ email, password })
349
+ body: JSON.stringify({
350
+ email,
351
+ password
352
+ })
252
353
  });
253
354
  const body = await res.json().catch(() => ({}));
254
355
  if (!res.ok) throwApiError(res.status, body, res.statusText);
255
356
  const session = handleAuthResponse(body, "SIGNED_IN");
256
- return { user: session.user, accessToken: session.accessToken, refreshToken: session.refreshToken };
357
+ return {
358
+ user: session.user,
359
+ accessToken: session.accessToken,
360
+ refreshToken: session.refreshToken
361
+ };
257
362
  }
258
363
  async function signUp(email, password, displayName) {
259
364
  const fetchFn = getFetch();
260
- const payload = { email, password };
365
+ const payload = {
366
+ email,
367
+ password
368
+ };
261
369
  if (displayName !== void 0) payload.displayName = displayName;
262
370
  const res = await fetchFn(authUrl("/register"), {
263
371
  method: "POST",
@@ -267,7 +375,11 @@ function createAuth(transport, options) {
267
375
  const body = await res.json().catch(() => ({}));
268
376
  if (!res.ok) throwApiError(res.status, body, res.statusText);
269
377
  const session = handleAuthResponse(body, "SIGNED_IN");
270
- return { user: session.user, accessToken: session.accessToken, refreshToken: session.refreshToken };
378
+ return {
379
+ user: session.user,
380
+ accessToken: session.accessToken,
381
+ refreshToken: session.refreshToken
382
+ };
271
383
  }
272
384
  async function signInWithGoogle(idToken) {
273
385
  const fetchFn = getFetch();
@@ -279,7 +391,108 @@ function createAuth(transport, options) {
279
391
  const body = await res.json().catch(() => ({}));
280
392
  if (!res.ok) throwApiError(res.status, body, res.statusText);
281
393
  const session = handleAuthResponse(body, "SIGNED_IN");
282
- return { user: session.user, accessToken: session.accessToken, refreshToken: session.refreshToken };
394
+ return {
395
+ user: session.user,
396
+ accessToken: session.accessToken,
397
+ refreshToken: session.refreshToken
398
+ };
399
+ }
400
+ async function signInWithLinkedin(code, redirectUri) {
401
+ const fetchFn = getFetch();
402
+ const res = await fetchFn(authUrl("/linkedin"), {
403
+ method: "POST",
404
+ headers: { "Content-Type": "application/json" },
405
+ body: JSON.stringify({
406
+ code,
407
+ redirectUri
408
+ })
409
+ });
410
+ const body = await res.json().catch(() => ({}));
411
+ if (!res.ok) throwApiError(res.status, body, res.statusText);
412
+ const session = handleAuthResponse(body, "SIGNED_IN");
413
+ return {
414
+ user: session.user,
415
+ accessToken: session.accessToken,
416
+ refreshToken: session.refreshToken
417
+ };
418
+ }
419
+ async function signInWithOAuth(providerId, payload) {
420
+ const fetchFn = getFetch();
421
+ const res = await fetchFn(authUrl(`/${providerId}`), {
422
+ method: "POST",
423
+ headers: { "Content-Type": "application/json" },
424
+ body: JSON.stringify(payload)
425
+ });
426
+ const body = await res.json().catch(() => ({}));
427
+ if (!res.ok) throwApiError(res.status, body, res.statusText);
428
+ const session = handleAuthResponse(body, "SIGNED_IN");
429
+ return {
430
+ user: session.user,
431
+ accessToken: session.accessToken,
432
+ refreshToken: session.refreshToken
433
+ };
434
+ }
435
+ async function signInWithGitHub(code, redirectUri) {
436
+ return signInWithOAuth("github", {
437
+ code,
438
+ redirectUri
439
+ });
440
+ }
441
+ async function signInWithMicrosoft(code, redirectUri) {
442
+ return signInWithOAuth("microsoft", {
443
+ code,
444
+ redirectUri
445
+ });
446
+ }
447
+ async function signInWithApple(code, redirectUri, user) {
448
+ return signInWithOAuth("apple", {
449
+ code,
450
+ redirectUri,
451
+ user
452
+ });
453
+ }
454
+ async function signInWithFacebook(code, redirectUri) {
455
+ return signInWithOAuth("facebook", {
456
+ code,
457
+ redirectUri
458
+ });
459
+ }
460
+ async function signInWithTwitter(code, redirectUri, codeVerifier) {
461
+ return signInWithOAuth("twitter", {
462
+ code,
463
+ redirectUri,
464
+ codeVerifier
465
+ });
466
+ }
467
+ async function signInWithDiscord(code, redirectUri) {
468
+ return signInWithOAuth("discord", {
469
+ code,
470
+ redirectUri
471
+ });
472
+ }
473
+ async function signInWithGitLab(code, redirectUri) {
474
+ return signInWithOAuth("gitlab", {
475
+ code,
476
+ redirectUri
477
+ });
478
+ }
479
+ async function signInWithBitbucket(code, redirectUri) {
480
+ return signInWithOAuth("bitbucket", {
481
+ code,
482
+ redirectUri
483
+ });
484
+ }
485
+ async function signInWithSlack(code, redirectUri) {
486
+ return signInWithOAuth("slack", {
487
+ code,
488
+ redirectUri
489
+ });
490
+ }
491
+ async function signInWithSpotify(code, redirectUri) {
492
+ return signInWithOAuth("spotify", {
493
+ code,
494
+ redirectUri
495
+ });
283
496
  }
284
497
  async function signOut() {
285
498
  const fetchFn = getFetch();
@@ -337,7 +550,10 @@ function createAuth(transport, options) {
337
550
  body: JSON.stringify(updates)
338
551
  });
339
552
  if (currentSession) {
340
- currentSession = { ...currentSession, user: data.user };
553
+ currentSession = {
554
+ ...currentSession,
555
+ user: data.user
556
+ };
341
557
  saveSession(currentSession);
342
558
  emit("USER_UPDATED", currentSession);
343
559
  }
@@ -359,7 +575,10 @@ function createAuth(transport, options) {
359
575
  const res = await fetchFn(authUrl("/reset-password"), {
360
576
  method: "POST",
361
577
  headers: { "Content-Type": "application/json" },
362
- body: JSON.stringify({ token, password })
578
+ body: JSON.stringify({
579
+ token,
580
+ password
581
+ })
363
582
  });
364
583
  const body = await res.json().catch(() => ({}));
365
584
  if (!res.ok) throwApiError(res.status, body, res.statusText);
@@ -368,7 +587,10 @@ function createAuth(transport, options) {
368
587
  async function changePassword(oldPassword, newPassword) {
369
588
  return transport.request(authPath + "/change-password", {
370
589
  method: "POST",
371
- body: JSON.stringify({ oldPassword, newPassword })
590
+ body: JSON.stringify({
591
+ oldPassword,
592
+ newPassword
593
+ })
372
594
  });
373
595
  }
374
596
  async function sendVerificationEmail() {
@@ -447,6 +669,18 @@ function createAuth(transport, options) {
447
669
  signInWithEmail,
448
670
  signUp,
449
671
  signInWithGoogle,
672
+ signInWithLinkedin,
673
+ signInWithOAuth,
674
+ signInWithGitHub,
675
+ signInWithMicrosoft,
676
+ signInWithApple,
677
+ signInWithFacebook,
678
+ signInWithTwitter,
679
+ signInWithDiscord,
680
+ signInWithGitLab,
681
+ signInWithBitbucket,
682
+ signInWithSlack,
683
+ signInWithSpotify,
450
684
  signOut,
451
685
  refreshSession,
452
686
  getUser,
@@ -546,6 +780,49 @@ function createAdmin(transport, options) {
546
780
  bootstrap
547
781
  };
548
782
  }
783
+ function createCron(transport, options) {
784
+ const cronPath = options?.cronPath || "/cron";
785
+ async function listJobs() {
786
+ return transport.request(cronPath, { method: "GET" });
787
+ }
788
+ async function getJob(jobId) {
789
+ return transport.request(
790
+ cronPath + "/" + encodeURIComponent(jobId),
791
+ { method: "GET" }
792
+ );
793
+ }
794
+ async function triggerJob(jobId) {
795
+ return transport.request(
796
+ cronPath + "/" + encodeURIComponent(jobId) + "/trigger",
797
+ { method: "POST" }
798
+ );
799
+ }
800
+ async function getJobLogs(jobId, options2) {
801
+ const params = new URLSearchParams();
802
+ if (options2?.limit !== void 0) params.set("limit", String(options2.limit));
803
+ const qs = params.toString();
804
+ return transport.request(
805
+ cronPath + "/" + encodeURIComponent(jobId) + "/logs" + (qs ? "?" + qs : ""),
806
+ { method: "GET" }
807
+ );
808
+ }
809
+ async function toggleJob(jobId, enabled) {
810
+ return transport.request(
811
+ cronPath + "/" + encodeURIComponent(jobId),
812
+ {
813
+ method: "PUT",
814
+ body: JSON.stringify({ enabled })
815
+ }
816
+ );
817
+ }
818
+ return {
819
+ listJobs,
820
+ getJob,
821
+ triggerJob,
822
+ getJobLogs,
823
+ toggleJob
824
+ };
825
+ }
549
826
  function mapOperator(op) {
550
827
  switch (op) {
551
828
  case "==":
@@ -577,7 +854,7 @@ class QueryBuilder {
577
854
  params = { where: {} };
578
855
  /**
579
856
  * Add a filter condition to your query.
580
- * @example
857
+ * @example
581
858
  * client.collection('users').where('age', '>=', 18).find()
582
859
  */
583
860
  where(column, operator, value) {
@@ -596,7 +873,7 @@ class QueryBuilder {
596
873
  }
597
874
  /**
598
875
  * Order the results by a specific column.
599
- * @example
876
+ * @example
600
877
  * client.collection('users').orderBy('createdAt', 'desc').find()
601
878
  */
602
879
  orderBy(column, ascending = "asc") {
@@ -659,7 +936,46 @@ class QueryBuilder {
659
936
  function parseWhereFilter(where) {
660
937
  if (!where) return void 0;
661
938
  const filters = {};
662
- for (const [key, value] of Object.entries(where)) {
939
+ for (const [key, rawValue] of Object.entries(where)) {
940
+ if (rawValue === null) {
941
+ filters[key] = ["==", null];
942
+ continue;
943
+ }
944
+ if (typeof rawValue === "boolean") {
945
+ filters[key] = ["==", rawValue];
946
+ continue;
947
+ }
948
+ if (typeof rawValue === "number") {
949
+ filters[key] = ["==", rawValue];
950
+ continue;
951
+ }
952
+ if (Array.isArray(rawValue) && rawValue.length === 2) {
953
+ const [rawOp, val] = rawValue;
954
+ const OP_TO_FILTER = {
955
+ "eq": "==",
956
+ "neq": "!=",
957
+ "gt": ">",
958
+ "gte": ">=",
959
+ "lt": "<",
960
+ "lte": "<=",
961
+ "==": "==",
962
+ "!=": "!=",
963
+ ">": ">",
964
+ ">=": ">=",
965
+ "<": "<",
966
+ "<=": "<=",
967
+ "in": "in",
968
+ "nin": "not-in",
969
+ "not-in": "not-in",
970
+ "cs": "array-contains",
971
+ "csa": "array-contains-any",
972
+ "array-contains": "array-contains",
973
+ "array-contains-any": "array-contains-any"
974
+ };
975
+ filters[key] = [OP_TO_FILTER[rawOp] ?? "==", val];
976
+ continue;
977
+ }
978
+ const value = String(rawValue);
663
979
  const dotIndex = value.indexOf(".");
664
980
  if (dotIndex > 0) {
665
981
  const opStr = value.substring(0, dotIndex);
@@ -707,7 +1023,7 @@ function parseWhereFilter(where) {
707
1023
  if (val === "true") val = true;
708
1024
  else if (val === "false") val = false;
709
1025
  else if (val === "null") val = null;
710
- else if (typeof val === "string" && /^[0-9]+(\.[0-9]+)?$/.test(val)) val = Number(val);
1026
+ else if (typeof val === "string" && /^[0-9]+(\.[0-9]+)?$/.test(val) && key !== "id" && !key.endsWith("_id")) val = Number(val);
711
1027
  filters[key] = [op, val];
712
1028
  } else {
713
1029
  filters[key] = ["==", value];
@@ -724,7 +1040,7 @@ function rowToEntity(row, slug) {
724
1040
  }
725
1041
  function createCollectionClient(transport, slug, ws) {
726
1042
  const basePath = `/data/${slug}`;
727
- return {
1043
+ const client = {
728
1044
  async find(params) {
729
1045
  const qs = buildQueryString(params);
730
1046
  const raw = await transport.request(basePath + qs, { method: "GET" });
@@ -761,74 +1077,84 @@ function createCollectionClient(transport, slug, ws) {
761
1077
  method: "DELETE"
762
1078
  });
763
1079
  },
764
- ...ws && {
765
- listen(params, onUpdate, onError) {
766
- return ws.listenCollection(
767
- {
768
- path: slug,
769
- filter: parseWhereFilter(params?.where),
770
- limit: params?.limit,
771
- startAfter: params?.offset ? String(params.offset) : void 0,
772
- orderBy: params?.orderBy?.split(":")[0],
773
- order: params?.orderBy?.split(":")[1],
774
- searchString: params?.searchString
775
- },
776
- (entities) => {
777
- const requestedLimit = params?.limit || 20;
778
- onUpdate({
779
- data: entities,
780
- meta: {
781
- total: entities.length,
782
- limit: requestedLimit,
783
- offset: params?.offset || 0,
784
- hasMore: entities.length >= requestedLimit
785
- }
786
- });
787
- },
788
- onError
789
- );
790
- },
791
- listenById(id, onUpdate, onError) {
792
- return ws.listenEntity(
793
- { path: slug, entityId: String(id) },
794
- (entity) => {
795
- if (entity) {
796
- onUpdate(entity);
797
- } else {
798
- onUpdate(void 0);
799
- }
800
- },
801
- onError
802
- );
803
- }
804
- },
805
1080
  // Fluent builder instantiation
806
1081
  where(column, operator, value) {
807
- return new QueryBuilder(this).where(column, operator, value);
1082
+ return new QueryBuilder(client).where(column, operator, value);
808
1083
  },
809
1084
  orderBy(column, ascending) {
810
- return new QueryBuilder(this).orderBy(column, ascending);
1085
+ return new QueryBuilder(client).orderBy(column, ascending);
811
1086
  },
812
1087
  limit(count) {
813
- return new QueryBuilder(this).limit(count);
1088
+ return new QueryBuilder(client).limit(count);
814
1089
  },
815
1090
  offset(count) {
816
- return new QueryBuilder(this).offset(count);
1091
+ return new QueryBuilder(client).offset(count);
817
1092
  },
818
1093
  search(searchString) {
819
- return new QueryBuilder(this).search(searchString);
1094
+ return new QueryBuilder(client).search(searchString);
820
1095
  },
821
1096
  include(...relations) {
822
- return new QueryBuilder(this).include(...relations);
1097
+ return new QueryBuilder(client).include(...relations);
823
1098
  }
824
1099
  };
1100
+ if (ws) {
1101
+ client.listen = (params, onUpdate, onError) => {
1102
+ return ws.listenCollection(
1103
+ {
1104
+ path: slug,
1105
+ filter: parseWhereFilter(params?.where),
1106
+ limit: params?.limit,
1107
+ startAfter: params?.offset ? String(params.offset) : void 0,
1108
+ orderBy: params?.orderBy?.split(":")[0],
1109
+ order: params?.orderBy?.split(":")[1],
1110
+ searchString: params?.searchString
1111
+ },
1112
+ (entities) => {
1113
+ const requestedLimit = params?.limit || 20;
1114
+ onUpdate({
1115
+ data: entities,
1116
+ meta: {
1117
+ total: entities.length,
1118
+ limit: requestedLimit,
1119
+ offset: params?.offset || 0,
1120
+ hasMore: entities.length >= requestedLimit
1121
+ }
1122
+ });
1123
+ },
1124
+ onError
1125
+ );
1126
+ };
1127
+ client.listenById = (id, onUpdate, onError) => {
1128
+ return ws.listenEntity(
1129
+ {
1130
+ path: slug,
1131
+ entityId: String(id)
1132
+ },
1133
+ (entity) => {
1134
+ if (entity) {
1135
+ onUpdate(entity);
1136
+ } else {
1137
+ onUpdate(void 0);
1138
+ }
1139
+ },
1140
+ onError
1141
+ );
1142
+ };
1143
+ }
1144
+ return client;
1145
+ }
1146
+ function rehydrateEntity(entity) {
1147
+ return entity;
825
1148
  }
826
1149
  function extractMessageError(message) {
827
1150
  const payload = message.payload;
828
1151
  const errPayload = payload?.error;
829
1152
  const errorMessage = typeof errPayload === "object" ? errPayload.message : payload?.message || (typeof errPayload === "string" ? errPayload : void 0) || message.error || "Unknown error";
830
1153
  const errorCode = typeof errPayload === "object" ? errPayload.code : payload?.code;
831
- return { errorMessage, errorCode };
1154
+ return {
1155
+ errorMessage,
1156
+ errorCode
1157
+ };
832
1158
  }
833
1159
  class ApiError extends Error {
834
1160
  code;
@@ -869,6 +1195,7 @@ class RebaseWebSocketClient {
869
1195
  maxReconnectAttempts = 5;
870
1196
  isConnected = false;
871
1197
  messageQueue = [];
1198
+ reconnectTimeout = null;
872
1199
  isAuthenticated = false;
873
1200
  authPromise = null;
874
1201
  WebSocketConstructor;
@@ -924,18 +1251,29 @@ class RebaseWebSocketClient {
924
1251
  if (this.isConnected && !this.isAuthenticated && !this.authPromise) {
925
1252
  console.log("WebSocket auto-authenticating after token getter set");
926
1253
  this.getAuthToken().then((token) => {
1254
+ if (!this.ws) return;
927
1255
  if (token) {
928
- this.authenticate(token).catch((e) => console.warn("WebSocket auto-auth failed:", e));
1256
+ this.authenticate(token).catch((e) => {
1257
+ if (this.ws) console.warn("WebSocket auto-auth failed:", e);
1258
+ });
929
1259
  }
930
1260
  }).catch((e) => {
931
- console.warn("WebSocket auto-auth failed:", e);
1261
+ if (this.ws) console.warn("WebSocket auto-auth failed:", e);
932
1262
  });
933
1263
  }
934
1264
  }
935
1265
  disconnect() {
936
1266
  this.isAuthenticated = false;
937
1267
  this.authPromise = null;
1268
+ if (this.reconnectTimeout) {
1269
+ clearTimeout(this.reconnectTimeout);
1270
+ this.reconnectTimeout = null;
1271
+ }
938
1272
  if (this.ws) {
1273
+ this.ws.onclose = null;
1274
+ this.ws.onerror = null;
1275
+ this.ws.onopen = null;
1276
+ this.ws.onmessage = null;
939
1277
  this.ws.close();
940
1278
  this.ws = null;
941
1279
  }
@@ -970,7 +1308,7 @@ class RebaseWebSocketClient {
970
1308
  };
971
1309
  this.ws.onmessage = (event) => {
972
1310
  try {
973
- const message = JSON.parse(event.data);
1311
+ const message = JSON.parse(event.data, rebaseReviver);
974
1312
  this.handleWebSocketMessage(message);
975
1313
  } catch (error) {
976
1314
  console.error("Error parsing WebSocket message:", error);
@@ -1020,7 +1358,11 @@ class RebaseWebSocketClient {
1020
1358
  this.reconnectAttempts++;
1021
1359
  const delay = Math.min(1e3 * Math.pow(2, this.reconnectAttempts), 3e4);
1022
1360
  console.log(`Attempting to reconnect in ${delay}ms (attempt ${this.reconnectAttempts})`);
1023
- setTimeout(() => {
1361
+ if (this.reconnectTimeout) {
1362
+ clearTimeout(this.reconnectTimeout);
1363
+ }
1364
+ this.reconnectTimeout = setTimeout(() => {
1365
+ this.reconnectTimeout = null;
1024
1366
  this.initWebSocket();
1025
1367
  }, delay);
1026
1368
  }
@@ -1049,7 +1391,7 @@ class RebaseWebSocketClient {
1049
1391
  if (subscriptionKey) {
1050
1392
  const collectionSub = this.collectionSubscriptions.get(subscriptionKey);
1051
1393
  if (collectionSub) {
1052
- const incomingEntities = message.entities || [];
1394
+ const incomingEntities = (message.entities || []).map((e) => rehydrateEntity(e));
1053
1395
  const entities = this.mergeEntities(collectionSub.latestData, incomingEntities);
1054
1396
  collectionSub.latestData = entities;
1055
1397
  collectionSub.lastUpdated = Date.now();
@@ -1073,7 +1415,7 @@ class RebaseWebSocketClient {
1073
1415
  if (subscriptionKey) {
1074
1416
  const collectionSub = this.collectionSubscriptions.get(subscriptionKey);
1075
1417
  if (collectionSub && collectionSub.isInitialDataReceived && collectionSub.latestData) {
1076
- const patchEntity = message.entity;
1418
+ const patchEntity = message.entity ? rehydrateEntity(message.entity) : message.entity;
1077
1419
  const patchEntityId = message.entityId;
1078
1420
  let updated;
1079
1421
  if (patchEntity === null || patchEntity === void 0) {
@@ -1108,7 +1450,7 @@ class RebaseWebSocketClient {
1108
1450
  if (subscriptionKey) {
1109
1451
  const entitySub = this.entitySubscriptions.get(subscriptionKey);
1110
1452
  if (entitySub) {
1111
- const entity = message.entity ?? null;
1453
+ const entity = message.entity ? rehydrateEntity(message.entity) : null;
1112
1454
  entitySub.latestData = entity;
1113
1455
  entitySub.lastUpdated = Date.now();
1114
1456
  entitySub.isInitialDataReceived = true;
@@ -1276,21 +1618,21 @@ class RebaseWebSocketClient {
1276
1618
  type: "FETCH_COLLECTION",
1277
1619
  payload: props
1278
1620
  });
1279
- return response.entities || [];
1621
+ return (response.entities || []).map((e) => rehydrateEntity(e));
1280
1622
  }
1281
1623
  async fetchEntity(props) {
1282
1624
  const response = await this.sendMessage({
1283
1625
  type: "FETCH_ENTITY",
1284
1626
  payload: props
1285
1627
  });
1286
- return response.entity;
1628
+ return response.entity ? rehydrateEntity(response.entity) : void 0;
1287
1629
  }
1288
1630
  async saveEntity(props) {
1289
1631
  const response = await this.sendMessage({
1290
1632
  type: "SAVE_ENTITY",
1291
1633
  payload: props
1292
1634
  });
1293
- return response.entity;
1635
+ return rehydrateEntity(response.entity);
1294
1636
  }
1295
1637
  async deleteEntity(props) {
1296
1638
  await this.sendMessage({
@@ -1301,7 +1643,10 @@ class RebaseWebSocketClient {
1301
1643
  async executeSql(sql, options) {
1302
1644
  const response = await this.sendMessage({
1303
1645
  type: "EXECUTE_SQL",
1304
- payload: { sql, options }
1646
+ payload: {
1647
+ sql,
1648
+ options
1649
+ }
1305
1650
  });
1306
1651
  return response.result || [];
1307
1652
  }
@@ -1356,7 +1701,35 @@ class RebaseWebSocketClient {
1356
1701
  type: "FETCH_TABLE_METADATA",
1357
1702
  payload: { tableName }
1358
1703
  });
1359
- return response.metadata || { columns: [], foreignKeys: [], junctions: [], policies: [] };
1704
+ return response.metadata || {
1705
+ columns: [],
1706
+ foreignKeys: [],
1707
+ junctions: [],
1708
+ policies: []
1709
+ };
1710
+ }
1711
+ async createBranch(name, options) {
1712
+ const response = await this.sendMessage({
1713
+ type: "CREATE_BRANCH",
1714
+ payload: {
1715
+ name,
1716
+ options
1717
+ }
1718
+ });
1719
+ return response.branch;
1720
+ }
1721
+ async deleteBranch(name) {
1722
+ await this.sendMessage({
1723
+ type: "DELETE_BRANCH",
1724
+ payload: { name }
1725
+ });
1726
+ }
1727
+ async listBranches() {
1728
+ const response = await this.sendMessage({
1729
+ type: "LIST_BRANCHES",
1730
+ payload: {}
1731
+ });
1732
+ return response.branches || [];
1360
1733
  }
1361
1734
  /**
1362
1735
  * Recursively compare two values for structural equality.
@@ -1442,7 +1815,10 @@ class RebaseWebSocketClient {
1442
1815
  const allKeys = /* @__PURE__ */ new Set([...Object.keys(normCached), ...Object.keys(normIncoming)]);
1443
1816
  for (const key of allKeys) {
1444
1817
  if (!this.deepEqual(normCached[key], normIncoming[key])) {
1445
- mismatches[key] = { cached: normCached[key], incoming: normIncoming[key] };
1818
+ mismatches[key] = {
1819
+ cached: normCached[key],
1820
+ incoming: normIncoming[key]
1821
+ };
1446
1822
  }
1447
1823
  }
1448
1824
  console.log(`[RebaseWS] Row ${incomingEntity.id} refetch mismatch:
@@ -1459,7 +1835,10 @@ class RebaseWebSocketClient {
1459
1835
  const existingSubscription = this.collectionSubscriptions.get(subscriptionKey);
1460
1836
  if (existingSubscription) {
1461
1837
  const callbackMap2 = existingSubscription.callbacks;
1462
- callbackMap2.set(callbackId, { onUpdate, onError });
1838
+ callbackMap2.set(callbackId, {
1839
+ onUpdate,
1840
+ onError
1841
+ });
1463
1842
  if (existingSubscription.latestData !== void 0 && existingSubscription.isInitialDataReceived) {
1464
1843
  try {
1465
1844
  onUpdate(existingSubscription.latestData);
@@ -1486,7 +1865,10 @@ class RebaseWebSocketClient {
1486
1865
  }
1487
1866
  const backendSubscriptionId = `collection_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
1488
1867
  const callbackMap = /* @__PURE__ */ new Map();
1489
- callbackMap.set(callbackId, { onUpdate, onError });
1868
+ callbackMap.set(callbackId, {
1869
+ onUpdate,
1870
+ onError
1871
+ });
1490
1872
  this.collectionSubscriptions.set(subscriptionKey, {
1491
1873
  backendSubscriptionId,
1492
1874
  callbacks: callbackMap,
@@ -1526,7 +1908,10 @@ class RebaseWebSocketClient {
1526
1908
  const existingSubscription = this.entitySubscriptions.get(subscriptionKey);
1527
1909
  if (existingSubscription) {
1528
1910
  const callbackMap2 = existingSubscription.callbacks;
1529
- callbackMap2.set(callbackId, { onUpdate, onError });
1911
+ callbackMap2.set(callbackId, {
1912
+ onUpdate,
1913
+ onError
1914
+ });
1530
1915
  if (existingSubscription.latestData !== void 0 && existingSubscription.isInitialDataReceived) {
1531
1916
  try {
1532
1917
  onUpdate(existingSubscription.latestData);
@@ -1553,7 +1938,10 @@ class RebaseWebSocketClient {
1553
1938
  }
1554
1939
  const backendSubscriptionId = `entity_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
1555
1940
  const callbackMap = /* @__PURE__ */ new Map();
1556
- callbackMap.set(callbackId, { onUpdate, onError });
1941
+ callbackMap.set(callbackId, {
1942
+ onUpdate,
1943
+ onError
1944
+ });
1557
1945
  this.entitySubscriptions.set(subscriptionKey, {
1558
1946
  backendSubscriptionId,
1559
1947
  callbacks: callbackMap,
@@ -1654,23 +2042,21 @@ class RebaseWebSocketClient {
1654
2042
  }
1655
2043
  function createStorage(transport) {
1656
2044
  const urlsCache = /* @__PURE__ */ new Map();
1657
- async function uploadFile({
2045
+ async function putObject({
1658
2046
  file,
1659
- fileName,
1660
- path,
2047
+ key,
1661
2048
  metadata,
1662
2049
  bucket
1663
2050
  }) {
1664
2051
  const formData = new FormData();
1665
2052
  formData.append("file", file);
1666
- if (fileName) formData.append("fileName", fileName);
1667
- if (path) formData.append("path", path);
2053
+ if (key) formData.append("key", key);
1668
2054
  if (bucket) formData.append("bucket", bucket);
1669
2055
  if (metadata) {
1670
- for (const [key, value] of Object.entries(metadata)) {
2056
+ for (const [key2, value] of Object.entries(metadata)) {
1671
2057
  if (value !== void 0 && value !== null) {
1672
2058
  formData.append(
1673
- `metadata_${key}`,
2059
+ `metadata_${key2}`,
1674
2060
  typeof value === "string" ? value : JSON.stringify(value)
1675
2061
  );
1676
2062
  }
@@ -1680,18 +2066,18 @@ function createStorage(transport) {
1680
2066
  method: "POST",
1681
2067
  body: formData,
1682
2068
  headers: {
1683
- // transport.request merges headers, so to prevent it setting application/json we can delete it
2069
+ // transport.request merges headers, so to prevent it setting application/json we can delete it
1684
2070
  // in transport if body is FormData, or we can explicitly set it to an empty string.
1685
2071
  // Let's rely on standard behaviour for now and adjust transport if it fails.
1686
2072
  }
1687
2073
  });
1688
2074
  return result.data;
1689
2075
  }
1690
- async function getDownloadURL(pathOrUrl, bucket) {
1691
- const cacheKey = bucket ? `${bucket}/${pathOrUrl}` : pathOrUrl;
2076
+ async function getSignedUrl(keyOrUrl, bucket) {
2077
+ const cacheKey = bucket ? `${bucket}/${keyOrUrl}` : keyOrUrl;
1692
2078
  const cached = urlsCache.get(cacheKey);
1693
2079
  if (cached) return cached;
1694
- let filePath = pathOrUrl;
2080
+ let filePath = keyOrUrl;
1695
2081
  if (filePath && (filePath.startsWith("local://") || filePath.startsWith("s3://"))) {
1696
2082
  filePath = filePath.substring(filePath.indexOf("://") + 3);
1697
2083
  }
@@ -1699,7 +2085,10 @@ function createStorage(transport) {
1699
2085
  filePath = `${bucket}/${filePath}`;
1700
2086
  }
1701
2087
  if (!filePath || filePath.trim() === "" || filePath === "/") {
1702
- return { url: null, fileNotFound: true };
2088
+ return {
2089
+ url: null,
2090
+ fileNotFound: true
2091
+ };
1703
2092
  }
1704
2093
  try {
1705
2094
  const result = await transport.request(`/storage/metadata/${filePath}`);
@@ -1713,13 +2102,16 @@ function createStorage(transport) {
1713
2102
  return downloadConfig;
1714
2103
  } catch (e) {
1715
2104
  if (e instanceof Error && "status" in e && e.status === 404) {
1716
- return { url: null, fileNotFound: true };
2105
+ return {
2106
+ url: null,
2107
+ fileNotFound: true
2108
+ };
1717
2109
  }
1718
2110
  throw e;
1719
2111
  }
1720
2112
  }
1721
- async function getFile(path, bucket) {
1722
- let filePath = path;
2113
+ async function getObject(key, bucket) {
2114
+ let filePath = key;
1723
2115
  if (filePath && (filePath.startsWith("local://") || filePath.startsWith("s3://"))) {
1724
2116
  filePath = filePath.substring(filePath.indexOf("://") + 3);
1725
2117
  }
@@ -1739,8 +2131,8 @@ function createStorage(transport) {
1739
2131
  const fileName = filePath.split("/").pop() || "file";
1740
2132
  return new File([blob], fileName, { type: blob.type });
1741
2133
  }
1742
- async function deleteFile(path, bucket) {
1743
- let filePath = path;
2134
+ async function deleteObject(key, bucket) {
2135
+ let filePath = key;
1744
2136
  if (filePath && (filePath.startsWith("local://") || filePath.startsWith("s3://"))) {
1745
2137
  filePath = filePath.substring(filePath.indexOf("://") + 3);
1746
2138
  }
@@ -1755,11 +2147,11 @@ function createStorage(transport) {
1755
2147
  } catch (e) {
1756
2148
  if (!(e instanceof Error && "status" in e && e.status === 404)) throw e;
1757
2149
  }
1758
- urlsCache.delete(bucket ? `${bucket}/${path}` : path);
2150
+ urlsCache.delete(bucket ? `${bucket}/${key}` : key);
1759
2151
  }
1760
- async function list(path, options) {
2152
+ async function listObjects(prefix, options) {
1761
2153
  const params = new URLSearchParams();
1762
- if (path) params.set("path", path);
2154
+ if (prefix) params.set("prefix", prefix);
1763
2155
  if (options?.bucket) params.set("bucket", options.bucket);
1764
2156
  if (options?.maxResults) params.set("maxResults", String(options.maxResults));
1765
2157
  if (options?.pageToken) params.set("pageToken", options.pageToken);
@@ -1767,20 +2159,28 @@ function createStorage(transport) {
1767
2159
  return result.data;
1768
2160
  }
1769
2161
  return {
1770
- uploadFile,
1771
- getDownloadURL,
1772
- getFile,
1773
- deleteFile,
1774
- list
2162
+ putObject,
2163
+ getSignedUrl,
2164
+ getObject,
2165
+ deleteObject,
2166
+ listObjects
1775
2167
  };
1776
2168
  }
1777
2169
  function deriveWebSocketUrl(baseUrl) {
2170
+ if (!baseUrl) {
2171
+ if (typeof window !== "undefined") {
2172
+ const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
2173
+ return `${protocol}//${window.location.host}`;
2174
+ }
2175
+ return "";
2176
+ }
1778
2177
  return baseUrl.replace(/^https:\/\//, "wss://").replace(/^http:\/\//, "ws://").replace(/\/$/, "");
1779
2178
  }
1780
2179
  function createRebaseClient(options) {
1781
2180
  const transport = createTransport(options);
1782
2181
  const auth = createAuth(transport, options.auth);
1783
2182
  const admin = createAdmin(transport, options.admin);
2183
+ const cron = createCron(transport, options.cron);
1784
2184
  const storage = createStorage(transport);
1785
2185
  const resolvedWsUrl = options.websocketUrl ?? deriveWebSocketUrl(options.baseUrl);
1786
2186
  let ws;
@@ -1804,14 +2204,14 @@ function createRebaseClient(options) {
1804
2204
  });
1805
2205
  }
1806
2206
  if (!options.onUnauthorized) {
1807
- options.onUnauthorized = async () => {
2207
+ transport.setOnUnauthorized(async () => {
1808
2208
  try {
1809
2209
  await auth.refreshSession();
1810
2210
  return true;
1811
2211
  } catch (e) {
1812
2212
  return false;
1813
2213
  }
1814
- };
2214
+ });
1815
2215
  }
1816
2216
  const collectionClients = /* @__PURE__ */ new Map();
1817
2217
  function collection(slug) {
@@ -1828,7 +2228,8 @@ function createRebaseClient(options) {
1828
2228
  }
1829
2229
  if (typeof prop === "symbol") return void 0;
1830
2230
  if (typeof prop === "string" && prop !== "then" && prop !== "toJSON" && prop !== "$$typeof") {
1831
- return collection(prop);
2231
+ const slug = toSnakeCase(prop);
2232
+ return collection(slug);
1832
2233
  }
1833
2234
  return void 0;
1834
2235
  }
@@ -1836,6 +2237,7 @@ function createRebaseClient(options) {
1836
2237
  const target = {
1837
2238
  auth,
1838
2239
  admin,
2240
+ cron,
1839
2241
  storage,
1840
2242
  ws,
1841
2243
  setToken: transport.setToken,
@@ -1852,19 +2254,10 @@ function createRebaseClient(options) {
1852
2254
  });
1853
2255
  return res.data ?? res;
1854
2256
  },
1855
- data: dataProxy
2257
+ data: dataProxy,
2258
+ email: void 0
1856
2259
  };
1857
- return new Proxy(target, {
1858
- get(obj, prop) {
1859
- if (prop in obj) {
1860
- return obj[prop];
1861
- }
1862
- if (typeof prop === "string" && prop !== "then") {
1863
- return collection(prop);
1864
- }
1865
- return void 0;
1866
- }
1867
- });
2260
+ return target;
1868
2261
  }
1869
2262
  export {
1870
2263
  ApiError,
@@ -1874,9 +2267,11 @@ export {
1874
2267
  createAdmin,
1875
2268
  createAuth,
1876
2269
  createCollectionClient,
2270
+ createCron,
1877
2271
  createMemoryStorage,
1878
2272
  createRebaseClient,
1879
2273
  createStorage,
1880
- createTransport
2274
+ createTransport,
2275
+ rebaseReviver
1881
2276
  };
1882
2277
  //# sourceMappingURL=index.es.js.map