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/README.md CHANGED
@@ -12,6 +12,74 @@ yarn add aurabase-js
12
12
  pnpm add aurabase-js
13
13
  ```
14
14
 
15
+ 설치 후 아래 명령어 한 번으로 `lib/` 폴더와 환경변수 파일을 자동 생성합니다:
16
+
17
+ ```bash
18
+ npx aurabase-js init
19
+ ```
20
+
21
+ ```
22
+ aurabase-js init
23
+ ──────────────────────────────────
24
+ ✔ lib/client.ts 생성
25
+ ✔ lib/server.ts 생성
26
+ ✔ lib/admin.ts 생성
27
+
28
+ 📋 환경변수 설정
29
+ ──────────────────────────────────
30
+ ✔ .env.local 생성 — 아래 값을 채워주세요
31
+
32
+ NEXT_PUBLIC_AURABASE_URL=https://your-project.cloudfront.net
33
+ NEXT_PUBLIC_AURABASE_ANON_KEY=your-anon-key
34
+ NEXT_PUBLIC_AURABASE_SERVICE_ROLE_KEY=your-service-role-key
35
+
36
+ → AuraBase 대시보드 API Keys 메뉴에서 확인하세요.
37
+ ```
38
+
39
+ ---
40
+
41
+ ## ⚙️ 환경변수 설정 (Next.js)
42
+
43
+ 설치 후 프로젝트 루트에 `.env.local` 파일을 생성하고 아래 값을 입력하세요.
44
+
45
+ ```env
46
+ # AuraBase 프로젝트 URL
47
+ NEXT_PUBLIC_AURABASE_URL=https://your-project.cloudfront.net
48
+
49
+ # 일반 사용자용 키 (클라이언트 컴포넌트, RLS 적용)
50
+ NEXT_PUBLIC_AURABASE_ANON_KEY=your-anon-key
51
+
52
+ # 관리자용 키 (서버 컴포넌트 / API Route 전용, RLS 우회)
53
+ NEXT_PUBLIC_AURABASE_SERVICE_ROLE_KEY=your-service-role-key
54
+ ```
55
+
56
+ > 위 값은 AuraBase 대시보드 → **API Keys** 메뉴에서 확인할 수 있습니다.
57
+
58
+ ### `lib/client.ts` — 클라이언트 컴포넌트용
59
+
60
+ ```typescript
61
+ import { createClient } from 'aurabase-js'
62
+
63
+ export const client = createClient({
64
+ url: process.env.NEXT_PUBLIC_AURABASE_URL!,
65
+ anonKey: process.env.NEXT_PUBLIC_AURABASE_ANON_KEY!,
66
+ })
67
+ ```
68
+
69
+ ### `lib/admin.ts` — 서버 전용 (API Route, Server Component)
70
+
71
+ ```typescript
72
+ import { createClient } from 'aurabase-js'
73
+
74
+ // ⚠️ 절대 클라이언트 컴포넌트에서 사용 금지 — RLS를 우회합니다
75
+ export const admin = createClient({
76
+ url: process.env.NEXT_PUBLIC_AURABASE_URL!,
77
+ anonKey: process.env.NEXT_PUBLIC_AURABASE_SERVICE_ROLE_KEY!,
78
+ })
79
+ ```
80
+
81
+ ---
82
+
15
83
  ## 사용법
16
84
 
17
85
  ### 클라이언트 생성
@@ -20,8 +88,8 @@ pnpm add aurabase-js
20
88
  import { createClient } from 'aurabase-js'
21
89
 
22
90
  const aurabase = createClient({
23
- url: 'http://localhost:8000', // 또는 배포된 URL
24
- anonKey: 'your-anon-key'
91
+ url: process.env.NEXT_PUBLIC_AURABASE_URL!,
92
+ anonKey: process.env.NEXT_PUBLIC_AURABASE_ANON_KEY!,
25
93
  })
26
94
  ```
27
95
 
package/dist/cli.js ADDED
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+
7
+ const args = process.argv.slice(2);
8
+ const command = args[0];
9
+
10
+ const BOLD = '\x1b[1m';
11
+ const GREEN = '\x1b[32m';
12
+ const YELLOW = '\x1b[33m';
13
+ const CYAN = '\x1b[36m';
14
+ const RESET = '\x1b[0m';
15
+
16
+ function success(msg) { console.log(`${GREEN}✔${RESET} ${msg}`); }
17
+ function warn(msg) { console.log(`${YELLOW}⚠${RESET} ${msg}`); }
18
+ function info(msg) { console.log(`${CYAN}ℹ${RESET} ${msg}`); }
19
+
20
+ // ─── 생성할 파일 내용 ──────────────────────────────────────────────────────────
21
+
22
+ const CLIENT_TS = `import { createClient } from 'aurabase-js';
23
+
24
+ export const client = createClient({
25
+ url: process.env.NEXT_PUBLIC_AURABASE_URL || '',
26
+ anonKey: process.env.NEXT_PUBLIC_AURABASE_ANON_KEY || '',
27
+ });
28
+ `;
29
+
30
+ const SERVER_TS = `import { createClient } from 'aurabase-js';
31
+ import { cookies } from 'next/headers';
32
+
33
+ export async function server() {
34
+ const cookieStore = await cookies();
35
+ const accessToken = cookieStore.get('access_token')?.value;
36
+
37
+ const client = createClient({
38
+ url: process.env.NEXT_PUBLIC_AURABASE_URL || '',
39
+ anonKey: process.env.NEXT_PUBLIC_AURABASE_ANON_KEY || '',
40
+ });
41
+
42
+ if (accessToken) {
43
+ client.setAccessToken(accessToken);
44
+ }
45
+
46
+ return client;
47
+ }
48
+ `;
49
+
50
+ const ADMIN_TS = `import { createClient } from 'aurabase-js';
51
+
52
+ /**
53
+ * Admin client with service_role key
54
+ * ⚠️ ONLY use on server-side (API routes, server components)
55
+ * ⚠️ Bypasses all RLS policies - use with caution!
56
+ */
57
+ export const admin = createClient({
58
+ url: process.env.NEXT_PUBLIC_AURABASE_URL || '',
59
+ anonKey: process.env.NEXT_PUBLIC_AURABASE_SERVICE_ROLE_KEY || '',
60
+ });
61
+ `;
62
+
63
+ const ENV_EXAMPLE = `# AuraBase
64
+ NEXT_PUBLIC_AURABASE_URL=https://your-project.cloudfront.net
65
+ NEXT_PUBLIC_AURABASE_ANON_KEY=your-anon-key
66
+ NEXT_PUBLIC_AURABASE_SERVICE_ROLE_KEY=your-service-role-key
67
+ `;
68
+
69
+ // ─── init ─────────────────────────────────────────────────────────────────────
70
+
71
+ function init() {
72
+ const cwd = process.cwd();
73
+ const libDir = path.join(cwd, 'lib');
74
+
75
+ console.log('');
76
+ console.log(`${BOLD}aurabase-js init${RESET}`);
77
+ console.log('──────────────────────────────────');
78
+
79
+ if (!fs.existsSync(libDir)) {
80
+ fs.mkdirSync(libDir, { recursive: true });
81
+ success('lib/ 폴더 생성');
82
+ } else {
83
+ info('lib/ 폴더가 이미 존재합니다');
84
+ }
85
+
86
+ const files = [
87
+ ['lib/client.ts', CLIENT_TS],
88
+ ['lib/server.ts', SERVER_TS],
89
+ ['lib/admin.ts', ADMIN_TS],
90
+ ];
91
+
92
+ for (const [relPath, content] of files) {
93
+ const fullPath = path.join(cwd, relPath);
94
+ if (fs.existsSync(fullPath)) {
95
+ warn(`${relPath} 이미 존재 — 건너뜀`);
96
+ } else {
97
+ fs.writeFileSync(fullPath, content, 'utf8');
98
+ success(`${relPath} 생성`);
99
+ }
100
+ }
101
+
102
+ const envPath = path.join(cwd, '.env.local');
103
+ console.log('');
104
+ console.log(`${BOLD}📋 환경변수 설정${RESET}`);
105
+ console.log('──────────────────────────────────');
106
+
107
+ if (!fs.existsSync(envPath)) {
108
+ fs.writeFileSync(envPath, ENV_EXAMPLE, 'utf8');
109
+ success('.env.local 생성 — 아래 값을 채워주세요');
110
+ } else {
111
+ info('.env.local 이미 존재 — 아래 항목이 있는지 확인하세요');
112
+ }
113
+
114
+ console.log('');
115
+ console.log(` ${CYAN}NEXT_PUBLIC_AURABASE_URL${RESET}=https://your-project.cloudfront.net`);
116
+ console.log(` ${CYAN}NEXT_PUBLIC_AURABASE_ANON_KEY${RESET}=your-anon-key`);
117
+ console.log(` ${CYAN}NEXT_PUBLIC_AURABASE_SERVICE_ROLE_KEY${RESET}=your-service-role-key`);
118
+ console.log('');
119
+ console.log(` → AuraBase 대시보드 ${BOLD}API Keys${RESET} 메뉴에서 확인하세요.`);
120
+ console.log('');
121
+ console.log(`${GREEN}✔ 완료!${RESET} 이제 아래처럼 사용할 수 있습니다:`);
122
+ console.log('');
123
+ console.log(` ${CYAN}// 클라이언트 컴포넌트${RESET}`);
124
+ console.log(` import { client } from '@/lib/client'`);
125
+ console.log(` const { data } = await client.from('todos').select('*')`);
126
+ console.log('');
127
+ console.log(` ${CYAN}// 서버 전용 (API Route, Server Component)${RESET}`);
128
+ console.log(` import { admin } from '@/lib/admin'`);
129
+ console.log(` const { data } = await admin.from('todos').select('*')`);
130
+ console.log('');
131
+ }
132
+
133
+ // ─── 진입점 ────────────────────────────────────────────────────────────────────
134
+
135
+ if (command === 'init') {
136
+ init();
137
+ } else {
138
+ console.log('');
139
+ console.log(`${BOLD}aurabase-js CLI${RESET}`);
140
+ console.log('');
141
+ console.log('사용법:');
142
+ console.log(` ${CYAN}npx aurabase-js init${RESET} lib/ 폴더 및 환경변수 파일 자동 생성`);
143
+ console.log('');
144
+ }
package/dist/index.js CHANGED
@@ -30,6 +30,11 @@ module.exports = __toCommonJS(index_exports);
30
30
  var QueryBuilder = class {
31
31
  constructor(url, anonKey, accessToken, tableName, headers = {}) {
32
32
  this.isSingle = false;
33
+ this._method = "GET";
34
+ this._body = void 0;
35
+ this._isUpsert = false;
36
+ this._onConflict = void 0;
37
+ this._idValue = void 0;
33
38
  this.url = url;
34
39
  this.anonKey = anonKey;
35
40
  this.accessToken = accessToken;
@@ -44,94 +49,119 @@ var QueryBuilder = class {
44
49
  * .select('*')
45
50
  */
46
51
  select(columns = "*") {
47
- this.queryParams.set("select", columns);
52
+ if (columns !== "*") this.queryParams.set("select", columns.replace(/\s/g, ""));
48
53
  return this;
49
54
  }
50
- /**
51
- * Filter by column equality
52
- * @example
53
- * .eq('id', 1)
54
- * .eq('status', 'active')
55
- */
56
55
  eq(column, value) {
57
- this.queryParams.append(column, `eq.${value}`);
56
+ if (column === "id") this._idValue = value;
57
+ this.queryParams.append("eq", `${column}:${value}`);
58
58
  return this;
59
59
  }
60
60
  /**
61
61
  * Filter by column inequality
62
62
  */
63
63
  neq(column, value) {
64
- this.queryParams.append(column, `neq.${value}`);
64
+ this.queryParams.append("neq", `${column}:${value}`);
65
65
  return this;
66
66
  }
67
67
  /**
68
68
  * Filter by greater than
69
69
  */
70
70
  gt(column, value) {
71
- this.queryParams.append(column, `gt.${value}`);
71
+ this.queryParams.append("gt", `${column}:${value}`);
72
72
  return this;
73
73
  }
74
74
  /**
75
75
  * Filter by greater than or equal
76
76
  */
77
77
  gte(column, value) {
78
- this.queryParams.append(column, `gte.${value}`);
78
+ this.queryParams.append("gte", `${column}:${value}`);
79
79
  return this;
80
80
  }
81
81
  /**
82
82
  * Filter by less than
83
83
  */
84
84
  lt(column, value) {
85
- this.queryParams.append(column, `lt.${value}`);
85
+ this.queryParams.append("lt", `${column}:${value}`);
86
86
  return this;
87
87
  }
88
88
  /**
89
89
  * Filter by less than or equal
90
90
  */
91
91
  lte(column, value) {
92
- this.queryParams.append(column, `lte.${value}`);
92
+ this.queryParams.append("lte", `${column}:${value}`);
93
93
  return this;
94
94
  }
95
95
  /**
96
96
  * Filter by like pattern
97
97
  */
98
98
  like(column, pattern) {
99
- this.queryParams.append(column, `like.${pattern}`);
99
+ this.queryParams.append("like", `${column}:${pattern}`);
100
100
  return this;
101
101
  }
102
102
  /**
103
103
  * Filter by case-insensitive like pattern
104
104
  */
105
105
  ilike(column, pattern) {
106
- this.queryParams.append(column, `ilike.${pattern}`);
106
+ this.queryParams.append("ilike", `${column}:${pattern}`);
107
107
  return this;
108
108
  }
109
109
  /**
110
110
  * Filter by array contains
111
111
  */
112
112
  contains(column, value) {
113
- this.queryParams.append(column, `cs.{${value.join(",")}}`);
113
+ this.queryParams.append("contains", `${column}:${JSON.stringify(value)}`);
114
114
  return this;
115
115
  }
116
116
  /**
117
117
  * Filter by value in array
118
118
  */
119
119
  in(column, values) {
120
- this.queryParams.append(column, `in.(${values.join(",")})`);
120
+ this.queryParams.append("in", `${column}:${values.join(",")}`);
121
121
  return this;
122
122
  }
123
123
  /**
124
124
  * Filter for null values
125
125
  */
126
126
  isNull(column) {
127
- this.queryParams.append(column, "is.null");
127
+ this.queryParams.append("is_null", `${column}:true`);
128
128
  return this;
129
129
  }
130
130
  /**
131
131
  * Filter for non-null values
132
132
  */
133
133
  isNotNull(column) {
134
- this.queryParams.append(column, "is.not.null");
134
+ this.queryParams.append("is_null", `${column}:false`);
135
+ return this;
136
+ }
137
+ /**
138
+ * Full-text search across all columns
139
+ */
140
+ search(query) {
141
+ this.queryParams.set("search", query);
142
+ return this;
143
+ }
144
+ /**
145
+ * Negate a filter
146
+ * @example .not('eq', 'category', 'Electronics')
147
+ */
148
+ not(operator, column, value) {
149
+ this.queryParams.append("not", `${operator}:${column}:${value}`);
150
+ return this;
151
+ }
152
+ /**
153
+ * OR conditions
154
+ * @example .or('category.eq.Electronics,price.lt.100000')
155
+ */
156
+ or(conditions) {
157
+ this.queryParams.set("or", conditions);
158
+ return this;
159
+ }
160
+ /**
161
+ * Filter by array contained by
162
+ */
163
+ containedBy(column, value) {
164
+ this.queryParams.append("contained_by", `${column}:${JSON.stringify(value)}`);
135
165
  return this;
136
166
  }
137
167
  /**
@@ -146,7 +176,8 @@ var QueryBuilder = class {
146
176
  if (nullsFirst !== void 0) {
147
177
  orderStr += nullsFirst ? ".nullsfirst" : ".nullslast";
148
178
  }
149
- this.queryParams.append("order", orderStr);
179
+ const existing = this.queryParams.get("order");
180
+ this.queryParams.set("order", existing ? `${existing},${orderStr}` : orderStr);
150
181
  return this;
151
182
  }
152
183
  /**
@@ -194,28 +225,36 @@ var QueryBuilder = class {
194
225
  ...this.headers
195
226
  };
196
227
  }
197
- async request(method, body) {
198
- const queryString = this.queryParams.toString();
199
- const fullUrl = `${this.url}/rest/v1/${this.tableName}${queryString ? `?${queryString}` : ""}`;
200
- const options = {
201
- method,
202
- headers: this.getHeaders()
203
- };
204
- if (body && method !== "GET") {
205
- options.body = JSON.stringify(body);
206
- options.headers["Prefer"] = "return=representation";
228
+ buildUrl() {
229
+ const qs = this.queryParams.toString();
230
+ if (this._isUpsert) {
231
+ const conflict = this._onConflict ? `?on_conflict=${this._onConflict}` : "";
232
+ return `${this.url}/api/${this.tableName}/upsert${conflict}`;
233
+ }
234
+ if ((this._method === "PATCH" || this._method === "DELETE") && this._idValue !== void 0) {
235
+ return `${this.url}/api/${this.tableName}/${this._idValue}/`;
236
+ }
237
+ return `${this.url}/api/${this.tableName}/${qs ? `?${qs}` : ""}`;
238
+ }
239
+ async execute() {
240
+ const fullUrl = this.buildUrl();
241
+ const reqHeaders = this.getHeaders();
242
+ const options = { method: this._method, headers: reqHeaders };
243
+ if (this._body !== void 0 && this._method !== "GET") {
244
+ options.body = JSON.stringify(this._body);
245
+ reqHeaders["Prefer"] = "return=representation";
207
246
  }
208
247
  try {
209
248
  const response = await fetch(fullUrl, options);
249
+ const text = await response.text();
210
250
  let data = null;
211
251
  let error = null;
212
- const text = await response.text();
213
252
  if (text) {
214
253
  try {
215
254
  const parsed = JSON.parse(text);
216
255
  if (!response.ok) {
217
256
  error = {
218
- message: parsed.message || parsed.error || `HTTP ${response.status}`,
257
+ message: parsed.message || parsed.error || parsed.detail || `HTTP ${response.status}`,
219
258
  code: parsed.code,
220
259
  details: parsed.details
221
260
  };
@@ -223,69 +262,54 @@ var QueryBuilder = class {
223
262
  data = this.isSingle && Array.isArray(parsed) ? parsed[0] ?? null : parsed;
224
263
  }
225
264
  } catch {
226
- if (!response.ok) {
227
- error = {
228
- message: text || `HTTP ${response.status}`
229
- };
230
- }
265
+ if (!response.ok) error = { message: text || `HTTP ${response.status}` };
231
266
  }
232
267
  }
233
- return {
234
- data,
235
- error,
236
- status: response.status,
237
- statusText: response.statusText
238
- };
268
+ return { data, error, status: response.status, statusText: response.statusText };
239
269
  } catch (err) {
240
270
  return {
241
271
  data: null,
242
- error: {
243
- message: err instanceof Error ? err.message : "Network error"
244
- },
272
+ error: { message: err instanceof Error ? err.message : "Network error" },
245
273
  status: 0,
246
274
  statusText: "Network Error"
247
275
  };
248
276
  }
249
277
  }
250
- /**
251
- * Execute SELECT query
252
- */
253
278
  async then(resolve, reject) {
254
279
  try {
255
- const result = await this.request("GET");
280
+ const result = await this.execute();
256
281
  await resolve(result);
257
282
  } catch (err) {
258
- if (reject) {
259
- await reject(err);
260
- }
283
+ if (reject) await reject(err);
261
284
  }
262
285
  }
263
- /**
264
- * Insert row(s)
265
- */
266
- async insert(row) {
286
+ // CUD — 모두 this 반환 → Supabase 스타일 체이닝 지원
287
+ // .insert({ title: '할일' })
288
+ insert(row) {
289
+ this._method = "POST";
267
290
  const rows = Array.isArray(row) ? row : [row];
268
- return this.request("POST", rows.length === 1 ? rows[0] : rows);
291
+ this._body = rows.length === 1 ? rows[0] : rows;
292
+ return this;
269
293
  }
270
- /**
271
- * Update row(s)
272
- */
273
- async update(data) {
274
- return this.request("PATCH", data);
294
+ // .update({ completed: true }).eq('id', 1)
295
+ update(data) {
296
+ this._method = "PATCH";
297
+ this._body = data;
298
+ return this;
275
299
  }
276
- /**
277
- * Upsert row(s)
278
- */
279
- async upsert(row) {
300
+ // .upsert({ id: 1, title: '수정됨' }, { onConflict: 'id' })
301
+ upsert(row, options = {}) {
302
+ this._method = "POST";
303
+ this._isUpsert = true;
304
+ this._onConflict = options.onConflict;
280
305
  const rows = Array.isArray(row) ? row : [row];
281
- this.headers["Prefer"] = "resolution=merge-duplicates,return=representation";
282
- return this.request("POST", rows.length === 1 ? rows[0] : rows);
306
+ this._body = rows.length === 1 ? rows[0] : rows;
307
+ return this;
283
308
  }
284
- /**
285
- * Delete row(s)
286
- */
287
- async delete() {
288
- return this.request("DELETE");
309
+ // .delete().eq('id', 1)
310
+ delete() {
311
+ this._method = "DELETE";
312
+ return this;
289
313
  }
290
314
  };
291
315
 
@@ -585,7 +609,7 @@ var AuraBaseClient = class {
585
609
  async rpc(functionName, params) {
586
610
  const token = this.accessToken || this.anonKey;
587
611
  try {
588
- const response = await fetch(`${this.url}/rpc/v1/${functionName}`, {
612
+ const response = await fetch(`${this.url}/api/${functionName}/`, {
589
613
  method: "POST",
590
614
  headers: {
591
615
  "Content-Type": "application/json",
@@ -759,6 +783,23 @@ var StorageBucket = class {
759
783
 
760
784
  // src/index.ts
761
785
  function createClient(options) {
786
+ if (!options.url) {
787
+ console.warn(
788
+ "[aurabase-js] NEXT_PUBLIC_AURABASE_URL이 설정되지 않았습니다.\n" +
789
+ ".env.local 파일에 다음 항목을 추가하세요:\n\n" +
790
+ " NEXT_PUBLIC_AURABASE_URL=https://your-project.cloudfront.net\n" +
791
+ " NEXT_PUBLIC_AURABASE_ANON_KEY=your-anon-key\n" +
792
+ " NEXT_PUBLIC_AURABASE_SERVICE_ROLE_KEY=your-service-role-key\n"
793
+ );
794
+ }
795
+ if (!options.anonKey) {
796
+ console.warn(
797
+ "[aurabase-js] anonKey가 설정되지 않았습니다.\n" +
798
+ ".env.local 파일에 다음 항목을 추가하세요:\n\n" +
799
+ " NEXT_PUBLIC_AURABASE_ANON_KEY=your-anon-key (클라이언트용)\n" +
800
+ " NEXT_PUBLIC_AURABASE_SERVICE_ROLE_KEY=your-service-role-key (서버/admin용)\n"
801
+ );
802
+ }
762
803
  return new AuraBaseClient(options);
763
804
  }
764
805
  var index_default = createClient;