@rebasepro/client 0.0.1-canary.eae7889 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth.d.ts +6 -1
- package/dist/collection.d.ts +2 -1
- package/dist/collection.test.d.ts +1 -0
- package/dist/functions.d.ts +49 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.es.js +46 -23
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +46 -23
- package/dist/index.umd.js.map +1 -1
- package/package.json +3 -4
- package/src/auth.ts +20 -8
- package/src/collection.test.ts +159 -0
- package/src/collection.ts +8 -0
- package/src/functions.ts +80 -0
- package/src/index.ts +6 -0
- package/src/websocket.ts +17 -13
package/dist/auth.d.ts
CHANGED
|
@@ -50,7 +50,12 @@ export declare function createAuth(transport: Transport, options?: CreateAuthOpt
|
|
|
50
50
|
accessToken: string;
|
|
51
51
|
refreshToken: string;
|
|
52
52
|
}>;
|
|
53
|
-
signInWithGoogle: (
|
|
53
|
+
signInWithGoogle: (tokenOrPayload: string | {
|
|
54
|
+
idToken?: string;
|
|
55
|
+
accessToken?: string;
|
|
56
|
+
code?: string;
|
|
57
|
+
redirectUri?: string;
|
|
58
|
+
}) => Promise<{
|
|
54
59
|
user: RebaseUser;
|
|
55
60
|
accessToken: string;
|
|
56
61
|
refreshToken: string;
|
package/dist/collection.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Transport } from "./transport";
|
|
1
|
+
import { Transport, FindParams } from "./transport";
|
|
2
2
|
import { RebaseWebSocketClient } from "./websocket";
|
|
3
3
|
import { CollectionAccessor } from "@rebasepro/types";
|
|
4
4
|
import { FilterOperator, QueryBuilder } from "./query_builder";
|
|
@@ -15,5 +15,6 @@ export interface CollectionClient<M extends Record<string, unknown> = Record<str
|
|
|
15
15
|
offset(count: number): QueryBuilder<M>;
|
|
16
16
|
search(searchString: string): QueryBuilder<M>;
|
|
17
17
|
include(...relations: string[]): QueryBuilder<M>;
|
|
18
|
+
count(params?: FindParams): Promise<number>;
|
|
18
19
|
}
|
|
19
20
|
export declare function createCollectionClient<M extends Record<string, unknown> = Record<string, unknown>>(transport: Transport, slug: string, ws?: RebaseWebSocketClient): CollectionClient<M>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { Transport } from "./transport";
|
|
2
|
+
/**
|
|
3
|
+
* Client interface for invoking custom backend functions.
|
|
4
|
+
*
|
|
5
|
+
* Custom functions are Hono route files auto-mounted by the Rebase backend
|
|
6
|
+
* at `/api/functions/{name}`. The `FunctionsClient` wraps the shared
|
|
7
|
+
* transport so callers never need to manually construct URLs or inject
|
|
8
|
+
* auth tokens.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* const result = await client.functions.invoke<{ job: Job }>('extract-job', {
|
|
13
|
+
* url: 'https://example.com/posting',
|
|
14
|
+
* html: htmlContent,
|
|
15
|
+
* });
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export interface FunctionsClient {
|
|
19
|
+
/**
|
|
20
|
+
* Invoke a custom backend function by name.
|
|
21
|
+
*
|
|
22
|
+
* @typeParam T - Expected shape of the response payload.
|
|
23
|
+
* @param name - Function name (the filename without extension, e.g. `"extract-job"`).
|
|
24
|
+
* @param payload - Optional JSON-serialisable body sent as `POST`.
|
|
25
|
+
* @param options - Optional overrides (HTTP method, sub-path, extra headers).
|
|
26
|
+
* @returns The parsed JSON response from the function.
|
|
27
|
+
*/
|
|
28
|
+
invoke<T = unknown>(name: string, payload?: unknown, options?: FunctionInvokeOptions): Promise<T>;
|
|
29
|
+
}
|
|
30
|
+
export interface FunctionInvokeOptions {
|
|
31
|
+
/** HTTP method — defaults to `"POST"`. */
|
|
32
|
+
method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
33
|
+
/** Sub-path appended after the function name, e.g. `"status/123"`. */
|
|
34
|
+
path?: string;
|
|
35
|
+
/** Extra headers merged into the request (auth is still injected automatically). */
|
|
36
|
+
headers?: Record<string, string>;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Create a `FunctionsClient` backed by the given transport.
|
|
40
|
+
*
|
|
41
|
+
* The transport already handles:
|
|
42
|
+
* - Base URL resolution
|
|
43
|
+
* - JWT injection via `Authorization: Bearer`
|
|
44
|
+
* - 401 retry / `onUnauthorized` flow
|
|
45
|
+
* - Consistent error throwing via `RebaseApiError`
|
|
46
|
+
*
|
|
47
|
+
* @internal
|
|
48
|
+
*/
|
|
49
|
+
export declare function createFunctionsClient(transport: Transport): FunctionsClient;
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { createAuth, CreateAuthOptions } from "./auth";
|
|
|
3
3
|
import { createAdmin, CreateAdminOptions } from "./admin";
|
|
4
4
|
import { createCron, CreateCronOptions } from "./cron";
|
|
5
5
|
import { CollectionClient } from "./collection";
|
|
6
|
+
import type { FunctionsClient } from "./functions";
|
|
6
7
|
export * from "./transport";
|
|
7
8
|
export * from "./auth";
|
|
8
9
|
export * from "./admin";
|
|
@@ -11,6 +12,7 @@ export * from "./collection";
|
|
|
11
12
|
export * from "./websocket";
|
|
12
13
|
export * from "./storage";
|
|
13
14
|
export * from "./reviver";
|
|
15
|
+
export * from "./functions";
|
|
14
16
|
export interface CreateRebaseClientOptions extends RebaseClientConfig {
|
|
15
17
|
auth?: CreateAuthOptions;
|
|
16
18
|
admin?: CreateAdminOptions;
|
|
@@ -26,6 +28,7 @@ export type RebaseClient<DB = Record<string, unknown>> = BaseRebaseClient<DB> &
|
|
|
26
28
|
auth: ReturnType<typeof createAuth>;
|
|
27
29
|
admin: ReturnType<typeof createAdmin>;
|
|
28
30
|
cron: ReturnType<typeof createCron>;
|
|
31
|
+
functions: FunctionsClient;
|
|
29
32
|
ws?: RebaseWebSocketClient;
|
|
30
33
|
storage?: StorageSource;
|
|
31
34
|
call: <T = unknown>(endpoint: string, payload?: unknown) => Promise<T>;
|
package/dist/index.es.js
CHANGED
|
@@ -381,21 +381,18 @@ function createAuth(transport, options) {
|
|
|
381
381
|
refreshToken: session.refreshToken
|
|
382
382
|
};
|
|
383
383
|
}
|
|
384
|
-
async function signInWithGoogle(
|
|
384
|
+
async function signInWithGoogle(tokenOrPayload) {
|
|
385
385
|
const fetchFn = getFetch();
|
|
386
|
+
const body = typeof tokenOrPayload === "string" ? { idToken: tokenOrPayload } : tokenOrPayload;
|
|
386
387
|
const res = await fetchFn(authUrl("/google"), {
|
|
387
388
|
method: "POST",
|
|
388
389
|
headers: { "Content-Type": "application/json" },
|
|
389
|
-
body: JSON.stringify(
|
|
390
|
+
body: JSON.stringify(body)
|
|
390
391
|
});
|
|
391
|
-
const
|
|
392
|
-
if (!res.ok) throwApiError(res.status,
|
|
393
|
-
const session = handleAuthResponse(
|
|
394
|
-
return {
|
|
395
|
-
user: session.user,
|
|
396
|
-
accessToken: session.accessToken,
|
|
397
|
-
refreshToken: session.refreshToken
|
|
398
|
-
};
|
|
392
|
+
const responseBody = await res.json().catch(() => ({}));
|
|
393
|
+
if (!res.ok) throwApiError(res.status, responseBody, res.statusText);
|
|
394
|
+
const session = handleAuthResponse(responseBody, "SIGNED_IN");
|
|
395
|
+
return { user: session.user, accessToken: session.accessToken, refreshToken: session.refreshToken };
|
|
399
396
|
}
|
|
400
397
|
async function signInWithLinkedin(code, redirectUri) {
|
|
401
398
|
const fetchFn = getFetch();
|
|
@@ -1077,6 +1074,12 @@ function createCollectionClient(transport, slug, ws) {
|
|
|
1077
1074
|
method: "DELETE"
|
|
1078
1075
|
});
|
|
1079
1076
|
},
|
|
1077
|
+
async count(params) {
|
|
1078
|
+
const countParams = { ...params, limit: void 0, offset: void 0 };
|
|
1079
|
+
const qs = buildQueryString(countParams);
|
|
1080
|
+
const raw = await transport.request(basePath + "/count" + qs, { method: "GET" });
|
|
1081
|
+
return raw.count ?? 0;
|
|
1082
|
+
},
|
|
1080
1083
|
// Fluent builder instantiation
|
|
1081
1084
|
where(column, operator, value) {
|
|
1082
1085
|
return new QueryBuilder(client).where(column, operator, value);
|
|
@@ -1143,6 +1146,23 @@ function createCollectionClient(transport, slug, ws) {
|
|
|
1143
1146
|
}
|
|
1144
1147
|
return client;
|
|
1145
1148
|
}
|
|
1149
|
+
function createFunctionsClient(transport) {
|
|
1150
|
+
return {
|
|
1151
|
+
async invoke(name, payload, options) {
|
|
1152
|
+
const method = options?.method ?? "POST";
|
|
1153
|
+
const subPath = options?.path ? `/${options.path.replace(/^\//, "")}` : "";
|
|
1154
|
+
const routePath = `/functions/${encodeURIComponent(name)}${subPath}`;
|
|
1155
|
+
const init = { method };
|
|
1156
|
+
if (payload !== void 0 && method !== "GET") {
|
|
1157
|
+
init.body = JSON.stringify(payload);
|
|
1158
|
+
}
|
|
1159
|
+
if (options?.headers) {
|
|
1160
|
+
init.headers = options.headers;
|
|
1161
|
+
}
|
|
1162
|
+
return transport.request(routePath, init);
|
|
1163
|
+
}
|
|
1164
|
+
};
|
|
1165
|
+
}
|
|
1146
1166
|
function rehydrateEntity(entity) {
|
|
1147
1167
|
return entity;
|
|
1148
1168
|
}
|
|
@@ -1249,16 +1269,16 @@ class RebaseWebSocketClient {
|
|
|
1249
1269
|
setAuthTokenGetter(getAuthToken) {
|
|
1250
1270
|
this.getAuthToken = getAuthToken;
|
|
1251
1271
|
if (this.isConnected && !this.isAuthenticated && !this.authPromise) {
|
|
1252
|
-
console.
|
|
1272
|
+
console.debug("WebSocket auto-authenticating after token getter set");
|
|
1253
1273
|
this.getAuthToken().then((token) => {
|
|
1254
1274
|
if (!this.ws) return;
|
|
1255
1275
|
if (token) {
|
|
1256
1276
|
this.authenticate(token).catch((e) => {
|
|
1257
|
-
if (this.ws) console.
|
|
1277
|
+
if (this.ws) console.debug("WebSocket auto-auth skipped:", e?.message || e);
|
|
1258
1278
|
});
|
|
1259
1279
|
}
|
|
1260
1280
|
}).catch((e) => {
|
|
1261
|
-
if (this.ws) console.
|
|
1281
|
+
if (this.ws) console.debug("WebSocket auto-auth skipped:", e?.message || e);
|
|
1262
1282
|
});
|
|
1263
1283
|
}
|
|
1264
1284
|
}
|
|
@@ -1285,7 +1305,7 @@ class RebaseWebSocketClient {
|
|
|
1285
1305
|
try {
|
|
1286
1306
|
this.ws = new this.WebSocketConstructor(this.websocketUrl);
|
|
1287
1307
|
this.ws.onopen = async () => {
|
|
1288
|
-
console.
|
|
1308
|
+
console.debug("Connected to PostgreSQL backend");
|
|
1289
1309
|
const wasReconnect = this.reconnectAttempts > 0;
|
|
1290
1310
|
this.isConnected = true;
|
|
1291
1311
|
this.reconnectAttempts = 0;
|
|
@@ -1294,10 +1314,10 @@ class RebaseWebSocketClient {
|
|
|
1294
1314
|
const token = await this.getAuthToken();
|
|
1295
1315
|
if (token) {
|
|
1296
1316
|
await this.authenticate(token);
|
|
1297
|
-
console.
|
|
1317
|
+
console.debug("WebSocket auto-authenticated");
|
|
1298
1318
|
}
|
|
1299
1319
|
} catch (error) {
|
|
1300
|
-
console.
|
|
1320
|
+
console.debug("WebSocket connected without auth:", error?.message || error);
|
|
1301
1321
|
}
|
|
1302
1322
|
}
|
|
1303
1323
|
this.emit(wasReconnect ? "reconnect" : "connect");
|
|
@@ -1315,7 +1335,7 @@ class RebaseWebSocketClient {
|
|
|
1315
1335
|
}
|
|
1316
1336
|
};
|
|
1317
1337
|
this.ws.onclose = () => {
|
|
1318
|
-
console.
|
|
1338
|
+
console.debug("Disconnected from PostgreSQL backend");
|
|
1319
1339
|
this.isConnected = false;
|
|
1320
1340
|
this.isAuthenticated = false;
|
|
1321
1341
|
this.authPromise = null;
|
|
@@ -1357,7 +1377,7 @@ class RebaseWebSocketClient {
|
|
|
1357
1377
|
}
|
|
1358
1378
|
this.reconnectAttempts++;
|
|
1359
1379
|
const delay = Math.min(1e3 * Math.pow(2, this.reconnectAttempts), 3e4);
|
|
1360
|
-
console.
|
|
1380
|
+
console.debug(`Attempting to reconnect in ${delay}ms (attempt ${this.reconnectAttempts})`);
|
|
1361
1381
|
if (this.reconnectTimeout) {
|
|
1362
1382
|
clearTimeout(this.reconnectTimeout);
|
|
1363
1383
|
}
|
|
@@ -1527,7 +1547,7 @@ class RebaseWebSocketClient {
|
|
|
1527
1547
|
this.authPromise = this.authenticate(token);
|
|
1528
1548
|
await this.authPromise;
|
|
1529
1549
|
this.authPromise = null;
|
|
1530
|
-
console.
|
|
1550
|
+
console.debug("WebSocket authenticated on demand");
|
|
1531
1551
|
return;
|
|
1532
1552
|
} catch (error) {
|
|
1533
1553
|
this.authPromise = null;
|
|
@@ -1546,7 +1566,7 @@ class RebaseWebSocketClient {
|
|
|
1546
1566
|
}
|
|
1547
1567
|
if (attempt < retryCount - 1) {
|
|
1548
1568
|
const delay = Math.min(1e3 * (attempt + 1), 3e3);
|
|
1549
|
-
console.
|
|
1569
|
+
console.debug(`WebSocket auth attempt ${attempt + 1} failed, retrying in ${delay}ms...`);
|
|
1550
1570
|
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
1551
1571
|
}
|
|
1552
1572
|
}
|
|
@@ -1563,7 +1583,7 @@ class RebaseWebSocketClient {
|
|
|
1563
1583
|
try {
|
|
1564
1584
|
const token = await this.getAuthToken();
|
|
1565
1585
|
await this.authenticate(token);
|
|
1566
|
-
console.
|
|
1586
|
+
console.debug("WebSocket reauthenticated successfully");
|
|
1567
1587
|
} catch (error) {
|
|
1568
1588
|
console.error("WebSocket reauthentication failed:", error);
|
|
1569
1589
|
throw error;
|
|
@@ -1821,7 +1841,7 @@ class RebaseWebSocketClient {
|
|
|
1821
1841
|
};
|
|
1822
1842
|
}
|
|
1823
1843
|
}
|
|
1824
|
-
console.
|
|
1844
|
+
console.debug(`[RebaseWS] Row ${incomingEntity.id} refetch mismatch:
|
|
1825
1845
|
`, JSON.stringify(mismatches, null, 2));
|
|
1826
1846
|
}
|
|
1827
1847
|
}
|
|
@@ -1981,7 +2001,7 @@ class RebaseWebSocketClient {
|
|
|
1981
2001
|
* we need to re-register everything to resume receiving updates.
|
|
1982
2002
|
*/
|
|
1983
2003
|
resubscribeAll() {
|
|
1984
|
-
console.
|
|
2004
|
+
console.debug(`[WS] Re-subscribing: ${this.collectionSubscriptions.size} collection(s), ${this.entitySubscriptions.size} entity(ies)`);
|
|
1985
2005
|
for (const [key, sub] of this.collectionSubscriptions.entries()) {
|
|
1986
2006
|
const oldBackendId = sub.backendSubscriptionId;
|
|
1987
2007
|
const newBackendId = `collection_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
@@ -2182,6 +2202,7 @@ function createRebaseClient(options) {
|
|
|
2182
2202
|
const admin = createAdmin(transport, options.admin);
|
|
2183
2203
|
const cron = createCron(transport, options.cron);
|
|
2184
2204
|
const storage = createStorage(transport);
|
|
2205
|
+
const functions = createFunctionsClient(transport);
|
|
2185
2206
|
const resolvedWsUrl = options.websocketUrl ?? deriveWebSocketUrl(options.baseUrl);
|
|
2186
2207
|
let ws;
|
|
2187
2208
|
if (resolvedWsUrl) {
|
|
@@ -2238,6 +2259,7 @@ function createRebaseClient(options) {
|
|
|
2238
2259
|
auth,
|
|
2239
2260
|
admin,
|
|
2240
2261
|
cron,
|
|
2262
|
+
functions,
|
|
2241
2263
|
storage,
|
|
2242
2264
|
ws,
|
|
2243
2265
|
setToken: transport.setToken,
|
|
@@ -2268,6 +2290,7 @@ export {
|
|
|
2268
2290
|
createAuth,
|
|
2269
2291
|
createCollectionClient,
|
|
2270
2292
|
createCron,
|
|
2293
|
+
createFunctionsClient,
|
|
2271
2294
|
createMemoryStorage,
|
|
2272
2295
|
createRebaseClient,
|
|
2273
2296
|
createStorage,
|