@rebasepro/client 0.9.0 → 0.9.1-canary.0fce67c

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.umd.js DELETED
@@ -1,2484 +0,0 @@
1
- (function(global, factory) {
2
- typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("@rebasepro/types"), require("@rebasepro/common"), require("@rebasepro/utils")) : typeof define === "function" && define.amd ? define([
3
- "exports",
4
- "@rebasepro/types",
5
- "@rebasepro/common",
6
- "@rebasepro/utils"
7
- ], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global["Rebase Client"] = {}, global._rebasepro_types, global._rebasepro_common, global._rebasepro_utils));
8
- })(this, function(exports, _rebasepro_types, _rebasepro_common, _rebasepro_utils) {
9
- Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
10
- //#region src/reviver.ts
11
- function rebaseReviver(_key, value) {
12
- if (value && typeof value === "object" && "__type" in value) {
13
- const record = value;
14
- switch (record.__type) {
15
- case "date":
16
- case "Date": {
17
- if (typeof record.value !== "string") return value;
18
- const date = new Date(record.value);
19
- return isNaN(date.getTime()) ? null : date;
20
- }
21
- case "reference":
22
- case "EntityReference": return new _rebasepro_types.EntityReference({
23
- id: String(record.id),
24
- path: record.path,
25
- driver: record.driver,
26
- databaseId: record.databaseId
27
- });
28
- case "relation":
29
- case "EntityRelation": return new _rebasepro_types.EntityRelation(record.id, record.path, record.data);
30
- case "GeoPoint": return new _rebasepro_types.GeoPoint(record.latitude, record.longitude);
31
- case "Vector": return new _rebasepro_types.Vector(record.value);
32
- default: return value;
33
- }
34
- }
35
- return value;
36
- }
37
- //#endregion
38
- //#region src/transport.ts
39
- function buildQueryString(params) {
40
- if (!params) return "";
41
- const parts = [];
42
- if (params.limit != null) parts.push(`limit=${params.limit}`);
43
- if (params.offset != null) parts.push(`offset=${params.offset}`);
44
- if (params.page != null) parts.push(`page=${params.page}`);
45
- if (params.orderBy) {
46
- const wire = (0, _rebasepro_common.serializeOrderBy)(params.orderBy);
47
- if (wire) parts.push(`orderBy=${encodeURIComponent(wire)}`);
48
- }
49
- if (params.searchString) parts.push(`searchString=${encodeURIComponent(params.searchString)}`);
50
- if (params.include && params.include.length > 0) parts.push(`include=${encodeURIComponent(params.include.join(","))}`);
51
- if (params.logical) {
52
- const root = params.logical;
53
- const serialized = (root.conditions ?? []).map(_rebasepro_common.serializeLogicalCondition).join(",");
54
- parts.push(`${root.type}=${encodeURIComponent(`(${serialized})`)}`);
55
- }
56
- if (params.where) {
57
- const serialized = (0, _rebasepro_common.serializeFilter)(params.where);
58
- for (const [field, value] of Object.entries(serialized)) if (Array.isArray(value)) for (const v of value) parts.push(`${encodeURIComponent(field)}=${encodeURIComponent(v)}`);
59
- else parts.push(`${encodeURIComponent(field)}=${encodeURIComponent(value)}`);
60
- }
61
- return parts.length > 0 ? "?" + parts.join("&") : "";
62
- }
63
- function createTransport(config) {
64
- const fetchFn = config.fetch || globalThis.fetch;
65
- const apiPath = config.apiPath || "/api";
66
- let token = config.token;
67
- let tokenGetter;
68
- let onUnauthorizedHandler = config.onUnauthorized;
69
- function getHeaders(activeToken, init) {
70
- return {
71
- "Content-Type": "application/json",
72
- ...activeToken ? { Authorization: `Bearer ${activeToken}` } : {},
73
- ...init?.headers || {}
74
- };
75
- }
76
- async function request(path, init) {
77
- const url = (config.baseUrl ? config.baseUrl.replace(/\/$/, "") : "") + apiPath + path;
78
- let activeToken = token;
79
- if (tokenGetter) try {
80
- const fetched = await tokenGetter();
81
- if (fetched !== null && fetched !== void 0) activeToken = fetched;
82
- } catch (e) {}
83
- const headers = getHeaders(activeToken, init);
84
- if (init?.body instanceof FormData) delete headers["Content-Type"];
85
- const res = await fetchFn(url, {
86
- ...init,
87
- headers
88
- });
89
- if (res.status === 204) return void 0;
90
- const text = await res.text().catch(() => "");
91
- let body = {};
92
- if (text) try {
93
- body = JSON.parse(text, rebaseReviver);
94
- } catch (e) {}
95
- const getErrorField = (obj, field) => {
96
- const err = obj?.error;
97
- if (err && typeof err === "object" && err !== null) return err[field];
98
- };
99
- if (res.status === 401 && onUnauthorizedHandler) {
100
- if (await onUnauthorizedHandler()) {
101
- let retryToken = token;
102
- if (tokenGetter) try {
103
- const fetched = await tokenGetter();
104
- if (fetched !== null && fetched !== void 0) retryToken = fetched;
105
- } catch (e) {}
106
- const retryHeaders = getHeaders(retryToken, init);
107
- const retryRes = await fetchFn(url, {
108
- ...init,
109
- headers: retryHeaders
110
- });
111
- if (retryRes.status === 204) return void 0;
112
- const retryText = await retryRes.text().catch(() => "");
113
- let retryBody = {};
114
- if (retryText) try {
115
- retryBody = JSON.parse(retryText, rebaseReviver);
116
- } catch (e) {}
117
- if (!retryRes.ok) {
118
- let fallbackMessage = retryRes.statusText;
119
- if (retryRes.status === 404 && !fallbackMessage) fallbackMessage = `Endpoint not found (${init?.method || "GET"} ${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.`;
120
- throw new _rebasepro_types.RebaseApiError(String(getErrorField(retryBody, "message") || fallbackMessage || `Request failed with status ${retryRes.status}`), {
121
- status: retryRes.status,
122
- code: getErrorField(retryBody, "code"),
123
- details: getErrorField(retryBody, "details")
124
- });
125
- }
126
- return retryBody;
127
- }
128
- }
129
- if (!res.ok) {
130
- let fallbackMessage = res.statusText;
131
- if (res.status === 404 && !fallbackMessage) fallbackMessage = `Endpoint not found (${init?.method || "GET"} ${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.`;
132
- throw new _rebasepro_types.RebaseApiError(String(getErrorField(body, "message") || fallbackMessage || `Request failed with status ${res.status}`), {
133
- status: res.status,
134
- code: getErrorField(body, "code"),
135
- details: getErrorField(body, "details")
136
- });
137
- }
138
- return body;
139
- }
140
- return {
141
- request,
142
- setToken(newToken) {
143
- token = newToken || void 0;
144
- },
145
- setAuthTokenGetter(getter) {
146
- tokenGetter = getter;
147
- },
148
- setOnUnauthorized(handler) {
149
- onUnauthorizedHandler = handler;
150
- },
151
- get baseUrl() {
152
- return config.baseUrl ? config.baseUrl.replace(/\/$/, "") : "";
153
- },
154
- get apiPath() {
155
- return apiPath;
156
- },
157
- get fetchFn() {
158
- return fetchFn;
159
- },
160
- getHeaders: (init) => getHeaders(token, init),
161
- resolveToken: async () => {
162
- if (tokenGetter) try {
163
- const fetched = await tokenGetter();
164
- if (fetched !== null && fetched !== void 0) return fetched;
165
- } catch (e) {}
166
- return token || null;
167
- }
168
- };
169
- }
170
- //#endregion
171
- //#region src/auth.ts
172
- /** Map a raw user object from an auth response (`/login`, `/refresh`, `/me`) to a `User`. */
173
- function mapRawUser(raw) {
174
- return {
175
- uid: raw.uid,
176
- email: raw.email ?? null,
177
- displayName: raw.displayName ?? null,
178
- photoURL: raw.photoURL ?? null,
179
- providerId: raw.providerId ?? "password",
180
- isAnonymous: raw.isAnonymous ?? false,
181
- emailVerified: raw.emailVerified,
182
- roles: raw.roles,
183
- metadata: raw.metadata
184
- };
185
- }
186
- /** Placeholder user, used only as a last resort when none can be resolved. */
187
- var EMPTY_USER = {
188
- uid: "",
189
- email: null,
190
- displayName: null,
191
- photoURL: null,
192
- providerId: "password",
193
- isAnonymous: false
194
- };
195
- function createMemoryStorage() {
196
- const store = {};
197
- return {
198
- getItem(key) {
199
- return store[key] ?? null;
200
- },
201
- setItem(key, value) {
202
- store[key] = value;
203
- },
204
- removeItem(key) {
205
- delete store[key];
206
- }
207
- };
208
- }
209
- function detectStorage() {
210
- try {
211
- if (typeof localStorage !== "undefined") {
212
- localStorage.setItem("__rebase_test__", "1");
213
- localStorage.removeItem("__rebase_test__");
214
- return localStorage;
215
- }
216
- } catch (e) {}
217
- return createMemoryStorage();
218
- }
219
- function createAuth(transport, options) {
220
- const opts = options || {};
221
- const storage = opts.storage || detectStorage();
222
- const authPath = opts.authPath || "/auth";
223
- const autoRefresh = opts.autoRefresh !== false;
224
- const persistSession = opts.persistSession !== false;
225
- const authFlowMode = opts.authFlowMode || "json";
226
- const STORAGE_KEY = "rebase_auth";
227
- const REFRESH_BUFFER_MS = 12e4;
228
- const MAX_REFRESH_RETRIES = 5;
229
- const REFRESH_RETRY_BASE_MS = 1e3;
230
- const REFRESH_RETRY_MAX_MS = 3e4;
231
- let currentSession = null;
232
- const listeners = /* @__PURE__ */ new Set();
233
- let refreshTimeout = null;
234
- let inFlightRefresh = null;
235
- let resolveInitialized;
236
- const isInitialized = new Promise((resolve) => {
237
- resolveInitialized = resolve;
238
- });
239
- function authUrl(endpoint) {
240
- return transport.baseUrl + transport.apiPath + authPath + endpoint;
241
- }
242
- function getFetch() {
243
- return transport.fetchFn || globalThis.fetch;
244
- }
245
- function throwApiError(status, body, statusText) {
246
- throw new _rebasepro_types.RebaseApiError(body?.error?.message || body?.message || statusText, {
247
- status,
248
- code: body?.error?.code || body?.code,
249
- details: body?.error?.details || body?.details
250
- });
251
- }
252
- function emit(event, session) {
253
- for (const fn of listeners) try {
254
- fn(event, session);
255
- } catch (e) {}
256
- }
257
- function saveSession(session) {
258
- if (!persistSession || authFlowMode === "cookie") return;
259
- try {
260
- storage.setItem(STORAGE_KEY, JSON.stringify(session));
261
- } catch (e) {}
262
- }
263
- function clearStoredSession() {
264
- try {
265
- storage.removeItem(STORAGE_KEY);
266
- } catch (e) {}
267
- }
268
- function loadStoredSession() {
269
- try {
270
- const raw = storage.getItem(STORAGE_KEY);
271
- if (raw) return JSON.parse(raw);
272
- } catch (e) {}
273
- return null;
274
- }
275
- /**
276
- * A refresh failure is only fatal if the refresh token itself is rejected
277
- * (expired / invalid / forbidden). Network blips, timeouts, and 5xx (e.g. a
278
- * backend restart mid-session) are transient and must NOT log the user out.
279
- */
280
- function isFatalRefreshError(err) {
281
- if (!(err instanceof _rebasepro_types.RebaseApiError)) return false;
282
- if (err.code === "INVALID_TOKEN" || err.code === "TOKEN_EXPIRED") return true;
283
- return err.status === 401 || err.status === 403;
284
- }
285
- async function attemptScheduledRefresh(attempt) {
286
- try {
287
- await refreshSession();
288
- } catch (err) {
289
- if (isFatalRefreshError(err)) {
290
- signOut();
291
- return;
292
- }
293
- if (attempt >= MAX_REFRESH_RETRIES) {
294
- signOut();
295
- return;
296
- }
297
- const backoff = Math.min(REFRESH_RETRY_BASE_MS * 2 ** attempt, REFRESH_RETRY_MAX_MS);
298
- refreshTimeout = setTimeout(() => {
299
- attemptScheduledRefresh(attempt + 1);
300
- }, backoff);
301
- }
302
- }
303
- function scheduleRefresh(expiresAt) {
304
- if (refreshTimeout) clearTimeout(refreshTimeout);
305
- if (!autoRefresh) return;
306
- const delay = expiresAt - REFRESH_BUFFER_MS - Date.now();
307
- if (delay <= 0) {
308
- attemptScheduledRefresh(0);
309
- return;
310
- }
311
- refreshTimeout = setTimeout(() => {
312
- attemptScheduledRefresh(0);
313
- }, delay);
314
- }
315
- function handleAuthResponse(data, event) {
316
- const user = mapRawUser(data.user);
317
- const session = {
318
- accessToken: data.tokens.accessToken,
319
- refreshToken: data.tokens.refreshToken || currentSession?.refreshToken || "",
320
- expiresAt: data.tokens.accessTokenExpiresAt,
321
- user
322
- };
323
- currentSession = session;
324
- saveSession(session);
325
- transport.setToken(session.accessToken);
326
- scheduleRefresh(session.expiresAt);
327
- emit(event || "SIGNED_IN", session);
328
- return session;
329
- }
330
- async function signInWithEmail(email, password) {
331
- const res = await getFetch()(authUrl("/login"), {
332
- method: "POST",
333
- headers: { "Content-Type": "application/json" },
334
- body: JSON.stringify({
335
- email,
336
- password
337
- }),
338
- credentials: authFlowMode === "cookie" ? "include" : void 0
339
- });
340
- const body = await res.json().catch(() => ({}));
341
- if (!res.ok) throwApiError(res.status, body, res.statusText);
342
- const session = handleAuthResponse(body, "SIGNED_IN");
343
- return {
344
- user: session.user,
345
- accessToken: session.accessToken,
346
- refreshToken: session.refreshToken
347
- };
348
- }
349
- async function signUp(email, password, displayName) {
350
- const fetchFn = getFetch();
351
- const payload = {
352
- email,
353
- password
354
- };
355
- if (displayName !== void 0) payload.displayName = displayName;
356
- const res = await fetchFn(authUrl("/register"), {
357
- method: "POST",
358
- headers: { "Content-Type": "application/json" },
359
- body: JSON.stringify(payload),
360
- credentials: authFlowMode === "cookie" ? "include" : void 0
361
- });
362
- const body = await res.json().catch(() => ({}));
363
- if (!res.ok) throwApiError(res.status, body, res.statusText);
364
- const session = handleAuthResponse(body, "SIGNED_IN");
365
- return {
366
- user: session.user,
367
- accessToken: session.accessToken,
368
- refreshToken: session.refreshToken
369
- };
370
- }
371
- /**
372
- * Sign in with Google.
373
- *
374
- * Supports three invocation styles:
375
- * - `signInWithGoogle({ idToken })` — ID-token flow (One Tap / Sign In button)
376
- * - `signInWithGoogle({ accessToken })` — Access-token flow (popup)
377
- * - `signInWithGoogle({ code, redirectUri })` — Authorization code flow (most secure)
378
- */
379
- async function signInWithGoogle(payload) {
380
- const res = await getFetch()(authUrl("/google"), {
381
- method: "POST",
382
- headers: { "Content-Type": "application/json" },
383
- body: JSON.stringify(payload),
384
- credentials: authFlowMode === "cookie" ? "include" : void 0
385
- });
386
- const responseBody = await res.json().catch(() => ({}));
387
- if (!res.ok) throwApiError(res.status, responseBody, res.statusText);
388
- const session = handleAuthResponse(responseBody, "SIGNED_IN");
389
- return {
390
- user: session.user,
391
- accessToken: session.accessToken,
392
- refreshToken: session.refreshToken
393
- };
394
- }
395
- async function signInWithLinkedin(code, redirectUri) {
396
- const res = await getFetch()(authUrl("/linkedin"), {
397
- method: "POST",
398
- headers: { "Content-Type": "application/json" },
399
- body: JSON.stringify({
400
- code,
401
- redirectUri
402
- }),
403
- credentials: authFlowMode === "cookie" ? "include" : void 0
404
- });
405
- const body = await res.json().catch(() => ({}));
406
- if (!res.ok) throwApiError(res.status, body, res.statusText);
407
- const session = handleAuthResponse(body, "SIGNED_IN");
408
- return {
409
- user: session.user,
410
- accessToken: session.accessToken,
411
- refreshToken: session.refreshToken
412
- };
413
- }
414
- /**
415
- * Generic OAuth sign-in. Posts the given payload to `/auth/{providerId}`.
416
- * Use this for any provider registered on the backend.
417
- */
418
- async function signInWithOAuth(providerId, payload) {
419
- const res = await getFetch()(authUrl(`/${providerId}`), {
420
- method: "POST",
421
- headers: { "Content-Type": "application/json" },
422
- body: JSON.stringify(payload),
423
- credentials: authFlowMode === "cookie" ? "include" : void 0
424
- });
425
- const body = await res.json().catch(() => ({}));
426
- if (!res.ok) throwApiError(res.status, body, res.statusText);
427
- const session = handleAuthResponse(body, "SIGNED_IN");
428
- return {
429
- user: session.user,
430
- accessToken: session.accessToken,
431
- refreshToken: session.refreshToken
432
- };
433
- }
434
- async function signInWithGitHub(code, redirectUri) {
435
- return signInWithOAuth("github", {
436
- code,
437
- redirectUri
438
- });
439
- }
440
- async function signInWithMicrosoft(code, redirectUri) {
441
- return signInWithOAuth("microsoft", {
442
- code,
443
- redirectUri
444
- });
445
- }
446
- async function signInWithApple(code, redirectUri, user) {
447
- return signInWithOAuth("apple", {
448
- code,
449
- redirectUri,
450
- user
451
- });
452
- }
453
- async function signInWithFacebook(code, redirectUri) {
454
- return signInWithOAuth("facebook", {
455
- code,
456
- redirectUri
457
- });
458
- }
459
- async function signInWithTwitter(code, redirectUri, codeVerifier) {
460
- return signInWithOAuth("twitter", {
461
- code,
462
- redirectUri,
463
- codeVerifier
464
- });
465
- }
466
- async function signInWithDiscord(code, redirectUri) {
467
- return signInWithOAuth("discord", {
468
- code,
469
- redirectUri
470
- });
471
- }
472
- async function signInWithGitLab(code, redirectUri) {
473
- return signInWithOAuth("gitlab", {
474
- code,
475
- redirectUri
476
- });
477
- }
478
- async function signInWithBitbucket(code, redirectUri) {
479
- return signInWithOAuth("bitbucket", {
480
- code,
481
- redirectUri
482
- });
483
- }
484
- async function signInWithSlack(code, redirectUri) {
485
- return signInWithOAuth("slack", {
486
- code,
487
- redirectUri
488
- });
489
- }
490
- async function signInWithSpotify(code, redirectUri) {
491
- return signInWithOAuth("spotify", {
492
- code,
493
- redirectUri
494
- });
495
- }
496
- async function signOut() {
497
- const fetchFn = getFetch();
498
- try {
499
- if (authFlowMode === "cookie" || currentSession?.refreshToken) await fetchFn(authUrl("/logout"), {
500
- method: "POST",
501
- headers: { "Content-Type": "application/json" },
502
- body: JSON.stringify({ refreshToken: currentSession?.refreshToken }),
503
- credentials: authFlowMode === "cookie" ? "include" : void 0
504
- });
505
- } catch (e) {}
506
- currentSession = null;
507
- clearStoredSession();
508
- if (refreshTimeout) {
509
- clearTimeout(refreshTimeout);
510
- refreshTimeout = null;
511
- }
512
- transport.setToken(null);
513
- emit("SIGNED_OUT", null);
514
- }
515
- function refreshSession() {
516
- if (inFlightRefresh) return inFlightRefresh;
517
- inFlightRefresh = doRefreshSession().finally(() => {
518
- inFlightRefresh = null;
519
- });
520
- return inFlightRefresh;
521
- }
522
- async function doRefreshSession() {
523
- if (authFlowMode !== "cookie" && !currentSession?.refreshToken) throw new Error("No active session to refresh");
524
- const res = await getFetch()(authUrl("/refresh"), {
525
- method: "POST",
526
- headers: { "Content-Type": "application/json" },
527
- body: JSON.stringify({ refreshToken: currentSession?.refreshToken }),
528
- credentials: authFlowMode === "cookie" ? "include" : void 0
529
- });
530
- const body = await res.json().catch(() => ({}));
531
- if (!res.ok) throwApiError(res.status, body, res.statusText);
532
- const accessToken = body.tokens.accessToken;
533
- transport.setToken(accessToken);
534
- let user = currentSession?.user;
535
- if (body.user && typeof body.user.uid === "string") user = mapRawUser(body.user);
536
- else if (!user || !user.uid) try {
537
- user = await getUser();
538
- } catch {}
539
- const session = {
540
- accessToken,
541
- refreshToken: body.tokens.refreshToken || currentSession?.refreshToken || "",
542
- expiresAt: body.tokens.accessTokenExpiresAt,
543
- user: user ?? EMPTY_USER
544
- };
545
- currentSession = session;
546
- saveSession(session);
547
- transport.setToken(session.accessToken);
548
- scheduleRefresh(session.expiresAt);
549
- emit("TOKEN_REFRESHED", session);
550
- return session;
551
- }
552
- async function getUser() {
553
- return (await transport.request(authPath + "/me", { method: "GET" })).user;
554
- }
555
- /**
556
- * Resolve an email to a minimal public profile (`uid`, `displayName`,
557
- * `photoURL`) for invite-by-email flows. Returns `null` when no account
558
- * matches. Requires the backend to opt in via `auth.allowUserLookup`;
559
- * otherwise the endpoint is absent and this rejects.
560
- */
561
- async function findUserByEmail(email) {
562
- return (await transport.request(authPath + "/find-user", {
563
- method: "POST",
564
- body: JSON.stringify({ email })
565
- })).user;
566
- }
567
- async function updateUser(updates) {
568
- const data = await transport.request(authPath + "/me", {
569
- method: "PATCH",
570
- body: JSON.stringify(updates)
571
- });
572
- if (currentSession) {
573
- currentSession = {
574
- ...currentSession,
575
- user: data.user
576
- };
577
- saveSession(currentSession);
578
- emit("USER_UPDATED", currentSession);
579
- }
580
- return data.user;
581
- }
582
- async function resetPasswordForEmail(email) {
583
- const res = await getFetch()(authUrl("/forgot-password"), {
584
- method: "POST",
585
- headers: { "Content-Type": "application/json" },
586
- body: JSON.stringify({ email })
587
- });
588
- const body = await res.json().catch(() => ({}));
589
- if (!res.ok) throwApiError(res.status, body, res.statusText);
590
- return body;
591
- }
592
- async function resetPassword(token, password) {
593
- const res = await getFetch()(authUrl("/reset-password"), {
594
- method: "POST",
595
- headers: { "Content-Type": "application/json" },
596
- body: JSON.stringify({
597
- token,
598
- password
599
- })
600
- });
601
- const body = await res.json().catch(() => ({}));
602
- if (!res.ok) throwApiError(res.status, body, res.statusText);
603
- return body;
604
- }
605
- async function changePassword(oldPassword, newPassword) {
606
- return transport.request(authPath + "/change-password", {
607
- method: "POST",
608
- body: JSON.stringify({
609
- oldPassword,
610
- newPassword
611
- })
612
- });
613
- }
614
- async function sendVerificationEmail() {
615
- return transport.request(authPath + "/send-verification", { method: "POST" });
616
- }
617
- async function verifyEmail(token) {
618
- const res = await getFetch()(authUrl("/verify-email?token=" + encodeURIComponent(token)), {
619
- method: "GET",
620
- headers: { "Content-Type": "application/json" }
621
- });
622
- const body = await res.json().catch(() => ({}));
623
- if (!res.ok) throwApiError(res.status, body, res.statusText);
624
- return body;
625
- }
626
- async function sendMagicLink(email) {
627
- const res = await getFetch()(authUrl("/magic-link"), {
628
- method: "POST",
629
- headers: { "Content-Type": "application/json" },
630
- body: JSON.stringify({ email })
631
- });
632
- const body = await res.json().catch(() => ({}));
633
- if (!res.ok) throwApiError(res.status, body, res.statusText);
634
- return body;
635
- }
636
- async function verifyMagicLink(token) {
637
- const res = await getFetch()(authUrl("/magic-link/verify"), {
638
- method: "POST",
639
- headers: { "Content-Type": "application/json" },
640
- body: JSON.stringify({ token }),
641
- credentials: authFlowMode === "cookie" ? "include" : void 0
642
- });
643
- const body = await res.json().catch(() => ({}));
644
- if (!res.ok) throwApiError(res.status, body, res.statusText);
645
- const session = handleAuthResponse(body, "SIGNED_IN");
646
- return {
647
- user: session.user,
648
- accessToken: session.accessToken,
649
- refreshToken: session.refreshToken
650
- };
651
- }
652
- async function getSessions() {
653
- return (await transport.request(authPath + "/sessions", { method: "GET" })).sessions;
654
- }
655
- async function revokeSession(sessionId) {
656
- return transport.request(authPath + "/sessions/" + encodeURIComponent(sessionId), { method: "DELETE" });
657
- }
658
- async function revokeAllSessions() {
659
- const result = await transport.request(authPath + "/sessions", { method: "DELETE" });
660
- currentSession = null;
661
- clearStoredSession();
662
- if (refreshTimeout) {
663
- clearTimeout(refreshTimeout);
664
- refreshTimeout = null;
665
- }
666
- transport.setToken(null);
667
- emit("SIGNED_OUT", null);
668
- return result;
669
- }
670
- async function getAuthConfig() {
671
- const res = await getFetch()(authUrl("/config"), {
672
- method: "GET",
673
- headers: { "Content-Type": "application/json" }
674
- });
675
- const body = await res.json().catch(() => ({}));
676
- if (!res.ok) throwApiError(res.status, body, res.statusText);
677
- return body;
678
- }
679
- function getSession() {
680
- return currentSession;
681
- }
682
- function onAuthStateChange(callback) {
683
- listeners.add(callback);
684
- return () => listeners.delete(callback);
685
- }
686
- if (persistSession) {
687
- const stored = loadStoredSession();
688
- if (stored && stored.accessToken) if (stored.expiresAt > Date.now()) {
689
- currentSession = stored;
690
- transport.setToken(stored.accessToken);
691
- scheduleRefresh(stored.expiresAt);
692
- resolveInitialized();
693
- } else if (authFlowMode === "cookie" || stored.refreshToken) {
694
- currentSession = stored;
695
- refreshSession().then(() => {
696
- resolveInitialized();
697
- }).catch(() => {
698
- currentSession = null;
699
- clearStoredSession();
700
- transport.setToken(null);
701
- resolveInitialized();
702
- });
703
- } else resolveInitialized();
704
- else if (authFlowMode === "cookie") refreshSession().then(() => {
705
- resolveInitialized();
706
- }).catch(() => {
707
- resolveInitialized();
708
- });
709
- else resolveInitialized();
710
- } else resolveInitialized();
711
- return {
712
- signInWithEmail,
713
- signUp,
714
- signInWithGoogle,
715
- signInWithLinkedin,
716
- signInWithOAuth,
717
- signInWithGitHub,
718
- signInWithMicrosoft,
719
- signInWithApple,
720
- signInWithFacebook,
721
- signInWithTwitter,
722
- signInWithDiscord,
723
- signInWithGitLab,
724
- signInWithBitbucket,
725
- signInWithSlack,
726
- signInWithSpotify,
727
- signOut,
728
- refreshSession,
729
- getUser,
730
- findUserByEmail,
731
- updateUser,
732
- resetPasswordForEmail,
733
- resetPassword,
734
- changePassword,
735
- sendVerificationEmail,
736
- verifyEmail,
737
- sendMagicLink,
738
- verifyMagicLink,
739
- getSessions,
740
- revokeSession,
741
- revokeAllSessions,
742
- getAuthConfig,
743
- getSession,
744
- onAuthStateChange,
745
- isInitialized: () => isInitialized
746
- };
747
- }
748
- function createCookieStorage(options = {}) {
749
- const defaultOptions = {
750
- path: "/",
751
- sameSite: "Lax",
752
- ...options
753
- };
754
- return {
755
- getItem(key) {
756
- if (typeof document === "undefined") return null;
757
- const nameEQ = encodeURIComponent(key) + "=";
758
- const ca = document.cookie.split(";");
759
- for (let i = 0; i < ca.length; i++) {
760
- let c = ca[i];
761
- while (c.charAt(0) === " ") c = c.substring(1, c.length);
762
- if (c.indexOf(nameEQ) === 0) return decodeURIComponent(c.substring(nameEQ.length, c.length));
763
- }
764
- return null;
765
- },
766
- setItem(key, value) {
767
- if (typeof document === "undefined") return;
768
- let cookieStr = `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
769
- if (defaultOptions.path) cookieStr += `; path=${defaultOptions.path}`;
770
- if (defaultOptions.domain) cookieStr += `; domain=${defaultOptions.domain}`;
771
- if (defaultOptions.maxAge !== void 0) cookieStr += `; max-age=${defaultOptions.maxAge}`;
772
- else cookieStr += `; max-age=${365 * 24 * 60 * 60}`;
773
- if (defaultOptions.secure) cookieStr += "; secure";
774
- if (defaultOptions.sameSite) cookieStr += `; samesite=${defaultOptions.sameSite}`;
775
- document.cookie = cookieStr;
776
- },
777
- removeItem(key) {
778
- if (typeof document === "undefined") return;
779
- let cookieStr = `${encodeURIComponent(key)}=; path=${defaultOptions.path || "/"}; max-age=-1`;
780
- if (defaultOptions.domain) cookieStr += `; domain=${defaultOptions.domain}`;
781
- document.cookie = cookieStr;
782
- }
783
- };
784
- }
785
- //#endregion
786
- //#region src/admin.ts
787
- function createAdmin(transport, options) {
788
- const adminPath = (options || {}).adminPath || "/admin";
789
- async function listUsers() {
790
- return transport.request(adminPath + "/users", { method: "GET" });
791
- }
792
- async function listUsersPaginated(options) {
793
- const params = new URLSearchParams();
794
- if (options?.limit !== void 0) params.set("limit", String(options.limit));
795
- if (options?.offset !== void 0) params.set("offset", String(options.offset));
796
- if (options?.search) params.set("search", options.search);
797
- if (options?.orderBy) params.set("orderBy", options.orderBy);
798
- if (options?.orderDir) params.set("orderDir", options.orderDir);
799
- const qs = params.toString();
800
- return transport.request(adminPath + "/users" + (qs ? "?" + qs : ""), { method: "GET" });
801
- }
802
- async function getUser(userId) {
803
- return transport.request(adminPath + "/users/" + encodeURIComponent(userId), { method: "GET" });
804
- }
805
- async function createUser(data) {
806
- return transport.request(adminPath + "/users", {
807
- method: "POST",
808
- body: JSON.stringify(data)
809
- });
810
- }
811
- async function updateUser(userId, data) {
812
- return transport.request(adminPath + "/users/" + encodeURIComponent(userId), {
813
- method: "PUT",
814
- body: JSON.stringify(data)
815
- });
816
- }
817
- async function deleteUser(userId) {
818
- return transport.request(adminPath + "/users/" + encodeURIComponent(userId), { method: "DELETE" });
819
- }
820
- async function resetPassword(userId, options) {
821
- return transport.request(adminPath + "/users/" + encodeURIComponent(userId) + "/reset-password", {
822
- method: "POST",
823
- ...options?.password ? { body: JSON.stringify({ password: options.password }) } : {}
824
- });
825
- }
826
- async function listRoles() {
827
- return transport.request(adminPath + "/roles", { method: "GET" });
828
- }
829
- async function bootstrap() {
830
- return transport.request(adminPath + "/bootstrap", { method: "POST" });
831
- }
832
- return {
833
- listUsers,
834
- listUsersPaginated,
835
- getUser,
836
- createUser,
837
- updateUser,
838
- deleteUser,
839
- resetPassword,
840
- listRoles,
841
- bootstrap
842
- };
843
- }
844
- //#endregion
845
- //#region src/cron.ts
846
- function createCron(transport, options) {
847
- const cronPath = options?.cronPath || "/cron";
848
- async function listJobs() {
849
- return transport.request(cronPath, { method: "GET" });
850
- }
851
- async function getJob(jobId) {
852
- return transport.request(cronPath + "/" + encodeURIComponent(jobId), { method: "GET" });
853
- }
854
- async function triggerJob(jobId) {
855
- return transport.request(cronPath + "/" + encodeURIComponent(jobId) + "/trigger", { method: "POST" });
856
- }
857
- async function getJobLogs(jobId, options) {
858
- const params = new URLSearchParams();
859
- if (options?.limit !== void 0) params.set("limit", String(options.limit));
860
- const qs = params.toString();
861
- return transport.request(cronPath + "/" + encodeURIComponent(jobId) + "/logs" + (qs ? "?" + qs : ""), { method: "GET" });
862
- }
863
- async function toggleJob(jobId, enabled) {
864
- return transport.request(cronPath + "/" + encodeURIComponent(jobId), {
865
- method: "PUT",
866
- body: JSON.stringify({ enabled })
867
- });
868
- }
869
- return {
870
- listJobs,
871
- getJob,
872
- triggerJob,
873
- getJobLogs,
874
- toggleJob
875
- };
876
- }
877
- //#endregion
878
- //#region src/api-keys.ts
879
- /**
880
- * Creates a client for managing API keys via the admin routes.
881
- *
882
- * @param transport - The shared HTTP transport created by `createTransport`.
883
- * @param options - Optional overrides (e.g. a custom base path).
884
- */
885
- function createApiKeys(transport, options) {
886
- const apiKeysPath = options?.apiKeysPath || "/admin/api-keys";
887
- /** List all API keys (masked). */
888
- async function listKeys() {
889
- return transport.request(apiKeysPath, { method: "GET" });
890
- }
891
- /** Get a single API key by ID (masked). */
892
- async function getKey(id) {
893
- return transport.request(apiKeysPath + "/" + encodeURIComponent(id), { method: "GET" });
894
- }
895
- /** Create a new API key. The full secret is included in the response. */
896
- async function createKey(data) {
897
- return transport.request(apiKeysPath, {
898
- method: "POST",
899
- body: JSON.stringify(data)
900
- });
901
- }
902
- /** Update an existing API key. */
903
- async function updateKey(id, data) {
904
- return transport.request(apiKeysPath + "/" + encodeURIComponent(id), {
905
- method: "PUT",
906
- body: JSON.stringify(data)
907
- });
908
- }
909
- /** Revoke (soft-delete) an API key. */
910
- async function revokeKey(id) {
911
- return transport.request(apiKeysPath + "/" + encodeURIComponent(id), { method: "DELETE" });
912
- }
913
- return {
914
- listKeys,
915
- getKey,
916
- createKey,
917
- updateKey,
918
- revokeKey
919
- };
920
- }
921
- //#endregion
922
- //#region src/sdk_query_builder.ts
923
- /**
924
- * SDK Query Builder — returns flat rows (`FindResult<M>`) instead of
925
- * Entity-wrapped results (`FindResponse<M>`).
926
- *
927
- * @example
928
- * const { data } = await rebase.data.posts
929
- * .where("status", "==", "published")
930
- * .orderBy("created_at", "desc")
931
- * .limit(10)
932
- * .find();
933
- *
934
- * console.log(data[0].title); // flat access
935
- */
936
- var SDKQueryBuilder = class {
937
- collection;
938
- params = { where: {} };
939
- constructor(collection) {
940
- this.collection = collection;
941
- }
942
- where(columnOrCondition, operator, value) {
943
- if (typeof columnOrCondition === "object" && columnOrCondition !== null && "type" in columnOrCondition) {
944
- this.params.logical = columnOrCondition;
945
- return this;
946
- }
947
- if (!this.params.where) this.params.where = {};
948
- const column = columnOrCondition;
949
- const condition = [operator, value];
950
- const existing = this.params.where[column];
951
- if (existing === void 0) this.params.where[column] = condition;
952
- else if (Array.isArray(existing) && existing.length > 0 && Array.isArray(existing[0])) this.params.where[column].push(condition);
953
- else {
954
- let firstCondition;
955
- if (Array.isArray(existing) && existing.length === 2 && typeof existing[0] === "string") firstCondition = existing;
956
- else firstCondition = ["==", existing];
957
- this.params.where[column] = [firstCondition, condition];
958
- }
959
- return this;
960
- }
961
- /**
962
- * Order the results by a specific column.
963
- */
964
- orderBy(column, direction = "asc") {
965
- this.params.orderBy = [column, direction];
966
- return this;
967
- }
968
- /**
969
- * Limit the number of results returned.
970
- */
971
- limit(count) {
972
- this.params.limit = count;
973
- return this;
974
- }
975
- /**
976
- * Skip the first N results.
977
- */
978
- offset(count) {
979
- this.params.offset = count;
980
- return this;
981
- }
982
- /**
983
- * Set a free-text search string if supported by the backend.
984
- */
985
- search(searchString) {
986
- this.params.searchString = searchString;
987
- return this;
988
- }
989
- /**
990
- * Include related entities in the response.
991
- * Relations will be populated with full data instead of just IDs.
992
- *
993
- * @param relations - Relation names to include, or "*" for all.
994
- * @example
995
- * client.data.posts.include("tags", "author").find()
996
- */
997
- include(...relations) {
998
- this.params.include = relations;
999
- return this;
1000
- }
1001
- /**
1002
- * Execute the find query and return the results as flat rows.
1003
- */
1004
- async find() {
1005
- return this.collection.find(this.params);
1006
- }
1007
- /**
1008
- * Count the records matching this query.
1009
- */
1010
- async count() {
1011
- if (!this.collection.count) throw new Error("count() is not supported by this collection client.");
1012
- return this.collection.count(this.params);
1013
- }
1014
- /**
1015
- * Listen to realtime updates matching this query.
1016
- */
1017
- listen(onUpdate, onError) {
1018
- if (!this.collection.listen) throw new Error("Listen is only available when RebaseClient is configured with a websocketUrl.");
1019
- return this.collection.listen(this.params, onUpdate, onError);
1020
- }
1021
- };
1022
- //#endregion
1023
- //#region src/collection.ts
1024
- function createCollectionClient(transport, slug, ws) {
1025
- const basePath = `/data/${slug}`;
1026
- const client = {
1027
- async find(params) {
1028
- const qs = buildQueryString(params);
1029
- const raw = await transport.request(basePath + qs, { method: "GET" });
1030
- return {
1031
- data: raw.data || [],
1032
- meta: raw.meta
1033
- };
1034
- },
1035
- async findById(id) {
1036
- try {
1037
- const raw = await transport.request(`${basePath}/${encodeURIComponent(String(id))}`, { method: "GET" });
1038
- if (!raw) return void 0;
1039
- return raw;
1040
- } catch (err) {
1041
- if (err instanceof _rebasepro_types.RebaseApiError && err.status === 404) return;
1042
- throw err;
1043
- }
1044
- },
1045
- async create(data, id) {
1046
- const body = { ...data };
1047
- if (id !== void 0) body.id = id;
1048
- return await transport.request(basePath, {
1049
- method: "POST",
1050
- body: JSON.stringify(body)
1051
- });
1052
- },
1053
- async update(id, data) {
1054
- return await transport.request(`${basePath}/${encodeURIComponent(String(id))}`, {
1055
- method: "PUT",
1056
- body: JSON.stringify(data)
1057
- });
1058
- },
1059
- async delete(id) {
1060
- await transport.request(`${basePath}/${encodeURIComponent(String(id))}`, { method: "DELETE" });
1061
- },
1062
- async count(params) {
1063
- const qs = buildQueryString({
1064
- ...params,
1065
- limit: void 0,
1066
- offset: void 0
1067
- });
1068
- return (await transport.request(basePath + "/count" + qs, { method: "GET" })).count ?? 0;
1069
- },
1070
- where(columnOrCondition, operator, value) {
1071
- const builder = new SDKQueryBuilder(client);
1072
- if (typeof columnOrCondition === "object") return builder.where(columnOrCondition);
1073
- return builder.where(columnOrCondition, operator, value);
1074
- },
1075
- orderBy(column, direction) {
1076
- return new SDKQueryBuilder(client).orderBy(column, direction);
1077
- },
1078
- limit(count) {
1079
- return new SDKQueryBuilder(client).limit(count);
1080
- },
1081
- offset(count) {
1082
- return new SDKQueryBuilder(client).offset(count);
1083
- },
1084
- search(searchString) {
1085
- return new SDKQueryBuilder(client).search(searchString);
1086
- },
1087
- include(...relations) {
1088
- return new SDKQueryBuilder(client).include(...relations);
1089
- }
1090
- };
1091
- if (ws) {
1092
- client.listen = (params, onUpdate, onError) => {
1093
- let active = true;
1094
- let lastUpdateId = 0;
1095
- const unsub = ws.listenCollection({
1096
- path: slug,
1097
- filter: params?.where,
1098
- limit: params?.limit,
1099
- startAfter: params?.offset ? String(params.offset) : void 0,
1100
- orderBy: params?.orderBy?.[0],
1101
- order: params?.orderBy?.[1],
1102
- searchString: params?.searchString
1103
- }, (incomingRows) => {
1104
- const currentUpdateId = ++lastUpdateId;
1105
- const requestedLimit = params?.limit || 20;
1106
- const offset = params?.offset || 0;
1107
- const rows = incomingRows;
1108
- const heuristicTotal = rows.length;
1109
- const heuristicHasMore = rows.length >= requestedLimit;
1110
- if (client.count) client.count(params).then((total) => {
1111
- if (active && currentUpdateId === lastUpdateId) onUpdate({
1112
- data: rows,
1113
- meta: {
1114
- total,
1115
- limit: requestedLimit,
1116
- offset,
1117
- hasMore: offset + rows.length < total
1118
- }
1119
- });
1120
- }).catch(() => {
1121
- if (active && currentUpdateId === lastUpdateId) onUpdate({
1122
- data: rows,
1123
- meta: {
1124
- total: heuristicTotal,
1125
- limit: requestedLimit,
1126
- offset,
1127
- hasMore: heuristicHasMore
1128
- }
1129
- });
1130
- });
1131
- else onUpdate({
1132
- data: rows,
1133
- meta: {
1134
- total: heuristicTotal,
1135
- limit: requestedLimit,
1136
- offset,
1137
- hasMore: heuristicHasMore
1138
- }
1139
- });
1140
- }, onError);
1141
- return () => {
1142
- active = false;
1143
- unsub();
1144
- };
1145
- };
1146
- client.listenById = (id, onUpdate, onError) => {
1147
- return ws.listenOne({
1148
- path: slug,
1149
- id: String(id)
1150
- }, (row) => {
1151
- if (row) onUpdate(row);
1152
- else onUpdate(void 0);
1153
- }, onError);
1154
- };
1155
- }
1156
- return client;
1157
- }
1158
- //#endregion
1159
- //#region src/functions.ts
1160
- /**
1161
- * Create a `FunctionsClient` backed by the given transport.
1162
- *
1163
- * The transport already handles:
1164
- * - Base URL resolution
1165
- * - JWT injection via `Authorization: Bearer`
1166
- * - 401 retry / `onUnauthorized` flow
1167
- * - Consistent error throwing via `RebaseApiError`
1168
- *
1169
- * @internal
1170
- */
1171
- function createFunctionsClient(transport) {
1172
- return { async invoke(name, payload, options) {
1173
- const method = options?.method ?? "POST";
1174
- const subPath = options?.path ? `/${options.path.replace(/^\//, "")}` : "";
1175
- const routePath = `/functions/${encodeURIComponent(name)}${subPath}`;
1176
- const init = { method };
1177
- if (payload !== void 0 && method !== "GET") init.body = JSON.stringify(payload);
1178
- if (options?.headers) init.headers = options.headers;
1179
- return transport.request(routePath, init);
1180
- } };
1181
- }
1182
- //#endregion
1183
- //#region src/storage.ts
1184
- /**
1185
- * Create a StorageSource that talks to the Rebase backend REST API.
1186
- *
1187
- * @param transport - HTTP transport instance
1188
- * @param storageId - Optional storage-source key for multi-backend routing.
1189
- * When set, it is forwarded to the server so the correct
1190
- * `StorageController` is resolved from the registry.
1191
- */
1192
- function createStorage(transport, storageId) {
1193
- const urlsCache = /* @__PURE__ */ new Map();
1194
- /** Append ?storageId=... to a path when multi-backend routing is active. */
1195
- const withStorageId = (path) => {
1196
- if (!storageId) return path;
1197
- return `${path}${path.includes("?") ? "&" : "?"}storageId=${encodeURIComponent(storageId)}`;
1198
- };
1199
- async function putObject({ file, key, metadata, bucket, public: isPublic }) {
1200
- const formData = new FormData();
1201
- formData.append("file", file);
1202
- let effectiveKey = key;
1203
- if (isPublic && effectiveKey && !(0, _rebasepro_types.isPublicStoragePath)(effectiveKey)) effectiveKey = `${_rebasepro_types.PUBLIC_STORAGE_PREFIX}${effectiveKey.replace(/^\/+/, "")}`;
1204
- if (effectiveKey) formData.append("key", effectiveKey);
1205
- if (bucket) formData.append("bucket", bucket);
1206
- if (storageId) formData.append("storageId", storageId);
1207
- if (metadata) {
1208
- for (const [key, value] of Object.entries(metadata)) if (value !== void 0 && value !== null) formData.append(`metadata_${key}`, typeof value === "string" ? value : JSON.stringify(value));
1209
- }
1210
- return (await transport.request(withStorageId("/storage/upload"), {
1211
- method: "POST",
1212
- body: formData,
1213
- headers: {}
1214
- })).data;
1215
- }
1216
- async function getSignedUrl(keyOrUrl, bucket) {
1217
- const cacheKey = bucket ? `${bucket}/${keyOrUrl}` : keyOrUrl;
1218
- const cachedEntry = urlsCache.get(cacheKey);
1219
- if (cachedEntry) {
1220
- if (!cachedEntry.expiresAt || cachedEntry.expiresAt > Date.now()) return cachedEntry.config;
1221
- urlsCache.delete(cacheKey);
1222
- }
1223
- let filePath = keyOrUrl;
1224
- if (filePath && (filePath.startsWith("local://") || filePath.startsWith("s3://") || filePath.startsWith("gs://"))) filePath = filePath.substring(filePath.indexOf("://") + 3);
1225
- if (bucket && filePath && !filePath.startsWith(bucket)) filePath = `${bucket}/${filePath}`;
1226
- if (!filePath || filePath.trim() === "" || filePath === "/") return {
1227
- url: null,
1228
- fileNotFound: true
1229
- };
1230
- if ((0, _rebasepro_types.isPublicStoragePath)(filePath)) {
1231
- const publicConfig = { url: withStorageId(`${transport.baseUrl}${transport.apiPath}/storage/file/${filePath}`) };
1232
- urlsCache.set(cacheKey, { config: publicConfig });
1233
- return publicConfig;
1234
- }
1235
- try {
1236
- const result = await transport.request(withStorageId(`/storage/metadata/${filePath}`));
1237
- if (result.data.public) {
1238
- const publicConfig = {
1239
- url: withStorageId(`${transport.baseUrl}${transport.apiPath}/storage/file/${filePath}`),
1240
- metadata: result.data
1241
- };
1242
- urlsCache.set(cacheKey, { config: publicConfig });
1243
- return publicConfig;
1244
- }
1245
- const scopedToken = result.data.token;
1246
- const tokenQuery = scopedToken ? `?token=${scopedToken}` : "";
1247
- const downloadConfig = {
1248
- url: withStorageId(`${transport.baseUrl}${transport.apiPath}/storage/file/${filePath}${tokenQuery}`),
1249
- metadata: result.data
1250
- };
1251
- const expiresAt = result.data.tokenExpiresIn ? Date.now() + (result.data.tokenExpiresIn - 10) * 1e3 : void 0;
1252
- urlsCache.set(cacheKey, {
1253
- config: downloadConfig,
1254
- expiresAt
1255
- });
1256
- return downloadConfig;
1257
- } catch (e) {
1258
- if (e instanceof Error && "status" in e && e.status === 404) return {
1259
- url: null,
1260
- fileNotFound: true
1261
- };
1262
- throw e;
1263
- }
1264
- }
1265
- async function getObject(key, bucket) {
1266
- const downloadConfig = await getSignedUrl(key, bucket);
1267
- if (downloadConfig.fileNotFound || !downloadConfig.url) return null;
1268
- const response = await transport.fetchFn(downloadConfig.url, { headers: {} });
1269
- if (response.status === 404) return null;
1270
- if (!response.ok) throw new Error("Failed to get file");
1271
- const blob = await response.blob();
1272
- const fileName = (bucket ? `${bucket}/${key}` : key).split("/").pop() || "file";
1273
- return new File([blob], fileName, { type: blob.type });
1274
- }
1275
- async function deleteObject(key, bucket) {
1276
- let filePath = key;
1277
- if (filePath && (filePath.startsWith("local://") || filePath.startsWith("s3://") || filePath.startsWith("gs://"))) filePath = filePath.substring(filePath.indexOf("://") + 3);
1278
- if (bucket && filePath && !filePath.startsWith(bucket)) filePath = `${bucket}/${filePath}`;
1279
- if (!filePath || filePath.trim() === "" || filePath === "/") return;
1280
- try {
1281
- await transport.request(withStorageId(`/storage/file/${filePath}`), { method: "DELETE" });
1282
- } catch (e) {
1283
- if (!(e instanceof Error && "status" in e && e.status === 404)) throw e;
1284
- }
1285
- urlsCache.delete(bucket ? `${bucket}/${key}` : key);
1286
- }
1287
- async function listObjects(prefix, options) {
1288
- const params = new URLSearchParams();
1289
- if (prefix) params.set("prefix", prefix);
1290
- if (options?.bucket) params.set("bucket", options.bucket);
1291
- if (options?.maxResults) params.set("maxResults", String(options.maxResults));
1292
- if (options?.pageToken) params.set("pageToken", options.pageToken);
1293
- if (storageId) params.set("storageId", storageId);
1294
- return (await transport.request(`/storage/list?${params.toString()}`)).data;
1295
- }
1296
- return {
1297
- putObject,
1298
- getSignedUrl,
1299
- getObject,
1300
- deleteObject,
1301
- listObjects
1302
- };
1303
- }
1304
- //#endregion
1305
- //#region src/storage-registry.ts
1306
- /**
1307
- * Default implementation of the client-side `StorageSourceRegistry`.
1308
- */
1309
- var ClientStorageSourceRegistry = class ClientStorageSourceRegistry {
1310
- sources = /* @__PURE__ */ new Map();
1311
- /**
1312
- * Register a storage source.
1313
- * @param key - Unique key matching a `StorageSourceDefinition.key`
1314
- * @param source - The `StorageSource` instance
1315
- */
1316
- register(key, source) {
1317
- this.sources.set(key, source);
1318
- }
1319
- getDefault() {
1320
- const source = this.sources.get(_rebasepro_types.DEFAULT_STORAGE_SOURCE_KEY);
1321
- if (!source) throw new Error(`[StorageSourceRegistry] No default storage source registered. Register one with key "${_rebasepro_types.DEFAULT_STORAGE_SOURCE_KEY}".`);
1322
- return source;
1323
- }
1324
- get(key) {
1325
- if (key === void 0 || key === null) return this.sources.get(_rebasepro_types.DEFAULT_STORAGE_SOURCE_KEY);
1326
- return this.sources.get(key);
1327
- }
1328
- getOrDefault(key) {
1329
- if (key === void 0 || key === null) return this.getDefault();
1330
- const source = this.sources.get(key);
1331
- if (source) return source;
1332
- console.warn(`[StorageSourceRegistry] Storage source "${key}" not found, falling back to "${_rebasepro_types.DEFAULT_STORAGE_SOURCE_KEY}".`);
1333
- return this.getDefault();
1334
- }
1335
- has(key) {
1336
- return this.sources.has(key);
1337
- }
1338
- list() {
1339
- return Array.from(this.sources.keys());
1340
- }
1341
- /**
1342
- * Build a registry from `StorageSourceDefinition[]` and an HTTP transport.
1343
- *
1344
- * - Sources with `transport: "server"` are auto-wired via `createStorage(transport, key)`.
1345
- * - Sources with `transport: "direct"` are **not** auto-wired — they must
1346
- * be registered manually after this call (e.g. via a Firebase hook).
1347
- *
1348
- * @param definitions - Array of storage source definitions
1349
- * @param transport - HTTP transport for server-backed sources
1350
- */
1351
- static fromDefinitions(definitions, transport) {
1352
- const registry = new ClientStorageSourceRegistry();
1353
- for (const def of definitions) if (def.transport === "server") {
1354
- const source = createStorage(transport, def.key === _rebasepro_types.DEFAULT_STORAGE_SOURCE_KEY ? void 0 : def.key);
1355
- registry.register(def.key, source);
1356
- }
1357
- return registry;
1358
- }
1359
- };
1360
- //#endregion
1361
- //#region src/websocket.ts
1362
- /**
1363
- * Extract error message and code from a WebSocket message payload.
1364
- * Handles both `{ error: string }` and `{ error: { message, code } }` shapes.
1365
- */
1366
- function extractMessageError(message) {
1367
- const payload = message.payload;
1368
- const errPayload = payload?.error;
1369
- return {
1370
- errorMessage: typeof errPayload === "object" ? errPayload.message : payload?.message || (typeof errPayload === "string" ? errPayload : void 0) || message.error || "Unknown error",
1371
- errorCode: typeof errPayload === "object" ? errPayload.code : payload?.code
1372
- };
1373
- }
1374
- /**
1375
- * Low-level realtime WebSocket client.
1376
- *
1377
- * @internal Not a stable app-facing API. `createRebaseClient()` constructs and
1378
- * manages this internally (exposed as `client.ws`, typed by the minimal
1379
- * `RebaseWebSocket` contract in `@rebasepro/types`). It is re-exported from the
1380
- * package root only because the `@rebasepro/client-postgresql` driver
1381
- * instantiates it directly; its surface may change without a major bump.
1382
- */
1383
- var RebaseWebSocketClient = class {
1384
- websocketUrl;
1385
- ws = null;
1386
- getAuthToken;
1387
- subscriptions = /* @__PURE__ */ new Map();
1388
- listeners = /* @__PURE__ */ new Map();
1389
- on(event, cb) {
1390
- if (!this.listeners.has(event)) this.listeners.set(event, /* @__PURE__ */ new Set());
1391
- this.listeners.get(event).add(cb);
1392
- return () => this.listeners.get(event).delete(cb);
1393
- }
1394
- emit(event, ...args) {
1395
- if (this.listeners.has(event)) this.listeners.get(event).forEach((cb) => cb(...args));
1396
- }
1397
- collectionSubscriptions = /* @__PURE__ */ new Map();
1398
- singleSubscriptions = /* @__PURE__ */ new Map();
1399
- backendToCollectionKey = /* @__PURE__ */ new Map();
1400
- backendToEntityKey = /* @__PURE__ */ new Map();
1401
- pendingRequests = /* @__PURE__ */ new Map();
1402
- reconnectAttempts = 0;
1403
- maxReconnectAttempts = 5;
1404
- isConnected = false;
1405
- messageQueue = [];
1406
- requestTimeoutMs = 3e4;
1407
- reconnectTimeout = null;
1408
- isAuthenticated = false;
1409
- authPromise = null;
1410
- WebSocketConstructor;
1411
- onUnauthorized;
1412
- refreshInProgress = null;
1413
- constructor(config) {
1414
- this.websocketUrl = config.websocketUrl;
1415
- this.getAuthToken = config.getAuthToken;
1416
- this.onUnauthorized = config.onUnauthorized;
1417
- this.WebSocketConstructor = config.WebSocket || (typeof WebSocket !== "undefined" ? WebSocket : void 0);
1418
- if (!this.WebSocketConstructor) console.warn("WebSocket is not defined in this environment. Realtime subscriptions will not work unless you provide a WebSocket implementation in the config.");
1419
- else this.initWebSocket();
1420
- }
1421
- /**
1422
- * Authenticate the WebSocket connection
1423
- */
1424
- async authenticate(token) {
1425
- return new Promise((resolve, reject) => {
1426
- const requestId = `auth_${Date.now()}`;
1427
- const timeout = setTimeout(() => {
1428
- this.pendingRequests.delete(requestId);
1429
- this.authPromise = null;
1430
- reject(/* @__PURE__ */ new Error("Authentication timeout"));
1431
- }, 3e4);
1432
- this.pendingRequests.set(requestId, {
1433
- resolve: () => {
1434
- clearTimeout(timeout);
1435
- this.isAuthenticated = true;
1436
- resolve();
1437
- },
1438
- reject: (error) => {
1439
- clearTimeout(timeout);
1440
- reject(error);
1441
- }
1442
- });
1443
- const message = {
1444
- type: "AUTHENTICATE",
1445
- requestId,
1446
- payload: { token }
1447
- };
1448
- if (!this.isConnected || !this.ws) this.messageQueue.unshift(message);
1449
- else this.ws.send(JSON.stringify(message));
1450
- });
1451
- }
1452
- /**
1453
- * Set the auth token getter function
1454
- */
1455
- setAuthTokenGetter(getAuthToken) {
1456
- this.getAuthToken = getAuthToken;
1457
- if (this.isConnected && !this.isAuthenticated && !this.authPromise) {
1458
- console.debug("WebSocket auto-authenticating after token getter set");
1459
- this.getAuthToken().then((token) => {
1460
- if (!this.ws) return;
1461
- if (token) this.authenticate(token).catch((e) => {
1462
- if (this.ws) console.debug("WebSocket auto-auth skipped:", e?.message || e);
1463
- });
1464
- }).catch((e) => {
1465
- if (this.ws) console.debug("WebSocket auto-auth skipped:", e?.message || e);
1466
- });
1467
- }
1468
- }
1469
- disconnect() {
1470
- this.isAuthenticated = false;
1471
- this.authPromise = null;
1472
- if (this.reconnectTimeout) {
1473
- clearTimeout(this.reconnectTimeout);
1474
- this.reconnectTimeout = null;
1475
- }
1476
- if (this.ws) {
1477
- this.ws.onclose = null;
1478
- this.ws.onerror = null;
1479
- this.ws.onopen = null;
1480
- this.ws.onmessage = null;
1481
- this.ws.close();
1482
- this.ws = null;
1483
- }
1484
- }
1485
- initWebSocket() {
1486
- if (!this.WebSocketConstructor) return;
1487
- if (this.ws?.readyState === this.WebSocketConstructor.OPEN) return;
1488
- if (this.ws) {
1489
- this.ws.onclose = null;
1490
- this.ws.close();
1491
- this.ws = null;
1492
- }
1493
- try {
1494
- this.ws = new this.WebSocketConstructor(this.websocketUrl);
1495
- this.ws.onopen = async () => {
1496
- console.debug("Connected to PostgreSQL backend");
1497
- const wasReconnect = this.reconnectAttempts > 0;
1498
- this.isConnected = true;
1499
- this.reconnectAttempts = 0;
1500
- if (this.getAuthToken && !this.isAuthenticated) try {
1501
- const token = await this.getAuthToken();
1502
- if (token) {
1503
- await this.authenticate(token);
1504
- console.debug("WebSocket auto-authenticated");
1505
- }
1506
- } catch (error) {
1507
- console.debug("WebSocket connected without auth:", error?.message || error);
1508
- }
1509
- this.emit(wasReconnect ? "reconnect" : "connect");
1510
- this.processMessageQueue();
1511
- if (wasReconnect) this.resubscribeAll();
1512
- };
1513
- this.ws.onmessage = (event) => {
1514
- try {
1515
- const message = JSON.parse(event.data, rebaseReviver);
1516
- this.handleWebSocketMessage(message);
1517
- } catch (error) {
1518
- console.error("Error parsing WebSocket message:", error);
1519
- }
1520
- };
1521
- this.ws.onclose = () => {
1522
- console.debug("Disconnected from PostgreSQL backend");
1523
- this.isConnected = false;
1524
- this.isAuthenticated = false;
1525
- this.authPromise = null;
1526
- this.emit("disconnect");
1527
- for (const [reqId, request] of this.pendingRequests.entries()) {
1528
- if (reqId.startsWith("auth_")) request.reject(/* @__PURE__ */ new Error("Connection closed during authentication"));
1529
- else if (request.message) {
1530
- request.message._queuedResolve = request.resolve;
1531
- request.message._queuedReject = request.reject;
1532
- this.messageQueue.push(request.message);
1533
- } else request.reject(new _rebasepro_types.RebaseApiError("Connection closed"));
1534
- this.pendingRequests.delete(reqId);
1535
- }
1536
- this.attemptReconnect();
1537
- };
1538
- this.ws.onerror = (error) => {
1539
- console.error("WebSocket error:", error);
1540
- this.isConnected = false;
1541
- this.emit("error", error);
1542
- };
1543
- } catch (error) {
1544
- console.error("Failed to initialize WebSocket:", error);
1545
- this.attemptReconnect();
1546
- }
1547
- }
1548
- processMessageQueue() {
1549
- while (this.messageQueue.length > 0 && this.isConnected) {
1550
- const message = this.messageQueue.shift();
1551
- if (message) this.sendMessage(message);
1552
- }
1553
- }
1554
- attemptReconnect() {
1555
- if (this.reconnectAttempts >= this.maxReconnectAttempts) {
1556
- console.error("Max reconnection attempts reached");
1557
- return;
1558
- }
1559
- this.reconnectAttempts++;
1560
- const delay = Math.min(1e3 * Math.pow(2, this.reconnectAttempts), 3e4);
1561
- console.debug(`Attempting to reconnect in ${delay}ms (attempt ${this.reconnectAttempts})`);
1562
- if (this.reconnectTimeout) clearTimeout(this.reconnectTimeout);
1563
- this.reconnectTimeout = setTimeout(() => {
1564
- this.reconnectTimeout = null;
1565
- this.initWebSocket();
1566
- }, delay);
1567
- }
1568
- isAuthError(message) {
1569
- if (message.type === "AUTH_ERROR") return true;
1570
- const { errorMessage, errorCode } = extractMessageError(message);
1571
- if (errorCode === "UNAUTHORIZED" || errorCode === "JWT_EXPIRED" || errorCode === "AUTH_ERROR") return true;
1572
- const lowerMessage = errorMessage.toLowerCase();
1573
- 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");
1574
- }
1575
- async handleAuthFailure() {
1576
- if (this.refreshInProgress) return this.refreshInProgress;
1577
- this.refreshInProgress = (async () => {
1578
- this.isAuthenticated = false;
1579
- this.authPromise = null;
1580
- if (this.onUnauthorized) try {
1581
- if (await this.onUnauthorized() && this.getAuthToken) {
1582
- const token = await this.getAuthToken();
1583
- if (token) {
1584
- await this.authenticate(token);
1585
- return true;
1586
- }
1587
- }
1588
- } catch (error) {
1589
- console.error("WebSocket auth refresh failed:", error);
1590
- }
1591
- return false;
1592
- })();
1593
- try {
1594
- return await this.refreshInProgress;
1595
- } finally {
1596
- this.refreshInProgress = null;
1597
- }
1598
- }
1599
- /**
1600
- * Shared logic for re-subscribing a collection or row subscription
1601
- * after an auth error is resolved by refreshing credentials.
1602
- */
1603
- resubscribeAfterAuthRefresh(message, subscription, subscriptionKey, idPrefix, backendKeyMap, messageType) {
1604
- this.handleAuthFailure().then((refreshed) => {
1605
- if (refreshed) {
1606
- const oldBackendId = subscription.backendSubscriptionId;
1607
- const newBackendId = `${idPrefix}_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
1608
- subscription.backendSubscriptionId = newBackendId;
1609
- backendKeyMap.delete(oldBackendId);
1610
- backendKeyMap.set(newBackendId, subscriptionKey);
1611
- this.sendMessage({
1612
- type: messageType,
1613
- payload: {
1614
- ...subscription.props,
1615
- subscriptionId: newBackendId
1616
- }
1617
- }).catch((error) => {
1618
- console.error(`[WS] Failed to re-subscribe ${idPrefix} after auth refresh:`, subscriptionKey, error);
1619
- subscription.callbacks.forEach((callback) => {
1620
- if (callback.onError) callback.onError(error);
1621
- });
1622
- });
1623
- } else {
1624
- const { errorMessage, errorCode } = extractMessageError(message);
1625
- const error = new _rebasepro_types.RebaseApiError(errorMessage, { code: errorCode });
1626
- subscription.callbacks.forEach((callback) => {
1627
- if (callback.onError) callback.onError(error);
1628
- });
1629
- }
1630
- }).catch((err) => {
1631
- subscription.callbacks.forEach((callback) => {
1632
- if (callback.onError) callback.onError(err);
1633
- });
1634
- });
1635
- }
1636
- handleWebSocketMessage(message) {
1637
- const { type, requestId, subscriptionId } = message;
1638
- if (requestId && this.pendingRequests.has(requestId)) {
1639
- const pendingReq = this.pendingRequests.get(requestId);
1640
- if (type === "ERROR" || type === "AUTH_ERROR" || message.error) if (this.isAuthError(message)) {
1641
- this.pendingRequests.delete(requestId);
1642
- this.handleAuthFailure().then((refreshed) => {
1643
- if (refreshed && pendingReq.message) this.doSendMessage(pendingReq.message, pendingReq.resolve, pendingReq.reject).catch(pendingReq.reject);
1644
- else {
1645
- const { errorMessage, errorCode } = extractMessageError(message);
1646
- pendingReq.reject(new _rebasepro_types.RebaseApiError(errorMessage, { code: errorCode }));
1647
- }
1648
- }).catch((err) => {
1649
- pendingReq.reject(err);
1650
- });
1651
- } else {
1652
- this.pendingRequests.delete(requestId);
1653
- const { errorMessage, errorCode } = extractMessageError(message);
1654
- pendingReq.reject(new _rebasepro_types.RebaseApiError(errorMessage, { code: errorCode }));
1655
- }
1656
- else {
1657
- this.pendingRequests.delete(requestId);
1658
- pendingReq.resolve(message.payload || message);
1659
- }
1660
- return;
1661
- }
1662
- if (subscriptionId && type === "collection_update") {
1663
- const subscriptionKey = this.backendToCollectionKey.get(subscriptionId);
1664
- if (subscriptionKey) {
1665
- const collectionSub = this.collectionSubscriptions.get(subscriptionKey);
1666
- if (collectionSub) {
1667
- const incomingRows = message.rows || [];
1668
- const rows = this.mergeRows(collectionSub.latestData, incomingRows);
1669
- collectionSub.latestData = rows;
1670
- collectionSub.lastUpdated = Date.now();
1671
- collectionSub.isInitialDataReceived = true;
1672
- collectionSub.callbacks.forEach((callback) => {
1673
- try {
1674
- callback.onUpdate(rows);
1675
- } catch (error) {
1676
- console.error("Error in collection subscription callback:", error);
1677
- if (callback.onError) callback.onError(error instanceof Error ? error : new Error(String(error)));
1678
- }
1679
- });
1680
- return;
1681
- }
1682
- }
1683
- }
1684
- if (subscriptionId && type === "collection_patch") {
1685
- const subscriptionKey = this.backendToCollectionKey.get(subscriptionId);
1686
- if (subscriptionKey) {
1687
- const collectionSub = this.collectionSubscriptions.get(subscriptionKey);
1688
- if (collectionSub && collectionSub.isInitialDataReceived && collectionSub.latestData) {
1689
- const patchWireEntity = message.row ?? null;
1690
- const patchEntityId = message.id;
1691
- const patchRow = patchWireEntity ? patchWireEntity : null;
1692
- let updated;
1693
- if (patchRow === null) updated = collectionSub.latestData.filter((e) => String(e.id) !== String(patchEntityId));
1694
- else {
1695
- const idx = collectionSub.latestData.findIndex((e) => String(e.id) === String(patchRow.id));
1696
- if (idx >= 0) {
1697
- updated = [...collectionSub.latestData];
1698
- updated[idx] = patchRow;
1699
- } else updated = [patchRow, ...collectionSub.latestData];
1700
- }
1701
- collectionSub.latestData = updated;
1702
- collectionSub.lastUpdated = Date.now();
1703
- collectionSub.callbacks.forEach((callback) => {
1704
- try {
1705
- callback.onUpdate(updated);
1706
- } catch (error) {
1707
- console.error("Error in collection patch callback:", error);
1708
- if (callback.onError) callback.onError(error instanceof Error ? error : new Error(String(error)));
1709
- }
1710
- });
1711
- return;
1712
- }
1713
- }
1714
- }
1715
- if (subscriptionId && type === "single_update") {
1716
- const subscriptionKey = this.backendToEntityKey.get(subscriptionId);
1717
- if (subscriptionKey) {
1718
- const entitySub = this.singleSubscriptions.get(subscriptionKey);
1719
- if (entitySub) {
1720
- const wireEntity = message.row ?? null;
1721
- const row = wireEntity ? wireEntity : null;
1722
- entitySub.latestData = row;
1723
- entitySub.lastUpdated = Date.now();
1724
- entitySub.isInitialDataReceived = true;
1725
- entitySub.callbacks.forEach((callback) => {
1726
- try {
1727
- callback.onUpdate(row);
1728
- } catch (error) {
1729
- console.error("Error in row subscription callback:", error);
1730
- if (callback.onError) callback.onError(error instanceof Error ? error : new Error(String(error)));
1731
- }
1732
- });
1733
- return;
1734
- }
1735
- }
1736
- }
1737
- if (subscriptionId && (type === "ERROR" || message.error)) {
1738
- const collectionKey = this.backendToCollectionKey.get(subscriptionId);
1739
- if (collectionKey) {
1740
- const collectionSub = this.collectionSubscriptions.get(collectionKey);
1741
- if (collectionSub) {
1742
- if (this.isAuthError(message)) {
1743
- this.resubscribeAfterAuthRefresh(message, collectionSub, collectionKey, "collection", this.backendToCollectionKey, "subscribe_collection");
1744
- return;
1745
- }
1746
- const { errorMessage, errorCode } = extractMessageError(message);
1747
- const error = new _rebasepro_types.RebaseApiError(errorMessage, { code: errorCode });
1748
- collectionSub.callbacks.forEach((callback) => {
1749
- if (callback.onError) callback.onError(error);
1750
- });
1751
- return;
1752
- }
1753
- }
1754
- const entityKey = this.backendToEntityKey.get(subscriptionId);
1755
- if (entityKey) {
1756
- const entitySub = this.singleSubscriptions.get(entityKey);
1757
- if (entitySub) {
1758
- if (this.isAuthError(message)) {
1759
- this.resubscribeAfterAuthRefresh(message, entitySub, entityKey, "row", this.backendToEntityKey, "subscribe_one");
1760
- return;
1761
- }
1762
- const { errorMessage, errorCode } = extractMessageError(message);
1763
- const error = new _rebasepro_types.RebaseApiError(errorMessage, { code: errorCode });
1764
- entitySub.callbacks.forEach((callback) => {
1765
- if (callback.onError) callback.onError(error);
1766
- });
1767
- return;
1768
- }
1769
- }
1770
- }
1771
- if (subscriptionId && this.subscriptions.has(subscriptionId)) {
1772
- const callback = this.subscriptions.get(subscriptionId);
1773
- if (!callback) throw new Error(`Subscription callback not found for subscriptionId: ${subscriptionId}`);
1774
- if (message.type === "ERROR" || message.error) {
1775
- if (callback.onError) {
1776
- const { errorMessage, errorCode } = extractMessageError(message);
1777
- callback.onError(new _rebasepro_types.RebaseApiError(errorMessage, { code: errorCode }));
1778
- }
1779
- } else callback.onUpdate(message);
1780
- }
1781
- }
1782
- async ensureAuthenticated(retryCount = 3) {
1783
- if (this.isAuthenticated || !this.getAuthToken) return;
1784
- if (this.authPromise) {
1785
- await this.authPromise;
1786
- return;
1787
- }
1788
- let lastError = null;
1789
- for (let attempt = 0; attempt < retryCount; attempt++) try {
1790
- const token = await this.getAuthToken();
1791
- if (!token) throw new Error("user not logged in");
1792
- this.authPromise = this.authenticate(token);
1793
- await this.authPromise;
1794
- this.authPromise = null;
1795
- console.debug("WebSocket authenticated on demand");
1796
- return;
1797
- } catch (error) {
1798
- this.authPromise = null;
1799
- lastError = error;
1800
- const errMsg = error instanceof Error ? error.message : String(error);
1801
- if (errMsg.includes("not logged in") || errMsg.includes("Session expired")) {
1802
- console.warn("WebSocket auth failed: user not logged in");
1803
- throw error;
1804
- }
1805
- if (errMsg.includes("still loading")) {
1806
- if (attempt < retryCount - 1) {
1807
- const delay = Math.min(500 * (attempt + 1), 2e3);
1808
- await new Promise((resolve) => setTimeout(resolve, delay));
1809
- continue;
1810
- }
1811
- }
1812
- if (attempt < retryCount - 1) {
1813
- const delay = Math.min(1e3 * (attempt + 1), 3e3);
1814
- console.debug(`WebSocket auth attempt ${attempt + 1} failed, retrying in ${delay}ms...`);
1815
- await new Promise((resolve) => setTimeout(resolve, delay));
1816
- }
1817
- }
1818
- console.warn("WebSocket on-demand auth failed after retries:", lastError);
1819
- throw lastError;
1820
- }
1821
- async reauthenticate() {
1822
- if (!this.getAuthToken) return;
1823
- this.isAuthenticated = false;
1824
- try {
1825
- const token = await this.getAuthToken();
1826
- if (!token) throw new Error("user not logged in");
1827
- await this.authenticate(token);
1828
- console.debug("WebSocket reauthenticated successfully");
1829
- } catch (error) {
1830
- console.error("WebSocket reauthentication failed:", error);
1831
- throw error;
1832
- }
1833
- }
1834
- sendMessage(message) {
1835
- const queuedMsg = message;
1836
- if (queuedMsg._queuedResolve && queuedMsg._queuedReject) return this.doSendMessage(message, queuedMsg._queuedResolve, queuedMsg._queuedReject);
1837
- if (!this.isConnected || !this.ws) return new Promise((resolve, reject) => {
1838
- const queueable = message;
1839
- queueable._queuedResolve = resolve;
1840
- queueable._queuedReject = reject;
1841
- this.messageQueue.push(message);
1842
- });
1843
- return new Promise((resolve, reject) => {
1844
- this.doSendMessage(message, resolve, reject);
1845
- });
1846
- }
1847
- async doSendMessage(message, resolve, reject) {
1848
- if (message.type !== "AUTHENTICATE" && this.getAuthToken && !this.isAuthenticated) try {
1849
- await this.ensureAuthenticated();
1850
- } catch (error) {
1851
- reject(new _rebasepro_types.RebaseApiError(error instanceof Error ? error.message : "Authentication required"));
1852
- return;
1853
- }
1854
- const requestId = message.requestId || `req_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
1855
- message.requestId = requestId;
1856
- const expectsResponse = ![
1857
- "subscribe_collection",
1858
- "subscribe_one",
1859
- "unsubscribe",
1860
- "join_channel",
1861
- "leave_channel",
1862
- "broadcast",
1863
- "presence_track",
1864
- "presence_untrack",
1865
- "presence_state"
1866
- ].includes(message.type);
1867
- if (expectsResponse && !this.pendingRequests.has(requestId)) {
1868
- const timeoutHandle = setTimeout(() => {
1869
- if (this.pendingRequests.has(requestId)) {
1870
- this.pendingRequests.delete(requestId);
1871
- reject(new _rebasepro_types.RebaseApiError("Request timed out"));
1872
- }
1873
- }, this.requestTimeoutMs);
1874
- this.pendingRequests.set(requestId, {
1875
- resolve: (value) => {
1876
- clearTimeout(timeoutHandle);
1877
- resolve(value);
1878
- },
1879
- reject: (error) => {
1880
- clearTimeout(timeoutHandle);
1881
- reject(error);
1882
- },
1883
- message
1884
- });
1885
- }
1886
- try {
1887
- this.ws.send(JSON.stringify(message));
1888
- if (!expectsResponse) resolve(void 0);
1889
- } catch (error) {
1890
- if (expectsResponse) this.pendingRequests.delete(requestId);
1891
- reject(new _rebasepro_types.RebaseApiError("Failed to send message", { cause: error }));
1892
- }
1893
- }
1894
- async fetchCollection(props) {
1895
- return (await this.sendMessage({
1896
- type: "FETCH_COLLECTION",
1897
- payload: props
1898
- })).rows || [];
1899
- }
1900
- async fetchOne(props) {
1901
- return (await this.sendMessage({
1902
- type: "FETCH_ONE",
1903
- payload: props
1904
- })).row ?? void 0;
1905
- }
1906
- async save(props) {
1907
- return (await this.sendMessage({
1908
- type: "SAVE",
1909
- payload: props
1910
- })).row;
1911
- }
1912
- async delete(props) {
1913
- await this.sendMessage({
1914
- type: "DELETE",
1915
- payload: props
1916
- });
1917
- }
1918
- async executeSql(sql, options) {
1919
- return (await this.sendMessage({
1920
- type: "EXECUTE_SQL",
1921
- payload: {
1922
- sql,
1923
- options
1924
- }
1925
- })).result || [];
1926
- }
1927
- async fetchAvailableDatabases() {
1928
- return (await this.sendMessage({
1929
- type: "FETCH_DATABASES",
1930
- payload: {}
1931
- })).databases || [];
1932
- }
1933
- async fetchAvailableRoles() {
1934
- return (await this.sendMessage({ type: "FETCH_ROLES" })).roles || [];
1935
- }
1936
- async fetchCurrentDatabase() {
1937
- return (await this.sendMessage({ type: "FETCH_CURRENT_DATABASE" })).database;
1938
- }
1939
- async checkUniqueField(path, name, value, id, collection) {
1940
- return (await this.sendMessage({
1941
- type: "CHECK_UNIQUE_FIELD",
1942
- payload: {
1943
- path,
1944
- name,
1945
- value,
1946
- id,
1947
- collection
1948
- }
1949
- })).isUnique;
1950
- }
1951
- async count(props) {
1952
- return (await this.sendMessage({
1953
- type: "COUNT",
1954
- payload: props
1955
- })).count;
1956
- }
1957
- async fetchUnmappedTables(mappedPaths) {
1958
- return (await this.sendMessage({
1959
- type: "FETCH_UNMAPPED_TABLES",
1960
- payload: { mappedPaths }
1961
- })).tables || [];
1962
- }
1963
- async fetchTableMetadata(tableName) {
1964
- return (await this.sendMessage({
1965
- type: "FETCH_TABLE_METADATA",
1966
- payload: { tableName }
1967
- })).metadata || {
1968
- columns: [],
1969
- foreignKeys: [],
1970
- junctions: [],
1971
- policies: []
1972
- };
1973
- }
1974
- async createBranch(name, options) {
1975
- return (await this.sendMessage({
1976
- type: "CREATE_BRANCH",
1977
- payload: {
1978
- name,
1979
- options
1980
- }
1981
- })).branch;
1982
- }
1983
- async deleteBranch(name) {
1984
- await this.sendMessage({
1985
- type: "DELETE_BRANCH",
1986
- payload: { name }
1987
- });
1988
- }
1989
- async listBranches() {
1990
- return (await this.sendMessage({
1991
- type: "LIST_BRANCHES",
1992
- payload: {}
1993
- })).branches || [];
1994
- }
1995
- /**
1996
- * Recursively compare two values for structural equality.
1997
- * Handles primitives, null, undefined, Date, RegExp, arrays, and plain objects.
1998
- */
1999
- deepEqual(a, b) {
2000
- if (a === b) return true;
2001
- if (a === null || b === null || a === void 0 || b === void 0) return false;
2002
- if (typeof a !== typeof b) return false;
2003
- if (typeof a !== "object") return false;
2004
- if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();
2005
- if (a instanceof Date || b instanceof Date) return false;
2006
- if (a instanceof RegExp && b instanceof RegExp) return a.source === b.source && a.flags === b.flags;
2007
- if (a instanceof RegExp || b instanceof RegExp) return false;
2008
- const aIsArray = Array.isArray(a);
2009
- const bIsArray = Array.isArray(b);
2010
- if (aIsArray !== bIsArray) return false;
2011
- if (aIsArray && bIsArray) {
2012
- if (a.length !== b.length) return false;
2013
- for (let i = 0; i < a.length; i++) if (!this.deepEqual(a[i], b[i])) return false;
2014
- return true;
2015
- }
2016
- const aObj = a;
2017
- const bObj = b;
2018
- const aKeys = Object.keys(aObj);
2019
- const bKeys = Object.keys(bObj);
2020
- if (aKeys.length !== bKeys.length) return false;
2021
- for (const key of aKeys) {
2022
- if (!Object.prototype.hasOwnProperty.call(bObj, key)) return false;
2023
- if (!this.deepEqual(aObj[key], bObj[key])) return false;
2024
- }
2025
- return true;
2026
- }
2027
- normalizeForComparison(val) {
2028
- if (!val) return val;
2029
- if (Array.isArray(val)) return val.map((item) => this.normalizeForComparison(item));
2030
- if (typeof val === "object") {
2031
- if (val instanceof Date) return val;
2032
- if (val instanceof RegExp) return val;
2033
- const obj = val;
2034
- if (obj.__type === "relation") {
2035
- const { data, ...rest } = obj;
2036
- return rest;
2037
- }
2038
- const result = {};
2039
- for (const [k, v] of Object.entries(obj)) result[k] = this.normalizeForComparison(v);
2040
- return result;
2041
- }
2042
- return val;
2043
- }
2044
- /**
2045
- * Merge incoming rows with cached data, preserving cached references
2046
- * for rows whose values haven't changed. This avoids unnecessary
2047
- * React re-renders when the server refetches all rows but most
2048
- * haven't actually changed.
2049
- */
2050
- mergeRows(cached, incoming) {
2051
- if (!cached || cached.length === 0) return incoming;
2052
- const cachedById = /* @__PURE__ */ new Map();
2053
- for (const row of cached) cachedById.set(row.id, row);
2054
- return incoming.map((incomingRow) => {
2055
- const cachedRow = cachedById.get(incomingRow.id);
2056
- if (!cachedRow) return incomingRow;
2057
- const normCached = this.normalizeForComparison(cachedRow);
2058
- const normIncoming = this.normalizeForComparison(incomingRow);
2059
- if (this.deepEqual(normCached, normIncoming)) return cachedRow;
2060
- else {
2061
- const mismatches = {};
2062
- const allKeys = new Set([...Object.keys(normCached), ...Object.keys(normIncoming)]);
2063
- for (const key of allKeys) if (!this.deepEqual(normCached[key], normIncoming[key])) mismatches[key] = {
2064
- cached: normCached[key],
2065
- incoming: normIncoming[key]
2066
- };
2067
- console.debug(`[RebaseWS] Row ${incomingRow.id} refetch mismatch:\n`, JSON.stringify(mismatches, null, 2));
2068
- }
2069
- return incomingRow;
2070
- });
2071
- }
2072
- listenCollection(props, onUpdate, onError) {
2073
- const subscriptionKey = this.createCollectionSubscriptionKey(props);
2074
- const callbackId = `callback_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
2075
- const existingSubscription = this.collectionSubscriptions.get(subscriptionKey);
2076
- if (existingSubscription) {
2077
- const callbackMap = existingSubscription.callbacks;
2078
- callbackMap.set(callbackId, {
2079
- onUpdate,
2080
- onError
2081
- });
2082
- if (existingSubscription.latestData !== void 0 && existingSubscription.isInitialDataReceived) try {
2083
- onUpdate(existingSubscription.latestData);
2084
- } catch (error) {
2085
- console.error("Error in collection subscription callback:", error);
2086
- if (onError) onError(error instanceof Error ? error : new Error(String(error)));
2087
- }
2088
- return () => {
2089
- callbackMap.delete(callbackId);
2090
- if (callbackMap.size === 0) {
2091
- this.collectionSubscriptions.delete(subscriptionKey);
2092
- this.backendToCollectionKey.delete(existingSubscription.backendSubscriptionId);
2093
- if (this.isConnected && this.ws) this.sendMessage({
2094
- type: "unsubscribe",
2095
- payload: { subscriptionId: existingSubscription.backendSubscriptionId }
2096
- }).catch(console.error);
2097
- }
2098
- };
2099
- }
2100
- const backendSubscriptionId = `collection_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
2101
- const callbackMap = /* @__PURE__ */ new Map();
2102
- callbackMap.set(callbackId, {
2103
- onUpdate,
2104
- onError
2105
- });
2106
- this.collectionSubscriptions.set(subscriptionKey, {
2107
- backendSubscriptionId,
2108
- callbacks: callbackMap,
2109
- props
2110
- });
2111
- this.backendToCollectionKey.set(backendSubscriptionId, subscriptionKey);
2112
- this.sendMessage({
2113
- type: "subscribe_collection",
2114
- payload: {
2115
- ...props,
2116
- subscriptionId: backendSubscriptionId
2117
- }
2118
- }).catch((error) => {
2119
- if (onError) onError(error);
2120
- });
2121
- return () => {
2122
- const subscription = this.collectionSubscriptions.get(subscriptionKey);
2123
- if (subscription) {
2124
- const callbacks = subscription.callbacks;
2125
- callbacks.delete(callbackId);
2126
- if (callbacks.size === 0) {
2127
- this.collectionSubscriptions.delete(subscriptionKey);
2128
- this.backendToCollectionKey.delete(subscription.backendSubscriptionId);
2129
- if (this.isConnected && this.ws) this.sendMessage({
2130
- type: "unsubscribe",
2131
- payload: { subscriptionId: subscription.backendSubscriptionId }
2132
- }).catch(console.error);
2133
- }
2134
- }
2135
- };
2136
- }
2137
- listenOne(props, onUpdate, onError) {
2138
- const subscriptionKey = this.createSingleSubscriptionKey(props);
2139
- const callbackId = `callback_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
2140
- const existingSubscription = this.singleSubscriptions.get(subscriptionKey);
2141
- if (existingSubscription) {
2142
- const callbackMap = existingSubscription.callbacks;
2143
- callbackMap.set(callbackId, {
2144
- onUpdate,
2145
- onError
2146
- });
2147
- if (existingSubscription.latestData !== void 0 && existingSubscription.isInitialDataReceived) try {
2148
- onUpdate(existingSubscription.latestData);
2149
- } catch (error) {
2150
- console.error("Error in row subscription callback:", error);
2151
- if (onError) onError(error instanceof Error ? error : new Error(String(error)));
2152
- }
2153
- return () => {
2154
- callbackMap.delete(callbackId);
2155
- if (callbackMap.size === 0) {
2156
- this.singleSubscriptions.delete(subscriptionKey);
2157
- this.backendToEntityKey.delete(existingSubscription.backendSubscriptionId);
2158
- if (this.isConnected && this.ws) this.sendMessage({
2159
- type: "unsubscribe",
2160
- payload: { subscriptionId: existingSubscription.backendSubscriptionId }
2161
- }).catch(console.error);
2162
- }
2163
- };
2164
- }
2165
- const backendSubscriptionId = `entity_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
2166
- const callbackMap = /* @__PURE__ */ new Map();
2167
- callbackMap.set(callbackId, {
2168
- onUpdate,
2169
- onError
2170
- });
2171
- this.singleSubscriptions.set(subscriptionKey, {
2172
- backendSubscriptionId,
2173
- callbacks: callbackMap,
2174
- props
2175
- });
2176
- this.backendToEntityKey.set(backendSubscriptionId, subscriptionKey);
2177
- this.sendMessage({
2178
- type: "subscribe_one",
2179
- payload: {
2180
- ...props,
2181
- subscriptionId: backendSubscriptionId
2182
- }
2183
- }).catch((error) => {
2184
- if (onError) onError(error);
2185
- });
2186
- return () => {
2187
- const subscription = this.singleSubscriptions.get(subscriptionKey);
2188
- if (subscription) {
2189
- const callbacks = subscription.callbacks;
2190
- callbacks.delete(callbackId);
2191
- if (callbacks.size === 0) {
2192
- this.singleSubscriptions.delete(subscriptionKey);
2193
- this.backendToEntityKey.delete(subscription.backendSubscriptionId);
2194
- if (this.isConnected && this.ws) this.sendMessage({
2195
- type: "unsubscribe",
2196
- payload: { subscriptionId: subscription.backendSubscriptionId }
2197
- }).catch(console.error);
2198
- }
2199
- }
2200
- };
2201
- }
2202
- /**
2203
- * Re-send all active subscriptions to the backend after a reconnect.
2204
- * The server wipes subscription state when a client disconnects, so
2205
- * we need to re-register everything to resume receiving updates.
2206
- */
2207
- resubscribeAll() {
2208
- console.debug(`[WS] Re-subscribing: ${this.collectionSubscriptions.size} collection(s), ${this.singleSubscriptions.size} row(ies)`);
2209
- for (const [key, sub] of this.collectionSubscriptions.entries()) {
2210
- const oldBackendId = sub.backendSubscriptionId;
2211
- const newBackendId = `collection_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
2212
- sub.backendSubscriptionId = newBackendId;
2213
- this.backendToCollectionKey.delete(oldBackendId);
2214
- this.backendToCollectionKey.set(newBackendId, key);
2215
- this.sendMessage({
2216
- type: "subscribe_collection",
2217
- payload: {
2218
- ...sub.props,
2219
- subscriptionId: newBackendId
2220
- }
2221
- }).catch((error) => {
2222
- console.error("[WS] Failed to re-subscribe collection:", key, error);
2223
- });
2224
- }
2225
- for (const [key, sub] of this.singleSubscriptions.entries()) {
2226
- const oldBackendId = sub.backendSubscriptionId;
2227
- const newBackendId = `entity_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
2228
- sub.backendSubscriptionId = newBackendId;
2229
- this.backendToEntityKey.delete(oldBackendId);
2230
- this.backendToEntityKey.set(newBackendId, key);
2231
- this.sendMessage({
2232
- type: "subscribe_one",
2233
- payload: {
2234
- ...sub.props,
2235
- subscriptionId: newBackendId
2236
- }
2237
- }).catch((error) => {
2238
- console.error("[WS] Failed to re-subscribe row:", key, error);
2239
- });
2240
- }
2241
- }
2242
- createCollectionSubscriptionKey(props) {
2243
- const key = {
2244
- path: props.path,
2245
- filter: props.filter,
2246
- limit: props.limit,
2247
- startAfter: props.startAfter,
2248
- orderBy: props.orderBy,
2249
- order: props.order,
2250
- searchString: props.searchString,
2251
- collection: props.collection?.name
2252
- };
2253
- return JSON.stringify(key, (_, value) => {
2254
- if (value && typeof value === "object" && !Array.isArray(value)) return Object.keys(value).sort().reduce((sorted, k) => {
2255
- sorted[k] = value[k];
2256
- return sorted;
2257
- }, {});
2258
- return value;
2259
- });
2260
- }
2261
- createSingleSubscriptionKey(props) {
2262
- return `${props.path}|${props.id}`;
2263
- }
2264
- };
2265
- //#endregion
2266
- //#region src/index.ts
2267
- /**
2268
- * Derive a WebSocket URL from an HTTP base URL.
2269
- * `http://` → `ws://`, `https://` → `wss://`.
2270
- */
2271
- function deriveWebSocketUrl(baseUrl) {
2272
- if (typeof window !== "undefined") {
2273
- let absoluteUrl = "";
2274
- if (!baseUrl) absoluteUrl = window.location.origin;
2275
- else if (/^https?:\/\//i.test(baseUrl) || /^wss?:\/\//i.test(baseUrl)) absoluteUrl = baseUrl;
2276
- else try {
2277
- absoluteUrl = new URL(baseUrl, window.location.href).origin;
2278
- } catch {
2279
- absoluteUrl = window.location.origin;
2280
- }
2281
- const protocol = absoluteUrl.startsWith("https:") || absoluteUrl.startsWith("wss:") ? "wss:" : "ws:";
2282
- return absoluteUrl.replace(/^https?:\/\//i, `${protocol}//`).replace(/^wss?:\/\//i, `${protocol}//`).replace(/\/$/, "");
2283
- }
2284
- if (!baseUrl) return "";
2285
- if (!/^https?:\/\//i.test(baseUrl) && !/^wss?:\/\//i.test(baseUrl)) return "";
2286
- return baseUrl.replace(/^https?:\/\//i, (match) => match.toLowerCase() === "https://" ? "wss://" : "ws://").replace(/\/$/, "");
2287
- }
2288
- function createRebaseClient(options) {
2289
- const transport = createTransport(options);
2290
- const auth = createAuth(transport, options.auth);
2291
- const admin = createAdmin(transport, options.admin);
2292
- const cron = createCron(transport, options.cron);
2293
- const apiKeys = createApiKeys(transport, options.apiKeys);
2294
- const storage = createStorage(transport);
2295
- const functions = createFunctionsClient(transport);
2296
- const createStorageSource = (storageId) => storageId === _rebasepro_types.DEFAULT_STORAGE_SOURCE_KEY ? storage : createStorage(transport, storageId);
2297
- const storageRegistry = new ClientStorageSourceRegistry();
2298
- storageRegistry.register(_rebasepro_types.DEFAULT_STORAGE_SOURCE_KEY, storage);
2299
- for (const def of options.storageSources ?? []) if (def.transport === "server" && def.key !== _rebasepro_types.DEFAULT_STORAGE_SOURCE_KEY) storageRegistry.register(def.key, createStorageSource(def.key));
2300
- let storageSourcesPromise;
2301
- const fetchStorageSources = () => {
2302
- if (storageSourcesPromise) return storageSourcesPromise;
2303
- storageSourcesPromise = transport.request("/storage/sources").then((res) => {
2304
- const defs = res.data ?? [];
2305
- for (const def of defs) if (def.transport === "server" && def.key !== _rebasepro_types.DEFAULT_STORAGE_SOURCE_KEY && !storageRegistry.has(def.key)) storageRegistry.register(def.key, createStorageSource(def.key));
2306
- return defs;
2307
- }).catch((e) => {
2308
- storageSourcesPromise = void 0;
2309
- throw e;
2310
- });
2311
- return storageSourcesPromise;
2312
- };
2313
- const resolvedWsUrl = options.websocketUrl ?? deriveWebSocketUrl(options.baseUrl);
2314
- let ws;
2315
- if (resolvedWsUrl) {
2316
- ws = new RebaseWebSocketClient({
2317
- websocketUrl: resolvedWsUrl,
2318
- getAuthToken: async () => {
2319
- let session = auth.getSession();
2320
- if (session && session.expiresAt <= Date.now() + 1e4) try {
2321
- session = await auth.refreshSession();
2322
- } catch (e) {}
2323
- return session?.accessToken || options.token || "";
2324
- },
2325
- onUnauthorized: options.onUnauthorized || (async () => {
2326
- try {
2327
- await auth.refreshSession();
2328
- return true;
2329
- } catch (e) {
2330
- return false;
2331
- }
2332
- })
2333
- });
2334
- auth.onAuthStateChange((event, session) => {
2335
- if (!ws) return;
2336
- if (event === "SIGNED_OUT") ws.disconnect();
2337
- else if (event === "SIGNED_IN" || event === "TOKEN_REFRESHED") {
2338
- if (session?.accessToken) ws.authenticate(session.accessToken).catch(console.warn);
2339
- }
2340
- });
2341
- }
2342
- if (!options.onUnauthorized) transport.setOnUnauthorized(async () => {
2343
- try {
2344
- await auth.refreshSession();
2345
- return true;
2346
- } catch (e) {
2347
- return false;
2348
- }
2349
- });
2350
- /**
2351
- * Suggest the closest known collection key for a mistyped accessor.
2352
- * Uses edit-distance-1 and prefix matching — no external dependency.
2353
- */
2354
- function suggestCollection(prop, knownKeys) {
2355
- const prefixMatch = knownKeys.find((k) => k.startsWith(prop) || prop.startsWith(k));
2356
- if (prefixMatch) return prefixMatch;
2357
- for (const key of knownKeys) {
2358
- if (Math.abs(key.length - prop.length) > 1) continue;
2359
- let diffs = 0;
2360
- const longer = key.length >= prop.length ? key : prop;
2361
- const shorter = key.length >= prop.length ? prop : key;
2362
- if (longer.length === shorter.length) for (let i = 0; i < longer.length; i++) {
2363
- if (longer[i] !== shorter[i]) {
2364
- if (i + 1 < longer.length && longer[i] === shorter[i + 1] && longer[i + 1] === shorter[i]) {
2365
- diffs++;
2366
- i++;
2367
- if (diffs > 1) break;
2368
- continue;
2369
- }
2370
- diffs++;
2371
- }
2372
- if (diffs > 1) break;
2373
- }
2374
- else {
2375
- let li = 0;
2376
- let si = 0;
2377
- while (li < longer.length) {
2378
- if (si < shorter.length && longer[li] === shorter[si]) si++;
2379
- else diffs++;
2380
- li++;
2381
- if (diffs > 1) break;
2382
- }
2383
- }
2384
- if (diffs <= 1) return key;
2385
- }
2386
- }
2387
- const collectionClients = /* @__PURE__ */ new Map();
2388
- let untypedWarned = false;
2389
- function collection(slug) {
2390
- if (!collectionClients.has(slug)) collectionClients.set(slug, createCollectionClient(transport, slug, ws));
2391
- return collectionClients.get(slug);
2392
- }
2393
- const dataProxy = new Proxy({ collection }, { get(_target, prop) {
2394
- if (prop === "collection") return collection;
2395
- if (typeof prop === "symbol") return void 0;
2396
- if (typeof prop === "string" && prop !== "then" && prop !== "toJSON" && prop !== "$$typeof") {
2397
- if (options.collections) {
2398
- if (prop in options.collections) return collection(options.collections[prop]);
2399
- const knownKeys = Object.keys(options.collections);
2400
- const suggestion = suggestCollection(prop, knownKeys);
2401
- let msg = `Unknown collection accessor "${prop}". Known collections: ${knownKeys.join(", ")}.`;
2402
- if (suggestion) msg += ` Did you mean "${suggestion}"?`;
2403
- msg += ` Use data.collection("<slug>") for dynamic slugs.`;
2404
- throw new _rebasepro_types.RebaseClientError(msg);
2405
- }
2406
- if (!untypedWarned) {
2407
- untypedWarned = true;
2408
- console.warn(`[Rebase] Untyped data access detected (client.data.${prop}). Collection names are resolved via snake_case conversion, which may cause silent 404s at request time. Pass a \`collections\` dictionary to createRebaseClient() or use the generated SDK for type-safe access.`);
2409
- }
2410
- return collection((0, _rebasepro_utils.toSnakeCase)(prop));
2411
- }
2412
- } });
2413
- return {
2414
- auth,
2415
- admin,
2416
- cron,
2417
- apiKeys,
2418
- functions,
2419
- storage,
2420
- storageRegistry,
2421
- createStorageSource,
2422
- fetchStorageSources,
2423
- ws,
2424
- setToken: transport.setToken,
2425
- setAuthTokenGetter: transport.setAuthTokenGetter,
2426
- setOnUnauthorized: transport.setOnUnauthorized,
2427
- resolveToken: transport.resolveToken,
2428
- baseUrl: transport.baseUrl,
2429
- collection,
2430
- call: async (endpoint, payload) => {
2431
- const prefix = endpoint.startsWith("/") ? "" : "/";
2432
- const res = await transport.request(`${prefix}${endpoint}`, {
2433
- method: "POST",
2434
- body: payload ? JSON.stringify(payload) : void 0
2435
- });
2436
- return res.data ?? res;
2437
- },
2438
- data: dataProxy
2439
- };
2440
- }
2441
- //#endregion
2442
- Object.defineProperty(exports, "QueryBuilder", {
2443
- enumerable: true,
2444
- get: function() {
2445
- return _rebasepro_common.QueryBuilder;
2446
- }
2447
- });
2448
- Object.defineProperty(exports, "RebaseApiError", {
2449
- enumerable: true,
2450
- get: function() {
2451
- return _rebasepro_types.RebaseApiError;
2452
- }
2453
- });
2454
- Object.defineProperty(exports, "RebaseClientError", {
2455
- enumerable: true,
2456
- get: function() {
2457
- return _rebasepro_types.RebaseClientError;
2458
- }
2459
- });
2460
- exports.RebaseWebSocketClient = RebaseWebSocketClient;
2461
- Object.defineProperty(exports, "and", {
2462
- enumerable: true,
2463
- get: function() {
2464
- return _rebasepro_common.and;
2465
- }
2466
- });
2467
- Object.defineProperty(exports, "cond", {
2468
- enumerable: true,
2469
- get: function() {
2470
- return _rebasepro_common.cond;
2471
- }
2472
- });
2473
- exports.createCookieStorage = createCookieStorage;
2474
- exports.createMemoryStorage = createMemoryStorage;
2475
- exports.createRebaseClient = createRebaseClient;
2476
- Object.defineProperty(exports, "or", {
2477
- enumerable: true,
2478
- get: function() {
2479
- return _rebasepro_common.or;
2480
- }
2481
- });
2482
- });
2483
-
2484
- //# sourceMappingURL=index.umd.js.map