Some checks failed
CI/CD Pipeline / unit-tests (push) Failing after 1m16s
CI/CD Pipeline / integration-tests (push) Failing after 2m32s
CI/CD Pipeline / lint (push) Successful in 5m22s
CI/CD Pipeline / e2e-tests (push) Has been skipped
CI/CD Pipeline / build (push) Has been skipped
2447 lines
71 KiB
JavaScript
2447 lines
71 KiB
JavaScript
//#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, _builder$urlLengthLim;
|
|
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;
|
|
this.urlLengthLimit = (_builder$urlLengthLim = builder.urlLengthLimit) !== null && _builder$urlLengthLim !== void 0 ? _builder$urlLengthLim : 8e3;
|
|
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 = "";
|
|
let hint = "";
|
|
let code = "";
|
|
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 : "";
|
|
}
|
|
const urlLength = this.url.toString().length;
|
|
if ((fetchError === null || fetchError === void 0 ? void 0 : fetchError.name) === "AbortError" || (fetchError === null || fetchError === void 0 ? void 0 : fetchError.code) === "ABORT_ERR") {
|
|
code = "";
|
|
hint = "Request was aborted (timeout or manual cancellation)";
|
|
if (urlLength > this.urlLengthLimit) hint += `. Note: Your request URL is ${urlLength} characters, which may exceed server limits. If selecting many fields, consider using views. If filtering with large arrays (e.g., .in('id', [many IDs])), consider using an RPC function to pass values server-side.`;
|
|
} else if ((cause === null || cause === void 0 ? void 0 : cause.name) === "HeadersOverflowError" || (cause === null || cause === void 0 ? void 0 : cause.code) === "UND_ERR_HEADERS_OVERFLOW") {
|
|
code = "";
|
|
hint = "HTTP headers exceeded server limits (typically 16KB)";
|
|
if (urlLength > this.urlLengthLimit) hint += `. Your request URL is ${urlLength} characters. If selecting many fields, consider using views. If filtering with large arrays (e.g., .in('id', [200+ IDs])), consider using an RPC function instead.`;
|
|
}
|
|
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, urlLengthLimit = 8e3 }) {
|
|
this.url = url;
|
|
this.headers = new Headers(headers);
|
|
this.schema = schema;
|
|
this.fetch = fetch$1;
|
|
this.urlLengthLimit = urlLengthLimit;
|
|
}
|
|
/**
|
|
* Clone URL and headers to prevent shared state between operations.
|
|
*/
|
|
cloneRequestState() {
|
|
return {
|
|
url: new URL(this.url.toString()),
|
|
headers: new Headers(this.headers)
|
|
};
|
|
}
|
|
/**
|
|
* 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.
|
|
*
|
|
* @remarks
|
|
* When using `count` with `.range()` or `.limit()`, the returned `count` is the total number of rows
|
|
* that match your filters, not the number of rows in the current page. Use this to build pagination UI.
|
|
|
|
* - By default, Supabase projects return a maximum of 1,000 rows. This setting can be changed in your project's [API settings](/dashboard/project/_/settings/api). It's recommended that you keep it low to limit the payload size of accidental or malicious requests. You can use `range()` queries to paginate through your data.
|
|
* - `select()` can be combined with [Filters](/docs/reference/javascript/using-filters)
|
|
* - `select()` can be combined with [Modifiers](/docs/reference/javascript/using-modifiers)
|
|
* - `apikey` is a reserved keyword if you're using the [Supabase Platform](/docs/guides/platform) and [should be avoided as a column name](https://github.com/supabase/supabase/issues/5465). *
|
|
* @category Database
|
|
*
|
|
* @example Getting your data
|
|
* ```js
|
|
* const { data, error } = await supabase
|
|
* .from('characters')
|
|
* .select()
|
|
* ```
|
|
*
|
|
* @exampleSql Getting your data
|
|
* ```sql
|
|
* create table
|
|
* characters (id int8 primary key, name text);
|
|
*
|
|
* insert into
|
|
* characters (id, name)
|
|
* values
|
|
* (1, 'Harry'),
|
|
* (2, 'Frodo'),
|
|
* (3, 'Katniss');
|
|
* ```
|
|
*
|
|
* @exampleResponse Getting your data
|
|
* ```json
|
|
* {
|
|
* "data": [
|
|
* {
|
|
* "id": 1,
|
|
* "name": "Harry"
|
|
* },
|
|
* {
|
|
* "id": 2,
|
|
* "name": "Frodo"
|
|
* },
|
|
* {
|
|
* "id": 3,
|
|
* "name": "Katniss"
|
|
* }
|
|
* ],
|
|
* "status": 200,
|
|
* "statusText": "OK"
|
|
* }
|
|
* ```
|
|
*
|
|
* @example Selecting specific columns
|
|
* ```js
|
|
* const { data, error } = await supabase
|
|
* .from('characters')
|
|
* .select('name')
|
|
* ```
|
|
*
|
|
* @exampleSql Selecting specific columns
|
|
* ```sql
|
|
* create table
|
|
* characters (id int8 primary key, name text);
|
|
*
|
|
* insert into
|
|
* characters (id, name)
|
|
* values
|
|
* (1, 'Frodo'),
|
|
* (2, 'Harry'),
|
|
* (3, 'Katniss');
|
|
* ```
|
|
*
|
|
* @exampleResponse Selecting specific columns
|
|
* ```json
|
|
* {
|
|
* "data": [
|
|
* {
|
|
* "name": "Frodo"
|
|
* },
|
|
* {
|
|
* "name": "Harry"
|
|
* },
|
|
* {
|
|
* "name": "Katniss"
|
|
* }
|
|
* ],
|
|
* "status": 200,
|
|
* "statusText": "OK"
|
|
* }
|
|
* ```
|
|
*
|
|
* @exampleDescription Query referenced tables
|
|
* If your database has foreign key relationships, you can query related tables too.
|
|
*
|
|
* @example Query referenced tables
|
|
* ```js
|
|
* const { data, error } = await supabase
|
|
* .from('orchestral_sections')
|
|
* .select(`
|
|
* name,
|
|
* instruments (
|
|
* name
|
|
* )
|
|
* `)
|
|
* ```
|
|
*
|
|
* @exampleSql Query referenced tables
|
|
* ```sql
|
|
* create table
|
|
* orchestral_sections (id int8 primary key, name text);
|
|
* create table
|
|
* instruments (
|
|
* id int8 primary key,
|
|
* section_id int8 not null references orchestral_sections,
|
|
* name text
|
|
* );
|
|
*
|
|
* insert into
|
|
* orchestral_sections (id, name)
|
|
* values
|
|
* (1, 'strings'),
|
|
* (2, 'woodwinds');
|
|
* insert into
|
|
* instruments (id, section_id, name)
|
|
* values
|
|
* (1, 2, 'flute'),
|
|
* (2, 1, 'violin');
|
|
* ```
|
|
*
|
|
* @exampleResponse Query referenced tables
|
|
* ```json
|
|
* {
|
|
* "data": [
|
|
* {
|
|
* "name": "strings",
|
|
* "instruments": [
|
|
* {
|
|
* "name": "violin"
|
|
* }
|
|
* ]
|
|
* },
|
|
* {
|
|
* "name": "woodwinds",
|
|
* "instruments": [
|
|
* {
|
|
* "name": "flute"
|
|
* }
|
|
* ]
|
|
* }
|
|
* ],
|
|
* "status": 200,
|
|
* "statusText": "OK"
|
|
* }
|
|
* ```
|
|
*
|
|
* @exampleDescription Query referenced tables with spaces in their names
|
|
* If your table name contains spaces, you must use double quotes in the `select` statement to reference the table.
|
|
*
|
|
* @example Query referenced tables with spaces in their names
|
|
* ```js
|
|
* const { data, error } = await supabase
|
|
* .from('orchestral sections')
|
|
* .select(`
|
|
* name,
|
|
* "musical instruments" (
|
|
* name
|
|
* )
|
|
* `)
|
|
* ```
|
|
*
|
|
* @exampleSql Query referenced tables with spaces in their names
|
|
* ```sql
|
|
* create table
|
|
* "orchestral sections" (id int8 primary key, name text);
|
|
* create table
|
|
* "musical instruments" (
|
|
* id int8 primary key,
|
|
* section_id int8 not null references "orchestral sections",
|
|
* name text
|
|
* );
|
|
*
|
|
* insert into
|
|
* "orchestral sections" (id, name)
|
|
* values
|
|
* (1, 'strings'),
|
|
* (2, 'woodwinds');
|
|
* insert into
|
|
* "musical instruments" (id, section_id, name)
|
|
* values
|
|
* (1, 2, 'flute'),
|
|
* (2, 1, 'violin');
|
|
* ```
|
|
*
|
|
* @exampleResponse Query referenced tables with spaces in their names
|
|
* ```json
|
|
* {
|
|
* "data": [
|
|
* {
|
|
* "name": "strings",
|
|
* "musical instruments": [
|
|
* {
|
|
* "name": "violin"
|
|
* }
|
|
* ]
|
|
* },
|
|
* {
|
|
* "name": "woodwinds",
|
|
* "musical instruments": [
|
|
* {
|
|
* "name": "flute"
|
|
* }
|
|
* ]
|
|
* }
|
|
* ],
|
|
* "status": 200,
|
|
* "statusText": "OK"
|
|
* }
|
|
* ```
|
|
*
|
|
* @exampleDescription Query referenced tables through a join table
|
|
* If you're in a situation where your tables are **NOT** directly
|
|
* related, but instead are joined by a _join table_, you can still use
|
|
* the `select()` method to query the related data. The join table needs
|
|
* to have the foreign keys as part of its composite primary key.
|
|
*
|
|
* @example Query referenced tables through a join table
|
|
* ```ts
|
|
* const { data, error } = await supabase
|
|
* .from('users')
|
|
* .select(`
|
|
* name,
|
|
* teams (
|
|
* name
|
|
* )
|
|
* `)
|
|
*
|
|
* ```
|
|
*
|
|
* @exampleSql Query referenced tables through a join table
|
|
* ```sql
|
|
* create table
|
|
* users (
|
|
* id int8 primary key,
|
|
* name text
|
|
* );
|
|
* create table
|
|
* teams (
|
|
* id int8 primary key,
|
|
* name text
|
|
* );
|
|
* -- join table
|
|
* create table
|
|
* users_teams (
|
|
* user_id int8 not null references users,
|
|
* team_id int8 not null references teams,
|
|
* -- both foreign keys must be part of a composite primary key
|
|
* primary key (user_id, team_id)
|
|
* );
|
|
*
|
|
* insert into
|
|
* users (id, name)
|
|
* values
|
|
* (1, 'Kiran'),
|
|
* (2, 'Evan');
|
|
* insert into
|
|
* teams (id, name)
|
|
* values
|
|
* (1, 'Green'),
|
|
* (2, 'Blue');
|
|
* insert into
|
|
* users_teams (user_id, team_id)
|
|
* values
|
|
* (1, 1),
|
|
* (1, 2),
|
|
* (2, 2);
|
|
* ```
|
|
*
|
|
* @exampleResponse Query referenced tables through a join table
|
|
* ```json
|
|
* {
|
|
* "data": [
|
|
* {
|
|
* "name": "Kiran",
|
|
* "teams": [
|
|
* {
|
|
* "name": "Green"
|
|
* },
|
|
* {
|
|
* "name": "Blue"
|
|
* }
|
|
* ]
|
|
* },
|
|
* {
|
|
* "name": "Evan",
|
|
* "teams": [
|
|
* {
|
|
* "name": "Blue"
|
|
* }
|
|
* ]
|
|
* }
|
|
* ],
|
|
* "status": 200,
|
|
* "statusText": "OK"
|
|
* }
|
|
*
|
|
* ```
|
|
*
|
|
* @exampleDescription Query the same referenced table multiple times
|
|
* If you need to query the same referenced table twice, use the name of the
|
|
* joined column to identify which join to use. You can also give each
|
|
* column an alias.
|
|
*
|
|
* @example Query the same referenced table multiple times
|
|
* ```ts
|
|
* const { data, error } = await supabase
|
|
* .from('messages')
|
|
* .select(`
|
|
* content,
|
|
* from:sender_id(name),
|
|
* to:receiver_id(name)
|
|
* `)
|
|
*
|
|
* // To infer types, use the name of the table (in this case `users`) and
|
|
* // the name of the foreign key constraint.
|
|
* const { data, error } = await supabase
|
|
* .from('messages')
|
|
* .select(`
|
|
* content,
|
|
* from:users!messages_sender_id_fkey(name),
|
|
* to:users!messages_receiver_id_fkey(name)
|
|
* `)
|
|
* ```
|
|
*
|
|
* @exampleSql Query the same referenced table multiple times
|
|
* ```sql
|
|
* create table
|
|
* users (id int8 primary key, name text);
|
|
*
|
|
* create table
|
|
* messages (
|
|
* sender_id int8 not null references users,
|
|
* receiver_id int8 not null references users,
|
|
* content text
|
|
* );
|
|
*
|
|
* insert into
|
|
* users (id, name)
|
|
* values
|
|
* (1, 'Kiran'),
|
|
* (2, 'Evan');
|
|
*
|
|
* insert into
|
|
* messages (sender_id, receiver_id, content)
|
|
* values
|
|
* (1, 2, '👋');
|
|
* ```
|
|
* ```
|
|
*
|
|
* @exampleResponse Query the same referenced table multiple times
|
|
* ```json
|
|
* {
|
|
* "data": [
|
|
* {
|
|
* "content": "👋",
|
|
* "from": {
|
|
* "name": "Kiran"
|
|
* },
|
|
* "to": {
|
|
* "name": "Evan"
|
|
* }
|
|
* }
|
|
* ],
|
|
* "status": 200,
|
|
* "statusText": "OK"
|
|
* }
|
|
* ```
|
|
*
|
|
* @exampleDescription Query nested foreign tables through a join table
|
|
* You can use the result of a joined table to gather data in
|
|
* another foreign table. With multiple references to the same foreign
|
|
* table you must specify the column on which to conduct the join.
|
|
*
|
|
* @example Query nested foreign tables through a join table
|
|
* ```ts
|
|
* const { data, error } = await supabase
|
|
* .from('games')
|
|
* .select(`
|
|
* game_id:id,
|
|
* away_team:teams!games_away_team_fkey (
|
|
* users (
|
|
* id,
|
|
* name
|
|
* )
|
|
* )
|
|
* `)
|
|
*
|
|
* ```
|
|
*
|
|
* @exampleSql Query nested foreign tables through a join table
|
|
* ```sql
|
|
* ```sql
|
|
* create table
|
|
* users (
|
|
* id int8 primary key,
|
|
* name text
|
|
* );
|
|
* create table
|
|
* teams (
|
|
* id int8 primary key,
|
|
* name text
|
|
* );
|
|
* -- join table
|
|
* create table
|
|
* users_teams (
|
|
* user_id int8 not null references users,
|
|
* team_id int8 not null references teams,
|
|
*
|
|
* primary key (user_id, team_id)
|
|
* );
|
|
* create table
|
|
* games (
|
|
* id int8 primary key,
|
|
* home_team int8 not null references teams,
|
|
* away_team int8 not null references teams,
|
|
* name text
|
|
* );
|
|
*
|
|
* insert into users (id, name)
|
|
* values
|
|
* (1, 'Kiran'),
|
|
* (2, 'Evan');
|
|
* insert into
|
|
* teams (id, name)
|
|
* values
|
|
* (1, 'Green'),
|
|
* (2, 'Blue');
|
|
* insert into
|
|
* users_teams (user_id, team_id)
|
|
* values
|
|
* (1, 1),
|
|
* (1, 2),
|
|
* (2, 2);
|
|
* insert into
|
|
* games (id, home_team, away_team, name)
|
|
* values
|
|
* (1, 1, 2, 'Green vs Blue'),
|
|
* (2, 2, 1, 'Blue vs Green');
|
|
* ```
|
|
*
|
|
* @exampleResponse Query nested foreign tables through a join table
|
|
* ```json
|
|
* {
|
|
* "data": [
|
|
* {
|
|
* "game_id": 1,
|
|
* "away_team": {
|
|
* "users": [
|
|
* {
|
|
* "id": 1,
|
|
* "name": "Kiran"
|
|
* },
|
|
* {
|
|
* "id": 2,
|
|
* "name": "Evan"
|
|
* }
|
|
* ]
|
|
* }
|
|
* },
|
|
* {
|
|
* "game_id": 2,
|
|
* "away_team": {
|
|
* "users": [
|
|
* {
|
|
* "id": 1,
|
|
* "name": "Kiran"
|
|
* }
|
|
* ]
|
|
* }
|
|
* }
|
|
* ],
|
|
* "status": 200,
|
|
* "statusText": "OK"
|
|
* }
|
|
*
|
|
* ```
|
|
*
|
|
* @exampleDescription Filtering through referenced tables
|
|
* If the filter on a referenced table's column is not satisfied, the referenced
|
|
* table returns `[]` or `null` but the parent table is not filtered out.
|
|
* If you want to filter out the parent table rows, use the `!inner` hint
|
|
*
|
|
* @example Filtering through referenced tables
|
|
* ```ts
|
|
* const { data, error } = await supabase
|
|
* .from('instruments')
|
|
* .select('name, orchestral_sections(*)')
|
|
* .eq('orchestral_sections.name', 'percussion')
|
|
* ```
|
|
*
|
|
* @exampleSql Filtering through referenced tables
|
|
* ```sql
|
|
* create table
|
|
* orchestral_sections (id int8 primary key, name text);
|
|
* create table
|
|
* instruments (
|
|
* id int8 primary key,
|
|
* section_id int8 not null references orchestral_sections,
|
|
* name text
|
|
* );
|
|
*
|
|
* insert into
|
|
* orchestral_sections (id, name)
|
|
* values
|
|
* (1, 'strings'),
|
|
* (2, 'woodwinds');
|
|
* insert into
|
|
* instruments (id, section_id, name)
|
|
* values
|
|
* (1, 2, 'flute'),
|
|
* (2, 1, 'violin');
|
|
* ```
|
|
*
|
|
* @exampleResponse Filtering through referenced tables
|
|
* ```json
|
|
* {
|
|
* "data": [
|
|
* {
|
|
* "name": "flute",
|
|
* "orchestral_sections": null
|
|
* },
|
|
* {
|
|
* "name": "violin",
|
|
* "orchestral_sections": null
|
|
* }
|
|
* ],
|
|
* "status": 200,
|
|
* "statusText": "OK"
|
|
* }
|
|
* ```
|
|
*
|
|
* @exampleDescription Querying referenced table with count
|
|
* You can get the number of rows in a related table by using the
|
|
* **count** property.
|
|
*
|
|
* @example Querying referenced table with count
|
|
* ```ts
|
|
* const { data, error } = await supabase
|
|
* .from('orchestral_sections')
|
|
* .select(`*, instruments(count)`)
|
|
* ```
|
|
*
|
|
* @exampleSql Querying referenced table with count
|
|
* ```sql
|
|
* create table orchestral_sections (
|
|
* "id" "uuid" primary key default "extensions"."uuid_generate_v4"() not null,
|
|
* "name" text
|
|
* );
|
|
*
|
|
* create table characters (
|
|
* "id" "uuid" primary key default "extensions"."uuid_generate_v4"() not null,
|
|
* "name" text,
|
|
* "section_id" "uuid" references public.orchestral_sections on delete cascade
|
|
* );
|
|
*
|
|
* with section as (
|
|
* insert into orchestral_sections (name)
|
|
* values ('strings') returning id
|
|
* )
|
|
* insert into instruments (name, section_id) values
|
|
* ('violin', (select id from section)),
|
|
* ('viola', (select id from section)),
|
|
* ('cello', (select id from section)),
|
|
* ('double bass', (select id from section));
|
|
* ```
|
|
*
|
|
* @exampleResponse Querying referenced table with count
|
|
* ```json
|
|
* [
|
|
* {
|
|
* "id": "693694e7-d993-4360-a6d7-6294e325d9b6",
|
|
* "name": "strings",
|
|
* "instruments": [
|
|
* {
|
|
* "count": 4
|
|
* }
|
|
* ]
|
|
* }
|
|
* ]
|
|
* ```
|
|
*
|
|
* @exampleDescription Querying with count option
|
|
* You can get the number of rows by using the
|
|
* [count](/docs/reference/javascript/select#parameters) option.
|
|
*
|
|
* @example Querying with count option
|
|
* ```ts
|
|
* const { count, error } = await supabase
|
|
* .from('characters')
|
|
* .select('*', { count: 'exact', head: true })
|
|
* ```
|
|
*
|
|
* @exampleSql Querying with count option
|
|
* ```sql
|
|
* create table
|
|
* characters (id int8 primary key, name text);
|
|
*
|
|
* insert into
|
|
* characters (id, name)
|
|
* values
|
|
* (1, 'Luke'),
|
|
* (2, 'Leia'),
|
|
* (3, 'Han');
|
|
* ```
|
|
*
|
|
* @exampleResponse Querying with count option
|
|
* ```json
|
|
* {
|
|
* "count": 3,
|
|
* "status": 200,
|
|
* "statusText": "OK"
|
|
* }
|
|
* ```
|
|
*
|
|
* @exampleDescription Querying JSON data
|
|
* You can select and filter data inside of
|
|
* [JSON](/docs/guides/database/json) columns. Postgres offers some
|
|
* [operators](/docs/guides/database/json#query-the-jsonb-data) for
|
|
* querying JSON data.
|
|
*
|
|
* @example Querying JSON data
|
|
* ```ts
|
|
* const { data, error } = await supabase
|
|
* .from('users')
|
|
* .select(`
|
|
* id, name,
|
|
* address->city
|
|
* `)
|
|
* ```
|
|
*
|
|
* @exampleSql Querying JSON data
|
|
* ```sql
|
|
* create table
|
|
* users (
|
|
* id int8 primary key,
|
|
* name text,
|
|
* address jsonb
|
|
* );
|
|
*
|
|
* insert into
|
|
* users (id, name, address)
|
|
* values
|
|
* (1, 'Frodo', '{"city":"Hobbiton"}');
|
|
* ```
|
|
*
|
|
* @exampleResponse Querying JSON data
|
|
* ```json
|
|
* {
|
|
* "data": [
|
|
* {
|
|
* "id": 1,
|
|
* "name": "Frodo",
|
|
* "city": "Hobbiton"
|
|
* }
|
|
* ],
|
|
* "status": 200,
|
|
* "statusText": "OK"
|
|
* }
|
|
* ```
|
|
*
|
|
* @exampleDescription Querying referenced table with inner join
|
|
* If you don't want to return the referenced table contents, you can leave the parenthesis empty.
|
|
* Like `.select('name, orchestral_sections!inner()')`.
|
|
*
|
|
* @example Querying referenced table with inner join
|
|
* ```ts
|
|
* const { data, error } = await supabase
|
|
* .from('instruments')
|
|
* .select('name, orchestral_sections!inner(name)')
|
|
* .eq('orchestral_sections.name', 'woodwinds')
|
|
* .limit(1)
|
|
* ```
|
|
*
|
|
* @exampleSql Querying referenced table with inner join
|
|
* ```sql
|
|
* create table orchestral_sections (
|
|
* "id" "uuid" primary key default "extensions"."uuid_generate_v4"() not null,
|
|
* "name" text
|
|
* );
|
|
*
|
|
* create table instruments (
|
|
* "id" "uuid" primary key default "extensions"."uuid_generate_v4"() not null,
|
|
* "name" text,
|
|
* "section_id" "uuid" references public.orchestral_sections on delete cascade
|
|
* );
|
|
*
|
|
* with section as (
|
|
* insert into orchestral_sections (name)
|
|
* values ('woodwinds') returning id
|
|
* )
|
|
* insert into instruments (name, section_id) values
|
|
* ('flute', (select id from section)),
|
|
* ('clarinet', (select id from section)),
|
|
* ('bassoon', (select id from section)),
|
|
* ('piccolo', (select id from section));
|
|
* ```
|
|
*
|
|
* @exampleResponse Querying referenced table with inner join
|
|
* ```json
|
|
* {
|
|
* "data": [
|
|
* {
|
|
* "name": "flute",
|
|
* "orchestral_sections": {"name": "woodwinds"}
|
|
* }
|
|
* ],
|
|
* "status": 200,
|
|
* "statusText": "OK"
|
|
* }
|
|
* ```
|
|
*
|
|
* @exampleDescription Switching schemas per query
|
|
* In addition to setting the schema during initialization, you can also switch schemas on a per-query basis.
|
|
* Make sure you've set up your [database privileges and API settings](/docs/guides/api/using-custom-schemas).
|
|
*
|
|
* @example Switching schemas per query
|
|
* ```ts
|
|
* const { data, error } = await supabase
|
|
* .schema('myschema')
|
|
* .from('mytable')
|
|
* .select()
|
|
* ```
|
|
*
|
|
* @exampleSql Switching schemas per query
|
|
* ```sql
|
|
* create schema myschema;
|
|
*
|
|
* create table myschema.mytable (
|
|
* id uuid primary key default gen_random_uuid(),
|
|
* data text
|
|
* );
|
|
*
|
|
* insert into myschema.mytable (data) values ('mydata');
|
|
* ```
|
|
*
|
|
* @exampleResponse Switching schemas per query
|
|
* ```json
|
|
* {
|
|
* "data": [
|
|
* {
|
|
* "id": "4162e008-27b0-4c0f-82dc-ccaeee9a624d",
|
|
* "data": "mydata"
|
|
* }
|
|
* ],
|
|
* "status": 200,
|
|
* "statusText": "OK"
|
|
* }
|
|
* ```
|
|
*/
|
|
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("");
|
|
const { url, headers } = this.cloneRequestState();
|
|
url.searchParams.set("select", cleanedColumns);
|
|
if (count) headers.append("Prefer", `count=${count}`);
|
|
return new PostgrestFilterBuilder({
|
|
method,
|
|
url,
|
|
headers,
|
|
schema: this.schema,
|
|
fetch: this.fetch,
|
|
urlLengthLimit: this.urlLengthLimit
|
|
});
|
|
}
|
|
/**
|
|
* 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.
|
|
*
|
|
* @category Database
|
|
*
|
|
* @example Create a record
|
|
* ```ts
|
|
* const { error } = await supabase
|
|
* .from('countries')
|
|
* .insert({ id: 1, name: 'Mordor' })
|
|
* ```
|
|
*
|
|
* @exampleSql Create a record
|
|
* ```sql
|
|
* create table
|
|
* countries (id int8 primary key, name text);
|
|
* ```
|
|
*
|
|
* @exampleResponse Create a record
|
|
* ```json
|
|
* {
|
|
* "status": 201,
|
|
* "statusText": "Created"
|
|
* }
|
|
* ```
|
|
*
|
|
* @example Create a record and return it
|
|
* ```ts
|
|
* const { data, error } = await supabase
|
|
* .from('countries')
|
|
* .insert({ id: 1, name: 'Mordor' })
|
|
* .select()
|
|
* ```
|
|
*
|
|
* @exampleSql Create a record and return it
|
|
* ```sql
|
|
* create table
|
|
* countries (id int8 primary key, name text);
|
|
* ```
|
|
*
|
|
* @exampleResponse Create a record and return it
|
|
* ```json
|
|
* {
|
|
* "data": [
|
|
* {
|
|
* "id": 1,
|
|
* "name": "Mordor"
|
|
* }
|
|
* ],
|
|
* "status": 201,
|
|
* "statusText": "Created"
|
|
* }
|
|
* ```
|
|
*
|
|
* @exampleDescription Bulk create
|
|
* A bulk create operation is handled in a single transaction.
|
|
* If any of the inserts fail, none of the rows are inserted.
|
|
*
|
|
* @example Bulk create
|
|
* ```ts
|
|
* const { error } = await supabase
|
|
* .from('countries')
|
|
* .insert([
|
|
* { id: 1, name: 'Mordor' },
|
|
* { id: 1, name: 'The Shire' },
|
|
* ])
|
|
* ```
|
|
*
|
|
* @exampleSql Bulk create
|
|
* ```sql
|
|
* create table
|
|
* countries (id int8 primary key, name text);
|
|
* ```
|
|
*
|
|
* @exampleResponse Bulk create
|
|
* ```json
|
|
* {
|
|
* "error": {
|
|
* "code": "23505",
|
|
* "details": "Key (id)=(1) already exists.",
|
|
* "hint": null,
|
|
* "message": "duplicate key value violates unique constraint \"countries_pkey\""
|
|
* },
|
|
* "status": 409,
|
|
* "statusText": "Conflict"
|
|
* }
|
|
* ```
|
|
*/
|
|
insert(values, { count, defaultToNull = true } = {}) {
|
|
var _this$fetch;
|
|
const method = "POST";
|
|
const { url, headers } = this.cloneRequestState();
|
|
if (count) headers.append("Prefer", `count=${count}`);
|
|
if (!defaultToNull) 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}"`);
|
|
url.searchParams.set("columns", uniqueColumns.join(","));
|
|
}
|
|
}
|
|
return new PostgrestFilterBuilder({
|
|
method,
|
|
url,
|
|
headers,
|
|
schema: this.schema,
|
|
body: values,
|
|
fetch: (_this$fetch = this.fetch) !== null && _this$fetch !== void 0 ? _this$fetch : fetch,
|
|
urlLengthLimit: this.urlLengthLimit
|
|
});
|
|
}
|
|
/**
|
|
* 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
|
|
* // }
|
|
* ```
|
|
*
|
|
* @category Database
|
|
*
|
|
* @remarks
|
|
* - Primary keys must be included in `values` to use upsert.
|
|
*
|
|
* @example Upsert your data
|
|
* ```ts
|
|
* const { data, error } = await supabase
|
|
* .from('instruments')
|
|
* .upsert({ id: 1, name: 'piano' })
|
|
* .select()
|
|
* ```
|
|
*
|
|
* @exampleSql Upsert your data
|
|
* ```sql
|
|
* create table
|
|
* instruments (id int8 primary key, name text);
|
|
*
|
|
* insert into
|
|
* instruments (id, name)
|
|
* values
|
|
* (1, 'harpsichord');
|
|
* ```
|
|
*
|
|
* @exampleResponse Upsert your data
|
|
* ```json
|
|
* {
|
|
* "data": [
|
|
* {
|
|
* "id": 1,
|
|
* "name": "piano"
|
|
* }
|
|
* ],
|
|
* "status": 201,
|
|
* "statusText": "Created"
|
|
* }
|
|
* ```
|
|
*
|
|
* @example Bulk Upsert your data
|
|
* ```ts
|
|
* const { data, error } = await supabase
|
|
* .from('instruments')
|
|
* .upsert([
|
|
* { id: 1, name: 'piano' },
|
|
* { id: 2, name: 'harp' },
|
|
* ])
|
|
* .select()
|
|
* ```
|
|
*
|
|
* @exampleSql Bulk Upsert your data
|
|
* ```sql
|
|
* create table
|
|
* instruments (id int8 primary key, name text);
|
|
*
|
|
* insert into
|
|
* instruments (id, name)
|
|
* values
|
|
* (1, 'harpsichord');
|
|
* ```
|
|
*
|
|
* @exampleResponse Bulk Upsert your data
|
|
* ```json
|
|
* {
|
|
* "data": [
|
|
* {
|
|
* "id": 1,
|
|
* "name": "piano"
|
|
* },
|
|
* {
|
|
* "id": 2,
|
|
* "name": "harp"
|
|
* }
|
|
* ],
|
|
* "status": 201,
|
|
* "statusText": "Created"
|
|
* }
|
|
* ```
|
|
*
|
|
* @exampleDescription Upserting into tables with constraints
|
|
* In the following query, `upsert()` implicitly uses the `id`
|
|
* (primary key) column to determine conflicts. If there is no existing
|
|
* row with the same `id`, `upsert()` inserts a new row, which
|
|
* will fail in this case as there is already a row with `handle` `"saoirse"`.
|
|
* Using the `onConflict` option, you can instruct `upsert()` to use
|
|
* another column with a unique constraint to determine conflicts.
|
|
*
|
|
* @example Upserting into tables with constraints
|
|
* ```ts
|
|
* const { data, error } = await supabase
|
|
* .from('users')
|
|
* .upsert({ id: 42, handle: 'saoirse', display_name: 'Saoirse' })
|
|
* .select()
|
|
* ```
|
|
*
|
|
* @exampleSql Upserting into tables with constraints
|
|
* ```sql
|
|
* create table
|
|
* users (
|
|
* id int8 generated by default as identity primary key,
|
|
* handle text not null unique,
|
|
* display_name text
|
|
* );
|
|
*
|
|
* insert into
|
|
* users (id, handle, display_name)
|
|
* values
|
|
* (1, 'saoirse', null);
|
|
* ```
|
|
*
|
|
* @exampleResponse Upserting into tables with constraints
|
|
* ```json
|
|
* {
|
|
* "error": {
|
|
* "code": "23505",
|
|
* "details": "Key (handle)=(saoirse) already exists.",
|
|
* "hint": null,
|
|
* "message": "duplicate key value violates unique constraint \"users_handle_key\""
|
|
* },
|
|
* "status": 409,
|
|
* "statusText": "Conflict"
|
|
* }
|
|
* ```
|
|
*/
|
|
upsert(values, { onConflict, ignoreDuplicates = false, count, defaultToNull = true } = {}) {
|
|
var _this$fetch2;
|
|
const method = "POST";
|
|
const { url, headers } = this.cloneRequestState();
|
|
headers.append("Prefer", `resolution=${ignoreDuplicates ? "ignore" : "merge"}-duplicates`);
|
|
if (onConflict !== void 0) url.searchParams.set("on_conflict", onConflict);
|
|
if (count) headers.append("Prefer", `count=${count}`);
|
|
if (!defaultToNull) 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}"`);
|
|
url.searchParams.set("columns", uniqueColumns.join(","));
|
|
}
|
|
}
|
|
return new PostgrestFilterBuilder({
|
|
method,
|
|
url,
|
|
headers,
|
|
schema: this.schema,
|
|
body: values,
|
|
fetch: (_this$fetch2 = this.fetch) !== null && _this$fetch2 !== void 0 ? _this$fetch2 : fetch,
|
|
urlLengthLimit: this.urlLengthLimit
|
|
});
|
|
}
|
|
/**
|
|
* 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.
|
|
*
|
|
* @category Database
|
|
*
|
|
* @remarks
|
|
* - `update()` should always be combined with [Filters](/docs/reference/javascript/using-filters) to target the item(s) you wish to update.
|
|
*
|
|
* @example Updating your data
|
|
* ```ts
|
|
* const { error } = await supabase
|
|
* .from('instruments')
|
|
* .update({ name: 'piano' })
|
|
* .eq('id', 1)
|
|
* ```
|
|
*
|
|
* @exampleSql Updating your data
|
|
* ```sql
|
|
* create table
|
|
* instruments (id int8 primary key, name text);
|
|
*
|
|
* insert into
|
|
* instruments (id, name)
|
|
* values
|
|
* (1, 'harpsichord');
|
|
* ```
|
|
*
|
|
* @exampleResponse Updating your data
|
|
* ```json
|
|
* {
|
|
* "status": 204,
|
|
* "statusText": "No Content"
|
|
* }
|
|
* ```
|
|
*
|
|
* @example Update a record and return it
|
|
* ```ts
|
|
* const { data, error } = await supabase
|
|
* .from('instruments')
|
|
* .update({ name: 'piano' })
|
|
* .eq('id', 1)
|
|
* .select()
|
|
* ```
|
|
*
|
|
* @exampleSql Update a record and return it
|
|
* ```sql
|
|
* create table
|
|
* instruments (id int8 primary key, name text);
|
|
*
|
|
* insert into
|
|
* instruments (id, name)
|
|
* values
|
|
* (1, 'harpsichord');
|
|
* ```
|
|
*
|
|
* @exampleResponse Update a record and return it
|
|
* ```json
|
|
* {
|
|
* "data": [
|
|
* {
|
|
* "id": 1,
|
|
* "name": "piano"
|
|
* }
|
|
* ],
|
|
* "status": 200,
|
|
* "statusText": "OK"
|
|
* }
|
|
* ```
|
|
*
|
|
* @exampleDescription Updating JSON data
|
|
* Postgres offers some
|
|
* [operators](/docs/guides/database/json#query-the-jsonb-data) for
|
|
* working with JSON data. Currently, it is only possible to update the entire JSON document.
|
|
*
|
|
* @example Updating JSON data
|
|
* ```ts
|
|
* const { data, error } = await supabase
|
|
* .from('users')
|
|
* .update({
|
|
* address: {
|
|
* street: 'Melrose Place',
|
|
* postcode: 90210
|
|
* }
|
|
* })
|
|
* .eq('address->postcode', 90210)
|
|
* .select()
|
|
* ```
|
|
*
|
|
* @exampleSql Updating JSON data
|
|
* ```sql
|
|
* create table
|
|
* users (
|
|
* id int8 primary key,
|
|
* name text,
|
|
* address jsonb
|
|
* );
|
|
*
|
|
* insert into
|
|
* users (id, name, address)
|
|
* values
|
|
* (1, 'Michael', '{ "postcode": 90210 }');
|
|
* ```
|
|
*
|
|
* @exampleResponse Updating JSON data
|
|
* ```json
|
|
* {
|
|
* "data": [
|
|
* {
|
|
* "id": 1,
|
|
* "name": "Michael",
|
|
* "address": {
|
|
* "street": "Melrose Place",
|
|
* "postcode": 90210
|
|
* }
|
|
* }
|
|
* ],
|
|
* "status": 200,
|
|
* "statusText": "OK"
|
|
* }
|
|
* ```
|
|
*/
|
|
update(values, { count } = {}) {
|
|
var _this$fetch3;
|
|
const method = "PATCH";
|
|
const { url, headers } = this.cloneRequestState();
|
|
if (count) headers.append("Prefer", `count=${count}`);
|
|
return new PostgrestFilterBuilder({
|
|
method,
|
|
url,
|
|
headers,
|
|
schema: this.schema,
|
|
body: values,
|
|
fetch: (_this$fetch3 = this.fetch) !== null && _this$fetch3 !== void 0 ? _this$fetch3 : fetch,
|
|
urlLengthLimit: this.urlLengthLimit
|
|
});
|
|
}
|
|
/**
|
|
* 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";
|
|
const { url, headers } = this.cloneRequestState();
|
|
if (count) headers.append("Prefer", `count=${count}`);
|
|
return new PostgrestFilterBuilder({
|
|
method,
|
|
url,
|
|
headers,
|
|
schema: this.schema,
|
|
fetch: (_this$fetch4 = this.fetch) !== null && _this$fetch4 !== void 0 ? _this$fetch4 : fetch,
|
|
urlLengthLimit: this.urlLengthLimit
|
|
});
|
|
}
|
|
};
|
|
|
|
//#endregion
|
|
//#region \0@oxc-project+runtime@0.101.0/helpers/typeof.js
|
|
function _typeof(o) {
|
|
"@babel/helpers - typeof";
|
|
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o$1) {
|
|
return typeof o$1;
|
|
} : function(o$1) {
|
|
return o$1 && "function" == typeof Symbol && o$1.constructor === Symbol && o$1 !== Symbol.prototype ? "symbol" : typeof o$1;
|
|
}, _typeof(o);
|
|
}
|
|
|
|
//#endregion
|
|
//#region \0@oxc-project+runtime@0.101.0/helpers/toPrimitive.js
|
|
function toPrimitive(t, r) {
|
|
if ("object" != _typeof(t) || !t) return t;
|
|
var e = t[Symbol.toPrimitive];
|
|
if (void 0 !== e) {
|
|
var i = e.call(t, r || "default");
|
|
if ("object" != _typeof(i)) return i;
|
|
throw new TypeError("@@toPrimitive must return a primitive value.");
|
|
}
|
|
return ("string" === r ? String : Number)(t);
|
|
}
|
|
|
|
//#endregion
|
|
//#region \0@oxc-project+runtime@0.101.0/helpers/toPropertyKey.js
|
|
function toPropertyKey(t) {
|
|
var i = toPrimitive(t, "string");
|
|
return "symbol" == _typeof(i) ? i : i + "";
|
|
}
|
|
|
|
//#endregion
|
|
//#region \0@oxc-project+runtime@0.101.0/helpers/defineProperty.js
|
|
function _defineProperty(e, r, t) {
|
|
return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
|
|
value: t,
|
|
enumerable: !0,
|
|
configurable: !0,
|
|
writable: !0
|
|
}) : e[r] = t, e;
|
|
}
|
|
|
|
//#endregion
|
|
//#region \0@oxc-project+runtime@0.101.0/helpers/objectSpread2.js
|
|
function ownKeys(e, r) {
|
|
var t = Object.keys(e);
|
|
if (Object.getOwnPropertySymbols) {
|
|
var o = Object.getOwnPropertySymbols(e);
|
|
r && (o = o.filter(function(r$1) {
|
|
return Object.getOwnPropertyDescriptor(e, r$1).enumerable;
|
|
})), t.push.apply(t, o);
|
|
}
|
|
return t;
|
|
}
|
|
function _objectSpread2(e) {
|
|
for (var r = 1; r < arguments.length; r++) {
|
|
var t = null != arguments[r] ? arguments[r] : {};
|
|
r % 2 ? ownKeys(Object(t), !0).forEach(function(r$1) {
|
|
_defineProperty(e, r$1, t[r$1]);
|
|
}) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function(r$1) {
|
|
Object.defineProperty(e, r$1, Object.getOwnPropertyDescriptor(t, r$1));
|
|
});
|
|
}
|
|
return e;
|
|
}
|
|
|
|
//#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
|
|
* @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.
|
|
* @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',
|
|
* timeout: 30000, // 30 second timeout
|
|
* })
|
|
* ```
|
|
*/
|
|
constructor(url, { headers = {}, schema, fetch: fetch$1, timeout, urlLengthLimit = 8e3 } = {}) {
|
|
this.url = url;
|
|
this.headers = new Headers(headers);
|
|
this.schemaName = schema;
|
|
this.urlLengthLimit = urlLengthLimit;
|
|
const originalFetch = fetch$1 !== null && fetch$1 !== void 0 ? fetch$1 : globalThis.fetch;
|
|
if (timeout !== void 0 && timeout > 0) this.fetch = (input, init) => {
|
|
const controller = new AbortController();
|
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
const existingSignal = init === null || init === void 0 ? void 0 : init.signal;
|
|
if (existingSignal) {
|
|
if (existingSignal.aborted) {
|
|
clearTimeout(timeoutId);
|
|
return originalFetch(input, init);
|
|
}
|
|
const abortHandler = () => {
|
|
clearTimeout(timeoutId);
|
|
controller.abort();
|
|
};
|
|
existingSignal.addEventListener("abort", abortHandler, { once: true });
|
|
return originalFetch(input, _objectSpread2(_objectSpread2({}, init), {}, { signal: controller.signal })).finally(() => {
|
|
clearTimeout(timeoutId);
|
|
existingSignal.removeEventListener("abort", abortHandler);
|
|
});
|
|
}
|
|
return originalFetch(input, _objectSpread2(_objectSpread2({}, init), {}, { signal: controller.signal })).finally(() => clearTimeout(timeoutId));
|
|
};
|
|
else this.fetch = originalFetch;
|
|
}
|
|
/**
|
|
* 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,
|
|
urlLengthLimit: this.urlLengthLimit
|
|
});
|
|
}
|
|
/**
|
|
* 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,
|
|
urlLengthLimit: this.urlLengthLimit
|
|
});
|
|
}
|
|
/**
|
|
* 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;
|
|
const _isObject = (v) => v !== null && typeof v === "object" && (!Array.isArray(v) || v.some(_isObject));
|
|
const _hasObjectArg = head && Object.values(args).some(_isObject);
|
|
if (_hasObjectArg) {
|
|
method = "POST";
|
|
body = args;
|
|
} else 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 (_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 = this.fetch) !== null && _this$fetch !== void 0 ? _this$fetch : fetch,
|
|
urlLengthLimit: this.urlLengthLimit
|
|
});
|
|
}
|
|
};
|
|
|
|
//#endregion
|
|
//#region src/index.ts
|
|
var src_default = {
|
|
PostgrestClient,
|
|
PostgrestQueryBuilder,
|
|
PostgrestFilterBuilder,
|
|
PostgrestTransformBuilder,
|
|
PostgrestBuilder,
|
|
PostgrestError
|
|
};
|
|
|
|
//#endregion
|
|
export { PostgrestBuilder, PostgrestClient, PostgrestError, PostgrestFilterBuilder, PostgrestQueryBuilder, PostgrestTransformBuilder, src_default as default };
|
|
//# sourceMappingURL=index.mjs.map
|