aurabase-js 0.1.0 → 0.2.1

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/dist/index.mjs CHANGED
@@ -2,6 +2,11 @@
2
2
  var QueryBuilder = class {
3
3
  constructor(url, anonKey, accessToken, tableName, headers = {}) {
4
4
  this.isSingle = false;
5
+ this._method = "GET";
6
+ this._body = void 0;
7
+ this._isUpsert = false;
8
+ this._onConflict = void 0;
9
+ this._idValue = void 0;
5
10
  this.url = url;
6
11
  this.anonKey = anonKey;
7
12
  this.accessToken = accessToken;
@@ -9,101 +14,120 @@ var QueryBuilder = class {
9
14
  this.queryParams = new URLSearchParams();
10
15
  this.headers = { ...headers };
11
16
  }
12
- /**
13
- * Select columns
14
- * @example
15
- * .select('id, name, email')
16
- * .select('*')
17
- */
18
17
  select(columns = "*") {
19
- this.queryParams.set("select", columns);
18
+ if (columns !== "*") this.queryParams.set("select", columns.replace(/\s/g, ""));
20
19
  return this;
21
20
  }
22
- /**
23
- * Filter by column equality
24
- * @example
25
- * .eq('id', 1)
26
- * .eq('status', 'active')
27
- */
28
21
  eq(column, value) {
29
- this.queryParams.append(column, `eq.${value}`);
22
+ if (column === "id") this._idValue = value;
23
+ this.queryParams.append("eq", `${column}:${value}`);
30
24
  return this;
31
25
  }
32
26
  /**
33
27
  * Filter by column inequality
34
28
  */
35
29
  neq(column, value) {
36
- this.queryParams.append(column, `neq.${value}`);
30
+ this.queryParams.append("neq", `${column}:${value}`);
37
31
  return this;
38
32
  }
39
33
  /**
40
34
  * Filter by greater than
41
35
  */
42
36
  gt(column, value) {
43
- this.queryParams.append(column, `gt.${value}`);
37
+ this.queryParams.append("gt", `${column}:${value}`);
44
38
  return this;
45
39
  }
46
40
  /**
47
41
  * Filter by greater than or equal
48
42
  */
49
43
  gte(column, value) {
50
- this.queryParams.append(column, `gte.${value}`);
44
+ this.queryParams.append("gte", `${column}:${value}`);
51
45
  return this;
52
46
  }
53
47
  /**
54
48
  * Filter by less than
55
49
  */
56
50
  lt(column, value) {
57
- this.queryParams.append(column, `lt.${value}`);
51
+ this.queryParams.append("lt", `${column}:${value}`);
58
52
  return this;
59
53
  }
60
54
  /**
61
55
  * Filter by less than or equal
62
56
  */
63
57
  lte(column, value) {
64
- this.queryParams.append(column, `lte.${value}`);
58
+ this.queryParams.append("lte", `${column}:${value}`);
65
59
  return this;
66
60
  }
67
61
  /**
68
62
  * Filter by like pattern
69
63
  */
70
64
  like(column, pattern) {
71
- this.queryParams.append(column, `like.${pattern}`);
65
+ this.queryParams.append("like", `${column}:${pattern}`);
72
66
  return this;
73
67
  }
74
68
  /**
75
69
  * Filter by case-insensitive like pattern
76
70
  */
77
71
  ilike(column, pattern) {
78
- this.queryParams.append(column, `ilike.${pattern}`);
72
+ this.queryParams.append("ilike", `${column}:${pattern}`);
79
73
  return this;
80
74
  }
81
75
  /**
82
76
  * Filter by array contains
83
77
  */
84
78
  contains(column, value) {
85
- this.queryParams.append(column, `cs.{${value.join(",")}}`);
79
+ this.queryParams.append("contains", `${column}:${JSON.stringify(value)}`);
86
80
  return this;
87
81
  }
88
82
  /**
89
83
  * Filter by value in array
90
84
  */
91
85
  in(column, values) {
92
- this.queryParams.append(column, `in.(${values.join(",")})`);
86
+ this.queryParams.append("in", `${column}:${values.join(",")}`);
93
87
  return this;
94
88
  }
95
89
  /**
96
90
  * Filter for null values
97
91
  */
98
92
  isNull(column) {
99
- this.queryParams.append(column, "is.null");
93
+ this.queryParams.append("is_null", `${column}:true`);
100
94
  return this;
101
95
  }
102
96
  /**
103
97
  * Filter for non-null values
104
98
  */
105
99
  isNotNull(column) {
106
- this.queryParams.append(column, "is.not.null");
100
+ this.queryParams.append("is_null", `${column}:false`);
101
+ return this;
102
+ }
103
+ /**
104
+ * Full-text search across all columns
105
+ */
106
+ search(query) {
107
+ this.queryParams.set("search", query);
108
+ return this;
109
+ }
110
+ /**
111
+ * Negate a filter
112
+ * @example .not('eq', 'category', 'Electronics')
113
+ */
114
+ not(operator, column, value) {
115
+ this.queryParams.append("not", `${operator}:${column}:${value}`);
116
+ return this;
117
+ }
118
+ /**
119
+ * OR conditions
120
+ * @example .or('category.eq.Electronics,price.lt.100000')
121
+ */
122
+ or(conditions) {
123
+ this.queryParams.set("or", conditions);
124
+ return this;
125
+ }
126
+ /**
127
+ * Filter by array contained by
128
+ */
129
+ containedBy(column, value) {
130
+ this.queryParams.append("contained_by", `${column}:${JSON.stringify(value)}`);
107
131
  return this;
108
132
  }
109
133
  /**
@@ -118,7 +142,8 @@ var QueryBuilder = class {
118
142
  if (nullsFirst !== void 0) {
119
143
  orderStr += nullsFirst ? ".nullsfirst" : ".nullslast";
120
144
  }
121
- this.queryParams.append("order", orderStr);
145
+ const existing = this.queryParams.get("order");
146
+ this.queryParams.set("order", existing ? `${existing},${orderStr}` : orderStr);
122
147
  return this;
123
148
  }
124
149
  /**
@@ -166,28 +191,36 @@ var QueryBuilder = class {
166
191
  ...this.headers
167
192
  };
168
193
  }
169
- async request(method, body) {
170
- const queryString = this.queryParams.toString();
171
- const fullUrl = `${this.url}/rest/v1/${this.tableName}${queryString ? `?${queryString}` : ""}`;
172
- const options = {
173
- method,
174
- headers: this.getHeaders()
175
- };
176
- if (body && method !== "GET") {
177
- options.body = JSON.stringify(body);
178
- options.headers["Prefer"] = "return=representation";
194
+ buildUrl() {
195
+ const qs = this.queryParams.toString();
196
+ if (this._isUpsert) {
197
+ const conflict = this._onConflict ? `?on_conflict=${this._onConflict}` : "";
198
+ return `${this.url}/api/${this.tableName}/upsert${conflict}`;
199
+ }
200
+ if ((this._method === "PATCH" || this._method === "DELETE") && this._idValue !== void 0) {
201
+ return `${this.url}/api/${this.tableName}/${this._idValue}/`;
202
+ }
203
+ return `${this.url}/api/${this.tableName}/${qs ? `?${qs}` : ""}`;
204
+ }
205
+ async execute() {
206
+ const fullUrl = this.buildUrl();
207
+ const reqHeaders = this.getHeaders();
208
+ const options = { method: this._method, headers: reqHeaders };
209
+ if (this._body !== void 0 && this._method !== "GET") {
210
+ options.body = JSON.stringify(this._body);
211
+ reqHeaders["Prefer"] = "return=representation";
179
212
  }
180
213
  try {
181
214
  const response = await fetch(fullUrl, options);
215
+ const text = await response.text();
182
216
  let data = null;
183
217
  let error = null;
184
- const text = await response.text();
185
218
  if (text) {
186
219
  try {
187
220
  const parsed = JSON.parse(text);
188
221
  if (!response.ok) {
189
222
  error = {
190
- message: parsed.message || parsed.error || `HTTP ${response.status}`,
223
+ message: parsed.message || parsed.error || parsed.detail || `HTTP ${response.status}`,
191
224
  code: parsed.code,
192
225
  details: parsed.details
193
226
  };
@@ -195,69 +228,49 @@ var QueryBuilder = class {
195
228
  data = this.isSingle && Array.isArray(parsed) ? parsed[0] ?? null : parsed;
196
229
  }
197
230
  } catch {
198
- if (!response.ok) {
199
- error = {
200
- message: text || `HTTP ${response.status}`
201
- };
202
- }
231
+ if (!response.ok) error = { message: text || `HTTP ${response.status}` };
203
232
  }
204
233
  }
205
- return {
206
- data,
207
- error,
208
- status: response.status,
209
- statusText: response.statusText
210
- };
234
+ return { data, error, status: response.status, statusText: response.statusText };
211
235
  } catch (err) {
212
236
  return {
213
237
  data: null,
214
- error: {
215
- message: err instanceof Error ? err.message : "Network error"
216
- },
238
+ error: { message: err instanceof Error ? err.message : "Network error" },
217
239
  status: 0,
218
240
  statusText: "Network Error"
219
241
  };
220
242
  }
221
243
  }
222
- /**
223
- * Execute SELECT query
224
- */
225
244
  async then(resolve, reject) {
226
245
  try {
227
- const result = await this.request("GET");
246
+ const result = await this.execute();
228
247
  await resolve(result);
229
248
  } catch (err) {
230
- if (reject) {
231
- await reject(err);
232
- }
249
+ if (reject) await reject(err);
233
250
  }
234
251
  }
235
- /**
236
- * Insert row(s)
237
- */
238
- async insert(row) {
252
+ insert(row) {
253
+ this._method = "POST";
239
254
  const rows = Array.isArray(row) ? row : [row];
240
- return this.request("POST", rows.length === 1 ? rows[0] : rows);
255
+ this._body = rows.length === 1 ? rows[0] : rows;
256
+ return this;
241
257
  }
242
- /**
243
- * Update row(s)
244
- */
245
- async update(data) {
246
- return this.request("PATCH", data);
258
+ update(data) {
259
+ this._method = "PATCH";
260
+ this._body = data;
261
+ return this;
247
262
  }
248
- /**
249
- * Upsert row(s)
250
- */
251
- async upsert(row) {
263
+ upsert(row, options = {}) {
264
+ this._method = "POST";
265
+ this._isUpsert = true;
266
+ this._onConflict = options.onConflict;
252
267
  const rows = Array.isArray(row) ? row : [row];
253
- this.headers["Prefer"] = "resolution=merge-duplicates,return=representation";
254
- return this.request("POST", rows.length === 1 ? rows[0] : rows);
268
+ this._body = rows.length === 1 ? rows[0] : rows;
269
+ return this;
255
270
  }
256
- /**
257
- * Delete row(s)
258
- */
259
- async delete() {
260
- return this.request("DELETE");
271
+ delete() {
272
+ this._method = "DELETE";
273
+ return this;
261
274
  }
262
275
  };
263
276
 
@@ -557,7 +570,7 @@ var AuraBaseClient = class {
557
570
  async rpc(functionName, params) {
558
571
  const token = this.accessToken || this.anonKey;
559
572
  try {
560
- const response = await fetch(`${this.url}/rpc/v1/${functionName}`, {
573
+ const response = await fetch(`${this.url}/api/${functionName}/`, {
561
574
  method: "POST",
562
575
  headers: {
563
576
  "Content-Type": "application/json",
@@ -731,6 +744,23 @@ var StorageBucket = class {
731
744
 
732
745
  // src/index.ts
733
746
  function createClient(options) {
747
+ if (!options.url) {
748
+ console.warn(
749
+ "[aurabase-js] NEXT_PUBLIC_AURABASE_URL이 설정되지 않았습니다.\n" +
750
+ ".env.local 파일에 다음 항목을 추가하세요:\n\n" +
751
+ " NEXT_PUBLIC_AURABASE_URL=https://your-project.cloudfront.net\n" +
752
+ " NEXT_PUBLIC_AURABASE_ANON_KEY=your-anon-key\n" +
753
+ " NEXT_PUBLIC_AURABASE_SERVICE_ROLE_KEY=your-service-role-key\n"
754
+ );
755
+ }
756
+ if (!options.anonKey) {
757
+ console.warn(
758
+ "[aurabase-js] anonKey가 설정되지 않았습니다.\n" +
759
+ ".env.local 파일에 다음 항목을 추가하세요:\n\n" +
760
+ " NEXT_PUBLIC_AURABASE_ANON_KEY=your-anon-key (클라이언트용)\n" +
761
+ " NEXT_PUBLIC_AURABASE_SERVICE_ROLE_KEY=your-service-role-key (서버/admin용)\n"
762
+ );
763
+ }
734
764
  return new AuraBaseClient(options);
735
765
  }
736
766
  var index_default = createClient;
package/package.json CHANGED
@@ -1,10 +1,13 @@
1
1
  {
2
2
  "name": "aurabase-js",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "AuraBase client library - Supabase-style SDK for AuraBase",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
7
7
  "types": "dist/index.d.ts",
8
+ "bin": {
9
+ "aurabase-js": "./dist/cli.js"
10
+ },
8
11
  "exports": {
9
12
  ".": {
10
13
  "types": "./dist/index.d.ts",
@@ -13,16 +16,17 @@
13
16
  }
14
17
  },
15
18
  "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
+ "build": "tsup src/index.ts --format cjs,esm --dts && tsup src/cli.ts --format cjs --no-dts",
20
+ "dev": "tsup src/index.ts src/cli.ts --format cjs,esm --dts --watch",
21
+ "prepublishOnly": "echo 'skipping build - dist already compiled'"
19
22
  },
20
23
  "keywords": [
21
24
  "aurabase",
22
25
  "database",
23
26
  "rest",
24
27
  "client",
25
- "supabase-like"
28
+ "supabase-like",
29
+ "cli"
26
30
  ],
27
31
  "author": "",
28
32
  "license": "MIT",
@@ -46,18 +46,16 @@ export class AuraBaseClient {
46
46
  }
47
47
 
48
48
  /**
49
- * Execute raw SQL (RPC function call)
50
- * @example
51
- * const { data, error } = await client.rpc('my_function', { arg1: 'value' })
49
+ * Call a serverless function
50
+ * POST /api/{functionName}/
52
51
  */
53
52
  async rpc<T = unknown>(
54
53
  functionName: string,
55
54
  params?: Record<string, unknown>
56
55
  ): Promise<{ data: T | null; error: { message: string } | null }> {
57
56
  const token = this.accessToken || this.anonKey;
58
-
59
57
  try {
60
- const response = await fetch(`${this.url}/rpc/v1/${functionName}`, {
58
+ const response = await fetch(`${this.url}/api/${functionName}/`, {
61
59
  method: 'POST',
62
60
  headers: {
63
61
  'Content-Type': 'application/json',
@@ -67,32 +65,24 @@ export class AuraBaseClient {
67
65
  },
68
66
  body: JSON.stringify(params || {}),
69
67
  });
70
-
71
68
  const text = await response.text();
72
69
  let data: T | null = null;
73
70
  let error: { message: string } | null = null;
74
-
75
71
  if (text) {
76
72
  try {
77
73
  const parsed = JSON.parse(text);
78
74
  if (!response.ok) {
79
- error = { message: parsed.message || parsed.error || `HTTP ${response.status}` };
75
+ error = { message: parsed.message || parsed.error || parsed.detail || `HTTP ${response.status}` };
80
76
  } else {
81
77
  data = parsed;
82
78
  }
83
79
  } catch {
84
- if (!response.ok) {
85
- error = { message: text || `HTTP ${response.status}` };
86
- }
80
+ if (!response.ok) error = { message: text || `HTTP ${response.status}` };
87
81
  }
88
82
  }
89
-
90
83
  return { data, error };
91
84
  } catch (err) {
92
- return {
93
- data: null,
94
- error: { message: err instanceof Error ? err.message : 'Network error' },
95
- };
85
+ return { data: null, error: { message: err instanceof Error ? err.message : 'Network error' } };
96
86
  }
97
87
  }
98
88