@rebasepro/client 0.0.1-canary.4d4fb3e

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.
@@ -0,0 +1,173 @@
1
+ import { FindParams as TypesFindParams, FindResponse as TypesFindResponse } from "@rebasepro/types";
2
+
3
+ export interface RebaseClientConfig {
4
+ baseUrl: string;
5
+ token?: string;
6
+ apiPath?: string;
7
+ fetch?: typeof globalThis.fetch;
8
+ onUnauthorized?: () => Promise<boolean>;
9
+ websocketUrl?: string; // Optional real-time WebSocket connection
10
+ }
11
+
12
+ /**
13
+ * Re-export from `@rebasepro/types` for backward compatibility.
14
+ */
15
+ export type FindParams = TypesFindParams;
16
+ export type FindResponse<T> = TypesFindResponse<T extends Record<string, any> ? T : any>;
17
+
18
+ export class RebaseApiError extends Error {
19
+ status: number;
20
+ code?: string;
21
+ details?: any;
22
+
23
+ constructor(status: number, message: string, code?: string, details?: any) {
24
+ super(message);
25
+ this.name = "RebaseApiError";
26
+ this.status = status;
27
+ this.code = code;
28
+ this.details = details;
29
+ }
30
+ }
31
+
32
+ export function buildQueryString(params?: FindParams): string {
33
+ if (!params) return "";
34
+ const parts: string[] = [];
35
+
36
+ if (params.limit != null) parts.push(`limit=${params.limit}`);
37
+ if (params.offset != null) parts.push(`offset=${params.offset}`);
38
+ if (params.page != null) parts.push(`page=${params.page}`);
39
+
40
+ if (params.orderBy) {
41
+ parts.push(`orderBy=${encodeURIComponent(params.orderBy)}`);
42
+ }
43
+
44
+ if (params.include && params.include.length > 0) {
45
+ parts.push(`include=${encodeURIComponent(params.include.join(","))}`);
46
+ }
47
+
48
+ if (params.where) {
49
+ for (const [field, value] of Object.entries(params.where)) {
50
+ parts.push(`${encodeURIComponent(field)}=${encodeURIComponent(String(value))}`);
51
+ }
52
+ }
53
+
54
+ return parts.length > 0 ? "?" + parts.join("&") : "";
55
+ }
56
+
57
+ export interface Transport {
58
+ request: <T = any>(path: string, init?: RequestInit) => Promise<T>;
59
+ setToken: (newToken: string | null) => void;
60
+ setAuthTokenGetter: (getter: () => Promise<string | null>) => void;
61
+ setOnUnauthorized: (handler: () => Promise<boolean>) => void;
62
+ readonly baseUrl: string;
63
+ readonly apiPath: string;
64
+ readonly fetchFn: typeof globalThis.fetch;
65
+ getHeaders: (init?: RequestInit) => Record<string, string>;
66
+ resolveToken: () => Promise<string | null>;
67
+ }
68
+
69
+ export function createTransport(config: RebaseClientConfig): Transport {
70
+ const fetchFn = config.fetch || globalThis.fetch;
71
+ const apiPath = config.apiPath || "/api";
72
+ let token = config.token;
73
+ let tokenGetter: (() => Promise<string | null>) | undefined;
74
+ let onUnauthorizedHandler = config.onUnauthorized;
75
+
76
+ function getHeaders(activeToken: string | undefined, init?: RequestInit) {
77
+ return {
78
+ "Content-Type": "application/json",
79
+ ...(activeToken ? { Authorization: `Bearer ${activeToken}` } : {}),
80
+ ...((init?.headers as Record<string, string>) || {}),
81
+ };
82
+ }
83
+
84
+ async function request<T = any>(path: string, init?: RequestInit): Promise<T> {
85
+ const url = config.baseUrl.replace(/\/$/, "") + apiPath + path;
86
+
87
+ let activeToken = token;
88
+ if (tokenGetter) {
89
+ try {
90
+ const fetched = await tokenGetter();
91
+ if (fetched !== null && fetched !== undefined) {
92
+ activeToken = fetched;
93
+ }
94
+ } catch (e) {
95
+ // Ignore error, fallback to static token if any
96
+ }
97
+ }
98
+
99
+ const headers = getHeaders(activeToken, init);
100
+
101
+ // If passing FormData, we MUST let fetch set the boundary, so remove Content-Type
102
+ if (init?.body instanceof FormData) {
103
+ delete (headers as Record<string, string>)["Content-Type"];
104
+ }
105
+
106
+ const res = await fetchFn(url, { ...init, headers });
107
+
108
+ if (res.status === 204) return undefined as unknown as T;
109
+
110
+ const body = await res.json().catch(() => ({}));
111
+
112
+ if (res.status === 401 && onUnauthorizedHandler) {
113
+ const retried = await onUnauthorizedHandler();
114
+ if (retried) {
115
+ let retryToken = token;
116
+ if (tokenGetter) {
117
+ try {
118
+ const fetched = await tokenGetter();
119
+ if (fetched !== null && fetched !== undefined) {
120
+ retryToken = fetched;
121
+ }
122
+ } catch (e) {}
123
+ }
124
+ const retryHeaders = getHeaders(retryToken, init) as Record<string, string>;
125
+ const retryRes = await fetchFn(url, { ...init, headers: retryHeaders });
126
+ if (retryRes.status === 204) return undefined as unknown as T;
127
+ const retryBody = await retryRes.json().catch(() => ({}));
128
+ if (!retryRes.ok) {
129
+ throw new RebaseApiError(
130
+ retryRes.status,
131
+ retryBody?.error?.message || retryBody?.message || retryRes.statusText,
132
+ retryBody?.error?.code || retryBody?.code,
133
+ retryBody?.error?.details || retryBody?.details,
134
+ );
135
+ }
136
+ return retryBody as T;
137
+ }
138
+ }
139
+
140
+ if (!res.ok) {
141
+ throw new RebaseApiError(
142
+ res.status,
143
+ body?.error?.message || body?.message || res.statusText,
144
+ body?.error?.code || body?.code,
145
+ body?.error?.details || body?.details,
146
+ );
147
+ }
148
+
149
+ return body as T;
150
+ }
151
+
152
+ return {
153
+ request,
154
+ setToken(newToken: string | null) { token = newToken || undefined; },
155
+ setAuthTokenGetter(getter: () => Promise<string | null>) { tokenGetter = getter; },
156
+ setOnUnauthorized(handler: () => Promise<boolean>) { onUnauthorizedHandler = handler; },
157
+ get baseUrl() { return config.baseUrl.replace(/\/$/, ""); },
158
+ get apiPath() { return apiPath; },
159
+ get fetchFn() { return fetchFn; },
160
+ getHeaders: (init?: RequestInit) => getHeaders(token, init) as Record<string, string>,
161
+ resolveToken: async () => {
162
+ if (tokenGetter) {
163
+ try {
164
+ const fetched = await tokenGetter();
165
+ if (fetched !== null && fetched !== undefined) {
166
+ return fetched;
167
+ }
168
+ } catch (e) {}
169
+ }
170
+ return token || null;
171
+ }
172
+ };
173
+ }