diff --git a/src/ParseQuery.ts b/src/ParseQuery.ts index 39cdfaa9d..4d0155f09 100644 --- a/src/ParseQuery.ts +++ b/src/ParseQuery.ts @@ -35,6 +35,10 @@ interface FullTextQueryOptions { diacriticSensitive?: boolean; } +interface SearchOptions { + index?: string; +} + export interface QueryJSON { where: WhereClause; watch?: string; @@ -1585,6 +1589,49 @@ class ParseQuery { return this._addCondition(key, '$text', { $search: fullOptions }); } + /* + * Triggers a MongoDb Atlas Text Search + * + * @param {string} value The string to search + * @param {string[]} path The fields to search + * @param {object} options (Optional) + * @param {string} options.index The index to search + * @returns {Promise} Returns a promise that will be resolved with the results + * of the search + * + */ + + async search(value: string, path: string[], options?: SearchOptions): Promise { + if (!value) { + throw new Error('A search term is required.'); + } + + if (typeof value !== 'string') { + throw new Error('The value being searched for must be a string.'); + } + + const controller = CoreManager.getQueryController(); + const params = { + $search: { + index: options?.index || 'default', + text: { + path, + query: value, + }, + }, + }; + + const searchOptions: { sessionToken?: string; useMasterKey: boolean } = { + useMasterKey: true, + }; + const results = await controller.aggregate(this.className, params, searchOptions); + return ( + results.results?.map(result => + ParseObject.fromJSON({ className: this.className, ...result }) + ) || [] + ); + } + /** * Method to sort the full text search by text score * diff --git a/src/__tests__/ParseQuery-test.js b/src/__tests__/ParseQuery-test.js index 5cad3c1fb..947a84dfa 100644 --- a/src/__tests__/ParseQuery-test.js +++ b/src/__tests__/ParseQuery-test.js @@ -2709,6 +2709,62 @@ describe('ParseQuery', () => { }); }); + it('can issue a search query', async () => { + CoreManager.setQueryController({ + find() {}, + aggregate(className, params, options) { + expect(className).toBe('Item'); + expect(params).toEqual({ + $search: { + index: 'searchIndex', + text: { + path: ['name'], + query: 'searchTerm', + }, + }, + }); + expect(options.useMasterKey).toEqual(true); + return Promise.resolve({ + results: [ + { + objectId: 'I55', + foo: 'bar', + }, + ], + }); + }, + }); + const q = new ParseQuery('Item'); + const obj = await q.search('searchTerm', ['name'], { index: 'searchIndex' }); + expect(obj[0].id).toBe('I55'); + expect(obj[0].get('foo')).toBe('bar'); + }); + + it('search term is required', async () => { + const q = new ParseQuery('Item'); + await expect(q.search()).rejects.toThrow('A search term is required.'); + }); + + it('search term must be a string', async () => { + const q = new ParseQuery('Item'); + await expect(q.search(123)).rejects.toThrow('The value being searched for must be a string.'); + }); + + it('search can return an empty array if no results', async () => { + CoreManager.setQueryController({ + find() {}, + aggregate() { + return Promise.resolve({ + results: null, + }); + }, + }); + + const q = new ParseQuery('Item'); + const results = await q.search('searchTerm', ['name'], { index: 'searchIndex' }); + expect(results).toEqual([]); + }); + it('aggregate query array pipeline with equalTo', done => { const pipeline = [{ group: { objectId: '$name' } }]; MockRESTController.request.mockImplementationOnce(() => { diff --git a/types/ParseQuery.d.ts b/types/ParseQuery.d.ts index 50bf65612..c86c6aa17 100644 --- a/types/ParseQuery.d.ts +++ b/types/ParseQuery.d.ts @@ -23,6 +23,9 @@ interface FullTextQueryOptions { caseSensitive?: boolean; diacriticSensitive?: boolean; } +interface SearchOptions { + index?: string; +} export interface QueryJSON { where: WhereClause; watch?: string; @@ -634,6 +637,7 @@ declare class ParseQuery { * @returns {Parse.Query} Returns the query, so you can chain this call. */ fullText(key: K, value: string, options?: FullTextQueryOptions): this; + search(value: string, path: string[], options?: SearchOptions): Promise; /** * Method to sort the full text search by text score *