aurabase-js 0.2.1 → 0.4.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 +3 -3
- package/.omc/state/hud-stdin-cache.json +1 -1
- package/README.md +2 -70
- package/dist/cli.d.mts +1 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +215 -120
- package/dist/cli.mjs +216 -0
- package/dist/index.js +75 -116
- package/dist/index.mjs +81 -111
- package/lib/aurabase.ts +9 -0
- package/package.json +6 -6
- package/src/AuraBaseClient.ts +16 -6
- package/src/QueryBuilder.ts +161 -122
- package/src/cli.ts +203 -115
package/src/QueryBuilder.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { AuraBaseApiError } from './errors';
|
|
1
2
|
import { PostgrestResponse, AuraBaseError } from './types';
|
|
2
3
|
|
|
3
|
-
type HttpMethod = 'GET' | 'POST' | 'PATCH' | 'DELETE';
|
|
4
|
+
type HttpMethod = 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE';
|
|
4
5
|
|
|
5
6
|
export class QueryBuilder<T> {
|
|
6
7
|
private url: string;
|
|
@@ -11,13 +12,6 @@ export class QueryBuilder<T> {
|
|
|
11
12
|
private headers: Record<string, string>;
|
|
12
13
|
private isSingle: boolean = false;
|
|
13
14
|
|
|
14
|
-
// CUD state — stored so filters can be chained AFTER insert/update/delete
|
|
15
|
-
private _method: HttpMethod = 'GET';
|
|
16
|
-
private _body: unknown = undefined;
|
|
17
|
-
private _isUpsert: boolean = false;
|
|
18
|
-
private _onConflict: string | undefined = undefined;
|
|
19
|
-
private _idValue: unknown = undefined; // .eq('id', x) → URL path for PATCH/DELETE
|
|
20
|
-
|
|
21
15
|
constructor(
|
|
22
16
|
url: string,
|
|
23
17
|
anonKey: string,
|
|
@@ -33,167 +27,173 @@ export class QueryBuilder<T> {
|
|
|
33
27
|
this.headers = { ...headers };
|
|
34
28
|
}
|
|
35
29
|
|
|
36
|
-
|
|
37
|
-
|
|
30
|
+
/**
|
|
31
|
+
* Select columns
|
|
32
|
+
* @example
|
|
33
|
+
* .select('id, name, email')
|
|
34
|
+
* .select('*')
|
|
35
|
+
*/
|
|
38
36
|
select(columns: string = '*'): this {
|
|
39
|
-
|
|
37
|
+
this.queryParams.set('select', columns);
|
|
40
38
|
return this;
|
|
41
39
|
}
|
|
42
40
|
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Filter by column equality
|
|
43
|
+
* @example
|
|
44
|
+
* .eq('id', 1)
|
|
45
|
+
* .eq('status', 'active')
|
|
46
|
+
*/
|
|
45
47
|
eq(column: string, value: unknown): this {
|
|
46
|
-
|
|
47
|
-
this.queryParams.append('eq', `${column}:${value}`);
|
|
48
|
+
this.queryParams.append(column, `eq.${value}`);
|
|
48
49
|
return this;
|
|
49
50
|
}
|
|
50
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Filter by column inequality
|
|
54
|
+
*/
|
|
51
55
|
neq(column: string, value: unknown): this {
|
|
52
|
-
this.queryParams.append(
|
|
56
|
+
this.queryParams.append(column, `neq.${value}`);
|
|
53
57
|
return this;
|
|
54
58
|
}
|
|
55
59
|
|
|
60
|
+
/**
|
|
61
|
+
* Filter by greater than
|
|
62
|
+
*/
|
|
56
63
|
gt(column: string, value: unknown): this {
|
|
57
|
-
this.queryParams.append(
|
|
64
|
+
this.queryParams.append(column, `gt.${value}`);
|
|
58
65
|
return this;
|
|
59
66
|
}
|
|
60
67
|
|
|
68
|
+
/**
|
|
69
|
+
* Filter by greater than or equal
|
|
70
|
+
*/
|
|
61
71
|
gte(column: string, value: unknown): this {
|
|
62
|
-
this.queryParams.append(
|
|
72
|
+
this.queryParams.append(column, `gte.${value}`);
|
|
63
73
|
return this;
|
|
64
74
|
}
|
|
65
75
|
|
|
76
|
+
/**
|
|
77
|
+
* Filter by less than
|
|
78
|
+
*/
|
|
66
79
|
lt(column: string, value: unknown): this {
|
|
67
|
-
this.queryParams.append(
|
|
80
|
+
this.queryParams.append(column, `lt.${value}`);
|
|
68
81
|
return this;
|
|
69
82
|
}
|
|
70
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Filter by less than or equal
|
|
86
|
+
*/
|
|
71
87
|
lte(column: string, value: unknown): this {
|
|
72
|
-
this.queryParams.append(
|
|
88
|
+
this.queryParams.append(column, `lte.${value}`);
|
|
73
89
|
return this;
|
|
74
90
|
}
|
|
75
91
|
|
|
92
|
+
/**
|
|
93
|
+
* Filter by like pattern
|
|
94
|
+
*/
|
|
76
95
|
like(column: string, pattern: string): this {
|
|
77
|
-
this.queryParams.append(
|
|
96
|
+
this.queryParams.append(column, `like.${pattern}`);
|
|
78
97
|
return this;
|
|
79
98
|
}
|
|
80
99
|
|
|
100
|
+
/**
|
|
101
|
+
* Filter by case-insensitive like pattern
|
|
102
|
+
*/
|
|
81
103
|
ilike(column: string, pattern: string): this {
|
|
82
|
-
this.queryParams.append(
|
|
83
|
-
return this;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
in(column: string, values: unknown[]): this {
|
|
87
|
-
this.queryParams.append('in', `${column}:${values.join(',')}`);
|
|
104
|
+
this.queryParams.append(column, `ilike.${pattern}`);
|
|
88
105
|
return this;
|
|
89
106
|
}
|
|
90
107
|
|
|
108
|
+
/**
|
|
109
|
+
* Filter by array contains
|
|
110
|
+
*/
|
|
91
111
|
contains(column: string, value: unknown[]): this {
|
|
92
|
-
this.queryParams.append(
|
|
112
|
+
this.queryParams.append(column, `cs.{${value.join(',')}}`);
|
|
93
113
|
return this;
|
|
94
114
|
}
|
|
95
115
|
|
|
96
|
-
|
|
97
|
-
|
|
116
|
+
/**
|
|
117
|
+
* Filter by value in array
|
|
118
|
+
*/
|
|
119
|
+
in(column: string, values: unknown[]): this {
|
|
120
|
+
this.queryParams.append(column, `in.(${values.join(',')})`);
|
|
98
121
|
return this;
|
|
99
122
|
}
|
|
100
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Filter for null values
|
|
126
|
+
*/
|
|
101
127
|
isNull(column: string): this {
|
|
102
|
-
this.queryParams.append('
|
|
128
|
+
this.queryParams.append(column, 'is.null');
|
|
103
129
|
return this;
|
|
104
130
|
}
|
|
105
131
|
|
|
132
|
+
/**
|
|
133
|
+
* Filter for non-null values
|
|
134
|
+
*/
|
|
106
135
|
isNotNull(column: string): this {
|
|
107
|
-
this.queryParams.append('
|
|
108
|
-
return this;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
search(query: string): this {
|
|
112
|
-
this.queryParams.set('search', query);
|
|
113
|
-
return this;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
not(operator: string, column: string, value: unknown): this {
|
|
117
|
-
this.queryParams.append('not', `${operator}:${column}:${value}`);
|
|
118
|
-
return this;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
or(conditions: string): this {
|
|
122
|
-
this.queryParams.set('or', conditions);
|
|
136
|
+
this.queryParams.append(column, 'is.not.null');
|
|
123
137
|
return this;
|
|
124
138
|
}
|
|
125
139
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
140
|
+
/**
|
|
141
|
+
* Order results
|
|
142
|
+
* @example
|
|
143
|
+
* .order('created_at', { ascending: false })
|
|
144
|
+
*/
|
|
129
145
|
order(column: string, options: { ascending?: boolean; nullsFirst?: boolean } = {}): this {
|
|
130
146
|
const { ascending = true, nullsFirst } = options;
|
|
131
|
-
let
|
|
132
|
-
if (
|
|
133
|
-
|
|
134
|
-
|
|
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);
|
|
135
153
|
return this;
|
|
136
154
|
}
|
|
137
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Limit results
|
|
158
|
+
*/
|
|
138
159
|
limit(count: number): this {
|
|
139
160
|
this.queryParams.set('limit', String(count));
|
|
140
161
|
return this;
|
|
141
162
|
}
|
|
142
163
|
|
|
164
|
+
/**
|
|
165
|
+
* Offset results
|
|
166
|
+
*/
|
|
143
167
|
offset(count: number): this {
|
|
144
168
|
this.queryParams.set('offset', String(count));
|
|
145
169
|
return this;
|
|
146
170
|
}
|
|
147
171
|
|
|
172
|
+
/**
|
|
173
|
+
* Range of results (offset + limit)
|
|
174
|
+
*/
|
|
148
175
|
range(from: number, to: number): this {
|
|
149
176
|
this.queryParams.set('offset', String(from));
|
|
150
177
|
this.queryParams.set('limit', String(to - from + 1));
|
|
151
178
|
return this;
|
|
152
179
|
}
|
|
153
180
|
|
|
181
|
+
/**
|
|
182
|
+
* Return single result
|
|
183
|
+
*/
|
|
154
184
|
single(): this {
|
|
155
185
|
this.isSingle = true;
|
|
156
186
|
return this;
|
|
157
187
|
}
|
|
158
188
|
|
|
189
|
+
/**
|
|
190
|
+
* Return single result or null
|
|
191
|
+
*/
|
|
159
192
|
maybeSingle(): this {
|
|
160
193
|
this.isSingle = true;
|
|
161
194
|
return this;
|
|
162
195
|
}
|
|
163
196
|
|
|
164
|
-
// ── CUD — Supabase 스타일: 모두 this 반환 → .eq() 체이닝 가능 ─────────────
|
|
165
|
-
// await client.from('todos').insert({ title: '할일' })
|
|
166
|
-
insert(row: Partial<T> | Partial<T>[]): this {
|
|
167
|
-
this._method = 'POST';
|
|
168
|
-
const rows = Array.isArray(row) ? row : [row];
|
|
169
|
-
this._body = rows.length === 1 ? rows[0] : rows;
|
|
170
|
-
return this;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
// await client.from('todos').update({ completed: true }).eq('id', 1)
|
|
174
|
-
update(data: Partial<T>): this {
|
|
175
|
-
this._method = 'PATCH';
|
|
176
|
-
this._body = data;
|
|
177
|
-
return this;
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
// await client.from('todos').upsert({ id: 1, title: '수정됨' }, { onConflict: 'id' })
|
|
181
|
-
upsert(row: Partial<T> | Partial<T>[], options: { onConflict?: string } = {}): this {
|
|
182
|
-
this._method = 'POST';
|
|
183
|
-
this._isUpsert = true;
|
|
184
|
-
this._onConflict = options.onConflict;
|
|
185
|
-
const rows = Array.isArray(row) ? row : [row];
|
|
186
|
-
this._body = rows.length === 1 ? rows[0] : rows;
|
|
187
|
-
return this;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
// await client.from('todos').delete().eq('id', 1)
|
|
191
|
-
delete(): this {
|
|
192
|
-
this._method = 'DELETE';
|
|
193
|
-
return this;
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
// ── INTERNALS ─────────────────────────────────────────────────────────────
|
|
197
197
|
private getHeaders(): Record<string, string> {
|
|
198
198
|
const token = this.accessToken || this.anonKey;
|
|
199
199
|
return {
|
|
@@ -204,77 +204,116 @@ export class QueryBuilder<T> {
|
|
|
204
204
|
};
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
-
private
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// PATCH/DELETE with id: /api/{table}/{id}/ (query params 제외)
|
|
215
|
-
if ((this._method === 'PATCH' || this._method === 'DELETE') && this._idValue !== undefined) {
|
|
216
|
-
return `${this.url}/api/${this.tableName}/${this._idValue}/`;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// GET/POST: /api/{table}/?...params
|
|
220
|
-
const qs = this.queryParams.toString();
|
|
221
|
-
return `${this.url}/api/${this.tableName}/${qs ? `?${qs}` : ''}`;
|
|
222
|
-
}
|
|
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}` : ''}`;
|
|
223
213
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
214
|
+
const options: RequestInit = {
|
|
215
|
+
method,
|
|
216
|
+
headers: this.getHeaders(),
|
|
217
|
+
};
|
|
228
218
|
|
|
229
|
-
if (
|
|
230
|
-
options.body = JSON.stringify(
|
|
231
|
-
(
|
|
219
|
+
if (body && method !== 'GET') {
|
|
220
|
+
options.body = JSON.stringify(body);
|
|
221
|
+
(options.headers as Record<string, string>)['Prefer'] = 'return=representation';
|
|
232
222
|
}
|
|
233
223
|
|
|
234
224
|
try {
|
|
235
225
|
const response = await fetch(fullUrl, options);
|
|
236
|
-
|
|
237
|
-
let data:
|
|
226
|
+
|
|
227
|
+
let data: TResponse | null = null;
|
|
238
228
|
let error: AuraBaseError | null = null;
|
|
239
229
|
|
|
230
|
+
const text = await response.text();
|
|
231
|
+
|
|
240
232
|
if (text) {
|
|
241
233
|
try {
|
|
242
234
|
const parsed = JSON.parse(text);
|
|
235
|
+
|
|
243
236
|
if (!response.ok) {
|
|
244
237
|
error = {
|
|
245
|
-
message: parsed.message || parsed.error ||
|
|
238
|
+
message: parsed.message || parsed.error || `HTTP ${response.status}`,
|
|
246
239
|
code: parsed.code,
|
|
247
240
|
details: parsed.details,
|
|
248
241
|
};
|
|
249
242
|
} else {
|
|
250
|
-
data = this.isSingle && Array.isArray(parsed) ?
|
|
243
|
+
data = this.isSingle && Array.isArray(parsed) ? parsed[0] ?? null : parsed;
|
|
251
244
|
}
|
|
252
245
|
} catch {
|
|
253
|
-
if (!response.ok)
|
|
246
|
+
if (!response.ok) {
|
|
247
|
+
error = {
|
|
248
|
+
message: text || `HTTP ${response.status}`,
|
|
249
|
+
};
|
|
250
|
+
}
|
|
254
251
|
}
|
|
255
252
|
}
|
|
256
253
|
|
|
257
|
-
return {
|
|
254
|
+
return {
|
|
255
|
+
data,
|
|
256
|
+
error,
|
|
257
|
+
status: response.status,
|
|
258
|
+
statusText: response.statusText,
|
|
259
|
+
};
|
|
258
260
|
} catch (err) {
|
|
259
261
|
return {
|
|
260
262
|
data: null,
|
|
261
|
-
error: {
|
|
263
|
+
error: {
|
|
264
|
+
message: err instanceof Error ? err.message : 'Network error',
|
|
265
|
+
},
|
|
262
266
|
status: 0,
|
|
263
267
|
statusText: 'Network Error',
|
|
264
268
|
};
|
|
265
269
|
}
|
|
266
270
|
}
|
|
267
271
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
272
|
+
/**
|
|
273
|
+
* Execute SELECT query
|
|
274
|
+
*/
|
|
275
|
+
async then<TResult = T[]>(
|
|
276
|
+
resolve: (value: PostgrestResponse<TResult>) => void | PromiseLike<void>,
|
|
271
277
|
reject?: (reason: unknown) => void | PromiseLike<void>
|
|
272
278
|
): Promise<void> {
|
|
273
279
|
try {
|
|
274
|
-
const result = await this.
|
|
280
|
+
const result = await this.request<TResult>('GET');
|
|
275
281
|
await resolve(result);
|
|
276
282
|
} catch (err) {
|
|
277
|
-
if (reject)
|
|
283
|
+
if (reject) {
|
|
284
|
+
await reject(err);
|
|
285
|
+
}
|
|
278
286
|
}
|
|
279
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
|
+
}
|
|
280
319
|
}
|