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