UNPKG

@sergio9929/pb-query

Version:

A type-safe PocketBase query builder

379 lines (374 loc) 22.7 kB
declare const OPERATORS: { readonly equal: "="; readonly notEqual: "!="; readonly greaterThan: ">"; readonly greaterThanOrEqual: ">="; readonly lessThan: "<"; readonly lessThanOrEqual: "<="; readonly like: "~"; readonly notLike: "!~"; readonly anyEqual: "?="; readonly anyNotEqual: "?!="; readonly anyGreaterThan: "?>"; readonly anyGreaterThanOrEqual: "?>="; readonly anyLessThan: "?<"; readonly anyLessThanOrEqual: "?<="; readonly anyLike: "?~"; readonly anyNotLike: "?!~"; }; declare const DATETIME_MACROS: readonly ["@now", "@second", "@minute", "@hour", "@weekday", "@day", "@month", "@year", "@yesterday", "@tomorrow", "@todayStart", "@todayEnd", "@monthStart", "@monthEnd", "@yearStart", "@yearEnd"]; type FilterFunction = (raw: string, params?: { [key: string]: unknown; }) => string; type DatetimeMacro = (typeof DATETIME_MACROS)[number]; type DepthCounter = [1, 2, 3, 4, 5, 6, never]; type Path<T, MaxDepth extends number, K extends keyof T = keyof T, D extends number = 0> = D extends MaxDepth ? never : K extends string ? KeyPaths<T, K, MaxDepth, D> : never; type KeyPaths<T, K extends string & keyof T, MaxDepth extends number, D extends number> = T[K] extends string ? `${K}` | `${K}:lower` : T[K] extends readonly object[] ? `${K}` | `${K}:each` | `${K}:length` | `${K}.${Path<T[K][number], MaxDepth, keyof T[K][number], DepthCounter[D]>}` : T[K] extends readonly unknown[] ? `${K}` | `${K}:each` | `${K}:length` : T[K] extends Date ? `${K}` : T[K] extends object ? `${K}` | `${K}.${Path<T[K], MaxDepth, keyof T[K], DepthCounter[D]>}` | `${string}_via_${K}` | `${string}_via_${K}.${string}` : `${K}`; type PathValueHelper<T, P extends string, MaxDepth extends number, D extends number> = P extends `${infer _Prefix}_via_${infer _Suffix}` ? unknown : P extends `${infer Key}.${infer Rest}` ? Key extends keyof T ? T[Key] extends readonly (infer E)[] ? PathValue<E, Rest, MaxDepth, DepthCounter[D]> : PathValue<T[Key], Rest, MaxDepth, DepthCounter[D]> : never : P extends `${infer Key}:${infer Modifier}` ? Key extends keyof T ? HandleModifier<T[Key], Modifier> : never : P extends keyof T ? T[P] extends object[] ? string : T[P] extends unknown[] ? T[P][number] : T[P] extends Date ? T[P] | DatetimeMacro : T[P] extends object ? string : T[P] : never; type PathValue<T, P extends string, MaxDepth extends number, D extends number = 0> = D extends MaxDepth ? never : PathValueHelper<T, P, MaxDepth, D>; type HandleModifier<V, Modifier extends string> = Modifier extends 'each' ? V extends number[] ? number : string : Modifier extends 'length' ? number : Modifier extends 'lower' ? string : never; interface QueryBuilder<T, MaxDepth extends number = 6> { /** * Matches records where `key` equals `value`. * @example * pbQuery<Post>().equal('author.name', 'Alice'); // name='Alice' * // This is case-sensitive. Use the `:lower` modifier for case-insensitive matching. * pbQuery<Post>().equal('author.name:lower', 'alice'); // name:lower='alice' */ equal<P extends Path<T, MaxDepth>>(key: P, value: PathValue<T, P, MaxDepth>): RestrictedQueryBuilder<T, MaxDepth>; /** * Matches records where `key` is not equal to `value`. * @example * pbQuery<Post>().notEqual('author.name', 'Alice'); // name!='Alice' * // This is case-sensitive. Use the `:lower` modifier for case-insensitive matching. * pbQuery<Post>().notEqual('author.name:lower', 'alice'); // name:lower!='alice' */ notEqual<P extends Path<T, MaxDepth>>(key: P, value: PathValue<T, P, MaxDepth>): RestrictedQueryBuilder<T, MaxDepth>; /** * Matches records where `key` is greater than `value`. * * [PocketBase's datetime macros](https://pocketbase.io/docs/api-rules-and-filters/#-macros) could be helpful when comparing dates: `@now`, `@yesterday`, `@tomorrow`, `@todayStart`, `@todayEnd`, `@monthStart`, `@monthEnd`, `@yearStart`, `@yearEnd`, [more...](https://pocketbase.io/docs/api-rules-and-filters/#-macros) * @example * pbQuery<User>().greaterThan('age', 21); // age>21 * pbQuery<User>().greaterThan('created', new Date('2021-01-01')); // created>'2021-01-01 00:00:00.000Z' */ greaterThan<P extends Path<T, MaxDepth>>(key: P, value: PathValue<T, P, MaxDepth>): RestrictedQueryBuilder<T, MaxDepth>; /** * Matches records where `key` is greater than or equal to `value`. * * [PocketBase's datetime macros](https://pocketbase.io/docs/api-rules-and-filters/#-macros) could be helpful when comparing dates: `@now`, `@yesterday`, `@tomorrow`, `@todayStart`, `@todayEnd`, `@monthStart`, `@monthEnd`, `@yearStart`, `@yearEnd`, [more...](https://pocketbase.io/docs/api-rules-and-filters/#-macros) * @example * pbQuery<User>().greaterThanOrEqual('age', 18); // age>=18 * pbQuery<User>().greaterThanOrEqual('created', new Date('2021-01-01')); // created>='2021-01-01 00:00:00.000Z' */ greaterThanOrEqual<P extends Path<T, MaxDepth>>(key: P, value: PathValue<T, P, MaxDepth>): RestrictedQueryBuilder<T, MaxDepth>; /** * Matches records where `key` is less than `value`. * * [PocketBase's datetime macros](https://pocketbase.io/docs/api-rules-and-filters/#-macros) could be helpful when comparing dates: `@now`, `@yesterday`, `@tomorrow`, `@todayStart`, `@todayEnd`, `@monthStart`, `@monthEnd`, `@yearStart`, `@yearEnd`, [more...](https://pocketbase.io/docs/api-rules-and-filters/#-macros) * @example * pbQuery<User>().lessThan('age', 50); // age<50 * pbQuery<User>().lessThan('created', new Date('2021-01-01')); // created<'2021-01-01 00:00:00.000Z' */ lessThan<P extends Path<T, MaxDepth>>(key: P, value: PathValue<T, P, MaxDepth>): RestrictedQueryBuilder<T, MaxDepth>; /** * Matches records where `key` is less than or equal to `value`. * * [PocketBase's datetime macros](https://pocketbase.io/docs/api-rules-and-filters/#-macros) could be helpful when comparing dates: `@now`, `@yesterday`, `@tomorrow`, `@todayStart`, `@todayEnd`, `@monthStart`, `@monthEnd`, `@yearStart`, `@yearEnd`, [more...](https://pocketbase.io/docs/api-rules-and-filters/#-macros) * @example * pbQuery<User>().lessThanOrEqual('age', 65); // age<=65 * pbQuery<User>().lessThanOrEqual('created', new Date('2021-01-01')); // created<='2021-01-01 00:00:00.000Z' */ lessThanOrEqual<P extends Path<T, MaxDepth>>(key: P, value: PathValue<T, P, MaxDepth>): RestrictedQueryBuilder<T, MaxDepth>; /** * Matches records where `key` contains `value`. * * It is case-insensitive, so the `:lower` [modifier](https://pocketbase.io/docs/api-rules-and-filters/#special-identifiers-and-modifiers) is unnecessary. * * @example * // Contains * pbQuery<Post>().like('author.name', 'Joh'); // name~'Joh' / name~'%Joh%' * // If not specified, auto-wraps the value in `%` for wildcard matching. * * @example * // Starts with * pbQuery<Post>().like('author.name', 'Joh%'); // name~'Joh%' * * @example * // Ends with * pbQuery<Post>().like('author.name', '%Doe'); // name~'%Doe' */ like<P extends Path<T, MaxDepth>>(key: P, value: PathValue<T, P, MaxDepth>): RestrictedQueryBuilder<T, MaxDepth>; /** * Matches records where `key` doesn't contain `value`. * * It is case-insensitive, so the `:lower` [modifier](https://pocketbase.io/docs/api-rules-and-filters/#special-identifiers-and-modifiers) is unnecessary. * * @example * // Doesn't contain * pbQuery<Post>().notLike('author.name', 'Joh'); // name!~'Joh' / name!~'%Joh%' * // If not specified, auto-wraps the value in `%` for wildcard matching. * * @example * // Doesn't start with * pbQuery<Post>().notLike('author.name', 'Joh%'); // name!~'Joh%' * * @example * // Doesn't end with * pbQuery<Post>().notLike('author.name', '%Doe'); // name!~'%Doe' */ notLike<P extends Path<T, MaxDepth>>(key: P, value: PathValue<T, P, MaxDepth>): RestrictedQueryBuilder<T, MaxDepth>; /** * Useful for queries involving [back-relations](https://pocketbase.io/docs/working-with-relations/#back-relations), [multiple relation](https://pocketbase.io/docs/collections/#relationfield), [multiple select](https://pocketbase.io/docs/collections/#selectfield), or [multiple file](https://pocketbase.io/docs/collections/#filefield). * * Matches records where at least one of the values in the given `key` equals `value`. * @example * pbQuery<Book>().anyEqual('books_via_author.title', 'The Island'); // post_via_author.name?='The Island' * * // This is case-sensitive. Use the `:lower` modifier for case-insensitive matching. * pbQuery<Book>().anyEqual('books_via_author.title:lower', 'the island'); // post_via_author.name:lower?='the island' */ anyEqual<P extends Path<T, MaxDepth>>(key: P, value: PathValue<T, P, MaxDepth>): RestrictedQueryBuilder<T, MaxDepth>; /** * Useful for queries involving [back-relations](https://pocketbase.io/docs/working-with-relations/#back-relations), [multiple relation](https://pocketbase.io/docs/collections/#relationfield), [multiple select](https://pocketbase.io/docs/collections/#selectfield), or [multiple file](https://pocketbase.io/docs/collections/#filefield). * * Matches records where at least one of the values in the given `key` is not equal to `value`. * @example * pbQuery<Book>().anyNotEqual('books_via_author.title', 'The Island'); // post_via_author.name?!='The Island' * * // This is case-sensitive. Use the `:lower` modifier for case-insensitive matching. * pbQuery<Book>().anyNotEqual('books_via_author.title:lower', 'the island'); // post_via_author.name:lower?!='the island' */ anyNotEqual<P extends Path<T, MaxDepth>>(key: P, value: PathValue<T, P, MaxDepth>): RestrictedQueryBuilder<T, MaxDepth>; /** * Useful for queries involving [back-relations](https://pocketbase.io/docs/working-with-relations/#back-relations), [multiple relation](https://pocketbase.io/docs/collections/#relationfield), [multiple select](https://pocketbase.io/docs/collections/#selectfield), or [multiple file](https://pocketbase.io/docs/collections/#filefield). * * Matches records where at least one of the values in the given `key` is greater than `value`. * @example pbQuery<User>().anyGreaterThan('age', 21); // age?>21 */ anyGreaterThan<P extends Path<T, MaxDepth>>(key: P, value: PathValue<T, P, MaxDepth>): RestrictedQueryBuilder<T, MaxDepth>; /** * Useful for queries involving [back-relations](https://pocketbase.io/docs/working-with-relations/#back-relations), [multiple relation](https://pocketbase.io/docs/collections/#relationfield), [multiple select](https://pocketbase.io/docs/collections/#selectfield), or [multiple file](https://pocketbase.io/docs/collections/#filefield). * * Matches records where at least one of the values in the given `key` is greater than or equal to `value`. * @example pbQuery<User>().anyGreaterThanOrEqual('age', 18); // age?>=18 */ anyGreaterThanOrEqual<P extends Path<T, MaxDepth>>(key: P, value: PathValue<T, P, MaxDepth>): RestrictedQueryBuilder<T, MaxDepth>; /** * Useful for queries involving [back-relations](https://pocketbase.io/docs/working-with-relations/#back-relations), [multiple relation](https://pocketbase.io/docs/collections/#relationfield), [multiple select](https://pocketbase.io/docs/collections/#selectfield), or [multiple file](https://pocketbase.io/docs/collections/#filefield). * * Matches records where at least one of the values in the given `key` is less than `value`. * @example pbQuery<User>().anyLessThan('age', 50); // age?<50 */ anyLessThan<P extends Path<T, MaxDepth>>(key: P, value: PathValue<T, P, MaxDepth>): RestrictedQueryBuilder<T, MaxDepth>; /** * Useful for queries involving [back-relations](https://pocketbase.io/docs/working-with-relations/#back-relations), [multiple relation](https://pocketbase.io/docs/collections/#relationfield), [multiple select](https://pocketbase.io/docs/collections/#selectfield), or [multiple file](https://pocketbase.io/docs/collections/#filefield). * * Matches records where at least one of the values in the given `key` is less than or equal to `value`. * @example pbQuery<User>().anyLessThanOrEqual('age', 65); // age?<=65 */ anyLessThanOrEqual<P extends Path<T, MaxDepth>>(key: P, value: PathValue<T, P, MaxDepth>): RestrictedQueryBuilder<T, MaxDepth>; /** * Useful for queries involving [back-relations](https://pocketbase.io/docs/working-with-relations/#back-relations), [multiple relation](https://pocketbase.io/docs/collections/#relationfield), [multiple select](https://pocketbase.io/docs/collections/#selectfield), or [multiple file](https://pocketbase.io/docs/collections/#filefield). * * Matches records where at least one of the values in the given `key` contains `value`. * * It is case-insensitive, so the `:lower` [modifier](https://pocketbase.io/docs/api-rules-and-filters/#special-identifiers-and-modifiers) is unnecessary. * * @example * // Contains * pbQuery<Post>().anyLike('author.name', 'Joh'); // name?~'Joh' / name?~'%Joh%' * // If not specified, auto-wraps the value in `%` for wildcard matching. * * @example * // Starts with * pbQuery<Post>().anyLike('author.name', 'Joh%'); // name?~'Joh%' * * @example * // Ends with * pbQuery<Post>().anyLike('author.name', '%Doe'); // name?~'%Doe' */ anyLike<P extends Path<T, MaxDepth>>(key: P, value: PathValue<T, P, MaxDepth>): RestrictedQueryBuilder<T, MaxDepth>; /** * Useful for queries involving [back-relations](https://pocketbase.io/docs/working-with-relations/#back-relations), [multiple relation](https://pocketbase.io/docs/collections/#relationfield), [multiple select](https://pocketbase.io/docs/collections/#selectfield), or [multiple file](https://pocketbase.io/docs/collections/#filefield). * * Matches records where at least one of the values in the given `key` doesn't contain `value`. * * It is case-insensitive, so the `:lower` [modifier](https://pocketbase.io/docs/api-rules-and-filters/#special-identifiers-and-modifiers) is unnecessary. * * @example * // Doesn't contain * pbQuery<Post>().anyNotLike('author.name', 'Joh'); // name?!~'Joh' / name?!~'%Joh%' * // If not specified, auto-wraps the value in `%` for wildcard matching. * * @example * // Doesn't start with * pbQuery<Post>().anyNotLike('author.name', 'Joh%'); // name?!~'Joh%' * * @example * // Doesn't end with * pbQuery<Post>().anyNotLike('author.name', '%Doe'); // name?!~'%Doe' */ anyNotLike<P extends Path<T, MaxDepth>>(key: P, value: PathValue<T, P, MaxDepth>): RestrictedQueryBuilder<T, MaxDepth>; /** * **_Helper_** * * Matches records where any of the `keys` contain `value`. * * It can be used to perform a full-text search (FTS). * * It is case-insensitive, so the `:lower` [modifier](https://pocketbase.io/docs/api-rules-and-filters/#special-identifiers-and-modifiers) is unnecessary. * * @example * // Full text search * pbQuery<Post>().search(['title', 'content', 'tags', 'author.name', 'author.surname'], 'Football'); // (title~'Football' || content~'Football' || tags~'Football' || author.name~'Football' || author.surname~'Football') * * @example * // Contains * pbQuery<User>().search(['name', 'surname'], 'Joh'); // (name~'Joh' || surname~'Joh') / (name~'%Joh%' || surname~'%Joh%') * // If not specified, auto-wraps the value in `%` for wildcard matching. * * @example * // Starts with * pbQuery<User>().search(['name', 'surname'], 'Joh%'); // (name~'Joh%' || surname~'Joh%') * * @example * // Ends with * pbQuery<User>().search(['name', 'surname'], '%Doe'); // (name~'%Doe' || surname~'%Doe') */ search<P extends Path<T, MaxDepth>>(keys: P[], value: PathValue<T, P, MaxDepth>): RestrictedQueryBuilder<T, MaxDepth>; /** * **_Helper_** * * Matches records where `key` is in `values`. * @example pbQuery<Post>().in('id', ['id_1', 'id_2', 'id_3']); // (id='id_1' || id='id_2' || id='id_3') */ in<P extends Path<T, MaxDepth>>(key: P, values: PathValue<T, P, MaxDepth>[]): RestrictedQueryBuilder<T, MaxDepth>; /** * **_Helper_** * * Matches records where `key` is not in `values`. * @example pbQuery<User>().notIn('age', [18, 21, 30]); // (age!=18 && age!=21 && age!=30) */ notIn<P extends Path<T, MaxDepth>>(key: P, values: PathValue<T, P, MaxDepth>[]): RestrictedQueryBuilder<T, MaxDepth>; /** * **_Helper_** * * Matches records where `key` is between `from` and `to`. * * [PocketBase's datetime macros](https://pocketbase.io/docs/api-rules-and-filters/#-macros) could be helpful when comparing dates: `@now`, `@yesterday`, `@tomorrow`, `@todayStart`, `@todayEnd`, `@monthStart`, `@monthEnd`, `@yearStart`, `@yearEnd`, [more...](https://pocketbase.io/docs/api-rules-and-filters/#-macros) * @example * pbQuery<User>().between('age', 18, 30); // (age>=18 && age<=30) * pbQuery<User>().between('created', new Date('2021-01-01'), '@now'); // (created>='2021-01-01 00:00:00.000Z' && created<=@now) */ between<P extends Path<T, MaxDepth>>(key: P, from: PathValue<T, P, MaxDepth>, to: PathValue<T, P, MaxDepth>): RestrictedQueryBuilder<T, MaxDepth>; /** * **_Helper_** * * Matches records where `key` is not between `from` and `to`. * * [PocketBase's datetime macros](https://pocketbase.io/docs/api-rules-and-filters/#-macros) could be helpful when comparing dates: `@now`, `@yesterday`, `@tomorrow`, `@todayStart`, `@todayEnd`, `@monthStart`, `@monthEnd`, `@yearStart`, `@yearEnd`, [more...](https://pocketbase.io/docs/api-rules-and-filters/#-macros) * @example * pbQuery<User>().notBetween('age', 18, 30); // (age<18 || age>30) * pbQuery<User>().notBetween('created', new Date('2021-01-01'), '@yesterday'); // (created<'2021-01-01 00:00:00.000Z' || created>@yesterday) */ notBetween<P extends Path<T, MaxDepth>>(key: P, from: PathValue<T, P, MaxDepth>, to: PathValue<T, P, MaxDepth>): RestrictedQueryBuilder<T, MaxDepth>; /** * **_Helper_** * * Matches records where `key` is null. * @example pbQuery<User>().isNull('name'); // name='' */ isNull<P extends Path<T, MaxDepth>>(key: P): RestrictedQueryBuilder<T, MaxDepth>; /** * **_Helper_** * * Matches records where `key` is not null. * @example pbQuery<User>().isNotNull('name'); // name!='' */ isNotNull<P extends Path<T, MaxDepth>>(key: P): RestrictedQueryBuilder<T, MaxDepth>; /** * **_Helper_** * * Executes a custom query. * * This helper is safe to use with `pb.filter()`, but we recommend using it as a last resort. * * If you have a special use case that might be useful to other developers, consider [opening an issue](https://github.com/sergio9929/pb-query/issues), and we may implement it as a new _helper_ in the future. * * @example * pbQuery<User>().custom('age > 21'); // age > 21 * * // We recommend using Pocketbase's native `pb.filter()` function * pbQuery<User>().custom(pb.filter('age > {:age}', { age: 21 })); // age > 21 */ custom(raw: string): RestrictedQueryBuilder<T, MaxDepth>; /** * Creates a logical group. * @example pbQuery<Post>().group((q) => q.equal('status', 'active').or().equal('status', 'inactive')); // (status~'active' || status~'inactive') */ group(callback: (q: QueryBuilder<T, MaxDepth>) => RestrictedQueryBuilder<T, MaxDepth>): RestrictedQueryBuilder<T, MaxDepth>; /** * Returns the query string and values. * @example * // We recommend using Pocketbase's native `pb.filter()` function * const query = pbQuery<User>().equal('name', 'Alice').build(pb.filter); * * // You can also filter it later * const query = pbQuery<User>().equal('name', 'Alice').build(); * console.log(query.raw); // name={:name1} * console.log(query.values); // { name: 'Alice' } * console.log(pb.filter(query.raw, query.values)); // name='Alice' */ build(): { raw: string; values: Record<string, unknown>; }; build(filter: FilterFunction): string; build(filter?: FilterFunction): { raw: string; values: Record<string, unknown>; } | string; } interface RestrictedQueryBuilder<T, MaxDepth extends number = 6> { /** * Combines the previous and the next conditions with an `and` logical operator. * @example pbQuery<User>().equal('name', 'Alice').and().equal('role', 'admin'); // name='Alice' && role='admin' */ and(): Omit<QueryBuilder<T, MaxDepth>, 'build'>; /** * Combines the previous and the next conditions with an `or` logical operator. * @example pbQuery<User>().equal('name', 'Alice').or().equal('name', 'Bob'); // name='Alice' || name='Bob' */ or(): Omit<QueryBuilder<T, MaxDepth>, 'build'>; /** * Returns the query string and values. * @example * // We recommend using Pocketbase's native `pb.filter()` function * const query = pbQuery<User>().equal('name', 'Alice').build(pb.filter); * * // You can also filter it later * const query = pbQuery<User>().equal('name', 'Alice').build(); * console.log(query.raw); // name={:name1} * console.log(query.values); // { name: 'Alice' } * console.log(pb.filter(query.raw, query.values)); // name='Alice' */ build(): { raw: string; values: Record<string, unknown>; }; build(filter: FilterFunction): string; build(filter?: FilterFunction): { raw: string; values: Record<string, unknown>; } | string; } declare function pbQuery<T, MaxDepth extends number = 6>(): QueryBuilder<T, MaxDepth>; /** * We expose a filter function, but we recommend using the native `pb.filter()` function instead. * @deprecated Use native `pb.filter()`, not this. */ declare function filter(raw: string, params?: { [key: string]: unknown; }): string; export { type FilterFunction, OPERATORS, type QueryBuilder, type RestrictedQueryBuilder, filter, pbQuery };