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.
Files changed (5) hide show
  1. package/index.cjs +129 -88
  2. package/index.d.ts +21 -0
  3. package/index.js +128 -86
  4. package/package.json +1 -1
  5. 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
- // ─── SQL Builder ───────────────────────────────────────────────────────────
139
-
140
- _filterToSQL(f) {
141
- const col = `"${f.col}"`;
142
- switch (f.op) {
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
- _buildSQL() {
165
- let sql = '';
146
+ // ─── Executor ──────────────────────────────────────────────────────────────
166
147
 
167
- if (this._operation === 'select') {
168
- sql = `SELECT ${this._columns} FROM "${this._table}"`;
169
- if (this._filters.length) {
170
- const where = this._filters.map(f => this._filterToSQL(f)).join(' AND ');
171
- sql += ` WHERE ${where}`;
172
- }
173
- if (this._orderCol) {
174
- sql += ` ORDER BY "${this._orderCol}" ${this._orderAsc ? 'ASC' : 'DESC'}`;
175
- }
176
- if (this._limitVal) sql += ` LIMIT ${this._limitVal}`;
177
- if (this._offsetVal) sql += ` OFFSET ${this._offsetVal}`;
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
- } else if (this._operation === 'delete') {
198
- sql = `DELETE FROM "${this._table}"`;
199
- if (this._filters.length) {
200
- const where = this._filters.map(f => this._filterToSQL(f)).join(' AND ');
201
- sql += ` WHERE ${where}`;
202
- }
203
- sql += ` RETURNING *`;
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
- return sql;
191
+ then(resolve, reject) {
192
+ return this._buildAndRun().then(resolve, reject);
207
193
  }
194
+ }
208
195
 
209
- // ─── Executor ──────────────────────────────────────────────────────────────
196
+ // ─── Auth Client ───────────────────────────────────────────────────────────────
210
197
 
211
- async _buildAndRun() {
212
- const sql = this._buildSQL();
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}/query`, {
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({ sql, db_name: this._dbName }),
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
- const { data, error } = json;
226
- if (error) return { data: null, error };
227
- if (this._single) return { data: data[0] || null, error: null };
228
- return { data, error: null };
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
- then(resolve, reject) {
235
- return this._buildAndRun().then(resolve, reject);
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(baseUrl, apiKey, dbName, anonKey) {
243
- this._baseUrl = baseUrl;
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
- return {
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.storage = new StorageClient(this._baseUrl, this._apiKey, this._dbName, this._anonKey);
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(this._baseUrl, this._apiKey, this._dbName, table, this._anonKey);
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
- // ─── SQL Builder ───────────────────────────────────────────────────────────
139
-
140
- _filterToSQL(f) {
141
- const col = `"${f.col}"`;
142
- switch (f.op) {
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
- _buildSQL() {
165
- let sql = '';
146
+ // ─── Executor ──────────────────────────────────────────────────────────────
166
147
 
167
- if (this._operation === 'select') {
168
- sql = `SELECT ${this._columns} FROM "${this._table}"`;
169
- if (this._filters.length) {
170
- const where = this._filters.map(f => this._filterToSQL(f)).join(' AND ');
171
- sql += ` WHERE ${where}`;
172
- }
173
- if (this._orderCol) {
174
- sql += ` ORDER BY "${this._orderCol}" ${this._orderAsc ? 'ASC' : 'DESC'}`;
175
- }
176
- if (this._limitVal) sql += ` LIMIT ${this._limitVal}`;
177
- if (this._offsetVal) sql += ` OFFSET ${this._offsetVal}`;
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
- } else if (this._operation === 'delete') {
198
- sql = `DELETE FROM "${this._table}"`;
199
- if (this._filters.length) {
200
- const where = this._filters.map(f => this._filters.map(f => this._filterToSQL(f)).join(' AND '));
201
- sql += ` WHERE ${where}`;
202
- }
203
- sql += ` RETURNING *`;
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
- return sql;
191
+ then(resolve, reject) {
192
+ return this._buildAndRun().then(resolve, reject);
207
193
  }
194
+ }
208
195
 
209
- // ─── Executor ──────────────────────────────────────────────────────────────
196
+ // ─── Auth Client ───────────────────────────────────────────────────────────────
210
197
 
211
- async _buildAndRun() {
212
- const sql = this._buildSQL();
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}/query`, {
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({ sql, db_name: this._dbName }),
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
- const { data, error } = json;
226
- if (error) return { data: null, error };
227
- if (this._single) return { data: data[0] || null, error: null };
228
- return { data, error: null };
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
- then(resolve, reject) {
235
- return this._buildAndRun().then(resolve, reject);
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(baseUrl, apiKey, dbName, anonKey) {
243
- this._baseUrl = baseUrl;
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
- return {
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.storage = new StorageClient(this._baseUrl, this._apiKey, this._dbName, this._anonKey);
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(this._baseUrl, this._apiKey, this._dbName, table, this._anonKey);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openbase-js",
3
- "version": "0.1.8",
3
+ "version": "0.1.9",
4
4
  "description": "JavaScript client for Openbase — a self-hosted Supabase alternative",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
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
- 'eHUJV8pFtbCh76xiUALc1YMWslyN8QxZgYG5RKySsgE',
12
+ 'umNrqtUU87S-e94PqVZm39Gdyu6FWOul1sPIw0DYKVE',
13
13
  'mydb',
14
14
  {
15
15
  // This identifies your project to the backend
16
- anonKey: 'Yur7U6O5DHzC7Sjtyx_tI7EYM6lpMBO_I9vWWrN4YdQ'
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);