arn-db 0.0.0-stage → 0.2.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/GUIDE.md +829 -0
- package/README.md +265 -2
- package/dist/index.cjs +1112 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +899 -0
- package/dist/index.d.mts +899 -0
- package/dist/index.mjs +1098 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +48 -4
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,899 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Values the database fills in: another column of the same row (`db.col`)
|
|
3
|
+
* and the database's current time (`db.now`).
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Another column of the same row. As a filter value it compares two columns;
|
|
7
|
+
* as the column of a filter it lets `ifNull()` replace NULL first.
|
|
8
|
+
*
|
|
9
|
+
* .lt('sent_per_hour', db.col('limit_per_hour'))
|
|
10
|
+
* .lt(db.col('sent_per_hour').ifNull(0), db.col('limit_per_hour'))
|
|
11
|
+
*/
|
|
12
|
+
export declare class Col {
|
|
13
|
+
#private;
|
|
14
|
+
readonly name: string;
|
|
15
|
+
constructor(name: string, ifNull?: {
|
|
16
|
+
value: unknown;
|
|
17
|
+
});
|
|
18
|
+
/** Use `value` where the column is NULL: `COALESCE(column, value)`. */
|
|
19
|
+
ifNull(value: unknown): Col;
|
|
20
|
+
/** @internal The NULL replacement, if any. */
|
|
21
|
+
nullValue(): {
|
|
22
|
+
value: unknown;
|
|
23
|
+
} | undefined;
|
|
24
|
+
/** @internal Wire format. */
|
|
25
|
+
toJSON(): {
|
|
26
|
+
$col: string;
|
|
27
|
+
ifNull?: unknown;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The database server's `now()`, optionally shifted by one interval like
|
|
32
|
+
* `'1 hour'` or `'30 minutes'` (units: second, minute, hour, day, week,
|
|
33
|
+
* month, year). Works in filters and in insert/update values.
|
|
34
|
+
*
|
|
35
|
+
* .lte('client_sent_time', db.now().minus('1 hour'))
|
|
36
|
+
* .update({ last_read_time: db.now() })
|
|
37
|
+
*/
|
|
38
|
+
export declare class Now {
|
|
39
|
+
#private;
|
|
40
|
+
constructor(interval?: string);
|
|
41
|
+
/** Later by `interval`: `db.now().plus('1 day')`. */
|
|
42
|
+
plus(interval: string): Now;
|
|
43
|
+
/** Earlier by `interval`: `db.now().minus('1 hour')`. */
|
|
44
|
+
minus(interval: string): Now;
|
|
45
|
+
/** @internal Wire format. */
|
|
46
|
+
toJSON(): {
|
|
47
|
+
$now: true;
|
|
48
|
+
interval?: string;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/** A filter's column: a name, or `db.col(name).ifNull(value)`. */
|
|
52
|
+
type Column = string | Col;
|
|
53
|
+
export declare const col: (name: string) => Col;
|
|
54
|
+
export declare const now: () => Now;
|
|
55
|
+
/**
|
|
56
|
+
* A raw SQL fragment: one expression inside the query the builder makes, with
|
|
57
|
+
* `?` for each binding (`\\?` is a literal `?`, e.g. the jsonb `?` operator).
|
|
58
|
+
* Bindings are sent as query parameters, never pasted into the SQL. Use it in
|
|
59
|
+
* `where()`, `order()`, `having()`, as a `select()` column with `.as()`, and as
|
|
60
|
+
* an insert/update/upsert value or upsert `merge` value. The API key must be
|
|
61
|
+
* allowed to use raw fragments.
|
|
62
|
+
*
|
|
63
|
+
* .where(db.raw('lower(email) = any(?)', [emails]))
|
|
64
|
+
* .update({ sent_total: db.raw('sent_total + ?', [1]) })
|
|
65
|
+
*/
|
|
66
|
+
export declare class Raw {
|
|
67
|
+
#private;
|
|
68
|
+
readonly sql: string;
|
|
69
|
+
readonly bindings: unknown[];
|
|
70
|
+
constructor(sql: string, bindings?: unknown[], as?: string);
|
|
71
|
+
/** Name of this fragment as a `select()` column: `raw('char_length(title)').as('title_len')`. */
|
|
72
|
+
as(name: string): Raw;
|
|
73
|
+
/** @internal The `.as()` name, if any. */
|
|
74
|
+
alias(): string | undefined;
|
|
75
|
+
/** @internal The fragment without its name, for filters, order and having. */
|
|
76
|
+
fragment(): {
|
|
77
|
+
$raw: string;
|
|
78
|
+
bindings: unknown[];
|
|
79
|
+
};
|
|
80
|
+
/** @internal Wire format. */
|
|
81
|
+
toJSON(): {
|
|
82
|
+
$raw: string;
|
|
83
|
+
bindings: unknown[];
|
|
84
|
+
as?: string;
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* A raw SQL fragment with `?` bindings: knex's `whereRaw` / `orderByRaw` /
|
|
89
|
+
* `havingRaw` / `knex.raw` value. Throws if `sql` is not a non-empty string
|
|
90
|
+
* or `bindings` not an array; the server checks the rest.
|
|
91
|
+
*
|
|
92
|
+
* @example
|
|
93
|
+
* db.from('users').select().where(raw('lower(trim(email)) = any(?)', [emails]))
|
|
94
|
+
*/
|
|
95
|
+
export declare const raw: (sql: string, bindings?: unknown[]) => Raw;
|
|
96
|
+
/**
|
|
97
|
+
* Atomic update operator, applied by Postgres to the current column value.
|
|
98
|
+
*
|
|
99
|
+
* update({ scores: ops.append(4), meta: ops.merge({ vip: true }), age: ops.increment() })
|
|
100
|
+
*/
|
|
101
|
+
export declare class Op {
|
|
102
|
+
readonly $op: string;
|
|
103
|
+
readonly values: unknown[];
|
|
104
|
+
readonly path: string[];
|
|
105
|
+
constructor(op: string, values: unknown[], path?: string[]);
|
|
106
|
+
/** Apply at a JSON path inside a json/jsonb column: `ops.append('x').at('address', 'lines')`. */
|
|
107
|
+
at(...path: (string | number)[]): Op;
|
|
108
|
+
}
|
|
109
|
+
export declare const ops: {
|
|
110
|
+
/**
|
|
111
|
+
* Add values at the end of a jsonb array or Postgres array. An array argument is added as one element.
|
|
112
|
+
* @example
|
|
113
|
+
* update({ scores: ops.append(4) }) // [1,2,3] -> [1,2,3,4]
|
|
114
|
+
* update({ meta: ops.append('y').at('tags') }) // inside a jsonb object
|
|
115
|
+
*/
|
|
116
|
+
append: (...values: unknown[]) => Op;
|
|
117
|
+
/**
|
|
118
|
+
* Add values at the start of a jsonb array or Postgres array.
|
|
119
|
+
* @example
|
|
120
|
+
* update({ scores: ops.prepend(0) }) // [1,2,3] -> [0,1,2,3]
|
|
121
|
+
*/
|
|
122
|
+
prepend: (...values: unknown[]) => Op;
|
|
123
|
+
/**
|
|
124
|
+
* Remove every element equal to one of the values.
|
|
125
|
+
* @example
|
|
126
|
+
* update({ scores: ops.remove(2) }) // [1,2,3] -> [1,3]
|
|
127
|
+
*/
|
|
128
|
+
remove: (...values: unknown[]) => Op;
|
|
129
|
+
/**
|
|
130
|
+
* Shallow-merge keys into a json/jsonb object.
|
|
131
|
+
* @example
|
|
132
|
+
* update({ meta: ops.merge({ vip: true }) }) // {a:1} -> {a:1,vip:true}
|
|
133
|
+
*/
|
|
134
|
+
merge: (object: Row) => Op;
|
|
135
|
+
/**
|
|
136
|
+
* Set a value at a JSON path (needs `.at(...)`); a number is an array position.
|
|
137
|
+
* @example
|
|
138
|
+
* update({ meta: ops.set('Lisbon').at('city') })
|
|
139
|
+
* update({ scores: ops.set(9).at(0) }) // [1,2,3] -> [9,2,3]
|
|
140
|
+
*/
|
|
141
|
+
set: (value: unknown) => Op;
|
|
142
|
+
/**
|
|
143
|
+
* Remove the key (or array position) at a JSON path (needs `.at(...)`).
|
|
144
|
+
* @example
|
|
145
|
+
* update({ meta: ops.unset().at('vip') })
|
|
146
|
+
* update({ scores: ops.unset().at(0) }) // [1,2,3] -> [2,3]
|
|
147
|
+
*/
|
|
148
|
+
unset: () => Op;
|
|
149
|
+
/**
|
|
150
|
+
* Add to a number column, or to a number at a JSON path (negative to subtract).
|
|
151
|
+
* @example
|
|
152
|
+
* update({ age: ops.increment() })
|
|
153
|
+
* update({ meta: ops.increment(5).at('points') })
|
|
154
|
+
*/
|
|
155
|
+
increment: (by?: number) => Op;
|
|
156
|
+
};
|
|
157
|
+
/** A table row. Pass your own row type to `from<T>()` for typed results. */
|
|
158
|
+
type Row = Record<string, any>;
|
|
159
|
+
interface PgError {
|
|
160
|
+
message: string;
|
|
161
|
+
/** Postgres SQLSTATE, e.g. "23505" for a unique violation. */
|
|
162
|
+
code?: string;
|
|
163
|
+
details?: string;
|
|
164
|
+
/** Failing step index (0-based) when a transaction fails. */
|
|
165
|
+
step?: number;
|
|
166
|
+
/** That step's `.as()` name, if any. */
|
|
167
|
+
stepName?: string;
|
|
168
|
+
}
|
|
169
|
+
type QueryResult<R> = {
|
|
170
|
+
data: R;
|
|
171
|
+
error: null;
|
|
172
|
+
count: number | null;
|
|
173
|
+
status: number;
|
|
174
|
+
} | {
|
|
175
|
+
data: null;
|
|
176
|
+
error: PgError;
|
|
177
|
+
count: null;
|
|
178
|
+
status: number;
|
|
179
|
+
};
|
|
180
|
+
type FilterOp = 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'like' | 'ilike' | 'is' | 'in' | 'cs' | 'cd' | 'ov';
|
|
181
|
+
/** One condition: `column op value`. */
|
|
182
|
+
interface ConditionFilter {
|
|
183
|
+
column: string;
|
|
184
|
+
op: FilterOp;
|
|
185
|
+
value: unknown;
|
|
186
|
+
/** Compare `COALESCE(column, ifNull)` instead of the column. */
|
|
187
|
+
ifNull?: unknown;
|
|
188
|
+
}
|
|
189
|
+
/** Conditions joined with AND (`and`, `not`) or OR (`or`); `not` negates the group. */
|
|
190
|
+
interface GroupFilter {
|
|
191
|
+
group: 'and' | 'or' | 'not';
|
|
192
|
+
filters: Filter[];
|
|
193
|
+
}
|
|
194
|
+
/** A raw SQL fragment on the wire: `?` placeholders and their values. */
|
|
195
|
+
interface RawFragment {
|
|
196
|
+
$raw: string;
|
|
197
|
+
bindings: unknown[];
|
|
198
|
+
}
|
|
199
|
+
/** A `where(raw(...))` condition: `AND (<fragment>)`, or one member of an OR group. */
|
|
200
|
+
interface RawFilter {
|
|
201
|
+
op: 'raw';
|
|
202
|
+
value: RawFragment;
|
|
203
|
+
}
|
|
204
|
+
type Filter = ConditionFilter | GroupFilter | RawFilter;
|
|
205
|
+
interface Order {
|
|
206
|
+
column?: string;
|
|
207
|
+
/** `order(raw(...))`: sort by the fragment instead of a column. */
|
|
208
|
+
raw?: RawFragment;
|
|
209
|
+
ascending?: boolean;
|
|
210
|
+
nullsFirst?: boolean;
|
|
211
|
+
random?: boolean;
|
|
212
|
+
seed?: string;
|
|
213
|
+
/** Embed name or alias; omitted for the main table. */
|
|
214
|
+
table?: string;
|
|
215
|
+
}
|
|
216
|
+
/** Wire format of one request to the Go server. */
|
|
217
|
+
interface QueryRequest {
|
|
218
|
+
table: string;
|
|
219
|
+
action: 'select' | 'insert' | 'update' | 'delete';
|
|
220
|
+
columns: string;
|
|
221
|
+
filters: Filter[];
|
|
222
|
+
order: Order[];
|
|
223
|
+
limit?: number;
|
|
224
|
+
offset?: number;
|
|
225
|
+
embedLimits?: Record<string, number>;
|
|
226
|
+
values?: Row[];
|
|
227
|
+
returning: boolean;
|
|
228
|
+
count: boolean;
|
|
229
|
+
head?: boolean;
|
|
230
|
+
countOnly?: boolean;
|
|
231
|
+
single?: 'single';
|
|
232
|
+
skipLocked?: boolean;
|
|
233
|
+
upsert?: boolean;
|
|
234
|
+
onConflict?: string[];
|
|
235
|
+
ignoreDuplicates?: boolean;
|
|
236
|
+
merge?: Record<string, MergeMode | Op | Raw>;
|
|
237
|
+
groupBy?: string[];
|
|
238
|
+
/** `having(raw(...))` fragments, joined with AND. Only with `groupBy`. */
|
|
239
|
+
having?: RawFragment[];
|
|
240
|
+
/** `select(..., raw(...).as(name))` columns of the main table. */
|
|
241
|
+
rawColumns?: (RawFragment & {
|
|
242
|
+
as: string;
|
|
243
|
+
})[];
|
|
244
|
+
distinct?: boolean;
|
|
245
|
+
aggregate?: {
|
|
246
|
+
fn: AggregateFn;
|
|
247
|
+
column: string;
|
|
248
|
+
distinct?: boolean;
|
|
249
|
+
};
|
|
250
|
+
readFrom?: ReadFrom;
|
|
251
|
+
}
|
|
252
|
+
type AggregateFn = 'count' | 'sum' | 'avg' | 'min' | 'max';
|
|
253
|
+
/**
|
|
254
|
+
* How `upsert()`'s `merge` changes a column of an existing row:
|
|
255
|
+
* `'set'` the incoming value, `'add'` stored + incoming (NULL counts as 0),
|
|
256
|
+
* `'append'` / `'prepend'` join stored and incoming jsonb or Postgres arrays.
|
|
257
|
+
*/
|
|
258
|
+
type MergeMode = 'set' | 'add' | 'append' | 'prepend';
|
|
259
|
+
/** Where a read runs: the primary, or a read replica when the server has one. */
|
|
260
|
+
type ReadFrom = 'primary' | 'replica';
|
|
261
|
+
interface UpsertOptions {
|
|
262
|
+
/** Columns with a unique constraint that identify the row, e.g. `'email'` or `'user_id,title'`. Default: primary key. */
|
|
263
|
+
onConflict?: string;
|
|
264
|
+
/**
|
|
265
|
+
* `true`: find-or-create. An existing row is left unchanged and returned.
|
|
266
|
+
* `false` (default): insert, or update the existing row with these values.
|
|
267
|
+
*/
|
|
268
|
+
ignoreDuplicates?: boolean;
|
|
269
|
+
/**
|
|
270
|
+
* Change only these columns of an existing row; the rest keep their stored
|
|
271
|
+
* value. Each is a {@link MergeMode} or a `db.ops` value applied to the stored
|
|
272
|
+
* value, or a `db.raw` fragment (`excluded.col` is the incoming value). A
|
|
273
|
+
* list of names means `'set'` for each. Without `merge`, every column you
|
|
274
|
+
* send (except the conflict key) is replaced.
|
|
275
|
+
*
|
|
276
|
+
* @example
|
|
277
|
+
* merge: { total: 'add', email: 'set', hits: db.ops.increment(1) }
|
|
278
|
+
* merge: { total: db.raw('greatest(accounts.total, excluded.total)') }
|
|
279
|
+
* merge: ['email', 'name']
|
|
280
|
+
*/
|
|
281
|
+
merge?: Record<string, MergeMode | Op | Raw> | string[];
|
|
282
|
+
}
|
|
283
|
+
/** `select()` options: `count: 'exact'` also fills `count`; `head: true` skips the rows. */
|
|
284
|
+
interface SelectOptions {
|
|
285
|
+
count?: 'exact';
|
|
286
|
+
head?: boolean;
|
|
287
|
+
}
|
|
288
|
+
/** Options for `order()`, `random()` and `limit()` that target an embedded table. */
|
|
289
|
+
interface ReferencedTableOption {
|
|
290
|
+
referencedTable?: string;
|
|
291
|
+
/** Older supabase-js name for `referencedTable`. */
|
|
292
|
+
foreignTable?: string;
|
|
293
|
+
}
|
|
294
|
+
interface Transport {
|
|
295
|
+
post(path: string, body: unknown): Promise<QueryResult<any>>;
|
|
296
|
+
}
|
|
297
|
+
/** One step of a transaction, as sent to the server. */
|
|
298
|
+
interface TxStepRequest extends QueryRequest {
|
|
299
|
+
name?: string;
|
|
300
|
+
}
|
|
301
|
+
/** Filter methods, shared by builders and groups. */
|
|
302
|
+
declare abstract class Filters {
|
|
303
|
+
#private;
|
|
304
|
+
/** @internal A problem found while building, reported instead of sending. */
|
|
305
|
+
protected problem: string | undefined;
|
|
306
|
+
/** @internal Adds one filter or group. */
|
|
307
|
+
protected abstract push(filter: Filter): void;
|
|
308
|
+
/**
|
|
309
|
+
* Equal. Works on columns, jsonb paths and related tables.
|
|
310
|
+
*
|
|
311
|
+
* @example
|
|
312
|
+
* .eq('active', true)
|
|
313
|
+
* .eq('meta->>city', 'Tokyo')
|
|
314
|
+
* .eq('posts.published', true) // filters the related posts
|
|
315
|
+
*/
|
|
316
|
+
eq(column: Column, value: unknown): this;
|
|
317
|
+
/** Not equal. */
|
|
318
|
+
neq(column: Column, value: unknown): this;
|
|
319
|
+
/**
|
|
320
|
+
* Greater than. For numbers inside jsonb, cast: `.gt('meta->>score::int', 90)`.
|
|
321
|
+
* `eq` to `lte` also compare with another column (`db.col`) or the
|
|
322
|
+
* database time (`db.now`).
|
|
323
|
+
*
|
|
324
|
+
* @example
|
|
325
|
+
* .gt('age', 30)
|
|
326
|
+
* .lt('sent_per_hour', db.col('limit_per_hour'))
|
|
327
|
+
* .lt(db.col('sent_per_hour').ifNull(0), db.col('limit_per_hour'))
|
|
328
|
+
* .lte('client_sent_time', db.now().minus('1 hour'))
|
|
329
|
+
*/
|
|
330
|
+
gt(column: Column, value: unknown): this;
|
|
331
|
+
/** Greater than or equal. @example .gte('meta->>score::int', 90) */
|
|
332
|
+
gte(column: Column, value: unknown): this;
|
|
333
|
+
/** Less than. */
|
|
334
|
+
lt(column: Column, value: unknown): this;
|
|
335
|
+
/** Less than or equal. */
|
|
336
|
+
lte(column: Column, value: unknown): this;
|
|
337
|
+
/**
|
|
338
|
+
* SQL LIKE: `%` is any text, `_` is one character (escape with `\\_`).
|
|
339
|
+
*
|
|
340
|
+
* @example
|
|
341
|
+
* .like('name', 'user\\_1%')
|
|
342
|
+
*/
|
|
343
|
+
like(column: string, pattern: string): this;
|
|
344
|
+
/**
|
|
345
|
+
* Case-insensitive LIKE.
|
|
346
|
+
*
|
|
347
|
+
* @example
|
|
348
|
+
* .ilike('email', '%@example.com')
|
|
349
|
+
* .ilike('posts.comments.body', '%hello%') // search a deeper related table
|
|
350
|
+
*/
|
|
351
|
+
ilike(column: string, pattern: string): this;
|
|
352
|
+
/** IS NULL / IS TRUE / IS FALSE. @example .is('deleted_at', null) */
|
|
353
|
+
is(column: Column, value: null | boolean): this;
|
|
354
|
+
/** One of the values. @example .in('name', ['user_3', 'user_5']) */
|
|
355
|
+
in(column: string, values: readonly unknown[]): this;
|
|
356
|
+
/**
|
|
357
|
+
* Has ALL of the values (arrays), or has these keys and values (jsonb objects).
|
|
358
|
+
*
|
|
359
|
+
* @example
|
|
360
|
+
* .contains('tags', ['one', 'two'])
|
|
361
|
+
* .contains('meta', { city: 'Dhaka', vip: true })
|
|
362
|
+
*/
|
|
363
|
+
contains(column: string, value: unknown): this;
|
|
364
|
+
/**
|
|
365
|
+
* Has nothing outside the values.
|
|
366
|
+
*
|
|
367
|
+
* @example
|
|
368
|
+
* .containedBy('tags', ['one', 'two', 'three'])
|
|
369
|
+
*/
|
|
370
|
+
containedBy(column: string, value: unknown): this;
|
|
371
|
+
/**
|
|
372
|
+
* Has ANY of the values. Works on jsonb/json arrays, jsonb paths and Postgres arrays.
|
|
373
|
+
*
|
|
374
|
+
* @example
|
|
375
|
+
* .overlaps('scores', ['one', 'three'])
|
|
376
|
+
* .overlaps('meta->tags', ['red', 'blue'])
|
|
377
|
+
*/
|
|
378
|
+
overlaps(column: string, values: readonly unknown[]): this;
|
|
379
|
+
/** Any filter by name: `.filter('age', 'gte', 30)`. */
|
|
380
|
+
filter(column: Column, op: FilterOp, value: unknown): this;
|
|
381
|
+
/**
|
|
382
|
+
* Equal on every key.
|
|
383
|
+
*
|
|
384
|
+
* @example
|
|
385
|
+
* .match({ active: true, age: 30 })
|
|
386
|
+
*/
|
|
387
|
+
match(values: Row): this;
|
|
388
|
+
/**
|
|
389
|
+
* A raw SQL condition, knex's `whereRaw`: `AND (<fragment>)`, or one member
|
|
390
|
+
* of an `or()` group. Only takes `raw()`; for a column use `eq()` and friends.
|
|
391
|
+
* The fragment sees the main table's columns, not related tables.
|
|
392
|
+
*
|
|
393
|
+
* @example
|
|
394
|
+
* .where(db.raw('lower(trim(email)) = any(?)', [emails]))
|
|
395
|
+
* .where(db.raw('sent_per_hour < limit_per_hour'))
|
|
396
|
+
* .or((q) => q.where(db.raw('meta \\? ?', ['vip'])).eq('age', 30)) // jsonb ? operator
|
|
397
|
+
*/
|
|
398
|
+
where(fragment: Raw): this;
|
|
399
|
+
/**
|
|
400
|
+
* Any of the conditions.
|
|
401
|
+
*
|
|
402
|
+
* @example
|
|
403
|
+
* .or((q) => q.gt('age', 30).eq('meta->>city', 'Dhaka')) // age > 30 OR city = 'Dhaka'
|
|
404
|
+
* .or((q) => q.eq('role', 'admin').and((q) => q.gte('age', 18).is('verified', true)))
|
|
405
|
+
* .or((q) => q.eq('posts.published', true).gt('posts.likes', 10)) // filters the related posts
|
|
406
|
+
*/
|
|
407
|
+
or(conditions: (q: FilterGroup) => unknown): this;
|
|
408
|
+
/**
|
|
409
|
+
* All of the conditions, as one group. Mostly useful inside `or()`.
|
|
410
|
+
*
|
|
411
|
+
* @example
|
|
412
|
+
* .or((q) => q.eq('role', 'admin').and((q) => q.gte('age', 18).eq('active', true)))
|
|
413
|
+
*/
|
|
414
|
+
and(conditions: (q: FilterGroup) => unknown): this;
|
|
415
|
+
/**
|
|
416
|
+
* Not all of the conditions: `NOT (a AND b)`. Like Postgres, NULL is neither
|
|
417
|
+
* true nor false: a row with a null `age` matches neither `.gt('age', 30)`
|
|
418
|
+
* nor `.not('age', 'gt', 30)`.
|
|
419
|
+
*
|
|
420
|
+
* @example
|
|
421
|
+
* .not('deleted_at', 'is', null)
|
|
422
|
+
* .not((q) => q.eq('meta->>city', 'Tokyo').lt('age', 30))
|
|
423
|
+
* .not((q) => q.or((q) => q.eq('a', 1).eq('b', 2))) // neither a = 1 nor b = 2
|
|
424
|
+
*/
|
|
425
|
+
not(conditions: (q: FilterGroup) => unknown): this;
|
|
426
|
+
not(column: Column, op: FilterOp, value: unknown): this;
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* The conditions inside `or()`, `and()` or `not()`: every filter method, plus
|
|
430
|
+
* nested groups. Chain them on `q`; the callback's return value is ignored.
|
|
431
|
+
*/
|
|
432
|
+
export declare class FilterGroup extends Filters {
|
|
433
|
+
/** @internal */ readonly filters: Filter[];
|
|
434
|
+
/** @internal */
|
|
435
|
+
protected push(filter: Filter): void;
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* What reads and writes share: filters, step names and sending. Awaiting a
|
|
439
|
+
* builder sends the request and resolves to `{ data, error, count, status }`;
|
|
440
|
+
* it never throws. `T` is the row type, `R` the type of `data`.
|
|
441
|
+
*/
|
|
442
|
+
declare abstract class Builder<T extends Row, R> extends Filters implements PromiseLike<QueryResult<R>> {
|
|
443
|
+
#private;
|
|
444
|
+
/** @internal */ protected readonly transport: Transport | null;
|
|
445
|
+
/** @internal */ protected readonly req: QueryRequest;
|
|
446
|
+
/** @internal */
|
|
447
|
+
constructor(transport: Transport | null, req: QueryRequest, name?: string, problem?: string);
|
|
448
|
+
/** @internal */
|
|
449
|
+
protected push(filter: Filter): void;
|
|
450
|
+
/**
|
|
451
|
+
* Name a step in the array form of `transaction()`, so later steps can use
|
|
452
|
+
* its rows with `tx.ref('name.column')`. (In the object form the key is the name.)
|
|
453
|
+
*
|
|
454
|
+
* @example
|
|
455
|
+
* db.transaction((tx) => [
|
|
456
|
+
* tx.from('posts').insert({ title, user_id }).returning('id').as('post'),
|
|
457
|
+
* tx.from('comments').insert({ post_id: tx.ref('post.id'), body, author_id }),
|
|
458
|
+
* ])
|
|
459
|
+
*/
|
|
460
|
+
as(name: string): this;
|
|
461
|
+
/** @internal The `.as()` name, if any. */
|
|
462
|
+
stepName(): string | undefined;
|
|
463
|
+
/** @internal A problem found while building, reported instead of sending. */
|
|
464
|
+
invalid(): string | undefined;
|
|
465
|
+
/** @internal The request as sent to POST /query. */
|
|
466
|
+
protected request(): QueryRequest;
|
|
467
|
+
/** @internal The request as sent to the server. */
|
|
468
|
+
toStep(): TxStepRequest;
|
|
469
|
+
then<A = QueryResult<R>, B = never>(onfulfilled?: ((value: QueryResult<R>) => A | PromiseLike<A>) | null, onrejected?: ((reason: unknown) => B | PromiseLike<B>) | null): PromiseLike<A | B>;
|
|
470
|
+
}
|
|
471
|
+
/**
|
|
472
|
+
* Supabase-style query builder. Reads use `select()` and its modifiers;
|
|
473
|
+
* `insert()` / `upsert()` / `update()` / `delete()` turn it into a
|
|
474
|
+
* {@link WriteBuilder}. See GUIDE.md for recipes.
|
|
475
|
+
*
|
|
476
|
+
* @example
|
|
477
|
+
* const { data, error } = await db.from('users').select('id, name').eq('active', true).random().limit(5)
|
|
478
|
+
*/
|
|
479
|
+
export declare class QueryBuilder<T extends Row = Row, R = T[]> extends Builder<T, R> {
|
|
480
|
+
#private;
|
|
481
|
+
/** @internal Use `client.from()` or `tx.from()`. */
|
|
482
|
+
constructor(transport: Transport | null, table: string, readFrom?: ReadFrom);
|
|
483
|
+
/**
|
|
484
|
+
* Columns to return. `'*'` (the default) means all. Supports aliases, jsonb
|
|
485
|
+
* paths, casts, related tables and related counts.
|
|
486
|
+
*
|
|
487
|
+
* `{ count: 'exact' }` also fills `count`; add `head: true` to skip the rows.
|
|
488
|
+
*
|
|
489
|
+
* @example
|
|
490
|
+
* db.from('users').select() // all columns
|
|
491
|
+
* db.from('users').select('id, fullName:name, city:meta->>city, score:meta->>score::int')
|
|
492
|
+
* db.from('posts').select('title, author:users!user_id(name), comments(body), labels(name)')
|
|
493
|
+
* db.from('users').select('name, posts!user_id!inner(title)') // only users that have posts
|
|
494
|
+
* db.from('users').select('name, posts!user_id(count)') // posts: [{ count: 2 }]
|
|
495
|
+
* db.from('posts').select('*', { count: 'exact' }).range(0, 9) // rows + total
|
|
496
|
+
*
|
|
497
|
+
* Extra `raw(...).as(name)` arguments add SQL expressions of the main table
|
|
498
|
+
* as columns (options, if any, go last).
|
|
499
|
+
*
|
|
500
|
+
* @example
|
|
501
|
+
* db.from('posts').select('id, title', db.raw('char_length(title)').as('title_len'))
|
|
502
|
+
*/
|
|
503
|
+
select(columns?: string, ...extra: (Raw | SelectOptions)[]): QueryBuilder<T, T[]>;
|
|
504
|
+
/**
|
|
505
|
+
* Number of matching rows: `data` is the number. All filters apply, including
|
|
506
|
+
* jsonb paths and `relation.column` filters on `!inner` relations.
|
|
507
|
+
*
|
|
508
|
+
* @example
|
|
509
|
+
* const { data: total } = await db.from('users').count() // 100
|
|
510
|
+
* const { data: active } = await db.from('users').eq('active', true).count() // 75
|
|
511
|
+
*/
|
|
512
|
+
count(options?: {
|
|
513
|
+
distinct?: string;
|
|
514
|
+
}): QueryBuilder<T, number>;
|
|
515
|
+
/**
|
|
516
|
+
* Smallest value of a column over the matching rows: `data` is that value,
|
|
517
|
+
* or `null` when no rows match. `max`, `sum` and `avg` work the same way.
|
|
518
|
+
*
|
|
519
|
+
* @example
|
|
520
|
+
* const { data: first } = await db.from('posts').eq('user_id', id).min('created_at')
|
|
521
|
+
*/
|
|
522
|
+
min<V = any>(column: string): QueryBuilder<T, V | null>;
|
|
523
|
+
/** Largest value. @example .eq('email', email).max('created_at') */
|
|
524
|
+
max<V = any>(column: string): QueryBuilder<T, V | null>;
|
|
525
|
+
/** Sum of a number column, `null` when no rows match. */
|
|
526
|
+
sum(column: string): QueryBuilder<T, number | null>;
|
|
527
|
+
/** Average of a number column, `null` when no rows match. */
|
|
528
|
+
avg(column: string): QueryBuilder<T, number | null>;
|
|
529
|
+
/**
|
|
530
|
+
* Group rows for aggregates in `select()`: `count()`, `sum(col)`, `avg(col)`,
|
|
531
|
+
* `min(col)`, `max(col)`, `count(distinct col)`. Every plain column in the
|
|
532
|
+
* select must be grouped. `order()` can use an aggregate's name.
|
|
533
|
+
*
|
|
534
|
+
* @example
|
|
535
|
+
* db.from('posts').select('user_id, n:count(), last:max(created_at)').groupBy('user_id').order('n', { ascending: false })
|
|
536
|
+
*/
|
|
537
|
+
groupBy(columns: string): this;
|
|
538
|
+
/**
|
|
539
|
+
* Keep only the groups where the raw condition holds, knex's `havingRaw`.
|
|
540
|
+
* Use with `groupBy()`; several calls are joined with AND.
|
|
541
|
+
*
|
|
542
|
+
* @example
|
|
543
|
+
* db.from('posts').select('user_id, n:count()').groupBy('user_id').having(db.raw('count(*) >= ?', [2]))
|
|
544
|
+
*/
|
|
545
|
+
having(fragment: Raw): this;
|
|
546
|
+
/**
|
|
547
|
+
* Leave out repeated rows: `SELECT DISTINCT`.
|
|
548
|
+
*
|
|
549
|
+
* @example
|
|
550
|
+
* db.from('posts').select('user_id').distinct()
|
|
551
|
+
*/
|
|
552
|
+
distinct(): this;
|
|
553
|
+
/**
|
|
554
|
+
* Insert one or more rows. Values may contain `rel.*` to write related rows
|
|
555
|
+
* in the same transaction. Add `.returning()` to get the new rows back.
|
|
556
|
+
*
|
|
557
|
+
* @example
|
|
558
|
+
* await db.from('users').insert({ name, email }) // data: null, count: 1
|
|
559
|
+
* const { data } = await db.from('users').insert({ name, email }).returning('id') // [{ id }]
|
|
560
|
+
* await db.from('posts').insert({ title, user_id, comments: rel.create([{ body, author_id }]) })
|
|
561
|
+
*/
|
|
562
|
+
insert(values: Row | Row[]): WriteBuilder<T>;
|
|
563
|
+
/**
|
|
564
|
+
* Insert rows, or handle an existing row with the same `onConflict` key
|
|
565
|
+
* (needs a unique constraint; default: primary key): update it, or with
|
|
566
|
+
* `ignoreDuplicates: true` return it unchanged (find-or-create). `merge`
|
|
567
|
+
* picks which columns change and how. Up to 5,000 rows per call, each key
|
|
568
|
+
* at most once.
|
|
569
|
+
*
|
|
570
|
+
* @example
|
|
571
|
+
* // find-or-create
|
|
572
|
+
* const { data } = await db.from('users')
|
|
573
|
+
* .upsert({ email, name }, { onConflict: 'email', ignoreDuplicates: true })
|
|
574
|
+
* .returning('id')
|
|
575
|
+
* data[0].id
|
|
576
|
+
* // insert or update
|
|
577
|
+
* await db.from('users').upsert({ email, name }, { onConflict: 'email' })
|
|
578
|
+
* // many rows: add the counters, set the rest
|
|
579
|
+
* await db.from('posts_stats').upsert(rows, {
|
|
580
|
+
* onConflict: 'post_id',
|
|
581
|
+
* merge: { views: 'add', likes: 'add', last_viewer_id: 'set' },
|
|
582
|
+
* })
|
|
583
|
+
*/
|
|
584
|
+
upsert(rows: Row | Row[], options?: UpsertOptions): WriteBuilder<T>;
|
|
585
|
+
/**
|
|
586
|
+
* Update every matching row (needs at least one filter). Values may be plain
|
|
587
|
+
* (full replace), `ops.*` (atomic change) or `rel.*` (related rows).
|
|
588
|
+
*
|
|
589
|
+
* @example
|
|
590
|
+
* await db.from('users').update({ name: 'New' }).eq('id', id)
|
|
591
|
+
* await db.from('users').update({ scores: ops.append(4), age: ops.increment() }).eq('id', id).returning('age')
|
|
592
|
+
* await db.from('posts').update({ labels: rel.set([labelId]) }).eq('id', postId)
|
|
593
|
+
*/
|
|
594
|
+
update(values: Row): WriteBuilder<T>;
|
|
595
|
+
/**
|
|
596
|
+
* Delete every matching row (needs at least one filter).
|
|
597
|
+
*
|
|
598
|
+
* @example
|
|
599
|
+
* await db.from('posts').delete().eq('id', postId)
|
|
600
|
+
* await db.from('posts').delete().eq('id', postId).returning('title') // the deleted rows
|
|
601
|
+
*/
|
|
602
|
+
delete(): WriteBuilder<T>;
|
|
603
|
+
/**
|
|
604
|
+
* Let this read run on a read replica. Replicas can be a moment behind the
|
|
605
|
+
* primary, so use it where slightly stale rows are fine (lists, search,
|
|
606
|
+
* counts), not right after writing the rows you read. Without a healthy
|
|
607
|
+
* replica on the server it reads from the primary. Reads only, not inside
|
|
608
|
+
* a transaction.
|
|
609
|
+
*
|
|
610
|
+
* @example
|
|
611
|
+
* await db.from('posts').select('title').eq('published', true).replica()
|
|
612
|
+
*/
|
|
613
|
+
replica(): this;
|
|
614
|
+
/**
|
|
615
|
+
* Read from the primary, overriding `createClient(..., { readFrom: 'replica' })`.
|
|
616
|
+
*
|
|
617
|
+
* @example
|
|
618
|
+
* await db.from('users').select().eq('id', id).primary() // just wrote it: read your own write
|
|
619
|
+
*/
|
|
620
|
+
primary(): this;
|
|
621
|
+
/** @internal Applies the client's readFrom default to reads. */
|
|
622
|
+
protected request(): QueryRequest;
|
|
623
|
+
/**
|
|
624
|
+
* Sort. Chain several: they apply in the order written. Ascending by default.
|
|
625
|
+
* With `referencedTable` it sorts the rows inside a relation.
|
|
626
|
+
*
|
|
627
|
+
* @example
|
|
628
|
+
* .order('active', { ascending: false }).order('age', { ascending: false }).order('name')
|
|
629
|
+
* .order('meta->>score::int', { ascending: false, nullsFirst: false })
|
|
630
|
+
* .order('title', { referencedTable: 'posts' }) // posts inside each user
|
|
631
|
+
* .order(db.raw("coalesce(last_read_time, '1970-01-01')")) // raw, main table only
|
|
632
|
+
*/
|
|
633
|
+
order(column: string | Raw, options?: {
|
|
634
|
+
ascending?: boolean;
|
|
635
|
+
nullsFirst?: boolean;
|
|
636
|
+
} & ReferencedTableOption): this;
|
|
637
|
+
/**
|
|
638
|
+
* Random order. After other `order()` calls it only shuffles ties. With a
|
|
639
|
+
* `seed` the order is stable (same seed, same order), so `range()` pages never
|
|
640
|
+
* repeat or skip rows.
|
|
641
|
+
*
|
|
642
|
+
* @example
|
|
643
|
+
* .random().limit(5) // 5 random rows
|
|
644
|
+
* .order('active', { ascending: false }).random() // active first, each group shuffled
|
|
645
|
+
* .random({ seed: sessionId }).range(10, 19) // stable page 2
|
|
646
|
+
* .random({ referencedTable: 'posts' }).limit(3, { referencedTable: 'posts' }) // 3 random posts each
|
|
647
|
+
*/
|
|
648
|
+
random(options?: ReferencedTableOption & {
|
|
649
|
+
seed?: string | number;
|
|
650
|
+
}): this;
|
|
651
|
+
/**
|
|
652
|
+
* At most `count` rows: always an array, `[]` when none match, even for
|
|
653
|
+
* `limit(1)`. With `referencedTable`, at most `count` related rows per parent.
|
|
654
|
+
*
|
|
655
|
+
* @example
|
|
656
|
+
* .limit(10)
|
|
657
|
+
* .limit(3, { referencedTable: 'posts' })
|
|
658
|
+
*/
|
|
659
|
+
limit(count: number, options?: ReferencedTableOption): this;
|
|
660
|
+
/**
|
|
661
|
+
* Rows `from` to `to`, inclusive, 0-based.
|
|
662
|
+
*
|
|
663
|
+
* @example
|
|
664
|
+
* .range(0, 9) // page 1
|
|
665
|
+
* .range(10, 19) // page 2
|
|
666
|
+
*/
|
|
667
|
+
range(from: number, to: number): this;
|
|
668
|
+
/**
|
|
669
|
+
* One row: `data` is that row as an object, or `null` when nothing matches
|
|
670
|
+
* (not an error). Adds `LIMIT 1`, so don't combine it with `limit()` or
|
|
671
|
+
* `range()`. With several matches it is the first in your `order()`.
|
|
672
|
+
*
|
|
673
|
+
* @example
|
|
674
|
+
* const { data: user } = await db.from('users').select().eq('id', id).single()
|
|
675
|
+
* if (!user) { ... }
|
|
676
|
+
* const { data: email } = await db.from('emails').select().eq('status', 'active').random().single()
|
|
677
|
+
*/
|
|
678
|
+
single(): QueryBuilder<T, T | null>;
|
|
679
|
+
/** @internal */
|
|
680
|
+
invalid(): string | undefined;
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* An insert, upsert, update or delete. Like Postgres, it writes every
|
|
684
|
+
* matching row; `data` is `null` and `count` the number of rows written,
|
|
685
|
+
* unless `returning()` asks for the rows.
|
|
686
|
+
*/
|
|
687
|
+
export declare class WriteBuilder<T extends Row = Row, R = null> extends Builder<T, R> {
|
|
688
|
+
/**
|
|
689
|
+
* Return the written rows, like Postgres `RETURNING`: always an array (one
|
|
690
|
+
* row is `[row]`, none is `[]`). Takes `'*'` (the default) or column names.
|
|
691
|
+
*
|
|
692
|
+
* @example
|
|
693
|
+
* await db.from('users').insert({ name, email }).returning() // [{ id, name, email, ... }]
|
|
694
|
+
* await db.from('users').update({ age: ops.increment() }).eq('id', id).returning('id, age')
|
|
695
|
+
* await db.from('posts').delete().eq('user_id', id).returning('id') // every deleted row
|
|
696
|
+
*/
|
|
697
|
+
returning(columns?: string): WriteBuilder<T, T[]>;
|
|
698
|
+
}
|
|
699
|
+
/** A related row's key: the value of a single-column key, or an object for composite keys. */
|
|
700
|
+
type Key = string | number | Row;
|
|
701
|
+
type RelOpName = 'create' | 'upsert' | 'connectOrCreate' | 'connect' | 'disconnect' | 'set' | 'update' | 'delete';
|
|
702
|
+
/**
|
|
703
|
+
* A write to a related table, used as a value in `insert()` / `update()` under
|
|
704
|
+
* the related table's name (add `!fk_column` when there are several FKs):
|
|
705
|
+
*
|
|
706
|
+
* insert({ title, comments: rel.create([{ body }]), labels: rel.connect([labelId]) })
|
|
707
|
+
*
|
|
708
|
+
* Everything in one insert()/update() runs in one transaction.
|
|
709
|
+
*/
|
|
710
|
+
interface RelOp {
|
|
711
|
+
readonly $rel: RelOpName;
|
|
712
|
+
readonly rows?: Row[];
|
|
713
|
+
readonly keys?: Key[];
|
|
714
|
+
readonly match?: Row;
|
|
715
|
+
readonly values?: Row;
|
|
716
|
+
readonly onConflict?: string[];
|
|
717
|
+
}
|
|
718
|
+
export declare const rel: {
|
|
719
|
+
/**
|
|
720
|
+
* Insert related rows (they may contain `rel.*` themselves).
|
|
721
|
+
* @example
|
|
722
|
+
* db.from('posts').insert({ title, user_id, comments: rel.create([{ body, author_id }]) })
|
|
723
|
+
*/
|
|
724
|
+
create: (rows: Row | Row[]) => RelOp;
|
|
725
|
+
/**
|
|
726
|
+
* Insert one related row, or update it if its `onConflict` key (default: primary key) exists.
|
|
727
|
+
* @example
|
|
728
|
+
* db.from('posts').update({ comments: rel.upsert({ id: commentId, body: 'v2', author_id }) }).eq('id', postId)
|
|
729
|
+
*/
|
|
730
|
+
upsert: (row: Row, options?: {
|
|
731
|
+
onConflict?: string;
|
|
732
|
+
}) => RelOp;
|
|
733
|
+
/**
|
|
734
|
+
* Find one related row by its `onConflict` key (default: primary key), or create it,
|
|
735
|
+
* then link it. An existing row is not changed.
|
|
736
|
+
* @example
|
|
737
|
+
* db.from('posts').insert({ title, user_id, labels: rel.connectOrCreate({ name: 'go' }, { onConflict: 'name' }) })
|
|
738
|
+
*/
|
|
739
|
+
connectOrCreate: (row: Row, options?: {
|
|
740
|
+
onConflict?: string;
|
|
741
|
+
}) => RelOp;
|
|
742
|
+
/**
|
|
743
|
+
* Link existing rows by key. Fails (and rolls back) if a key does not exist.
|
|
744
|
+
* @example
|
|
745
|
+
* db.from('posts').update({ labels: rel.connect([labelA, labelB]) }).eq('id', postId)
|
|
746
|
+
*/
|
|
747
|
+
connect: (keys: Key | Key[]) => RelOp;
|
|
748
|
+
/**
|
|
749
|
+
* Unlink rows by key (many-to-one: clears the FK; one-to-many: sets the child FK to null).
|
|
750
|
+
* @example
|
|
751
|
+
* db.from('posts').update({ labels: rel.disconnect(labelId) }).eq('id', postId)
|
|
752
|
+
*/
|
|
753
|
+
disconnect: (keys?: Key | Key[]) => RelOp;
|
|
754
|
+
/**
|
|
755
|
+
* Make exactly these rows linked: unlinks the others, links the missing ones.
|
|
756
|
+
* @example
|
|
757
|
+
* db.from('posts').update({ labels: rel.set([labelA]) }).eq('id', postId) // rel.set([]) unlinks all
|
|
758
|
+
*/
|
|
759
|
+
set: (keys: Key[]) => RelOp;
|
|
760
|
+
/**
|
|
761
|
+
* Update linked rows that match (equality on every key of `match`).
|
|
762
|
+
* @example
|
|
763
|
+
* db.from('posts').update({ comments: rel.update({ id: commentId }, { body: 'edited' }) }).eq('id', postId)
|
|
764
|
+
*/
|
|
765
|
+
update: (match: Row, values: Row) => RelOp;
|
|
766
|
+
/**
|
|
767
|
+
* Delete linked rows that match; `rel.delete()` deletes all linked rows.
|
|
768
|
+
* @example
|
|
769
|
+
* db.from('posts').update({ comments: rel.delete({ body: 'spam' }) }).eq('id', postId)
|
|
770
|
+
*/
|
|
771
|
+
delete: (match?: Row) => RelOp;
|
|
772
|
+
};
|
|
773
|
+
interface ClientOptions {
|
|
774
|
+
/** Extra headers on every request. */
|
|
775
|
+
headers?: Record<string, string>;
|
|
776
|
+
/** Custom fetch (defaults to the global one, Node 18+). */
|
|
777
|
+
fetch?: typeof fetch;
|
|
778
|
+
/**
|
|
779
|
+
* Where reads run by default: `'primary'` (default) or `'replica'` (a read
|
|
780
|
+
* replica when the server has a healthy one, else the primary). Writes and
|
|
781
|
+
* transactions always use the primary. Override per query with
|
|
782
|
+
* `.replica()` / `.primary()`.
|
|
783
|
+
*/
|
|
784
|
+
readFrom?: ReadFrom;
|
|
785
|
+
}
|
|
786
|
+
/** Placeholder for a value from an earlier transaction step. */
|
|
787
|
+
interface Ref {
|
|
788
|
+
readonly $ref: string;
|
|
789
|
+
}
|
|
790
|
+
/** Any step a transaction accepts. */
|
|
791
|
+
type Step = QueryBuilder<any, any> | WriteBuilder<any, any>;
|
|
792
|
+
/** The `data` type of a step. */
|
|
793
|
+
type StepData<S> = S extends PromiseLike<QueryResult<infer R>> ? R : never;
|
|
794
|
+
interface Tx {
|
|
795
|
+
/** Start a step. Don't await it: return it from the transaction callback. */
|
|
796
|
+
from<T extends Row = Row>(table: string): QueryBuilder<T>;
|
|
797
|
+
/**
|
|
798
|
+
* A value from an earlier named step, usable in values and filters.
|
|
799
|
+
* A step that returned one row is read directly (`'post.id'`); with several
|
|
800
|
+
* rows, pick one: `tx.ref('posts.0.id')`.
|
|
801
|
+
*
|
|
802
|
+
* @example
|
|
803
|
+
* tx.from('comments').insert({ post_id: tx.ref('post.id'), body, author_id })
|
|
804
|
+
*/
|
|
805
|
+
ref(path: string): Ref;
|
|
806
|
+
/** Atomic jsonb/array/number changes, same as `db.ops`. */
|
|
807
|
+
readonly ops: typeof ops;
|
|
808
|
+
/** Writes to related rows, same as `db.rel`. */
|
|
809
|
+
readonly rel: typeof rel;
|
|
810
|
+
/** Another column of the same row, same as `db.col`. */
|
|
811
|
+
readonly col: typeof col;
|
|
812
|
+
/** The database server's time, same as `db.now`. */
|
|
813
|
+
readonly now: typeof now;
|
|
814
|
+
/** A raw SQL fragment, same as `db.raw`. */
|
|
815
|
+
readonly raw: typeof raw;
|
|
816
|
+
}
|
|
817
|
+
interface Client {
|
|
818
|
+
from<T extends Row = Row>(table: string): QueryBuilder<T>;
|
|
819
|
+
/**
|
|
820
|
+
* Atomic jsonb/array/number changes for `update()` values (same as `import { ops }`).
|
|
821
|
+
*
|
|
822
|
+
* @example
|
|
823
|
+
* await db.from('users').update({ scores: db.ops.append(4), age: db.ops.increment() }).eq('id', id)
|
|
824
|
+
*/
|
|
825
|
+
readonly ops: typeof ops;
|
|
826
|
+
/**
|
|
827
|
+
* Writes to related rows inside `insert()` / `update()` values (same as `import { rel }`).
|
|
828
|
+
*
|
|
829
|
+
* @example
|
|
830
|
+
* await db.from('posts').insert({ title, user_id, comments: db.rel.create([{ body, author_id }]) })
|
|
831
|
+
*/
|
|
832
|
+
readonly rel: typeof rel;
|
|
833
|
+
/**
|
|
834
|
+
* Another column of the same row, for comparing two columns in a filter.
|
|
835
|
+
*
|
|
836
|
+
* @example
|
|
837
|
+
* await db.from('accounts').select().lt('sent_per_hour', db.col('limit_per_hour'))
|
|
838
|
+
* await db.from('accounts').select().lt(db.col('sent_per_hour').ifNull(0), db.col('limit_per_hour'))
|
|
839
|
+
*/
|
|
840
|
+
readonly col: typeof col;
|
|
841
|
+
/**
|
|
842
|
+
* The database server's `now()`, optionally `.plus()` / `.minus()` one interval.
|
|
843
|
+
* Works in filters and in insert/update values.
|
|
844
|
+
*
|
|
845
|
+
* @example
|
|
846
|
+
* await db.from('clients').select().lte('sent_at', db.now().minus('1 hour'))
|
|
847
|
+
* await db.from('accounts').update({ last_read_time: db.now() }).eq('id', id)
|
|
848
|
+
*/
|
|
849
|
+
readonly now: typeof now;
|
|
850
|
+
/**
|
|
851
|
+
* A raw SQL fragment with `?` bindings (same as `import { raw }`), for
|
|
852
|
+
* `where()`, `order()`, `having()`, `select()` columns and write values.
|
|
853
|
+
* The API key must be allowed to use raw fragments.
|
|
854
|
+
*
|
|
855
|
+
* @example
|
|
856
|
+
* await db.from('accounts').select().where(db.raw('sent_per_hour < limit_per_hour'))
|
|
857
|
+
* await db.from('accounts').update({ sent_total: db.raw('sent_total + ?', [1]) }).eq('id', id)
|
|
858
|
+
*/
|
|
859
|
+
readonly raw: typeof raw;
|
|
860
|
+
/**
|
|
861
|
+
* Run several steps in one database transaction: all succeed or nothing is saved.
|
|
862
|
+
* On failure `error.step` / `error.stepName` say which step failed.
|
|
863
|
+
*
|
|
864
|
+
* Return an object (keys name the steps; `data` has the same keys) or an
|
|
865
|
+
* array (name steps with `.as()`; `data` is an array). Don't await the steps.
|
|
866
|
+
*
|
|
867
|
+
* @example
|
|
868
|
+
* const { data, error } = await db.transaction((tx) => ({
|
|
869
|
+
* user: tx.from('users').upsert({ email }, { onConflict: 'email', ignoreDuplicates: true }).returning('id'),
|
|
870
|
+
* post: tx.from('posts').insert({ title, user_id: tx.ref('user.id') }).returning('id'),
|
|
871
|
+
* total: tx.from('posts').eq('user_id', tx.ref('user.id')).count(),
|
|
872
|
+
* }))
|
|
873
|
+
* data.post[0].id // returning() is always an array
|
|
874
|
+
*/
|
|
875
|
+
transaction<S extends Record<string, Step>>(steps: (tx: Tx) => S): Promise<QueryResult<{ [K in keyof S]: StepData<S[K]>; }>>;
|
|
876
|
+
transaction<S extends Step[]>(steps: (tx: Tx) => [...S]): Promise<QueryResult<{ [K in keyof S]: StepData<S[K]>; }>>;
|
|
877
|
+
}
|
|
878
|
+
/**
|
|
879
|
+
* Server-side client for the Go API (it refuses browser requests). Every call
|
|
880
|
+
* resolves to `{ data, error, count, status }` and never throws.
|
|
881
|
+
*
|
|
882
|
+
* @example
|
|
883
|
+
* const db = createClient(process.env.PG_API_URL!, process.env.PG_API_KEY!)
|
|
884
|
+
* const { data, error } = await db.from('users').select().eq('active', true)
|
|
885
|
+
*/
|
|
886
|
+
export declare function createClient(url: string, apiKey: string, options?: ClientOptions): Client;
|
|
887
|
+
/**
|
|
888
|
+
* Ready-made client from the environment: `ARN_DB_URL` (e.g.
|
|
889
|
+
* `https://your-api.example.com/v2/raw`) and `ARN_DB_KEY`, read on the first query,
|
|
890
|
+
* with `./.env` loaded if they are not set yet. A missing variable throws on
|
|
891
|
+
* that first query. Use `createClient()` for other keys or options.
|
|
892
|
+
*
|
|
893
|
+
* @example
|
|
894
|
+
* import { db } from 'arn-db'
|
|
895
|
+
* const { data, error } = await db.from('users').select().eq('id', id).single()
|
|
896
|
+
*/
|
|
897
|
+
export declare const db: Client;
|
|
898
|
+
export type { AggregateFn, Client, ClientOptions, Column, ConditionFilter, Filter, FilterOp, GroupFilter, Key, MergeMode, Order, PgError, QueryRequest, QueryResult, RawFilter, RawFragment, ReadFrom, Ref, ReferencedTableOption, RelOp, RelOpName, Row, SelectOptions, Step, StepData, Tx, TxStepRequest, UpsertOptions };
|
|
899
|
+
//# sourceMappingURL=index.d.mts.map
|