@rebasepro/client 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -10
- package/dist/auth.d.ts +35 -38
- package/dist/collection.d.ts +9 -13
- package/dist/data-proxy.test.d.ts +1 -0
- package/dist/errors.d.ts +9 -0
- package/dist/index.d.ts +23 -22
- package/dist/index.es.js +485 -215
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +499 -230
- package/dist/index.umd.js.map +1 -1
- package/dist/sdk_query_builder.d.ts +63 -0
- package/dist/transport.d.ts +2 -6
- package/dist/websocket.d.ts +25 -21
- package/package.json +4 -4
- package/src/auth.ts +188 -64
- package/src/collection.test.ts +92 -3
- package/src/collection.ts +72 -75
- package/src/data-proxy.test.ts +167 -0
- package/src/errors.ts +9 -0
- package/src/index.ts +146 -27
- package/src/reviver.ts +2 -2
- package/src/sdk_query_builder.ts +138 -0
- package/src/storage.ts +64 -32
- package/src/transport.ts +25 -25
- package/src/websocket.ts +133 -135
package/dist/index.umd.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
(function(global, factory) {
|
|
2
|
-
typeof exports === "object" && typeof module !== "undefined" ? factory(exports, require("@rebasepro/
|
|
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
3
|
"exports",
|
|
4
|
-
"@rebasepro/common",
|
|
5
4
|
"@rebasepro/types",
|
|
5
|
+
"@rebasepro/common",
|
|
6
6
|
"@rebasepro/utils"
|
|
7
|
-
], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global["Rebase Client"] = {}, global.
|
|
8
|
-
})(this, function(exports,
|
|
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
9
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
10
10
|
//#region src/reviver.ts
|
|
11
11
|
function rebaseReviver(_key, value) {
|
|
@@ -36,25 +36,16 @@
|
|
|
36
36
|
}
|
|
37
37
|
//#endregion
|
|
38
38
|
//#region src/transport.ts
|
|
39
|
-
var RebaseApiError = class extends Error {
|
|
40
|
-
status;
|
|
41
|
-
code;
|
|
42
|
-
details;
|
|
43
|
-
constructor(status, message, code, details) {
|
|
44
|
-
super(message);
|
|
45
|
-
this.name = "RebaseApiError";
|
|
46
|
-
this.status = status;
|
|
47
|
-
this.code = code;
|
|
48
|
-
this.details = details;
|
|
49
|
-
}
|
|
50
|
-
};
|
|
51
39
|
function buildQueryString(params) {
|
|
52
40
|
if (!params) return "";
|
|
53
41
|
const parts = [];
|
|
54
42
|
if (params.limit != null) parts.push(`limit=${params.limit}`);
|
|
55
43
|
if (params.offset != null) parts.push(`offset=${params.offset}`);
|
|
56
44
|
if (params.page != null) parts.push(`page=${params.page}`);
|
|
57
|
-
if (params.orderBy)
|
|
45
|
+
if (params.orderBy) {
|
|
46
|
+
const wire = (0, _rebasepro_common.serializeOrderBy)(params.orderBy);
|
|
47
|
+
if (wire) parts.push(`orderBy=${encodeURIComponent(wire)}`);
|
|
48
|
+
}
|
|
58
49
|
if (params.searchString) parts.push(`searchString=${encodeURIComponent(params.searchString)}`);
|
|
59
50
|
if (params.include && params.include.length > 0) parts.push(`include=${encodeURIComponent(params.include.join(","))}`);
|
|
60
51
|
if (params.logical) {
|
|
@@ -103,8 +94,7 @@
|
|
|
103
94
|
} catch (e) {}
|
|
104
95
|
const getErrorField = (obj, field) => {
|
|
105
96
|
const err = obj?.error;
|
|
106
|
-
if (err && typeof err === "object" && err !== null
|
|
107
|
-
return obj?.[field];
|
|
97
|
+
if (err && typeof err === "object" && err !== null) return err[field];
|
|
108
98
|
};
|
|
109
99
|
if (res.status === 401 && onUnauthorizedHandler) {
|
|
110
100
|
if (await onUnauthorizedHandler()) {
|
|
@@ -127,7 +117,11 @@
|
|
|
127
117
|
if (!retryRes.ok) {
|
|
128
118
|
let fallbackMessage = retryRes.statusText;
|
|
129
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.`;
|
|
130
|
-
throw new RebaseApiError(
|
|
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
|
+
});
|
|
131
125
|
}
|
|
132
126
|
return retryBody;
|
|
133
127
|
}
|
|
@@ -135,7 +129,11 @@
|
|
|
135
129
|
if (!res.ok) {
|
|
136
130
|
let fallbackMessage = res.statusText;
|
|
137
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.`;
|
|
138
|
-
throw new RebaseApiError(
|
|
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
|
+
});
|
|
139
137
|
}
|
|
140
138
|
return body;
|
|
141
139
|
}
|
|
@@ -171,6 +169,29 @@
|
|
|
171
169
|
}
|
|
172
170
|
//#endregion
|
|
173
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
|
+
};
|
|
174
195
|
function createMemoryStorage() {
|
|
175
196
|
const store = {};
|
|
176
197
|
return {
|
|
@@ -201,11 +222,20 @@
|
|
|
201
222
|
const authPath = opts.authPath || "/auth";
|
|
202
223
|
const autoRefresh = opts.autoRefresh !== false;
|
|
203
224
|
const persistSession = opts.persistSession !== false;
|
|
225
|
+
const authFlowMode = opts.authFlowMode || "json";
|
|
204
226
|
const STORAGE_KEY = "rebase_auth";
|
|
205
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;
|
|
206
231
|
let currentSession = null;
|
|
207
232
|
const listeners = /* @__PURE__ */ new Set();
|
|
208
233
|
let refreshTimeout = null;
|
|
234
|
+
let inFlightRefresh = null;
|
|
235
|
+
let resolveInitialized;
|
|
236
|
+
const isInitialized = new Promise((resolve) => {
|
|
237
|
+
resolveInitialized = resolve;
|
|
238
|
+
});
|
|
209
239
|
function authUrl(endpoint) {
|
|
210
240
|
return transport.baseUrl + transport.apiPath + authPath + endpoint;
|
|
211
241
|
}
|
|
@@ -213,7 +243,11 @@
|
|
|
213
243
|
return transport.fetchFn || globalThis.fetch;
|
|
214
244
|
}
|
|
215
245
|
function throwApiError(status, body, statusText) {
|
|
216
|
-
throw new RebaseApiError(
|
|
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
|
+
});
|
|
217
251
|
}
|
|
218
252
|
function emit(event, session) {
|
|
219
253
|
for (const fn of listeners) try {
|
|
@@ -221,7 +255,7 @@
|
|
|
221
255
|
} catch (e) {}
|
|
222
256
|
}
|
|
223
257
|
function saveSession(session) {
|
|
224
|
-
if (!persistSession) return;
|
|
258
|
+
if (!persistSession || authFlowMode === "cookie") return;
|
|
225
259
|
try {
|
|
226
260
|
storage.setItem(STORAGE_KEY, JSON.stringify(session));
|
|
227
261
|
} catch (e) {}
|
|
@@ -238,28 +272,53 @@
|
|
|
238
272
|
} catch (e) {}
|
|
239
273
|
return null;
|
|
240
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
|
+
}
|
|
241
303
|
function scheduleRefresh(expiresAt) {
|
|
242
304
|
if (refreshTimeout) clearTimeout(refreshTimeout);
|
|
243
305
|
if (!autoRefresh) return;
|
|
244
306
|
const delay = expiresAt - REFRESH_BUFFER_MS - Date.now();
|
|
245
307
|
if (delay <= 0) {
|
|
246
|
-
|
|
308
|
+
attemptScheduledRefresh(0);
|
|
247
309
|
return;
|
|
248
310
|
}
|
|
249
|
-
refreshTimeout = setTimeout(
|
|
250
|
-
|
|
251
|
-
await refreshSession();
|
|
252
|
-
} catch (e) {
|
|
253
|
-
signOut();
|
|
254
|
-
}
|
|
311
|
+
refreshTimeout = setTimeout(() => {
|
|
312
|
+
attemptScheduledRefresh(0);
|
|
255
313
|
}, delay);
|
|
256
314
|
}
|
|
257
315
|
function handleAuthResponse(data, event) {
|
|
316
|
+
const user = mapRawUser(data.user);
|
|
258
317
|
const session = {
|
|
259
318
|
accessToken: data.tokens.accessToken,
|
|
260
|
-
refreshToken: data.tokens.refreshToken,
|
|
319
|
+
refreshToken: data.tokens.refreshToken || currentSession?.refreshToken || "",
|
|
261
320
|
expiresAt: data.tokens.accessTokenExpiresAt,
|
|
262
|
-
user
|
|
321
|
+
user
|
|
263
322
|
};
|
|
264
323
|
currentSession = session;
|
|
265
324
|
saveSession(session);
|
|
@@ -275,7 +334,8 @@
|
|
|
275
334
|
body: JSON.stringify({
|
|
276
335
|
email,
|
|
277
336
|
password
|
|
278
|
-
})
|
|
337
|
+
}),
|
|
338
|
+
credentials: authFlowMode === "cookie" ? "include" : void 0
|
|
279
339
|
});
|
|
280
340
|
const body = await res.json().catch(() => ({}));
|
|
281
341
|
if (!res.ok) throwApiError(res.status, body, res.statusText);
|
|
@@ -296,7 +356,8 @@
|
|
|
296
356
|
const res = await fetchFn(authUrl("/register"), {
|
|
297
357
|
method: "POST",
|
|
298
358
|
headers: { "Content-Type": "application/json" },
|
|
299
|
-
body: JSON.stringify(payload)
|
|
359
|
+
body: JSON.stringify(payload),
|
|
360
|
+
credentials: authFlowMode === "cookie" ? "include" : void 0
|
|
300
361
|
});
|
|
301
362
|
const body = await res.json().catch(() => ({}));
|
|
302
363
|
if (!res.ok) throwApiError(res.status, body, res.statusText);
|
|
@@ -319,7 +380,8 @@
|
|
|
319
380
|
const res = await getFetch()(authUrl("/google"), {
|
|
320
381
|
method: "POST",
|
|
321
382
|
headers: { "Content-Type": "application/json" },
|
|
322
|
-
body: JSON.stringify(payload)
|
|
383
|
+
body: JSON.stringify(payload),
|
|
384
|
+
credentials: authFlowMode === "cookie" ? "include" : void 0
|
|
323
385
|
});
|
|
324
386
|
const responseBody = await res.json().catch(() => ({}));
|
|
325
387
|
if (!res.ok) throwApiError(res.status, responseBody, res.statusText);
|
|
@@ -337,7 +399,8 @@
|
|
|
337
399
|
body: JSON.stringify({
|
|
338
400
|
code,
|
|
339
401
|
redirectUri
|
|
340
|
-
})
|
|
402
|
+
}),
|
|
403
|
+
credentials: authFlowMode === "cookie" ? "include" : void 0
|
|
341
404
|
});
|
|
342
405
|
const body = await res.json().catch(() => ({}));
|
|
343
406
|
if (!res.ok) throwApiError(res.status, body, res.statusText);
|
|
@@ -356,7 +419,8 @@
|
|
|
356
419
|
const res = await getFetch()(authUrl(`/${providerId}`), {
|
|
357
420
|
method: "POST",
|
|
358
421
|
headers: { "Content-Type": "application/json" },
|
|
359
|
-
body: JSON.stringify(payload)
|
|
422
|
+
body: JSON.stringify(payload),
|
|
423
|
+
credentials: authFlowMode === "cookie" ? "include" : void 0
|
|
360
424
|
});
|
|
361
425
|
const body = await res.json().catch(() => ({}));
|
|
362
426
|
if (!res.ok) throwApiError(res.status, body, res.statusText);
|
|
@@ -432,10 +496,11 @@
|
|
|
432
496
|
async function signOut() {
|
|
433
497
|
const fetchFn = getFetch();
|
|
434
498
|
try {
|
|
435
|
-
if (currentSession?.refreshToken) await fetchFn(authUrl("/logout"), {
|
|
499
|
+
if (authFlowMode === "cookie" || currentSession?.refreshToken) await fetchFn(authUrl("/logout"), {
|
|
436
500
|
method: "POST",
|
|
437
501
|
headers: { "Content-Type": "application/json" },
|
|
438
|
-
body: JSON.stringify({ refreshToken: currentSession
|
|
502
|
+
body: JSON.stringify({ refreshToken: currentSession?.refreshToken }),
|
|
503
|
+
credentials: authFlowMode === "cookie" ? "include" : void 0
|
|
439
504
|
});
|
|
440
505
|
} catch (e) {}
|
|
441
506
|
currentSession = null;
|
|
@@ -447,20 +512,35 @@
|
|
|
447
512
|
transport.setToken(null);
|
|
448
513
|
emit("SIGNED_OUT", null);
|
|
449
514
|
}
|
|
450
|
-
|
|
451
|
-
if (
|
|
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");
|
|
452
524
|
const res = await getFetch()(authUrl("/refresh"), {
|
|
453
525
|
method: "POST",
|
|
454
526
|
headers: { "Content-Type": "application/json" },
|
|
455
|
-
body: JSON.stringify({ refreshToken: currentSession
|
|
527
|
+
body: JSON.stringify({ refreshToken: currentSession?.refreshToken }),
|
|
528
|
+
credentials: authFlowMode === "cookie" ? "include" : void 0
|
|
456
529
|
});
|
|
457
530
|
const body = await res.json().catch(() => ({}));
|
|
458
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 {}
|
|
459
539
|
const session = {
|
|
460
|
-
accessToken
|
|
461
|
-
refreshToken: body.tokens.refreshToken,
|
|
540
|
+
accessToken,
|
|
541
|
+
refreshToken: body.tokens.refreshToken || currentSession?.refreshToken || "",
|
|
462
542
|
expiresAt: body.tokens.accessTokenExpiresAt,
|
|
463
|
-
user:
|
|
543
|
+
user: user ?? EMPTY_USER
|
|
464
544
|
};
|
|
465
545
|
currentSession = session;
|
|
466
546
|
saveSession(session);
|
|
@@ -472,6 +552,18 @@
|
|
|
472
552
|
async function getUser() {
|
|
473
553
|
return (await transport.request(authPath + "/me", { method: "GET" })).user;
|
|
474
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
|
+
}
|
|
475
567
|
async function updateUser(updates) {
|
|
476
568
|
const data = await transport.request(authPath + "/me", {
|
|
477
569
|
method: "PATCH",
|
|
@@ -545,7 +637,8 @@
|
|
|
545
637
|
const res = await getFetch()(authUrl("/magic-link/verify"), {
|
|
546
638
|
method: "POST",
|
|
547
639
|
headers: { "Content-Type": "application/json" },
|
|
548
|
-
body: JSON.stringify({ token })
|
|
640
|
+
body: JSON.stringify({ token }),
|
|
641
|
+
credentials: authFlowMode === "cookie" ? "include" : void 0
|
|
549
642
|
});
|
|
550
643
|
const body = await res.json().catch(() => ({}));
|
|
551
644
|
if (!res.ok) throwApiError(res.status, body, res.statusText);
|
|
@@ -592,21 +685,29 @@
|
|
|
592
685
|
}
|
|
593
686
|
if (persistSession) {
|
|
594
687
|
const stored = loadStoredSession();
|
|
595
|
-
if (stored && stored.accessToken
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
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();
|
|
610
711
|
return {
|
|
611
712
|
signInWithEmail,
|
|
612
713
|
signUp,
|
|
@@ -626,6 +727,7 @@
|
|
|
626
727
|
signOut,
|
|
627
728
|
refreshSession,
|
|
628
729
|
getUser,
|
|
730
|
+
findUserByEmail,
|
|
629
731
|
updateUser,
|
|
630
732
|
resetPasswordForEmail,
|
|
631
733
|
resetPassword,
|
|
@@ -639,7 +741,8 @@
|
|
|
639
741
|
revokeAllSessions,
|
|
640
742
|
getAuthConfig,
|
|
641
743
|
getSession,
|
|
642
|
-
onAuthStateChange
|
|
744
|
+
onAuthStateChange,
|
|
745
|
+
isInitialized: () => isInitialized
|
|
643
746
|
};
|
|
644
747
|
}
|
|
645
748
|
function createCookieStorage(options = {}) {
|
|
@@ -816,20 +919,108 @@
|
|
|
816
919
|
};
|
|
817
920
|
}
|
|
818
921
|
//#endregion
|
|
819
|
-
//#region src/
|
|
922
|
+
//#region src/sdk_query_builder.ts
|
|
820
923
|
/**
|
|
821
|
-
*
|
|
822
|
-
*
|
|
823
|
-
*
|
|
824
|
-
*
|
|
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
|
|
825
935
|
*/
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
}
|
|
832
|
-
|
|
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
|
|
833
1024
|
function createCollectionClient(transport, slug, ws) {
|
|
834
1025
|
const basePath = `/data/${slug}`;
|
|
835
1026
|
const client = {
|
|
@@ -837,7 +1028,7 @@
|
|
|
837
1028
|
const qs = buildQueryString(params);
|
|
838
1029
|
const raw = await transport.request(basePath + qs, { method: "GET" });
|
|
839
1030
|
return {
|
|
840
|
-
data:
|
|
1031
|
+
data: raw.data || [],
|
|
841
1032
|
meta: raw.meta
|
|
842
1033
|
};
|
|
843
1034
|
},
|
|
@@ -845,28 +1036,28 @@
|
|
|
845
1036
|
try {
|
|
846
1037
|
const raw = await transport.request(`${basePath}/${encodeURIComponent(String(id))}`, { method: "GET" });
|
|
847
1038
|
if (!raw) return void 0;
|
|
848
|
-
return
|
|
1039
|
+
return raw;
|
|
849
1040
|
} catch (err) {
|
|
850
|
-
if (err instanceof RebaseApiError && err.status === 404) return;
|
|
1041
|
+
if (err instanceof _rebasepro_types.RebaseApiError && err.status === 404) return;
|
|
851
1042
|
throw err;
|
|
852
1043
|
}
|
|
853
1044
|
},
|
|
854
1045
|
async create(data, id) {
|
|
855
1046
|
const body = { ...data };
|
|
856
1047
|
if (id !== void 0) body.id = id;
|
|
857
|
-
return
|
|
1048
|
+
return await transport.request(basePath, {
|
|
858
1049
|
method: "POST",
|
|
859
1050
|
body: JSON.stringify(body)
|
|
860
|
-
})
|
|
1051
|
+
});
|
|
861
1052
|
},
|
|
862
1053
|
async update(id, data) {
|
|
863
|
-
return
|
|
1054
|
+
return await transport.request(`${basePath}/${encodeURIComponent(String(id))}`, {
|
|
864
1055
|
method: "PUT",
|
|
865
1056
|
body: JSON.stringify(data)
|
|
866
|
-
})
|
|
1057
|
+
});
|
|
867
1058
|
},
|
|
868
1059
|
async delete(id) {
|
|
869
|
-
|
|
1060
|
+
await transport.request(`${basePath}/${encodeURIComponent(String(id))}`, { method: "DELETE" });
|
|
870
1061
|
},
|
|
871
1062
|
async count(params) {
|
|
872
1063
|
const qs = buildQueryString({
|
|
@@ -877,24 +1068,24 @@
|
|
|
877
1068
|
return (await transport.request(basePath + "/count" + qs, { method: "GET" })).count ?? 0;
|
|
878
1069
|
},
|
|
879
1070
|
where(columnOrCondition, operator, value) {
|
|
880
|
-
const builder = new
|
|
1071
|
+
const builder = new SDKQueryBuilder(client);
|
|
881
1072
|
if (typeof columnOrCondition === "object") return builder.where(columnOrCondition);
|
|
882
1073
|
return builder.where(columnOrCondition, operator, value);
|
|
883
1074
|
},
|
|
884
1075
|
orderBy(column, direction) {
|
|
885
|
-
return new
|
|
1076
|
+
return new SDKQueryBuilder(client).orderBy(column, direction);
|
|
886
1077
|
},
|
|
887
1078
|
limit(count) {
|
|
888
|
-
return new
|
|
1079
|
+
return new SDKQueryBuilder(client).limit(count);
|
|
889
1080
|
},
|
|
890
1081
|
offset(count) {
|
|
891
|
-
return new
|
|
1082
|
+
return new SDKQueryBuilder(client).offset(count);
|
|
892
1083
|
},
|
|
893
1084
|
search(searchString) {
|
|
894
|
-
return new
|
|
1085
|
+
return new SDKQueryBuilder(client).search(searchString);
|
|
895
1086
|
},
|
|
896
1087
|
include(...relations) {
|
|
897
|
-
return new
|
|
1088
|
+
return new SDKQueryBuilder(client).include(...relations);
|
|
898
1089
|
}
|
|
899
1090
|
};
|
|
900
1091
|
if (ws) {
|
|
@@ -906,33 +1097,46 @@
|
|
|
906
1097
|
filter: params?.where,
|
|
907
1098
|
limit: params?.limit,
|
|
908
1099
|
startAfter: params?.offset ? String(params.offset) : void 0,
|
|
909
|
-
orderBy: params?.orderBy?.
|
|
910
|
-
order: params?.orderBy?.
|
|
1100
|
+
orderBy: params?.orderBy?.[0],
|
|
1101
|
+
order: params?.orderBy?.[1],
|
|
911
1102
|
searchString: params?.searchString
|
|
912
|
-
}, (
|
|
1103
|
+
}, (incomingRows) => {
|
|
913
1104
|
const currentUpdateId = ++lastUpdateId;
|
|
914
1105
|
const requestedLimit = params?.limit || 20;
|
|
915
1106
|
const offset = params?.offset || 0;
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
total: entities.length,
|
|
920
|
-
limit: requestedLimit,
|
|
921
|
-
offset,
|
|
922
|
-
hasMore: entities.length >= requestedLimit
|
|
923
|
-
}
|
|
924
|
-
});
|
|
1107
|
+
const rows = incomingRows;
|
|
1108
|
+
const heuristicTotal = rows.length;
|
|
1109
|
+
const heuristicHasMore = rows.length >= requestedLimit;
|
|
925
1110
|
if (client.count) client.count(params).then((total) => {
|
|
926
1111
|
if (active && currentUpdateId === lastUpdateId) onUpdate({
|
|
927
|
-
data:
|
|
1112
|
+
data: rows,
|
|
928
1113
|
meta: {
|
|
929
1114
|
total,
|
|
930
1115
|
limit: requestedLimit,
|
|
931
1116
|
offset,
|
|
932
|
-
hasMore: 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
|
|
933
1128
|
}
|
|
934
1129
|
});
|
|
935
|
-
})
|
|
1130
|
+
});
|
|
1131
|
+
else onUpdate({
|
|
1132
|
+
data: rows,
|
|
1133
|
+
meta: {
|
|
1134
|
+
total: heuristicTotal,
|
|
1135
|
+
limit: requestedLimit,
|
|
1136
|
+
offset,
|
|
1137
|
+
hasMore: heuristicHasMore
|
|
1138
|
+
}
|
|
1139
|
+
});
|
|
936
1140
|
}, onError);
|
|
937
1141
|
return () => {
|
|
938
1142
|
active = false;
|
|
@@ -940,11 +1144,11 @@
|
|
|
940
1144
|
};
|
|
941
1145
|
};
|
|
942
1146
|
client.listenById = (id, onUpdate, onError) => {
|
|
943
|
-
return ws.
|
|
1147
|
+
return ws.listenOne({
|
|
944
1148
|
path: slug,
|
|
945
|
-
|
|
946
|
-
}, (
|
|
947
|
-
if (
|
|
1149
|
+
id: String(id)
|
|
1150
|
+
}, (row) => {
|
|
1151
|
+
if (row) onUpdate(row);
|
|
948
1152
|
else onUpdate(void 0);
|
|
949
1153
|
}, onError);
|
|
950
1154
|
};
|
|
@@ -992,10 +1196,12 @@
|
|
|
992
1196
|
if (!storageId) return path;
|
|
993
1197
|
return `${path}${path.includes("?") ? "&" : "?"}storageId=${encodeURIComponent(storageId)}`;
|
|
994
1198
|
};
|
|
995
|
-
async function putObject({ file, key, metadata, bucket }) {
|
|
1199
|
+
async function putObject({ file, key, metadata, bucket, public: isPublic }) {
|
|
996
1200
|
const formData = new FormData();
|
|
997
1201
|
formData.append("file", file);
|
|
998
|
-
|
|
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);
|
|
999
1205
|
if (bucket) formData.append("bucket", bucket);
|
|
1000
1206
|
if (storageId) formData.append("storageId", storageId);
|
|
1001
1207
|
if (metadata) {
|
|
@@ -1009,8 +1215,11 @@
|
|
|
1009
1215
|
}
|
|
1010
1216
|
async function getSignedUrl(keyOrUrl, bucket) {
|
|
1011
1217
|
const cacheKey = bucket ? `${bucket}/${keyOrUrl}` : keyOrUrl;
|
|
1012
|
-
const
|
|
1013
|
-
if (
|
|
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
|
+
}
|
|
1014
1223
|
let filePath = keyOrUrl;
|
|
1015
1224
|
if (filePath && (filePath.startsWith("local://") || filePath.startsWith("s3://") || filePath.startsWith("gs://"))) filePath = filePath.substring(filePath.indexOf("://") + 3);
|
|
1016
1225
|
if (bucket && filePath && !filePath.startsWith(bucket)) filePath = `${bucket}/${filePath}`;
|
|
@@ -1018,15 +1227,32 @@
|
|
|
1018
1227
|
url: null,
|
|
1019
1228
|
fileNotFound: true
|
|
1020
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
|
+
}
|
|
1021
1235
|
try {
|
|
1022
1236
|
const result = await transport.request(withStorageId(`/storage/metadata/${filePath}`));
|
|
1023
|
-
|
|
1024
|
-
|
|
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}` : "";
|
|
1025
1247
|
const downloadConfig = {
|
|
1026
1248
|
url: withStorageId(`${transport.baseUrl}${transport.apiPath}/storage/file/${filePath}${tokenQuery}`),
|
|
1027
1249
|
metadata: result.data
|
|
1028
1250
|
};
|
|
1029
|
-
|
|
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
|
+
});
|
|
1030
1256
|
return downloadConfig;
|
|
1031
1257
|
} catch (e) {
|
|
1032
1258
|
if (e instanceof Error && "status" in e && e.status === 404) return {
|
|
@@ -1037,16 +1263,13 @@
|
|
|
1037
1263
|
}
|
|
1038
1264
|
}
|
|
1039
1265
|
async function getObject(key, bucket) {
|
|
1040
|
-
|
|
1041
|
-
if (
|
|
1042
|
-
|
|
1043
|
-
if (!filePath || filePath.trim() === "" || filePath === "/") return null;
|
|
1044
|
-
const url = withStorageId(`${transport.baseUrl}${transport.apiPath}/storage/file/${filePath}`);
|
|
1045
|
-
const response = await transport.fetchFn(url, { headers: transport.getHeaders ? transport.getHeaders() : {} });
|
|
1266
|
+
const downloadConfig = await getSignedUrl(key, bucket);
|
|
1267
|
+
if (downloadConfig.fileNotFound || !downloadConfig.url) return null;
|
|
1268
|
+
const response = await transport.fetchFn(downloadConfig.url, { headers: {} });
|
|
1046
1269
|
if (response.status === 404) return null;
|
|
1047
1270
|
if (!response.ok) throw new Error("Failed to get file");
|
|
1048
1271
|
const blob = await response.blob();
|
|
1049
|
-
const fileName =
|
|
1272
|
+
const fileName = (bucket ? `${bucket}/${key}` : key).split("/").pop() || "file";
|
|
1050
1273
|
return new File([blob], fileName, { type: blob.type });
|
|
1051
1274
|
}
|
|
1052
1275
|
async function deleteObject(key, bucket) {
|
|
@@ -1148,16 +1371,15 @@
|
|
|
1148
1371
|
errorCode: typeof errPayload === "object" ? errPayload.code : payload?.code
|
|
1149
1372
|
};
|
|
1150
1373
|
}
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
};
|
|
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
|
+
*/
|
|
1161
1383
|
var RebaseWebSocketClient = class {
|
|
1162
1384
|
websocketUrl;
|
|
1163
1385
|
ws = null;
|
|
@@ -1173,7 +1395,7 @@
|
|
|
1173
1395
|
if (this.listeners.has(event)) this.listeners.get(event).forEach((cb) => cb(...args));
|
|
1174
1396
|
}
|
|
1175
1397
|
collectionSubscriptions = /* @__PURE__ */ new Map();
|
|
1176
|
-
|
|
1398
|
+
singleSubscriptions = /* @__PURE__ */ new Map();
|
|
1177
1399
|
backendToCollectionKey = /* @__PURE__ */ new Map();
|
|
1178
1400
|
backendToEntityKey = /* @__PURE__ */ new Map();
|
|
1179
1401
|
pendingRequests = /* @__PURE__ */ new Map();
|
|
@@ -1308,7 +1530,7 @@
|
|
|
1308
1530
|
request.message._queuedResolve = request.resolve;
|
|
1309
1531
|
request.message._queuedReject = request.reject;
|
|
1310
1532
|
this.messageQueue.push(request.message);
|
|
1311
|
-
} else request.reject(new
|
|
1533
|
+
} else request.reject(new _rebasepro_types.RebaseApiError("Connection closed"));
|
|
1312
1534
|
this.pendingRequests.delete(reqId);
|
|
1313
1535
|
}
|
|
1314
1536
|
this.attemptReconnect();
|
|
@@ -1375,7 +1597,7 @@
|
|
|
1375
1597
|
}
|
|
1376
1598
|
}
|
|
1377
1599
|
/**
|
|
1378
|
-
* Shared logic for re-subscribing a collection or
|
|
1600
|
+
* Shared logic for re-subscribing a collection or row subscription
|
|
1379
1601
|
* after an auth error is resolved by refreshing credentials.
|
|
1380
1602
|
*/
|
|
1381
1603
|
resubscribeAfterAuthRefresh(message, subscription, subscriptionKey, idPrefix, backendKeyMap, messageType) {
|
|
@@ -1400,7 +1622,7 @@
|
|
|
1400
1622
|
});
|
|
1401
1623
|
} else {
|
|
1402
1624
|
const { errorMessage, errorCode } = extractMessageError(message);
|
|
1403
|
-
const error = new
|
|
1625
|
+
const error = new _rebasepro_types.RebaseApiError(errorMessage, { code: errorCode });
|
|
1404
1626
|
subscription.callbacks.forEach((callback) => {
|
|
1405
1627
|
if (callback.onError) callback.onError(error);
|
|
1406
1628
|
});
|
|
@@ -1421,7 +1643,7 @@
|
|
|
1421
1643
|
if (refreshed && pendingReq.message) this.doSendMessage(pendingReq.message, pendingReq.resolve, pendingReq.reject).catch(pendingReq.reject);
|
|
1422
1644
|
else {
|
|
1423
1645
|
const { errorMessage, errorCode } = extractMessageError(message);
|
|
1424
|
-
pendingReq.reject(new
|
|
1646
|
+
pendingReq.reject(new _rebasepro_types.RebaseApiError(errorMessage, { code: errorCode }));
|
|
1425
1647
|
}
|
|
1426
1648
|
}).catch((err) => {
|
|
1427
1649
|
pendingReq.reject(err);
|
|
@@ -1429,7 +1651,7 @@
|
|
|
1429
1651
|
} else {
|
|
1430
1652
|
this.pendingRequests.delete(requestId);
|
|
1431
1653
|
const { errorMessage, errorCode } = extractMessageError(message);
|
|
1432
|
-
pendingReq.reject(new
|
|
1654
|
+
pendingReq.reject(new _rebasepro_types.RebaseApiError(errorMessage, { code: errorCode }));
|
|
1433
1655
|
}
|
|
1434
1656
|
else {
|
|
1435
1657
|
this.pendingRequests.delete(requestId);
|
|
@@ -1442,14 +1664,14 @@
|
|
|
1442
1664
|
if (subscriptionKey) {
|
|
1443
1665
|
const collectionSub = this.collectionSubscriptions.get(subscriptionKey);
|
|
1444
1666
|
if (collectionSub) {
|
|
1445
|
-
const
|
|
1446
|
-
const
|
|
1447
|
-
collectionSub.latestData =
|
|
1667
|
+
const incomingRows = message.rows || [];
|
|
1668
|
+
const rows = this.mergeRows(collectionSub.latestData, incomingRows);
|
|
1669
|
+
collectionSub.latestData = rows;
|
|
1448
1670
|
collectionSub.lastUpdated = Date.now();
|
|
1449
1671
|
collectionSub.isInitialDataReceived = true;
|
|
1450
1672
|
collectionSub.callbacks.forEach((callback) => {
|
|
1451
1673
|
try {
|
|
1452
|
-
callback.onUpdate(
|
|
1674
|
+
callback.onUpdate(rows);
|
|
1453
1675
|
} catch (error) {
|
|
1454
1676
|
console.error("Error in collection subscription callback:", error);
|
|
1455
1677
|
if (callback.onError) callback.onError(error instanceof Error ? error : new Error(String(error)));
|
|
@@ -1459,21 +1681,22 @@
|
|
|
1459
1681
|
}
|
|
1460
1682
|
}
|
|
1461
1683
|
}
|
|
1462
|
-
if (subscriptionId && type === "
|
|
1684
|
+
if (subscriptionId && type === "collection_patch") {
|
|
1463
1685
|
const subscriptionKey = this.backendToCollectionKey.get(subscriptionId);
|
|
1464
1686
|
if (subscriptionKey) {
|
|
1465
1687
|
const collectionSub = this.collectionSubscriptions.get(subscriptionKey);
|
|
1466
1688
|
if (collectionSub && collectionSub.isInitialDataReceived && collectionSub.latestData) {
|
|
1467
|
-
const
|
|
1468
|
-
const patchEntityId = message.
|
|
1689
|
+
const patchWireEntity = message.row ?? null;
|
|
1690
|
+
const patchEntityId = message.id;
|
|
1691
|
+
const patchRow = patchWireEntity ? patchWireEntity : null;
|
|
1469
1692
|
let updated;
|
|
1470
|
-
if (
|
|
1693
|
+
if (patchRow === null) updated = collectionSub.latestData.filter((e) => String(e.id) !== String(patchEntityId));
|
|
1471
1694
|
else {
|
|
1472
|
-
const idx = collectionSub.latestData.findIndex((e) => String(e.id) === String(
|
|
1695
|
+
const idx = collectionSub.latestData.findIndex((e) => String(e.id) === String(patchRow.id));
|
|
1473
1696
|
if (idx >= 0) {
|
|
1474
1697
|
updated = [...collectionSub.latestData];
|
|
1475
|
-
updated[idx] =
|
|
1476
|
-
} else updated = [
|
|
1698
|
+
updated[idx] = patchRow;
|
|
1699
|
+
} else updated = [patchRow, ...collectionSub.latestData];
|
|
1477
1700
|
}
|
|
1478
1701
|
collectionSub.latestData = updated;
|
|
1479
1702
|
collectionSub.lastUpdated = Date.now();
|
|
@@ -1489,20 +1712,21 @@
|
|
|
1489
1712
|
}
|
|
1490
1713
|
}
|
|
1491
1714
|
}
|
|
1492
|
-
if (subscriptionId && type === "
|
|
1715
|
+
if (subscriptionId && type === "single_update") {
|
|
1493
1716
|
const subscriptionKey = this.backendToEntityKey.get(subscriptionId);
|
|
1494
1717
|
if (subscriptionKey) {
|
|
1495
|
-
const entitySub = this.
|
|
1718
|
+
const entitySub = this.singleSubscriptions.get(subscriptionKey);
|
|
1496
1719
|
if (entitySub) {
|
|
1497
|
-
const
|
|
1498
|
-
|
|
1720
|
+
const wireEntity = message.row ?? null;
|
|
1721
|
+
const row = wireEntity ? wireEntity : null;
|
|
1722
|
+
entitySub.latestData = row;
|
|
1499
1723
|
entitySub.lastUpdated = Date.now();
|
|
1500
1724
|
entitySub.isInitialDataReceived = true;
|
|
1501
1725
|
entitySub.callbacks.forEach((callback) => {
|
|
1502
1726
|
try {
|
|
1503
|
-
callback.onUpdate(
|
|
1727
|
+
callback.onUpdate(row);
|
|
1504
1728
|
} catch (error) {
|
|
1505
|
-
console.error("Error in
|
|
1729
|
+
console.error("Error in row subscription callback:", error);
|
|
1506
1730
|
if (callback.onError) callback.onError(error instanceof Error ? error : new Error(String(error)));
|
|
1507
1731
|
}
|
|
1508
1732
|
});
|
|
@@ -1520,7 +1744,7 @@
|
|
|
1520
1744
|
return;
|
|
1521
1745
|
}
|
|
1522
1746
|
const { errorMessage, errorCode } = extractMessageError(message);
|
|
1523
|
-
const error = new
|
|
1747
|
+
const error = new _rebasepro_types.RebaseApiError(errorMessage, { code: errorCode });
|
|
1524
1748
|
collectionSub.callbacks.forEach((callback) => {
|
|
1525
1749
|
if (callback.onError) callback.onError(error);
|
|
1526
1750
|
});
|
|
@@ -1529,14 +1753,14 @@
|
|
|
1529
1753
|
}
|
|
1530
1754
|
const entityKey = this.backendToEntityKey.get(subscriptionId);
|
|
1531
1755
|
if (entityKey) {
|
|
1532
|
-
const entitySub = this.
|
|
1756
|
+
const entitySub = this.singleSubscriptions.get(entityKey);
|
|
1533
1757
|
if (entitySub) {
|
|
1534
1758
|
if (this.isAuthError(message)) {
|
|
1535
|
-
this.resubscribeAfterAuthRefresh(message, entitySub, entityKey, "
|
|
1759
|
+
this.resubscribeAfterAuthRefresh(message, entitySub, entityKey, "row", this.backendToEntityKey, "subscribe_one");
|
|
1536
1760
|
return;
|
|
1537
1761
|
}
|
|
1538
1762
|
const { errorMessage, errorCode } = extractMessageError(message);
|
|
1539
|
-
const error = new
|
|
1763
|
+
const error = new _rebasepro_types.RebaseApiError(errorMessage, { code: errorCode });
|
|
1540
1764
|
entitySub.callbacks.forEach((callback) => {
|
|
1541
1765
|
if (callback.onError) callback.onError(error);
|
|
1542
1766
|
});
|
|
@@ -1550,7 +1774,7 @@
|
|
|
1550
1774
|
if (message.type === "ERROR" || message.error) {
|
|
1551
1775
|
if (callback.onError) {
|
|
1552
1776
|
const { errorMessage, errorCode } = extractMessageError(message);
|
|
1553
|
-
callback.onError(new
|
|
1777
|
+
callback.onError(new _rebasepro_types.RebaseApiError(errorMessage, { code: errorCode }));
|
|
1554
1778
|
}
|
|
1555
1779
|
} else callback.onUpdate(message);
|
|
1556
1780
|
}
|
|
@@ -1624,15 +1848,14 @@
|
|
|
1624
1848
|
if (message.type !== "AUTHENTICATE" && this.getAuthToken && !this.isAuthenticated) try {
|
|
1625
1849
|
await this.ensureAuthenticated();
|
|
1626
1850
|
} catch (error) {
|
|
1627
|
-
|
|
1628
|
-
reject(new ApiError(errorMessage, errorMessage));
|
|
1851
|
+
reject(new _rebasepro_types.RebaseApiError(error instanceof Error ? error.message : "Authentication required"));
|
|
1629
1852
|
return;
|
|
1630
1853
|
}
|
|
1631
1854
|
const requestId = message.requestId || `req_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
1632
1855
|
message.requestId = requestId;
|
|
1633
1856
|
const expectsResponse = ![
|
|
1634
1857
|
"subscribe_collection",
|
|
1635
|
-
"
|
|
1858
|
+
"subscribe_one",
|
|
1636
1859
|
"unsubscribe",
|
|
1637
1860
|
"join_channel",
|
|
1638
1861
|
"leave_channel",
|
|
@@ -1645,7 +1868,7 @@
|
|
|
1645
1868
|
const timeoutHandle = setTimeout(() => {
|
|
1646
1869
|
if (this.pendingRequests.has(requestId)) {
|
|
1647
1870
|
this.pendingRequests.delete(requestId);
|
|
1648
|
-
reject(new
|
|
1871
|
+
reject(new _rebasepro_types.RebaseApiError("Request timed out"));
|
|
1649
1872
|
}
|
|
1650
1873
|
}, this.requestTimeoutMs);
|
|
1651
1874
|
this.pendingRequests.set(requestId, {
|
|
@@ -1665,30 +1888,30 @@
|
|
|
1665
1888
|
if (!expectsResponse) resolve(void 0);
|
|
1666
1889
|
} catch (error) {
|
|
1667
1890
|
if (expectsResponse) this.pendingRequests.delete(requestId);
|
|
1668
|
-
reject(new
|
|
1891
|
+
reject(new _rebasepro_types.RebaseApiError("Failed to send message", { cause: error }));
|
|
1669
1892
|
}
|
|
1670
1893
|
}
|
|
1671
1894
|
async fetchCollection(props) {
|
|
1672
1895
|
return (await this.sendMessage({
|
|
1673
1896
|
type: "FETCH_COLLECTION",
|
|
1674
1897
|
payload: props
|
|
1675
|
-
})).
|
|
1898
|
+
})).rows || [];
|
|
1676
1899
|
}
|
|
1677
|
-
async
|
|
1900
|
+
async fetchOne(props) {
|
|
1678
1901
|
return (await this.sendMessage({
|
|
1679
|
-
type: "
|
|
1902
|
+
type: "FETCH_ONE",
|
|
1680
1903
|
payload: props
|
|
1681
|
-
})).
|
|
1904
|
+
})).row ?? void 0;
|
|
1682
1905
|
}
|
|
1683
|
-
async
|
|
1906
|
+
async save(props) {
|
|
1684
1907
|
return (await this.sendMessage({
|
|
1685
|
-
type: "
|
|
1908
|
+
type: "SAVE",
|
|
1686
1909
|
payload: props
|
|
1687
|
-
})).
|
|
1910
|
+
})).row;
|
|
1688
1911
|
}
|
|
1689
|
-
async
|
|
1912
|
+
async delete(props) {
|
|
1690
1913
|
await this.sendMessage({
|
|
1691
|
-
type: "
|
|
1914
|
+
type: "DELETE",
|
|
1692
1915
|
payload: props
|
|
1693
1916
|
});
|
|
1694
1917
|
}
|
|
@@ -1713,21 +1936,21 @@
|
|
|
1713
1936
|
async fetchCurrentDatabase() {
|
|
1714
1937
|
return (await this.sendMessage({ type: "FETCH_CURRENT_DATABASE" })).database;
|
|
1715
1938
|
}
|
|
1716
|
-
async checkUniqueField(path, name, value,
|
|
1939
|
+
async checkUniqueField(path, name, value, id, collection) {
|
|
1717
1940
|
return (await this.sendMessage({
|
|
1718
1941
|
type: "CHECK_UNIQUE_FIELD",
|
|
1719
1942
|
payload: {
|
|
1720
1943
|
path,
|
|
1721
1944
|
name,
|
|
1722
1945
|
value,
|
|
1723
|
-
|
|
1946
|
+
id,
|
|
1724
1947
|
collection
|
|
1725
1948
|
}
|
|
1726
1949
|
})).isUnique;
|
|
1727
1950
|
}
|
|
1728
|
-
async
|
|
1951
|
+
async count(props) {
|
|
1729
1952
|
return (await this.sendMessage({
|
|
1730
|
-
type: "
|
|
1953
|
+
type: "COUNT",
|
|
1731
1954
|
payload: props
|
|
1732
1955
|
})).count;
|
|
1733
1956
|
}
|
|
@@ -1819,33 +2042,31 @@
|
|
|
1819
2042
|
return val;
|
|
1820
2043
|
}
|
|
1821
2044
|
/**
|
|
1822
|
-
* Merge incoming
|
|
1823
|
-
* for
|
|
1824
|
-
* React re-renders when the server refetches all
|
|
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
|
|
1825
2048
|
* haven't actually changed.
|
|
1826
2049
|
*/
|
|
1827
|
-
|
|
2050
|
+
mergeRows(cached, incoming) {
|
|
1828
2051
|
if (!cached || cached.length === 0) return incoming;
|
|
1829
2052
|
const cachedById = /* @__PURE__ */ new Map();
|
|
1830
|
-
for (const
|
|
1831
|
-
return incoming.map((
|
|
1832
|
-
const
|
|
1833
|
-
if (!
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
console.debug(`[RebaseWS] Row ${incomingEntity.id} refetch mismatch:\n`, JSON.stringify(mismatches, null, 2));
|
|
1846
|
-
}
|
|
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));
|
|
1847
2068
|
}
|
|
1848
|
-
return
|
|
2069
|
+
return incomingRow;
|
|
1849
2070
|
});
|
|
1850
2071
|
}
|
|
1851
2072
|
listenCollection(props, onUpdate, onError) {
|
|
@@ -1913,10 +2134,10 @@
|
|
|
1913
2134
|
}
|
|
1914
2135
|
};
|
|
1915
2136
|
}
|
|
1916
|
-
|
|
1917
|
-
const subscriptionKey = this.
|
|
2137
|
+
listenOne(props, onUpdate, onError) {
|
|
2138
|
+
const subscriptionKey = this.createSingleSubscriptionKey(props);
|
|
1918
2139
|
const callbackId = `callback_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
1919
|
-
const existingSubscription = this.
|
|
2140
|
+
const existingSubscription = this.singleSubscriptions.get(subscriptionKey);
|
|
1920
2141
|
if (existingSubscription) {
|
|
1921
2142
|
const callbackMap = existingSubscription.callbacks;
|
|
1922
2143
|
callbackMap.set(callbackId, {
|
|
@@ -1926,13 +2147,13 @@
|
|
|
1926
2147
|
if (existingSubscription.latestData !== void 0 && existingSubscription.isInitialDataReceived) try {
|
|
1927
2148
|
onUpdate(existingSubscription.latestData);
|
|
1928
2149
|
} catch (error) {
|
|
1929
|
-
console.error("Error in
|
|
2150
|
+
console.error("Error in row subscription callback:", error);
|
|
1930
2151
|
if (onError) onError(error instanceof Error ? error : new Error(String(error)));
|
|
1931
2152
|
}
|
|
1932
2153
|
return () => {
|
|
1933
2154
|
callbackMap.delete(callbackId);
|
|
1934
2155
|
if (callbackMap.size === 0) {
|
|
1935
|
-
this.
|
|
2156
|
+
this.singleSubscriptions.delete(subscriptionKey);
|
|
1936
2157
|
this.backendToEntityKey.delete(existingSubscription.backendSubscriptionId);
|
|
1937
2158
|
if (this.isConnected && this.ws) this.sendMessage({
|
|
1938
2159
|
type: "unsubscribe",
|
|
@@ -1947,14 +2168,14 @@
|
|
|
1947
2168
|
onUpdate,
|
|
1948
2169
|
onError
|
|
1949
2170
|
});
|
|
1950
|
-
this.
|
|
2171
|
+
this.singleSubscriptions.set(subscriptionKey, {
|
|
1951
2172
|
backendSubscriptionId,
|
|
1952
2173
|
callbacks: callbackMap,
|
|
1953
2174
|
props
|
|
1954
2175
|
});
|
|
1955
2176
|
this.backendToEntityKey.set(backendSubscriptionId, subscriptionKey);
|
|
1956
2177
|
this.sendMessage({
|
|
1957
|
-
type: "
|
|
2178
|
+
type: "subscribe_one",
|
|
1958
2179
|
payload: {
|
|
1959
2180
|
...props,
|
|
1960
2181
|
subscriptionId: backendSubscriptionId
|
|
@@ -1963,12 +2184,12 @@
|
|
|
1963
2184
|
if (onError) onError(error);
|
|
1964
2185
|
});
|
|
1965
2186
|
return () => {
|
|
1966
|
-
const subscription = this.
|
|
2187
|
+
const subscription = this.singleSubscriptions.get(subscriptionKey);
|
|
1967
2188
|
if (subscription) {
|
|
1968
2189
|
const callbacks = subscription.callbacks;
|
|
1969
2190
|
callbacks.delete(callbackId);
|
|
1970
2191
|
if (callbacks.size === 0) {
|
|
1971
|
-
this.
|
|
2192
|
+
this.singleSubscriptions.delete(subscriptionKey);
|
|
1972
2193
|
this.backendToEntityKey.delete(subscription.backendSubscriptionId);
|
|
1973
2194
|
if (this.isConnected && this.ws) this.sendMessage({
|
|
1974
2195
|
type: "unsubscribe",
|
|
@@ -1984,7 +2205,7 @@
|
|
|
1984
2205
|
* we need to re-register everything to resume receiving updates.
|
|
1985
2206
|
*/
|
|
1986
2207
|
resubscribeAll() {
|
|
1987
|
-
console.debug(`[WS] Re-subscribing: ${this.collectionSubscriptions.size} collection(s), ${this.
|
|
2208
|
+
console.debug(`[WS] Re-subscribing: ${this.collectionSubscriptions.size} collection(s), ${this.singleSubscriptions.size} row(ies)`);
|
|
1988
2209
|
for (const [key, sub] of this.collectionSubscriptions.entries()) {
|
|
1989
2210
|
const oldBackendId = sub.backendSubscriptionId;
|
|
1990
2211
|
const newBackendId = `collection_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
@@ -2001,20 +2222,20 @@
|
|
|
2001
2222
|
console.error("[WS] Failed to re-subscribe collection:", key, error);
|
|
2002
2223
|
});
|
|
2003
2224
|
}
|
|
2004
|
-
for (const [key, sub] of this.
|
|
2225
|
+
for (const [key, sub] of this.singleSubscriptions.entries()) {
|
|
2005
2226
|
const oldBackendId = sub.backendSubscriptionId;
|
|
2006
2227
|
const newBackendId = `entity_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
2007
2228
|
sub.backendSubscriptionId = newBackendId;
|
|
2008
2229
|
this.backendToEntityKey.delete(oldBackendId);
|
|
2009
2230
|
this.backendToEntityKey.set(newBackendId, key);
|
|
2010
2231
|
this.sendMessage({
|
|
2011
|
-
type: "
|
|
2232
|
+
type: "subscribe_one",
|
|
2012
2233
|
payload: {
|
|
2013
2234
|
...sub.props,
|
|
2014
2235
|
subscriptionId: newBackendId
|
|
2015
2236
|
}
|
|
2016
2237
|
}).catch((error) => {
|
|
2017
|
-
console.error("[WS] Failed to re-subscribe
|
|
2238
|
+
console.error("[WS] Failed to re-subscribe row:", key, error);
|
|
2018
2239
|
});
|
|
2019
2240
|
}
|
|
2020
2241
|
}
|
|
@@ -2037,8 +2258,8 @@
|
|
|
2037
2258
|
return value;
|
|
2038
2259
|
});
|
|
2039
2260
|
}
|
|
2040
|
-
|
|
2041
|
-
return `${props.path}|${props.
|
|
2261
|
+
createSingleSubscriptionKey(props) {
|
|
2262
|
+
return `${props.path}|${props.id}`;
|
|
2042
2263
|
}
|
|
2043
2264
|
};
|
|
2044
2265
|
//#endregion
|
|
@@ -2126,7 +2347,45 @@
|
|
|
2126
2347
|
return false;
|
|
2127
2348
|
}
|
|
2128
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
|
+
}
|
|
2129
2387
|
const collectionClients = /* @__PURE__ */ new Map();
|
|
2388
|
+
let untypedWarned = false;
|
|
2130
2389
|
function collection(slug) {
|
|
2131
2390
|
if (!collectionClients.has(slug)) collectionClients.set(slug, createCollectionClient(transport, slug, ws));
|
|
2132
2391
|
return collectionClients.get(slug);
|
|
@@ -2135,7 +2394,19 @@
|
|
|
2135
2394
|
if (prop === "collection") return collection;
|
|
2136
2395
|
if (typeof prop === "symbol") return void 0;
|
|
2137
2396
|
if (typeof prop === "string" && prop !== "then" && prop !== "toJSON" && prop !== "$$typeof") {
|
|
2138
|
-
if (options.collections
|
|
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
|
+
}
|
|
2139
2410
|
return collection((0, _rebasepro_utils.toSnakeCase)(prop));
|
|
2140
2411
|
}
|
|
2141
2412
|
} });
|
|
@@ -2164,20 +2435,28 @@
|
|
|
2164
2435
|
});
|
|
2165
2436
|
return res.data ?? res;
|
|
2166
2437
|
},
|
|
2167
|
-
data: dataProxy
|
|
2168
|
-
email: void 0
|
|
2438
|
+
data: dataProxy
|
|
2169
2439
|
};
|
|
2170
2440
|
}
|
|
2171
2441
|
//#endregion
|
|
2172
|
-
exports.ApiError = ApiError;
|
|
2173
|
-
exports.ClientStorageSourceRegistry = ClientStorageSourceRegistry;
|
|
2174
2442
|
Object.defineProperty(exports, "QueryBuilder", {
|
|
2175
2443
|
enumerable: true,
|
|
2176
2444
|
get: function() {
|
|
2177
2445
|
return _rebasepro_common.QueryBuilder;
|
|
2178
2446
|
}
|
|
2179
2447
|
});
|
|
2180
|
-
exports
|
|
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
|
+
});
|
|
2181
2460
|
exports.RebaseWebSocketClient = RebaseWebSocketClient;
|
|
2182
2461
|
Object.defineProperty(exports, "and", {
|
|
2183
2462
|
enumerable: true,
|
|
@@ -2185,31 +2464,21 @@
|
|
|
2185
2464
|
return _rebasepro_common.and;
|
|
2186
2465
|
}
|
|
2187
2466
|
});
|
|
2188
|
-
exports.buildQueryString = buildQueryString;
|
|
2189
2467
|
Object.defineProperty(exports, "cond", {
|
|
2190
2468
|
enumerable: true,
|
|
2191
2469
|
get: function() {
|
|
2192
2470
|
return _rebasepro_common.cond;
|
|
2193
2471
|
}
|
|
2194
2472
|
});
|
|
2195
|
-
exports.createAdmin = createAdmin;
|
|
2196
|
-
exports.createApiKeys = createApiKeys;
|
|
2197
|
-
exports.createAuth = createAuth;
|
|
2198
|
-
exports.createCollectionClient = createCollectionClient;
|
|
2199
2473
|
exports.createCookieStorage = createCookieStorage;
|
|
2200
|
-
exports.createCron = createCron;
|
|
2201
|
-
exports.createFunctionsClient = createFunctionsClient;
|
|
2202
2474
|
exports.createMemoryStorage = createMemoryStorage;
|
|
2203
2475
|
exports.createRebaseClient = createRebaseClient;
|
|
2204
|
-
exports.createStorage = createStorage;
|
|
2205
|
-
exports.createTransport = createTransport;
|
|
2206
2476
|
Object.defineProperty(exports, "or", {
|
|
2207
2477
|
enumerable: true,
|
|
2208
2478
|
get: function() {
|
|
2209
2479
|
return _rebasepro_common.or;
|
|
2210
2480
|
}
|
|
2211
2481
|
});
|
|
2212
|
-
exports.rebaseReviver = rebaseReviver;
|
|
2213
2482
|
});
|
|
2214
2483
|
|
|
2215
2484
|
//# sourceMappingURL=index.umd.js.map
|