UNPKG

slonik-trpc

Version:
219 lines (218 loc) 12.9 kB
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 {};