import Schema from "../schema/schema" ; import Client , { SearchOptions } from "../client" ; import Entity from '../entity/entity' ; import Where from './where' ; import WhereAnd from './where-and' ; import WhereOr from './where-or' ; import WhereField from './where-field' ; import WhereArray from './where-array' ; import { WhereHashBoolean , WhereJsonBoolean } from './where-boolean' ; import WhereNumber from './where-number' ; import WhereString from './where-string' ; import WhereText from './where-text' ; import { HashSearchResultsConverter , JsonSearchResultsConverter } from "./results-converter" ; import { RedisError } from ".." ; /**
 * A function that takes a  Search and returns a  Search . Used in nested queries.
 *  template TEntity The type of  Entity being sought.
  export type SubSearchFunction < TEntity extends Entity > = ( search : Search < TEntity > ) => Search < TEntity > ; type AndOrConstructor = new ( left , right ) => Where ; template TEntity The type of  Entity being sought.
  export default class Search < TEntity extends Entity > { private schema : Schema < TEntity > ; private client : Client ; private rootWhere ? : Where ; internal constructor ( schema , client ) { this . schema = schema ; this . client = client ; } internal get query ( ) : string { if ( this . rootWhere === undefined ) return '*' ; return `${ this . rootWhere . toString ( ) }` ; } returns get return ( ) : Search < TEntity > { return this ; } /**
     * Returns the number of  Entity that match this query.
     *  returns async count ( ) { let searchResults = await this . callSearch ( ) ; return this . schema . dataStructure === 'JSON' ? new JsonSearchResultsConverter ( this . schema , searchResults ) . count : new HashSearchResultsConverter ( this . schema , searchResults ) . count ; } /**
     * Returns a page of  Entity that match this query.
     *  param offset The offset for where to start returning  Entity .
     *  param pageSize The number of  Entity to return.
     *  returns An array of  Entity matching the query.
      async page ( offset , count ) { let searchResults = await this . callSearch ( offset , count ) ; return this . schema . dataStructure === 'JSON' ? new JsonSearchResultsConverter ( this . schema , searchResults ) . entities : new HashSearchResultsConverter ( this . schema , searchResults ) . entities ; } /**
     * Returns all the  Entity that match this query. This method
     * makes multiple calls to Redis until all the  Entity are returned.
     * You can specify the batch size by setting the `pageSize` property on the
     * options:
     *
     * ```typescript
     * let entities = await repository.search().returnAll({ pageSize: 100 });
     * ```
     *
     *  param options param options . pageSize Number of  Entity returned per batch.
     *  returns An array of  Entity matching the query.
      async all ( options = { pageSize : 10 } ) { let entities = [ ] ; let offset = 0 ; let pageSize = options . pageSize ; while ( true ) { let foundEntities = await this . page ( offset , pageSize ) ; entities . push ( ... foundEntities ) ; if ( foundEntities . length < pageSize ) break ; offset += pageSize ; } return entities ; } /**
     * Returns only the first  Entity that matches this query.
      async first ( ) { let foundEntity = await this . page ( 0 , 1 ) ; return foundEntity [ 0 ] ?? null ; } /**
     * Alias for  Search . count .
      async returnCount ( ) { return await this . count ( ) ; } /**
     * Alias for  Search . page .
      async returnPage ( offset , count ) { return await this . page ( offset , count ) ; } /**
     * Alias for  Search . all .
      async returnAll ( options = { pageSize : 10 } ) { return await this . all ( options ) ; } /**
     *
     * Alias for (@link Search.first).
     */ async returnFirst ( ) { return await this . first ( ) ; } /**
     * Sets up a query matching a particular field. If there are multiple calls
     * to  Search . where , they are treated logically as AND.
     *  param field returns A subclass of  WhereField matching the type of the field.
      where ( field ) ; /**
     * Sets up a nested search. If there are multiple calls to  Search . where ,
     * they are treated logically as AND.
     *  param subSearchFn A function that takes a  Search and returns another  Search .
     *  returns where ( subSearchFn ) ; where ( fieldOrFn ) { return this . anyWhere ( WhereAnd , fieldOrFn ) ; } param field returns A subclass of  WhereField matching the type of the field.
      and ( field ) ; param subSearchFn A function that takes a  Search and returns another  Search .
     *  returns and ( subSearchFn ) ; and ( fieldOrFn ) { return this . anyWhere ( WhereAnd , fieldOrFn ) ; } param field returns A subclass of  WhereField matching the type of the field.
      or ( field ) ; param subSearchFn A function that takes a  Search and returns another  Search .
     *  returns or ( subSearchFn ) ; or ( fieldOrFn ) { return this . anyWhere ( WhereOr , fieldOrFn ) ; } private async callSearch ( offset = 0 , count = 0 ) { let options = { indexName : this . schema . indexName , query : this . query , offset , count } ; let searchResults ; try { searchResults = await this . client . search ( options ) ; } catch ( error ) { let message = ( error as Error ) . message ; if ( message . startsWith ( "Syntax error" ) ) { throw new RedisError ( `The query to RediSearch had a syntax error: "${ message }".\nThis is often the result of using a stop word in the query. Either change the query to not use a stop word or change the stop words in the schema definition. You can check the RediSearch source for the default stop words at: https://github.com/RediSearch/RediSearch/blob/master/src/stopwords.h.` ) ; } throw error ; } return searchResults ; } private anyWhere ( ctor , fieldOrFn ) { if ( typeof fieldOrFn === 'string' ) { return this . anyWhereForField ( ctor , fieldOrFn ) ; } else { return this . anyWhereForFunction ( ctor , fieldOrFn ) ; } } private anyWhereForField ( ctor , field ) { let where = this . createWhere ( field ) ; if ( this . rootWhere === undefined ) { this . rootWhere = where ; } else { this . rootWhere = new ctor ( this . rootWhere , where ) ; } return where ; } private anyWhereForFunction ( ctor , subSearchFn ) { let search = new Search < TEntity > ( this . schema , this . client ) ; let subSearch = subSearchFn ( search ) ; if ( subSearch . rootWhere === undefined ) { throw new Error ( "Sub-search without and root where was somehow defined." ) ; } else { if ( this . rootWhere === undefined ) { this . rootWhere = subSearch . rootWhere ; } else { this . rootWhere = new ctor ( this . rootWhere , subSearch . rootWhere ) ; } } return this ; } private createWhere ( field ) { let fieldDef = this . schema . definition [ field ] ; if ( fieldDef === undefined ) throw new Error ( `The field '${ field }' is not part of the schema.` ) ; if ( fieldDef . type === 'array' ) return new WhereArray < TEntity > ( this , field ) ; if ( fieldDef . type === 'boolean' && this . schema . dataStructure === 'HASH' ) return new WhereHashBoolean < TEntity > ( this , field ) ; if ( fieldDef . type === 'boolean' && this . schema . dataStructure === 'JSON' ) return new WhereJsonBoolean < TEntity > ( this , field ) ; if ( fieldDef . type === 'number' ) return new WhereNumber < TEntity > ( this , field ) ; if ( fieldDef . type === 'string' && fieldDef . textSearch === true ) return new WhereText < TEntity > ( this , field ) ; if ( fieldDef . type === 'string' && fieldDef . textSearch !== true ) return new WhereString < TEntity > ( this , field ) ; throw new Error ( `The field type of '${ fieldDef . type }' is not a valid field type. Valid types include 'array', 'boolean', 'number', and 'string'.` ) ; } }