aurabase-js 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/.omc/state/hud-state.json +6 -0
- package/.omc/state/hud-stdin-cache.json +1 -0
- package/README.md +204 -0
- package/dist/index.d.mts +392 -0
- package/dist/index.d.ts +392 -0
- package/dist/index.js +769 -0
- package/dist/index.mjs +741 -0
- package/package.json +34 -0
- package/src/AuraBaseClient.ts +271 -0
- package/src/Auth.ts +329 -0
- package/src/QueryBuilder.ts +319 -0
- package/src/errors.ts +15 -0
- package/src/index.ts +72 -0
- package/src/types.ts +79 -0
- package/tsconfig.json +20 -0
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "aurabase-js",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "AuraBase client library - Supabase-style SDK for AuraBase",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsup src/index.ts --format cjs,esm --dts",
|
|
17
|
+
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
18
|
+
"prepublishOnly": "npm run build"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"aurabase",
|
|
22
|
+
"database",
|
|
23
|
+
"rest",
|
|
24
|
+
"client",
|
|
25
|
+
"supabase-like"
|
|
26
|
+
],
|
|
27
|
+
"author": "",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/node": "^20.10.0",
|
|
31
|
+
"tsup": "^8.0.0",
|
|
32
|
+
"typescript": "^5.3.0"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { AuraBaseClientOptions } from './types';
|
|
2
|
+
import { QueryBuilder } from './QueryBuilder';
|
|
3
|
+
import { AuthClient } from './Auth';
|
|
4
|
+
|
|
5
|
+
export class AuraBaseClient {
|
|
6
|
+
private url: string;
|
|
7
|
+
private anonKey: string;
|
|
8
|
+
private accessToken: string | null = null;
|
|
9
|
+
private customHeaders: Record<string, string>;
|
|
10
|
+
public auth: AuthClient;
|
|
11
|
+
|
|
12
|
+
constructor(options: AuraBaseClientOptions) {
|
|
13
|
+
this.url = options.url;
|
|
14
|
+
this.anonKey = options.anonKey;
|
|
15
|
+
this.customHeaders = options.headers || {};
|
|
16
|
+
this.auth = new AuthClient(this.url, this.anonKey);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Set access token for authenticated requests
|
|
21
|
+
*/
|
|
22
|
+
setAccessToken(token: string | null): void {
|
|
23
|
+
this.accessToken = token;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Get the current access token
|
|
28
|
+
*/
|
|
29
|
+
getAccessToken(): string | null {
|
|
30
|
+
return this.accessToken;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Query a table
|
|
35
|
+
* @example
|
|
36
|
+
* const { data, error } = await client.from('todos').select('*')
|
|
37
|
+
*/
|
|
38
|
+
from<T = unknown>(tableName: string): QueryBuilder<T> {
|
|
39
|
+
return new QueryBuilder<T>(
|
|
40
|
+
this.url,
|
|
41
|
+
this.anonKey,
|
|
42
|
+
this.accessToken,
|
|
43
|
+
tableName,
|
|
44
|
+
this.customHeaders
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Execute raw SQL (RPC function call)
|
|
50
|
+
* @example
|
|
51
|
+
* const { data, error } = await client.rpc('my_function', { arg1: 'value' })
|
|
52
|
+
*/
|
|
53
|
+
async rpc<T = unknown>(
|
|
54
|
+
functionName: string,
|
|
55
|
+
params?: Record<string, unknown>
|
|
56
|
+
): Promise<{ data: T | null; error: { message: string } | null }> {
|
|
57
|
+
const token = this.accessToken || this.anonKey;
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
const response = await fetch(`${this.url}/rpc/v1/${functionName}`, {
|
|
61
|
+
method: 'POST',
|
|
62
|
+
headers: {
|
|
63
|
+
'Content-Type': 'application/json',
|
|
64
|
+
'apikey': this.anonKey,
|
|
65
|
+
'Authorization': `Bearer ${token}`,
|
|
66
|
+
...this.customHeaders,
|
|
67
|
+
},
|
|
68
|
+
body: JSON.stringify(params || {}),
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const text = await response.text();
|
|
72
|
+
let data: T | null = null;
|
|
73
|
+
let error: { message: string } | null = null;
|
|
74
|
+
|
|
75
|
+
if (text) {
|
|
76
|
+
try {
|
|
77
|
+
const parsed = JSON.parse(text);
|
|
78
|
+
if (!response.ok) {
|
|
79
|
+
error = { message: parsed.message || parsed.error || `HTTP ${response.status}` };
|
|
80
|
+
} else {
|
|
81
|
+
data = parsed;
|
|
82
|
+
}
|
|
83
|
+
} catch {
|
|
84
|
+
if (!response.ok) {
|
|
85
|
+
error = { message: text || `HTTP ${response.status}` };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return { data, error };
|
|
91
|
+
} catch (err) {
|
|
92
|
+
return {
|
|
93
|
+
data: null,
|
|
94
|
+
error: { message: err instanceof Error ? err.message : 'Network error' },
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Get storage client (if available)
|
|
101
|
+
*/
|
|
102
|
+
get storage() {
|
|
103
|
+
return {
|
|
104
|
+
from: (bucket: string) => new StorageBucket(this.url, this.anonKey, bucket, this.accessToken),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Storage bucket operations
|
|
111
|
+
*/
|
|
112
|
+
class StorageBucket {
|
|
113
|
+
private url: string;
|
|
114
|
+
private anonKey: string;
|
|
115
|
+
private bucket: string;
|
|
116
|
+
private accessToken: string | null;
|
|
117
|
+
|
|
118
|
+
constructor(url: string, anonKey: string, bucket: string, accessToken: string | null) {
|
|
119
|
+
this.url = url;
|
|
120
|
+
this.anonKey = anonKey;
|
|
121
|
+
this.bucket = bucket;
|
|
122
|
+
this.accessToken = accessToken;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private getHeaders(): Record<string, string> {
|
|
126
|
+
const token = this.accessToken || this.anonKey;
|
|
127
|
+
return {
|
|
128
|
+
'apikey': this.anonKey,
|
|
129
|
+
'Authorization': `Bearer ${token}`,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Upload a file
|
|
135
|
+
*/
|
|
136
|
+
async upload(
|
|
137
|
+
path: string,
|
|
138
|
+
file: File | Blob,
|
|
139
|
+
options?: { contentType?: string; upsert?: boolean }
|
|
140
|
+
): Promise<{ data: { path: string } | null; error: { message: string } | null }> {
|
|
141
|
+
const formData = new FormData();
|
|
142
|
+
formData.append('file', file);
|
|
143
|
+
|
|
144
|
+
const headers = this.getHeaders();
|
|
145
|
+
if (options?.contentType) {
|
|
146
|
+
headers['Content-Type'] = options.contentType;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
const response = await fetch(
|
|
151
|
+
`${this.url}/storage/v1/object/${this.bucket}/${path}`,
|
|
152
|
+
{
|
|
153
|
+
method: 'POST',
|
|
154
|
+
headers,
|
|
155
|
+
body: formData,
|
|
156
|
+
}
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
const data = await response.json();
|
|
160
|
+
|
|
161
|
+
if (!response.ok) {
|
|
162
|
+
return {
|
|
163
|
+
data: null,
|
|
164
|
+
error: { message: data.message || data.error || 'Upload failed' },
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return { data: { path: data.path || path }, error: null };
|
|
169
|
+
} catch (err) {
|
|
170
|
+
return {
|
|
171
|
+
data: null,
|
|
172
|
+
error: { message: err instanceof Error ? err.message : 'Network error' },
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Download a file
|
|
179
|
+
*/
|
|
180
|
+
async download(path: string): Promise<{ data: Blob | null; error: { message: string } | null }> {
|
|
181
|
+
try {
|
|
182
|
+
const response = await fetch(
|
|
183
|
+
`${this.url}/storage/v1/object/${this.bucket}/${path}`,
|
|
184
|
+
{
|
|
185
|
+
headers: this.getHeaders(),
|
|
186
|
+
}
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
if (!response.ok) {
|
|
190
|
+
const data = await response.json();
|
|
191
|
+
return {
|
|
192
|
+
data: null,
|
|
193
|
+
error: { message: data.message || data.error || 'Download failed' },
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return { data: await response.blob(), error: null };
|
|
198
|
+
} catch (err) {
|
|
199
|
+
return {
|
|
200
|
+
data: null,
|
|
201
|
+
error: { message: err instanceof Error ? err.message : 'Network error' },
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Get public URL for a file
|
|
208
|
+
*/
|
|
209
|
+
getPublicUrl(path: string): string {
|
|
210
|
+
return `${this.url}/storage/v1/object/public/${this.bucket}/${path}`;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Delete a file
|
|
215
|
+
*/
|
|
216
|
+
async remove(paths: string[]): Promise<{ error: { message: string } | null }> {
|
|
217
|
+
try {
|
|
218
|
+
const response = await fetch(`${this.url}/storage/v1/object/${this.bucket}`, {
|
|
219
|
+
method: 'DELETE',
|
|
220
|
+
headers: {
|
|
221
|
+
...this.getHeaders(),
|
|
222
|
+
'Content-Type': 'application/json',
|
|
223
|
+
},
|
|
224
|
+
body: JSON.stringify({ prefixes: paths }),
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
if (!response.ok) {
|
|
228
|
+
const data = await response.json();
|
|
229
|
+
return { error: { message: data.message || data.error || 'Delete failed' } };
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return { error: null };
|
|
233
|
+
} catch (err) {
|
|
234
|
+
return { error: { message: err instanceof Error ? err.message : 'Network error' } };
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* List files in a bucket
|
|
240
|
+
*/
|
|
241
|
+
async list(prefix?: string): Promise<{
|
|
242
|
+
data: Array<{ name: string; id: string; created_at: string }> | null;
|
|
243
|
+
error: { message: string } | null;
|
|
244
|
+
}> {
|
|
245
|
+
try {
|
|
246
|
+
const url = prefix
|
|
247
|
+
? `${this.url}/storage/v1/object/list/${this.bucket}?prefix=${prefix}`
|
|
248
|
+
: `${this.url}/storage/v1/object/list/${this.bucket}`;
|
|
249
|
+
|
|
250
|
+
const response = await fetch(url, {
|
|
251
|
+
headers: this.getHeaders(),
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
const data = await response.json();
|
|
255
|
+
|
|
256
|
+
if (!response.ok) {
|
|
257
|
+
return {
|
|
258
|
+
data: null,
|
|
259
|
+
error: { message: data.message || data.error || 'List failed' },
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
return { data, error: null };
|
|
264
|
+
} catch (err) {
|
|
265
|
+
return {
|
|
266
|
+
data: null,
|
|
267
|
+
error: { message: err instanceof Error ? err.message : 'Network error' },
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
package/src/Auth.ts
ADDED
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AuthSession,
|
|
3
|
+
User,
|
|
4
|
+
AuthError,
|
|
5
|
+
SignInWithPasswordCredentials,
|
|
6
|
+
SignUpCredentials,
|
|
7
|
+
UpdateUserAttributes,
|
|
8
|
+
AuthListener,
|
|
9
|
+
AuthChangeEvent,
|
|
10
|
+
Subscription,
|
|
11
|
+
} from './types';
|
|
12
|
+
import { AuraBaseApiError } from './errors';
|
|
13
|
+
|
|
14
|
+
export class AuthClient {
|
|
15
|
+
private url: string;
|
|
16
|
+
private anonKey: string;
|
|
17
|
+
private listeners: Map<string, AuthListener[]> = new Map();
|
|
18
|
+
private currentSession: AuthSession | null = null;
|
|
19
|
+
private storageKey: string = 'aurabase_session';
|
|
20
|
+
|
|
21
|
+
constructor(url: string, anonKey: string) {
|
|
22
|
+
this.url = url;
|
|
23
|
+
this.anonKey = anonKey;
|
|
24
|
+
this.loadSession();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private loadSession(): void {
|
|
28
|
+
if (typeof window === 'undefined') return;
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
const stored = localStorage.getItem(this.storageKey);
|
|
32
|
+
if (stored) {
|
|
33
|
+
this.currentSession = JSON.parse(stored);
|
|
34
|
+
}
|
|
35
|
+
} catch {
|
|
36
|
+
// Ignore parse errors
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
private saveSession(session: AuthSession | null): void {
|
|
41
|
+
this.currentSession = session;
|
|
42
|
+
|
|
43
|
+
if (typeof window === 'undefined') return;
|
|
44
|
+
|
|
45
|
+
if (session) {
|
|
46
|
+
localStorage.setItem(this.storageKey, JSON.stringify(session));
|
|
47
|
+
} else {
|
|
48
|
+
localStorage.removeItem(this.storageKey);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
private getHeaders(): Record<string, string> {
|
|
53
|
+
return {
|
|
54
|
+
'Content-Type': 'application/json',
|
|
55
|
+
'apikey': this.anonKey,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
private emitEvent(event: AuthChangeEvent, session: AuthSession | null): void {
|
|
60
|
+
const listeners = this.listeners.get(event) || [];
|
|
61
|
+
listeners.forEach((listener) => listener(event, session));
|
|
62
|
+
|
|
63
|
+
// Also emit to '*' listeners
|
|
64
|
+
const allListeners = this.listeners.get('*') || [];
|
|
65
|
+
allListeners.forEach((listener) => listener(event, session));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Sign in with email and password
|
|
70
|
+
*/
|
|
71
|
+
async signInWithPassword(credentials: SignInWithPasswordCredentials): Promise<{
|
|
72
|
+
data: { user: User; session: AuthSession } | null;
|
|
73
|
+
error: AuthError | null;
|
|
74
|
+
}> {
|
|
75
|
+
try {
|
|
76
|
+
const response = await fetch(`${this.url}/auth/login`, {
|
|
77
|
+
method: 'POST',
|
|
78
|
+
headers: this.getHeaders(),
|
|
79
|
+
body: JSON.stringify(credentials),
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
const data = await response.json();
|
|
83
|
+
|
|
84
|
+
if (!response.ok) {
|
|
85
|
+
const errorMsg = typeof data.detail === 'string'
|
|
86
|
+
? data.detail
|
|
87
|
+
: Array.isArray(data.detail)
|
|
88
|
+
? data.detail.map((e: { msg: string }) => e.msg).join(', ')
|
|
89
|
+
: data.error || 'Login failed';
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
data: null,
|
|
93
|
+
error: { error: errorMsg },
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const session: AuthSession = {
|
|
98
|
+
access_token: data.access_token,
|
|
99
|
+
token_type: 'bearer',
|
|
100
|
+
user: data.user,
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
this.saveSession(session);
|
|
104
|
+
this.emitEvent('SIGNED_IN', session);
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
data: { user: data.user, session },
|
|
108
|
+
error: null,
|
|
109
|
+
};
|
|
110
|
+
} catch (err) {
|
|
111
|
+
return {
|
|
112
|
+
data: null,
|
|
113
|
+
error: { error: err instanceof Error ? err.message : 'Network error' },
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Sign up with email and password
|
|
120
|
+
*/
|
|
121
|
+
async signUp(credentials: SignUpCredentials): Promise<{
|
|
122
|
+
data: { user: User } | null;
|
|
123
|
+
error: AuthError | null;
|
|
124
|
+
}> {
|
|
125
|
+
try {
|
|
126
|
+
const response = await fetch(`${this.url}/auth/register`, {
|
|
127
|
+
method: 'POST',
|
|
128
|
+
headers: this.getHeaders(),
|
|
129
|
+
body: JSON.stringify(credentials),
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const data = await response.json();
|
|
133
|
+
|
|
134
|
+
if (!response.ok) {
|
|
135
|
+
return {
|
|
136
|
+
data: null,
|
|
137
|
+
error: { error: data.detail || data.error || 'Registration failed' },
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return {
|
|
142
|
+
data: { user: data.user || data },
|
|
143
|
+
error: null,
|
|
144
|
+
};
|
|
145
|
+
} catch (err) {
|
|
146
|
+
return {
|
|
147
|
+
data: null,
|
|
148
|
+
error: { error: err instanceof Error ? err.message : 'Network error' },
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Sign out
|
|
155
|
+
*/
|
|
156
|
+
async signOut(): Promise<{ error: AuthError | null }> {
|
|
157
|
+
try {
|
|
158
|
+
const token = this.currentSession?.access_token;
|
|
159
|
+
|
|
160
|
+
if (token) {
|
|
161
|
+
await fetch(`${this.url}/auth/logout`, {
|
|
162
|
+
method: 'POST',
|
|
163
|
+
headers: {
|
|
164
|
+
...this.getHeaders(),
|
|
165
|
+
'Authorization': `Bearer ${token}`,
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
this.saveSession(null);
|
|
171
|
+
this.emitEvent('SIGNED_OUT', null);
|
|
172
|
+
|
|
173
|
+
return { error: null };
|
|
174
|
+
} catch (err) {
|
|
175
|
+
return { error: { error: err instanceof Error ? err.message : 'Network error' } };
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Get current session
|
|
181
|
+
*/
|
|
182
|
+
getSession(): { data: { session: AuthSession | null } } {
|
|
183
|
+
return { data: { session: this.currentSession } };
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Get current user
|
|
188
|
+
*/
|
|
189
|
+
async getUser(): Promise<{ data: { user: User | null }; error: AuthError | null }> {
|
|
190
|
+
const token = this.currentSession?.access_token;
|
|
191
|
+
|
|
192
|
+
if (!token) {
|
|
193
|
+
return { data: { user: null }, error: null };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
try {
|
|
197
|
+
const response = await fetch(`${this.url}/auth/me`, {
|
|
198
|
+
headers: {
|
|
199
|
+
...this.getHeaders(),
|
|
200
|
+
'Authorization': `Bearer ${token}`,
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
if (!response.ok) {
|
|
205
|
+
return {
|
|
206
|
+
data: { user: null },
|
|
207
|
+
error: { error: 'Failed to get user' },
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const user = await response.json();
|
|
212
|
+
return { data: { user }, error: null };
|
|
213
|
+
} catch (err) {
|
|
214
|
+
return {
|
|
215
|
+
data: { user: null },
|
|
216
|
+
error: { error: err instanceof Error ? err.message : 'Network error' },
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Update user
|
|
223
|
+
*/
|
|
224
|
+
async updateUser(attributes: UpdateUserAttributes): Promise<{
|
|
225
|
+
data: { user: User } | null;
|
|
226
|
+
error: AuthError | null;
|
|
227
|
+
}> {
|
|
228
|
+
const token = this.currentSession?.access_token;
|
|
229
|
+
|
|
230
|
+
if (!token) {
|
|
231
|
+
return {
|
|
232
|
+
data: null,
|
|
233
|
+
error: { error: 'Not authenticated' },
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
const response = await fetch(`${this.url}/auth/me`, {
|
|
239
|
+
method: 'PATCH',
|
|
240
|
+
headers: {
|
|
241
|
+
...this.getHeaders(),
|
|
242
|
+
'Authorization': `Bearer ${token}`,
|
|
243
|
+
},
|
|
244
|
+
body: JSON.stringify(attributes),
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
const data = await response.json();
|
|
248
|
+
|
|
249
|
+
if (!response.ok) {
|
|
250
|
+
return {
|
|
251
|
+
data: null,
|
|
252
|
+
error: { error: data.detail || data.error || 'Update failed' },
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (this.currentSession) {
|
|
257
|
+
this.currentSession.user = data;
|
|
258
|
+
this.saveSession(this.currentSession);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
this.emitEvent('USER_UPDATED', this.currentSession);
|
|
262
|
+
|
|
263
|
+
return { data: { user: data }, error: null };
|
|
264
|
+
} catch (err) {
|
|
265
|
+
return {
|
|
266
|
+
data: null,
|
|
267
|
+
error: { error: err instanceof Error ? err.message : 'Network error' },
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Request password reset
|
|
274
|
+
*/
|
|
275
|
+
async resetPasswordForEmail(email: string): Promise<{ error: AuthError | null }> {
|
|
276
|
+
try {
|
|
277
|
+
const response = await fetch(`${this.url}/auth/find-password`, {
|
|
278
|
+
method: 'POST',
|
|
279
|
+
headers: this.getHeaders(),
|
|
280
|
+
body: JSON.stringify({ email }),
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
const data = await response.json();
|
|
284
|
+
|
|
285
|
+
if (!response.ok) {
|
|
286
|
+
return { error: { error: data.detail || data.error || 'Request failed' } };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
return { error: null };
|
|
290
|
+
} catch (err) {
|
|
291
|
+
return { error: { error: err instanceof Error ? err.message : 'Network error' } };
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Subscribe to auth state changes
|
|
297
|
+
*/
|
|
298
|
+
onAuthStateChange(
|
|
299
|
+
callback: (event: AuthChangeEvent, session: AuthSession | null) => void
|
|
300
|
+
): { data: { subscription: Subscription } } {
|
|
301
|
+
const addListener = (event: AuthChangeEvent) => {
|
|
302
|
+
if (!this.listeners.has(event)) {
|
|
303
|
+
this.listeners.set(event, []);
|
|
304
|
+
}
|
|
305
|
+
this.listeners.get(event)!.push(callback);
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
// Subscribe to all events
|
|
309
|
+
addListener('SIGNED_IN');
|
|
310
|
+
addListener('SIGNED_OUT');
|
|
311
|
+
addListener('TOKEN_REFRESHED');
|
|
312
|
+
addListener('USER_UPDATED');
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
data: {
|
|
316
|
+
subscription: {
|
|
317
|
+
unsubscribe: () => {
|
|
318
|
+
this.listeners.forEach((listeners) => {
|
|
319
|
+
const index = listeners.indexOf(callback);
|
|
320
|
+
if (index > -1) {
|
|
321
|
+
listeners.splice(index, 1);
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
}
|