| import PostgrestQueryBuilder from './PostgrestQueryBuilder' | |
| import PostgrestFilterBuilder from './PostgrestFilterBuilder' | |
| import { Fetch, GenericSchema, ClientServerOptions } from './types/common/common' | |
| import { GetRpcFunctionFilterBuilderByArgs } from './types/common/rpc' | |
| /** | |
| * PostgREST client. | |
| * | |
| * @typeParam Database - Types for the schema from the [type | |
| * generator](https://supabase.com/docs/reference/javascript/next/typescript-support) | |
| * | |
| * @typeParam SchemaName - Postgres schema to switch to. Must be a string | |
| * literal, the same one passed to the constructor. If the schema is not | |
| * `"public"`, this must be supplied manually. | |
| */ | |
| export default class PostgrestClient< | |
| Database = any, | |
| ClientOptions extends ClientServerOptions = Database extends { | |
| __InternalSupabase: infer I extends ClientServerOptions | |
| } | |
| ? I | |
| : {}, | |
| SchemaName extends string & keyof Omit<Database, '__InternalSupabase'> = | |
| 'public' extends keyof Omit<Database, '__InternalSupabase'> | |
| ? 'public' | |
| : string & keyof Omit<Database, '__InternalSupabase'>, | |
| Schema extends GenericSchema = Omit< | |
| Database, | |
| '__InternalSupabase' | |
| >[SchemaName] extends GenericSchema | |
| ? Omit<Database, '__InternalSupabase'>[SchemaName] | |
| : any, | |
| > { | |
| url: string | |
| headers: Headers | |
| schemaName?: SchemaName | |
| fetch?: Fetch | |
| urlLengthLimit: number | |
| // Retry configuration - enabled by default | |
| retry?: boolean | |
| // TODO: Add back shouldThrowOnError once we figure out the typings | |
| /** | |
| * Creates a PostgREST client. | |
| * | |
| * @param url - URL of the PostgREST endpoint | |
| * @param options - Named parameters | |
| * @param options.headers - Custom headers | |
| * @param options.schema - Postgres schema to switch to | |
| * @param options.fetch - Custom fetch | |
| * @param options.timeout - Optional timeout in milliseconds for all requests. When set, requests will automatically abort after this duration to prevent indefinite hangs. | |
| * @param options.urlLengthLimit - Maximum URL length in characters before warnings/errors are triggered. Defaults to 8000. | |
| * @param options.retry - Enable or disable automatic retries for transient errors. | |
| * When enabled, idempotent requests (GET, HEAD, OPTIONS) that fail with network | |
| * errors or HTTP 503/520 responses will be automatically retried up to 3 times | |
| * with exponential backoff (1s, 2s, 4s). Defaults to `true`. | |
| * @example Using supabase-js (recommended) | |
| * ```ts | |
| * import { createClient } from '@supabase/supabase-js' | |
| * | |
| * const supabase = createClient('https://xyzcompany.supabase.co', 'your-publishable-key') | |
| * const { data, error } = await supabase.from('profiles').select('*') | |
| * ``` | |
| * | |
| * @category Database | |
| * | |
| * @remarks | |
| * - A `timeout` option (in milliseconds) can be set to automatically abort requests that take too long. | |
| * - A `urlLengthLimit` option (default: 8000) can be set to control when URL length warnings are included in error messages for aborted requests. | |
| * | |
| * @example Standalone import for bundle-sensitive environments | |
| * ```ts | |
| * import { PostgrestClient } from '@supabase/postgrest-js' | |
| * | |
| * const postgrest = new PostgrestClient('https://xyzcompany.supabase.co/rest/v1', { | |
| * headers: { apikey: 'your-publishable-key' }, | |
| * schema: 'public', | |
| * timeout: 30000, // 30 second timeout | |
| * }) | |
| * ``` | |
| */ | |
| constructor( | |
| url: string, | |
| { | |
| headers = {}, | |
| schema, | |
| fetch, | |
| timeout, | |
| urlLengthLimit = 8000, | |
| retry, | |
| }: { | |
| headers?: HeadersInit | |
| schema?: SchemaName | |
| fetch?: Fetch | |
| timeout?: number | |
| urlLengthLimit?: number | |
| retry?: boolean | |
| } = {} | |
| ) { | |
| this.url = url | |
| this.headers = new Headers(headers) | |
| this.schemaName = schema | |
| this.urlLengthLimit = urlLengthLimit | |
| const originalFetch = fetch ?? globalThis.fetch | |
| // Wrap fetch with timeout if specified | |
| if (timeout !== undefined && timeout > 0) { | |
| this.fetch = (input, init) => { | |
| const controller = new AbortController() | |
| const timeoutId = setTimeout(() => controller.abort(), timeout) | |
| // Merge abort signals if one already exists | |
| const existingSignal = init?.signal | |
| if (existingSignal) { | |
| // If the existing signal is already aborted, use it directly | |
| if (existingSignal.aborted) { | |
| clearTimeout(timeoutId) | |
| return originalFetch(input, init) | |
| } | |
| // Listen to existing signal and abort our controller too | |
| const abortHandler = () => { | |
| clearTimeout(timeoutId) | |
| controller.abort() | |
| } | |
| existingSignal.addEventListener('abort', abortHandler, { once: true }) | |
| return originalFetch(input, { | |
| ...init, | |
| signal: controller.signal, | |
| }).finally(() => { | |
| clearTimeout(timeoutId) | |
| existingSignal.removeEventListener('abort', abortHandler) | |
| }) | |
| } | |
| return originalFetch(input, { | |
| ...init, | |
| signal: controller.signal, | |
| }).finally(() => clearTimeout(timeoutId)) | |
| } | |
| } else { | |
| this.fetch = originalFetch | |
| } | |
| this.retry = retry | |
| } | |
| /** | |
| * Perform a query on a table or a view. | |
| * | |
| * @param relation - The table or view name to query | |
| * | |
| * @category Database | |
| */ | |
| from< | |
| TableName extends string & keyof Schema['Tables'], | |
| Table extends Schema['Tables'][TableName], | |
| >(relation: TableName): PostgrestQueryBuilder<ClientOptions, Schema, Table, TableName> | |
| from<ViewName extends string & keyof Schema['Views'], View extends Schema['Views'][ViewName]>( | |
| relation: ViewName | |
| ): PostgrestQueryBuilder<ClientOptions, Schema, View, ViewName> | |
| from( | |
| relation: (string & keyof Schema['Tables']) | (string & keyof Schema['Views']) | |
| ): PostgrestQueryBuilder<ClientOptions, Schema, any, any> { | |
| if (!relation || typeof relation !== 'string' || relation.trim() === '') { | |
| throw new Error('Invalid relation name: relation must be a non-empty string.') | |
| } | |
| const url = new URL(`${this.url}/${relation}`) | |
| return new PostgrestQueryBuilder(url, { | |
| headers: new Headers(this.headers), | |
| schema: this.schemaName, | |
| fetch: this.fetch, | |
| urlLengthLimit: this.urlLengthLimit, | |
| retry: this.retry, | |
| }) | |
| } | |
| /** | |
| * Select a schema to query or perform an function (rpc) call. | |
| * | |
| * The schema needs to be on the list of exposed schemas inside Supabase. | |
| * | |
| * @param schema - The schema to query | |
| * | |
| * @category Database | |
| */ | |
| schema<DynamicSchema extends string & keyof Omit<Database, '__InternalSupabase'>>( | |
| schema: DynamicSchema | |
| ): PostgrestClient< | |
| Database, | |
| ClientOptions, | |
| DynamicSchema, | |
| Database[DynamicSchema] extends GenericSchema ? Database[DynamicSchema] : any | |
| > { | |
| return new PostgrestClient(this.url, { | |
| headers: this.headers, | |
| schema, | |
| fetch: this.fetch, | |
| urlLengthLimit: this.urlLengthLimit, | |
| retry: this.retry, | |
| }) | |
| } | |
| /** | |
| * Perform a function call. | |
| * | |
| * @param fn - The function name to call | |
| * @param args - The arguments to pass to the function call | |
| * @param options - Named parameters | |
| * @param options.head - When set to `true`, `data` will not be returned. | |
| * Useful if you only need the count. | |
| * @param options.get - When set to `true`, the function will be called with | |
| * read-only access mode. | |
| * @param options.count - Count algorithm to use to count rows returned by the | |
| * function. Only applicable for [set-returning | |
| * functions](https://www.postgresql.org/docs/current/functions-srf.html). | |
| * | |
| * `"exact"`: Exact but slow count algorithm. Performs a `COUNT(*)` under the | |
| * hood. | |
| * | |
| * `"planned"`: Approximated but fast count algorithm. Uses the Postgres | |
| * statistics under the hood. | |
| * | |
| * `"estimated"`: Uses exact count for low numbers and planned count for high | |
| * numbers. | |
| * | |
| * @example | |
| * ```ts | |
| * // For cross-schema functions where type inference fails, use overrideTypes: | |
| * const { data } = await supabase | |
| * .schema('schema_b') | |
| * .rpc('function_a', {}) | |
| * .overrideTypes<{ id: string; user_id: string }[]>() | |
| * ``` | |
| * | |
| * @category Database | |
| * | |
| * @example Call a Postgres function without arguments | |
| * ```ts | |
| * const { data, error } = await supabase.rpc('hello_world') | |
| * ``` | |
| * | |
| * @exampleSql Call a Postgres function without arguments | |
| * ```sql | |
| * create function hello_world() returns text as $$ | |
| * select 'Hello world'; | |
| * $$ language sql; | |
| * ``` | |
| * | |
| * @exampleResponse Call a Postgres function without arguments | |
| * ```json | |
| * { | |
| * "data": "Hello world", | |
| * "status": 200, | |
| * "statusText": "OK" | |
| * } | |
| * ``` | |
| * | |
| * @example Call a Postgres function with arguments | |
| * ```ts | |
| * const { data, error } = await supabase.rpc('echo', { say: '👋' }) | |
| * ``` | |
| * | |
| * @exampleSql Call a Postgres function with arguments | |
| * ```sql | |
| * create function echo(say text) returns text as $$ | |
| * select say; | |
| * $$ language sql; | |
| * ``` | |
| * | |
| * @exampleResponse Call a Postgres function with arguments | |
| * ```json | |
| * { | |
| * "data": "👋", | |
| * "status": 200, | |
| * "statusText": "OK" | |
| * } | |
| * | |
| * ``` | |
| * | |
| * @exampleDescription Bulk processing | |
| * You can process large payloads by passing in an array as an argument. | |
| * | |
| * @example Bulk processing | |
| * ```ts | |
| * const { data, error } = await supabase.rpc('add_one_each', { arr: [1, 2, 3] }) | |
| * ``` | |
| * | |
| * @exampleSql Bulk processing | |
| * ```sql | |
| * create function add_one_each(arr int[]) returns int[] as $$ | |
| * select array_agg(n + 1) from unnest(arr) as n; | |
| * $$ language sql; | |
| * ``` | |
| * | |
| * @exampleResponse Bulk processing | |
| * ```json | |
| * { | |
| * "data": [ | |
| * 2, | |
| * 3, | |
| * 4 | |
| * ], | |
| * "status": 200, | |
| * "statusText": "OK" | |
| * } | |
| * ``` | |
| * | |
| * @exampleDescription Call a Postgres function with filters | |
| * Postgres functions that return tables can also be combined with [Filters](/docs/reference/javascript/using-filters) and [Modifiers](/docs/reference/javascript/using-modifiers). | |
| * | |
| * @example Call a Postgres function with filters | |
| * ```ts | |
| * const { data, error } = await supabase | |
| * .rpc('list_stored_countries') | |
| * .eq('id', 1) | |
| * .single() | |
| * ``` | |
| * | |
| * @exampleSql Call a Postgres function with filters | |
| * ```sql | |
| * create table | |
| * countries (id int8 primary key, name text); | |
| * | |
| * insert into | |
| * countries (id, name) | |
| * values | |
| * (1, 'Rohan'), | |
| * (2, 'The Shire'); | |
| * | |
| * create function list_stored_countries() returns setof countries as $$ | |
| * select * from countries; | |
| * $$ language sql; | |
| * ``` | |
| * | |
| * @exampleResponse Call a Postgres function with filters | |
| * ```json | |
| * { | |
| * "data": { | |
| * "id": 1, | |
| * "name": "Rohan" | |
| * }, | |
| * "status": 200, | |
| * "statusText": "OK" | |
| * } | |
| * ``` | |
| * | |
| * @example Call a read-only Postgres function | |
| * ```ts | |
| * const { data, error } = await supabase.rpc('hello_world', undefined, { get: true }) | |
| * ``` | |
| * | |
| * @exampleSql Call a read-only Postgres function | |
| * ```sql | |
| * create function hello_world() returns text as $$ | |
| * select 'Hello world'; | |
| * $$ language sql; | |
| * ``` | |
| * | |
| * @exampleResponse Call a read-only Postgres function | |
| * ```json | |
| * { | |
| * "data": "Hello world", | |
| * "status": 200, | |
| * "statusText": "OK" | |
| * } | |
| * ``` | |
| */ | |
| rpc< | |
| FnName extends string & keyof Schema['Functions'], | |
| Args extends Schema['Functions'][FnName]['Args'] = never, | |
| FilterBuilder extends GetRpcFunctionFilterBuilderByArgs<Schema, FnName, Args> = | |
| GetRpcFunctionFilterBuilderByArgs<Schema, FnName, Args>, | |
| >( | |
| fn: FnName, | |
| args: Args = {} as Args, | |
| { | |
| head = false, | |
| get = false, | |
| count, | |
| }: { | |
| head?: boolean | |
| get?: boolean | |
| count?: 'exact' | 'planned' | 'estimated' | (string & {}) | |
| } = {} | |
| ): PostgrestFilterBuilder< | |
| ClientOptions, | |
| Schema, | |
| FilterBuilder['Row'], | |
| FilterBuilder['Result'], | |
| FilterBuilder['RelationName'], | |
| FilterBuilder['Relationships'], | |
| 'RPC' | |
| > { | |
| let method: 'HEAD' | 'GET' | 'POST' | |
| const url = new URL(`${this.url}/rpc/${fn}`) | |
| let body: unknown | undefined | |
| // objects/arrays-of-objects can't be serialized to URL params, use POST + return=minimal instead | |
| const _isObject = (v: unknown): boolean => | |
| v !== null && typeof v === 'object' && (!Array.isArray(v) || v.some(_isObject)) | |
| const _hasObjectArg = head && Object.values(args as object).some(_isObject) | |
| if (_hasObjectArg) { | |
| method = 'POST' | |
| body = args | |
| } else if (head || get) { | |
| method = head ? 'HEAD' : 'GET' | |
| Object.entries(args) | |
| // params with undefined value needs to be filtered out, otherwise it'll | |
| // show up as `?param=undefined` | |
| .filter(([_, value]) => value !== undefined) | |
| // array values need special syntax | |
| .map(([name, value]) => [name, Array.isArray(value) ? `{${value.join(',')}}` : `${value}`]) | |
| .forEach(([name, value]) => { | |
| url.searchParams.append(name, value) | |
| }) | |
| } else { | |
| method = 'POST' | |
| body = args | |
| } | |
| const headers = new Headers(this.headers) | |
| if (_hasObjectArg) { | |
| headers.set('Prefer', count ? `count=${count},return=minimal` : 'return=minimal') | |
| } else if (count) { | |
| headers.set('Prefer', `count=${count}`) | |
| } | |
| return new PostgrestFilterBuilder({ | |
| method, | |
| url, | |
| headers, | |
| schema: this.schemaName, | |
| body, | |
| fetch: this.fetch ?? fetch, | |
| urlLengthLimit: this.urlLengthLimit, | |
| retry: this.retry, | |
| }) | |
| } | |
| } | |