openbase-js 0.1.4 → 0.1.5
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 +126 -7
- package/index.d.ts +57 -1
- package/index.js +126 -7
- package/package.json +1 -1
- package/test.js +52 -1
package/index.cjs
CHANGED
|
@@ -6,12 +6,12 @@ class QueryBuilder {
|
|
|
6
6
|
this._apiKey = apiKey;
|
|
7
7
|
this._dbName = dbName;
|
|
8
8
|
this._table = table;
|
|
9
|
-
this._filters = [];
|
|
9
|
+
this._filters = [];
|
|
10
10
|
this._columns = '*';
|
|
11
11
|
this._limitVal = null;
|
|
12
|
-
this._offsetVal = null;
|
|
13
|
-
this._orderCol = null;
|
|
14
|
-
this._orderAsc = true;
|
|
12
|
+
this._offsetVal = null;
|
|
13
|
+
this._orderCol = null;
|
|
14
|
+
this._orderAsc = true;
|
|
15
15
|
this._single = false;
|
|
16
16
|
this._operation = 'select';
|
|
17
17
|
this._insertData = null;
|
|
@@ -67,7 +67,6 @@ class QueryBuilder {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
is(column, value) {
|
|
70
|
-
// value should be null, true, or false
|
|
71
70
|
this._filters.push({ col: column, op: 'is', val: value });
|
|
72
71
|
return this;
|
|
73
72
|
}
|
|
@@ -86,7 +85,6 @@ class QueryBuilder {
|
|
|
86
85
|
}
|
|
87
86
|
|
|
88
87
|
range(from, to) {
|
|
89
|
-
// e.g. range(0, 9) => LIMIT 10 OFFSET 0
|
|
90
88
|
this._limitVal = to - from + 1;
|
|
91
89
|
this._offsetVal = from;
|
|
92
90
|
return this;
|
|
@@ -148,7 +146,6 @@ class QueryBuilder {
|
|
|
148
146
|
|
|
149
147
|
if (this._operation === 'select') {
|
|
150
148
|
sql = `SELECT ${this._columns} FROM "${this._table}"`;
|
|
151
|
-
|
|
152
149
|
if (this._filters.length) {
|
|
153
150
|
const where = this._filters.map(f => this._filterToSQL(f)).join(' AND ');
|
|
154
151
|
sql += ` WHERE ${where}`;
|
|
@@ -218,6 +215,127 @@ class QueryBuilder {
|
|
|
218
215
|
}
|
|
219
216
|
}
|
|
220
217
|
|
|
218
|
+
// ─── Storage Client ───────────────────────────────────────────────────────────
|
|
219
|
+
|
|
220
|
+
class StorageClient {
|
|
221
|
+
constructor(baseUrl, apiKey, dbName) {
|
|
222
|
+
this._baseUrl = baseUrl;
|
|
223
|
+
this._apiKey = apiKey;
|
|
224
|
+
this._dbName = dbName;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
_headers() {
|
|
228
|
+
return { 'Authorization': `Bearer ${this._apiKey}` };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Detect if we're running in Node.js
|
|
232
|
+
_isNode() {
|
|
233
|
+
return typeof window === 'undefined' && typeof process !== 'undefined';
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Build a FormData object that works in both browser and Node.js
|
|
237
|
+
// In browser: file = File or Blob object
|
|
238
|
+
// In Node.js: file = file path string, Buffer, or ReadStream
|
|
239
|
+
async _buildFormData(file, filename) {
|
|
240
|
+
if (this._isNode()) {
|
|
241
|
+
// Node.js — use form-data package
|
|
242
|
+
let FormDataNode;
|
|
243
|
+
try {
|
|
244
|
+
FormDataNode = require('form-data');
|
|
245
|
+
} catch {
|
|
246
|
+
throw new Error(
|
|
247
|
+
'[openbase] In Node.js, storage.upload() requires the "form-data" package.\n' +
|
|
248
|
+
'Install it with: npm install form-data'
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const fs = require('fs');
|
|
253
|
+
const path = require('path');
|
|
254
|
+
const formData = new FormDataNode();
|
|
255
|
+
|
|
256
|
+
if (typeof file === 'string') {
|
|
257
|
+
// file path string — read from disk
|
|
258
|
+
const resolvedName = filename || path.basename(file);
|
|
259
|
+
formData.append('file', fs.createReadStream(file), resolvedName);
|
|
260
|
+
} else if (Buffer.isBuffer(file)) {
|
|
261
|
+
// Buffer
|
|
262
|
+
formData.append('file', file, filename || 'file');
|
|
263
|
+
} else {
|
|
264
|
+
// ReadStream or anything else
|
|
265
|
+
formData.append('file', file, filename || 'file');
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return formData;
|
|
269
|
+
|
|
270
|
+
} else {
|
|
271
|
+
// Browser — use native FormData
|
|
272
|
+
const formData = new FormData();
|
|
273
|
+
formData.append('file', file, filename || file.name);
|
|
274
|
+
return formData;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Upload a file
|
|
279
|
+
// Browser: pass a File or Blob (from <input type="file"> or drag-and-drop)
|
|
280
|
+
// Node.js: pass a file path string, Buffer, or ReadStream
|
|
281
|
+
async upload(file, filename) {
|
|
282
|
+
try {
|
|
283
|
+
const formData = await this._buildFormData(file, filename);
|
|
284
|
+
|
|
285
|
+
// In Node.js, form-data needs its own headers (includes boundary)
|
|
286
|
+
const headers = this._isNode()
|
|
287
|
+
? { ...this._headers(), ...formData.getHeaders() }
|
|
288
|
+
: this._headers();
|
|
289
|
+
|
|
290
|
+
const res = await fetch(`${this._baseUrl}/storage/${this._dbName}/upload`, {
|
|
291
|
+
method: 'POST',
|
|
292
|
+
headers,
|
|
293
|
+
body: formData,
|
|
294
|
+
});
|
|
295
|
+
return await res.json();
|
|
296
|
+
} catch (err) {
|
|
297
|
+
return { data: null, error: err.message };
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// List all files in this project's bucket
|
|
302
|
+
async list() {
|
|
303
|
+
try {
|
|
304
|
+
const res = await fetch(`${this._baseUrl}/storage/${this._dbName}/list`, {
|
|
305
|
+
headers: this._headers(),
|
|
306
|
+
});
|
|
307
|
+
return await res.json();
|
|
308
|
+
} catch (err) {
|
|
309
|
+
return { data: null, error: err.message };
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Get a presigned URL valid for 7 days
|
|
314
|
+
async getUrl(filename) {
|
|
315
|
+
try {
|
|
316
|
+
const res = await fetch(`${this._baseUrl}/storage/${this._dbName}/url/${encodeURIComponent(filename)}`, {
|
|
317
|
+
headers: this._headers(),
|
|
318
|
+
});
|
|
319
|
+
return await res.json();
|
|
320
|
+
} catch (err) {
|
|
321
|
+
return { data: null, error: err.message };
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Delete a file
|
|
326
|
+
async remove(filename) {
|
|
327
|
+
try {
|
|
328
|
+
const res = await fetch(`${this._baseUrl}/storage/${this._dbName}/${encodeURIComponent(filename)}`, {
|
|
329
|
+
method: 'DELETE',
|
|
330
|
+
headers: this._headers(),
|
|
331
|
+
});
|
|
332
|
+
return await res.json();
|
|
333
|
+
} catch (err) {
|
|
334
|
+
return { data: null, error: err.message };
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
221
339
|
// ─── Client ───────────────────────────────────────────────────────────────────
|
|
222
340
|
|
|
223
341
|
class OpenbaseClient {
|
|
@@ -225,6 +343,7 @@ class OpenbaseClient {
|
|
|
225
343
|
this._baseUrl = baseUrl.replace(/\/$/, '');
|
|
226
344
|
this._apiKey = apiKey;
|
|
227
345
|
this._dbName = dbName;
|
|
346
|
+
this.storage = new StorageClient(this._baseUrl, this._apiKey, this._dbName);
|
|
228
347
|
}
|
|
229
348
|
|
|
230
349
|
from(table) {
|
package/index.d.ts
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { ReadStream } from 'fs';
|
|
3
|
+
|
|
1
4
|
export interface OpenbaseResponse<T> {
|
|
2
5
|
data: T | null;
|
|
3
6
|
error: string | null;
|
|
4
7
|
}
|
|
5
8
|
|
|
6
9
|
export declare class QueryBuilder<T = Record<string, unknown>> {
|
|
10
|
+
// Column selection
|
|
7
11
|
select(columns?: string): this;
|
|
12
|
+
|
|
13
|
+
// Filter operators
|
|
8
14
|
eq(column: string, value: unknown): this;
|
|
9
15
|
gt(column: string, value: number): this;
|
|
10
16
|
lt(column: string, value: number): this;
|
|
@@ -14,20 +20,70 @@ export declare class QueryBuilder<T = Record<string, unknown>> {
|
|
|
14
20
|
ilike(column: string, pattern: string): this;
|
|
15
21
|
in(column: string, values: unknown[]): this;
|
|
16
22
|
is(column: string, value: null | boolean): this;
|
|
23
|
+
|
|
24
|
+
// Modifiers
|
|
17
25
|
order(column: string, options?: { ascending?: boolean }): this;
|
|
18
|
-
range(from: number, to: number): this;
|
|
19
26
|
limit(n: number): this;
|
|
27
|
+
range(from: number, to: number): this;
|
|
20
28
|
single(): this;
|
|
29
|
+
|
|
30
|
+
// Mutations
|
|
21
31
|
insert(data: Partial<T>): this;
|
|
22
32
|
update(data: Partial<T>): this;
|
|
23
33
|
delete(): this;
|
|
34
|
+
|
|
24
35
|
then<R>(
|
|
25
36
|
resolve: (value: OpenbaseResponse<T[]>) => R,
|
|
26
37
|
reject?: (reason: unknown) => R
|
|
27
38
|
): Promise<R>;
|
|
28
39
|
}
|
|
29
40
|
|
|
41
|
+
export interface StorageFileInfo {
|
|
42
|
+
name: string;
|
|
43
|
+
size: number;
|
|
44
|
+
content_type: string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface StorageListItem {
|
|
48
|
+
name: string;
|
|
49
|
+
size: number;
|
|
50
|
+
last_modified: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export declare class StorageClient {
|
|
54
|
+
/**
|
|
55
|
+
* Upload a file to this project's storage bucket.
|
|
56
|
+
*
|
|
57
|
+
* Browser: pass a File or Blob (e.g. from <input type="file">)
|
|
58
|
+
* Node.js: pass a file path string, Buffer, or ReadStream
|
|
59
|
+
* Requires the "form-data" package: npm install form-data
|
|
60
|
+
*/
|
|
61
|
+
upload(
|
|
62
|
+
file: File | Blob | string | Buffer | ReadStream,
|
|
63
|
+
filename?: string
|
|
64
|
+
): Promise<OpenbaseResponse<StorageFileInfo>>;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* List all files in this project's storage bucket.
|
|
68
|
+
*/
|
|
69
|
+
list(): Promise<OpenbaseResponse<StorageListItem[]>>;
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Get a presigned URL for a file, valid for 7 days.
|
|
73
|
+
*/
|
|
74
|
+
getUrl(filename: string): Promise<OpenbaseResponse<{ url: string }>>;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Delete a file from this project's storage bucket.
|
|
78
|
+
*/
|
|
79
|
+
remove(filename: string): Promise<OpenbaseResponse<{ name: string }>>;
|
|
80
|
+
}
|
|
81
|
+
|
|
30
82
|
export declare class OpenbaseClient {
|
|
83
|
+
/** Storage client for file upload, listing, URLs, and deletion */
|
|
84
|
+
storage: StorageClient;
|
|
85
|
+
|
|
86
|
+
/** Query builder for database operations */
|
|
31
87
|
from<T = Record<string, unknown>>(table: string): QueryBuilder<T>;
|
|
32
88
|
}
|
|
33
89
|
|
package/index.js
CHANGED
|
@@ -6,12 +6,12 @@ class QueryBuilder {
|
|
|
6
6
|
this._apiKey = apiKey;
|
|
7
7
|
this._dbName = dbName;
|
|
8
8
|
this._table = table;
|
|
9
|
-
this._filters = [];
|
|
9
|
+
this._filters = [];
|
|
10
10
|
this._columns = '*';
|
|
11
11
|
this._limitVal = null;
|
|
12
|
-
this._offsetVal = null;
|
|
13
|
-
this._orderCol = null;
|
|
14
|
-
this._orderAsc = true;
|
|
12
|
+
this._offsetVal = null;
|
|
13
|
+
this._orderCol = null;
|
|
14
|
+
this._orderAsc = true;
|
|
15
15
|
this._single = false;
|
|
16
16
|
this._operation = 'select';
|
|
17
17
|
this._insertData = null;
|
|
@@ -67,7 +67,6 @@ class QueryBuilder {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
is(column, value) {
|
|
70
|
-
// value should be null, true, or false
|
|
71
70
|
this._filters.push({ col: column, op: 'is', val: value });
|
|
72
71
|
return this;
|
|
73
72
|
}
|
|
@@ -86,7 +85,6 @@ class QueryBuilder {
|
|
|
86
85
|
}
|
|
87
86
|
|
|
88
87
|
range(from, to) {
|
|
89
|
-
// e.g. range(0, 9) => LIMIT 10 OFFSET 0
|
|
90
88
|
this._limitVal = to - from + 1;
|
|
91
89
|
this._offsetVal = from;
|
|
92
90
|
return this;
|
|
@@ -148,7 +146,6 @@ class QueryBuilder {
|
|
|
148
146
|
|
|
149
147
|
if (this._operation === 'select') {
|
|
150
148
|
sql = `SELECT ${this._columns} FROM "${this._table}"`;
|
|
151
|
-
|
|
152
149
|
if (this._filters.length) {
|
|
153
150
|
const where = this._filters.map(f => this._filterToSQL(f)).join(' AND ');
|
|
154
151
|
sql += ` WHERE ${where}`;
|
|
@@ -218,6 +215,127 @@ class QueryBuilder {
|
|
|
218
215
|
}
|
|
219
216
|
}
|
|
220
217
|
|
|
218
|
+
// ─── Storage Client ───────────────────────────────────────────────────────────
|
|
219
|
+
|
|
220
|
+
class StorageClient {
|
|
221
|
+
constructor(baseUrl, apiKey, dbName) {
|
|
222
|
+
this._baseUrl = baseUrl;
|
|
223
|
+
this._apiKey = apiKey;
|
|
224
|
+
this._dbName = dbName;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
_headers() {
|
|
228
|
+
return { 'Authorization': `Bearer ${this._apiKey}` };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Detect if we're running in Node.js
|
|
232
|
+
_isNode() {
|
|
233
|
+
return typeof window === 'undefined' && typeof process !== 'undefined';
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Build a FormData object that works in both browser and Node.js
|
|
237
|
+
// In browser: file = File or Blob object
|
|
238
|
+
// In Node.js: file = file path string, Buffer, or ReadStream
|
|
239
|
+
async _buildFormData(file, filename) {
|
|
240
|
+
if (this._isNode()) {
|
|
241
|
+
// Node.js — use form-data package
|
|
242
|
+
let FormDataNode;
|
|
243
|
+
try {
|
|
244
|
+
FormDataNode = require('form-data');
|
|
245
|
+
} catch {
|
|
246
|
+
throw new Error(
|
|
247
|
+
'[openbase] In Node.js, storage.upload() requires the "form-data" package.\n' +
|
|
248
|
+
'Install it with: npm install form-data'
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const fs = require('fs');
|
|
253
|
+
const path = require('path');
|
|
254
|
+
const formData = new FormDataNode();
|
|
255
|
+
|
|
256
|
+
if (typeof file === 'string') {
|
|
257
|
+
// file path string — read from disk
|
|
258
|
+
const resolvedName = filename || path.basename(file);
|
|
259
|
+
formData.append('file', fs.createReadStream(file), resolvedName);
|
|
260
|
+
} else if (Buffer.isBuffer(file)) {
|
|
261
|
+
// Buffer
|
|
262
|
+
formData.append('file', file, filename || 'file');
|
|
263
|
+
} else {
|
|
264
|
+
// ReadStream or anything else
|
|
265
|
+
formData.append('file', file, filename || 'file');
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return formData;
|
|
269
|
+
|
|
270
|
+
} else {
|
|
271
|
+
// Browser — use native FormData
|
|
272
|
+
const formData = new FormData();
|
|
273
|
+
formData.append('file', file, filename || file.name);
|
|
274
|
+
return formData;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Upload a file
|
|
279
|
+
// Browser: pass a File or Blob (from <input type="file"> or drag-and-drop)
|
|
280
|
+
// Node.js: pass a file path string, Buffer, or ReadStream
|
|
281
|
+
async upload(file, filename) {
|
|
282
|
+
try {
|
|
283
|
+
const formData = await this._buildFormData(file, filename);
|
|
284
|
+
|
|
285
|
+
// In Node.js, form-data needs its own headers (includes boundary)
|
|
286
|
+
const headers = this._isNode()
|
|
287
|
+
? { ...this._headers(), ...formData.getHeaders() }
|
|
288
|
+
: this._headers();
|
|
289
|
+
|
|
290
|
+
const res = await fetch(`${this._baseUrl}/storage/${this._dbName}/upload`, {
|
|
291
|
+
method: 'POST',
|
|
292
|
+
headers,
|
|
293
|
+
body: formData,
|
|
294
|
+
});
|
|
295
|
+
return await res.json();
|
|
296
|
+
} catch (err) {
|
|
297
|
+
return { data: null, error: err.message };
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// List all files in this project's bucket
|
|
302
|
+
async list() {
|
|
303
|
+
try {
|
|
304
|
+
const res = await fetch(`${this._baseUrl}/storage/${this._dbName}/list`, {
|
|
305
|
+
headers: this._headers(),
|
|
306
|
+
});
|
|
307
|
+
return await res.json();
|
|
308
|
+
} catch (err) {
|
|
309
|
+
return { data: null, error: err.message };
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Get a presigned URL valid for 7 days
|
|
314
|
+
async getUrl(filename) {
|
|
315
|
+
try {
|
|
316
|
+
const res = await fetch(`${this._baseUrl}/storage/${this._dbName}/url/${encodeURIComponent(filename)}`, {
|
|
317
|
+
headers: this._headers(),
|
|
318
|
+
});
|
|
319
|
+
return await res.json();
|
|
320
|
+
} catch (err) {
|
|
321
|
+
return { data: null, error: err.message };
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Delete a file
|
|
326
|
+
async remove(filename) {
|
|
327
|
+
try {
|
|
328
|
+
const res = await fetch(`${this._baseUrl}/storage/${this._dbName}/${encodeURIComponent(filename)}`, {
|
|
329
|
+
method: 'DELETE',
|
|
330
|
+
headers: this._headers(),
|
|
331
|
+
});
|
|
332
|
+
return await res.json();
|
|
333
|
+
} catch (err) {
|
|
334
|
+
return { data: null, error: err.message };
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
221
339
|
// ─── Client ───────────────────────────────────────────────────────────────────
|
|
222
340
|
|
|
223
341
|
class OpenbaseClient {
|
|
@@ -225,6 +343,7 @@ class OpenbaseClient {
|
|
|
225
343
|
this._baseUrl = baseUrl.replace(/\/$/, '');
|
|
226
344
|
this._apiKey = apiKey;
|
|
227
345
|
this._dbName = dbName;
|
|
346
|
+
this.storage = new StorageClient(this._baseUrl, this._apiKey, this._dbName);
|
|
228
347
|
}
|
|
229
348
|
|
|
230
349
|
from(table) {
|
package/package.json
CHANGED
package/test.js
CHANGED
|
@@ -43,4 +43,55 @@ async function test() {
|
|
|
43
43
|
console.log('deleted:', deleted);
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
|
|
46
|
+
async function testOperators() {
|
|
47
|
+
console.log('\n── GT / LT ─────────────────────────');
|
|
48
|
+
const { data: gt } = await client.from('leads').select('*').gt('id', 2);
|
|
49
|
+
console.log('gt id > 2:', gt);
|
|
50
|
+
|
|
51
|
+
console.log('\n── GTE / LTE ───────────────────────');
|
|
52
|
+
const { data: lte } = await client.from('leads').select('*').lte('id', 3);
|
|
53
|
+
console.log('lte id <= 3:', lte);
|
|
54
|
+
|
|
55
|
+
console.log('\n── LIKE ────────────────────────────');
|
|
56
|
+
const { data: like } = await client.from('leads').select('*').like('company_name', '%oo%');
|
|
57
|
+
console.log('like company_name %oo%:', like);
|
|
58
|
+
|
|
59
|
+
console.log('\n── ILIKE (case insensitive) ────────');
|
|
60
|
+
const { data: ilike } = await client.from('leads').select('*').ilike('company_name', '%google%');
|
|
61
|
+
console.log('ilike company_name %google%:', ilike);
|
|
62
|
+
|
|
63
|
+
console.log('\n── IN ──────────────────────────────');
|
|
64
|
+
const { data: inResult } = await client.from('leads').select('*').in('slug', ['google', 'meta']);
|
|
65
|
+
console.log('in slug [google, meta]:', inResult);
|
|
66
|
+
|
|
67
|
+
console.log('\n── IS NULL ─────────────────────────');
|
|
68
|
+
const { data: isNull } = await client.from('leads').select('*').is('custom_pitch', null);
|
|
69
|
+
console.log('is custom_pitch null:', isNull);
|
|
70
|
+
|
|
71
|
+
console.log('\n── ORDER ASC ───────────────────────');
|
|
72
|
+
const { data: asc } = await client.from('leads').select('*').order('id', { ascending: true });
|
|
73
|
+
console.log('order id asc:', asc?.map(r => r.id));
|
|
74
|
+
|
|
75
|
+
console.log('\n── ORDER DESC ──────────────────────');
|
|
76
|
+
const { data: desc } = await client.from('leads').select('*').order('id', { ascending: false });
|
|
77
|
+
console.log('order id desc:', desc?.map(r => r.id));
|
|
78
|
+
|
|
79
|
+
console.log('\n── LIMIT ───────────────────────────');
|
|
80
|
+
const { data: limited } = await client.from('leads').select('*').limit(2);
|
|
81
|
+
console.log('limit 2:', limited?.length, 'rows');
|
|
82
|
+
|
|
83
|
+
console.log('\n── RANGE (pagination) ──────────────');
|
|
84
|
+
const { data: page } = await client.from('leads').select('*').range(0, 1);
|
|
85
|
+
console.log('range 0-1:', page?.length, 'rows');
|
|
86
|
+
|
|
87
|
+
console.log('\n── CHAINED (gt + order + limit) ────');
|
|
88
|
+
const { data: chained } = await client
|
|
89
|
+
.from('leads')
|
|
90
|
+
.select('*')
|
|
91
|
+
.gt('id', 1)
|
|
92
|
+
.order('id', { ascending: false })
|
|
93
|
+
.limit(3);
|
|
94
|
+
console.log('chained:', chained);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
testOperators().catch(console.error);
|