import assert from 'assert' ; import { AnyObject } from './types' ; const nonWhereFields = [ 'fields' , 'order' , 'limit' , 'skip' , 'offset' , 'include' , ] ; const filterFields = [ 'where' , ... nonWhereFields ] ; /**
 * Operators for where clauses
 */ export type Operators = 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'inq' | 'nin' | 'between' | 'exists' | 'and' | 'or' | 'like' | 'nlike' | 'ilike' | 'nilike' | 'regexp' ; /**
 * Matching predicate comparison
 */ export type PredicateComparison < PT > = { eq ? : PT ; neq ? : PT ; gt ? : PT ; gte ? : PT ; lt ? : PT ; lte ? : PT ; inq ? : PT [ ] ; nin ? : PT [ ] ; between ? : [ PT , PT ] ; exists ? : boolean ; like ? : PT ; nlike ? : PT ; ilike ? : PT ; nilike ? : PT ; regexp ? : string | RegExp ; } ; /**
 * Value types for `{propertyName: value}`
 */ export type ShortHandEqualType = string | number | boolean | Date ; /**
 * Key types of a given model, excluding operators
 */ export type KeyOf < MT extends object > = Exclude < Extract < keyof MT , string > , Operators > ; example export type Condition < MT extends object > = { [ P in KeyOf < MT > ] ? : PredicateComparison < MT [ P ] > | ( MT [ P ] & ShortHandEqualType ) ; } ; example export type Where < MT extends object = AnyObject > = Condition < MT > | AndClause < MT > | OrClause < MT > ; example export interface AndClause < MT extends object > { and : Where < MT > [ ] ; } example export interface OrClause < MT extends object > { or : Where < MT > [ ] ; } /**
 * Order by direction
 */ export type Direction = 'ASC' | 'DESC' ; /**
 * Order by
 *
 * Example:
 * `{afieldname: 'ASC'}`
 */ export type Order < MT = AnyObject > = { [ P in keyof MT ] : Direction ; } ; /**
 * Selection of fields
 *
 * Example:
 * `{afieldname: true}`
 */ export type Fields < MT = AnyObject > = { [ P in keyof MT ] ? : boolean ; } | Extract < keyof MT , string > [ ] ; /**
 * Inclusion of related items
 *
 * Note: scope means filter on related items
 *
 * Example:
 * `{relation: 'aRelationName', scope: {<AFilterObject>}}`
 */ export interface Inclusion { relation : string ; scope ? : Filter < AnyObject > & { /**
         * Global maximum number of inclusions. This is just to remain backward
         * compatability. This totalLimit props takes precedence over limit
         * https://github.com/loopbackio/loopback-next/issues/6832
         */ totalLimit ? : number ; } ; } /**
 * Query filter object
 */ export interface Filter < MT extends object = AnyObject > { /**
     * The matching criteria
     */ where ? : Where < MT > ; /**
     * To include/exclude fields
     */ fields ? : Fields < MT > ; /**
     * Sorting order for matched entities. Each item should be formatted as
     * `fieldName ASC` or `fieldName DESC`.
     * For example: `['f1 ASC', 'f2 DESC', 'f3 ASC']`.
     *
     * We might want to use `Order` in the future. Keep it as `string[]` for now
     * for compatibility with LoopBack 3.x.
     */ order ? : string [ ] ; /**
     * Maximum number of entities
     */ limit ? : number ; /**
     * Skip N number of entities
     */ skip ? : number ; /**
     * Offset N number of entities. An alias for `skip`
     */ offset ? : number ; /**
     * To include related objects
     */ include ? : InclusionFilter [ ] ; } /**
 * Inclusion filter type e.g. 'property', {relation: 'property'}
 */ export type InclusionFilter = string | Inclusion ; /**
 * Filter without `where` property
 */ export type FilterExcludingWhere < MT extends object = AnyObject > = Omit < Filter < MT > , 'where' > ; param candidate export function isFilter < MT extends object > ( candidate ) { if ( typeof candidate !== 'object' ) return false ; for ( const key in candidate ) { if ( ! filterFields . includes ( key ) ) { return false ; } } return true ; } example export class WhereBuilder < MT extends object = AnyObject > { where : Where < MT > ; constructor ( w ? ) { this . where = w ?? { } ; } private add ( w ) { for ( const k of Object . keys ( w ) ) { if ( k in this . where ) { const where = { and : [ this . where , w ] } ; this . where = where ; return this ; } } this . where = Object . assign ( this . where , w ) ; return this ; } deprecated param clause cast ( clause ) { return clause ; } param w and ( ... w ) { let clauses = [ ] ; w . forEach ( where => { clauses = clauses . concat ( Array . isArray ( where ) ? where : [ where ] ) ; } ) ; return this . add ( { and : clauses } ) ; } param w or ( ... w ) { let clauses = [ ] ; w . forEach ( where => { clauses = clauses . concat ( Array . isArray ( where ) ? where : [ where ] ) ; } ) ; return this . add ( { or : clauses } ) ; } param key param val eq < K extends KeyOf < MT > > ( key , val ) { const w = { } ; w [ key ] = val ; return this . add ( w ) ; } param key param val neq < K extends KeyOf < MT > > ( key , val ) { const w = { } ; w [ key ] = { neq : val } ; return this . add ( w ) ; } param key param val gt < K extends KeyOf < MT > > ( key , val ) { const w = { } ; w [ key ] = { gt : val } ; return this . add ( w ) ; } param key param val gte < K extends KeyOf < MT > > ( key , val ) { const w = { } ; w [ key ] = { gte : val } ; return this . add ( w ) ; } param key param val lt < K extends KeyOf < MT > > ( key , val ) { const w = { } ; w [ key ] = { lt : val } ; return this . add ( w ) ; } param key param val lte < K extends KeyOf < MT > > ( key , val ) { const w = { } ; w [ key ] = { lte : val } ; return this . add ( w ) ; } param key param val inq < K extends KeyOf < MT > > ( key , val ) { const w = { } ; w [ key ] = { inq : val } ; return this . add ( w ) ; } param key param val nin < K extends KeyOf < MT > > ( key , val ) { const w = { } ; w [ key ] = { nin : val } ; return this . add ( w ) ; } param key param val1 param val2 between < K extends KeyOf < MT > > ( key , val1 , val2 ) { const w = { } ; w [ key ] = { between : [ val1 , val2 ] } ; return this . add ( w ) ; } param key param val exists < K extends KeyOf < MT > > ( key , val ? ) { const w = { } ; w [ key ] = { exists : ! ! val || val == null } ; return this . add ( w ) ; } param where impose ( where ) { if ( ! this . where ) { this . where = where || { } ; } else { this . add ( where ) ; } return this ; } param key param val like < K extends KeyOf < MT > > ( key , val ) { const w = { } ; w [ key ] = { like : val } ; return this . add ( w ) ; } param key param val nlike < K extends KeyOf < MT > > ( key , val ) { const w = { } ; w [ key ] = { nlike : val } ; return this . add ( w ) ; } param key param val ilike < K extends KeyOf < MT > > ( key , val ) { const w = { } ; w [ key ] = { ilike : val } ; return this . add ( w ) ; } param key param val nilike < K extends KeyOf < MT > > ( key , val ) { const w = { } ; w [ key ] = { nilike : val } ; return this . add ( w ) ; } param key param val regexp < K extends KeyOf < MT > > ( key , val ) { const w = { } ; w [ key ] = { regexp : val } ; return this . add ( w ) ; } /**
     * Get the where object
     */ build ( ) { return this . where ; } } example export class FilterBuilder < MT extends object = AnyObject > { filter : Filter < MT > ; constructor ( f ? ) { this . filter = f ?? { } ; } param limit limit ( limit ) { assert ( limit >= 1 , `Limit ${ limit } must a positive number` ) ; this . filter . limit = limit ; return this ; } param offset offset ( offset ) { this . filter . offset = offset ; return this ; } param skip skip ( skip ) { return this . offset ( skip ) ; } param f fields ( ... f ) { if ( ! this . filter . fields ) { this . filter . fields = { } ; } else if ( Array . isArray ( this . filter . fields ) ) { this . filter . fields = this . filter . fields . reduce ( ( prev , current ) => ( { ... prev , [ current ] : true } ) , { } ) ; } const fields = this . filter . fields ; for ( const field of f ) { if ( Array . isArray ( field ) ) { field . forEach ( i => ( fields [ i ] = true ) ) ; } else if ( typeof field === 'string' ) { fields [ field ] = true ; } else { Object . assign ( fields , field ) ; } } return this ; } private validateOrder ( order ) { assert ( order . match ( /^[^\s]+( (ASC|DESC))?$/ ) , 'Invalid order: ' + order ) ; } param o order ( ... o ) { if ( ! this . filter . order ) { this . filter . order = [ ] ; } o . forEach ( order => { if ( typeof order === 'string' ) { this . validateOrder ( order ) ; if ( ! order . endsWith ( ' ASC' ) && ! order . endsWith ( ' DESC' ) ) { order = order + ' ASC' ; } this . filter . order ! . push ( order ) ; return this ; } if ( Array . isArray ( order ) ) { order . forEach ( this . validateOrder ) ; order = order . map ( i => { if ( ! i . endsWith ( ' ASC' ) && ! i . endsWith ( ' DESC' ) ) { i = i + ' ASC' ; } return i ; } ) ; this . filter . order = this . filter . order ! . concat ( order ) ; return this ; } for ( const i in order ) { this . filter . order ! . push ( `${ i } ${ order [ i ] }` ) ; } } ) ; return this ; } param i include ( ... i ) { if ( this . filter . include == null ) { this . filter . include = [ ] ; } for ( const include of i ) { if ( typeof include === 'string' ) { this . filter . include . push ( { relation : include } ) ; } else if ( Array . isArray ( include ) ) { for ( const inc of include ) this . filter . include . push ( { relation : inc } ) ; } else { this . filter . include ! . push ( include ) ; } } return this ; } param w where ( w ) { this . filter . where = w ; return this ; } param constraint impose ( constraint ) { if ( ! this . filter ) { if ( ! isFilter ( constraint ) ) { constraint = { where : constraint } ; } this . filter = ( constraint as Filter < MT > ) || { } ; } else { if ( isFilter ( constraint ) ) { for ( const key of Object . keys ( constraint ) ) { if ( nonWhereFields . includes ( key ) ) { throw new Error ( 'merging strategy for selection, pagination, and sorting not implemented' ) ; } } } this . filter . where = isFilter ( constraint ) ? new WhereBuilder ( this . filter . where ) . impose ( constraint . where ! ) . build ( ) : new WhereBuilder ( this . filter . where ) . impose ( constraint ) . build ( ) ; } return this ; } /**
     * Return the filter object
     */ build ( ) { return this . filter ; } } param value param path function getDeepProperty ( value , path ) { const props = path . split ( '.' ) ; for ( const p of props ) { value = value [ p ] ; if ( value == null ) { return null ; } } return value ; } export function filterTemplate ( strings , ... keys ) { return function filter ( ctx ) { const tokens = [ strings [ 0 ] ] ; keys . forEach ( ( key , i ) => { if ( typeof key === 'object' || typeof key === 'boolean' || typeof key === 'number' ) { tokens . push ( JSON . stringify ( key ) , strings [ i + 1 ] ) ; return ; } const value = getDeepProperty ( ctx , key ) ; tokens . push ( JSON . stringify ( value ) , strings [ i + 1 ] ) ; } ) ; const result = tokens . join ( '' ) ; try { return JSON . parse ( result ) ; } catch ( e ) { throw new Error ( 'Invalid JSON: ' + result ) ; } } ; }