@miuiu/postgrest
Version:
Isomorphic PostgREST client
515 lines (490 loc) • 15.2 kB
text/typescript
import PostgrestTransformBuilder from "./PostgrestTransformBuilder";
import { GenericSchema } from "./types";
type FilterOperator =
| "eq"
| "neq"
| "gt"
| "gte"
| "lt"
| "lte"
| "like"
| "ilike"
| "is"
| "in"
| "cs"
| "cd"
| "sl"
| "sr"
| "nxl"
| "nxr"
| "adj"
| "ov"
| "fts"
| "plfts"
| "phfts"
| "wfts";
export default class PostgrestFilterBuilder<
Schema extends GenericSchema,
Row extends Record<string, unknown>,
Result,
> extends PostgrestTransformBuilder<Schema, Row, Result> {
eq<ColumnName extends string & keyof Row>(
column: ColumnName,
value: Row[ColumnName],
): this;
eq(column: string, value: unknown): this;
/**
* 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: string, value: unknown): this {
this.url.searchParams.append(column, `eq.${value}`);
return this;
}
neq<ColumnName extends string & keyof Row>(
column: ColumnName,
value: Row[ColumnName],
): this;
neq(column: string, value: unknown): 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: string, value: unknown): this {
this.url.searchParams.append(column, `neq.${value}`);
return this;
}
gt<ColumnName extends string & keyof Row>(
column: ColumnName,
value: Row[ColumnName],
): this;
gt(column: string, value: unknown): 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: string, value: unknown): this {
this.url.searchParams.append(column, `gt.${value}`);
return this;
}
gte<ColumnName extends string & keyof Row>(
column: ColumnName,
value: Row[ColumnName],
): this;
gte(column: string, value: unknown): 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: string, value: unknown): this {
this.url.searchParams.append(column, `gte.${value}`);
return this;
}
lt<ColumnName extends string & keyof Row>(
column: ColumnName,
value: Row[ColumnName],
): this;
lt(column: string, value: unknown): 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: string, value: unknown): this {
this.url.searchParams.append(column, `lt.${value}`);
return this;
}
lte<ColumnName extends string & keyof Row>(
column: ColumnName,
value: Row[ColumnName],
): this;
lte(column: string, value: unknown): 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: string, value: unknown): this {
this.url.searchParams.append(column, `lte.${value}`);
return this;
}
like<ColumnName extends string & keyof Row>(
column: ColumnName,
pattern: string,
): this;
like(column: string, pattern: string): 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: string, pattern: string): this {
this.url.searchParams.append(column, `like.${pattern}`);
return this;
}
ilike<ColumnName extends string & keyof Row>(
column: ColumnName,
pattern: string,
): this;
ilike(column: string, pattern: string): 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: string, pattern: string): this {
this.url.searchParams.append(column, `ilike.${pattern}`);
return this;
}
is<ColumnName extends string & keyof Row>(
column: ColumnName,
value: Row[ColumnName] & (boolean | null),
): this;
is(column: string, value: boolean | null): 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: string, value: boolean | null): this {
this.url.searchParams.append(column, `is.${value}`);
return this;
}
in<ColumnName extends string & keyof Row>(
column: ColumnName,
values: Row[ColumnName][],
): this;
in(column: string, values: unknown[]): 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: string, values: unknown[]): this {
const cleanedValues = values
.map((s) => {
// handle postgrest reserved characters
// https://postgrest.org/en/v7.0.0/api.html#reserved-characters
if (typeof s === "string" && new RegExp("[,()]").test(s))
return `"${s}"`;
else return `${s}`;
})
.join(",");
this.url.searchParams.append(column, `in.(${cleanedValues})`);
return this;
}
contains<ColumnName extends string & keyof Row>(
column: ColumnName,
value: string | Row[ColumnName][] | Record<string, unknown>,
): this;
contains(
column: string,
value: string | unknown[] | Record<string, unknown>,
): 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: string,
value: string | unknown[] | Record<string, unknown>,
): this {
if (typeof value === "string") {
// range types can be inclusive '[', ']' or exclusive '(', ')' so just
// keep it simple and accept a string
this.url.searchParams.append(column, `cs.${value}`);
} else if (Array.isArray(value)) {
// array
this.url.searchParams.append(column, `cs.{${value.join(",")}}`);
} else {
// json
this.url.searchParams.append(column, `cs.${JSON.stringify(value)}`);
}
return this;
}
containedBy<ColumnName extends string & keyof Row>(
column: ColumnName,
value: string | Row[ColumnName][] | Record<string, unknown>,
): this;
containedBy(
column: string,
value: string | unknown[] | Record<string, unknown>,
): 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: string,
value: string | unknown[] | Record<string, unknown>,
): this {
if (typeof value === "string") {
// range
this.url.searchParams.append(column, `cd.${value}`);
} else if (Array.isArray(value)) {
// array
this.url.searchParams.append(column, `cd.{${value.join(",")}}`);
} else {
// json
this.url.searchParams.append(column, `cd.${JSON.stringify(value)}`);
}
return this;
}
rangeGt<ColumnName extends string & keyof Row>(
column: ColumnName,
range: string,
): this;
rangeGt(column: string, range: string): 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: string, range: string): this {
this.url.searchParams.append(column, `sr.${range}`);
return this;
}
rangeGte<ColumnName extends string & keyof Row>(
column: ColumnName,
range: string,
): this;
rangeGte(column: string, range: string): 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: string, range: string): this {
this.url.searchParams.append(column, `nxl.${range}`);
return this;
}
rangeLt<ColumnName extends string & keyof Row>(
column: ColumnName,
range: string,
): this;
rangeLt(column: string, range: string): 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: string, range: string): this {
this.url.searchParams.append(column, `sl.${range}`);
return this;
}
rangeLte<ColumnName extends string & keyof Row>(
column: ColumnName,
range: string,
): this;
rangeLte(column: string, range: string): 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: string, range: string): this {
this.url.searchParams.append(column, `nxr.${range}`);
return this;
}
rangeAdjacent<ColumnName extends string & keyof Row>(
column: ColumnName,
range: string,
): this;
rangeAdjacent(column: string, range: string): 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: string, range: string): this {
this.url.searchParams.append(column, `adj.${range}`);
return this;
}
overlaps<ColumnName extends string & keyof Row>(
column: ColumnName,
value: string | Row[ColumnName][],
): this;
overlaps(column: string, value: string | unknown[]): 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: string, value: string | unknown[]): this {
if (typeof value === "string") {
// range
this.url.searchParams.append(column, `ov.${value}`);
} else {
// array
this.url.searchParams.append(column, `ov.{${value.join(",")}}`);
}
return this;
}
textSearch<ColumnName extends string & keyof Row>(
column: ColumnName,
query: string,
options?: { config?: string; type?: "plain" | "phrase" | "websearch" },
): this;
textSearch(
column: string,
query: string,
options?: { config?: string; type?: "plain" | "phrase" | "websearch" },
): 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: string,
query: string,
{
config,
type,
}: { config?: string; type?: "plain" | "phrase" | "websearch" } = {},
): this {
let typePart = "";
if (type === "plain") {
typePart = "pl";
} else if (type === "phrase") {
typePart = "ph";
} else if (type === "websearch") {
typePart = "w";
}
const configPart = config === undefined ? "" : `(${config})`;
this.url.searchParams.append(
column,
`${typePart}fts${configPart}.${query}`,
);
return this;
}
match<ColumnName extends string & keyof Row>(
query: Record<ColumnName, Row[ColumnName]>,
): this;
match(query: Record<string, unknown>): 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: Record<string, unknown>): this {
Object.entries(query).forEach(([column, value]) => {
this.url.searchParams.append(column, `eq.${value}`);
});
return this;
}
not<ColumnName extends string & keyof Row>(
column: ColumnName,
operator: FilterOperator,
value: Row[ColumnName],
): this;
not(column: string, operator: string, value: unknown): 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: string, operator: string, value: unknown): this {
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 foreignTable - Set this to filter on foreign tables instead of the
* current table
*/
or(filters: string, { foreignTable }: { foreignTable?: string } = {}): this {
const key = foreignTable ? `${foreignTable}.or` : "or";
this.url.searchParams.append(key, `(${filters})`);
return this;
}
filter<ColumnName extends string & keyof Row>(
column: ColumnName,
operator: `${"" | "not."}${FilterOperator}`,
value: unknown,
): this;
filter(column: string, operator: string, value: unknown): 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: string, operator: string, value: unknown): this {
this.url.searchParams.append(column, `${operator}.${value}`);
return this;
}
}