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/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
+ }