@linabase/js 0.3.1 → 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 CHANGED
@@ -57,10 +57,11 @@ var DatabaseClient = class {
57
57
  if (options?.count) this.preferHeaders.push(`count=${options.count}`);
58
58
  return this;
59
59
  }
60
- insert(data) {
60
+ insert(data, options) {
61
61
  this.method = "POST";
62
62
  this.body = data;
63
63
  this.preferHeaders.push("return=representation");
64
+ if (options?.defaultToNull === false) this.preferHeaders.push("missing=default");
64
65
  return this;
65
66
  }
66
67
  /** Upsert: insert or update on conflict. Uses merge-duplicates by default. */
@@ -74,6 +75,7 @@ var DatabaseClient = class {
74
75
  if (options?.onConflict) {
75
76
  this.params.set("on_conflict", options.onConflict);
76
77
  }
78
+ if (options?.defaultToNull === false) this.preferHeaders.push("missing=default");
77
79
  return this;
78
80
  }
79
81
  update(data) {
@@ -82,8 +84,28 @@ var DatabaseClient = class {
82
84
  this.preferHeaders.push("return=representation");
83
85
  return this;
84
86
  }
85
- delete() {
87
+ delete(options) {
86
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("|"));
87
109
  return this;
88
110
  }
89
111
  // ─── Comparison Filters ─────────────────────────────────
@@ -167,6 +189,32 @@ var DatabaseClient = class {
167
189
  this.params.set(column, `isdistinct.${value}`);
168
190
  return this;
169
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
+ }
170
218
  // ─── Negation ───────────────────────────────────────────
171
219
  /** Negate a filter: not.eq, not.in, not.is, etc. */
172
220
  not(column, operator, value) {
@@ -175,29 +223,53 @@ var DatabaseClient = class {
175
223
  }
176
224
  // ─── Logical ────────────────────────────────────────────
177
225
  /** OR filter: or("age.gt.20,name.eq.John") */
178
- or(filters) {
179
- this.params.set("or", `(${filters})`);
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}`);
180
240
  return this;
181
241
  }
182
242
  // ─── Full-Text Search ───────────────────────────────────
183
- /** to_tsquery */
243
+ /** Full-text search (to_tsquery, plainto_tsquery, phraseto_tsquery, or websearch_to_tsquery) */
184
244
  textSearch(column, query, options) {
185
245
  const op = options?.type === "plain" ? "plfts" : options?.type === "phrase" ? "phfts" : options?.type === "websearch" ? "wfts" : "fts";
186
- this.params.set(column, `${op}.${query}`);
246
+ const config = options?.config ? `(${options.config})` : "";
247
+ this.params.set(column, `${op}${config}.${query}`);
187
248
  return this;
188
249
  }
189
250
  // ─── Ordering & Pagination ──────────────────────────────
190
251
  order(column, options) {
191
252
  const dir = options?.ascending === false ? "desc" : "asc";
192
253
  const nulls = options?.nullsFirst === true ? ".nullsfirst" : options?.nullsFirst === false ? ".nullslast" : "";
193
- const existing = this.params.get("order");
254
+ const key = options?.foreignTable ? `${options.foreignTable}.order` : "order";
255
+ const existing = this.params.get(key);
194
256
  if (existing) {
195
- this.params.set("order", `${existing},${column}.${dir}${nulls}`);
257
+ this.params.set(key, `${existing},${column}.${dir}${nulls}`);
196
258
  } else {
197
- this.params.set("order", `${column}.${dir}${nulls}`);
259
+ this.params.set(key, `${column}.${dir}${nulls}`);
198
260
  }
199
261
  return this;
200
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
+ }
201
273
  limit(count) {
202
274
  this.params.set("limit", String(count));
203
275
  return this;
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>[]): this;
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(): this;
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): this;
76
- /** to_tsquery */
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>[]): this;
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(): this;
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): this;
76
- /** to_tsquery */
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
@@ -25,10 +25,11 @@ var DatabaseClient = class {
25
25
  if (options?.count) this.preferHeaders.push(`count=${options.count}`);
26
26
  return this;
27
27
  }
28
- insert(data) {
28
+ insert(data, options) {
29
29
  this.method = "POST";
30
30
  this.body = data;
31
31
  this.preferHeaders.push("return=representation");
32
+ if (options?.defaultToNull === false) this.preferHeaders.push("missing=default");
32
33
  return this;
33
34
  }
34
35
  /** Upsert: insert or update on conflict. Uses merge-duplicates by default. */
@@ -42,6 +43,7 @@ var DatabaseClient = class {
42
43
  if (options?.onConflict) {
43
44
  this.params.set("on_conflict", options.onConflict);
44
45
  }
46
+ if (options?.defaultToNull === false) this.preferHeaders.push("missing=default");
45
47
  return this;
46
48
  }
47
49
  update(data) {
@@ -50,8 +52,28 @@ var DatabaseClient = class {
50
52
  this.preferHeaders.push("return=representation");
51
53
  return this;
52
54
  }
53
- delete() {
55
+ delete(options) {
54
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("|"));
55
77
  return this;
56
78
  }
57
79
  // ─── Comparison Filters ─────────────────────────────────
@@ -135,6 +157,32 @@ var DatabaseClient = class {
135
157
  this.params.set(column, `isdistinct.${value}`);
136
158
  return this;
137
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
+ }
138
186
  // ─── Negation ───────────────────────────────────────────
139
187
  /** Negate a filter: not.eq, not.in, not.is, etc. */
140
188
  not(column, operator, value) {
@@ -143,29 +191,53 @@ var DatabaseClient = class {
143
191
  }
144
192
  // ─── Logical ────────────────────────────────────────────
145
193
  /** OR filter: or("age.gt.20,name.eq.John") */
146
- or(filters) {
147
- this.params.set("or", `(${filters})`);
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}`);
148
208
  return this;
149
209
  }
150
210
  // ─── Full-Text Search ───────────────────────────────────
151
- /** to_tsquery */
211
+ /** Full-text search (to_tsquery, plainto_tsquery, phraseto_tsquery, or websearch_to_tsquery) */
152
212
  textSearch(column, query, options) {
153
213
  const op = options?.type === "plain" ? "plfts" : options?.type === "phrase" ? "phfts" : options?.type === "websearch" ? "wfts" : "fts";
154
- this.params.set(column, `${op}.${query}`);
214
+ const config = options?.config ? `(${options.config})` : "";
215
+ this.params.set(column, `${op}${config}.${query}`);
155
216
  return this;
156
217
  }
157
218
  // ─── Ordering & Pagination ──────────────────────────────
158
219
  order(column, options) {
159
220
  const dir = options?.ascending === false ? "desc" : "asc";
160
221
  const nulls = options?.nullsFirst === true ? ".nullsfirst" : options?.nullsFirst === false ? ".nullslast" : "";
161
- const existing = this.params.get("order");
222
+ const key = options?.foreignTable ? `${options.foreignTable}.order` : "order";
223
+ const existing = this.params.get(key);
162
224
  if (existing) {
163
- this.params.set("order", `${existing},${column}.${dir}${nulls}`);
225
+ this.params.set(key, `${existing},${column}.${dir}${nulls}`);
164
226
  } else {
165
- this.params.set("order", `${column}.${dir}${nulls}`);
227
+ this.params.set(key, `${column}.${dir}${nulls}`);
166
228
  }
167
229
  return this;
168
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
+ }
169
241
  limit(count) {
170
242
  this.params.set("limit", String(count));
171
243
  return this;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@linabase/js",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "description": "JavaScript/TypeScript client SDK for Linabase (database, storage, auth)",
5
5
  "license": "MIT",
6
6
  "type": "module",