arn-db 0.1.0 → 0.2.1
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 +108 -9
- package/README.md +58 -7
- package/dist/index.cjs +136 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +114 -13
- package/dist/index.d.mts +114 -13
- package/dist/index.mjs +135 -12
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":[],"sources":["../src/rel.ts","../src/values.ts","../src/builder.ts","../src/ops.ts","../src/client.ts","../src/env.ts"],"sourcesContent":["import type { Row } from './types.js'\n\n/** A related row's key: the value of a single-column key, or an object for composite keys. */\nexport type Key = string | number | Row\n\nexport type RelOpName = 'create' | 'upsert' | 'connectOrCreate' | 'connect' | 'disconnect' | 'set' | 'update' | 'delete'\n\n/**\n * A write to a related table, used as a value in `insert()` / `update()` under\n * the related table's name (add `!fk_column` when there are several FKs):\n *\n * insert({ title, comments: rel.create([{ body }]), labels: rel.connect([labelId]) })\n *\n * Everything in one insert()/update() runs in one transaction.\n */\nexport interface RelOp {\n readonly $rel: RelOpName\n readonly rows?: Row[]\n readonly keys?: Key[]\n readonly match?: Row\n readonly values?: Row\n readonly onConflict?: string[]\n}\n\nconst list = <T>(v: T | T[]): T[] => (Array.isArray(v) ? v : [v])\n\n/** \"user_id, title\" -> { onConflict: ['user_id', 'title'] } */\nexport const conflict = (columns?: string) =>\n columns ? { onConflict: columns.split(',').map((c) => c.trim()).filter(Boolean) } : {}\n\nexport const rel = {\n /**\n * Insert related rows (they may contain `rel.*` themselves).\n * @example\n * db.from('posts').insert({ title, user_id, comments: rel.create([{ body, author_id }]) })\n */\n create: (rows: Row | Row[]): RelOp => ({ $rel: 'create', rows: list(rows) }),\n\n /**\n * Insert one related row, or update it if its `onConflict` key (default: primary key) exists.\n * @example\n * db.from('posts').update({ comments: rel.upsert({ id: commentId, body: 'v2', author_id }) }).eq('id', postId)\n */\n upsert: (row: Row, options: { onConflict?: string } = {}): RelOp => ({\n $rel: 'upsert',\n rows: [row],\n ...conflict(options.onConflict),\n }),\n\n /**\n * Find one related row by its `onConflict` key (default: primary key), or create it,\n * then link it. An existing row is not changed.\n * @example\n * db.from('posts').insert({ title, user_id, labels: rel.connectOrCreate({ name: 'go' }, { onConflict: 'name' }) })\n */\n connectOrCreate: (row: Row, options: { onConflict?: string } = {}): RelOp => ({\n $rel: 'connectOrCreate',\n rows: [row],\n ...conflict(options.onConflict),\n }),\n\n /**\n * Link existing rows by key. Fails (and rolls back) if a key does not exist.\n * @example\n * db.from('posts').update({ labels: rel.connect([labelA, labelB]) }).eq('id', postId)\n */\n connect: (keys: Key | Key[]): RelOp => ({ $rel: 'connect', keys: list(keys) }),\n\n /**\n * Unlink rows by key (many-to-one: clears the FK; one-to-many: sets the child FK to null).\n * @example\n * db.from('posts').update({ labels: rel.disconnect(labelId) }).eq('id', postId)\n */\n disconnect: (keys: Key | Key[] = []): RelOp => ({ $rel: 'disconnect', keys: list(keys) }),\n\n /**\n * Make exactly these rows linked: unlinks the others, links the missing ones.\n * @example\n * db.from('posts').update({ labels: rel.set([labelA]) }).eq('id', postId) // rel.set([]) unlinks all\n */\n set: (keys: Key[]): RelOp => ({ $rel: 'set', keys }),\n\n /**\n * Update linked rows that match (equality on every key of `match`).\n * @example\n * db.from('posts').update({ comments: rel.update({ id: commentId }, { body: 'edited' }) }).eq('id', postId)\n */\n update: (match: Row, values: Row): RelOp => ({ $rel: 'update', match, values }),\n\n /**\n * Delete linked rows that match; `rel.delete()` deletes all linked rows.\n * @example\n * db.from('posts').update({ comments: rel.delete({ body: 'spam' }) }).eq('id', postId)\n */\n delete: (match: Row = {}): RelOp => ({ $rel: 'delete', match }),\n}\n","/**\n * Values the database fills in: another column of the same row (`db.col`)\n * and the database's current time (`db.now`).\n */\n\n/**\n * Another column of the same row. As a filter value it compares two columns;\n * as the column of a filter it lets `ifNull()` replace NULL first.\n *\n * .lt('sent_per_hour', db.col('limit_per_hour'))\n * .lt(db.col('sent_per_hour').ifNull(0), db.col('limit_per_hour'))\n */\nexport class Col {\n readonly name: string\n readonly #ifNull: { value: unknown } | undefined\n\n constructor(name: string, ifNull?: { value: unknown }) {\n this.name = name\n this.#ifNull = ifNull\n }\n\n /** Use `value` where the column is NULL: `COALESCE(column, value)`. */\n ifNull(value: unknown): Col {\n return new Col(this.name, { value })\n }\n\n /** @internal The NULL replacement, if any. */\n nullValue(): { value: unknown } | undefined {\n return this.#ifNull\n }\n\n /** @internal Wire format. */\n toJSON(): { $col: string; ifNull?: unknown } {\n return this.#ifNull ? { $col: this.name, ifNull: this.#ifNull.value } : { $col: this.name }\n }\n}\n\n/**\n * The database server's `now()`, optionally shifted by one interval like\n * `'1 hour'` or `'30 minutes'` (units: second, minute, hour, day, week,\n * month, year). Works in filters and in insert/update values.\n *\n * .lte('client_sent_time', db.now().minus('1 hour'))\n * .update({ last_read_time: db.now() })\n */\nexport class Now {\n readonly #interval: string | undefined\n\n constructor(interval?: string) {\n this.#interval = interval\n }\n\n /** Later by `interval`: `db.now().plus('1 day')`. */\n plus(interval: string): Now {\n return this.#shift(interval.trim())\n }\n\n /** Earlier by `interval`: `db.now().minus('1 hour')`. */\n minus(interval: string): Now {\n const i = interval.trim()\n return this.#shift(i.startsWith('-') ? i.slice(1) : `-${i}`)\n }\n\n #shift(interval: string): Now {\n if (this.#interval !== undefined) throw new Error('arn-db: db.now() takes one plus() or minus(); use a single interval')\n return new Now(interval)\n }\n\n /** @internal Wire format. */\n toJSON(): { $now: true; interval?: string } {\n return this.#interval === undefined ? { $now: true } : { $now: true, interval: this.#interval }\n }\n}\n\n/** A filter's column: a name, or `db.col(name).ifNull(value)`. */\nexport type Column = string | Col\n\nexport const col = (name: string) => new Col(name)\nexport const now = () => new Now()\n","import { conflict } from './rel.js'\nimport { Col, type Column } from './values.js'\nimport type {\n AggregateFn,\n Filter,\n FilterOp,\n GroupFilter,\n QueryRequest,\n QueryResult,\n ReadFrom,\n ReferencedTableOption,\n Row,\n Transport,\n UpsertOptions,\n} from './types.js'\n\nconst embedOf = (o: ReferencedTableOption) => o.referencedTable ?? o.foreignTable\n\n/** One step of a transaction, as sent to the server. */\nexport interface TxStepRequest extends QueryRequest {\n name?: string\n}\n\n/** Filter methods, shared by builders and groups. */\nabstract class Filters {\n // ---- filters ----\n // `column` may be a jsonb path ('meta->>city', 'meta->>score::int') or\n // 'relation.column' to filter related rows ('posts.published').\n\n /** @internal Adds one filter or group. */\n protected abstract push(filter: Filter): void\n\n #filter(column: Column, op: FilterOp, value: unknown): this {\n if (column instanceof Col) {\n const ifNull = column.nullValue()\n this.push(ifNull ? { column: column.name, op, value, ifNull: ifNull.value } : { column: column.name, op, value })\n } else {\n this.push({ column, op, value })\n }\n return this\n }\n\n /**\n * Equal. Works on columns, jsonb paths and related tables.\n *\n * @example\n * .eq('active', true)\n * .eq('meta->>city', 'Tokyo')\n * .eq('posts.published', true) // filters the related posts\n */\n eq(column: Column, value: unknown): this { return this.#filter(column, 'eq', value) }\n /** Not equal. */\n neq(column: Column, value: unknown): this { return this.#filter(column, 'neq', value) }\n /**\n * Greater than. For numbers inside jsonb, cast: `.gt('meta->>score::int', 90)`.\n * `eq` to `lte` also compare with another column (`db.col`) or the\n * database time (`db.now`).\n *\n * @example\n * .gt('age', 30)\n * .lt('sent_per_hour', db.col('limit_per_hour'))\n * .lt(db.col('sent_per_hour').ifNull(0), db.col('limit_per_hour'))\n * .lte('client_sent_time', db.now().minus('1 hour'))\n */\n gt(column: Column, value: unknown): this { return this.#filter(column, 'gt', value) }\n /** Greater than or equal. @example .gte('meta->>score::int', 90) */\n gte(column: Column, value: unknown): this { return this.#filter(column, 'gte', value) }\n /** Less than. */\n lt(column: Column, value: unknown): this { return this.#filter(column, 'lt', value) }\n /** Less than or equal. */\n lte(column: Column, value: unknown): this { return this.#filter(column, 'lte', value) }\n /**\n * SQL LIKE: `%` is any text, `_` is one character (escape with `\\\\_`).\n *\n * @example\n * .like('name', 'user\\\\_1%')\n */\n like(column: string, pattern: string): this { return this.#filter(column, 'like', pattern) }\n /**\n * Case-insensitive LIKE.\n *\n * @example\n * .ilike('email', '%@example.com')\n * .ilike('posts.comments.body', '%hello%') // search a deeper related table\n */\n ilike(column: string, pattern: string): this { return this.#filter(column, 'ilike', pattern) }\n /** IS NULL / IS TRUE / IS FALSE. @example .is('deleted_at', null) */\n is(column: Column, value: null | boolean): this { return this.#filter(column, 'is', value) }\n /** One of the values. @example .in('name', ['user_3', 'user_5']) */\n in(column: string, values: readonly unknown[]): this { return this.#filter(column, 'in', values) }\n /**\n * Has ALL of the values (arrays), or has these keys and values (jsonb objects).\n *\n * @example\n * .contains('tags', ['one', 'two'])\n * .contains('meta', { city: 'Dhaka', vip: true })\n */\n contains(column: string, value: unknown): this { return this.#filter(column, 'cs', value) }\n /**\n * Has nothing outside the values.\n *\n * @example\n * .containedBy('tags', ['one', 'two', 'three'])\n */\n containedBy(column: string, value: unknown): this { return this.#filter(column, 'cd', value) }\n /**\n * Has ANY of the values. Works on jsonb/json arrays, jsonb paths and Postgres arrays.\n *\n * @example\n * .overlaps('scores', ['one', 'three'])\n * .overlaps('meta->tags', ['red', 'blue'])\n */\n overlaps(column: string, values: readonly unknown[]): this { return this.#filter(column, 'ov', values) }\n /** Any filter by name: `.filter('age', 'gte', 30)`. */\n filter(column: Column, op: FilterOp, value: unknown): this { return this.#filter(column, op, value) }\n /**\n * Equal on every key.\n *\n * @example\n * .match({ active: true, age: 30 })\n */\n match(values: Row): this {\n for (const [k, v] of Object.entries(values)) this.eq(k, v)\n return this\n }\n\n // ---- groups ----\n // A group is one condition of its parent. Inside `or()` the conditions are\n // joined with OR; inside `and()` and `not()` with AND. Groups nest, and all\n // conditions in a group must be on the same table.\n\n /**\n * Any of the conditions.\n *\n * @example\n * .or((q) => q.gt('age', 30).eq('meta->>city', 'Dhaka')) // age > 30 OR city = 'Dhaka'\n * .or((q) => q.eq('role', 'admin').and((q) => q.gte('age', 18).is('verified', true)))\n * .or((q) => q.eq('posts.published', true).gt('posts.likes', 10)) // filters the related posts\n */\n or(conditions: (q: FilterGroup) => unknown): this { return this.#group('or', conditions) }\n /**\n * All of the conditions, as one group. Mostly useful inside `or()`.\n *\n * @example\n * .or((q) => q.eq('role', 'admin').and((q) => q.gte('age', 18).eq('active', true)))\n */\n and(conditions: (q: FilterGroup) => unknown): this { return this.#group('and', conditions) }\n /**\n * Not all of the conditions: `NOT (a AND b)`. Like Postgres, NULL is neither\n * true nor false: a row with a null `age` matches neither `.gt('age', 30)`\n * nor `.not('age', 'gt', 30)`.\n *\n * @example\n * .not('deleted_at', 'is', null)\n * .not((q) => q.eq('meta->>city', 'Tokyo').lt('age', 30))\n * .not((q) => q.or((q) => q.eq('a', 1).eq('b', 2))) // neither a = 1 nor b = 2\n */\n not(conditions: (q: FilterGroup) => unknown): this\n not(column: Column, op: FilterOp, value: unknown): this\n not(a: Column | ((q: FilterGroup) => unknown), op?: FilterOp, value?: unknown): this {\n if (typeof a === 'function') return this.#group('not', a)\n return this.#group('not', (q) => q.filter(a, op as FilterOp, value))\n }\n\n #group(group: GroupFilter['group'], conditions: (q: FilterGroup) => unknown): this {\n const q = new FilterGroup()\n conditions(q)\n this.push({ group, filters: q.filters })\n return this\n }\n}\n\n/**\n * The conditions inside `or()`, `and()` or `not()`: every filter method, plus\n * nested groups. Chain them on `q`; the callback's return value is ignored.\n */\nexport class FilterGroup extends Filters {\n /** @internal */ readonly filters: Filter[] = []\n\n /** @internal */\n protected push(filter: Filter): void {\n this.filters.push(filter)\n }\n}\n\n/**\n * What reads and writes share: filters, step names and sending. Awaiting a\n * builder sends the request and resolves to `{ data, error, count, status }`;\n * it never throws. `T` is the row type, `R` the type of `data`.\n */\nabstract class Builder<T extends Row, R> extends Filters implements PromiseLike<QueryResult<R>> {\n /** @internal */ protected readonly transport: Transport | null\n /** @internal */ protected readonly req: QueryRequest\n /** @internal A problem found while building, reported instead of sending. */\n protected problem: string | undefined\n #name: string | undefined\n\n /** @internal */\n constructor(transport: Transport | null, req: QueryRequest, name?: string, problem?: string) {\n super()\n this.transport = transport\n this.req = req\n this.#name = name\n this.problem = problem\n }\n\n /** @internal */\n protected push(filter: Filter): void {\n this.req.filters.push(filter)\n }\n\n /**\n * Name a step in the array form of `transaction()`, so later steps can use\n * its rows with `tx.ref('name.column')`. (In the object form the key is the name.)\n *\n * @example\n * db.transaction((tx) => [\n * tx.from('posts').insert({ title, user_id }).returning('id').as('post'),\n * tx.from('comments').insert({ post_id: tx.ref('post.id'), body, author_id }),\n * ])\n */\n as(name: string): this {\n this.#name = name\n return this\n }\n\n /** @internal The `.as()` name, if any. */\n stepName(): string | undefined {\n return this.#name\n }\n\n /** @internal A problem found while building, reported instead of sending. */\n invalid(): string | undefined {\n return this.problem\n }\n\n // ---- execution ----\n\n /** @internal The request as sent to POST /query. */\n protected request(): QueryRequest {\n return this.req\n }\n\n /** @internal The request as sent to the server. */\n toStep(): TxStepRequest {\n return this.#name ? { name: this.#name, ...this.req } : { ...this.req }\n }\n\n then<A = QueryResult<R>, B = never>(\n onfulfilled?: ((value: QueryResult<R>) => A | PromiseLike<A>) | null,\n onrejected?: ((reason: unknown) => B | PromiseLike<B>) | null,\n ): PromiseLike<A | B> {\n const run = async (): Promise<QueryResult<R>> => {\n const invalid = this.invalid()\n if (invalid) {\n return { data: null, error: { message: invalid }, count: null, status: 0 }\n }\n if (!this.transport) {\n return {\n data: null,\n error: { message: 'this query belongs to a transaction: return it from the transaction callback instead of awaiting it' },\n count: null,\n status: 0,\n }\n }\n return this.transport.post('/query', this.request())\n }\n return run().then(onfulfilled, onrejected)\n }\n}\n\n/**\n * Supabase-style query builder. Reads use `select()` and its modifiers;\n * `insert()` / `upsert()` / `update()` / `delete()` turn it into a\n * {@link WriteBuilder}. See GUIDE.md for recipes.\n *\n * @example\n * const { data, error } = await db.from('users').select('id, name').eq('active', true).random().limit(5)\n */\nexport class QueryBuilder<T extends Row = Row, R = T[]> extends Builder<T, R> {\n #single = false\n #defaultReadFrom: ReadFrom | undefined\n\n /** @internal Use `client.from()` or `tx.from()`. */\n constructor(transport: Transport | null, table: string, readFrom?: ReadFrom) {\n super(transport, {\n table,\n action: 'select',\n columns: '*',\n filters: [],\n order: [],\n returning: false,\n count: false,\n })\n this.#defaultReadFrom = readFrom\n }\n\n // ---- reads ----\n\n /**\n * Columns to return. `'*'` (the default) means all. Supports aliases, jsonb\n * paths, casts, related tables and related counts.\n *\n * `{ count: 'exact' }` also fills `count`; add `head: true` to skip the rows.\n *\n * @example\n * db.from('users').select() // all columns\n * db.from('users').select('id, fullName:name, city:meta->>city, score:meta->>score::int')\n * db.from('posts').select('title, author:users!user_id(name), comments(body), labels(name)')\n * db.from('users').select('name, posts!user_id!inner(title)') // only users that have posts\n * db.from('users').select('name, posts!user_id(count)') // posts: [{ count: 2 }]\n * db.from('posts').select('*', { count: 'exact' }).range(0, 9) // rows + total\n */\n select(columns = '*', options: { count?: 'exact'; head?: boolean } = {}): QueryBuilder<T, T[]> {\n this.req.columns = columns\n if (options.count) this.req.count = true\n if (options.head) this.req.head = true\n return this as unknown as QueryBuilder<T, T[]>\n }\n\n /**\n * Number of matching rows: `data` is the number. All filters apply, including\n * jsonb paths and `relation.column` filters on `!inner` relations.\n *\n * @example\n * const { data: total } = await db.from('users').count() // 100\n * const { data: active } = await db.from('users').eq('active', true).count() // 75\n */\n count(options: { distinct?: string } = {}): QueryBuilder<T, number> {\n if (options.distinct) return this.#aggregate('count', options.distinct, true)\n this.req.countOnly = true\n return this as unknown as QueryBuilder<T, number>\n }\n\n /**\n * Smallest value of a column over the matching rows: `data` is that value,\n * or `null` when no rows match. `max`, `sum` and `avg` work the same way.\n *\n * @example\n * const { data: first } = await db.from('posts').eq('user_id', id).min('created_at')\n */\n min<V = any>(column: string): QueryBuilder<T, V | null> { return this.#aggregate('min', column) }\n /** Largest value. @example .eq('email', email).max('created_at') */\n max<V = any>(column: string): QueryBuilder<T, V | null> { return this.#aggregate('max', column) }\n /** Sum of a number column, `null` when no rows match. */\n sum(column: string): QueryBuilder<T, number | null> { return this.#aggregate('sum', column) }\n /** Average of a number column, `null` when no rows match. */\n avg(column: string): QueryBuilder<T, number | null> { return this.#aggregate('avg', column) }\n\n #aggregate(fn: AggregateFn, column: string, distinct?: boolean): QueryBuilder<T, any> {\n this.req.aggregate = distinct ? { fn, column, distinct } : { fn, column }\n return this as unknown as QueryBuilder<T, any>\n }\n\n /**\n * Group rows for aggregates in `select()`: `count()`, `sum(col)`, `avg(col)`,\n * `min(col)`, `max(col)`, `count(distinct col)`. Every plain column in the\n * select must be grouped. `order()` can use an aggregate's name.\n *\n * @example\n * db.from('posts').select('user_id, n:count(), last:max(created_at)').groupBy('user_id').order('n', { ascending: false })\n */\n groupBy(columns: string): this {\n this.req.groupBy = columns.split(',').map((c) => c.trim()).filter(Boolean)\n return this\n }\n\n /**\n * Leave out repeated rows: `SELECT DISTINCT`.\n *\n * @example\n * db.from('posts').select('user_id').distinct()\n */\n distinct(): this {\n this.req.distinct = true\n return this\n }\n\n // ---- writes ----\n\n /**\n * Insert one or more rows. Values may contain `rel.*` to write related rows\n * in the same transaction. Add `.returning()` to get the new rows back.\n *\n * @example\n * await db.from('users').insert({ name, email }) // data: null, count: 1\n * const { data } = await db.from('users').insert({ name, email }).returning('id') // [{ id }]\n * await db.from('posts').insert({ title, user_id, comments: rel.create([{ body, author_id }]) })\n */\n insert(values: Row | Row[]): WriteBuilder<T> {\n return this.#write('insert', Array.isArray(values) ? values : [values])\n }\n\n /**\n * Insert rows, or handle an existing row with the same `onConflict` key\n * (needs a unique constraint; default: primary key): update it, or with\n * `ignoreDuplicates: true` return it unchanged (find-or-create). `merge`\n * picks which columns change and how. Up to 5,000 rows per call, each key\n * at most once.\n *\n * @example\n * // find-or-create\n * const { data } = await db.from('users')\n * .upsert({ email, name }, { onConflict: 'email', ignoreDuplicates: true })\n * .returning('id')\n * data[0].id\n * // insert or update\n * await db.from('users').upsert({ email, name }, { onConflict: 'email' })\n * // many rows: add the counters, set the rest\n * await db.from('posts_stats').upsert(rows, {\n * onConflict: 'post_id',\n * merge: { views: 'add', likes: 'add', last_viewer_id: 'set' },\n * })\n */\n upsert(rows: Row | Row[], options: UpsertOptions = {}): WriteBuilder<T> {\n const { onConflict } = conflict(options.onConflict)\n const { merge } = options\n return this.#write('insert', Array.isArray(rows) ? rows : [rows], {\n upsert: true,\n ...(options.ignoreDuplicates && { ignoreDuplicates: true }),\n ...(onConflict && { onConflict }),\n ...(merge && { merge: Array.isArray(merge) ? Object.fromEntries(merge.map((c) => [c, 'set' as const])) : merge }),\n })\n }\n\n /**\n * Update every matching row (needs at least one filter). Values may be plain\n * (full replace), `ops.*` (atomic change) or `rel.*` (related rows).\n *\n * @example\n * await db.from('users').update({ name: 'New' }).eq('id', id)\n * await db.from('users').update({ scores: ops.append(4), age: ops.increment() }).eq('id', id).returning('age')\n * await db.from('posts').update({ labels: rel.set([labelId]) }).eq('id', postId)\n */\n update(values: Row): WriteBuilder<T> {\n return this.#write('update', [values])\n }\n\n /**\n * Delete every matching row (needs at least one filter).\n *\n * @example\n * await db.from('posts').delete().eq('id', postId)\n * await db.from('posts').delete().eq('id', postId).returning('title') // the deleted rows\n */\n delete(): WriteBuilder<T> {\n return this.#write('delete', undefined)\n }\n\n #write(action: 'insert' | 'update' | 'delete', values: Row[] | undefined, extra: Partial<QueryRequest> = {}, problem?: string): WriteBuilder<T> {\n const { readFrom, ...rest } = this.req\n const req: QueryRequest = { ...rest, ...extra, action, columns: '*' }\n if (values) req.values = values\n if (readFrom === 'replica') problem ??= `replica() only works with select(); ${action} always runs on the primary`\n return new WriteBuilder<T>(this.transport, req, this.stepName(), problem ?? this.invalid())\n }\n\n // ---- where reads run ----\n\n /**\n * Let this read run on a read replica. Replicas can be a moment behind the\n * primary, so use it where slightly stale rows are fine (lists, search,\n * counts), not right after writing the rows you read. Without a healthy\n * replica on the server it reads from the primary. Reads only, not inside\n * a transaction.\n *\n * @example\n * await db.from('posts').select('title').eq('published', true).replica()\n */\n replica(): this {\n this.req.readFrom = 'replica'\n return this\n }\n\n /**\n * Read from the primary, overriding `createClient(..., { readFrom: 'replica' })`.\n *\n * @example\n * await db.from('users').select().eq('id', id).primary() // just wrote it: read your own write\n */\n primary(): this {\n this.req.readFrom = 'primary'\n return this\n }\n\n /** @internal Applies the client's readFrom default to reads. */\n protected override request(): QueryRequest {\n if (this.req.readFrom || !this.#defaultReadFrom) return this.req\n return { ...this.req, readFrom: this.#defaultReadFrom }\n }\n\n // ---- modifiers ----\n\n /**\n * Sort. Chain several: they apply in the order written. Ascending by default.\n * With `referencedTable` it sorts the rows inside a relation.\n *\n * @example\n * .order('active', { ascending: false }).order('age', { ascending: false }).order('name')\n * .order('meta->>score::int', { ascending: false, nullsFirst: false })\n * .order('title', { referencedTable: 'posts' }) // posts inside each user\n */\n order(column: string, options: { ascending?: boolean; nullsFirst?: boolean } & ReferencedTableOption = {}): this {\n const { ascending = true, nullsFirst } = options\n this.req.order.push({ column, ascending, nullsFirst, table: embedOf(options) })\n return this\n }\n\n /**\n * Random order. After other `order()` calls it only shuffles ties. With a\n * `seed` the order is stable (same seed, same order), so `range()` pages never\n * repeat or skip rows.\n *\n * @example\n * .random().limit(5) // 5 random rows\n * .order('active', { ascending: false }).random() // active first, each group shuffled\n * .random({ seed: sessionId }).range(10, 19) // stable page 2\n * .random({ referencedTable: 'posts' }).limit(3, { referencedTable: 'posts' }) // 3 random posts each\n */\n random(options: ReferencedTableOption & { seed?: string | number } = {}): this {\n const seed = options.seed === undefined ? undefined : String(options.seed)\n this.req.order.push({ random: true, seed, table: embedOf(options) })\n return this\n }\n\n /**\n * At most `count` rows: always an array, `[]` when none match, even for\n * `limit(1)`. With `referencedTable`, at most `count` related rows per parent.\n *\n * @example\n * .limit(10)\n * .limit(3, { referencedTable: 'posts' })\n */\n limit(count: number, options: ReferencedTableOption = {}): this {\n const embed = embedOf(options)\n if (embed) this.req.embedLimits = { ...this.req.embedLimits, [embed]: count }\n else this.req.limit = count\n return this\n }\n\n /**\n * Rows `from` to `to`, inclusive, 0-based.\n *\n * @example\n * .range(0, 9) // page 1\n * .range(10, 19) // page 2\n */\n range(from: number, to: number): this {\n this.req.offset = from\n this.req.limit = to - from + 1\n return this\n }\n\n /**\n * One row: `data` is that row as an object, or `null` when nothing matches\n * (not an error). Adds `LIMIT 1`, so don't combine it with `limit()` or\n * `range()`. With several matches it is the first in your `order()`.\n *\n * @example\n * const { data: user } = await db.from('users').select().eq('id', id).single()\n * if (!user) { ... }\n * const { data: email } = await db.from('emails').select().eq('status', 'active').random().single()\n */\n single(): QueryBuilder<T, T | null> {\n this.#single = true\n this.req.single = 'single'\n return this as unknown as QueryBuilder<T, T | null>\n }\n\n /** @internal */\n override invalid(): string | undefined {\n if (this.problem) return this.problem\n if (this.#single && (this.req.limit !== undefined || this.req.offset !== undefined)) {\n return \"single() already means one row: don't combine it with limit() or range()\"\n }\n return undefined\n }\n}\n\n/**\n * An insert, upsert, update or delete. Like Postgres, it writes every\n * matching row; `data` is `null` and `count` the number of rows written,\n * unless `returning()` asks for the rows.\n */\nexport class WriteBuilder<T extends Row = Row, R = null> extends Builder<T, R> {\n /**\n * Return the written rows, like Postgres `RETURNING`: always an array (one\n * row is `[row]`, none is `[]`). Takes `'*'` (the default) or column names.\n *\n * @example\n * await db.from('users').insert({ name, email }).returning() // [{ id, name, email, ... }]\n * await db.from('users').update({ age: ops.increment() }).eq('id', id).returning('id, age')\n * await db.from('posts').delete().eq('user_id', id).returning('id') // every deleted row\n */\n returning(columns = '*'): WriteBuilder<T, T[]> {\n this.req.columns = columns\n this.req.returning = true\n return this as unknown as WriteBuilder<T, T[]>\n }\n}\n\nexport type { Filter }\n","import type { Row } from './types.js'\n\n/**\n * Atomic update operator, applied by Postgres to the current column value.\n *\n * update({ scores: ops.append(4), meta: ops.merge({ vip: true }), age: ops.increment() })\n */\nexport class Op {\n readonly $op: string\n readonly values: unknown[]\n readonly path: string[]\n\n constructor(op: string, values: unknown[], path: string[] = []) {\n this.$op = op\n this.values = values\n this.path = path\n }\n\n /** Apply at a JSON path inside a json/jsonb column: `ops.append('x').at('address', 'lines')`. */\n at(...path: (string | number)[]): Op {\n return new Op(this.$op, this.values, path.map(String))\n }\n}\n\nexport const ops = {\n /**\n * Add values at the end of a jsonb array or Postgres array. An array argument is added as one element.\n * @example\n * update({ scores: ops.append(4) }) // [1,2,3] -> [1,2,3,4]\n * update({ meta: ops.append('y').at('tags') }) // inside a jsonb object\n */\n append: (...values: unknown[]) => new Op('append', values),\n /**\n * Add values at the start of a jsonb array or Postgres array.\n * @example\n * update({ scores: ops.prepend(0) }) // [1,2,3] -> [0,1,2,3]\n */\n prepend: (...values: unknown[]) => new Op('prepend', values),\n /**\n * Remove every element equal to one of the values.\n * @example\n * update({ scores: ops.remove(2) }) // [1,2,3] -> [1,3]\n */\n remove: (...values: unknown[]) => new Op('remove', values),\n /**\n * Shallow-merge keys into a json/jsonb object.\n * @example\n * update({ meta: ops.merge({ vip: true }) }) // {a:1} -> {a:1,vip:true}\n */\n merge: (object: Row) => new Op('merge', [object]),\n /**\n * Set a value at a JSON path (needs `.at(...)`); a number is an array position.\n * @example\n * update({ meta: ops.set('Lisbon').at('city') })\n * update({ scores: ops.set(9).at(0) }) // [1,2,3] -> [9,2,3]\n */\n set: (value: unknown) => new Op('set', [value]),\n /**\n * Remove the key (or array position) at a JSON path (needs `.at(...)`).\n * @example\n * update({ meta: ops.unset().at('vip') })\n * update({ scores: ops.unset().at(0) }) // [1,2,3] -> [2,3]\n */\n unset: () => new Op('unset', []),\n /**\n * Add to a number column, or to a number at a JSON path (negative to subtract).\n * @example\n * update({ age: ops.increment() })\n * update({ meta: ops.increment(5).at('points') })\n */\n increment: (by = 1) => new Op('increment', [by]),\n}\n","import { QueryBuilder, WriteBuilder } from './builder.js'\nimport { ops } from './ops.js'\nimport { rel } from './rel.js'\nimport { col, now } from './values.js'\nimport type { PgError, QueryResult, ReadFrom, Row, Transport } from './types.js'\n\nexport interface ClientOptions {\n /** Extra headers on every request. */\n headers?: Record<string, string>\n /** Custom fetch (defaults to the global one, Node 18+). */\n fetch?: typeof fetch\n /**\n * Where reads run by default: `'primary'` (default) or `'replica'` (a read\n * replica when the server has a healthy one, else the primary). Writes and\n * transactions always use the primary. Override per query with\n * `.replica()` / `.primary()`.\n */\n readFrom?: ReadFrom\n}\n\n/** Placeholder for a value from an earlier transaction step. */\nexport interface Ref {\n readonly $ref: string\n}\n\n/** Any step a transaction accepts. */\nexport type Step = QueryBuilder<any, any> | WriteBuilder<any, any>\n\n/** The `data` type of a step. */\nexport type StepData<S> = S extends PromiseLike<QueryResult<infer R>> ? R : never\n\nexport interface Tx {\n /** Start a step. Don't await it: return it from the transaction callback. */\n from<T extends Row = Row>(table: string): QueryBuilder<T>\n /**\n * A value from an earlier named step, usable in values and filters.\n * A step that returned one row is read directly (`'post.id'`); with several\n * rows, pick one: `tx.ref('posts.0.id')`.\n *\n * @example\n * tx.from('comments').insert({ post_id: tx.ref('post.id'), body, author_id })\n */\n ref(path: string): Ref\n /** Atomic jsonb/array/number changes, same as `db.ops`. */\n readonly ops: typeof ops\n /** Writes to related rows, same as `db.rel`. */\n readonly rel: typeof rel\n /** Another column of the same row, same as `db.col`. */\n readonly col: typeof col\n /** The database server's time, same as `db.now`. */\n readonly now: typeof now\n}\n\nexport interface Client {\n from<T extends Row = Row>(table: string): QueryBuilder<T>\n /**\n * Atomic jsonb/array/number changes for `update()` values (same as `import { ops }`).\n *\n * @example\n * await db.from('users').update({ scores: db.ops.append(4), age: db.ops.increment() }).eq('id', id)\n */\n readonly ops: typeof ops\n /**\n * Writes to related rows inside `insert()` / `update()` values (same as `import { rel }`).\n *\n * @example\n * await db.from('posts').insert({ title, user_id, comments: db.rel.create([{ body, author_id }]) })\n */\n readonly rel: typeof rel\n /**\n * Another column of the same row, for comparing two columns in a filter.\n *\n * @example\n * await db.from('accounts').select().lt('sent_per_hour', db.col('limit_per_hour'))\n * await db.from('accounts').select().lt(db.col('sent_per_hour').ifNull(0), db.col('limit_per_hour'))\n */\n readonly col: typeof col\n /**\n * The database server's `now()`, optionally `.plus()` / `.minus()` one interval.\n * Works in filters and in insert/update values.\n *\n * @example\n * await db.from('clients').select().lte('sent_at', db.now().minus('1 hour'))\n * await db.from('accounts').update({ last_read_time: db.now() }).eq('id', id)\n */\n readonly now: typeof now\n /**\n * Run several steps in one database transaction: all succeed or nothing is saved.\n * On failure `error.step` / `error.stepName` say which step failed.\n *\n * Return an object (keys name the steps; `data` has the same keys) or an\n * array (name steps with `.as()`; `data` is an array). Don't await the steps.\n *\n * @example\n * const { data, error } = await db.transaction((tx) => ({\n * user: tx.from('users').upsert({ email }, { onConflict: 'email', ignoreDuplicates: true }).returning('id'),\n * post: tx.from('posts').insert({ title, user_id: tx.ref('user.id') }).returning('id'),\n * total: tx.from('posts').eq('user_id', tx.ref('user.id')).count(),\n * }))\n * data.post[0].id // returning() is always an array\n */\n transaction<S extends Record<string, Step>>(\n steps: (tx: Tx) => S,\n ): Promise<QueryResult<{ [K in keyof S]: StepData<S[K]> }>>\n transaction<S extends Step[]>(\n steps: (tx: Tx) => [...S],\n ): Promise<QueryResult<{ [K in keyof S]: StepData<S[K]> }>>\n}\n\nconst fail = (message: string): QueryResult<never> => ({ data: null, error: { message }, count: null, status: 0 })\n\nfunction stepProblem(s: Step): string | undefined {\n if (s.toStep().readFrom === 'replica') return 'transaction steps always run on the primary: drop replica()'\n return s.invalid()\n}\n\n/** Turn the callback's result into wire steps; null + message when it is not usable. */\nfunction toSteps(steps: unknown): { names: string[] | null; wire: object[] } | string {\n if (Array.isArray(steps)) {\n if (steps.length === 0) return 'transaction callback must return a non-empty array or object of steps'\n for (const s of steps) {\n if (!(s instanceof QueryBuilder || s instanceof WriteBuilder)) return 'every transaction step must be tx.from(...)'\n const bad = stepProblem(s)\n if (bad) return bad\n }\n return { names: null, wire: steps.map((s: Step) => s.toStep()) }\n }\n if (steps && typeof steps === 'object') {\n const entries = Object.entries(steps)\n if (entries.length === 0) return 'transaction callback must return a non-empty array or object of steps'\n for (const [name, s] of entries) {\n // JavaScript puts integer-like keys first, which would silently change the step order.\n if (/^(0|[1-9]\\d*)$/.test(name)) return `step name \"${name}\" looks like a number; use a word (JavaScript reorders number keys)`\n if (name.includes('.')) return `step name \"${name}\" must not contain a dot (refs use name.column)`\n if (!(s instanceof QueryBuilder || s instanceof WriteBuilder)) return `step \"${name}\" must be tx.from(...)`\n if (s.stepName()) return `step \"${name}\" already has a name; drop .as(\"${s.stepName()}\") in the object form`\n const bad = stepProblem(s as Step)\n if (bad) return bad\n }\n return { names: entries.map(([n]) => n), wire: entries.map(([name, s]) => ({ ...(s as Step).toStep(), name })) }\n }\n return 'transaction callback must return an array or object of steps'\n}\n\nfunction createTransport(url: string, apiKey: string, options: ClientOptions): Transport {\n const base = url.replace(/\\/+$/, '')\n const fetchImpl = options.fetch ?? globalThis.fetch\n if (!fetchImpl) throw new Error('arn-db: no fetch available; pass options.fetch (Node 18+ has a global fetch)')\n if (typeof apiKey !== 'string' || apiKey === '') throw new Error('arn-db: createClient(url, apiKey) needs an API key')\n\n return {\n async post(path, body) {\n let res: Response\n try {\n res = await fetchImpl(base + path, {\n method: 'POST',\n headers: { ...options.headers, 'Content-Type': 'application/json', apikey: apiKey },\n body: JSON.stringify(body),\n })\n } catch (err) {\n return { data: null, error: { message: (err as Error).message }, count: null, status: 0 }\n }\n let json: { data?: unknown; error?: PgError | null; count?: number | null }\n try {\n json = await res.json()\n } catch {\n return { data: null, error: { message: `invalid response (HTTP ${res.status})` }, count: null, status: res.status }\n }\n if (json.error) return { data: null, error: json.error, count: null, status: res.status }\n return { data: json.data ?? null, error: null, count: json.count ?? null, status: res.status }\n },\n }\n}\n\n/**\n * Server-side client for the Go API (it refuses browser requests). Every call\n * resolves to `{ data, error, count, status }` and never throws.\n *\n * @example\n * const db = createClient(process.env.PG_API_URL!, process.env.PG_API_KEY!)\n * const { data, error } = await db.from('users').select().eq('active', true)\n */\nexport function createClient(url: string, apiKey: string, options: ClientOptions = {}): Client {\n const transport = createTransport(url, apiKey, options)\n const { readFrom } = options\n if (readFrom !== undefined && readFrom !== 'primary' && readFrom !== 'replica') {\n throw new Error(`arn-db: readFrom must be 'primary' or 'replica', got ${JSON.stringify(readFrom)}`)\n }\n return {\n from: (table: string) => new QueryBuilder(transport, table, readFrom),\n ops,\n rel,\n col,\n now,\n\n async transaction(build: (tx: Tx) => unknown): Promise<QueryResult<any>> {\n const tx: Tx = {\n from: (table) => new QueryBuilder(null, table),\n ref: (path) => ({ $ref: path }),\n ops,\n rel,\n col,\n now,\n }\n const steps = toSteps(build(tx))\n if (typeof steps === 'string') return fail(steps)\n\n const res = await transport.post('/transaction', { steps: steps.wire })\n if (res.error || !steps.names) return res\n const results = res.data as unknown[]\n const byName = Object.fromEntries(steps.names.map((n, i) => [n, results[i]]))\n return { ...res, data: byName }\n },\n } as Client\n}\n","import { createClient, type Client } from './client.js'\nimport { ops } from './ops.js'\nimport { rel } from './rel.js'\nimport { col, now } from './values.js'\n\n// The src tsconfig has no Node types: describe only the bits used here.\ninterface NodeProcess {\n env: Record<string, string | undefined>\n loadEnvFile?: (path?: string) => void\n}\n\nlet client: Client | undefined\n\n/** The ARN_DB_URL / ARN_DB_KEY client, made on the first query (so a later dotenv still counts). */\nfunction envClient(): Client {\n if (client) return client\n const proc = (globalThis as { process?: NodeProcess }).process\n if (!proc) throw new Error('arn-db: `db` reads ARN_DB_URL / ARN_DB_KEY from process.env; use createClient(url, key) here')\n // Like arn-knexjs's dotenv/config: read ./.env when the app has not (Node 20.12+; never overrides set vars).\n if ((!proc.env.ARN_DB_URL || !proc.env.ARN_DB_KEY) && proc.loadEnvFile) {\n try {\n proc.loadEnvFile()\n } catch {\n // no .env in the working directory\n }\n }\n const url = proc.env.ARN_DB_URL\n const key = proc.env.ARN_DB_KEY\n if (!url) throw new Error('arn-db: set ARN_DB_URL (e.g. https://your-api.example.com/v2/raw) or use createClient(url, key)')\n if (!key) throw new Error('arn-db: set ARN_DB_KEY or use createClient(url, key)')\n client = createClient(url, key)\n return client\n}\n\n/**\n * Ready-made client from the environment: `ARN_DB_URL` (e.g.\n * `https://your-api.example.com/v2/raw`) and `ARN_DB_KEY`, read on the first query,\n * with `./.env` loaded if they are not set yet. A missing variable throws on\n * that first query. Use `createClient()` for other keys or options.\n *\n * @example\n * import { db } from 'arn-db'\n * const { data, error } = await db.from('users').select().eq('id', id).single()\n */\nexport const db: Client = {\n from: (table) => envClient().from(table),\n transaction: ((build: never) => envClient().transaction(build)) as Client['transaction'],\n ops,\n rel,\n col,\n now,\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":[],"sources":["../src/rel.ts","../src/values.ts","../src/builder.ts","../src/ops.ts","../src/client.ts","../src/env.ts"],"sourcesContent":["import type { Row } from './types.js'\n\n/** A related row's key: the value of a single-column key, or an object for composite keys. */\nexport type Key = string | number | Row\n\nexport type RelOpName = 'create' | 'upsert' | 'connectOrCreate' | 'connect' | 'disconnect' | 'set' | 'update' | 'delete'\n\n/**\n * A write to a related table, used as a value in `insert()` / `update()` under\n * the related table's name (add `!fk_column` when there are several FKs):\n *\n * insert({ title, comments: rel.create([{ body }]), labels: rel.connect([labelId]) })\n *\n * Everything in one insert()/update() runs in one transaction.\n */\nexport interface RelOp {\n readonly $rel: RelOpName\n readonly rows?: Row[]\n readonly keys?: Key[]\n readonly match?: Row\n readonly values?: Row\n readonly onConflict?: string[]\n}\n\nconst list = <T>(v: T | T[]): T[] => (Array.isArray(v) ? v : [v])\n\n/** \"user_id, title\" -> { onConflict: ['user_id', 'title'] } */\nexport const conflict = (columns?: string) =>\n columns ? { onConflict: columns.split(',').map((c) => c.trim()).filter(Boolean) } : {}\n\nexport const rel = {\n /**\n * Insert related rows (they may contain `rel.*` themselves).\n * @example\n * db.from('posts').insert({ title, user_id, comments: rel.create([{ body, author_id }]) })\n */\n create: (rows: Row | Row[]): RelOp => ({ $rel: 'create', rows: list(rows) }),\n\n /**\n * Insert one related row, or update it if its `onConflict` key (default: primary key) exists.\n * @example\n * db.from('posts').update({ comments: rel.upsert({ id: commentId, body: 'v2', author_id }) }).eq('id', postId)\n */\n upsert: (row: Row, options: { onConflict?: string } = {}): RelOp => ({\n $rel: 'upsert',\n rows: [row],\n ...conflict(options.onConflict),\n }),\n\n /**\n * Find one related row by its `onConflict` key (default: primary key), or create it,\n * then link it. An existing row is not changed.\n * @example\n * db.from('posts').insert({ title, user_id, labels: rel.connectOrCreate({ name: 'go' }, { onConflict: 'name' }) })\n */\n connectOrCreate: (row: Row, options: { onConflict?: string } = {}): RelOp => ({\n $rel: 'connectOrCreate',\n rows: [row],\n ...conflict(options.onConflict),\n }),\n\n /**\n * Link existing rows by key. Fails (and rolls back) if a key does not exist.\n * @example\n * db.from('posts').update({ labels: rel.connect([labelA, labelB]) }).eq('id', postId)\n */\n connect: (keys: Key | Key[]): RelOp => ({ $rel: 'connect', keys: list(keys) }),\n\n /**\n * Unlink rows by key (many-to-one: clears the FK; one-to-many: sets the child FK to null).\n * @example\n * db.from('posts').update({ labels: rel.disconnect(labelId) }).eq('id', postId)\n */\n disconnect: (keys: Key | Key[] = []): RelOp => ({ $rel: 'disconnect', keys: list(keys) }),\n\n /**\n * Make exactly these rows linked: unlinks the others, links the missing ones.\n * @example\n * db.from('posts').update({ labels: rel.set([labelA]) }).eq('id', postId) // rel.set([]) unlinks all\n */\n set: (keys: Key[]): RelOp => ({ $rel: 'set', keys }),\n\n /**\n * Update linked rows that match (equality on every key of `match`).\n * @example\n * db.from('posts').update({ comments: rel.update({ id: commentId }, { body: 'edited' }) }).eq('id', postId)\n */\n update: (match: Row, values: Row): RelOp => ({ $rel: 'update', match, values }),\n\n /**\n * Delete linked rows that match; `rel.delete()` deletes all linked rows.\n * @example\n * db.from('posts').update({ comments: rel.delete({ body: 'spam' }) }).eq('id', postId)\n */\n delete: (match: Row = {}): RelOp => ({ $rel: 'delete', match }),\n}\n","/**\n * Values the database fills in: another column of the same row (`db.col`)\n * and the database's current time (`db.now`).\n */\n\n/**\n * Another column of the same row. As a filter value it compares two columns;\n * as the column of a filter it lets `ifNull()` replace NULL first.\n *\n * .lt('sent_per_hour', db.col('limit_per_hour'))\n * .lt(db.col('sent_per_hour').ifNull(0), db.col('limit_per_hour'))\n */\nexport class Col {\n readonly name: string\n readonly #ifNull: { value: unknown } | undefined\n\n constructor(name: string, ifNull?: { value: unknown }) {\n this.name = name\n this.#ifNull = ifNull\n }\n\n /** Use `value` where the column is NULL: `COALESCE(column, value)`. */\n ifNull(value: unknown): Col {\n return new Col(this.name, { value })\n }\n\n /** @internal The NULL replacement, if any. */\n nullValue(): { value: unknown } | undefined {\n return this.#ifNull\n }\n\n /** @internal Wire format. */\n toJSON(): { $col: string; ifNull?: unknown } {\n return this.#ifNull ? { $col: this.name, ifNull: this.#ifNull.value } : { $col: this.name }\n }\n}\n\n/**\n * The database server's `now()`, optionally shifted by one interval like\n * `'1 hour'` or `'30 minutes'` (units: second, minute, hour, day, week,\n * month, year). Works in filters and in insert/update values.\n *\n * .lte('client_sent_time', db.now().minus('1 hour'))\n * .update({ last_read_time: db.now() })\n */\nexport class Now {\n readonly #interval: string | undefined\n\n constructor(interval?: string) {\n this.#interval = interval\n }\n\n /** Later by `interval`: `db.now().plus('1 day')`. */\n plus(interval: string): Now {\n return this.#shift(interval.trim())\n }\n\n /** Earlier by `interval`: `db.now().minus('1 hour')`. */\n minus(interval: string): Now {\n const i = interval.trim()\n return this.#shift(i.startsWith('-') ? i.slice(1) : `-${i}`)\n }\n\n #shift(interval: string): Now {\n if (this.#interval !== undefined) throw new Error('arn-db: db.now() takes one plus() or minus(); use a single interval')\n return new Now(interval)\n }\n\n /** @internal Wire format. */\n toJSON(): { $now: true; interval?: string } {\n return this.#interval === undefined ? { $now: true } : { $now: true, interval: this.#interval }\n }\n}\n\n/** A filter's column: a name, or `db.col(name).ifNull(value)`. */\nexport type Column = string | Col\n\nexport const col = (name: string) => new Col(name)\nexport const now = () => new Now()\n\n/**\n * A raw SQL fragment: one expression inside the query the builder makes, with\n * `?` for each binding (`\\\\?` is a literal `?`, e.g. the jsonb `?` operator).\n * Bindings are sent as query parameters, never pasted into the SQL. Use it in\n * `where()`, `order()`, `having()`, as a `select()` column with `.as()`, and as\n * an insert/update/upsert value or upsert `merge` value. The API key must be\n * allowed to use raw fragments.\n *\n * .where(db.raw('lower(email) = any(?)', [emails]))\n * .update({ sent_total: db.raw('sent_total + ?', [1]) })\n */\nexport class Raw {\n readonly sql: string\n readonly bindings: unknown[]\n readonly #as: string | undefined\n\n constructor(sql: string, bindings: unknown[] = [], as?: string) {\n if (typeof sql !== 'string' || sql.trim() === '') throw new Error('arn-db: raw(sql) needs a non-empty SQL string')\n if (!Array.isArray(bindings)) throw new Error('arn-db: raw(sql, bindings) takes the bindings as an array')\n this.sql = sql\n this.bindings = bindings\n this.#as = as\n }\n\n /** Name of this fragment as a `select()` column: `raw('char_length(title)').as('title_len')`. */\n as(name: string): Raw {\n return new Raw(this.sql, this.bindings, name)\n }\n\n /** @internal The `.as()` name, if any. */\n alias(): string | undefined {\n return this.#as\n }\n\n /** @internal The fragment without its name, for filters, order and having. */\n fragment(): { $raw: string; bindings: unknown[] } {\n return { $raw: this.sql, bindings: this.bindings }\n }\n\n /** @internal Wire format. */\n toJSON(): { $raw: string; bindings: unknown[]; as?: string } {\n return this.#as === undefined ? this.fragment() : { ...this.fragment(), as: this.#as }\n }\n}\n\n/**\n * A raw SQL fragment with `?` bindings: knex's `whereRaw` / `orderByRaw` /\n * `havingRaw` / `knex.raw` value. Throws if `sql` is not a non-empty string\n * or `bindings` not an array; the server checks the rest.\n *\n * @example\n * db.from('users').select().where(raw('lower(trim(email)) = any(?)', [emails]))\n */\nexport const raw = (sql: string, bindings?: unknown[]) => new Raw(sql, bindings)\n","import { conflict } from './rel.js'\nimport { Col, Raw, type Column } from './values.js'\nimport type {\n AggregateFn,\n Filter,\n FilterOp,\n GroupFilter,\n QueryRequest,\n QueryResult,\n RawFragment,\n ReadFrom,\n ReferencedTableOption,\n Row,\n SelectOptions,\n Transport,\n UpsertOptions,\n} from './types.js'\n\nconst embedOf = (o: ReferencedTableOption) => o.referencedTable ?? o.foreignTable\n\n/** One step of a transaction, as sent to the server. */\nexport interface TxStepRequest extends QueryRequest {\n name?: string\n}\n\n/** Filter methods, shared by builders and groups. */\nabstract class Filters {\n // ---- filters ----\n // `column` may be a jsonb path ('meta->>city', 'meta->>score::int') or\n // 'relation.column' to filter related rows ('posts.published').\n\n /** @internal A problem found while building, reported instead of sending. */\n protected problem: string | undefined\n\n /** @internal Adds one filter or group. */\n protected abstract push(filter: Filter): void\n\n #filter(column: Column, op: FilterOp, value: unknown): this {\n if (column instanceof Col) {\n const ifNull = column.nullValue()\n this.push(ifNull ? { column: column.name, op, value, ifNull: ifNull.value } : { column: column.name, op, value })\n } else {\n this.push({ column, op, value })\n }\n return this\n }\n\n /**\n * Equal. Works on columns, jsonb paths and related tables.\n *\n * @example\n * .eq('active', true)\n * .eq('meta->>city', 'Tokyo')\n * .eq('posts.published', true) // filters the related posts\n */\n eq(column: Column, value: unknown): this { return this.#filter(column, 'eq', value) }\n /** Not equal. */\n neq(column: Column, value: unknown): this { return this.#filter(column, 'neq', value) }\n /**\n * Greater than. For numbers inside jsonb, cast: `.gt('meta->>score::int', 90)`.\n * `eq` to `lte` also compare with another column (`db.col`) or the\n * database time (`db.now`).\n *\n * @example\n * .gt('age', 30)\n * .lt('sent_per_hour', db.col('limit_per_hour'))\n * .lt(db.col('sent_per_hour').ifNull(0), db.col('limit_per_hour'))\n * .lte('client_sent_time', db.now().minus('1 hour'))\n */\n gt(column: Column, value: unknown): this { return this.#filter(column, 'gt', value) }\n /** Greater than or equal. @example .gte('meta->>score::int', 90) */\n gte(column: Column, value: unknown): this { return this.#filter(column, 'gte', value) }\n /** Less than. */\n lt(column: Column, value: unknown): this { return this.#filter(column, 'lt', value) }\n /** Less than or equal. */\n lte(column: Column, value: unknown): this { return this.#filter(column, 'lte', value) }\n /**\n * SQL LIKE: `%` is any text, `_` is one character (escape with `\\\\_`).\n *\n * @example\n * .like('name', 'user\\\\_1%')\n */\n like(column: string, pattern: string): this { return this.#filter(column, 'like', pattern) }\n /**\n * Case-insensitive LIKE.\n *\n * @example\n * .ilike('email', '%@example.com')\n * .ilike('posts.comments.body', '%hello%') // search a deeper related table\n */\n ilike(column: string, pattern: string): this { return this.#filter(column, 'ilike', pattern) }\n /** IS NULL / IS TRUE / IS FALSE. @example .is('deleted_at', null) */\n is(column: Column, value: null | boolean): this { return this.#filter(column, 'is', value) }\n /** One of the values. @example .in('name', ['user_3', 'user_5']) */\n in(column: string, values: readonly unknown[]): this { return this.#filter(column, 'in', values) }\n /**\n * Has ALL of the values (arrays), or has these keys and values (jsonb objects).\n *\n * @example\n * .contains('tags', ['one', 'two'])\n * .contains('meta', { city: 'Dhaka', vip: true })\n */\n contains(column: string, value: unknown): this { return this.#filter(column, 'cs', value) }\n /**\n * Has nothing outside the values.\n *\n * @example\n * .containedBy('tags', ['one', 'two', 'three'])\n */\n containedBy(column: string, value: unknown): this { return this.#filter(column, 'cd', value) }\n /**\n * Has ANY of the values. Works on jsonb/json arrays, jsonb paths and Postgres arrays.\n *\n * @example\n * .overlaps('scores', ['one', 'three'])\n * .overlaps('meta->tags', ['red', 'blue'])\n */\n overlaps(column: string, values: readonly unknown[]): this { return this.#filter(column, 'ov', values) }\n /** Any filter by name: `.filter('age', 'gte', 30)`. */\n filter(column: Column, op: FilterOp, value: unknown): this { return this.#filter(column, op, value) }\n /**\n * Equal on every key.\n *\n * @example\n * .match({ active: true, age: 30 })\n */\n match(values: Row): this {\n for (const [k, v] of Object.entries(values)) this.eq(k, v)\n return this\n }\n\n /**\n * A raw SQL condition, knex's `whereRaw`: `AND (<fragment>)`, or one member\n * of an `or()` group. Only takes `raw()`; for a column use `eq()` and friends.\n * The fragment sees the main table's columns, not related tables.\n *\n * @example\n * .where(db.raw('lower(trim(email)) = any(?)', [emails]))\n * .where(db.raw('sent_per_hour < limit_per_hour'))\n * .or((q) => q.where(db.raw('meta \\\\? ?', ['vip'])).eq('age', 30)) // jsonb ? operator\n */\n where(fragment: Raw): this {\n if (fragment instanceof Raw) this.push({ op: 'raw', value: fragment.fragment() })\n else this.problem ??= 'where() takes a raw() fragment; for a column use eq(), gt(), in() and friends'\n return this\n }\n\n // ---- groups ----\n // A group is one condition of its parent. Inside `or()` the conditions are\n // joined with OR; inside `and()` and `not()` with AND. Groups nest, and all\n // conditions in a group must be on the same table.\n\n /**\n * Any of the conditions.\n *\n * @example\n * .or((q) => q.gt('age', 30).eq('meta->>city', 'Dhaka')) // age > 30 OR city = 'Dhaka'\n * .or((q) => q.eq('role', 'admin').and((q) => q.gte('age', 18).is('verified', true)))\n * .or((q) => q.eq('posts.published', true).gt('posts.likes', 10)) // filters the related posts\n */\n or(conditions: (q: FilterGroup) => unknown): this { return this.#group('or', conditions) }\n /**\n * All of the conditions, as one group. Mostly useful inside `or()`.\n *\n * @example\n * .or((q) => q.eq('role', 'admin').and((q) => q.gte('age', 18).eq('active', true)))\n */\n and(conditions: (q: FilterGroup) => unknown): this { return this.#group('and', conditions) }\n /**\n * Not all of the conditions: `NOT (a AND b)`. Like Postgres, NULL is neither\n * true nor false: a row with a null `age` matches neither `.gt('age', 30)`\n * nor `.not('age', 'gt', 30)`.\n *\n * @example\n * .not('deleted_at', 'is', null)\n * .not((q) => q.eq('meta->>city', 'Tokyo').lt('age', 30))\n * .not((q) => q.or((q) => q.eq('a', 1).eq('b', 2))) // neither a = 1 nor b = 2\n */\n not(conditions: (q: FilterGroup) => unknown): this\n not(column: Column, op: FilterOp, value: unknown): this\n not(a: Column | ((q: FilterGroup) => unknown), op?: FilterOp, value?: unknown): this {\n if (typeof a === 'function') return this.#group('not', a)\n return this.#group('not', (q) => q.filter(a, op as FilterOp, value))\n }\n\n #group(group: GroupFilter['group'], conditions: (q: FilterGroup) => unknown): this {\n const q = new FilterGroup()\n conditions(q)\n if (q.problem) this.problem ??= q.problem\n this.push({ group, filters: q.filters })\n return this\n }\n}\n\n/**\n * The conditions inside `or()`, `and()` or `not()`: every filter method, plus\n * nested groups. Chain them on `q`; the callback's return value is ignored.\n */\nexport class FilterGroup extends Filters {\n /** @internal */ readonly filters: Filter[] = []\n\n /** @internal */\n protected push(filter: Filter): void {\n this.filters.push(filter)\n }\n}\n\n/**\n * What reads and writes share: filters, step names and sending. Awaiting a\n * builder sends the request and resolves to `{ data, error, count, status }`;\n * it never throws. `T` is the row type, `R` the type of `data`.\n */\nabstract class Builder<T extends Row, R> extends Filters implements PromiseLike<QueryResult<R>> {\n /** @internal */ protected readonly transport: Transport | null\n /** @internal */ protected readonly req: QueryRequest\n #name: string | undefined\n\n /** @internal */\n constructor(transport: Transport | null, req: QueryRequest, name?: string, problem?: string) {\n super()\n this.transport = transport\n this.req = req\n this.#name = name\n this.problem = problem\n }\n\n /** @internal */\n protected push(filter: Filter): void {\n this.req.filters.push(filter)\n }\n\n /**\n * Name a step in the array form of `transaction()`, so later steps can use\n * its rows with `tx.ref('name.column')`. (In the object form the key is the name.)\n *\n * @example\n * db.transaction((tx) => [\n * tx.from('posts').insert({ title, user_id }).returning('id').as('post'),\n * tx.from('comments').insert({ post_id: tx.ref('post.id'), body, author_id }),\n * ])\n */\n as(name: string): this {\n this.#name = name\n return this\n }\n\n /** @internal The `.as()` name, if any. */\n stepName(): string | undefined {\n return this.#name\n }\n\n /** @internal A problem found while building, reported instead of sending. */\n invalid(): string | undefined {\n return this.problem\n }\n\n // ---- execution ----\n\n /** @internal The request as sent to POST /query. */\n protected request(): QueryRequest {\n return this.req\n }\n\n /** @internal The request as sent to the server. */\n toStep(): TxStepRequest {\n return this.#name ? { name: this.#name, ...this.req } : { ...this.req }\n }\n\n then<A = QueryResult<R>, B = never>(\n onfulfilled?: ((value: QueryResult<R>) => A | PromiseLike<A>) | null,\n onrejected?: ((reason: unknown) => B | PromiseLike<B>) | null,\n ): PromiseLike<A | B> {\n const run = async (): Promise<QueryResult<R>> => {\n const invalid = this.invalid()\n if (invalid) {\n return { data: null, error: { message: invalid }, count: null, status: 0 }\n }\n if (!this.transport) {\n return {\n data: null,\n error: { message: 'this query belongs to a transaction: return it from the transaction callback instead of awaiting it' },\n count: null,\n status: 0,\n }\n }\n return this.transport.post('/query', this.request())\n }\n return run().then(onfulfilled, onrejected)\n }\n}\n\n/**\n * Supabase-style query builder. Reads use `select()` and its modifiers;\n * `insert()` / `upsert()` / `update()` / `delete()` turn it into a\n * {@link WriteBuilder}. See GUIDE.md for recipes.\n *\n * @example\n * const { data, error } = await db.from('users').select('id, name').eq('active', true).random().limit(5)\n */\nexport class QueryBuilder<T extends Row = Row, R = T[]> extends Builder<T, R> {\n #single = false\n #defaultReadFrom: ReadFrom | undefined\n\n /** @internal Use `client.from()` or `tx.from()`. */\n constructor(transport: Transport | null, table: string, readFrom?: ReadFrom) {\n super(transport, {\n table,\n action: 'select',\n columns: '*',\n filters: [],\n order: [],\n returning: false,\n count: false,\n })\n this.#defaultReadFrom = readFrom\n }\n\n // ---- reads ----\n\n /**\n * Columns to return. `'*'` (the default) means all. Supports aliases, jsonb\n * paths, casts, related tables and related counts.\n *\n * `{ count: 'exact' }` also fills `count`; add `head: true` to skip the rows.\n *\n * @example\n * db.from('users').select() // all columns\n * db.from('users').select('id, fullName:name, city:meta->>city, score:meta->>score::int')\n * db.from('posts').select('title, author:users!user_id(name), comments(body), labels(name)')\n * db.from('users').select('name, posts!user_id!inner(title)') // only users that have posts\n * db.from('users').select('name, posts!user_id(count)') // posts: [{ count: 2 }]\n * db.from('posts').select('*', { count: 'exact' }).range(0, 9) // rows + total\n *\n * Extra `raw(...).as(name)` arguments add SQL expressions of the main table\n * as columns (options, if any, go last).\n *\n * @example\n * db.from('posts').select('id, title', db.raw('char_length(title)').as('title_len'))\n */\n select(columns = '*', ...extra: (Raw | SelectOptions)[]): QueryBuilder<T, T[]> {\n this.req.columns = columns\n const rawColumns: (RawFragment & { as: string })[] = []\n for (const e of extra) {\n if (e instanceof Raw) {\n const as = e.alias()\n if (as === undefined) this.problem ??= `raw(\"${e.sql}\") in select() needs a column name: add .as('name')`\n else if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(as)) this.problem ??= `raw(...).as(\"${as}\"): the name must be letters, digits and _`\n else rawColumns.push({ ...e.fragment(), as })\n } else if (e) {\n if (e.count) this.req.count = true\n if (e.head) this.req.head = true\n }\n }\n if (rawColumns.length) this.req.rawColumns = rawColumns\n else delete this.req.rawColumns\n return this as unknown as QueryBuilder<T, T[]>\n }\n\n /**\n * Number of matching rows: `data` is the number. All filters apply, including\n * jsonb paths and `relation.column` filters on `!inner` relations.\n *\n * @example\n * const { data: total } = await db.from('users').count() // 100\n * const { data: active } = await db.from('users').eq('active', true).count() // 75\n */\n count(options: { distinct?: string } = {}): QueryBuilder<T, number> {\n if (options.distinct) return this.#aggregate('count', options.distinct, true)\n this.req.countOnly = true\n return this as unknown as QueryBuilder<T, number>\n }\n\n /**\n * Smallest value of a column over the matching rows: `data` is that value,\n * or `null` when no rows match. `max`, `sum` and `avg` work the same way.\n *\n * @example\n * const { data: first } = await db.from('posts').eq('user_id', id).min('created_at')\n */\n min<V = any>(column: string): QueryBuilder<T, V | null> { return this.#aggregate('min', column) }\n /** Largest value. @example .eq('email', email).max('created_at') */\n max<V = any>(column: string): QueryBuilder<T, V | null> { return this.#aggregate('max', column) }\n /** Sum of a number column, `null` when no rows match. */\n sum(column: string): QueryBuilder<T, number | null> { return this.#aggregate('sum', column) }\n /** Average of a number column, `null` when no rows match. */\n avg(column: string): QueryBuilder<T, number | null> { return this.#aggregate('avg', column) }\n\n #aggregate(fn: AggregateFn, column: string, distinct?: boolean): QueryBuilder<T, any> {\n this.req.aggregate = distinct ? { fn, column, distinct } : { fn, column }\n return this as unknown as QueryBuilder<T, any>\n }\n\n /**\n * Group rows for aggregates in `select()`: `count()`, `sum(col)`, `avg(col)`,\n * `min(col)`, `max(col)`, `count(distinct col)`. Every plain column in the\n * select must be grouped. `order()` can use an aggregate's name.\n *\n * @example\n * db.from('posts').select('user_id, n:count(), last:max(created_at)').groupBy('user_id').order('n', { ascending: false })\n */\n groupBy(columns: string): this {\n this.req.groupBy = columns.split(',').map((c) => c.trim()).filter(Boolean)\n return this\n }\n\n /**\n * Keep only the groups where the raw condition holds, knex's `havingRaw`.\n * Use with `groupBy()`; several calls are joined with AND.\n *\n * @example\n * db.from('posts').select('user_id, n:count()').groupBy('user_id').having(db.raw('count(*) >= ?', [2]))\n */\n having(fragment: Raw): this {\n if (fragment instanceof Raw) (this.req.having ??= []).push(fragment.fragment())\n else this.problem ??= 'having() takes a raw() fragment: having(db.raw(\"count(*) >= ?\", [2]))'\n return this\n }\n\n /**\n * Leave out repeated rows: `SELECT DISTINCT`.\n *\n * @example\n * db.from('posts').select('user_id').distinct()\n */\n distinct(): this {\n this.req.distinct = true\n return this\n }\n\n // ---- writes ----\n\n /**\n * Insert one or more rows. Values may contain `rel.*` to write related rows\n * in the same transaction. Add `.returning()` to get the new rows back.\n *\n * @example\n * await db.from('users').insert({ name, email }) // data: null, count: 1\n * const { data } = await db.from('users').insert({ name, email }).returning('id') // [{ id }]\n * await db.from('posts').insert({ title, user_id, comments: rel.create([{ body, author_id }]) })\n */\n insert(values: Row | Row[]): WriteBuilder<T> {\n return this.#write('insert', Array.isArray(values) ? values : [values])\n }\n\n /**\n * Insert rows, or handle an existing row with the same `onConflict` key\n * (needs a unique constraint; default: primary key): update it, or with\n * `ignoreDuplicates: true` return it unchanged (find-or-create). `merge`\n * picks which columns change and how. Up to 5,000 rows per call, each key\n * at most once.\n *\n * @example\n * // find-or-create\n * const { data } = await db.from('users')\n * .upsert({ email, name }, { onConflict: 'email', ignoreDuplicates: true })\n * .returning('id')\n * data[0].id\n * // insert or update\n * await db.from('users').upsert({ email, name }, { onConflict: 'email' })\n * // many rows: add the counters, set the rest\n * await db.from('posts_stats').upsert(rows, {\n * onConflict: 'post_id',\n * merge: { views: 'add', likes: 'add', last_viewer_id: 'set' },\n * })\n */\n upsert(rows: Row | Row[], options: UpsertOptions = {}): WriteBuilder<T> {\n const { onConflict } = conflict(options.onConflict)\n const { merge } = options\n return this.#write('insert', Array.isArray(rows) ? rows : [rows], {\n upsert: true,\n ...(options.ignoreDuplicates && { ignoreDuplicates: true }),\n ...(onConflict && { onConflict }),\n ...(merge && { merge: Array.isArray(merge) ? Object.fromEntries(merge.map((c) => [c, 'set' as const])) : merge }),\n })\n }\n\n /**\n * Update every matching row (needs at least one filter). Values may be plain\n * (full replace), `ops.*` (atomic change) or `rel.*` (related rows).\n *\n * @example\n * await db.from('users').update({ name: 'New' }).eq('id', id)\n * await db.from('users').update({ scores: ops.append(4), age: ops.increment() }).eq('id', id).returning('age')\n * await db.from('posts').update({ labels: rel.set([labelId]) }).eq('id', postId)\n */\n update(values: Row): WriteBuilder<T> {\n return this.#write('update', [values])\n }\n\n /**\n * Delete every matching row (needs at least one filter).\n *\n * @example\n * await db.from('posts').delete().eq('id', postId)\n * await db.from('posts').delete().eq('id', postId).returning('title') // the deleted rows\n */\n delete(): WriteBuilder<T> {\n return this.#write('delete', undefined)\n }\n\n #write(action: 'insert' | 'update' | 'delete', values: Row[] | undefined, extra: Partial<QueryRequest> = {}, problem?: string): WriteBuilder<T> {\n const { readFrom, ...rest } = this.req\n const req: QueryRequest = { ...rest, ...extra, action, columns: '*' }\n if (values) req.values = values\n if (readFrom === 'replica') problem ??= `replica() only works with select(); ${action} always runs on the primary`\n return new WriteBuilder<T>(this.transport, req, this.stepName(), problem ?? this.invalid())\n }\n\n // ---- where reads run ----\n\n /**\n * Let this read run on a read replica. Replicas can be a moment behind the\n * primary, so use it where slightly stale rows are fine (lists, search,\n * counts), not right after writing the rows you read. Without a healthy\n * replica on the server it reads from the primary. Reads only, not inside\n * a transaction.\n *\n * @example\n * await db.from('posts').select('title').eq('published', true).replica()\n */\n replica(): this {\n this.req.readFrom = 'replica'\n return this\n }\n\n /**\n * Read from the primary, overriding `createClient(..., { readFrom: 'replica' })`.\n *\n * @example\n * await db.from('users').select().eq('id', id).primary() // just wrote it: read your own write\n */\n primary(): this {\n this.req.readFrom = 'primary'\n return this\n }\n\n /** @internal Applies the client's readFrom default to reads. */\n protected override request(): QueryRequest {\n if (this.req.readFrom || !this.#defaultReadFrom) return this.req\n return { ...this.req, readFrom: this.#defaultReadFrom }\n }\n\n // ---- modifiers ----\n\n /**\n * Sort. Chain several: they apply in the order written. Ascending by default.\n * With `referencedTable` it sorts the rows inside a relation.\n *\n * @example\n * .order('active', { ascending: false }).order('age', { ascending: false }).order('name')\n * .order('meta->>score::int', { ascending: false, nullsFirst: false })\n * .order('title', { referencedTable: 'posts' }) // posts inside each user\n * .order(db.raw(\"coalesce(last_read_time, '1970-01-01')\")) // raw, main table only\n */\n order(column: string | Raw, options: { ascending?: boolean; nullsFirst?: boolean } & ReferencedTableOption = {}): this {\n const { ascending = true, nullsFirst } = options\n if (column instanceof Raw) {\n if (embedOf(options)) this.problem ??= 'order(raw(...)) sorts the main table only: drop referencedTable'\n else this.req.order.push({ raw: column.fragment(), ascending, nullsFirst })\n return this\n }\n this.req.order.push({ column, ascending, nullsFirst, table: embedOf(options) })\n return this\n }\n\n /**\n * Random order. After other `order()` calls it only shuffles ties. With a\n * `seed` the order is stable (same seed, same order), so `range()` pages never\n * repeat or skip rows.\n *\n * @example\n * .random().limit(5) // 5 random rows\n * .order('active', { ascending: false }).random() // active first, each group shuffled\n * .random({ seed: sessionId }).range(10, 19) // stable page 2\n * .random({ referencedTable: 'posts' }).limit(3, { referencedTable: 'posts' }) // 3 random posts each\n */\n random(options: ReferencedTableOption & { seed?: string | number } = {}): this {\n const seed = options.seed === undefined ? undefined : String(options.seed)\n this.req.order.push({ random: true, seed, table: embedOf(options) })\n return this\n }\n\n /**\n * At most `count` rows: always an array, `[]` when none match, even for\n * `limit(1)`. With `referencedTable`, at most `count` related rows per parent.\n *\n * @example\n * .limit(10)\n * .limit(3, { referencedTable: 'posts' })\n */\n limit(count: number, options: ReferencedTableOption = {}): this {\n const embed = embedOf(options)\n if (embed) this.req.embedLimits = { ...this.req.embedLimits, [embed]: count }\n else this.req.limit = count\n return this\n }\n\n /**\n * Rows `from` to `to`, inclusive, 0-based.\n *\n * @example\n * .range(0, 9) // page 1\n * .range(10, 19) // page 2\n */\n range(from: number, to: number): this {\n this.req.offset = from\n this.req.limit = to - from + 1\n return this\n }\n\n /**\n * One row: `data` is that row as an object, or `null` when nothing matches\n * (not an error). Adds `LIMIT 1`, so don't combine it with `limit()` or\n * `range()`. With several matches it is the first in your `order()`.\n *\n * @example\n * const { data: user } = await db.from('users').select().eq('id', id).single()\n * if (!user) { ... }\n * const { data: email } = await db.from('emails').select().eq('status', 'active').random().single()\n */\n single(): QueryBuilder<T, T | null> {\n this.#single = true\n this.req.single = 'single'\n return this as unknown as QueryBuilder<T, T | null>\n }\n\n /** @internal */\n override invalid(): string | undefined {\n if (this.problem) return this.problem\n if (this.#single && (this.req.limit !== undefined || this.req.offset !== undefined)) {\n return \"single() already means one row: don't combine it with limit() or range()\"\n }\n return undefined\n }\n}\n\n/**\n * An insert, upsert, update or delete. Like Postgres, it writes every\n * matching row; `data` is `null` and `count` the number of rows written,\n * unless `returning()` asks for the rows.\n */\nexport class WriteBuilder<T extends Row = Row, R = null> extends Builder<T, R> {\n /**\n * Return the written rows, like Postgres `RETURNING`: always an array (one\n * row is `[row]`, none is `[]`). Takes `'*'` (the default) or column names.\n *\n * @example\n * await db.from('users').insert({ name, email }).returning() // [{ id, name, email, ... }]\n * await db.from('users').update({ age: ops.increment() }).eq('id', id).returning('id, age')\n * await db.from('posts').delete().eq('user_id', id).returning('id') // every deleted row\n */\n returning(columns = '*'): WriteBuilder<T, T[]> {\n this.req.columns = columns\n this.req.returning = true\n return this as unknown as WriteBuilder<T, T[]>\n }\n}\n\nexport type { Filter }\n","import type { Row } from './types.js'\n\n/**\n * Atomic update operator, applied by Postgres to the current column value.\n *\n * update({ scores: ops.append(4), meta: ops.merge({ vip: true }), age: ops.increment() })\n */\nexport class Op {\n readonly $op: string\n readonly values: unknown[]\n readonly path: string[]\n\n constructor(op: string, values: unknown[], path: string[] = []) {\n this.$op = op\n this.values = values\n this.path = path\n }\n\n /** Apply at a JSON path inside a json/jsonb column: `ops.append('x').at('address', 'lines')`. */\n at(...path: (string | number)[]): Op {\n return new Op(this.$op, this.values, path.map(String))\n }\n}\n\nexport const ops = {\n /**\n * Add values at the end of a jsonb array or Postgres array. An array argument is added as one element.\n * @example\n * update({ scores: ops.append(4) }) // [1,2,3] -> [1,2,3,4]\n * update({ meta: ops.append('y').at('tags') }) // inside a jsonb object\n */\n append: (...values: unknown[]) => new Op('append', values),\n /**\n * Add values at the start of a jsonb array or Postgres array.\n * @example\n * update({ scores: ops.prepend(0) }) // [1,2,3] -> [0,1,2,3]\n */\n prepend: (...values: unknown[]) => new Op('prepend', values),\n /**\n * Remove every element equal to one of the values.\n * @example\n * update({ scores: ops.remove(2) }) // [1,2,3] -> [1,3]\n */\n remove: (...values: unknown[]) => new Op('remove', values),\n /**\n * Shallow-merge keys into a json/jsonb object.\n * @example\n * update({ meta: ops.merge({ vip: true }) }) // {a:1} -> {a:1,vip:true}\n */\n merge: (object: Row) => new Op('merge', [object]),\n /**\n * Set a value at a JSON path (needs `.at(...)`); a number is an array position.\n * @example\n * update({ meta: ops.set('Lisbon').at('city') })\n * update({ scores: ops.set(9).at(0) }) // [1,2,3] -> [9,2,3]\n */\n set: (value: unknown) => new Op('set', [value]),\n /**\n * Remove the key (or array position) at a JSON path (needs `.at(...)`).\n * @example\n * update({ meta: ops.unset().at('vip') })\n * update({ scores: ops.unset().at(0) }) // [1,2,3] -> [2,3]\n */\n unset: () => new Op('unset', []),\n /**\n * Add to a number column, or to a number at a JSON path (negative to subtract).\n * @example\n * update({ age: ops.increment() })\n * update({ meta: ops.increment(5).at('points') })\n */\n increment: (by = 1) => new Op('increment', [by]),\n}\n","import { QueryBuilder, WriteBuilder } from './builder.js'\nimport { ops } from './ops.js'\nimport { rel } from './rel.js'\nimport { col, now, raw } from './values.js'\nimport type { PgError, QueryResult, ReadFrom, Row, Transport } from './types.js'\n\nexport interface ClientOptions {\n /** Extra headers on every request. */\n headers?: Record<string, string>\n /** Custom fetch (defaults to the global one, Node 18+). */\n fetch?: typeof fetch\n /**\n * Where reads run by default: `'primary'` (default) or `'replica'` (a read\n * replica when the server has a healthy one, else the primary). Writes and\n * transactions always use the primary. Override per query with\n * `.replica()` / `.primary()`.\n */\n readFrom?: ReadFrom\n}\n\n/** Placeholder for a value from an earlier transaction step. */\nexport interface Ref {\n readonly $ref: string\n}\n\n/** Any step a transaction accepts. */\nexport type Step = QueryBuilder<any, any> | WriteBuilder<any, any>\n\n/** The `data` type of a step. */\nexport type StepData<S> = S extends PromiseLike<QueryResult<infer R>> ? R : never\n\nexport interface Tx {\n /** Start a step. Don't await it: return it from the transaction callback. */\n from<T extends Row = Row>(table: string): QueryBuilder<T>\n /**\n * A value from an earlier named step, usable in values and filters.\n * A step that returned one row is read directly (`'post.id'`); with several\n * rows, pick one: `tx.ref('posts.0.id')`.\n *\n * @example\n * tx.from('comments').insert({ post_id: tx.ref('post.id'), body, author_id })\n */\n ref(path: string): Ref\n /** Atomic jsonb/array/number changes, same as `db.ops`. */\n readonly ops: typeof ops\n /** Writes to related rows, same as `db.rel`. */\n readonly rel: typeof rel\n /** Another column of the same row, same as `db.col`. */\n readonly col: typeof col\n /** The database server's time, same as `db.now`. */\n readonly now: typeof now\n /** A raw SQL fragment, same as `db.raw`. */\n readonly raw: typeof raw\n}\n\nexport interface Client {\n from<T extends Row = Row>(table: string): QueryBuilder<T>\n /**\n * Atomic jsonb/array/number changes for `update()` values (same as `import { ops }`).\n *\n * @example\n * await db.from('users').update({ scores: db.ops.append(4), age: db.ops.increment() }).eq('id', id)\n */\n readonly ops: typeof ops\n /**\n * Writes to related rows inside `insert()` / `update()` values (same as `import { rel }`).\n *\n * @example\n * await db.from('posts').insert({ title, user_id, comments: db.rel.create([{ body, author_id }]) })\n */\n readonly rel: typeof rel\n /**\n * Another column of the same row, for comparing two columns in a filter.\n *\n * @example\n * await db.from('accounts').select().lt('sent_per_hour', db.col('limit_per_hour'))\n * await db.from('accounts').select().lt(db.col('sent_per_hour').ifNull(0), db.col('limit_per_hour'))\n */\n readonly col: typeof col\n /**\n * The database server's `now()`, optionally `.plus()` / `.minus()` one interval.\n * Works in filters and in insert/update values.\n *\n * @example\n * await db.from('clients').select().lte('sent_at', db.now().minus('1 hour'))\n * await db.from('accounts').update({ last_read_time: db.now() }).eq('id', id)\n */\n readonly now: typeof now\n /**\n * A raw SQL fragment with `?` bindings (same as `import { raw }`), for\n * `where()`, `order()`, `having()`, `select()` columns and write values.\n * The API key must be allowed to use raw fragments.\n *\n * @example\n * await db.from('accounts').select().where(db.raw('sent_per_hour < limit_per_hour'))\n * await db.from('accounts').update({ sent_total: db.raw('sent_total + ?', [1]) }).eq('id', id)\n */\n readonly raw: typeof raw\n /**\n * Run several steps in one database transaction: all succeed or nothing is saved.\n * On failure `error.step` / `error.stepName` say which step failed.\n *\n * Return an object (keys name the steps; `data` has the same keys) or an\n * array (name steps with `.as()`; `data` is an array). Don't await the steps.\n *\n * @example\n * const { data, error } = await db.transaction((tx) => ({\n * user: tx.from('users').upsert({ email }, { onConflict: 'email', ignoreDuplicates: true }).returning('id'),\n * post: tx.from('posts').insert({ title, user_id: tx.ref('user.id') }).returning('id'),\n * total: tx.from('posts').eq('user_id', tx.ref('user.id')).count(),\n * }))\n * data.post[0].id // returning() is always an array\n */\n transaction<S extends Record<string, Step>>(\n steps: (tx: Tx) => S,\n ): Promise<QueryResult<{ [K in keyof S]: StepData<S[K]> }>>\n transaction<S extends Step[]>(\n steps: (tx: Tx) => [...S],\n ): Promise<QueryResult<{ [K in keyof S]: StepData<S[K]> }>>\n}\n\nconst fail = (message: string): QueryResult<never> => ({ data: null, error: { message }, count: null, status: 0 })\n\nfunction stepProblem(s: Step): string | undefined {\n if (s.toStep().readFrom === 'replica') return 'transaction steps always run on the primary: drop replica()'\n return s.invalid()\n}\n\n/** Turn the callback's result into wire steps; null + message when it is not usable. */\nfunction toSteps(steps: unknown): { names: string[] | null; wire: object[] } | string {\n if (Array.isArray(steps)) {\n if (steps.length === 0) return 'transaction callback must return a non-empty array or object of steps'\n for (const s of steps) {\n if (!(s instanceof QueryBuilder || s instanceof WriteBuilder)) return 'every transaction step must be tx.from(...)'\n const bad = stepProblem(s)\n if (bad) return bad\n }\n return { names: null, wire: steps.map((s: Step) => s.toStep()) }\n }\n if (steps && typeof steps === 'object') {\n const entries = Object.entries(steps)\n if (entries.length === 0) return 'transaction callback must return a non-empty array or object of steps'\n for (const [name, s] of entries) {\n // JavaScript puts integer-like keys first, which would silently change the step order.\n if (/^(0|[1-9]\\d*)$/.test(name)) return `step name \"${name}\" looks like a number; use a word (JavaScript reorders number keys)`\n if (name.includes('.')) return `step name \"${name}\" must not contain a dot (refs use name.column)`\n if (!(s instanceof QueryBuilder || s instanceof WriteBuilder)) return `step \"${name}\" must be tx.from(...)`\n if (s.stepName()) return `step \"${name}\" already has a name; drop .as(\"${s.stepName()}\") in the object form`\n const bad = stepProblem(s as Step)\n if (bad) return bad\n }\n return { names: entries.map(([n]) => n), wire: entries.map(([name, s]) => ({ ...(s as Step).toStep(), name })) }\n }\n return 'transaction callback must return an array or object of steps'\n}\n\nfunction createTransport(url: string, apiKey: string, options: ClientOptions): Transport {\n const base = url.replace(/\\/+$/, '')\n const fetchImpl = options.fetch ?? globalThis.fetch\n if (!fetchImpl) throw new Error('arn-db: no fetch available; pass options.fetch (Node 18+ has a global fetch)')\n if (typeof apiKey !== 'string' || apiKey === '') throw new Error('arn-db: createClient(url, apiKey) needs an API key')\n\n return {\n async post(path, body) {\n let res: Response\n try {\n res = await fetchImpl(base + path, {\n method: 'POST',\n headers: { ...options.headers, 'Content-Type': 'application/json', apikey: apiKey },\n body: JSON.stringify(body),\n })\n } catch (err) {\n return { data: null, error: { message: (err as Error).message }, count: null, status: 0 }\n }\n let json: { data?: unknown; error?: PgError | null; count?: number | null }\n try {\n json = await res.json()\n } catch {\n return { data: null, error: { message: `invalid response (HTTP ${res.status})` }, count: null, status: res.status }\n }\n if (json.error) return { data: null, error: json.error, count: null, status: res.status }\n return { data: json.data ?? null, error: null, count: json.count ?? null, status: res.status }\n },\n }\n}\n\n/**\n * Server-side client for the Go API (it refuses browser requests). Every call\n * resolves to `{ data, error, count, status }` and never throws.\n *\n * @example\n * const db = createClient(process.env.PG_API_URL!, process.env.PG_API_KEY!)\n * const { data, error } = await db.from('users').select().eq('active', true)\n */\nexport function createClient(url: string, apiKey: string, options: ClientOptions = {}): Client {\n const transport = createTransport(url, apiKey, options)\n const { readFrom } = options\n if (readFrom !== undefined && readFrom !== 'primary' && readFrom !== 'replica') {\n throw new Error(`arn-db: readFrom must be 'primary' or 'replica', got ${JSON.stringify(readFrom)}`)\n }\n return {\n from: (table: string) => new QueryBuilder(transport, table, readFrom),\n ops,\n rel,\n col,\n now,\n raw,\n\n async transaction(build: (tx: Tx) => unknown): Promise<QueryResult<any>> {\n const tx: Tx = {\n from: (table) => new QueryBuilder(null, table),\n ref: (path) => ({ $ref: path }),\n ops,\n rel,\n col,\n now,\n raw,\n }\n const steps = toSteps(build(tx))\n if (typeof steps === 'string') return fail(steps)\n\n const res = await transport.post('/transaction', { steps: steps.wire })\n if (res.error || !steps.names) return res\n const results = res.data as unknown[]\n const byName = Object.fromEntries(steps.names.map((n, i) => [n, results[i]]))\n return { ...res, data: byName }\n },\n } as Client\n}\n","import { createClient, type Client } from './client.js'\nimport { ops } from './ops.js'\nimport { rel } from './rel.js'\nimport { col, now, raw } from './values.js'\n\n// The src tsconfig has no Node types: describe only the bits used here.\ninterface NodeProcess {\n env: Record<string, string | undefined>\n loadEnvFile?: (path?: string) => void\n}\n\nlet client: Client | undefined\nconst proc = (globalThis as { process?: NodeProcess }).process\n\n/** Reads ./.env into process.env (Node 20.12+; never overrides set vars). */\nfunction loadDotEnv(): void {\n if (!proc?.loadEnvFile) return\n try {\n proc.loadEnvFile()\n } catch {\n // no .env in the working directory\n }\n}\n\n// Like arn-knexjs's `import \"dotenv/config\"`: importing arn-db loads ./.env, so the\n// app's other variables are set too and its files need no dotenv import.\nloadDotEnv()\n\n/** The ARN_DB_URL / ARN_DB_KEY client, made on the first query (so a later dotenv still counts). */\nfunction envClient(): Client {\n if (client) return client\n if (!proc) throw new Error('arn-db: `db` reads ARN_DB_URL / ARN_DB_KEY from process.env; use createClient(url, key) here')\n // The app may have changed directory or set the variables after the import: try once more.\n if (!proc.env.ARN_DB_URL || !proc.env.ARN_DB_KEY) loadDotEnv()\n const url = proc.env.ARN_DB_URL\n const key = proc.env.ARN_DB_KEY\n if (!url) throw new Error('arn-db: set ARN_DB_URL (e.g. https://your-api.example.com/v2/raw) or use createClient(url, key)')\n if (!key) throw new Error('arn-db: set ARN_DB_KEY or use createClient(url, key)')\n client = createClient(url, key)\n return client\n}\n\n/**\n * Ready-made client from the environment: `ARN_DB_URL` (e.g.\n * `https://your-api.example.com/v2/raw`) and `ARN_DB_KEY`, read on the first query,\n * with `./.env` loaded if they are not set yet. A missing variable throws on\n * that first query. Use `createClient()` for other keys or options.\n *\n * @example\n * import { db } from 'arn-db'\n * const { data, error } = await db.from('users').select().eq('id', id).single()\n */\nexport const db: Client = {\n from: (table) => envClient().from(table),\n transaction: ((build: never) => envClient().transaction(build)) as Client['transaction'],\n ops,\n rel,\n col,\n now,\n raw,\n}\n"],"mappings":"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"}
|
package/dist/index.d.cts
CHANGED
|
@@ -52,6 +52,47 @@ export declare class Now {
|
|
|
52
52
|
type Column = string | Col;
|
|
53
53
|
export declare const col: (name: string) => Col;
|
|
54
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;
|
|
55
96
|
/**
|
|
56
97
|
* Atomic update operator, applied by Postgres to the current column value.
|
|
57
98
|
*
|
|
@@ -150,9 +191,21 @@ interface GroupFilter {
|
|
|
150
191
|
group: 'and' | 'or' | 'not';
|
|
151
192
|
filters: Filter[];
|
|
152
193
|
}
|
|
153
|
-
|
|
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;
|
|
154
205
|
interface Order {
|
|
155
206
|
column?: string;
|
|
207
|
+
/** `order(raw(...))`: sort by the fragment instead of a column. */
|
|
208
|
+
raw?: RawFragment;
|
|
156
209
|
ascending?: boolean;
|
|
157
210
|
nullsFirst?: boolean;
|
|
158
211
|
random?: boolean;
|
|
@@ -180,8 +233,14 @@ interface QueryRequest {
|
|
|
180
233
|
upsert?: boolean;
|
|
181
234
|
onConflict?: string[];
|
|
182
235
|
ignoreDuplicates?: boolean;
|
|
183
|
-
merge?: Record<string, MergeMode | Op>;
|
|
236
|
+
merge?: Record<string, MergeMode | Op | Raw>;
|
|
184
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
|
+
})[];
|
|
185
244
|
distinct?: boolean;
|
|
186
245
|
aggregate?: {
|
|
187
246
|
fn: AggregateFn;
|
|
@@ -210,14 +269,21 @@ interface UpsertOptions {
|
|
|
210
269
|
/**
|
|
211
270
|
* Change only these columns of an existing row; the rest keep their stored
|
|
212
271
|
* value. Each is a {@link MergeMode} or a `db.ops` value applied to the stored
|
|
213
|
-
* value
|
|
214
|
-
*
|
|
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.
|
|
215
275
|
*
|
|
216
276
|
* @example
|
|
217
277
|
* merge: { total: 'add', email: 'set', hits: db.ops.increment(1) }
|
|
278
|
+
* merge: { total: db.raw('greatest(accounts.total, excluded.total)') }
|
|
218
279
|
* merge: ['email', 'name']
|
|
219
280
|
*/
|
|
220
|
-
merge?: Record<string, MergeMode | Op> | string[];
|
|
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;
|
|
221
287
|
}
|
|
222
288
|
/** Options for `order()`, `random()` and `limit()` that target an embedded table. */
|
|
223
289
|
interface ReferencedTableOption {
|
|
@@ -235,6 +301,8 @@ interface TxStepRequest extends QueryRequest {
|
|
|
235
301
|
/** Filter methods, shared by builders and groups. */
|
|
236
302
|
declare abstract class Filters {
|
|
237
303
|
#private;
|
|
304
|
+
/** @internal A problem found while building, reported instead of sending. */
|
|
305
|
+
protected problem: string | undefined;
|
|
238
306
|
/** @internal Adds one filter or group. */
|
|
239
307
|
protected abstract push(filter: Filter): void;
|
|
240
308
|
/**
|
|
@@ -317,6 +385,17 @@ declare abstract class Filters {
|
|
|
317
385
|
* .match({ active: true, age: 30 })
|
|
318
386
|
*/
|
|
319
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;
|
|
320
399
|
/**
|
|
321
400
|
* Any of the conditions.
|
|
322
401
|
*
|
|
@@ -364,8 +443,6 @@ declare abstract class Builder<T extends Row, R> extends Filters implements Prom
|
|
|
364
443
|
#private;
|
|
365
444
|
/** @internal */ protected readonly transport: Transport | null;
|
|
366
445
|
/** @internal */ protected readonly req: QueryRequest;
|
|
367
|
-
/** @internal A problem found while building, reported instead of sending. */
|
|
368
|
-
protected problem: string | undefined;
|
|
369
446
|
/** @internal */
|
|
370
447
|
constructor(transport: Transport | null, req: QueryRequest, name?: string, problem?: string);
|
|
371
448
|
/** @internal */
|
|
@@ -416,11 +493,14 @@ export declare class QueryBuilder<T extends Row = Row, R = T[]> extends Builder<
|
|
|
416
493
|
* db.from('users').select('name, posts!user_id!inner(title)') // only users that have posts
|
|
417
494
|
* db.from('users').select('name, posts!user_id(count)') // posts: [{ count: 2 }]
|
|
418
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'))
|
|
419
502
|
*/
|
|
420
|
-
select(columns?: string,
|
|
421
|
-
count?: 'exact';
|
|
422
|
-
head?: boolean;
|
|
423
|
-
}): QueryBuilder<T, T[]>;
|
|
503
|
+
select(columns?: string, ...extra: (Raw | SelectOptions)[]): QueryBuilder<T, T[]>;
|
|
424
504
|
/**
|
|
425
505
|
* Number of matching rows: `data` is the number. All filters apply, including
|
|
426
506
|
* jsonb paths and `relation.column` filters on `!inner` relations.
|
|
@@ -455,6 +535,14 @@ export declare class QueryBuilder<T extends Row = Row, R = T[]> extends Builder<
|
|
|
455
535
|
* db.from('posts').select('user_id, n:count(), last:max(created_at)').groupBy('user_id').order('n', { ascending: false })
|
|
456
536
|
*/
|
|
457
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;
|
|
458
546
|
/**
|
|
459
547
|
* Leave out repeated rows: `SELECT DISTINCT`.
|
|
460
548
|
*
|
|
@@ -540,8 +628,9 @@ export declare class QueryBuilder<T extends Row = Row, R = T[]> extends Builder<
|
|
|
540
628
|
* .order('active', { ascending: false }).order('age', { ascending: false }).order('name')
|
|
541
629
|
* .order('meta->>score::int', { ascending: false, nullsFirst: false })
|
|
542
630
|
* .order('title', { referencedTable: 'posts' }) // posts inside each user
|
|
631
|
+
* .order(db.raw("coalesce(last_read_time, '1970-01-01')")) // raw, main table only
|
|
543
632
|
*/
|
|
544
|
-
order(column: string, options?: {
|
|
633
|
+
order(column: string | Raw, options?: {
|
|
545
634
|
ascending?: boolean;
|
|
546
635
|
nullsFirst?: boolean;
|
|
547
636
|
} & ReferencedTableOption): this;
|
|
@@ -722,6 +811,8 @@ interface Tx {
|
|
|
722
811
|
readonly col: typeof col;
|
|
723
812
|
/** The database server's time, same as `db.now`. */
|
|
724
813
|
readonly now: typeof now;
|
|
814
|
+
/** A raw SQL fragment, same as `db.raw`. */
|
|
815
|
+
readonly raw: typeof raw;
|
|
725
816
|
}
|
|
726
817
|
interface Client {
|
|
727
818
|
from<T extends Row = Row>(table: string): QueryBuilder<T>;
|
|
@@ -756,6 +847,16 @@ interface Client {
|
|
|
756
847
|
* await db.from('accounts').update({ last_read_time: db.now() }).eq('id', id)
|
|
757
848
|
*/
|
|
758
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;
|
|
759
860
|
/**
|
|
760
861
|
* Run several steps in one database transaction: all succeed or nothing is saved.
|
|
761
862
|
* On failure `error.step` / `error.stepName` say which step failed.
|
|
@@ -794,5 +895,5 @@ export declare function createClient(url: string, apiKey: string, options?: Clie
|
|
|
794
895
|
* const { data, error } = await db.from('users').select().eq('id', id).single()
|
|
795
896
|
*/
|
|
796
897
|
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 };
|
|
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 };
|
|
798
899
|
//# sourceMappingURL=index.d.cts.map
|