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.
@@ -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