@rebasepro/client 0.4.0 → 0.6.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 +164 -0
- package/dist/admin.d.ts +2 -10
- package/dist/auth.d.ts +2 -1
- package/dist/index.d.ts +25 -17
- package/dist/index.es.js +2079 -2378
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +2160 -2426
- package/dist/index.umd.js.map +1 -1
- package/dist/websocket.d.ts +9 -6
- package/package.json +13 -13
- package/src/admin.ts +3 -10
- package/src/auth.ts +9 -4
- package/src/collection.test.ts +11 -6
- package/src/functions.ts +2 -2
- package/src/index.ts +34 -22
- package/src/websocket.ts +121 -90
package/dist/websocket.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { DeleteEntityProps, Entity, EntityCollection, FetchCollectionProps, Fetc
|
|
|
2
2
|
export interface RebaseWebSocketConfig {
|
|
3
3
|
websocketUrl: string;
|
|
4
4
|
/** Optional auth token getter for WebSocket authentication */
|
|
5
|
-
getAuthToken?: () => Promise<string>;
|
|
5
|
+
getAuthToken?: () => Promise<string | null>;
|
|
6
6
|
/** Optional WebSocket constructor to override globalThis.WebSocket (e.g. for Node environments) */
|
|
7
7
|
WebSocket?: typeof WebSocket;
|
|
8
8
|
/** Callback to handle unauthorized requests or token expiration (refreshes auth session) */
|
|
@@ -16,7 +16,7 @@ export declare class ApiError extends Error {
|
|
|
16
16
|
export declare class RebaseWebSocketClient {
|
|
17
17
|
private websocketUrl;
|
|
18
18
|
private ws;
|
|
19
|
-
getAuthToken?: () => Promise<string>;
|
|
19
|
+
getAuthToken?: () => Promise<string | null>;
|
|
20
20
|
private subscriptions;
|
|
21
21
|
private listeners;
|
|
22
22
|
on(event: "connect" | "disconnect" | "reconnect" | "error", cb: (...args: unknown[]) => void): () => boolean;
|
|
@@ -30,6 +30,7 @@ export declare class RebaseWebSocketClient {
|
|
|
30
30
|
private maxReconnectAttempts;
|
|
31
31
|
private isConnected;
|
|
32
32
|
private messageQueue;
|
|
33
|
+
private requestTimeoutMs;
|
|
33
34
|
private reconnectTimeout;
|
|
34
35
|
private isAuthenticated;
|
|
35
36
|
private authPromise;
|
|
@@ -44,18 +45,20 @@ export declare class RebaseWebSocketClient {
|
|
|
44
45
|
/**
|
|
45
46
|
* Set the auth token getter function
|
|
46
47
|
*/
|
|
47
|
-
setAuthTokenGetter(getAuthToken: () => Promise<string>): void;
|
|
48
|
+
setAuthTokenGetter(getAuthToken: () => Promise<string | null>): void;
|
|
48
49
|
disconnect(): void;
|
|
49
50
|
private initWebSocket;
|
|
50
51
|
private processMessageQueue;
|
|
51
52
|
private attemptReconnect;
|
|
52
53
|
private isAuthError;
|
|
53
54
|
private handleAuthFailure;
|
|
54
|
-
private handleWebSocketMessage;
|
|
55
|
-
private ensureAuthenticated;
|
|
56
55
|
/**
|
|
57
|
-
*
|
|
56
|
+
* Shared logic for re-subscribing a collection or entity subscription
|
|
57
|
+
* after an auth error is resolved by refreshing credentials.
|
|
58
58
|
*/
|
|
59
|
+
private resubscribeAfterAuthRefresh;
|
|
60
|
+
private handleWebSocketMessage;
|
|
61
|
+
private ensureAuthenticated;
|
|
59
62
|
reauthenticate(): Promise<void>;
|
|
60
63
|
private sendMessage;
|
|
61
64
|
private doSendMessage;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rebasepro/client",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.6.0",
|
|
5
5
|
"description": "HTTP SDK client for the Rebase custom backend",
|
|
6
6
|
"funding": {
|
|
7
7
|
"url": "https://github.com/sponsors/rebaseco"
|
|
@@ -30,20 +30,20 @@
|
|
|
30
30
|
"./package.json": "./package.json"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@rebasepro/common": "0.
|
|
34
|
-
"@rebasepro/
|
|
35
|
-
"@rebasepro/
|
|
33
|
+
"@rebasepro/common": "0.6.0",
|
|
34
|
+
"@rebasepro/utils": "0.6.0",
|
|
35
|
+
"@rebasepro/types": "0.6.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
|
-
"@jest/globals": "^
|
|
39
|
-
"@types/jest": "^
|
|
40
|
-
"@types/node": "^
|
|
41
|
-
"cross-env": "^
|
|
42
|
-
"jest": "^
|
|
43
|
-
"ts-jest": "^29.4.
|
|
44
|
-
"tsd": "^0.
|
|
45
|
-
"typescript": "^
|
|
46
|
-
"vite": "^
|
|
38
|
+
"@jest/globals": "^30.4.1",
|
|
39
|
+
"@types/jest": "^30.0.0",
|
|
40
|
+
"@types/node": "^25.9.3",
|
|
41
|
+
"cross-env": "^10.1.0",
|
|
42
|
+
"jest": "^30.4.2",
|
|
43
|
+
"ts-jest": "^29.4.11",
|
|
44
|
+
"tsd": "^0.33.0",
|
|
45
|
+
"typescript": "^6.0.3",
|
|
46
|
+
"vite": "^8.0.16"
|
|
47
47
|
},
|
|
48
48
|
"files": [
|
|
49
49
|
"dist",
|
package/src/admin.ts
CHANGED
|
@@ -1,15 +1,8 @@
|
|
|
1
1
|
import type { Transport } from "./transport";
|
|
2
|
+
import { AdminUser } from "@rebasepro/types";
|
|
3
|
+
|
|
4
|
+
export type { AdminUser };
|
|
2
5
|
|
|
3
|
-
export interface AdminUser {
|
|
4
|
-
uid: string;
|
|
5
|
-
email: string;
|
|
6
|
-
displayName: string | null;
|
|
7
|
-
photoURL: string | null;
|
|
8
|
-
provider: string;
|
|
9
|
-
roles: string[];
|
|
10
|
-
createdAt: string;
|
|
11
|
-
updatedAt: string;
|
|
12
|
-
}
|
|
13
6
|
|
|
14
7
|
export interface CreateAdminOptions {
|
|
15
8
|
adminPath?: string;
|
package/src/auth.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { RebaseApiError, Transport } from "./transport";
|
|
2
|
+
import { AuthChangeEvent } from "@rebasepro/types";
|
|
3
|
+
|
|
2
4
|
|
|
3
5
|
export interface RebaseUser {
|
|
4
6
|
uid: string;
|
|
@@ -24,7 +26,8 @@ export interface RebaseSession {
|
|
|
24
26
|
user: RebaseUser;
|
|
25
27
|
}
|
|
26
28
|
|
|
27
|
-
export type AuthChangeEvent
|
|
29
|
+
export type { AuthChangeEvent };
|
|
30
|
+
|
|
28
31
|
|
|
29
32
|
export interface AuthConfig {
|
|
30
33
|
needsSetup: boolean;
|
|
@@ -215,7 +218,9 @@ refreshToken: session.refreshToken };
|
|
|
215
218
|
const responseBody = await res.json().catch(() => ({}));
|
|
216
219
|
if (!res.ok) throwApiError(res.status, responseBody, res.statusText);
|
|
217
220
|
const session = handleAuthResponse(responseBody, "SIGNED_IN");
|
|
218
|
-
return { user: session.user,
|
|
221
|
+
return { user: session.user,
|
|
222
|
+
accessToken: session.accessToken,
|
|
223
|
+
refreshToken: session.refreshToken };
|
|
219
224
|
}
|
|
220
225
|
|
|
221
226
|
async function signInWithLinkedin(code: string, redirectUri: string) {
|
|
@@ -553,7 +558,7 @@ export function createCookieStorage(options: CookieStorageOptions = {}): AuthSto
|
|
|
553
558
|
setItem(key: string, value: string): void {
|
|
554
559
|
if (typeof document === "undefined") return;
|
|
555
560
|
let cookieStr = `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
|
|
556
|
-
|
|
561
|
+
|
|
557
562
|
if (defaultOptions.path) {
|
|
558
563
|
cookieStr += `; path=${defaultOptions.path}`;
|
|
559
564
|
}
|
|
@@ -571,7 +576,7 @@ export function createCookieStorage(options: CookieStorageOptions = {}): AuthSto
|
|
|
571
576
|
if (defaultOptions.sameSite) {
|
|
572
577
|
cookieStr += `; samesite=${defaultOptions.sameSite}`;
|
|
573
578
|
}
|
|
574
|
-
|
|
579
|
+
|
|
575
580
|
document.cookie = cookieStr;
|
|
576
581
|
},
|
|
577
582
|
removeItem(key: string): void {
|
package/src/collection.test.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jest } from
|
|
1
|
+
import { jest } from "@jest/globals";
|
|
2
2
|
import { createCollectionClient } from "./collection";
|
|
3
3
|
import type { Transport } from "./transport";
|
|
4
4
|
|
|
@@ -12,7 +12,7 @@ function createMockTransport(): Transport {
|
|
|
12
12
|
apiPath: "/api",
|
|
13
13
|
fetchFn: globalThis.fetch,
|
|
14
14
|
getHeaders: () => ({}),
|
|
15
|
-
resolveToken: jest.fn<any>().mockResolvedValue(null)
|
|
15
|
+
resolveToken: jest.fn<any>().mockResolvedValue(null)
|
|
16
16
|
};
|
|
17
17
|
}
|
|
18
18
|
|
|
@@ -74,7 +74,8 @@ describe("createCollectionClient", () => {
|
|
|
74
74
|
|
|
75
75
|
const client = createCollectionClient(transport, "users");
|
|
76
76
|
// Even if the caller passes limit/offset in params, they should be stripped
|
|
77
|
-
await client.count({ limit: 50,
|
|
77
|
+
await client.count({ limit: 50,
|
|
78
|
+
offset: 10 });
|
|
78
79
|
|
|
79
80
|
const calledUrl = (transport.request as ReturnType<typeof jest.fn>).mock.calls[0][0] as string;
|
|
80
81
|
expect(calledUrl).not.toContain("limit=");
|
|
@@ -111,7 +112,7 @@ describe("createCollectionClient", () => {
|
|
|
111
112
|
await client.count({
|
|
112
113
|
where: {
|
|
113
114
|
status: "eq.active",
|
|
114
|
-
total: [">=", 100]
|
|
115
|
+
total: [">=", 100]
|
|
115
116
|
}
|
|
116
117
|
});
|
|
117
118
|
|
|
@@ -124,8 +125,12 @@ describe("createCollectionClient", () => {
|
|
|
124
125
|
describe("find()", () => {
|
|
125
126
|
it("should call the list endpoint and return entities", async () => {
|
|
126
127
|
(transport.request as ReturnType<typeof jest.fn>).mockResolvedValue({
|
|
127
|
-
data: [{ id: "1",
|
|
128
|
-
|
|
128
|
+
data: [{ id: "1",
|
|
129
|
+
name: "Product A" }],
|
|
130
|
+
meta: { total: 1,
|
|
131
|
+
limit: 20,
|
|
132
|
+
offset: 0,
|
|
133
|
+
hasMore: false }
|
|
129
134
|
});
|
|
130
135
|
|
|
131
136
|
const client = createCollectionClient(transport, "products");
|
package/src/functions.ts
CHANGED
|
@@ -58,7 +58,7 @@ export function createFunctionsClient(transport: Transport): FunctionsClient {
|
|
|
58
58
|
async invoke<T = unknown>(
|
|
59
59
|
name: string,
|
|
60
60
|
payload?: unknown,
|
|
61
|
-
options?: FunctionInvokeOptions
|
|
61
|
+
options?: FunctionInvokeOptions
|
|
62
62
|
): Promise<T> {
|
|
63
63
|
const method = options?.method ?? "POST";
|
|
64
64
|
const subPath = options?.path ? `/${options.path.replace(/^\//, "")}` : "";
|
|
@@ -75,6 +75,6 @@ export function createFunctionsClient(transport: Transport): FunctionsClient {
|
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
return transport.request<T>(routePath, init);
|
|
78
|
-
}
|
|
78
|
+
}
|
|
79
79
|
};
|
|
80
80
|
}
|
package/src/index.ts
CHANGED
|
@@ -4,7 +4,10 @@ import { createAdmin, CreateAdminOptions } from "./admin";
|
|
|
4
4
|
import { createCron, CreateCronOptions } from "./cron";
|
|
5
5
|
import { createCollectionClient, CollectionClient } from "./collection";
|
|
6
6
|
import { createFunctionsClient } from "./functions";
|
|
7
|
-
import
|
|
7
|
+
import { createStorage } from "./storage";
|
|
8
|
+
import { RebaseWebSocketClient } from "./websocket";
|
|
9
|
+
import { RebaseClient, RebaseData, StorageSource } from "@rebasepro/types";
|
|
10
|
+
import { toSnakeCase } from "@rebasepro/utils";
|
|
8
11
|
|
|
9
12
|
export * from "./transport";
|
|
10
13
|
export * from "./auth";
|
|
@@ -16,6 +19,7 @@ export * from "./websocket";
|
|
|
16
19
|
export * from "./storage";
|
|
17
20
|
export * from "./reviver";
|
|
18
21
|
export * from "./functions";
|
|
22
|
+
export type { Entity, FindResponse } from "@rebasepro/types";
|
|
19
23
|
|
|
20
24
|
export interface CreateRebaseClientOptions extends RebaseClientConfig {
|
|
21
25
|
auth?: CreateAuthOptions;
|
|
@@ -23,17 +27,34 @@ export interface CreateRebaseClientOptions extends RebaseClientConfig {
|
|
|
23
27
|
cron?: CreateCronOptions;
|
|
24
28
|
}
|
|
25
29
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
export type { Entity, FindResponse } from "@rebasepro/types";
|
|
29
|
-
import { toSnakeCase } from "@rebasepro/utils";
|
|
30
|
+
// ─── Typed Data Proxy ────────────────────────────────────────────────────────
|
|
31
|
+
// Adds typed collection accessors when `DB` is provided via the SDK generator.
|
|
30
32
|
|
|
31
33
|
type KebabToCamelCase<S extends string> =
|
|
32
34
|
S extends `${infer T}-${infer U}`
|
|
33
35
|
? `${T}${Capitalize<KebabToCamelCase<U>>}`
|
|
34
36
|
: S;
|
|
35
37
|
|
|
36
|
-
|
|
38
|
+
type TypedDataLayer<DB> = {
|
|
39
|
+
collection<S extends string>(slug: S): CollectionClient<
|
|
40
|
+
KebabToCamelCase<S> extends keyof DB
|
|
41
|
+
? (DB[KebabToCamelCase<S>] extends { Row: infer R extends Record<string, unknown> } ? R : Record<string, unknown>)
|
|
42
|
+
: Record<string, unknown>
|
|
43
|
+
>;
|
|
44
|
+
} & {
|
|
45
|
+
[K in keyof DB]: CollectionClient<
|
|
46
|
+
DB[K] extends { Row: infer R extends Record<string, unknown> } ? R : Record<string, unknown>
|
|
47
|
+
>;
|
|
48
|
+
} & RebaseData;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The return type of `createRebaseClient<DB>()`.
|
|
52
|
+
*
|
|
53
|
+
* This is `RebaseClient` (from `@rebasepro/types`) with all optional
|
|
54
|
+
* capabilities populated and the `data` layer narrowed to provide
|
|
55
|
+
* typed collection accessors when a `DB` schema generic is supplied.
|
|
56
|
+
*/
|
|
57
|
+
export type CreateRebaseClientResult<DB = Record<string, unknown>> = Omit<RebaseClient, "data"> & {
|
|
37
58
|
setToken: (token: string | null) => void;
|
|
38
59
|
setAuthTokenGetter: (getter: () => Promise<string | null>) => void;
|
|
39
60
|
setOnUnauthorized: (handler: () => Promise<boolean>) => void;
|
|
@@ -41,24 +62,14 @@ export type RebaseClient<DB = Record<string, unknown>> = Omit<BaseRebaseClient<D
|
|
|
41
62
|
auth: ReturnType<typeof createAuth>;
|
|
42
63
|
admin: ReturnType<typeof createAdmin>;
|
|
43
64
|
cron: ReturnType<typeof createCron>;
|
|
44
|
-
functions:
|
|
65
|
+
functions: ReturnType<typeof createFunctionsClient>;
|
|
45
66
|
ws?: RebaseWebSocketClient;
|
|
46
|
-
storage
|
|
67
|
+
storage: StorageSource;
|
|
47
68
|
call: <T = unknown>(endpoint: string, payload?: unknown) => Promise<T>;
|
|
48
|
-
data:
|
|
49
|
-
collection<S extends string>(slug: S): CollectionClient<
|
|
50
|
-
KebabToCamelCase<S> extends keyof DB
|
|
51
|
-
? (DB[KebabToCamelCase<S>] extends { Row: infer R extends Record<string, unknown> } ? R : Record<string, unknown>)
|
|
52
|
-
: Record<string, unknown>
|
|
53
|
-
>;
|
|
54
|
-
} & {
|
|
55
|
-
[K in keyof DB]: CollectionClient<
|
|
56
|
-
DB[K] extends { Row: infer R extends Record<string, unknown> } ? R : Record<string, unknown>
|
|
57
|
-
>;
|
|
58
|
-
} & RebaseData;
|
|
69
|
+
data: TypedDataLayer<DB>;
|
|
59
70
|
};
|
|
60
71
|
|
|
61
|
-
|
|
72
|
+
// ─── Factory ─────────────────────────────────────────────────────────────────
|
|
62
73
|
|
|
63
74
|
/**
|
|
64
75
|
* Derive a WebSocket URL from an HTTP base URL.
|
|
@@ -94,7 +105,7 @@ function deriveWebSocketUrl(baseUrl?: string): string {
|
|
|
94
105
|
.replace(/\/$/, "");
|
|
95
106
|
}
|
|
96
107
|
|
|
97
|
-
export function createRebaseClient<DB = Record<string, unknown>>(options: CreateRebaseClientOptions):
|
|
108
|
+
export function createRebaseClient<DB = Record<string, unknown>>(options: CreateRebaseClientOptions): CreateRebaseClientResult<DB> {
|
|
98
109
|
const transport = createTransport(options);
|
|
99
110
|
const auth = createAuth(transport, options.auth);
|
|
100
111
|
const admin = createAdmin(transport, options.admin);
|
|
@@ -206,7 +217,8 @@ export function createRebaseClient<DB = Record<string, unknown>>(options: Create
|
|
|
206
217
|
},
|
|
207
218
|
data: dataProxy,
|
|
208
219
|
email: undefined
|
|
209
|
-
} as unknown as
|
|
220
|
+
} as unknown as CreateRebaseClientResult<DB>;
|
|
210
221
|
|
|
211
222
|
return target;
|
|
212
223
|
}
|
|
224
|
+
|