@linabase/js 0.3.0 → 0.4.0
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/dist/index.cjs +88 -13
- package/dist/index.d.cts +44 -4
- package/dist/index.d.ts +44 -4
- package/dist/index.js +88 -13
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -52,16 +52,16 @@ var DatabaseClient = class {
|
|
|
52
52
|
// ─── Query Methods ──────────────────────────────────────
|
|
53
53
|
/** Select columns. Supports nested joins: "*, comments(*)" and aliases: "full_name:name" */
|
|
54
54
|
select(columns, options) {
|
|
55
|
-
this.method = "GET";
|
|
56
|
-
if (columns) this.params.set("select", columns);
|
|
55
|
+
this.method = options?.head ? "HEAD" : "GET";
|
|
56
|
+
if (columns && columns !== "*") this.params.set("select", columns);
|
|
57
57
|
if (options?.count) this.preferHeaders.push(`count=${options.count}`);
|
|
58
|
-
if (options?.head) this.method = "GET";
|
|
59
58
|
return this;
|
|
60
59
|
}
|
|
61
|
-
insert(data) {
|
|
60
|
+
insert(data, options) {
|
|
62
61
|
this.method = "POST";
|
|
63
62
|
this.body = data;
|
|
64
63
|
this.preferHeaders.push("return=representation");
|
|
64
|
+
if (options?.defaultToNull === false) this.preferHeaders.push("missing=default");
|
|
65
65
|
return this;
|
|
66
66
|
}
|
|
67
67
|
/** Upsert: insert or update on conflict. Uses merge-duplicates by default. */
|
|
@@ -75,6 +75,7 @@ var DatabaseClient = class {
|
|
|
75
75
|
if (options?.onConflict) {
|
|
76
76
|
this.params.set("on_conflict", options.onConflict);
|
|
77
77
|
}
|
|
78
|
+
if (options?.defaultToNull === false) this.preferHeaders.push("missing=default");
|
|
78
79
|
return this;
|
|
79
80
|
}
|
|
80
81
|
update(data) {
|
|
@@ -83,8 +84,28 @@ var DatabaseClient = class {
|
|
|
83
84
|
this.preferHeaders.push("return=representation");
|
|
84
85
|
return this;
|
|
85
86
|
}
|
|
86
|
-
delete() {
|
|
87
|
+
delete(options) {
|
|
87
88
|
this.method = "DELETE";
|
|
89
|
+
if (options?.count) this.preferHeaders.push(`count=${options.count}`);
|
|
90
|
+
return this;
|
|
91
|
+
}
|
|
92
|
+
/** Specify return=minimal to skip returning data on mutations (better performance). */
|
|
93
|
+
returning(mode = "representation") {
|
|
94
|
+
this.preferHeaders = this.preferHeaders.filter((h) => !h.startsWith("return="));
|
|
95
|
+
this.preferHeaders.push(`return=${mode}`);
|
|
96
|
+
return this;
|
|
97
|
+
}
|
|
98
|
+
/** Get the query execution plan (EXPLAIN). */
|
|
99
|
+
explain(options) {
|
|
100
|
+
const parts = ["plan"];
|
|
101
|
+
if (options?.analyze) parts.push("analyze");
|
|
102
|
+
if (options?.verbose) parts.push("verbose");
|
|
103
|
+
if (options?.settings) parts.push("settings");
|
|
104
|
+
if (options?.buffers) parts.push("buffers");
|
|
105
|
+
if (options?.wal) parts.push("wal");
|
|
106
|
+
if (options?.format) parts.push(`format:${options.format}`);
|
|
107
|
+
this.preferHeaders = this.preferHeaders.filter((h) => !h.startsWith("plan"));
|
|
108
|
+
this.preferHeaders.push(parts.join("|"));
|
|
88
109
|
return this;
|
|
89
110
|
}
|
|
90
111
|
// ─── Comparison Filters ─────────────────────────────────
|
|
@@ -168,6 +189,32 @@ var DatabaseClient = class {
|
|
|
168
189
|
this.params.set(column, `isdistinct.${value}`);
|
|
169
190
|
return this;
|
|
170
191
|
}
|
|
192
|
+
// ─── Range Type Filters ──────────────────────────────────
|
|
193
|
+
/** Strictly left of: [a,b] << [c,d] */
|
|
194
|
+
rangeLt(column, range) {
|
|
195
|
+
this.params.set(column, `sl.${range}`);
|
|
196
|
+
return this;
|
|
197
|
+
}
|
|
198
|
+
/** Strictly right of: [a,b] >> [c,d] */
|
|
199
|
+
rangeGt(column, range) {
|
|
200
|
+
this.params.set(column, `sr.${range}`);
|
|
201
|
+
return this;
|
|
202
|
+
}
|
|
203
|
+
/** Does not extend to the left of */
|
|
204
|
+
rangeGte(column, range) {
|
|
205
|
+
this.params.set(column, `nxl.${range}`);
|
|
206
|
+
return this;
|
|
207
|
+
}
|
|
208
|
+
/** Does not extend to the right of */
|
|
209
|
+
rangeLte(column, range) {
|
|
210
|
+
this.params.set(column, `nxr.${range}`);
|
|
211
|
+
return this;
|
|
212
|
+
}
|
|
213
|
+
/** Range is adjacent to */
|
|
214
|
+
rangeAdjacent(column, range) {
|
|
215
|
+
this.params.set(column, `adj.${range}`);
|
|
216
|
+
return this;
|
|
217
|
+
}
|
|
171
218
|
// ─── Negation ───────────────────────────────────────────
|
|
172
219
|
/** Negate a filter: not.eq, not.in, not.is, etc. */
|
|
173
220
|
not(column, operator, value) {
|
|
@@ -176,29 +223,53 @@ var DatabaseClient = class {
|
|
|
176
223
|
}
|
|
177
224
|
// ─── Logical ────────────────────────────────────────────
|
|
178
225
|
/** OR filter: or("age.gt.20,name.eq.John") */
|
|
179
|
-
or(filters) {
|
|
180
|
-
|
|
226
|
+
or(filters, options) {
|
|
227
|
+
const key = options?.foreignTable ? `${options.foreignTable}.or` : "or";
|
|
228
|
+
this.params.set(key, `(${filters})`);
|
|
229
|
+
return this;
|
|
230
|
+
}
|
|
231
|
+
/** AND filter: and("age.gt.20,name.eq.John") */
|
|
232
|
+
and(filters, options) {
|
|
233
|
+
const key = options?.foreignTable ? `${options.foreignTable}.and` : "and";
|
|
234
|
+
this.params.set(key, `(${filters})`);
|
|
235
|
+
return this;
|
|
236
|
+
}
|
|
237
|
+
/** Generic filter: filter("column", "operator", "value") */
|
|
238
|
+
filter(column, operator, value) {
|
|
239
|
+
this.params.set(column, `${operator}.${value}`);
|
|
181
240
|
return this;
|
|
182
241
|
}
|
|
183
242
|
// ─── Full-Text Search ───────────────────────────────────
|
|
184
|
-
/** to_tsquery */
|
|
243
|
+
/** Full-text search (to_tsquery, plainto_tsquery, phraseto_tsquery, or websearch_to_tsquery) */
|
|
185
244
|
textSearch(column, query, options) {
|
|
186
245
|
const op = options?.type === "plain" ? "plfts" : options?.type === "phrase" ? "phfts" : options?.type === "websearch" ? "wfts" : "fts";
|
|
187
|
-
|
|
246
|
+
const config = options?.config ? `(${options.config})` : "";
|
|
247
|
+
this.params.set(column, `${op}${config}.${query}`);
|
|
188
248
|
return this;
|
|
189
249
|
}
|
|
190
250
|
// ─── Ordering & Pagination ──────────────────────────────
|
|
191
251
|
order(column, options) {
|
|
192
252
|
const dir = options?.ascending === false ? "desc" : "asc";
|
|
193
253
|
const nulls = options?.nullsFirst === true ? ".nullsfirst" : options?.nullsFirst === false ? ".nullslast" : "";
|
|
194
|
-
const
|
|
254
|
+
const key = options?.foreignTable ? `${options.foreignTable}.order` : "order";
|
|
255
|
+
const existing = this.params.get(key);
|
|
195
256
|
if (existing) {
|
|
196
|
-
this.params.set(
|
|
257
|
+
this.params.set(key, `${existing},${column}.${dir}${nulls}`);
|
|
197
258
|
} else {
|
|
198
|
-
this.params.set(
|
|
259
|
+
this.params.set(key, `${column}.${dir}${nulls}`);
|
|
199
260
|
}
|
|
200
261
|
return this;
|
|
201
262
|
}
|
|
263
|
+
/** Limit results on a foreign table */
|
|
264
|
+
limitForeignTable(count, foreignTable) {
|
|
265
|
+
this.params.set(`${foreignTable}.limit`, String(count));
|
|
266
|
+
return this;
|
|
267
|
+
}
|
|
268
|
+
/** Offset results on a foreign table */
|
|
269
|
+
offsetForeignTable(count, foreignTable) {
|
|
270
|
+
this.params.set(`${foreignTable}.offset`, String(count));
|
|
271
|
+
return this;
|
|
272
|
+
}
|
|
202
273
|
limit(count) {
|
|
203
274
|
this.params.set("limit", String(count));
|
|
204
275
|
return this;
|
|
@@ -269,6 +340,7 @@ var DatabaseClient = class {
|
|
|
269
340
|
async execute() {
|
|
270
341
|
const qs = this.params.toString();
|
|
271
342
|
const path = `/rest/v1/${this.table}${qs ? `?${qs}` : ""}`;
|
|
343
|
+
const isHead = this.method === "HEAD";
|
|
272
344
|
const isSingle = this._single;
|
|
273
345
|
const isMaybeSingle = this._maybeSingle;
|
|
274
346
|
const shouldThrow = this._throwOnError;
|
|
@@ -301,8 +373,11 @@ var DatabaseClient = class {
|
|
|
301
373
|
body: this.body ? JSON.stringify(this.body) : void 0,
|
|
302
374
|
signal: abortSignal
|
|
303
375
|
});
|
|
304
|
-
const countHeader = res.headers.get("X-Total-Count");
|
|
376
|
+
const countHeader = res.headers.get("X-Total-Count") || res.headers.get("content-range")?.split("/")[1];
|
|
305
377
|
const totalCount = countHeader ? parseInt(countHeader) : null;
|
|
378
|
+
if (isHead) {
|
|
379
|
+
return { data: null, error: res.ok ? null : { message: res.statusText }, count: totalCount };
|
|
380
|
+
}
|
|
306
381
|
if (isCsv) {
|
|
307
382
|
if (!res.ok) {
|
|
308
383
|
const text = await res.text();
|
package/dist/index.d.cts
CHANGED
|
@@ -39,14 +39,30 @@ declare class DatabaseClient {
|
|
|
39
39
|
count?: "exact" | "estimated" | "planned";
|
|
40
40
|
head?: boolean;
|
|
41
41
|
}): this;
|
|
42
|
-
insert(data: Record<string, any> | Record<string, any>[]
|
|
42
|
+
insert(data: Record<string, any> | Record<string, any>[], options?: {
|
|
43
|
+
defaultToNull?: boolean;
|
|
44
|
+
}): this;
|
|
43
45
|
/** Upsert: insert or update on conflict. Uses merge-duplicates by default. */
|
|
44
46
|
upsert(data: Record<string, any> | Record<string, any>[], options?: {
|
|
45
47
|
ignoreDuplicates?: boolean;
|
|
46
48
|
onConflict?: string;
|
|
49
|
+
defaultToNull?: boolean;
|
|
47
50
|
}): this;
|
|
48
51
|
update(data: Record<string, any>): this;
|
|
49
|
-
delete(
|
|
52
|
+
delete(options?: {
|
|
53
|
+
count?: "exact" | "estimated" | "planned";
|
|
54
|
+
}): this;
|
|
55
|
+
/** Specify return=minimal to skip returning data on mutations (better performance). */
|
|
56
|
+
returning(mode?: "minimal" | "representation"): this;
|
|
57
|
+
/** Get the query execution plan (EXPLAIN). */
|
|
58
|
+
explain(options?: {
|
|
59
|
+
analyze?: boolean;
|
|
60
|
+
verbose?: boolean;
|
|
61
|
+
settings?: boolean;
|
|
62
|
+
buffers?: boolean;
|
|
63
|
+
wal?: boolean;
|
|
64
|
+
format?: "json" | "text";
|
|
65
|
+
}): this;
|
|
50
66
|
eq(column: string, value: any): this;
|
|
51
67
|
neq(column: string, value: any): this;
|
|
52
68
|
gt(column: string, value: any): this;
|
|
@@ -69,18 +85,42 @@ declare class DatabaseClient {
|
|
|
69
85
|
is(column: string, value: "null" | "true" | "false" | null | boolean): this;
|
|
70
86
|
/** IS DISTINCT FROM */
|
|
71
87
|
isDistinct(column: string, value: any): this;
|
|
88
|
+
/** Strictly left of: [a,b] << [c,d] */
|
|
89
|
+
rangeLt(column: string, range: string): this;
|
|
90
|
+
/** Strictly right of: [a,b] >> [c,d] */
|
|
91
|
+
rangeGt(column: string, range: string): this;
|
|
92
|
+
/** Does not extend to the left of */
|
|
93
|
+
rangeGte(column: string, range: string): this;
|
|
94
|
+
/** Does not extend to the right of */
|
|
95
|
+
rangeLte(column: string, range: string): this;
|
|
96
|
+
/** Range is adjacent to */
|
|
97
|
+
rangeAdjacent(column: string, range: string): this;
|
|
72
98
|
/** Negate a filter: not.eq, not.in, not.is, etc. */
|
|
73
99
|
not(column: string, operator: string, value: any): this;
|
|
74
100
|
/** OR filter: or("age.gt.20,name.eq.John") */
|
|
75
|
-
or(filters: string
|
|
76
|
-
|
|
101
|
+
or(filters: string, options?: {
|
|
102
|
+
foreignTable?: string;
|
|
103
|
+
}): this;
|
|
104
|
+
/** AND filter: and("age.gt.20,name.eq.John") */
|
|
105
|
+
and(filters: string, options?: {
|
|
106
|
+
foreignTable?: string;
|
|
107
|
+
}): this;
|
|
108
|
+
/** Generic filter: filter("column", "operator", "value") */
|
|
109
|
+
filter(column: string, operator: string, value: any): this;
|
|
110
|
+
/** Full-text search (to_tsquery, plainto_tsquery, phraseto_tsquery, or websearch_to_tsquery) */
|
|
77
111
|
textSearch(column: string, query: string, options?: {
|
|
78
112
|
type?: "plain" | "phrase" | "websearch";
|
|
113
|
+
config?: string;
|
|
79
114
|
}): this;
|
|
80
115
|
order(column: string, options?: {
|
|
81
116
|
ascending?: boolean;
|
|
82
117
|
nullsFirst?: boolean;
|
|
118
|
+
foreignTable?: string;
|
|
83
119
|
}): this;
|
|
120
|
+
/** Limit results on a foreign table */
|
|
121
|
+
limitForeignTable(count: number, foreignTable: string): this;
|
|
122
|
+
/** Offset results on a foreign table */
|
|
123
|
+
offsetForeignTable(count: number, foreignTable: string): this;
|
|
84
124
|
limit(count: number): this;
|
|
85
125
|
offset(count: number): this;
|
|
86
126
|
/** Request exact, estimated, or planned count via Prefer header */
|
package/dist/index.d.ts
CHANGED
|
@@ -39,14 +39,30 @@ declare class DatabaseClient {
|
|
|
39
39
|
count?: "exact" | "estimated" | "planned";
|
|
40
40
|
head?: boolean;
|
|
41
41
|
}): this;
|
|
42
|
-
insert(data: Record<string, any> | Record<string, any>[]
|
|
42
|
+
insert(data: Record<string, any> | Record<string, any>[], options?: {
|
|
43
|
+
defaultToNull?: boolean;
|
|
44
|
+
}): this;
|
|
43
45
|
/** Upsert: insert or update on conflict. Uses merge-duplicates by default. */
|
|
44
46
|
upsert(data: Record<string, any> | Record<string, any>[], options?: {
|
|
45
47
|
ignoreDuplicates?: boolean;
|
|
46
48
|
onConflict?: string;
|
|
49
|
+
defaultToNull?: boolean;
|
|
47
50
|
}): this;
|
|
48
51
|
update(data: Record<string, any>): this;
|
|
49
|
-
delete(
|
|
52
|
+
delete(options?: {
|
|
53
|
+
count?: "exact" | "estimated" | "planned";
|
|
54
|
+
}): this;
|
|
55
|
+
/** Specify return=minimal to skip returning data on mutations (better performance). */
|
|
56
|
+
returning(mode?: "minimal" | "representation"): this;
|
|
57
|
+
/** Get the query execution plan (EXPLAIN). */
|
|
58
|
+
explain(options?: {
|
|
59
|
+
analyze?: boolean;
|
|
60
|
+
verbose?: boolean;
|
|
61
|
+
settings?: boolean;
|
|
62
|
+
buffers?: boolean;
|
|
63
|
+
wal?: boolean;
|
|
64
|
+
format?: "json" | "text";
|
|
65
|
+
}): this;
|
|
50
66
|
eq(column: string, value: any): this;
|
|
51
67
|
neq(column: string, value: any): this;
|
|
52
68
|
gt(column: string, value: any): this;
|
|
@@ -69,18 +85,42 @@ declare class DatabaseClient {
|
|
|
69
85
|
is(column: string, value: "null" | "true" | "false" | null | boolean): this;
|
|
70
86
|
/** IS DISTINCT FROM */
|
|
71
87
|
isDistinct(column: string, value: any): this;
|
|
88
|
+
/** Strictly left of: [a,b] << [c,d] */
|
|
89
|
+
rangeLt(column: string, range: string): this;
|
|
90
|
+
/** Strictly right of: [a,b] >> [c,d] */
|
|
91
|
+
rangeGt(column: string, range: string): this;
|
|
92
|
+
/** Does not extend to the left of */
|
|
93
|
+
rangeGte(column: string, range: string): this;
|
|
94
|
+
/** Does not extend to the right of */
|
|
95
|
+
rangeLte(column: string, range: string): this;
|
|
96
|
+
/** Range is adjacent to */
|
|
97
|
+
rangeAdjacent(column: string, range: string): this;
|
|
72
98
|
/** Negate a filter: not.eq, not.in, not.is, etc. */
|
|
73
99
|
not(column: string, operator: string, value: any): this;
|
|
74
100
|
/** OR filter: or("age.gt.20,name.eq.John") */
|
|
75
|
-
or(filters: string
|
|
76
|
-
|
|
101
|
+
or(filters: string, options?: {
|
|
102
|
+
foreignTable?: string;
|
|
103
|
+
}): this;
|
|
104
|
+
/** AND filter: and("age.gt.20,name.eq.John") */
|
|
105
|
+
and(filters: string, options?: {
|
|
106
|
+
foreignTable?: string;
|
|
107
|
+
}): this;
|
|
108
|
+
/** Generic filter: filter("column", "operator", "value") */
|
|
109
|
+
filter(column: string, operator: string, value: any): this;
|
|
110
|
+
/** Full-text search (to_tsquery, plainto_tsquery, phraseto_tsquery, or websearch_to_tsquery) */
|
|
77
111
|
textSearch(column: string, query: string, options?: {
|
|
78
112
|
type?: "plain" | "phrase" | "websearch";
|
|
113
|
+
config?: string;
|
|
79
114
|
}): this;
|
|
80
115
|
order(column: string, options?: {
|
|
81
116
|
ascending?: boolean;
|
|
82
117
|
nullsFirst?: boolean;
|
|
118
|
+
foreignTable?: string;
|
|
83
119
|
}): this;
|
|
120
|
+
/** Limit results on a foreign table */
|
|
121
|
+
limitForeignTable(count: number, foreignTable: string): this;
|
|
122
|
+
/** Offset results on a foreign table */
|
|
123
|
+
offsetForeignTable(count: number, foreignTable: string): this;
|
|
84
124
|
limit(count: number): this;
|
|
85
125
|
offset(count: number): this;
|
|
86
126
|
/** Request exact, estimated, or planned count via Prefer header */
|
package/dist/index.js
CHANGED
|
@@ -20,16 +20,16 @@ var DatabaseClient = class {
|
|
|
20
20
|
// ─── Query Methods ──────────────────────────────────────
|
|
21
21
|
/** Select columns. Supports nested joins: "*, comments(*)" and aliases: "full_name:name" */
|
|
22
22
|
select(columns, options) {
|
|
23
|
-
this.method = "GET";
|
|
24
|
-
if (columns) this.params.set("select", columns);
|
|
23
|
+
this.method = options?.head ? "HEAD" : "GET";
|
|
24
|
+
if (columns && columns !== "*") this.params.set("select", columns);
|
|
25
25
|
if (options?.count) this.preferHeaders.push(`count=${options.count}`);
|
|
26
|
-
if (options?.head) this.method = "GET";
|
|
27
26
|
return this;
|
|
28
27
|
}
|
|
29
|
-
insert(data) {
|
|
28
|
+
insert(data, options) {
|
|
30
29
|
this.method = "POST";
|
|
31
30
|
this.body = data;
|
|
32
31
|
this.preferHeaders.push("return=representation");
|
|
32
|
+
if (options?.defaultToNull === false) this.preferHeaders.push("missing=default");
|
|
33
33
|
return this;
|
|
34
34
|
}
|
|
35
35
|
/** Upsert: insert or update on conflict. Uses merge-duplicates by default. */
|
|
@@ -43,6 +43,7 @@ var DatabaseClient = class {
|
|
|
43
43
|
if (options?.onConflict) {
|
|
44
44
|
this.params.set("on_conflict", options.onConflict);
|
|
45
45
|
}
|
|
46
|
+
if (options?.defaultToNull === false) this.preferHeaders.push("missing=default");
|
|
46
47
|
return this;
|
|
47
48
|
}
|
|
48
49
|
update(data) {
|
|
@@ -51,8 +52,28 @@ var DatabaseClient = class {
|
|
|
51
52
|
this.preferHeaders.push("return=representation");
|
|
52
53
|
return this;
|
|
53
54
|
}
|
|
54
|
-
delete() {
|
|
55
|
+
delete(options) {
|
|
55
56
|
this.method = "DELETE";
|
|
57
|
+
if (options?.count) this.preferHeaders.push(`count=${options.count}`);
|
|
58
|
+
return this;
|
|
59
|
+
}
|
|
60
|
+
/** Specify return=minimal to skip returning data on mutations (better performance). */
|
|
61
|
+
returning(mode = "representation") {
|
|
62
|
+
this.preferHeaders = this.preferHeaders.filter((h) => !h.startsWith("return="));
|
|
63
|
+
this.preferHeaders.push(`return=${mode}`);
|
|
64
|
+
return this;
|
|
65
|
+
}
|
|
66
|
+
/** Get the query execution plan (EXPLAIN). */
|
|
67
|
+
explain(options) {
|
|
68
|
+
const parts = ["plan"];
|
|
69
|
+
if (options?.analyze) parts.push("analyze");
|
|
70
|
+
if (options?.verbose) parts.push("verbose");
|
|
71
|
+
if (options?.settings) parts.push("settings");
|
|
72
|
+
if (options?.buffers) parts.push("buffers");
|
|
73
|
+
if (options?.wal) parts.push("wal");
|
|
74
|
+
if (options?.format) parts.push(`format:${options.format}`);
|
|
75
|
+
this.preferHeaders = this.preferHeaders.filter((h) => !h.startsWith("plan"));
|
|
76
|
+
this.preferHeaders.push(parts.join("|"));
|
|
56
77
|
return this;
|
|
57
78
|
}
|
|
58
79
|
// ─── Comparison Filters ─────────────────────────────────
|
|
@@ -136,6 +157,32 @@ var DatabaseClient = class {
|
|
|
136
157
|
this.params.set(column, `isdistinct.${value}`);
|
|
137
158
|
return this;
|
|
138
159
|
}
|
|
160
|
+
// ─── Range Type Filters ──────────────────────────────────
|
|
161
|
+
/** Strictly left of: [a,b] << [c,d] */
|
|
162
|
+
rangeLt(column, range) {
|
|
163
|
+
this.params.set(column, `sl.${range}`);
|
|
164
|
+
return this;
|
|
165
|
+
}
|
|
166
|
+
/** Strictly right of: [a,b] >> [c,d] */
|
|
167
|
+
rangeGt(column, range) {
|
|
168
|
+
this.params.set(column, `sr.${range}`);
|
|
169
|
+
return this;
|
|
170
|
+
}
|
|
171
|
+
/** Does not extend to the left of */
|
|
172
|
+
rangeGte(column, range) {
|
|
173
|
+
this.params.set(column, `nxl.${range}`);
|
|
174
|
+
return this;
|
|
175
|
+
}
|
|
176
|
+
/** Does not extend to the right of */
|
|
177
|
+
rangeLte(column, range) {
|
|
178
|
+
this.params.set(column, `nxr.${range}`);
|
|
179
|
+
return this;
|
|
180
|
+
}
|
|
181
|
+
/** Range is adjacent to */
|
|
182
|
+
rangeAdjacent(column, range) {
|
|
183
|
+
this.params.set(column, `adj.${range}`);
|
|
184
|
+
return this;
|
|
185
|
+
}
|
|
139
186
|
// ─── Negation ───────────────────────────────────────────
|
|
140
187
|
/** Negate a filter: not.eq, not.in, not.is, etc. */
|
|
141
188
|
not(column, operator, value) {
|
|
@@ -144,29 +191,53 @@ var DatabaseClient = class {
|
|
|
144
191
|
}
|
|
145
192
|
// ─── Logical ────────────────────────────────────────────
|
|
146
193
|
/** OR filter: or("age.gt.20,name.eq.John") */
|
|
147
|
-
or(filters) {
|
|
148
|
-
|
|
194
|
+
or(filters, options) {
|
|
195
|
+
const key = options?.foreignTable ? `${options.foreignTable}.or` : "or";
|
|
196
|
+
this.params.set(key, `(${filters})`);
|
|
197
|
+
return this;
|
|
198
|
+
}
|
|
199
|
+
/** AND filter: and("age.gt.20,name.eq.John") */
|
|
200
|
+
and(filters, options) {
|
|
201
|
+
const key = options?.foreignTable ? `${options.foreignTable}.and` : "and";
|
|
202
|
+
this.params.set(key, `(${filters})`);
|
|
203
|
+
return this;
|
|
204
|
+
}
|
|
205
|
+
/** Generic filter: filter("column", "operator", "value") */
|
|
206
|
+
filter(column, operator, value) {
|
|
207
|
+
this.params.set(column, `${operator}.${value}`);
|
|
149
208
|
return this;
|
|
150
209
|
}
|
|
151
210
|
// ─── Full-Text Search ───────────────────────────────────
|
|
152
|
-
/** to_tsquery */
|
|
211
|
+
/** Full-text search (to_tsquery, plainto_tsquery, phraseto_tsquery, or websearch_to_tsquery) */
|
|
153
212
|
textSearch(column, query, options) {
|
|
154
213
|
const op = options?.type === "plain" ? "plfts" : options?.type === "phrase" ? "phfts" : options?.type === "websearch" ? "wfts" : "fts";
|
|
155
|
-
|
|
214
|
+
const config = options?.config ? `(${options.config})` : "";
|
|
215
|
+
this.params.set(column, `${op}${config}.${query}`);
|
|
156
216
|
return this;
|
|
157
217
|
}
|
|
158
218
|
// ─── Ordering & Pagination ──────────────────────────────
|
|
159
219
|
order(column, options) {
|
|
160
220
|
const dir = options?.ascending === false ? "desc" : "asc";
|
|
161
221
|
const nulls = options?.nullsFirst === true ? ".nullsfirst" : options?.nullsFirst === false ? ".nullslast" : "";
|
|
162
|
-
const
|
|
222
|
+
const key = options?.foreignTable ? `${options.foreignTable}.order` : "order";
|
|
223
|
+
const existing = this.params.get(key);
|
|
163
224
|
if (existing) {
|
|
164
|
-
this.params.set(
|
|
225
|
+
this.params.set(key, `${existing},${column}.${dir}${nulls}`);
|
|
165
226
|
} else {
|
|
166
|
-
this.params.set(
|
|
227
|
+
this.params.set(key, `${column}.${dir}${nulls}`);
|
|
167
228
|
}
|
|
168
229
|
return this;
|
|
169
230
|
}
|
|
231
|
+
/** Limit results on a foreign table */
|
|
232
|
+
limitForeignTable(count, foreignTable) {
|
|
233
|
+
this.params.set(`${foreignTable}.limit`, String(count));
|
|
234
|
+
return this;
|
|
235
|
+
}
|
|
236
|
+
/** Offset results on a foreign table */
|
|
237
|
+
offsetForeignTable(count, foreignTable) {
|
|
238
|
+
this.params.set(`${foreignTable}.offset`, String(count));
|
|
239
|
+
return this;
|
|
240
|
+
}
|
|
170
241
|
limit(count) {
|
|
171
242
|
this.params.set("limit", String(count));
|
|
172
243
|
return this;
|
|
@@ -237,6 +308,7 @@ var DatabaseClient = class {
|
|
|
237
308
|
async execute() {
|
|
238
309
|
const qs = this.params.toString();
|
|
239
310
|
const path = `/rest/v1/${this.table}${qs ? `?${qs}` : ""}`;
|
|
311
|
+
const isHead = this.method === "HEAD";
|
|
240
312
|
const isSingle = this._single;
|
|
241
313
|
const isMaybeSingle = this._maybeSingle;
|
|
242
314
|
const shouldThrow = this._throwOnError;
|
|
@@ -269,8 +341,11 @@ var DatabaseClient = class {
|
|
|
269
341
|
body: this.body ? JSON.stringify(this.body) : void 0,
|
|
270
342
|
signal: abortSignal
|
|
271
343
|
});
|
|
272
|
-
const countHeader = res.headers.get("X-Total-Count");
|
|
344
|
+
const countHeader = res.headers.get("X-Total-Count") || res.headers.get("content-range")?.split("/")[1];
|
|
273
345
|
const totalCount = countHeader ? parseInt(countHeader) : null;
|
|
346
|
+
if (isHead) {
|
|
347
|
+
return { data: null, error: res.ok ? null : { message: res.statusText }, count: totalCount };
|
|
348
|
+
}
|
|
274
349
|
if (isCsv) {
|
|
275
350
|
if (!res.ok) {
|
|
276
351
|
const text = await res.text();
|