@supabase/postgrest-js
Version:
Isomorphic PostgREST client
1,237 lines (1,229 loc) • 42 kB
JavaScript
Object.defineProperty(exports, '__esModule', { value: true });
//#region src/PostgrestError.ts
/**
* Error format
*
* {@link https://postgrest.org/en/stable/api.html?highlight=options#errors-and-http-status-codes}
*/
var PostgrestError = class extends Error {
/**
* @example
* ```ts
* import PostgrestError from '@supabase/postgrest-js'
*
* throw new PostgrestError({
* message: 'Row level security prevented the request',
* details: 'RLS denied the insert',
* hint: 'Check your policies',
* code: 'PGRST301',
* })
* ```
*/
constructor(context) {
super(context.message);
this.name = "PostgrestError";
this.details = context.details;
this.hint = context.hint;
this.code = context.code;
}
};
//#endregion
//#region src/PostgrestBuilder.ts
var PostgrestBuilder = class {
/**
* Creates a builder configured for a specific PostgREST request.
*
* @example
* ```ts
* import PostgrestQueryBuilder from '@supabase/postgrest-js'
*
* const builder = new PostgrestQueryBuilder(
* new URL('https://xyzcompany.supabase.co/rest/v1/users'),
* { headers: new Headers({ apikey: 'public-anon-key' }) }
* )
* ```
*/
constructor(builder) {
var _builder$shouldThrowO, _builder$isMaybeSingl;
this.shouldThrowOnError = false;
this.method = builder.method;
this.url = builder.url;
this.headers = new Headers(builder.headers);
this.schema = builder.schema;
this.body = builder.body;
this.shouldThrowOnError = (_builder$shouldThrowO = builder.shouldThrowOnError) !== null && _builder$shouldThrowO !== void 0 ? _builder$shouldThrowO : false;
this.signal = builder.signal;
this.isMaybeSingle = (_builder$isMaybeSingl = builder.isMaybeSingle) !== null && _builder$isMaybeSingl !== void 0 ? _builder$isMaybeSingl : false;
if (builder.fetch) this.fetch = builder.fetch;
else this.fetch = fetch;
}
/**
* If there's an error with the query, throwOnError will reject the promise by
* throwing the error instead of returning it as part of a successful response.
*
* {@link https://github.com/supabase/supabase-js/issues/92}
*/
throwOnError() {
this.shouldThrowOnError = true;
return this;
}
/**
* Set an HTTP header for the request.
*/
setHeader(name, value) {
this.headers = new Headers(this.headers);
this.headers.set(name, value);
return this;
}
then(onfulfilled, onrejected) {
var _this = this;
if (this.schema === void 0) {} else if (["GET", "HEAD"].includes(this.method)) this.headers.set("Accept-Profile", this.schema);
else this.headers.set("Content-Profile", this.schema);
if (this.method !== "GET" && this.method !== "HEAD") this.headers.set("Content-Type", "application/json");
const _fetch = this.fetch;
let res = _fetch(this.url.toString(), {
method: this.method,
headers: this.headers,
body: JSON.stringify(this.body),
signal: this.signal
}).then(async (res$1) => {
let error = null;
let data = null;
let count = null;
let status = res$1.status;
let statusText = res$1.statusText;
if (res$1.ok) {
var _this$headers$get2, _res$headers$get;
if (_this.method !== "HEAD") {
var _this$headers$get;
const body = await res$1.text();
if (body === "") {} else if (_this.headers.get("Accept") === "text/csv") data = body;
else if (_this.headers.get("Accept") && ((_this$headers$get = _this.headers.get("Accept")) === null || _this$headers$get === void 0 ? void 0 : _this$headers$get.includes("application/vnd.pgrst.plan+text"))) data = body;
else data = JSON.parse(body);
}
const countHeader = (_this$headers$get2 = _this.headers.get("Prefer")) === null || _this$headers$get2 === void 0 ? void 0 : _this$headers$get2.match(/count=(exact|planned|estimated)/);
const contentRange = (_res$headers$get = res$1.headers.get("content-range")) === null || _res$headers$get === void 0 ? void 0 : _res$headers$get.split("/");
if (countHeader && contentRange && contentRange.length > 1) count = parseInt(contentRange[1]);
if (_this.isMaybeSingle && _this.method === "GET" && Array.isArray(data)) if (data.length > 1) {
error = {
code: "PGRST116",
details: `Results contain ${data.length} rows, application/vnd.pgrst.object+json requires 1 row`,
hint: null,
message: "JSON object requested, multiple (or no) rows returned"
};
data = null;
count = null;
status = 406;
statusText = "Not Acceptable";
} else if (data.length === 1) data = data[0];
else data = null;
} else {
var _error$details;
const body = await res$1.text();
try {
error = JSON.parse(body);
if (Array.isArray(error) && res$1.status === 404) {
data = [];
error = null;
status = 200;
statusText = "OK";
}
} catch (_unused) {
if (res$1.status === 404 && body === "") {
status = 204;
statusText = "No Content";
} else error = { message: body };
}
if (error && _this.isMaybeSingle && (error === null || error === void 0 || (_error$details = error.details) === null || _error$details === void 0 ? void 0 : _error$details.includes("0 rows"))) {
error = null;
status = 200;
statusText = "OK";
}
if (error && _this.shouldThrowOnError) throw new PostgrestError(error);
}
return {
error,
data,
count,
status,
statusText
};
});
if (!this.shouldThrowOnError) res = res.catch((fetchError) => {
var _fetchError$name2;
let errorDetails = "";
const cause = fetchError === null || fetchError === void 0 ? void 0 : fetchError.cause;
if (cause) {
var _cause$message, _cause$code, _fetchError$name, _cause$name;
const causeMessage = (_cause$message = cause === null || cause === void 0 ? void 0 : cause.message) !== null && _cause$message !== void 0 ? _cause$message : "";
const causeCode = (_cause$code = cause === null || cause === void 0 ? void 0 : cause.code) !== null && _cause$code !== void 0 ? _cause$code : "";
errorDetails = `${(_fetchError$name = fetchError === null || fetchError === void 0 ? void 0 : fetchError.name) !== null && _fetchError$name !== void 0 ? _fetchError$name : "FetchError"}: ${fetchError === null || fetchError === void 0 ? void 0 : fetchError.message}`;
errorDetails += `\n\nCaused by: ${(_cause$name = cause === null || cause === void 0 ? void 0 : cause.name) !== null && _cause$name !== void 0 ? _cause$name : "Error"}: ${causeMessage}`;
if (causeCode) errorDetails += ` (${causeCode})`;
if (cause === null || cause === void 0 ? void 0 : cause.stack) errorDetails += `\n${cause.stack}`;
} else {
var _fetchError$stack;
errorDetails = (_fetchError$stack = fetchError === null || fetchError === void 0 ? void 0 : fetchError.stack) !== null && _fetchError$stack !== void 0 ? _fetchError$stack : "";
}
return {
error: {
message: `${(_fetchError$name2 = fetchError === null || fetchError === void 0 ? void 0 : fetchError.name) !== null && _fetchError$name2 !== void 0 ? _fetchError$name2 : "FetchError"}: ${fetchError === null || fetchError === void 0 ? void 0 : fetchError.message}`,
details: errorDetails,
hint: "",
code: ""
},
data: null,
count: null,
status: 0,
statusText: ""
};
});
return res.then(onfulfilled, onrejected);
}
/**
* 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() {
/* istanbul ignore next */
return this;
}
/**
* Override the type of the returned `data` field in the response.
*
* @typeParam NewResult - The new type to cast the response data to
* @typeParam Options - Optional type configuration (defaults to { merge: true })
* @typeParam Options.merge - When true, merges the new type with existing return type. When false, replaces the existing types entirely (defaults to true)
* @example
* ```typescript
* // Merge with existing types (default behavior)
* const query = supabase
* .from('users')
* .select()
* .overrideTypes<{ custom_field: string }>()
*
* // Replace existing types completely
* const replaceQuery = supabase
* .from('users')
* .select()
* .overrideTypes<{ id: number; name: string }, { merge: false }>()
* ```
* @returns A PostgrestBuilder instance with the new type
*/
overrideTypes() {
return this;
}
};
//#endregion
//#region src/PostgrestTransformBuilder.ts
var PostgrestTransformBuilder = class extends PostgrestBuilder {
/**
* 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(columns) {
let quoted = false;
const cleanedColumns = (columns !== null && columns !== void 0 ? 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;
}
/**
* 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, { ascending = true, nullsFirst, foreignTable, referencedTable = foreignTable } = {}) {
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 === void 0 ? "" : 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, { foreignTable, referencedTable = foreignTable } = {}) {
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, to, { foreignTable, referencedTable = foreignTable } = {}) {
const keyOffset = typeof referencedTable === "undefined" ? "offset" : `${referencedTable}.offset`;
const keyLimit = typeof referencedTable === "undefined" ? "limit" : `${referencedTable}.limit`;
this.url.searchParams.set(keyOffset, `${from}`);
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) {
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() {
this.headers.set("Accept", "application/vnd.pgrst.object+json");
return this;
}
/**
* 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() {
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;
}
/**
* Return `data` as a string in CSV format.
*/
csv() {
this.headers.set("Accept", "text/csv");
return this;
}
/**
* Return `data` as an object in [GeoJSON](https://geojson.org) format.
*/
geojson() {
this.headers.set("Accept", "application/geo+json");
return this;
}
/**
* 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" } = {}) {
var _this$headers$get;
const options = [
analyze ? "analyze" : null,
verbose ? "verbose" : null,
settings ? "settings" : null,
buffers ? "buffers" : null,
wal ? "wal" : null
].filter(Boolean).join("|");
const forMediatype = (_this$headers$get = this.headers.get("Accept")) !== null && _this$headers$get !== void 0 ? _this$headers$get : "application/json";
this.headers.set("Accept", `application/vnd.pgrst.plan+${format}; for="${forMediatype}"; options=${options};`);
if (format === "json") return this;
else return this;
}
/**
* Rollback the query.
*
* `data` will still be returned, but the query is not committed.
*/
rollback() {
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() {
return this;
}
/**
* 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) {
this.headers.append("Prefer", "handling=strict");
this.headers.append("Prefer", `max-affected=${value}`);
return this;
}
};
//#endregion
//#region src/PostgrestFilterBuilder.ts
const PostgrestReservedCharsRegexp = /* @__PURE__ */ new RegExp("[,()]");
var PostgrestFilterBuilder = class extends PostgrestTransformBuilder {
/**
* Match only rows where `column` is equal to `value`.
*
* To check if the value of `column` is NULL, you should use `.is()` instead.
*
* @param column - The column to filter on
* @param value - The value to filter with
*/
eq(column, value) {
this.url.searchParams.append(column, `eq.${value}`);
return this;
}
/**
* Match only rows where `column` is not equal to `value`.
*
* @param column - The column to filter on
* @param value - The value to filter with
*/
neq(column, value) {
this.url.searchParams.append(column, `neq.${value}`);
return this;
}
/**
* Match only rows where `column` is greater than `value`.
*
* @param column - The column to filter on
* @param value - The value to filter with
*/
gt(column, value) {
this.url.searchParams.append(column, `gt.${value}`);
return this;
}
/**
* Match only rows where `column` is greater than or equal to `value`.
*
* @param column - The column to filter on
* @param value - The value to filter with
*/
gte(column, value) {
this.url.searchParams.append(column, `gte.${value}`);
return this;
}
/**
* Match only rows where `column` is less than `value`.
*
* @param column - The column to filter on
* @param value - The value to filter with
*/
lt(column, value) {
this.url.searchParams.append(column, `lt.${value}`);
return this;
}
/**
* Match only rows where `column` is less than or equal to `value`.
*
* @param column - The column to filter on
* @param value - The value to filter with
*/
lte(column, value) {
this.url.searchParams.append(column, `lte.${value}`);
return this;
}
/**
* Match only rows where `column` matches `pattern` case-sensitively.
*
* @param column - The column to filter on
* @param pattern - The pattern to match with
*/
like(column, pattern) {
this.url.searchParams.append(column, `like.${pattern}`);
return this;
}
/**
* Match only rows where `column` matches all of `patterns` case-sensitively.
*
* @param column - The column to filter on
* @param patterns - The patterns to match with
*/
likeAllOf(column, patterns) {
this.url.searchParams.append(column, `like(all).{${patterns.join(",")}}`);
return this;
}
/**
* Match only rows where `column` matches any of `patterns` case-sensitively.
*
* @param column - The column to filter on
* @param patterns - The patterns to match with
*/
likeAnyOf(column, patterns) {
this.url.searchParams.append(column, `like(any).{${patterns.join(",")}}`);
return this;
}
/**
* Match only rows where `column` matches `pattern` case-insensitively.
*
* @param column - The column to filter on
* @param pattern - The pattern to match with
*/
ilike(column, pattern) {
this.url.searchParams.append(column, `ilike.${pattern}`);
return this;
}
/**
* Match only rows where `column` matches all of `patterns` case-insensitively.
*
* @param column - The column to filter on
* @param patterns - The patterns to match with
*/
ilikeAllOf(column, patterns) {
this.url.searchParams.append(column, `ilike(all).{${patterns.join(",")}}`);
return this;
}
/**
* Match only rows where `column` matches any of `patterns` case-insensitively.
*
* @param column - The column to filter on
* @param patterns - The patterns to match with
*/
ilikeAnyOf(column, patterns) {
this.url.searchParams.append(column, `ilike(any).{${patterns.join(",")}}`);
return this;
}
/**
* Match only rows where `column` matches the PostgreSQL regex `pattern`
* case-sensitively (using the `~` operator).
*
* @param column - The column to filter on
* @param pattern - The PostgreSQL regular expression pattern to match with
*/
regexMatch(column, pattern) {
this.url.searchParams.append(column, `match.${pattern}`);
return this;
}
/**
* Match only rows where `column` matches the PostgreSQL regex `pattern`
* case-insensitively (using the `~*` operator).
*
* @param column - The column to filter on
* @param pattern - The PostgreSQL regular expression pattern to match with
*/
regexIMatch(column, pattern) {
this.url.searchParams.append(column, `imatch.${pattern}`);
return this;
}
/**
* Match only rows where `column` IS `value`.
*
* For non-boolean columns, this is only relevant for checking if the value of
* `column` is NULL by setting `value` to `null`.
*
* For boolean columns, you can also set `value` to `true` or `false` and it
* will behave the same way as `.eq()`.
*
* @param column - The column to filter on
* @param value - The value to filter with
*/
is(column, value) {
this.url.searchParams.append(column, `is.${value}`);
return this;
}
/**
* Match only rows where `column` IS DISTINCT FROM `value`.
*
* Unlike `.neq()`, this treats `NULL` as a comparable value. Two `NULL` values
* are considered equal (not distinct), and comparing `NULL` with any non-NULL
* value returns true (distinct).
*
* @param column - The column to filter on
* @param value - The value to filter with
*/
isDistinct(column, value) {
this.url.searchParams.append(column, `isdistinct.${value}`);
return this;
}
/**
* Match only rows where `column` is included in the `values` array.
*
* @param column - The column to filter on
* @param values - The values array to filter with
*/
in(column, values) {
const cleanedValues = Array.from(new Set(values)).map((s) => {
if (typeof s === "string" && PostgrestReservedCharsRegexp.test(s)) return `"${s}"`;
else return `${s}`;
}).join(",");
this.url.searchParams.append(column, `in.(${cleanedValues})`);
return this;
}
/**
* Match only rows where `column` is NOT included in the `values` array.
*
* @param column - The column to filter on
* @param values - The values array to filter with
*/
notIn(column, values) {
const cleanedValues = Array.from(new Set(values)).map((s) => {
if (typeof s === "string" && PostgrestReservedCharsRegexp.test(s)) return `"${s}"`;
else return `${s}`;
}).join(",");
this.url.searchParams.append(column, `not.in.(${cleanedValues})`);
return this;
}
/**
* Only relevant for jsonb, array, and range columns. Match only rows where
* `column` contains every element appearing in `value`.
*
* @param column - The jsonb, array, or range column to filter on
* @param value - The jsonb, array, or range value to filter with
*/
contains(column, value) {
if (typeof value === "string") this.url.searchParams.append(column, `cs.${value}`);
else if (Array.isArray(value)) this.url.searchParams.append(column, `cs.{${value.join(",")}}`);
else this.url.searchParams.append(column, `cs.${JSON.stringify(value)}`);
return this;
}
/**
* Only relevant for jsonb, array, and range columns. Match only rows where
* every element appearing in `column` is contained by `value`.
*
* @param column - The jsonb, array, or range column to filter on
* @param value - The jsonb, array, or range value to filter with
*/
containedBy(column, value) {
if (typeof value === "string") this.url.searchParams.append(column, `cd.${value}`);
else if (Array.isArray(value)) this.url.searchParams.append(column, `cd.{${value.join(",")}}`);
else this.url.searchParams.append(column, `cd.${JSON.stringify(value)}`);
return this;
}
/**
* Only relevant for range columns. Match only rows where every element in
* `column` is greater than any element in `range`.
*
* @param column - The range column to filter on
* @param range - The range to filter with
*/
rangeGt(column, range) {
this.url.searchParams.append(column, `sr.${range}`);
return this;
}
/**
* Only relevant for range columns. Match only rows where every element in
* `column` is either contained in `range` or greater than any element in
* `range`.
*
* @param column - The range column to filter on
* @param range - The range to filter with
*/
rangeGte(column, range) {
this.url.searchParams.append(column, `nxl.${range}`);
return this;
}
/**
* Only relevant for range columns. Match only rows where every element in
* `column` is less than any element in `range`.
*
* @param column - The range column to filter on
* @param range - The range to filter with
*/
rangeLt(column, range) {
this.url.searchParams.append(column, `sl.${range}`);
return this;
}
/**
* Only relevant for range columns. Match only rows where every element in
* `column` is either contained in `range` or less than any element in
* `range`.
*
* @param column - The range column to filter on
* @param range - The range to filter with
*/
rangeLte(column, range) {
this.url.searchParams.append(column, `nxr.${range}`);
return this;
}
/**
* Only relevant for range columns. Match only rows where `column` is
* mutually exclusive to `range` and there can be no element between the two
* ranges.
*
* @param column - The range column to filter on
* @param range - The range to filter with
*/
rangeAdjacent(column, range) {
this.url.searchParams.append(column, `adj.${range}`);
return this;
}
/**
* Only relevant for array and range columns. Match only rows where
* `column` and `value` have an element in common.
*
* @param column - The array or range column to filter on
* @param value - The array or range value to filter with
*/
overlaps(column, value) {
if (typeof value === "string") this.url.searchParams.append(column, `ov.${value}`);
else this.url.searchParams.append(column, `ov.{${value.join(",")}}`);
return this;
}
/**
* Only relevant for text and tsvector columns. Match only rows where
* `column` matches the query string in `query`.
*
* @param column - The text or tsvector column to filter on
* @param query - The query text to match with
* @param options - Named parameters
* @param options.config - The text search configuration to use
* @param options.type - Change how the `query` text is interpreted
*/
textSearch(column, query, { config, type } = {}) {
let typePart = "";
if (type === "plain") typePart = "pl";
else if (type === "phrase") typePart = "ph";
else if (type === "websearch") typePart = "w";
const configPart = config === void 0 ? "" : `(${config})`;
this.url.searchParams.append(column, `${typePart}fts${configPart}.${query}`);
return this;
}
/**
* Match only rows where each column in `query` keys is equal to its
* associated value. Shorthand for multiple `.eq()`s.
*
* @param query - The object to filter with, with column names as keys mapped
* to their filter values
*/
match(query) {
Object.entries(query).forEach(([column, value]) => {
this.url.searchParams.append(column, `eq.${value}`);
});
return this;
}
/**
* Match only rows which doesn't satisfy the filter.
*
* Unlike most filters, `opearator` and `value` are used as-is and need to
* follow [PostgREST
* syntax](https://postgrest.org/en/stable/api.html#operators). You also need
* to make sure they are properly sanitized.
*
* @param column - The column to filter on
* @param operator - The operator to be negated to filter with, following
* PostgREST syntax
* @param value - The value to filter with, following PostgREST syntax
*/
not(column, operator, value) {
this.url.searchParams.append(column, `not.${operator}.${value}`);
return this;
}
/**
* Match only rows which satisfy at least one of the filters.
*
* Unlike most filters, `filters` is used as-is and needs to follow [PostgREST
* syntax](https://postgrest.org/en/stable/api.html#operators). You also need
* to make sure it's properly sanitized.
*
* It's currently not possible to do an `.or()` filter across multiple tables.
*
* @param filters - The filters to use, following PostgREST syntax
* @param options - Named parameters
* @param options.referencedTable - Set this to filter on referenced tables
* instead of the parent table
* @param options.foreignTable - Deprecated, use `referencedTable` instead
*/
or(filters, { foreignTable, referencedTable = foreignTable } = {}) {
const key = referencedTable ? `${referencedTable}.or` : "or";
this.url.searchParams.append(key, `(${filters})`);
return this;
}
/**
* Match only rows which satisfy the filter. This is an escape hatch - you
* should use the specific filter methods wherever possible.
*
* Unlike most filters, `opearator` and `value` are used as-is and need to
* follow [PostgREST
* syntax](https://postgrest.org/en/stable/api.html#operators). You also need
* to make sure they are properly sanitized.
*
* @param column - The column to filter on
* @param operator - The operator to filter with, following PostgREST syntax
* @param value - The value to filter with, following PostgREST syntax
*/
filter(column, operator, value) {
this.url.searchParams.append(column, `${operator}.${value}`);
return this;
}
};
//#endregion
//#region src/PostgrestQueryBuilder.ts
var PostgrestQueryBuilder = class {
/**
* Creates a query builder scoped to a Postgres table or view.
*
* @example
* ```ts
* import PostgrestQueryBuilder from '@supabase/postgrest-js'
*
* const query = new PostgrestQueryBuilder(
* new URL('https://xyzcompany.supabase.co/rest/v1/users'),
* { headers: { apikey: 'public-anon-key' } }
* )
* ```
*/
constructor(url, { headers = {}, schema, fetch: fetch$1 }) {
this.url = url;
this.headers = new Headers(headers);
this.schema = schema;
this.fetch = fetch$1;
}
/**
* Perform a SELECT query on the table or view.
*
* @param columns - The columns to retrieve, separated by commas. Columns can be renamed when returned with `customName:columnName`
*
* @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.count - Count algorithm to use to count rows in the table or view.
*
* `"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.
*/
select(columns, options) {
const { head = false, count } = options !== null && options !== void 0 ? options : {};
const method = head ? "HEAD" : "GET";
let quoted = false;
const cleanedColumns = (columns !== null && columns !== void 0 ? columns : "*").split("").map((c) => {
if (/\s/.test(c) && !quoted) return "";
if (c === "\"") quoted = !quoted;
return c;
}).join("");
this.url.searchParams.set("select", cleanedColumns);
if (count) this.headers.append("Prefer", `count=${count}`);
return new PostgrestFilterBuilder({
method,
url: this.url,
headers: this.headers,
schema: this.schema,
fetch: this.fetch
});
}
/**
* Perform an INSERT into the table or view.
*
* By default, inserted rows are not returned. To return it, chain the call
* with `.select()`.
*
* @param values - The values to insert. Pass an object to insert a single row
* or an array to insert multiple rows.
*
* @param options - Named parameters
*
* @param options.count - Count algorithm to use to count inserted rows.
*
* `"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.
*
* @param options.defaultToNull - Make missing fields default to `null`.
* Otherwise, use the default value for the column. Only applies for bulk
* inserts.
*/
insert(values, { count, defaultToNull = true } = {}) {
var _this$fetch;
const method = "POST";
if (count) this.headers.append("Prefer", `count=${count}`);
if (!defaultToNull) this.headers.append("Prefer", `missing=default`);
if (Array.isArray(values)) {
const columns = values.reduce((acc, x) => acc.concat(Object.keys(x)), []);
if (columns.length > 0) {
const uniqueColumns = [...new Set(columns)].map((column) => `"${column}"`);
this.url.searchParams.set("columns", uniqueColumns.join(","));
}
}
return new PostgrestFilterBuilder({
method,
url: this.url,
headers: this.headers,
schema: this.schema,
body: values,
fetch: (_this$fetch = this.fetch) !== null && _this$fetch !== void 0 ? _this$fetch : fetch
});
}
/**
* Perform an UPSERT on the table or view. Depending on the column(s) passed
* to `onConflict`, `.upsert()` allows you to perform the equivalent of
* `.insert()` if a row with the corresponding `onConflict` columns doesn't
* exist, or if it does exist, perform an alternative action depending on
* `ignoreDuplicates`.
*
* By default, upserted rows are not returned. To return it, chain the call
* with `.select()`.
*
* @param values - The values to upsert with. Pass an object to upsert a
* single row or an array to upsert multiple rows.
*
* @param options - Named parameters
*
* @param options.onConflict - Comma-separated UNIQUE column(s) to specify how
* duplicate rows are determined. Two rows are duplicates if all the
* `onConflict` columns are equal.
*
* @param options.ignoreDuplicates - If `true`, duplicate rows are ignored. If
* `false`, duplicate rows are merged with existing rows.
*
* @param options.count - Count algorithm to use to count upserted rows.
*
* `"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.
*
* @param options.defaultToNull - Make missing fields default to `null`.
* Otherwise, use the default value for the column. This only applies when
* inserting new rows, not when merging with existing rows under
* `ignoreDuplicates: false`. This also only applies when doing bulk upserts.
*
* @example Upsert a single row using a unique key
* ```ts
* // Upserting a single row, overwriting based on the 'username' unique column
* const { data, error } = await supabase
* .from('users')
* .upsert({ username: 'supabot' }, { onConflict: 'username' })
*
* // Example response:
* // {
* // data: [
* // { id: 4, message: 'bar', username: 'supabot' }
* // ],
* // error: null
* // }
* ```
*
* @example Upsert with conflict resolution and exact row counting
* ```ts
* // Upserting and returning exact count
* const { data, error, count } = await supabase
* .from('users')
* .upsert(
* {
* id: 3,
* message: 'foo',
* username: 'supabot'
* },
* {
* onConflict: 'username',
* count: 'exact'
* }
* )
*
* // Example response:
* // {
* // data: [
* // {
* // id: 42,
* // handle: "saoirse",
* // display_name: "Saoirse"
* // }
* // ],
* // count: 1,
* // error: null
* // }
* ```
*/
upsert(values, { onConflict, ignoreDuplicates = false, count, defaultToNull = true } = {}) {
var _this$fetch2;
const method = "POST";
this.headers.append("Prefer", `resolution=${ignoreDuplicates ? "ignore" : "merge"}-duplicates`);
if (onConflict !== void 0) this.url.searchParams.set("on_conflict", onConflict);
if (count) this.headers.append("Prefer", `count=${count}`);
if (!defaultToNull) this.headers.append("Prefer", "missing=default");
if (Array.isArray(values)) {
const columns = values.reduce((acc, x) => acc.concat(Object.keys(x)), []);
if (columns.length > 0) {
const uniqueColumns = [...new Set(columns)].map((column) => `"${column}"`);
this.url.searchParams.set("columns", uniqueColumns.join(","));
}
}
return new PostgrestFilterBuilder({
method,
url: this.url,
headers: this.headers,
schema: this.schema,
body: values,
fetch: (_this$fetch2 = this.fetch) !== null && _this$fetch2 !== void 0 ? _this$fetch2 : fetch
});
}
/**
* Perform an UPDATE on the table or view.
*
* By default, updated rows are not returned. To return it, chain the call
* with `.select()` after filters.
*
* @param values - The values to update with
*
* @param options - Named parameters
*
* @param options.count - Count algorithm to use to count updated rows.
*
* `"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.
*/
update(values, { count } = {}) {
var _this$fetch3;
const method = "PATCH";
if (count) this.headers.append("Prefer", `count=${count}`);
return new PostgrestFilterBuilder({
method,
url: this.url,
headers: this.headers,
schema: this.schema,
body: values,
fetch: (_this$fetch3 = this.fetch) !== null && _this$fetch3 !== void 0 ? _this$fetch3 : fetch
});
}
/**
* Perform a DELETE on the table or view.
*
* By default, deleted rows are not returned. To return it, chain the call
* with `.select()` after filters.
*
* @param options - Named parameters
*
* @param options.count - Count algorithm to use to count deleted rows.
*
* `"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.
*/
delete({ count } = {}) {
var _this$fetch4;
const method = "DELETE";
if (count) this.headers.append("Prefer", `count=${count}`);
return new PostgrestFilterBuilder({
method,
url: this.url,
headers: this.headers,
schema: this.schema,
fetch: (_this$fetch4 = this.fetch) !== null && _this$fetch4 !== void 0 ? _this$fetch4 : fetch
});
}
};
//#endregion
//#region src/PostgrestClient.ts
/**
* 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.
*/
var PostgrestClient = class PostgrestClient {
/**
* 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
* @example
* ```ts
* import PostgrestClient from '@supabase/postgrest-js'
*
* const postgrest = new PostgrestClient('https://xyzcompany.supabase.co/rest/v1', {
* headers: { apikey: 'public-anon-key' },
* schema: 'public',
* })
* ```
*/
constructor(url, { headers = {}, schema, fetch: fetch$1 } = {}) {
this.url = url;
this.headers = new Headers(headers);
this.schemaName = schema;
this.fetch = fetch$1;
}
/**
* Perform a query on a table or a view.
*
* @param relation - The table or view name to query
*/
from(relation) {
if (!relation || typeof relation !== "string" || relation.trim() === "") throw new Error("Invalid relation name: relation must be a non-empty string.");
return new PostgrestQueryBuilder(new URL(`${this.url}/${relation}`), {
headers: new Headers(this.headers),
schema: this.schemaName,
fetch: this.fetch
});
}
/**
* 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
*/
schema(schema) {
return new PostgrestClient(this.url, {
headers: this.headers,
schema,
fetch: this.fetch
});
}
/**
* 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 }[]>()
* ```
*/
rpc(fn, args = {}, { head = false, get = false, count } = {}) {
var _this$fetch;
let method;
const url = new URL(`${this.url}/rpc/${fn}`);
let body;
if (head || get) {
method = head ? "HEAD" : "GET";
Object.entries(args).filter(([_, value]) => value !== void 0).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 (count) headers.set("Prefer", `count=${count}`);
return new PostgrestFilterBuilder({
method,
url,
headers,
schema: this.schemaName,
body,
fetch: (_this$fetch = this.fetch) !== null && _this$fetch !== void 0 ? _this$fetch : fetch
});
}
};
//#endregion
//#region src/index.ts
var src_default = {
PostgrestClient,
PostgrestQueryBuilder,
PostgrestFilterBuilder,
PostgrestTransformBuilder,
PostgrestBuilder,
PostgrestError
};
//#endregion
exports.PostgrestBuilder = PostgrestBuilder;
exports.PostgrestClient = PostgrestClient;
exports.PostgrestError = PostgrestError;
exports.PostgrestFilterBuilder = PostgrestFilterBuilder;
exports.PostgrestQueryBuilder = PostgrestQueryBuilder;
exports.PostgrestTransformBuilder = PostgrestTransformBuilder;
exports.default = src_default;
//# sourceMappingURL=index.cjs.map