openbase-js 0.1.7 → 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/README.md ADDED
@@ -0,0 +1,182 @@
1
+ # OpenBase JS SDK
2
+
3
+ The OpenBase JS SDK provides a lightweight, expressive interface for interacting with your OpenBase projects. It handles database queries (PostgreSQL), file storage (S3/MinIO), and realtime subscriptions via a simple, Supabase-like API.
4
+
5
+ ## 📦 Installation
6
+
7
+ ```bash
8
+ npm install openbase-js
9
+ # or
10
+ yarn add openbase-js
11
+ ```
12
+
13
+ > [!NOTE]
14
+ > If you are using Node.js for file uploads, you will also need `form-data`:
15
+ > `npm install form-data`
16
+
17
+ ## 🚀 Quick Start
18
+
19
+ Initialize the client with your project URL, API key, and database name.
20
+
21
+ ```javascript
22
+ const { createClient } = require('openbase-js');
23
+
24
+ const openbase = createClient(
25
+ 'http://localhost:3003', // The URL where your OpenBase backend is running
26
+ 'your-anon-key',
27
+ 'your-db-name'
28
+ );
29
+
30
+ // Fetch data
31
+ async function getLeads() {
32
+ const { data, error } = await openbase
33
+ .from('leads')
34
+ .select('*')
35
+ .eq('status', 'active');
36
+
37
+ if (error) console.error(error);
38
+ else console.log(data);
39
+ }
40
+ ```
41
+
42
+ ---
43
+
44
+ ## 📊 Database Operations
45
+
46
+ OpenBase uses a chainable query builder for database operations.
47
+
48
+ ### Basic CRUD
49
+
50
+ #### Select
51
+ ```javascript
52
+ const { data } = await openbase.from('users').select('id, username');
53
+ ```
54
+
55
+ #### Insert
56
+ ```javascript
57
+ const { data } = await openbase.from('users').insert({
58
+ username: 'jdoe',
59
+ email: 'john@example.com'
60
+ });
61
+ ```
62
+
63
+ #### Update
64
+ ```javascript
65
+ const { data } = await openbase.from('users')
66
+ .update({ email: 'newemail@example.com' })
67
+ .eq('id', 1);
68
+ ```
69
+
70
+ #### Delete
71
+ ```javascript
72
+ const { data } = await openbase.from('users')
73
+ .delete()
74
+ .eq('id', 1);
75
+ ```
76
+
77
+ ### 🔍 Detailed Operator Guide
78
+
79
+ Filtering is done using chainable operator methods.
80
+
81
+ | Operator | Method | Description | Example |
82
+ | :--- | :--- | :--- | :--- |
83
+ | `==` | `.eq(col, val)` | Equal to | `.eq('name', 'Alice')` |
84
+ | `>` | `.gt(col, val)` | Greater than | `.gt('age', 21)` |
85
+ | `<` | `.lt(col, val)` | Less than | `.lt('score', 50)` |
86
+ | `>=` | `.gte(col, val)` | Greater than or equal to | `.gte('price', 100)` |
87
+ | `<=` | `.lte(col, val)` | Less than or equal to | `.lte('stock', 5)` |
88
+ | `LIKE` | `.like(col, pattern)` | Case-sensitive pattern match | `.like('name', '%son%')` |
89
+ | `ILIKE` | `.ilike(col, pattern)`| Case-insensitive pattern match | `.ilike('name', '%SON%')` |
90
+ | `IN` | `.in(col, array)` | Contained in array | `.in('role', ['admin', 'mod'])` |
91
+ | `IS` | `.is(col, value)` | Check for `null` or `boolean` | `.is('deleted_at', null)` |
92
+
93
+ ### 🛠️ Modifiers
94
+
95
+ * **`.order(column, { ascending: boolean })`**: Sort the results.
96
+ * **`.limit(count)`**: Limit the number of rows returned.
97
+ * **`.range(from, to)`**: Pagination (0-indexed, inclusive).
98
+ * **`.single()`**: Returns a single object instead of an array.
99
+
100
+ ```javascript
101
+ const { data } = await openbase
102
+ .from('posts')
103
+ .select('*')
104
+ .gt('likes', 10)
105
+ .order('created_at', { ascending: false })
106
+ .range(0, 9) // First 10 items
107
+ ```
108
+
109
+ ---
110
+
111
+ ## 📁 Storage
112
+
113
+ OpenBase provides built-in S3-compatible storage.
114
+
115
+ ### Uploading Files
116
+
117
+ In the browser, you can pass a `File` or `Blob`. In Node.js, you can pass a file path, `Buffer`, or `ReadStream`.
118
+
119
+ ```javascript
120
+ // Browser
121
+ const file = event.target.files[0];
122
+ const { data, error } = await openbase.storage.upload(file, 'profile.jpg');
123
+
124
+ // Node.js (requires form-data)
125
+ const { data, error } = await openbase.storage.upload('./local-image.png', 'remote-name.png');
126
+ ```
127
+
128
+ ### Listing & Managing Files
129
+
130
+ ```javascript
131
+ // List all files in bucket
132
+ const { data: files } = await openbase.storage.list();
133
+
134
+ // Get a public presigned URL (valid for 7 days)
135
+ const { data: { url } } = await openbase.storage.getUrl('profile.jpg');
136
+
137
+ // Remove a file
138
+ await openbase.storage.remove('profile.jpg');
139
+ ```
140
+
141
+ ---
142
+
143
+ ## 🔄 Realtime
144
+
145
+ Subscribe to database changes using WebSockets.
146
+
147
+ ```javascript
148
+ const subscription = openbase
149
+ .from('messages')
150
+ .on('INSERT', (payload) => {
151
+ console.log('New message:', payload.record);
152
+ })
153
+ .subscribe();
154
+
155
+ // To stop listening
156
+ // subscription.unsubscribe();
157
+ ```
158
+
159
+ ---
160
+
161
+ ## ⌨️ TypeScript Support
162
+
163
+ The SDK is written with full TypeScript support. Types are automatically included.
164
+
165
+ ```typescript
166
+ interface Lead {
167
+ id: number;
168
+ company_name: string;
169
+ }
170
+
171
+ const { data } = await openbase.from<Lead>('leads').select('*');
172
+ // data is typed as Lead[] | null
173
+ ```
174
+
175
+ ---
176
+
177
+ ## 🧪 Testing
178
+
179
+ To run the local tests:
180
+ 1. Ensure the OpenBase backend is running.
181
+ 2. Update the credentials in `test.js`.
182
+ 3. Run: `node test.js`
package/index.cjs CHANGED
@@ -1,11 +1,13 @@
1
1
  // ─── Query Builder ────────────────────────────────────────────────────────────
2
2
 
3
3
  class QueryBuilder {
4
- constructor(baseUrl, apiKey, dbName, table) {
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
+ this._anonKey = anonKey;
10
+ this._sessionToken = sessionToken;
9
11
  this._filters = [];
10
12
  this._columns = '*';
11
13
  this._limitVal = null;
@@ -27,7 +29,7 @@ class QueryBuilder {
27
29
  on(event, callback) {
28
30
  if (!this._realtimeSub) {
29
31
  this._realtimeSub = new RealtimeSubscription(
30
- this._baseUrl, this._apiKey, this._dbName, this._table
32
+ this._baseUrl, this._apiKey, this._dbName, this._table, this._anonKey
31
33
  );
32
34
  }
33
35
  this._realtimeSub.on(event, callback);
@@ -37,7 +39,7 @@ class QueryBuilder {
37
39
  subscribe() {
38
40
  if (!this._realtimeSub) {
39
41
  this._realtimeSub = new RealtimeSubscription(
40
- this._baseUrl, this._apiKey, this._dbName, this._table
42
+ this._baseUrl, this._apiKey, this._dbName, this._table, this._anonKey
41
43
  );
42
44
  }
43
45
  return this._realtimeSub.subscribe();
@@ -134,117 +136,154 @@ class QueryBuilder {
134
136
  return this;
135
137
  }
136
138
 
137
- // ─── SQL Builder ───────────────────────────────────────────────────────────
138
-
139
- _filterToSQL(f) {
140
- const col = `"${f.col}"`;
141
- switch (f.op) {
142
- case 'eq': return typeof f.val === 'string' ? `${col} = '${f.val}'` : `${col} = ${f.val}`;
143
- case 'gt': return `${col} > ${f.val}`;
144
- case 'lt': return `${col} < ${f.val}`;
145
- case 'gte': return `${col} >= ${f.val}`;
146
- case 'lte': return `${col} <= ${f.val}`;
147
- case 'like': return `${col} LIKE '${f.val}'`;
148
- case 'ilike': return `${col} ILIKE '${f.val}'`;
149
- case 'in': {
150
- const list = f.val.map(v => typeof v === 'string' ? `'${v}'` : v).join(', ');
151
- return `${col} IN (${list})`;
152
- }
153
- case 'is': {
154
- if (f.val === null) return `${col} IS NULL`;
155
- if (f.val === true) return `${col} IS TRUE`;
156
- if (f.val === false) return `${col} IS FALSE`;
157
- return `${col} IS NULL`;
158
- }
159
- default: return `${col} = '${f.val}'`;
160
- }
139
+ upsert(data, onConflict = 'id') {
140
+ this._operation = 'upsert';
141
+ this._insertData = data;
142
+ this._upsertOnConflict = onConflict;
143
+ return this;
161
144
  }
162
145
 
163
- _buildSQL() {
164
- let sql = '';
146
+ // ─── Executor ──────────────────────────────────────────────────────────────
165
147
 
166
- if (this._operation === 'select') {
167
- sql = `SELECT ${this._columns} FROM "${this._table}"`;
168
- if (this._filters.length) {
169
- const where = this._filters.map(f => this._filterToSQL(f)).join(' AND ');
170
- sql += ` WHERE ${where}`;
171
- }
172
- if (this._orderCol) {
173
- sql += ` ORDER BY "${this._orderCol}" ${this._orderAsc ? 'ASC' : 'DESC'}`;
174
- }
175
- if (this._limitVal) sql += ` LIMIT ${this._limitVal}`;
176
- if (this._offsetVal) sql += ` OFFSET ${this._offsetVal}`;
177
-
178
- } else if (this._operation === 'insert') {
179
- const cols = Object.keys(this._insertData).map(c => `"${c}"`).join(', ');
180
- const vals = Object.values(this._insertData).map(v =>
181
- typeof v === 'string' ? `'${v.replace(/'/g, "''")}'` : v
182
- ).join(', ');
183
- sql = `INSERT INTO "${this._table}" (${cols}) VALUES (${vals}) RETURNING *`;
184
-
185
- } else if (this._operation === 'update') {
186
- const setClauses = Object.entries(this._updateData).map(([col, val]) =>
187
- typeof val === 'string' ? `"${col}" = '${val.replace(/'/g, "''")}'` : `"${col}" = ${val}`
188
- ).join(', ');
189
- sql = `UPDATE "${this._table}" SET ${setClauses}`;
190
- if (this._filters.length) {
191
- const where = this._filters.map(f => this._filterToSQL(f)).join(' AND ');
192
- 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}`;
193
159
  }
194
- sql += ` RETURNING *`;
195
160
 
196
- } else if (this._operation === 'delete') {
197
- sql = `DELETE FROM "${this._table}"`;
198
- if (this._filters.length) {
199
- const where = this._filters.map(f => this._filterToSQL(f)).join(' AND ');
200
- sql += ` WHERE ${where}`;
201
- }
202
- 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 };
203
188
  }
189
+ }
204
190
 
205
- return sql;
191
+ then(resolve, reject) {
192
+ return this._buildAndRun().then(resolve, reject);
206
193
  }
194
+ }
207
195
 
208
- // ─── Executor ──────────────────────────────────────────────────────────────
196
+ // ─── Auth Client ───────────────────────────────────────────────────────────────
209
197
 
210
- async _buildAndRun() {
211
- const sql = this._buildSQL();
198
+ class AuthClient {
199
+ constructor(client) {
200
+ this._client = client;
201
+ }
202
+
203
+ async signUp(email, password) {
212
204
  try {
213
- const res = await fetch(`${this._baseUrl}/query`, {
205
+ const res = await fetch(`${this._client._baseUrl}/sdk/auth/signup`, {
214
206
  method: 'POST',
215
207
  headers: {
216
208
  'Content-Type': 'application/json',
217
- 'Authorization': `Bearer ${this._apiKey}`,
209
+ 'Authorization': `Bearer ${this._client._apiKey}`
218
210
  },
219
- body: JSON.stringify({ sql, db_name: this._dbName }),
211
+ body: JSON.stringify({
212
+ db_name: this._client._dbName,
213
+ email,
214
+ password
215
+ }),
220
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
+ }
221
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
+ });
222
241
  const json = await res.json();
223
- const { data, error } = json;
224
- if (error) return { data: null, error };
225
- if (this._single) return { data: data[0] || null, error: null };
226
- return { data, error: null };
242
+ if (json.data && json.data.session) {
243
+ this._client._session = json.data.session;
244
+ }
245
+ return json;
227
246
  } catch (err) {
228
247
  return { data: null, error: err.message };
229
248
  }
230
249
  }
231
250
 
232
- then(resolve, reject) {
233
- 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 };
234
268
  }
235
269
  }
236
270
 
237
271
  // ─── Storage Client ───────────────────────────────────────────────────────────
238
272
 
239
273
  class StorageClient {
240
- constructor(baseUrl, apiKey, dbName) {
241
- this._baseUrl = baseUrl;
242
- this._apiKey = apiKey;
243
- this._dbName = dbName;
274
+ constructor(client) {
275
+ this._client = client;
244
276
  }
245
277
 
246
278
  _headers() {
247
- return { 'Authorization': `Bearer ${this._apiKey}` };
279
+ const headers = {
280
+ 'Authorization': `Bearer ${this._client._apiKey}`,
281
+ 'X-Anon-Key': this._client._anonKey
282
+ };
283
+ if (this._client._session) {
284
+ headers['Authorization'] = `Bearer ${this._client._session.access_token}`;
285
+ }
286
+ return headers;
248
287
  }
249
288
 
250
289
  // Detect if we're running in Node.js
@@ -253,8 +292,6 @@ class StorageClient {
253
292
  }
254
293
 
255
294
  // Build a FormData object that works in both browser and Node.js
256
- // In browser: file = File or Blob object
257
- // In Node.js: file = file path string, Buffer, or ReadStream
258
295
  async _buildFormData(file, filename) {
259
296
  if (this._isNode()) {
260
297
  // Node.js — use form-data package
@@ -273,14 +310,11 @@ class StorageClient {
273
310
  const formData = new FormDataNode();
274
311
 
275
312
  if (typeof file === 'string') {
276
- // file path string — read from disk
277
313
  const resolvedName = filename || path.basename(file);
278
314
  formData.append('file', fs.createReadStream(file), resolvedName);
279
315
  } else if (Buffer.isBuffer(file)) {
280
- // Buffer
281
316
  formData.append('file', file, filename || 'file');
282
317
  } else {
283
- // ReadStream or anything else
284
318
  formData.append('file', file, filename || 'file');
285
319
  }
286
320
 
@@ -294,14 +328,10 @@ class StorageClient {
294
328
  }
295
329
  }
296
330
 
297
- // Upload a file
298
- // Browser: pass a File or Blob (from <input type="file"> or drag-and-drop)
299
- // Node.js: pass a file path string, Buffer, or ReadStream
300
331
  async upload(file, filename) {
301
332
  try {
302
333
  const formData = await this._buildFormData(file, filename);
303
334
 
304
- // In Node.js, form-data needs its own headers (includes boundary)
305
335
  const headers = this._isNode()
306
336
  ? { ...this._headers(), ...formData.getHeaders() }
307
337
  : this._headers();
@@ -317,7 +347,6 @@ class StorageClient {
317
347
  }
318
348
  }
319
349
 
320
- // List all files in this project's bucket
321
350
  async list() {
322
351
  try {
323
352
  const res = await fetch(`${this._baseUrl}/storage/${this._dbName}/list`, {
@@ -329,7 +358,6 @@ class StorageClient {
329
358
  }
330
359
  }
331
360
 
332
- // Get a presigned URL valid for 7 days
333
361
  async getUrl(filename) {
334
362
  try {
335
363
  const res = await fetch(`${this._baseUrl}/storage/${this._dbName}/url/${encodeURIComponent(filename)}`, {
@@ -341,7 +369,6 @@ class StorageClient {
341
369
  }
342
370
  }
343
371
 
344
- // Delete a file
345
372
  async remove(filename) {
346
373
  try {
347
374
  const res = await fetch(`${this._baseUrl}/storage/${this._dbName}/${encodeURIComponent(filename)}`, {
@@ -358,12 +385,13 @@ class StorageClient {
358
385
  // ─── Realtime Subscription ────────────────────────────────────────────────────
359
386
 
360
387
  class RealtimeSubscription {
361
- constructor(baseUrl, apiKey, dbName, table) {
388
+ constructor(baseUrl, apiKey, dbName, table, anonKey) {
362
389
  this._baseUrl = baseUrl.replace('http://', 'ws://').replace('https://', 'wss://');
363
390
  this._apiKey = apiKey;
364
391
  this._dbName = dbName;
365
392
  this._table = table;
366
- this._listeners = {}; // { INSERT: [fn], UPDATE: [fn], DELETE: [fn] }
393
+ this._anonKey = anonKey;
394
+ this._listeners = {};
367
395
  this._ws = null;
368
396
  }
369
397
 
@@ -374,7 +402,7 @@ class RealtimeSubscription {
374
402
  }
375
403
 
376
404
  subscribe() {
377
- const url = `${this._baseUrl}/realtime/${this._dbName}/${this._table}?token=${this._apiKey}`;
405
+ const url = `${this._baseUrl}/realtime/${this._dbName}/${this._table}?token=${this._apiKey}&anon_key=${this._anonKey}`;
378
406
  this._ws = new WebSocket(url);
379
407
 
380
408
  this._ws.onmessage = (e) => {
@@ -404,25 +432,35 @@ class RealtimeSubscription {
404
432
  // ─── Client ───────────────────────────────────────────────────────────────────
405
433
 
406
434
  class OpenbaseClient {
407
- constructor(baseUrl, apiKey, dbName) {
435
+ constructor(baseUrl, apiKey, dbName, options = {}) {
408
436
  this._baseUrl = baseUrl.replace(/\/$/, '');
409
437
  this._apiKey = apiKey;
410
438
  this._dbName = dbName;
411
- this.storage = new StorageClient(this._baseUrl, this._apiKey, this._dbName);
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);
412
443
  }
413
444
 
414
445
  from(table) {
415
- return new QueryBuilder(this._baseUrl, this._apiKey, this._dbName, table);
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
+ );
416
454
  }
417
455
  }
418
456
 
419
457
  // ─── Factory ──────────────────────────────────────────────────────────────────
420
458
 
421
- function createClient(baseUrl, apiKey, dbName) {
422
- if (!baseUrl) throw new Error('[openbase] Missing baseUrl. Pass your backend URL as the first argument.');
423
- if (!apiKey) throw new Error('[openbase] Missing apiKey. Pass your anon or service key as the second argument.');
424
- if (!dbName) throw new Error('[openbase] Missing dbName. Pass your database name as the third argument.');
425
- return new OpenbaseClient(baseUrl, apiKey, dbName);
459
+ function createClient(baseUrl, apiKey, dbName, options = {}) {
460
+ if (!baseUrl) throw new Error('[openbase] Missing baseUrl.');
461
+ if (!apiKey) throw new Error('[openbase] Missing apiKey.');
462
+ if (!dbName) throw new Error('[openbase] Missing dbName.');
463
+ return new OpenbaseClient(baseUrl, apiKey, dbName, options);
426
464
  }
427
465
 
428
466
  if (typeof module !== 'undefined') {
@@ -432,4 +470,3 @@ if (typeof module !== 'undefined') {
432
470
  if (typeof window !== 'undefined') {
433
471
  window.openbase = { createClient };
434
472
  }
435
- module.exports = { createClient };
package/index.d.ts CHANGED
@@ -6,6 +6,31 @@ export interface OpenbaseResponse<T> {
6
6
  error: string | null;
7
7
  }
8
8
 
9
+ export interface OpenbaseClientOptions {
10
+ /**
11
+ * The public Anon Key for your project.
12
+ * If not provided, the 'apiKey' argument will be used as the project identifier.
13
+ */
14
+ anonKey?: string;
15
+ }
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
+
9
34
  export declare class QueryBuilder<T = Record<string, unknown>> {
10
35
  // Column selection
11
36
  select(columns?: string): this;
@@ -31,6 +56,7 @@ export declare class QueryBuilder<T = Record<string, unknown>> {
31
56
  insert(data: Partial<T>): this;
32
57
  update(data: Partial<T>): this;
33
58
  delete(): this;
59
+ upsert(data: Partial<T>, onConflict?: string): this;
34
60
 
35
61
  then<R>(
36
62
  resolve: (value: OpenbaseResponse<T[]>) => R,
@@ -91,6 +117,9 @@ export declare class OpenbaseClient {
91
117
  /** Storage client for file upload, listing, URLs, and deletion */
92
118
  storage: StorageClient;
93
119
 
120
+ /** Auth client for user registration, login, and session management */
121
+ auth: AuthClient;
122
+
94
123
  /** Query builder for database operations */
95
124
  from<T = Record<string, unknown>>(table: string): QueryBuilder<T>;
96
125
  }
@@ -98,7 +127,8 @@ export declare class OpenbaseClient {
98
127
  export declare function createClient(
99
128
  baseUrl: string,
100
129
  apiKey: string,
101
- dbName: string
130
+ dbName: string,
131
+ options?: OpenbaseClientOptions
102
132
  ): OpenbaseClient;
103
133
 
104
134
  export declare class RealtimeSubscription {