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.
@@ -0,0 +1,319 @@
1
+ import { AuraBaseApiError } from './errors';
2
+ import { PostgrestResponse, AuraBaseError } from './types';
3
+
4
+ type HttpMethod = 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';
5
+
6
+ export class QueryBuilder<T> {
7
+ private url: string;
8
+ private anonKey: string;
9
+ private accessToken: string | null;
10
+ private tableName: string;
11
+ private queryParams: URLSearchParams;
12
+ private headers: Record<string, string>;
13
+ private isSingle: boolean = false;
14
+
15
+ constructor(
16
+ url: string,
17
+ anonKey: string,
18
+ accessToken: string | null,
19
+ tableName: string,
20
+ headers: Record<string, string> = {}
21
+ ) {
22
+ this.url = url;
23
+ this.anonKey = anonKey;
24
+ this.accessToken = accessToken;
25
+ this.tableName = tableName;
26
+ this.queryParams = new URLSearchParams();
27
+ this.headers = { ...headers };
28
+ }
29
+
30
+ /**
31
+ * Select columns
32
+ * @example
33
+ * .select('id, name, email')
34
+ * .select('*')
35
+ */
36
+ select(columns: string = '*'): this {
37
+ this.queryParams.set('select', columns);
38
+ return this;
39
+ }
40
+
41
+ /**
42
+ * Filter by column equality
43
+ * @example
44
+ * .eq('id', 1)
45
+ * .eq('status', 'active')
46
+ */
47
+ eq(column: string, value: unknown): this {
48
+ this.queryParams.append(column, `eq.${value}`);
49
+ return this;
50
+ }
51
+
52
+ /**
53
+ * Filter by column inequality
54
+ */
55
+ neq(column: string, value: unknown): this {
56
+ this.queryParams.append(column, `neq.${value}`);
57
+ return this;
58
+ }
59
+
60
+ /**
61
+ * Filter by greater than
62
+ */
63
+ gt(column: string, value: unknown): this {
64
+ this.queryParams.append(column, `gt.${value}`);
65
+ return this;
66
+ }
67
+
68
+ /**
69
+ * Filter by greater than or equal
70
+ */
71
+ gte(column: string, value: unknown): this {
72
+ this.queryParams.append(column, `gte.${value}`);
73
+ return this;
74
+ }
75
+
76
+ /**
77
+ * Filter by less than
78
+ */
79
+ lt(column: string, value: unknown): this {
80
+ this.queryParams.append(column, `lt.${value}`);
81
+ return this;
82
+ }
83
+
84
+ /**
85
+ * Filter by less than or equal
86
+ */
87
+ lte(column: string, value: unknown): this {
88
+ this.queryParams.append(column, `lte.${value}`);
89
+ return this;
90
+ }
91
+
92
+ /**
93
+ * Filter by like pattern
94
+ */
95
+ like(column: string, pattern: string): this {
96
+ this.queryParams.append(column, `like.${pattern}`);
97
+ return this;
98
+ }
99
+
100
+ /**
101
+ * Filter by case-insensitive like pattern
102
+ */
103
+ ilike(column: string, pattern: string): this {
104
+ this.queryParams.append(column, `ilike.${pattern}`);
105
+ return this;
106
+ }
107
+
108
+ /**
109
+ * Filter by array contains
110
+ */
111
+ contains(column: string, value: unknown[]): this {
112
+ this.queryParams.append(column, `cs.{${value.join(',')}}`);
113
+ return this;
114
+ }
115
+
116
+ /**
117
+ * Filter by value in array
118
+ */
119
+ in(column: string, values: unknown[]): this {
120
+ this.queryParams.append(column, `in.(${values.join(',')})`);
121
+ return this;
122
+ }
123
+
124
+ /**
125
+ * Filter for null values
126
+ */
127
+ isNull(column: string): this {
128
+ this.queryParams.append(column, 'is.null');
129
+ return this;
130
+ }
131
+
132
+ /**
133
+ * Filter for non-null values
134
+ */
135
+ isNotNull(column: string): this {
136
+ this.queryParams.append(column, 'is.not.null');
137
+ return this;
138
+ }
139
+
140
+ /**
141
+ * Order results
142
+ * @example
143
+ * .order('created_at', { ascending: false })
144
+ */
145
+ order(column: string, options: { ascending?: boolean; nullsFirst?: boolean } = {}): this {
146
+ const { ascending = true, nullsFirst } = options;
147
+ let orderStr = column;
148
+ if (!ascending) orderStr += '.desc';
149
+ if (nullsFirst !== undefined) {
150
+ orderStr += nullsFirst ? '.nullsfirst' : '.nullslast';
151
+ }
152
+ this.queryParams.append('order', orderStr);
153
+ return this;
154
+ }
155
+
156
+ /**
157
+ * Limit results
158
+ */
159
+ limit(count: number): this {
160
+ this.queryParams.set('limit', String(count));
161
+ return this;
162
+ }
163
+
164
+ /**
165
+ * Offset results
166
+ */
167
+ offset(count: number): this {
168
+ this.queryParams.set('offset', String(count));
169
+ return this;
170
+ }
171
+
172
+ /**
173
+ * Range of results (offset + limit)
174
+ */
175
+ range(from: number, to: number): this {
176
+ this.queryParams.set('offset', String(from));
177
+ this.queryParams.set('limit', String(to - from + 1));
178
+ return this;
179
+ }
180
+
181
+ /**
182
+ * Return single result
183
+ */
184
+ single(): this {
185
+ this.isSingle = true;
186
+ return this;
187
+ }
188
+
189
+ /**
190
+ * Return single result or null
191
+ */
192
+ maybeSingle(): this {
193
+ this.isSingle = true;
194
+ return this;
195
+ }
196
+
197
+ private getHeaders(): Record<string, string> {
198
+ const token = this.accessToken || this.anonKey;
199
+ return {
200
+ 'Content-Type': 'application/json',
201
+ 'apikey': this.anonKey,
202
+ 'Authorization': `Bearer ${token}`,
203
+ ...this.headers,
204
+ };
205
+ }
206
+
207
+ private async request<TResponse>(
208
+ method: HttpMethod,
209
+ body?: unknown
210
+ ): Promise<PostgrestResponse<TResponse>> {
211
+ const queryString = this.queryParams.toString();
212
+ const fullUrl = `${this.url}/rest/v1/${this.tableName}${queryString ? `?${queryString}` : ''}`;
213
+
214
+ const options: RequestInit = {
215
+ method,
216
+ headers: this.getHeaders(),
217
+ };
218
+
219
+ if (body && method !== 'GET') {
220
+ options.body = JSON.stringify(body);
221
+ (options.headers as Record<string, string>)['Prefer'] = 'return=representation';
222
+ }
223
+
224
+ try {
225
+ const response = await fetch(fullUrl, options);
226
+
227
+ let data: TResponse | null = null;
228
+ let error: AuraBaseError | null = null;
229
+
230
+ const text = await response.text();
231
+
232
+ if (text) {
233
+ try {
234
+ const parsed = JSON.parse(text);
235
+
236
+ if (!response.ok) {
237
+ error = {
238
+ message: parsed.message || parsed.error || `HTTP ${response.status}`,
239
+ code: parsed.code,
240
+ details: parsed.details,
241
+ };
242
+ } else {
243
+ data = this.isSingle && Array.isArray(parsed) ? parsed[0] ?? null : parsed;
244
+ }
245
+ } catch {
246
+ if (!response.ok) {
247
+ error = {
248
+ message: text || `HTTP ${response.status}`,
249
+ };
250
+ }
251
+ }
252
+ }
253
+
254
+ return {
255
+ data,
256
+ error,
257
+ status: response.status,
258
+ statusText: response.statusText,
259
+ };
260
+ } catch (err) {
261
+ return {
262
+ data: null,
263
+ error: {
264
+ message: err instanceof Error ? err.message : 'Network error',
265
+ },
266
+ status: 0,
267
+ statusText: 'Network Error',
268
+ };
269
+ }
270
+ }
271
+
272
+ /**
273
+ * Execute SELECT query
274
+ */
275
+ async then<TResult = T[]>(
276
+ resolve: (value: PostgrestResponse<TResult>) => void | PromiseLike<void>,
277
+ reject?: (reason: unknown) => void | PromiseLike<void>
278
+ ): Promise<void> {
279
+ try {
280
+ const result = await this.request<TResult>('GET');
281
+ await resolve(result);
282
+ } catch (err) {
283
+ if (reject) {
284
+ await reject(err);
285
+ }
286
+ }
287
+ }
288
+
289
+ /**
290
+ * Insert row(s)
291
+ */
292
+ async insert(row: Partial<T> | Partial<T>[]): Promise<PostgrestResponse<T>> {
293
+ const rows = Array.isArray(row) ? row : [row];
294
+ return this.request<T>('POST', rows.length === 1 ? rows[0] : rows);
295
+ }
296
+
297
+ /**
298
+ * Update row(s)
299
+ */
300
+ async update(data: Partial<T>): Promise<PostgrestResponse<T>> {
301
+ return this.request<T>('PATCH', data);
302
+ }
303
+
304
+ /**
305
+ * Upsert row(s)
306
+ */
307
+ async upsert(row: Partial<T> | Partial<T>[]): Promise<PostgrestResponse<T>> {
308
+ const rows = Array.isArray(row) ? row : [row];
309
+ this.headers['Prefer'] = 'resolution=merge-duplicates,return=representation';
310
+ return this.request<T>('POST', rows.length === 1 ? rows[0] : rows);
311
+ }
312
+
313
+ /**
314
+ * Delete row(s)
315
+ */
316
+ async delete(): Promise<PostgrestResponse<T>> {
317
+ return this.request<T>('DELETE');
318
+ }
319
+ }
package/src/errors.ts ADDED
@@ -0,0 +1,15 @@
1
+ import { AuraBaseError } from './types';
2
+
3
+ export class AuraBaseApiError extends Error implements AuraBaseError {
4
+ code?: string;
5
+ details?: string;
6
+ status: number;
7
+
8
+ constructor(message: string, status: number, code?: string, details?: string) {
9
+ super(message);
10
+ this.name = 'AuraBaseApiError';
11
+ this.status = status;
12
+ this.code = code;
13
+ this.details = details;
14
+ }
15
+ }
package/src/index.ts ADDED
@@ -0,0 +1,72 @@
1
+ // AuraBase JavaScript Client
2
+ // Supabase-style SDK for AuraBase
3
+
4
+ import { AuraBaseClient } from './AuraBaseClient';
5
+ import type {
6
+ AuraBaseClientOptions,
7
+ AuthSession,
8
+ User,
9
+ AuthError,
10
+ PostgrestResponse,
11
+ AuraBaseError,
12
+ SignInWithPasswordCredentials,
13
+ SignUpCredentials,
14
+ UpdateUserAttributes,
15
+ AuthChangeEvent,
16
+ Subscription,
17
+ } from './types';
18
+
19
+ export type {
20
+ AuraBaseClientOptions,
21
+ AuthSession,
22
+ User,
23
+ AuthError,
24
+ PostgrestResponse,
25
+ AuraBaseError,
26
+ SignInWithPasswordCredentials,
27
+ SignUpCredentials,
28
+ UpdateUserAttributes,
29
+ AuthChangeEvent,
30
+ Subscription,
31
+ };
32
+
33
+ export { AuraBaseClient };
34
+
35
+ /**
36
+ * Create an AuraBase client
37
+ * @param options - Client options containing url and anonKey
38
+ * @returns AuraBaseClient instance
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * import { createClient } from 'aurabase-js'
43
+ *
44
+ * const aurabase = createClient({
45
+ * url: 'http://localhost:8000',
46
+ * anonKey: 'your-anon-key'
47
+ * })
48
+ *
49
+ * // Query data
50
+ * const { data, error } = await aurabase
51
+ * .from('todos')
52
+ * .select('*')
53
+ * .order('created_at', { ascending: false })
54
+ * .limit(10)
55
+ *
56
+ * // Insert data
57
+ * const { data: newTodo, error: insertError } = await aurabase
58
+ * .from('todos')
59
+ * .insert({ title: 'My todo', completed: false })
60
+ *
61
+ * // Auth
62
+ * const { data, error } = await aurabase.auth.signInWithPassword({
63
+ * email: 'user@example.com',
64
+ * password: 'password'
65
+ * })
66
+ * ```
67
+ */
68
+ export function createClient(options: AuraBaseClientOptions): AuraBaseClient {
69
+ return new AuraBaseClient(options);
70
+ }
71
+
72
+ export default createClient;
package/src/types.ts ADDED
@@ -0,0 +1,79 @@
1
+ // Types for AuraBase SDK
2
+
3
+ export interface AuraBaseClientOptions {
4
+ url: string;
5
+ anonKey: string;
6
+ headers?: Record<string, string>;
7
+ }
8
+
9
+ export interface AuthSession {
10
+ access_token: string;
11
+ token_type: string;
12
+ user: User;
13
+ }
14
+
15
+ export interface User {
16
+ id: number;
17
+ email: string;
18
+ username?: string;
19
+ role_id?: number;
20
+ created_at?: string;
21
+ }
22
+
23
+ export interface AuthError {
24
+ error: string;
25
+ detail?: string | Array<{ msg: string }>;
26
+ }
27
+
28
+ export interface QueryOptions {
29
+ select?: string;
30
+ filter?: Record<string, unknown>;
31
+ order?: string;
32
+ limit?: number;
33
+ offset?: number;
34
+ }
35
+
36
+ export interface PostgrestResponse<T> {
37
+ data: T | T[] | null;
38
+ error: AuraBaseError | null;
39
+ status: number;
40
+ statusText: string;
41
+ }
42
+
43
+ export interface AuraBaseError {
44
+ message: string;
45
+ code?: string;
46
+ details?: string;
47
+ }
48
+
49
+ export interface AuthStateChangeEvent {
50
+ SIGNED_IN: 'SIGNED_IN';
51
+ SIGNED_OUT: 'SIGNED_OUT';
52
+ TOKEN_REFRESHED: 'TOKEN_REFRESHED';
53
+ USER_UPDATED: 'USER_UPDATED';
54
+ }
55
+
56
+ export type AuthChangeEvent = AuthStateChangeEvent[keyof AuthStateChangeEvent];
57
+
58
+ export interface Subscription {
59
+ unsubscribe: () => void;
60
+ }
61
+
62
+ export type AuthListener = (event: AuthChangeEvent, session: AuthSession | null) => void;
63
+
64
+ export interface SignInWithPasswordCredentials {
65
+ email: string;
66
+ password: string;
67
+ }
68
+
69
+ export interface SignUpCredentials {
70
+ email: string;
71
+ password: string;
72
+ username?: string;
73
+ }
74
+
75
+ export interface UpdateUserAttributes {
76
+ email?: string;
77
+ password?: string;
78
+ username?: string;
79
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "ESNext",
5
+ "lib": ["ES2020", "DOM"],
6
+ "declaration": true,
7
+ "declarationMap": true,
8
+ "sourceMap": true,
9
+ "outDir": "./dist",
10
+ "rootDir": "./src",
11
+ "strict": true,
12
+ "esModuleInterop": true,
13
+ "skipLibCheck": true,
14
+ "forceConsistentCasingInFileNames": true,
15
+ "moduleResolution": "node",
16
+ "resolveJsonModule": true
17
+ },
18
+ "include": ["src/**/*"],
19
+ "exclude": ["node_modules", "dist"]
20
+ }