slonik-trpc
Version:
Slonik tRPC loader
219 lines (218 loc) • 12.9 kB
TypeScript
import { ValueExpression, SqlFragment, IdentifierSqlToken, CommonQueryMethods, FragmentSqlToken, QuerySqlToken } from "slonik";
import { z } from "zod";
import { comparisonFilterType, dateFilterType, jsonbContainsFilter, stringFilterType } from "../helpers/sqlUtils";
import type { PromiseOrValue } from "../helpers/types";
declare type LoadViewParameters<TFilter extends Record<string, any> = Record<never, any>, TFilterKey extends keyof TFilter = never, TFragment extends SqlFragment | QuerySqlToken = SqlFragment, TColumns extends string[] = never> = {
select: TFragment | TColumns;
orderBy?: SqlFragment;
groupBy?: SqlFragment;
take?: number;
skip?: number;
ctx?: any;
where?: RecursiveFilterConditions<{
[x in TFilterKey]?: TFilter[x];
}>;
db?: Pick<CommonQueryMethods, 'any'>;
};
export declare type Interpretors<TFilter extends Record<string, any>, TFilterKey extends keyof TFilter = keyof TFilter extends Record<infer K, any> ? K extends string ? K : never : never, TContext = any> = {
[x in TFilterKey]?: {
prefix?: string;
interpret: (filter: TFilter[x], allFilters: TFilter, context: TContext) => Promise<SqlFragment | null | undefined | false> | SqlFragment | null | undefined | false;
};
};
export declare type BuildView<TFilter extends Record<string, any> = Record<never, any>, TFilterKey extends keyof TFilter = never, TAliases extends string = "_main", TColumns extends string = never> = {
/**
* Allows adding custom filters to the view
* Multiple filters can be added at once
* This is mainly to be used in conjunction with getFilters
* WARNING: Do not use this otherwise, unless you know what you're doing.
* Prefer using the other filter methods, especially addGenericFilter if you need more flexibility
* @param filters - The filters to add
*/
addFilters<TNewFilter extends Record<string, any> = Record<never, any>, TNewFilterKey extends keyof TNewFilter = keyof TNewFilter extends Record<infer K, any> ? K extends string ? K : never : never>(filters: {
[x in TNewFilterKey]?: (filter: TNewFilter[x], allFilters: TFilter & TNewFilter, context: any) => Promise<SqlFragment | null | undefined | false> | SqlFragment | null | undefined | false;
}): BuildView<TNewFilter & TFilter, keyof TNewFilter | TFilterKey, TAliases, TColumns>;
/**
* Allows filtering by string operators, e.g. "contains", "starts with", "ends with", etc.
* @param field - The name of the filter - Can be a nested field, e.g. "user.name"
* @param mapper - Optional if you want to use a different column name than the filter name
*/
addStringFilter: <TKey extends Exclude<string, TFilterKey>>(field: TKey | TKey[], name?: SqlFragment | ((table: {
[x in TAliases]: IdentifierSqlToken;
} & {
[x: string]: IdentifierSqlToken;
}, value?: z.infer<typeof stringFilterType>, allFilters?: TFilter, ctx?: any) => SqlFragment)) => BuildView<TFilter & {
[x in TKey]?: z.infer<typeof stringFilterType>;
}, keyof TFilter | TKey, TAliases, TColumns>;
/**
* Allows filtering by comparison operators, e.g. "greater than", "less than", "between", "in", etc.
* @param field - The name of the filter - Can be a nested field, e.g. "user.name"
* @param mapper - Optional if you want to use a different column name than the filter name
* @returns
*/
addComparisonFilter: <TKey extends Exclude<string, TFilterKey>>(name: TKey | TKey[], mapper?: SqlFragment | ((table: {
[x in TAliases]: IdentifierSqlToken;
} & {
[x: string]: IdentifierSqlToken;
}, value?: z.infer<typeof comparisonFilterType>, allFilters?: TFilter, ctx?: any) => SqlFragment), type?: "text" | "numeric" | "integer" | "bigint" | string) => BuildView<TFilter & {
[x in TKey]?: z.infer<typeof comparisonFilterType>;
}, keyof TFilter | TKey, TAliases, TColumns>;
/**
* Allows filtering jsonb columns, using the @> operator to check if a JSONB column contains a certain value or structure.
* ```
view.addJsonContainsFilter('settings', () => sql.fragment`'user.user_settings'`)
```
Allows for
```
where: {
settings: {
notifications: true,
theme: 'dark',
nested: {
value: 'something'
}
}
}
```
* */
addJsonContainsFilter: <TKey extends Exclude<string, TFilterKey>>(name: TKey | TKey[], mapper?: SqlFragment | ((table: {
[x in TAliases]: IdentifierSqlToken;
} & {
[x: string]: IdentifierSqlToken;
}, value?: any, allFilters?: TFilter, ctx?: any) => SqlFragment)) => BuildView<TFilter & {
[x in TKey]?: Parameters<typeof jsonbContainsFilter>[0];
}, keyof TFilter | TKey, TAliases, TColumns>;
/**
* Allows filtering by date operators, e.g. "greater than", "less than" etc.
* */
addDateFilter: <TKey extends Exclude<string, TFilterKey>>(name: TKey | TKey[], mapper?: SqlFragment | ((table: {
[x in TAliases]: IdentifierSqlToken;
} & {
[x: string]: IdentifierSqlToken;
}, value?: z.infer<typeof dateFilterType>, allFilters?: TFilter, ctx?: any) => SqlFragment)) => BuildView<TFilter & {
[x in TKey]?: z.infer<typeof dateFilterType>;
}, keyof TFilter | TKey, TAliases, TColumns>;
/**
* Loads data from the view
* ```
* const data = await usersView
* .options({ db }).load({
* select: sql.fragment`*`,
* where: {
* id: 1
* },
* })
* ```
* */
load: <TFragment extends SqlFragment | QuerySqlToken, TSelect extends TColumns = never, TObject = [TSelect] extends [never] ? TFragment extends QuerySqlToken<infer T> ? z.infer<T> : any : Record<TSelect, any>>(args: LoadViewParameters<TFilter, TFilterKey, TFragment, TSelect[]>) => Promise<readonly TObject[]>;
setColumns: <TNewColumns extends string = never>(columns: ({
[x in TNewColumns]: SqlFragment;
}) | ArrayLike<TNewColumns>) => BuildView<TFilter, TFilterKey, TAliases, TColumns | TNewColumns>;
/**
* Sets the context for the view. This context can be used in various parts of the view lifecycle, such as in filters or constraints.
* @param ctx - The context object. Each key-value pair in the object sets a context variable.
* @returns The updated BuildView instance with the new context.
* */
context: <TContext extends Record<string, any>>(ctx?: TContext) => BuildView<TFilter, TFilterKey, TAliases, TColumns>;
/**
* Sets options for the view. Options can configure various aspects of how the view operates.
* @param opts - The options object. Each key-value pair in the object sets an option.
* @returns The updated BuildView instance with the new options.
* */
options: <TOptions extends Record<string, any>>(opts?: TOptions) => BuildView<TFilter, TFilterKey, TAliases, TColumns>;
/**
* Allows preprocessing the filters before they are interpreted
* */
setFilterPreprocess: (preprocess: (filters: TFilter, context: any) => Promise<TFilter> | TFilter) => BuildView<TFilter, TFilterKey, TAliases, TColumns>;
/**
* Sets table aliases. By default there's a `_main` alias for the main table that's referenced in the FROM fragment.
*
* These aliases can then be used in some of the filters, e.g.
* ```ts
* buildView`FROM users`
* .addStringFilter('name', (table) => sql.fragment`COALESCE(${table._main}.first_name, ${table._main}.last_name)`)
* ```
*
* would be translated to `COALESCE(users.first_name, users.last_name)`
*
* because `users` is the main table that's referred in the FROM clause.
* */
setTableAliases: <TNewAliases extends string>(table: Record<TNewAliases, string | IdentifierSqlToken>) => BuildView<TFilter, TFilterKey, TAliases | TNewAliases, TColumns>;
/**
* Allows filtering by boolean operators, e.g. "is true", "is false", "is null", etc.
* @param field - The name of the filter - Can be a nested field, e.g. "user.name"
* @param mapper - Optional if you want to use a different column name than the filter name
* @returns
* */
addBooleanFilter: <TKey extends Exclude<string, TFilterKey>>(name: TKey | TKey[], mapper?: SqlFragment | ((table: {
[x in TAliases]: IdentifierSqlToken;
} & {
[x: string]: IdentifierSqlToken;
}, value?: boolean, allFilters?: TFilter, ctx?: any) => SqlFragment), falseFragment?: SqlFragment) => BuildView<TFilter & {
[x in TKey]?: boolean;
}, keyof TFilter | TKey, TAliases, TColumns>;
/**
* Allows filtering by single or multiple string values
* And returns all rows where the value is in the array
* */
addInArrayFilter: <TKey extends Exclude<string, TFilterKey>, TType extends "text" | "numeric" | "integer" | "bigint" = never, TValue = [TType] extends [never] ? string : TType extends "numeric" | "integer" | "bigint" ? number : string>(name: TKey | TKey[], mapper?: SqlFragment | ((table: {
[x in TAliases]: IdentifierSqlToken;
} & {
[x: string]: IdentifierSqlToken;
}, value?: TValue | TValue[] | null, allFilters?: TFilter, ctx?: any) => SqlFragment), type?: TType) => BuildView<TFilter & {
[x in TKey]?: TValue | TValue[] | null;
}, keyof TFilter | TKey, TAliases, TColumns>;
/**
* Use this to add a generic filter, that returns a SQL fragment
* This filter won't be applied if the value is null or undefined
* */
addGenericFilter: <TKey extends Exclude<string, TFilterKey>, TNewFilter>(name: TKey, interpret: (filter: TNewFilter, allFilters: TFilter & {
TKey: TNewFilter;
}, context: any) => Promise<SqlFragment | null | undefined | false> | SqlFragment | null | undefined | false) => BuildView<TFilter & {
[x in TKey]?: TNewFilter;
}, keyof TFilter | TKey, TAliases, TColumns>;
/**
* Returns the SQL query
* @param args - The arguments to filter by
* @returns - The SQL query fragment
* */
getWhereConditions(args: {
where?: RecursiveFilterConditions<{
[x in TFilterKey]?: TFilter[x];
}>;
ctx?: any;
options?: FilterOptions;
}): Promise<SqlFragment[]>;
getWhereFragment(args: {
where?: RecursiveFilterConditions<{
[x in TFilterKey]?: TFilter[x];
}>;
ctx?: any;
options?: FilterOptions;
}): Promise<FragmentSqlToken>;
setConstraints: (constraints: (ctx: any) => PromiseOrValue<SqlFragment | SqlFragment[] | null | undefined>) => BuildView<TFilter, TFilterKey, TAliases>;
getFromFragment(ctx: Record<string, any>): FragmentSqlToken;
/**
* Returns all filters that have been added to the view
* @param options - Options for configuring the filters
*/
getFilters<TInclude extends Extract<TFilterKey, string> | `${string}*` = never, TExclude extends Extract<TFilterKey, string> | `${string}*` = never, TRealInclude extends Extract<TFilterKey, string> = TInclude extends `${infer K}*` ? Extract<TFilterKey, `${K}${string}`> : Extract<TInclude, Extract<TFilterKey, string>>, TRealExclude extends Extract<TFilterKey, string> = TExclude extends `${infer K}*` ? Extract<TFilterKey, `${K}${string}`> : Extract<TExclude, Extract<TFilterKey, string>>, TPrefix extends string = "", TRealPrefix extends string = TPrefix extends `${string}.` ? TPrefix : `${TPrefix}.`>(options?: {
table?: TPrefix;
include?: readonly TInclude[];
exclude?: readonly TExclude[];
}): {
[x in TFilterKey extends TRealExclude ? never : [TRealInclude] extends [never] ? TFilterKey extends `${TRealPrefix}${string}` ? TFilterKey : `${TRealPrefix}${Extract<TFilterKey, string>}` : TFilterKey extends `${TRealPrefix}${string}` ? Extract<TFilterKey, TRealInclude> : `${TRealPrefix}${Extract<TFilterKey, TRealInclude>}`]?: (filter: TFilter[x extends `${TRealPrefix}${infer K}` ? K extends TFilterKey ? K : x : x], allFilters: any, context: any) => Promise<SqlFragment | null | undefined | false> | SqlFragment | null | undefined | false;
};
} & SqlFragment;
declare type FilterOptions = {
orEnabled?: boolean;
/** If true, auth constraints aren't considered. Only use if you're already adding them in query loaders */
bypassConstraints?: boolean;
};
export declare const buildView: (parts: readonly string[], ...values: readonly (ValueExpression | ((ctx: Record<string, any>) => ValueExpression))[]) => BuildView<Record<never, any>, never, "_main", never>;
export declare type RecursiveFilterConditions<TFilter, TDisabled extends "AND" | "OR" | "NOT" = never> = TFilter & Omit<{
AND?: RecursiveFilterConditions<TFilter>[];
OR?: RecursiveFilterConditions<TFilter>[];
NOT?: RecursiveFilterConditions<TFilter>;
}, TDisabled>;
export {};