UNPKG

@supabase/postgrest-js

Version:
369 lines (354 loc) 11.9 kB
import PostgrestBuilder from './PostgrestBuilder' import { InvalidMethodError } from './PostgrestFilterBuilder' import { GetResult } from './select-query-parser/result' import { GenericSchema, CheckMatchingArrayTypes, ClientServerOptions, MaxAffectedEnabled, } from './types' export default class PostgrestTransformBuilder< ClientOptions extends ClientServerOptions, Schema extends GenericSchema, Row extends Record<string, unknown>, Result, RelationName = unknown, Relationships = unknown, Method = unknown > extends PostgrestBuilder<ClientOptions, Result> { /** * Perform a SELECT on the query result. * * By default, `.insert()`, `.update()`, `.upsert()`, and `.delete()` do not * return modified rows. By calling this method, modified rows are returned in * `data`. * * @param columns - The columns to retrieve, separated by commas */ select< Query extends string = '*', NewResultOne = GetResult<Schema, Row, RelationName, Relationships, Query, ClientOptions> >( columns?: Query ): PostgrestTransformBuilder< ClientOptions, Schema, Row, NewResultOne[], RelationName, Relationships, Method > { // Remove whitespaces except when quoted let quoted = false const cleanedColumns = (columns ?? '*') .split('') .map((c) => { if (/\s/.test(c) && !quoted) { return '' } if (c === '"') { quoted = !quoted } return c }) .join('') this.url.searchParams.set('select', cleanedColumns) this.headers.append('Prefer', 'return=representation') return this as unknown as PostgrestTransformBuilder< ClientOptions, Schema, Row, NewResultOne[], RelationName, Relationships, Method > } order<ColumnName extends string & keyof Row>( column: ColumnName, options?: { ascending?: boolean; nullsFirst?: boolean; referencedTable?: undefined } ): this order( column: string, options?: { ascending?: boolean; nullsFirst?: boolean; referencedTable?: string } ): this /** * @deprecated Use `options.referencedTable` instead of `options.foreignTable` */ order<ColumnName extends string & keyof Row>( column: ColumnName, options?: { ascending?: boolean; nullsFirst?: boolean; foreignTable?: undefined } ): this /** * @deprecated Use `options.referencedTable` instead of `options.foreignTable` */ order( column: string, options?: { ascending?: boolean; nullsFirst?: boolean; foreignTable?: string } ): this /** * Order the query result by `column`. * * You can call this method multiple times to order by multiple columns. * * You can order referenced tables, but it only affects the ordering of the * parent table if you use `!inner` in the query. * * @param column - The column to order by * @param options - Named parameters * @param options.ascending - If `true`, the result will be in ascending order * @param options.nullsFirst - If `true`, `null`s appear first. If `false`, * `null`s appear last. * @param options.referencedTable - Set this to order a referenced table by * its columns * @param options.foreignTable - Deprecated, use `options.referencedTable` * instead */ order( column: string, { ascending = true, nullsFirst, foreignTable, referencedTable = foreignTable, }: { ascending?: boolean nullsFirst?: boolean foreignTable?: string referencedTable?: string } = {} ): this { const key = referencedTable ? `${referencedTable}.order` : 'order' const existingOrder = this.url.searchParams.get(key) this.url.searchParams.set( key, `${existingOrder ? `${existingOrder},` : ''}${column}.${ascending ? 'asc' : 'desc'}${ nullsFirst === undefined ? '' : nullsFirst ? '.nullsfirst' : '.nullslast' }` ) return this } /** * Limit the query result by `count`. * * @param count - The maximum number of rows to return * @param options - Named parameters * @param options.referencedTable - Set this to limit rows of referenced * tables instead of the parent table * @param options.foreignTable - Deprecated, use `options.referencedTable` * instead */ limit( count: number, { foreignTable, referencedTable = foreignTable, }: { foreignTable?: string; referencedTable?: string } = {} ): this { const key = typeof referencedTable === 'undefined' ? 'limit' : `${referencedTable}.limit` this.url.searchParams.set(key, `${count}`) return this } /** * Limit the query result by starting at an offset `from` and ending at the offset `to`. * Only records within this range are returned. * This respects the query order and if there is no order clause the range could behave unexpectedly. * The `from` and `to` values are 0-based and inclusive: `range(1, 3)` will include the second, third * and fourth rows of the query. * * @param from - The starting index from which to limit the result * @param to - The last index to which to limit the result * @param options - Named parameters * @param options.referencedTable - Set this to limit rows of referenced * tables instead of the parent table * @param options.foreignTable - Deprecated, use `options.referencedTable` * instead */ range( from: number, to: number, { foreignTable, referencedTable = foreignTable, }: { foreignTable?: string; referencedTable?: string } = {} ): this { const keyOffset = typeof referencedTable === 'undefined' ? 'offset' : `${referencedTable}.offset` const keyLimit = typeof referencedTable === 'undefined' ? 'limit' : `${referencedTable}.limit` this.url.searchParams.set(keyOffset, `${from}`) // Range is inclusive, so add 1 this.url.searchParams.set(keyLimit, `${to - from + 1}`) return this } /** * Set the AbortSignal for the fetch request. * * @param signal - The AbortSignal to use for the fetch request */ abortSignal(signal: AbortSignal): this { this.signal = signal return this } /** * Return `data` as a single object instead of an array of objects. * * Query result must be one row (e.g. using `.limit(1)`), otherwise this * returns an error. */ single<ResultOne = Result extends (infer ResultOne)[] ? ResultOne : never>(): PostgrestBuilder< ClientOptions, ResultOne > { this.headers.set('Accept', 'application/vnd.pgrst.object+json') return this as unknown as PostgrestBuilder<ClientOptions, ResultOne> } /** * Return `data` as a single object instead of an array of objects. * * Query result must be zero or one row (e.g. using `.limit(1)`), otherwise * this returns an error. */ maybeSingle< ResultOne = Result extends (infer ResultOne)[] ? ResultOne : never >(): PostgrestBuilder<ClientOptions, ResultOne | null> { // Temporary partial fix for https://github.com/supabase/postgrest-js/issues/361 // Issue persists e.g. for `.insert([...]).select().maybeSingle()` if (this.method === 'GET') { this.headers.set('Accept', 'application/json') } else { this.headers.set('Accept', 'application/vnd.pgrst.object+json') } this.isMaybeSingle = true return this as unknown as PostgrestBuilder<ClientOptions, ResultOne | null> } /** * Return `data` as a string in CSV format. */ csv(): PostgrestBuilder<ClientOptions, string> { this.headers.set('Accept', 'text/csv') return this as unknown as PostgrestBuilder<ClientOptions, string> } /** * Return `data` as an object in [GeoJSON](https://geojson.org) format. */ geojson(): PostgrestBuilder<ClientOptions, Record<string, unknown>> { this.headers.set('Accept', 'application/geo+json') return this as unknown as PostgrestBuilder<ClientOptions, Record<string, unknown>> } /** * Return `data` as the EXPLAIN plan for the query. * * You need to enable the * [db_plan_enabled](https://supabase.com/docs/guides/database/debugging-performance#enabling-explain) * setting before using this method. * * @param options - Named parameters * * @param options.analyze - If `true`, the query will be executed and the * actual run time will be returned * * @param options.verbose - If `true`, the query identifier will be returned * and `data` will include the output columns of the query * * @param options.settings - If `true`, include information on configuration * parameters that affect query planning * * @param options.buffers - If `true`, include information on buffer usage * * @param options.wal - If `true`, include information on WAL record generation * * @param options.format - The format of the output, can be `"text"` (default) * or `"json"` */ explain({ analyze = false, verbose = false, settings = false, buffers = false, wal = false, format = 'text', }: { analyze?: boolean verbose?: boolean settings?: boolean buffers?: boolean wal?: boolean format?: 'json' | 'text' } = {}) { const options = [ analyze ? 'analyze' : null, verbose ? 'verbose' : null, settings ? 'settings' : null, buffers ? 'buffers' : null, wal ? 'wal' : null, ] .filter(Boolean) .join('|') // An Accept header can carry multiple media types but postgrest-js always sends one const forMediatype = this.headers.get('Accept') ?? 'application/json' this.headers.set( 'Accept', `application/vnd.pgrst.plan+${format}; for="${forMediatype}"; options=${options};` ) if (format === 'json') { return this as unknown as PostgrestBuilder<ClientOptions, Record<string, unknown>[]> } else { return this as unknown as PostgrestBuilder<ClientOptions, string> } } /** * Rollback the query. * * `data` will still be returned, but the query is not committed. */ rollback(): this { this.headers.append('Prefer', 'tx=rollback') return this } /** * Override the type of the returned `data`. * * @typeParam NewResult - The new result type to override with * @deprecated Use overrideTypes<yourType, { merge: false }>() method at the end of your call chain instead */ returns<NewResult>(): PostgrestTransformBuilder< ClientOptions, Schema, Row, CheckMatchingArrayTypes<Result, NewResult>, RelationName, Relationships, Method > { return this as unknown as PostgrestTransformBuilder< ClientOptions, Schema, Row, CheckMatchingArrayTypes<Result, NewResult>, RelationName, Relationships, Method > } /** * Set the maximum number of rows that can be affected by the query. * Only available in PostgREST v13+ and only works with PATCH and DELETE methods. * * @param value - The maximum number of rows that can be affected */ maxAffected(value: number): MaxAffectedEnabled<ClientOptions['PostgrestVersion']> extends true ? // TODO: update the RPC case to only work on RPC that returns SETOF rows Method extends 'PATCH' | 'DELETE' | 'RPC' ? this : InvalidMethodError<'maxAffected method only available on update or delete'> : InvalidMethodError<'maxAffected method only available on postgrest 13+'> { this.headers.append('Prefer', 'handling=strict') this.headers.append('Prefer', `max-affected=${value}`) return this as unknown as MaxAffectedEnabled<ClientOptions['PostgrestVersion']> extends true ? Method extends 'PATCH' | 'DELETE' | 'RPC' ? this : InvalidMethodError<'maxAffected method only available on update or delete'> : InvalidMethodError<'maxAffected method only available on postgrest 13+'> } }