@svarabase/js 1.0.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/index.d.ts +172 -0
- package/dist/auth/index.d.ts.map +1 -0
- package/dist/auth/index.js +282 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/auth/session.d.ts +42 -0
- package/dist/auth/session.d.ts.map +1 -0
- package/dist/auth/session.js +65 -0
- package/dist/auth/session.js.map +1 -0
- package/dist/client.d.ts +38 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +41 -0
- package/dist/client.js.map +1 -0
- package/dist/db/filterBuilder.d.ts +84 -0
- package/dist/db/filterBuilder.d.ts.map +1 -0
- package/dist/db/filterBuilder.js +227 -0
- package/dist/db/filterBuilder.js.map +1 -0
- package/dist/db/index.d.ts +42 -0
- package/dist/db/index.d.ts.map +1 -0
- package/dist/db/index.js +67 -0
- package/dist/db/index.js.map +1 -0
- package/dist/esm/auth/index.js +277 -0
- package/dist/esm/auth/session.js +60 -0
- package/dist/esm/client.js +36 -0
- package/dist/esm/db/filterBuilder.js +222 -0
- package/dist/esm/db/index.js +61 -0
- package/dist/esm/index.js +11 -0
- package/dist/esm/realtime/index.js +23 -0
- package/dist/esm/storage/index.js +144 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/realtime/index.d.ts +14 -0
- package/dist/realtime/index.d.ts.map +1 -0
- package/dist/realtime/index.js +29 -0
- package/dist/realtime/index.js.map +1 -0
- package/dist/storage/index.d.ts +74 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +149 -0
- package/dist/storage/index.js.map +1 -0
- package/package.json +46 -0
- package/src/auth/index.ts +325 -0
- package/src/auth/session.ts +94 -0
- package/src/client.ts +66 -0
- package/src/db/filterBuilder.ts +302 -0
- package/src/db/index.ts +86 -0
- package/src/index.ts +22 -0
- package/src/realtime/index.ts +38 -0
- package/src/storage/index.ts +174 -0
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import { SessionManager, Session, UserData, AuthListener } from './session';
|
|
2
|
+
|
|
3
|
+
type SvarabaseAuthAdminClient = {
|
|
4
|
+
createUser(attributes: {
|
|
5
|
+
email: string;
|
|
6
|
+
phone?: string;
|
|
7
|
+
password?: string;
|
|
8
|
+
email_confirm?: boolean;
|
|
9
|
+
user_metadata?: Record<string, unknown>;
|
|
10
|
+
app_metadata?: Record<string, unknown>;
|
|
11
|
+
}): Promise<{ data: { user: UserData | null }; error: Error | null }>;
|
|
12
|
+
|
|
13
|
+
updateUserById(
|
|
14
|
+
uid: string,
|
|
15
|
+
attributes: {
|
|
16
|
+
email?: string;
|
|
17
|
+
password?: string;
|
|
18
|
+
phone?: string;
|
|
19
|
+
user_metadata?: Record<string, unknown>;
|
|
20
|
+
app_metadata?: Record<string, unknown>;
|
|
21
|
+
ban_duration?: string;
|
|
22
|
+
}
|
|
23
|
+
): Promise<{ data: { user: UserData | null }; error: Error | null }>;
|
|
24
|
+
|
|
25
|
+
deleteUser(
|
|
26
|
+
id: string,
|
|
27
|
+
options?: { shouldSoftDelete?: boolean }
|
|
28
|
+
): Promise<{ data: object; error: Error | null }>;
|
|
29
|
+
|
|
30
|
+
listUsers(
|
|
31
|
+
params?: { page?: number; perPage?: number }
|
|
32
|
+
): Promise<{ data: { users: UserData[] }; error: Error | null }>;
|
|
33
|
+
|
|
34
|
+
generateLink(params: {
|
|
35
|
+
type: 'signup' | 'invite' | 'magic_link' | 'recovery';
|
|
36
|
+
email: string;
|
|
37
|
+
password?: string;
|
|
38
|
+
data?: Record<string, unknown>;
|
|
39
|
+
options?: { redirectTo?: string };
|
|
40
|
+
}): Promise<{ data: { action_link: string } | null; error: Error | null }>;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export class SvarabaseAuthClient {
|
|
44
|
+
private url: string;
|
|
45
|
+
private key: string;
|
|
46
|
+
private sessionManager: SessionManager;
|
|
47
|
+
public admin: SvarabaseAuthAdminClient;
|
|
48
|
+
|
|
49
|
+
constructor(url: string, key: string, options?: { storageKey?: string; persistSession?: boolean; autoRefreshToken?: boolean }) {
|
|
50
|
+
this.url = `${url}/auth/v1`;
|
|
51
|
+
this.key = key;
|
|
52
|
+
this.sessionManager = new SessionManager(
|
|
53
|
+
options?.storageKey ?? 'svarabase.auth.token',
|
|
54
|
+
options?.persistSession !== false
|
|
55
|
+
);
|
|
56
|
+
this.admin = this._buildAdminClient();
|
|
57
|
+
|
|
58
|
+
// Auto-refresh token if enabled
|
|
59
|
+
if (options?.autoRefreshToken !== false) {
|
|
60
|
+
this._setupAutoRefresh();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
private async _fetch(path: string, init: RequestInit = {}): Promise<Response> {
|
|
65
|
+
const session = this.sessionManager.getSession();
|
|
66
|
+
const headers: Record<string, string> = {
|
|
67
|
+
'Content-Type': 'application/json',
|
|
68
|
+
apikey: this.key,
|
|
69
|
+
...(init.headers as Record<string, string> ?? {}),
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
if (session?.access_token) {
|
|
73
|
+
headers['Authorization'] = `Bearer ${session.access_token}`;
|
|
74
|
+
} else {
|
|
75
|
+
headers['Authorization'] = `Bearer ${this.key}`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return fetch(`${this.url}${path}`, { ...init, headers });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private _setupAutoRefresh() {
|
|
82
|
+
if (typeof setInterval === 'undefined') return;
|
|
83
|
+
|
|
84
|
+
setInterval(async () => {
|
|
85
|
+
const session = this.sessionManager.getSession();
|
|
86
|
+
if (!session) return;
|
|
87
|
+
|
|
88
|
+
const now = Math.floor(Date.now() / 1000);
|
|
89
|
+
const expiresAt = session.expires_at ?? 0;
|
|
90
|
+
|
|
91
|
+
// Refresh 60 seconds before expiry
|
|
92
|
+
if (expiresAt - now < 60) {
|
|
93
|
+
await this.refreshSession({ refresh_token: session.refresh_token });
|
|
94
|
+
}
|
|
95
|
+
}, 30000);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async getSession(): Promise<{ data: { session: Session | null }; error: null }> {
|
|
99
|
+
return { data: { session: this.sessionManager.getSession() }, error: null };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async setSession(params: { access_token: string; refresh_token: string }): Promise<{ data: { session: Session | null; user: UserData | null }; error: Error | null }> {
|
|
103
|
+
try {
|
|
104
|
+
const res = await this._fetch('/setsession', {
|
|
105
|
+
method: 'POST',
|
|
106
|
+
body: JSON.stringify(params),
|
|
107
|
+
});
|
|
108
|
+
if (!res.ok) {
|
|
109
|
+
const data = await res.json();
|
|
110
|
+
return { data: { session: null, user: null }, error: new Error(data.message ?? 'setSession failed') };
|
|
111
|
+
}
|
|
112
|
+
const data = await res.json();
|
|
113
|
+
const session: Session = { ...data, expires_at: data.expires_at ?? Math.floor(Date.now() / 1000) + (data.expires_in ?? 3600) };
|
|
114
|
+
this.sessionManager.setSession(session);
|
|
115
|
+
this.sessionManager.notify('SIGNED_IN', session);
|
|
116
|
+
return { data: { session, user: data.user }, error: null };
|
|
117
|
+
} catch (e: unknown) {
|
|
118
|
+
return { data: { session: null, user: null }, error: e as Error };
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async signInWithPassword(credentials: { email: string; password: string }): Promise<{ data: { user: UserData | null; session: Session | null }; error: Error | null }> {
|
|
123
|
+
try {
|
|
124
|
+
const res = await this._fetch('/token?grant_type=password', {
|
|
125
|
+
method: 'POST',
|
|
126
|
+
body: JSON.stringify(credentials),
|
|
127
|
+
});
|
|
128
|
+
const data = await res.json();
|
|
129
|
+
if (!res.ok) return { data: { user: null, session: null }, error: new Error(data.error_description ?? data.message) };
|
|
130
|
+
|
|
131
|
+
const session: Session = { ...data, expires_at: Math.floor(Date.now() / 1000) + data.expires_in };
|
|
132
|
+
this.sessionManager.setSession(session);
|
|
133
|
+
this.sessionManager.notify('SIGNED_IN', session);
|
|
134
|
+
return { data: { user: data.user, session }, error: null };
|
|
135
|
+
} catch (e: unknown) {
|
|
136
|
+
return { data: { user: null, session: null }, error: e as Error };
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async signUp(credentials: { email: string; password: string; phone?: string; options?: { data?: Record<string, unknown>; emailRedirectTo?: string } }): Promise<{ data: { user: UserData | null; session: Session | null }; error: Error | null }> {
|
|
141
|
+
try {
|
|
142
|
+
const body: Record<string, unknown> = {
|
|
143
|
+
email: credentials.email,
|
|
144
|
+
password: credentials.password,
|
|
145
|
+
phone: credentials.phone,
|
|
146
|
+
data: credentials.options?.data,
|
|
147
|
+
};
|
|
148
|
+
const res = await this._fetch('/signup', {
|
|
149
|
+
method: 'POST',
|
|
150
|
+
body: JSON.stringify(body),
|
|
151
|
+
});
|
|
152
|
+
const data = await res.json();
|
|
153
|
+
if (!res.ok) return { data: { user: null, session: null }, error: new Error(data.error_description ?? data.message) };
|
|
154
|
+
|
|
155
|
+
const session: Session = { ...data, expires_at: Math.floor(Date.now() / 1000) + data.expires_in };
|
|
156
|
+
this.sessionManager.setSession(session);
|
|
157
|
+
this.sessionManager.notify('SIGNED_IN', session);
|
|
158
|
+
return { data: { user: data.user, session }, error: null };
|
|
159
|
+
} catch (e: unknown) {
|
|
160
|
+
return { data: { user: null, session: null }, error: e as Error };
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async signOut(options?: { scope?: 'global' | 'local' | 'others' }): Promise<{ error: Error | null }> {
|
|
165
|
+
try {
|
|
166
|
+
await this._fetch('/logout', { method: 'POST', body: JSON.stringify({ scope: options?.scope }) });
|
|
167
|
+
this.sessionManager.setSession(null);
|
|
168
|
+
this.sessionManager.notify('SIGNED_OUT', null);
|
|
169
|
+
return { error: null };
|
|
170
|
+
} catch (e: unknown) {
|
|
171
|
+
return { error: e as Error };
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
signInWithOAuth(params: { provider: string; options?: { redirectTo?: string; scopes?: string } }): { data: { provider: string; url: string }; error: null } {
|
|
176
|
+
const redirectTo = params.options?.redirectTo ?? (typeof window !== 'undefined' ? window.location.origin : '');
|
|
177
|
+
// apikey travels as a query param (not a header) because this is a top-level
|
|
178
|
+
// browser navigation - it lets the server know which project's OAuth config to use.
|
|
179
|
+
const url = `${this.url}/authorize?provider=${params.provider}&redirect_to=${encodeURIComponent(redirectTo)}&apikey=${encodeURIComponent(this.key)}`;
|
|
180
|
+
|
|
181
|
+
if (typeof window !== 'undefined') {
|
|
182
|
+
window.location.href = url;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return { data: { provider: params.provider, url }, error: null };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async getUser(jwt?: string): Promise<{ data: { user: UserData | null }; error: Error | null }> {
|
|
189
|
+
try {
|
|
190
|
+
const headers: Record<string, string> = {};
|
|
191
|
+
if (jwt) headers['Authorization'] = `Bearer ${jwt}`;
|
|
192
|
+
|
|
193
|
+
const res = await fetch(`${this.url}/user`, {
|
|
194
|
+
headers: {
|
|
195
|
+
'Content-Type': 'application/json',
|
|
196
|
+
apikey: this.key,
|
|
197
|
+
Authorization: jwt ? `Bearer ${jwt}` : `Bearer ${this.sessionManager.getSession()?.access_token ?? this.key}`,
|
|
198
|
+
},
|
|
199
|
+
});
|
|
200
|
+
if (!res.ok) {
|
|
201
|
+
const data = await res.json();
|
|
202
|
+
return { data: { user: null }, error: new Error(data.message) };
|
|
203
|
+
}
|
|
204
|
+
const user = await res.json();
|
|
205
|
+
return { data: { user }, error: null };
|
|
206
|
+
} catch (e: unknown) {
|
|
207
|
+
return { data: { user: null }, error: e as Error };
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async updateUser(attributes: { email?: string; password?: string; phone?: string; data?: Record<string, unknown> }): Promise<{ data: { user: UserData | null }; error: Error | null }> {
|
|
212
|
+
try {
|
|
213
|
+
const body = { email: attributes.email, password: attributes.password, phone: attributes.phone, data: attributes.data };
|
|
214
|
+
const res = await this._fetch('/user', { method: 'PUT', body: JSON.stringify(body) });
|
|
215
|
+
const data = await res.json();
|
|
216
|
+
if (!res.ok) return { data: { user: null }, error: new Error(data.message) };
|
|
217
|
+
return { data: { user: data }, error: null };
|
|
218
|
+
} catch (e: unknown) {
|
|
219
|
+
return { data: { user: null }, error: e as Error };
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async resetPasswordForEmail(email: string, options?: { redirectTo?: string }): Promise<{ data: object; error: Error | null }> {
|
|
224
|
+
try {
|
|
225
|
+
await this._fetch('/recover', {
|
|
226
|
+
method: 'POST',
|
|
227
|
+
body: JSON.stringify({ email, options }),
|
|
228
|
+
});
|
|
229
|
+
return { data: {}, error: null };
|
|
230
|
+
} catch (e: unknown) {
|
|
231
|
+
return { data: {}, error: e as Error };
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async refreshSession(params?: { refresh_token?: string }): Promise<{ data: { session: Session | null; user: UserData | null }; error: Error | null }> {
|
|
236
|
+
try {
|
|
237
|
+
const refreshToken = params?.refresh_token ?? this.sessionManager.getSession()?.refresh_token;
|
|
238
|
+
if (!refreshToken) return { data: { session: null, user: null }, error: new Error('No refresh token') };
|
|
239
|
+
|
|
240
|
+
const res = await this._fetch('/token?grant_type=refresh_token', {
|
|
241
|
+
method: 'POST',
|
|
242
|
+
body: JSON.stringify({ refresh_token: refreshToken }),
|
|
243
|
+
});
|
|
244
|
+
const data = await res.json();
|
|
245
|
+
if (!res.ok) {
|
|
246
|
+
this.sessionManager.setSession(null);
|
|
247
|
+
return { data: { session: null, user: null }, error: new Error(data.message) };
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const session: Session = { ...data, expires_at: Math.floor(Date.now() / 1000) + data.expires_in };
|
|
251
|
+
this.sessionManager.setSession(session);
|
|
252
|
+
this.sessionManager.notify('TOKEN_REFRESHED', session);
|
|
253
|
+
return { data: { session, user: data.user }, error: null };
|
|
254
|
+
} catch (e: unknown) {
|
|
255
|
+
return { data: { session: null, user: null }, error: e as Error };
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
onAuthStateChange(callback: AuthListener) {
|
|
260
|
+
return this.sessionManager.subscribe(callback);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
private _buildAdminClient(): SvarabaseAuthAdminClient {
|
|
264
|
+
const adminUrl = `${this.url}/admin`;
|
|
265
|
+
const key = this.key;
|
|
266
|
+
|
|
267
|
+
const adminFetch = (path: string, init: RequestInit = {}) =>
|
|
268
|
+
fetch(`${adminUrl}${path}`, {
|
|
269
|
+
...init,
|
|
270
|
+
headers: {
|
|
271
|
+
'Content-Type': 'application/json',
|
|
272
|
+
Authorization: `Bearer ${key}`,
|
|
273
|
+
apikey: key,
|
|
274
|
+
...(init.headers as Record<string, string> ?? {}),
|
|
275
|
+
},
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
return {
|
|
279
|
+
async createUser(attrs) {
|
|
280
|
+
try {
|
|
281
|
+
const res = await adminFetch('/users', { method: 'POST', body: JSON.stringify(attrs) });
|
|
282
|
+
const data = await res.json();
|
|
283
|
+
if (!res.ok) return { data: { user: null }, error: new Error(data.message) };
|
|
284
|
+
return { data: { user: data }, error: null };
|
|
285
|
+
} catch (e: unknown) { return { data: { user: null }, error: e as Error }; }
|
|
286
|
+
},
|
|
287
|
+
|
|
288
|
+
async updateUserById(uid, attrs) {
|
|
289
|
+
try {
|
|
290
|
+
const res = await adminFetch(`/users/${uid}`, { method: 'PUT', body: JSON.stringify(attrs) });
|
|
291
|
+
const data = await res.json();
|
|
292
|
+
if (!res.ok) return { data: { user: null }, error: new Error(data.message) };
|
|
293
|
+
return { data: { user: data }, error: null };
|
|
294
|
+
} catch (e: unknown) { return { data: { user: null }, error: e as Error }; }
|
|
295
|
+
},
|
|
296
|
+
|
|
297
|
+
async deleteUser(id, options) {
|
|
298
|
+
try {
|
|
299
|
+
const qs = options?.shouldSoftDelete === false ? '?is_soft_delete=false' : '';
|
|
300
|
+
await adminFetch(`/users/${id}${qs}`, { method: 'DELETE' });
|
|
301
|
+
return { data: {}, error: null };
|
|
302
|
+
} catch (e: unknown) { return { data: {}, error: e as Error }; }
|
|
303
|
+
},
|
|
304
|
+
|
|
305
|
+
async listUsers(params) {
|
|
306
|
+
try {
|
|
307
|
+
const qs = params ? `?page=${params.page ?? 1}&per_page=${params.perPage ?? 50}` : '';
|
|
308
|
+
const res = await adminFetch(`/users${qs}`);
|
|
309
|
+
const data = await res.json();
|
|
310
|
+
if (!res.ok) return { data: { users: [] }, error: new Error(data.message) };
|
|
311
|
+
return { data: { users: data.users }, error: null };
|
|
312
|
+
} catch (e: unknown) { return { data: { users: [] }, error: e as Error }; }
|
|
313
|
+
},
|
|
314
|
+
|
|
315
|
+
async generateLink(params) {
|
|
316
|
+
try {
|
|
317
|
+
const res = await adminFetch('/generate_link', { method: 'POST', body: JSON.stringify(params) });
|
|
318
|
+
const data = await res.json();
|
|
319
|
+
if (!res.ok) return { data: null, error: new Error(data.message) };
|
|
320
|
+
return { data, error: null };
|
|
321
|
+
} catch (e: unknown) { return { data: null, error: e as Error }; }
|
|
322
|
+
},
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
export interface Session {
|
|
2
|
+
access_token: string;
|
|
3
|
+
refresh_token: string;
|
|
4
|
+
token_type: string;
|
|
5
|
+
expires_in: number;
|
|
6
|
+
expires_at: number;
|
|
7
|
+
user: UserData;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface UserData {
|
|
11
|
+
id: string;
|
|
12
|
+
aud: string;
|
|
13
|
+
role: string;
|
|
14
|
+
email: string;
|
|
15
|
+
email_confirmed_at?: string;
|
|
16
|
+
phone?: string;
|
|
17
|
+
last_sign_in_at?: string;
|
|
18
|
+
app_metadata: Record<string, unknown>;
|
|
19
|
+
user_metadata: Record<string, unknown>;
|
|
20
|
+
identities?: unknown[];
|
|
21
|
+
created_at: string;
|
|
22
|
+
updated_at: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export type AuthChangeEvent =
|
|
26
|
+
| 'SIGNED_IN'
|
|
27
|
+
| 'SIGNED_OUT'
|
|
28
|
+
| 'TOKEN_REFRESHED'
|
|
29
|
+
| 'USER_UPDATED'
|
|
30
|
+
| 'PASSWORD_RECOVERY';
|
|
31
|
+
|
|
32
|
+
export type AuthListener = (event: AuthChangeEvent, session: Session | null) => void;
|
|
33
|
+
|
|
34
|
+
export class SessionManager {
|
|
35
|
+
private storageKey: string;
|
|
36
|
+
private useLocalStorage: boolean;
|
|
37
|
+
private memorySession: Session | null = null;
|
|
38
|
+
private listeners: AuthListener[] = [];
|
|
39
|
+
|
|
40
|
+
constructor(storageKey: string, persistSession = true) {
|
|
41
|
+
this.storageKey = storageKey;
|
|
42
|
+
this.useLocalStorage = persistSession && (() => {
|
|
43
|
+
try {
|
|
44
|
+
if (typeof localStorage === 'undefined') return false;
|
|
45
|
+
localStorage.getItem('__svarabase_test__');
|
|
46
|
+
return true;
|
|
47
|
+
} catch {
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
})();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
getSession(): Session | null {
|
|
54
|
+
if (this.useLocalStorage) {
|
|
55
|
+
const raw = localStorage.getItem(this.storageKey);
|
|
56
|
+
if (!raw) return null;
|
|
57
|
+
try {
|
|
58
|
+
return JSON.parse(raw);
|
|
59
|
+
} catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return this.memorySession;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
setSession(session: Session | null): void {
|
|
67
|
+
if (this.useLocalStorage) {
|
|
68
|
+
if (session) {
|
|
69
|
+
localStorage.setItem(this.storageKey, JSON.stringify(session));
|
|
70
|
+
} else {
|
|
71
|
+
localStorage.removeItem(this.storageKey);
|
|
72
|
+
}
|
|
73
|
+
} else {
|
|
74
|
+
this.memorySession = session;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
notify(event: AuthChangeEvent, session: Session | null): void {
|
|
79
|
+
this.listeners.forEach(l => l(event, session));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
subscribe(listener: AuthListener): { data: { subscription: { unsubscribe: () => void } } } {
|
|
83
|
+
this.listeners.push(listener);
|
|
84
|
+
return {
|
|
85
|
+
data: {
|
|
86
|
+
subscription: {
|
|
87
|
+
unsubscribe: () => {
|
|
88
|
+
this.listeners = this.listeners.filter(l => l !== listener);
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
}
|
package/src/client.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { SvarabaseAuthClient } from './auth';
|
|
2
|
+
import { SvarabaseQueryBuilder, TableQuery } from './db';
|
|
3
|
+
import { SvarabaseStorageClient } from './storage';
|
|
4
|
+
import { SvarabaseRealtimeClient } from './realtime';
|
|
5
|
+
|
|
6
|
+
export interface SvarabaseClientOptions {
|
|
7
|
+
auth?: {
|
|
8
|
+
autoRefreshToken?: boolean;
|
|
9
|
+
persistSession?: boolean;
|
|
10
|
+
storageKey?: string;
|
|
11
|
+
};
|
|
12
|
+
global?: {
|
|
13
|
+
headers?: Record<string, string>;
|
|
14
|
+
};
|
|
15
|
+
db?: {
|
|
16
|
+
schema?: string;
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class SvarabaseClient {
|
|
21
|
+
protected url: string;
|
|
22
|
+
protected key: string;
|
|
23
|
+
|
|
24
|
+
readonly auth: SvarabaseAuthClient;
|
|
25
|
+
readonly storage: SvarabaseStorageClient;
|
|
26
|
+
readonly realtime: SvarabaseRealtimeClient;
|
|
27
|
+
|
|
28
|
+
protected _queryBuilder: SvarabaseQueryBuilder;
|
|
29
|
+
|
|
30
|
+
constructor(url: string, key: string, options?: SvarabaseClientOptions) {
|
|
31
|
+
this.url = url.replace(/\/$/, '');
|
|
32
|
+
this.key = key;
|
|
33
|
+
|
|
34
|
+
const getAuthHeader = () => {
|
|
35
|
+
const session = this.auth['sessionManager']?.getSession?.();
|
|
36
|
+
if (session?.access_token) return `Bearer ${session.access_token}`;
|
|
37
|
+
return `Bearer ${this.key}`;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
this.auth = new SvarabaseAuthClient(this.url, this.key, {
|
|
41
|
+
autoRefreshToken: options?.auth?.autoRefreshToken,
|
|
42
|
+
persistSession: options?.auth?.persistSession,
|
|
43
|
+
storageKey: options?.auth?.storageKey,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
this._queryBuilder = new SvarabaseQueryBuilder(this.url, this.key, getAuthHeader);
|
|
47
|
+
this.storage = new SvarabaseStorageClient(this.url, this.key, getAuthHeader);
|
|
48
|
+
this.realtime = new SvarabaseRealtimeClient();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
from<T = Record<string, unknown>>(table: string): TableQuery<T> {
|
|
52
|
+
return this._queryBuilder.from<T>(table);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
rpc<T = unknown>(functionName: string, params?: Record<string, unknown>, options?: { count?: 'exact'; head?: boolean }) {
|
|
56
|
+
return this._queryBuilder.rpc<T>(functionName, params ?? {}, options);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
channel(id: string) {
|
|
60
|
+
return this.realtime.channel(id);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
removeChannel(channel: ReturnType<SvarabaseRealtimeClient['channel']>) {
|
|
64
|
+
return this.realtime.removeChannel(channel);
|
|
65
|
+
}
|
|
66
|
+
}
|