openbase-js 0.1.8 → 0.1.9
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/index.cjs +129 -88
- package/index.d.ts +21 -0
- package/index.js +128 -86
- package/package.json +1 -1
- package/test.js +54 -2
package/index.cjs
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
// ─── Query Builder ────────────────────────────────────────────────────────────
|
|
2
2
|
|
|
3
3
|
class QueryBuilder {
|
|
4
|
-
constructor(baseUrl, apiKey, dbName, table, anonKey) {
|
|
4
|
+
constructor(baseUrl, apiKey, dbName, table, anonKey, sessionToken = null) {
|
|
5
5
|
this._baseUrl = baseUrl;
|
|
6
6
|
this._apiKey = apiKey;
|
|
7
7
|
this._dbName = dbName;
|
|
8
8
|
this._table = table;
|
|
9
9
|
this._anonKey = anonKey;
|
|
10
|
+
this._sessionToken = sessionToken;
|
|
10
11
|
this._filters = [];
|
|
11
12
|
this._columns = '*';
|
|
12
13
|
this._limitVal = null;
|
|
@@ -135,122 +136,154 @@ class QueryBuilder {
|
|
|
135
136
|
return this;
|
|
136
137
|
}
|
|
137
138
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
case 'eq': return typeof f.val === 'string' ? `${col} = '${f.val}'` : `${col} = ${f.val}`;
|
|
144
|
-
case 'gt': return `${col} > ${f.val}`;
|
|
145
|
-
case 'lt': return `${col} < ${f.val}`;
|
|
146
|
-
case 'gte': return `${col} >= ${f.val}`;
|
|
147
|
-
case 'lte': return `${col} <= ${f.val}`;
|
|
148
|
-
case 'like': return `${col} LIKE '${f.val}'`;
|
|
149
|
-
case 'ilike': return `${col} ILIKE '${f.val}'`;
|
|
150
|
-
case 'in': {
|
|
151
|
-
const list = f.val.map(v => typeof v === 'string' ? `'${v}'` : v).join(', ');
|
|
152
|
-
return `${col} IN (${list})`;
|
|
153
|
-
}
|
|
154
|
-
case 'is': {
|
|
155
|
-
if (f.val === null) return `${col} IS NULL`;
|
|
156
|
-
if (f.val === true) return `${col} IS TRUE`;
|
|
157
|
-
if (f.val === false) return `${col} IS FALSE`;
|
|
158
|
-
return `${col} IS NULL`;
|
|
159
|
-
}
|
|
160
|
-
default: return `${col} = '${f.val}'`;
|
|
161
|
-
}
|
|
139
|
+
upsert(data, onConflict = 'id') {
|
|
140
|
+
this._operation = 'upsert';
|
|
141
|
+
this._insertData = data;
|
|
142
|
+
this._upsertOnConflict = onConflict;
|
|
143
|
+
return this;
|
|
162
144
|
}
|
|
163
145
|
|
|
164
|
-
|
|
165
|
-
let sql = '';
|
|
146
|
+
// ─── Executor ──────────────────────────────────────────────────────────────
|
|
166
147
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
if (this.
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
} else if (this._operation === 'insert') {
|
|
180
|
-
const cols = Object.keys(this._insertData).map(c => `"${c}"`).join(', ');
|
|
181
|
-
const vals = Object.values(this._insertData).map(v =>
|
|
182
|
-
typeof v === 'string' ? `'${v.replace(/'/g, "''")}'` : v
|
|
183
|
-
).join(', ');
|
|
184
|
-
sql = `INSERT INTO "${this._table}" (${cols}) VALUES (${vals}) RETURNING *`;
|
|
185
|
-
|
|
186
|
-
} else if (this._operation === 'update') {
|
|
187
|
-
const setClauses = Object.entries(this._updateData).map(([col, val]) =>
|
|
188
|
-
typeof val === 'string' ? `"${col}" = '${val.replace(/'/g, "''")}'` : `"${col}" = ${val}`
|
|
189
|
-
).join(', ');
|
|
190
|
-
sql = `UPDATE "${this._table}" SET ${setClauses}`;
|
|
191
|
-
if (this._filters.length) {
|
|
192
|
-
const where = this._filters.map(f => this._filterToSQL(f)).join(' AND ');
|
|
193
|
-
sql += ` WHERE ${where}`;
|
|
148
|
+
async _buildAndRun() {
|
|
149
|
+
try {
|
|
150
|
+
const headers = {
|
|
151
|
+
'Content-Type': 'application/json',
|
|
152
|
+
'Authorization': `Bearer ${this._apiKey}`,
|
|
153
|
+
'X-Anon-Key': this._anonKey,
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
// If we have an active end-user session, use its token instead of the anon key
|
|
157
|
+
if (this._sessionToken) {
|
|
158
|
+
headers['Authorization'] = `Bearer ${this._sessionToken}`;
|
|
194
159
|
}
|
|
195
|
-
sql += ` RETURNING *`;
|
|
196
160
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
161
|
+
const body = {
|
|
162
|
+
db_name: this._dbName,
|
|
163
|
+
table: this._table,
|
|
164
|
+
operation: this._operation,
|
|
165
|
+
columns: this._columns,
|
|
166
|
+
filters: this._filters,
|
|
167
|
+
limit: this._limitVal,
|
|
168
|
+
offset: this._offsetVal,
|
|
169
|
+
order_col: this._orderCol,
|
|
170
|
+
order_asc: this._orderAsc,
|
|
171
|
+
data: (this._operation === 'insert' || this._operation === 'upsert') ? this._insertData : this._updateData,
|
|
172
|
+
upsert_on_conflict: this._upsertOnConflict
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const res = await fetch(`${this._baseUrl}/sdk/query`, {
|
|
176
|
+
method: 'POST',
|
|
177
|
+
headers,
|
|
178
|
+
body: JSON.stringify(body),
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
const json = await res.json();
|
|
182
|
+
const { data, error } = json;
|
|
183
|
+
if (error) return { data: null, error };
|
|
184
|
+
if (this._single) return { data: data[0] || null, error: null };
|
|
185
|
+
return { data, error: null };
|
|
186
|
+
} catch (err) {
|
|
187
|
+
return { data: null, error: err.message };
|
|
204
188
|
}
|
|
189
|
+
}
|
|
205
190
|
|
|
206
|
-
|
|
191
|
+
then(resolve, reject) {
|
|
192
|
+
return this._buildAndRun().then(resolve, reject);
|
|
207
193
|
}
|
|
194
|
+
}
|
|
208
195
|
|
|
209
|
-
|
|
196
|
+
// ─── Auth Client ───────────────────────────────────────────────────────────────
|
|
210
197
|
|
|
211
|
-
|
|
212
|
-
|
|
198
|
+
class AuthClient {
|
|
199
|
+
constructor(client) {
|
|
200
|
+
this._client = client;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async signUp(email, password) {
|
|
213
204
|
try {
|
|
214
|
-
const res = await fetch(`${this._baseUrl}/
|
|
205
|
+
const res = await fetch(`${this._client._baseUrl}/sdk/auth/signup`, {
|
|
215
206
|
method: 'POST',
|
|
216
207
|
headers: {
|
|
217
208
|
'Content-Type': 'application/json',
|
|
218
|
-
'Authorization': `Bearer ${this._apiKey}
|
|
219
|
-
'X-Anon-Key': this._anonKey,
|
|
209
|
+
'Authorization': `Bearer ${this._client._apiKey}`
|
|
220
210
|
},
|
|
221
|
-
body: JSON.stringify({
|
|
211
|
+
body: JSON.stringify({
|
|
212
|
+
db_name: this._client._dbName,
|
|
213
|
+
email,
|
|
214
|
+
password
|
|
215
|
+
}),
|
|
222
216
|
});
|
|
217
|
+
const json = await res.json();
|
|
218
|
+
if (json.data && json.data.session) {
|
|
219
|
+
this._client._session = json.data.session;
|
|
220
|
+
}
|
|
221
|
+
return json;
|
|
222
|
+
} catch (err) {
|
|
223
|
+
return { data: null, error: err.message };
|
|
224
|
+
}
|
|
225
|
+
}
|
|
223
226
|
|
|
227
|
+
async signIn(email, password) {
|
|
228
|
+
try {
|
|
229
|
+
const res = await fetch(`${this._client._baseUrl}/sdk/auth/signin`, {
|
|
230
|
+
method: 'POST',
|
|
231
|
+
headers: {
|
|
232
|
+
'Content-Type': 'application/json',
|
|
233
|
+
'Authorization': `Bearer ${this._client._apiKey}`
|
|
234
|
+
},
|
|
235
|
+
body: JSON.stringify({
|
|
236
|
+
db_name: this._client._dbName,
|
|
237
|
+
email,
|
|
238
|
+
password
|
|
239
|
+
}),
|
|
240
|
+
});
|
|
224
241
|
const json = await res.json();
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
return
|
|
242
|
+
if (json.data && json.data.session) {
|
|
243
|
+
this._client._session = json.data.session;
|
|
244
|
+
}
|
|
245
|
+
return json;
|
|
229
246
|
} catch (err) {
|
|
230
247
|
return { data: null, error: err.message };
|
|
231
248
|
}
|
|
232
249
|
}
|
|
233
250
|
|
|
234
|
-
|
|
235
|
-
|
|
251
|
+
async getSession() {
|
|
252
|
+
if (!this._client._session) return { data: null, error: null };
|
|
253
|
+
try {
|
|
254
|
+
const res = await fetch(`${this._client._baseUrl}/sdk/auth/session`, {
|
|
255
|
+
headers: {
|
|
256
|
+
'Authorization': `Bearer ${this._client._session.access_token}`
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
return await res.json();
|
|
260
|
+
} catch (err) {
|
|
261
|
+
return { data: null, error: err.message };
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
signOut() {
|
|
266
|
+
this._client._session = null;
|
|
267
|
+
return { data: { ok: true }, error: null };
|
|
236
268
|
}
|
|
237
269
|
}
|
|
238
270
|
|
|
239
271
|
// ─── Storage Client ───────────────────────────────────────────────────────────
|
|
240
272
|
|
|
241
273
|
class StorageClient {
|
|
242
|
-
constructor(
|
|
243
|
-
this.
|
|
244
|
-
this._apiKey = apiKey;
|
|
245
|
-
this._dbName = dbName;
|
|
246
|
-
this._anonKey = anonKey;
|
|
274
|
+
constructor(client) {
|
|
275
|
+
this._client = client;
|
|
247
276
|
}
|
|
248
277
|
|
|
249
278
|
_headers() {
|
|
250
|
-
|
|
251
|
-
'Authorization': `Bearer ${this._apiKey}`,
|
|
252
|
-
'X-Anon-Key': this._anonKey
|
|
279
|
+
const headers = {
|
|
280
|
+
'Authorization': `Bearer ${this._client._apiKey}`,
|
|
281
|
+
'X-Anon-Key': this._client._anonKey
|
|
253
282
|
};
|
|
283
|
+
if (this._client._session) {
|
|
284
|
+
headers['Authorization'] = `Bearer ${this._client._session.access_token}`;
|
|
285
|
+
}
|
|
286
|
+
return headers;
|
|
254
287
|
}
|
|
255
288
|
|
|
256
289
|
// Detect if we're running in Node.js
|
|
@@ -403,12 +436,21 @@ class OpenbaseClient {
|
|
|
403
436
|
this._baseUrl = baseUrl.replace(/\/$/, '');
|
|
404
437
|
this._apiKey = apiKey;
|
|
405
438
|
this._dbName = dbName;
|
|
406
|
-
this._anonKey = options.anonKey || apiKey;
|
|
407
|
-
this.
|
|
439
|
+
this._anonKey = options.anonKey || apiKey; // Default to apiKey if anonKey not provided
|
|
440
|
+
this._session = null;
|
|
441
|
+
this.storage = new StorageClient(this);
|
|
442
|
+
this.auth = new AuthClient(this);
|
|
408
443
|
}
|
|
409
444
|
|
|
410
445
|
from(table) {
|
|
411
|
-
return new QueryBuilder(
|
|
446
|
+
return new QueryBuilder(
|
|
447
|
+
this._baseUrl,
|
|
448
|
+
this._apiKey,
|
|
449
|
+
this._dbName,
|
|
450
|
+
table,
|
|
451
|
+
this._anonKey,
|
|
452
|
+
this._session ? this._session.access_token : null
|
|
453
|
+
);
|
|
412
454
|
}
|
|
413
455
|
}
|
|
414
456
|
|
|
@@ -428,4 +470,3 @@ if (typeof module !== 'undefined') {
|
|
|
428
470
|
if (typeof window !== 'undefined') {
|
|
429
471
|
window.openbase = { createClient };
|
|
430
472
|
}
|
|
431
|
-
module.exports = { createClient };
|
package/index.d.ts
CHANGED
|
@@ -14,6 +14,23 @@ export interface OpenbaseClientOptions {
|
|
|
14
14
|
anonKey?: string;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
export interface User {
|
|
18
|
+
id: string;
|
|
19
|
+
email: string;
|
|
20
|
+
created_at: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface Session {
|
|
24
|
+
access_token: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export declare class AuthClient {
|
|
28
|
+
signUp(email: string, password: string): Promise<OpenbaseResponse<{ user: User, session: Session }>>;
|
|
29
|
+
signIn(email: string, password: string): Promise<OpenbaseResponse<{ user: User, session: Session }>>;
|
|
30
|
+
getSession(): Promise<OpenbaseResponse<{ user: User }>>;
|
|
31
|
+
signOut(): OpenbaseResponse<{ ok: boolean }>;
|
|
32
|
+
}
|
|
33
|
+
|
|
17
34
|
export declare class QueryBuilder<T = Record<string, unknown>> {
|
|
18
35
|
// Column selection
|
|
19
36
|
select(columns?: string): this;
|
|
@@ -39,6 +56,7 @@ export declare class QueryBuilder<T = Record<string, unknown>> {
|
|
|
39
56
|
insert(data: Partial<T>): this;
|
|
40
57
|
update(data: Partial<T>): this;
|
|
41
58
|
delete(): this;
|
|
59
|
+
upsert(data: Partial<T>, onConflict?: string): this;
|
|
42
60
|
|
|
43
61
|
then<R>(
|
|
44
62
|
resolve: (value: OpenbaseResponse<T[]>) => R,
|
|
@@ -99,6 +117,9 @@ export declare class OpenbaseClient {
|
|
|
99
117
|
/** Storage client for file upload, listing, URLs, and deletion */
|
|
100
118
|
storage: StorageClient;
|
|
101
119
|
|
|
120
|
+
/** Auth client for user registration, login, and session management */
|
|
121
|
+
auth: AuthClient;
|
|
122
|
+
|
|
102
123
|
/** Query builder for database operations */
|
|
103
124
|
from<T = Record<string, unknown>>(table: string): QueryBuilder<T>;
|
|
104
125
|
}
|
package/index.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
// ─── Query Builder ────────────────────────────────────────────────────────────
|
|
2
2
|
|
|
3
3
|
class QueryBuilder {
|
|
4
|
-
constructor(baseUrl, apiKey, dbName, table, anonKey) {
|
|
4
|
+
constructor(baseUrl, apiKey, dbName, table, anonKey, sessionToken = null) {
|
|
5
5
|
this._baseUrl = baseUrl;
|
|
6
6
|
this._apiKey = apiKey;
|
|
7
7
|
this._dbName = dbName;
|
|
8
8
|
this._table = table;
|
|
9
9
|
this._anonKey = anonKey;
|
|
10
|
+
this._sessionToken = sessionToken;
|
|
10
11
|
this._filters = [];
|
|
11
12
|
this._columns = '*';
|
|
12
13
|
this._limitVal = null;
|
|
@@ -135,122 +136,154 @@ class QueryBuilder {
|
|
|
135
136
|
return this;
|
|
136
137
|
}
|
|
137
138
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
case 'eq': return typeof f.val === 'string' ? `${col} = '${f.val}'` : `${col} = ${f.val}`;
|
|
144
|
-
case 'gt': return `${col} > ${f.val}`;
|
|
145
|
-
case 'lt': return `${col} < ${f.val}`;
|
|
146
|
-
case 'gte': return `${col} >= ${f.val}`;
|
|
147
|
-
case 'lte': return `${col} <= ${f.val}`;
|
|
148
|
-
case 'like': return `${col} LIKE '${f.val}'`;
|
|
149
|
-
case 'ilike': return `${col} ILIKE '${f.val}'`;
|
|
150
|
-
case 'in': {
|
|
151
|
-
const list = f.val.map(v => typeof v === 'string' ? `'${v}'` : v).join(', ');
|
|
152
|
-
return `${col} IN (${list})`;
|
|
153
|
-
}
|
|
154
|
-
case 'is': {
|
|
155
|
-
if (f.val === null) return `${col} IS NULL`;
|
|
156
|
-
if (f.val === true) return `${col} IS TRUE`;
|
|
157
|
-
if (f.val === false) return `${col} IS FALSE`;
|
|
158
|
-
return `${col} IS NULL`;
|
|
159
|
-
}
|
|
160
|
-
default: return `${col} = '${f.val}'`;
|
|
161
|
-
}
|
|
139
|
+
upsert(data, onConflict = 'id') {
|
|
140
|
+
this._operation = 'upsert';
|
|
141
|
+
this._insertData = data;
|
|
142
|
+
this._upsertOnConflict = onConflict;
|
|
143
|
+
return this;
|
|
162
144
|
}
|
|
163
145
|
|
|
164
|
-
|
|
165
|
-
let sql = '';
|
|
146
|
+
// ─── Executor ──────────────────────────────────────────────────────────────
|
|
166
147
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
if (this.
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
} else if (this._operation === 'insert') {
|
|
180
|
-
const cols = Object.keys(this._insertData).map(c => `"${c}"`).join(', ');
|
|
181
|
-
const vals = Object.values(this._insertData).map(v =>
|
|
182
|
-
typeof v === 'string' ? `'${v.replace(/'/g, "''")}'` : v
|
|
183
|
-
).join(', ');
|
|
184
|
-
sql = `INSERT INTO "${this._table}" (${cols}) VALUES (${vals}) RETURNING *`;
|
|
185
|
-
|
|
186
|
-
} else if (this._operation === 'update') {
|
|
187
|
-
const setClauses = Object.entries(this._updateData).map(([col, val]) =>
|
|
188
|
-
typeof val === 'string' ? `"${col}" = '${val.replace(/'/g, "''")}'` : `"${col}" = ${val}`
|
|
189
|
-
).join(', ');
|
|
190
|
-
sql = `UPDATE "${this._table}" SET ${setClauses}`;
|
|
191
|
-
if (this._filters.length) {
|
|
192
|
-
const where = this._filters.map(f => this._filterToSQL(f)).join(' AND ');
|
|
193
|
-
sql += ` WHERE ${where}`;
|
|
148
|
+
async _buildAndRun() {
|
|
149
|
+
try {
|
|
150
|
+
const headers = {
|
|
151
|
+
'Content-Type': 'application/json',
|
|
152
|
+
'Authorization': `Bearer ${this._apiKey}`,
|
|
153
|
+
'X-Anon-Key': this._anonKey,
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
// If we have an active end-user session, use its token instead of the anon key
|
|
157
|
+
if (this._sessionToken) {
|
|
158
|
+
headers['Authorization'] = `Bearer ${this._sessionToken}`;
|
|
194
159
|
}
|
|
195
|
-
sql += ` RETURNING *`;
|
|
196
160
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
161
|
+
const body = {
|
|
162
|
+
db_name: this._dbName,
|
|
163
|
+
table: this._table,
|
|
164
|
+
operation: this._operation,
|
|
165
|
+
columns: this._columns,
|
|
166
|
+
filters: this._filters,
|
|
167
|
+
limit: this._limitVal,
|
|
168
|
+
offset: this._offsetVal,
|
|
169
|
+
order_col: this._orderCol,
|
|
170
|
+
order_asc: this._orderAsc,
|
|
171
|
+
data: (this._operation === 'insert' || this._operation === 'upsert') ? this._insertData : this._updateData,
|
|
172
|
+
upsert_on_conflict: this._upsertOnConflict
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const res = await fetch(`${this._baseUrl}/sdk/query`, {
|
|
176
|
+
method: 'POST',
|
|
177
|
+
headers,
|
|
178
|
+
body: JSON.stringify(body),
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
const json = await res.json();
|
|
182
|
+
const { data, error } = json;
|
|
183
|
+
if (error) return { data: null, error };
|
|
184
|
+
if (this._single) return { data: data[0] || null, error: null };
|
|
185
|
+
return { data, error: null };
|
|
186
|
+
} catch (err) {
|
|
187
|
+
return { data: null, error: err.message };
|
|
204
188
|
}
|
|
189
|
+
}
|
|
205
190
|
|
|
206
|
-
|
|
191
|
+
then(resolve, reject) {
|
|
192
|
+
return this._buildAndRun().then(resolve, reject);
|
|
207
193
|
}
|
|
194
|
+
}
|
|
208
195
|
|
|
209
|
-
|
|
196
|
+
// ─── Auth Client ───────────────────────────────────────────────────────────────
|
|
210
197
|
|
|
211
|
-
|
|
212
|
-
|
|
198
|
+
class AuthClient {
|
|
199
|
+
constructor(client) {
|
|
200
|
+
this._client = client;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
async signUp(email, password) {
|
|
213
204
|
try {
|
|
214
|
-
const res = await fetch(`${this._baseUrl}/
|
|
205
|
+
const res = await fetch(`${this._client._baseUrl}/sdk/auth/signup`, {
|
|
215
206
|
method: 'POST',
|
|
216
207
|
headers: {
|
|
217
208
|
'Content-Type': 'application/json',
|
|
218
|
-
'Authorization': `Bearer ${this._apiKey}
|
|
219
|
-
'X-Anon-Key': this._anonKey,
|
|
209
|
+
'Authorization': `Bearer ${this._client._apiKey}`
|
|
220
210
|
},
|
|
221
|
-
body: JSON.stringify({
|
|
211
|
+
body: JSON.stringify({
|
|
212
|
+
db_name: this._client._dbName,
|
|
213
|
+
email,
|
|
214
|
+
password
|
|
215
|
+
}),
|
|
222
216
|
});
|
|
217
|
+
const json = await res.json();
|
|
218
|
+
if (json.data && json.data.session) {
|
|
219
|
+
this._client._session = json.data.session;
|
|
220
|
+
}
|
|
221
|
+
return json;
|
|
222
|
+
} catch (err) {
|
|
223
|
+
return { data: null, error: err.message };
|
|
224
|
+
}
|
|
225
|
+
}
|
|
223
226
|
|
|
227
|
+
async signIn(email, password) {
|
|
228
|
+
try {
|
|
229
|
+
const res = await fetch(`${this._client._baseUrl}/sdk/auth/signin`, {
|
|
230
|
+
method: 'POST',
|
|
231
|
+
headers: {
|
|
232
|
+
'Content-Type': 'application/json',
|
|
233
|
+
'Authorization': `Bearer ${this._client._apiKey}`
|
|
234
|
+
},
|
|
235
|
+
body: JSON.stringify({
|
|
236
|
+
db_name: this._client._dbName,
|
|
237
|
+
email,
|
|
238
|
+
password
|
|
239
|
+
}),
|
|
240
|
+
});
|
|
224
241
|
const json = await res.json();
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
return
|
|
242
|
+
if (json.data && json.data.session) {
|
|
243
|
+
this._client._session = json.data.session;
|
|
244
|
+
}
|
|
245
|
+
return json;
|
|
229
246
|
} catch (err) {
|
|
230
247
|
return { data: null, error: err.message };
|
|
231
248
|
}
|
|
232
249
|
}
|
|
233
250
|
|
|
234
|
-
|
|
235
|
-
|
|
251
|
+
async getSession() {
|
|
252
|
+
if (!this._client._session) return { data: null, error: null };
|
|
253
|
+
try {
|
|
254
|
+
const res = await fetch(`${this._client._baseUrl}/sdk/auth/session`, {
|
|
255
|
+
headers: {
|
|
256
|
+
'Authorization': `Bearer ${this._client._session.access_token}`
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
return await res.json();
|
|
260
|
+
} catch (err) {
|
|
261
|
+
return { data: null, error: err.message };
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
signOut() {
|
|
266
|
+
this._client._session = null;
|
|
267
|
+
return { data: { ok: true }, error: null };
|
|
236
268
|
}
|
|
237
269
|
}
|
|
238
270
|
|
|
239
271
|
// ─── Storage Client ───────────────────────────────────────────────────────────
|
|
240
272
|
|
|
241
273
|
class StorageClient {
|
|
242
|
-
constructor(
|
|
243
|
-
this.
|
|
244
|
-
this._apiKey = apiKey;
|
|
245
|
-
this._dbName = dbName;
|
|
246
|
-
this._anonKey = anonKey;
|
|
274
|
+
constructor(client) {
|
|
275
|
+
this._client = client;
|
|
247
276
|
}
|
|
248
277
|
|
|
249
278
|
_headers() {
|
|
250
|
-
|
|
251
|
-
'Authorization': `Bearer ${this._apiKey}`,
|
|
252
|
-
'X-Anon-Key': this._anonKey
|
|
279
|
+
const headers = {
|
|
280
|
+
'Authorization': `Bearer ${this._client._apiKey}`,
|
|
281
|
+
'X-Anon-Key': this._client._anonKey
|
|
253
282
|
};
|
|
283
|
+
if (this._client._session) {
|
|
284
|
+
headers['Authorization'] = `Bearer ${this._client._session.access_token}`;
|
|
285
|
+
}
|
|
286
|
+
return headers;
|
|
254
287
|
}
|
|
255
288
|
|
|
256
289
|
// Detect if we're running in Node.js
|
|
@@ -404,11 +437,20 @@ class OpenbaseClient {
|
|
|
404
437
|
this._apiKey = apiKey;
|
|
405
438
|
this._dbName = dbName;
|
|
406
439
|
this._anonKey = options.anonKey || apiKey; // Default to apiKey if anonKey not provided
|
|
407
|
-
this.
|
|
440
|
+
this._session = null;
|
|
441
|
+
this.storage = new StorageClient(this);
|
|
442
|
+
this.auth = new AuthClient(this);
|
|
408
443
|
}
|
|
409
444
|
|
|
410
445
|
from(table) {
|
|
411
|
-
return new QueryBuilder(
|
|
446
|
+
return new QueryBuilder(
|
|
447
|
+
this._baseUrl,
|
|
448
|
+
this._apiKey,
|
|
449
|
+
this._dbName,
|
|
450
|
+
table,
|
|
451
|
+
this._anonKey,
|
|
452
|
+
this._session ? this._session.access_token : null
|
|
453
|
+
);
|
|
412
454
|
}
|
|
413
455
|
}
|
|
414
456
|
|
package/package.json
CHANGED
package/test.js
CHANGED
|
@@ -9,11 +9,11 @@ const { createClient } = require('./index');
|
|
|
9
9
|
const client = createClient(
|
|
10
10
|
'http://localhost:3003',
|
|
11
11
|
// In production, this would be your secret JWT
|
|
12
|
-
'
|
|
12
|
+
'umNrqtUU87S-e94PqVZm39Gdyu6FWOul1sPIw0DYKVE',
|
|
13
13
|
'mydb',
|
|
14
14
|
{
|
|
15
15
|
// This identifies your project to the backend
|
|
16
|
-
anonKey: '
|
|
16
|
+
anonKey: 'umNrqtUU87S-e94PqVZm39Gdyu6FWOul1sPIw0DYKVE'
|
|
17
17
|
}
|
|
18
18
|
);
|
|
19
19
|
|
|
@@ -110,6 +110,58 @@ async function testOperators() {
|
|
|
110
110
|
console.log('chained:', chained?.map(r => `${r.firstname} (${r.salary})`));
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
async function testUpsert() {
|
|
114
|
+
console.log('\n── UPSERT ──────────────────────────');
|
|
115
|
+
// First, ensure record 999 is gone
|
|
116
|
+
await client.from('employees').delete().eq('employeeid', 999);
|
|
117
|
+
|
|
118
|
+
console.log('1. Upserting new record...');
|
|
119
|
+
const { data: upserted1 } = await client.from('employees').upsert({
|
|
120
|
+
employeeid: 999,
|
|
121
|
+
firstname: 'Upsert',
|
|
122
|
+
lastname: 'Test',
|
|
123
|
+
salary: 77777,
|
|
124
|
+
}, 'employeeid');
|
|
125
|
+
console.log('upserted1:', upserted1);
|
|
126
|
+
|
|
127
|
+
console.log('2. Upserting same record with new salary...');
|
|
128
|
+
const { data: upserted2 } = await client.from('employees').upsert({
|
|
129
|
+
employeeid: 999,
|
|
130
|
+
firstname: 'Upsert',
|
|
131
|
+
lastname: 'Test',
|
|
132
|
+
salary: 88888,
|
|
133
|
+
}, 'employeeid');
|
|
134
|
+
console.log('upserted2 (should be 88888):', upserted2);
|
|
135
|
+
|
|
136
|
+
// Clean up
|
|
137
|
+
await client.from('employees').delete().eq('employeeid', 999);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function testAuth() {
|
|
141
|
+
console.log('\n── AUTH ────────────────────────────');
|
|
142
|
+
const email = `test_${Math.random().toString(36).substring(7)}@example.com`;
|
|
143
|
+
const password = 'password123';
|
|
144
|
+
|
|
145
|
+
console.log('1. Signing up...');
|
|
146
|
+
const { data: signup, error: err1 } = await client.auth.signUp(email, password);
|
|
147
|
+
console.log('signup:', signup?.user?.email, err1);
|
|
148
|
+
|
|
149
|
+
console.log('2. Signing in...');
|
|
150
|
+
const { data: signin, error: err2 } = await client.auth.signIn(email, password);
|
|
151
|
+
console.log('signin:', signin?.session ? 'SUCCESS' : 'FAILED', err2);
|
|
152
|
+
|
|
153
|
+
console.log('3. Getting session...');
|
|
154
|
+
const { data: session, error: err3 } = await client.auth.getSession();
|
|
155
|
+
console.log('session user:', session?.user?.email, err3);
|
|
156
|
+
|
|
157
|
+
console.log('4. Signing out...');
|
|
158
|
+
client.auth.signOut();
|
|
159
|
+
const { data: sessionAfter } = await client.auth.getSession();
|
|
160
|
+
console.log('session after signout:', sessionAfter);
|
|
161
|
+
}
|
|
162
|
+
|
|
113
163
|
test()
|
|
114
164
|
.then(() => testOperators())
|
|
165
|
+
.then(() => testUpsert())
|
|
166
|
+
.then(() => testAuth())
|
|
115
167
|
.catch(console.error);
|