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