diff --git a/CHANGELOG.md b/CHANGELOG.md index a432d12..5d98f63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +# 2.0.0 +Extract web-worker search utilty into its own NPM package, [js-worker-search](https://github.com/bvaughn/js-worker-search). Moved web-worker support detection (previously managed by `CapabilitiesBasedSearchApi`) into that module as well to simplify the redux-search interface. + +### Upgrade path (1.x to 2.x) +If you were previously importing `CapabilitiesBasedSearchApi` or `WorkerSearchApi` directly you should now just import `SearchApi`. It will handle auto-detecting web-worker support and use the correct implementation under the hood. + +`SearchUtility` will now longer be exported by this package. Import it from [js-worker-search](https://github.com/bvaughn/js-worker-search) instead. + # 1.0.0 Result selector created by `getSearchSelectors` automatically filters the result list to ensure that all results are all present in the resource collection. (See issue #29 for more background information.) diff --git a/README.md b/README.md index c6f9e94..abee092 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ redux-search [![Circle CI][circle-image]][circle-url] -Higher-order Redux library for searching collections of objects. Search algorithms based on [js-search](https://github.com/bvaughn/js-search) but with added web-worker support for better performance. +Higher-order Redux library for searching collections of objects. Search algorithms powered by [js-worker-search](https://github.com/bvaughn/js-worker-search). Check out the live demo at [treasure-data.github.io/redux-search](http://treasure-data.github.io/redux-search/) @@ -35,7 +35,7 @@ redux-search watches the store for changes to searchable collections and automat ```javascript import { applyMiddleware, combineReducers, compose, createStore } from 'redux' -import { reducer as searchReducer, reduxSearch } from '../src/index' +import { reducer as searchReducer, reduxSearch } from 'redux-search' // Configure reducer to store state at state.search // You can store it elsewhere but you will need to supply your own :searchStateSelector diff --git a/docs/README.md b/docs/README.md index 1e87795..c468374 100644 --- a/docs/README.md +++ b/docs/README.md @@ -7,10 +7,7 @@ redux-search provides the following named exports: * [`getSearchSelectors`](#getsearchselectors-filterfunction-resourcename-resourceselector-searchstateselector-) * [`reducer`](#reducer) * [`reduxSearch`](#reduxsearch-resourceindexes-resourceselector-searchapi-searchstateselector-) -* [`CapabilitiesBasedSearchApi`](#searchapi--workersearchapicapabilitiesbasedsearchapi) -* [`SearchApi`](#searchapi--workersearchapi) -* [`SearchUtility`](#searchutility) -* [`WorkerSearchApi`](#searchapi--workersearchapi) +* [`SearchApi`](#searchapi--searchapi) ### `createSearchAction(resourceName)` Factory function that creates Redux search actions. This function requires a single parameter (a `resourceName` string) that identifies a searchable resource. For example: @@ -102,42 +99,19 @@ Observable Search API. Should only be overridden for testing purposes. Refer to ##### searchStateSelector: Selects the search sub-state within the state store. A default implementation is provided. Override only if you add `searchReducer()` to the store somewhere other than `state.search`. -### `SearchUtility` +### `SearchApi` ++The search API is an observable that manages communication between the redux-search middleware and the underlying search utility. It maps resource names to search indicies and coordinates searches. It supports both single-threaded and web-worker modes and will determine which to use automatically based on the capabilities of the current environment. -Forked from [JS search](github.com/bvaughn/js-search), this utility builds a search index and runs actual searches. One instance of `SearchUtility` is created for each searchable resource type in redux-search. Depending on how [`reduxSearch`](#reduxsearch-resourceindexes-resourceselector-searchapi-searchstateselector-) has been configured this instance may live in the UI thread or in a web-worker. - -In most cases this utility does not need to be used directly. redux-search will automatically create and configure searchable instances in response to store changes. The utility is exposed though in case custom search functionality is needed that falls outside of the primary scope of redux-search. In that case you can build and run your own searches using the following methods: - -##### `indexDocument (uid, text)` -Adds or updates a uid in the search index and associates it with the specified text. Note that at this time uids can only be added or updated in the index, not removed. - -* **uid**: Uniquely identifies a searchable object -* **text**: Searchable text to associate with the uid - -##### `search(query)` -Searches the current index for the specified query text. Only uids matching all of the words within the text will be accepted. If an empty query string is provided all indexed uids will be returned. - -Document searches are case-insensitive (e.g. "search" will match "Search"). Document searches use substring matching (e.g. "na" and "me" will both match "name"). - -* **query**: Searchable query text - -This method will return an array of uids. - -### `SearchApi` / `WorkerSearchApi` / `CapabilitiesBasedSearchApi` -+The search API is an observable that manages communication between the redux-search middleware and the underlying search utility. It maps resource names to search indicies and coordinates searches. Both a single-threaded implementation (`SearchApi`) and a web-worker implementation (`WorkerSearchApi`) are provided. - -By default a capabilities-based search API is used (`CapabilitiesBasedSearchApi`) that auto-detects web worker support and uses the best implementation for the current environment. You can override this behavior with `reduxSearch()` for testing purposes like so: +You can override this API with `reduxSearch()` (for testing purposes) like so: ```javascript -import { SearchApi } from './SearchApi' - reduxSearch({ // Configure :resourceIndexes and :resourceSelector here and then... - searchApi = new SearchApi() + searchApi = new StubSearchApi() }) ``` -You may also want to override this value for unit testing purposes. To do so you will need to implement the following methods: +In the above example `StubSearchApi` will need to implement the following methods: ##### `subscribe (onNext, onError)` Subscribe to Search events. Subscribers will be notified each time a Search is performed. diff --git a/package.json b/package.json index f8ec28d..04a236e 100644 --- a/package.json +++ b/package.json @@ -59,9 +59,11 @@ "gh-pages": "^0.8.0", "html-webpack-plugin": "^1.7.0", "immutable": "^3.7.5", + "js-worker-search": "file:///Users/brianvaughn/Documents/git/js-worker-search/js-worker-search-1.0.0.tgz", "keymirror": "^0.1.1", "react": "^0.14.3", "react-dom": "^0.14.3", + "react-highlight-words": "^0.1.1", "react-redux": "^4.0.0", "react-virtualized": "^2.1.0", "redux-thunk": "^1.0.0", @@ -73,12 +75,10 @@ "tape": "^4.2.2", "watch": "^0.16.0", "webpack": "^1.12.9", - "webpack-dev-server": "^1.14.0", - "worker-loader": "^0.7.0" + "webpack-dev-server": "^1.14.0" }, "dependencies": { - "node-uuid": "^1.4.7", - "react-highlight-words": "^0.1.1", + "js-worker-search": "file:///Users/brianvaughn/Documents/git/js-worker-search/js-worker-search-1.0.0.tgz", "redux": "^3.0.4" } } diff --git a/src/SearchApi.js b/src/SearchApi.js index fe49a45..c307077 100644 --- a/src/SearchApi.js +++ b/src/SearchApi.js @@ -1,19 +1,16 @@ /** @flow */ -import WorkerSearch, { Search } from './lib' +import Search from 'js-worker-search' /** * Observable that manages communication between redux-search middleware and the Search utility. * This class maps resource names to search indicies and manages subscribers. */ -export class SubscribableSearchApi { +export default class SubscribableSearchApi { /** * Constructor. - * - * @param createSearch Factory function responsible for creating a Search instance */ - constructor (createSearch) { - this._createSearch = createSearch + constructor () { this._resourceToSearchMap = {} // Subscribers @@ -59,7 +56,7 @@ export class SubscribableSearchApi { * @param resources Map of resource uid to resource (Object) */ indexResource (resourceName, fieldNamesOrIndexFunction, resources) { - const search = this._createSearch() + const search = new Search() if (Array.isArray(fieldNamesOrIndexFunction)) { if (resources.forEach instanceof Function) { @@ -98,9 +95,7 @@ export class SubscribableSearchApi { async performSearch (resourceName, text) { try { const search = this._resourceToSearchMap[resourceName] - - // Promise.resolve handles both synchronous and web-worker versions of Search - const result = await Promise.resolve(search.search(text)) + const result = await search.search(text) this._notifyNext({ result, @@ -130,40 +125,3 @@ export class SubscribableSearchApi { ) } } - -/** - * Single-threaded search API. - * This implementation is provided for browsers that do not support web workers. - */ -export class SearchApi extends SubscribableSearchApi { - constructor () { - super(() => new Search()) - } -} - -/** - * Web worker search API. - * Indexing and searching is performed in a web worker thread. - */ -export class WorkerSearchApi extends SubscribableSearchApi { - constructor () { - super(() => new WorkerSearch()) - } -} - -/** - * Search API that uses web workers when available. - * Indexing and searching is performed in the UI thread as a fallback when web workers aren't supported. - */ -export class CapabilitiesBasedSearchApi extends SubscribableSearchApi { - constructor () { - // Based on https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers - // But with added check for Node environment - if (typeof window !== 'undefined' && window.Worker) { - super(() => new WorkerSearch()) - } else { - super(() => new Search()) - } - } -} - diff --git a/src/SearchApi.test.js b/src/SearchApi.test.js index efd87d7..e1b7af9 100644 --- a/src/SearchApi.test.js +++ b/src/SearchApi.test.js @@ -1,5 +1,5 @@ import test from 'tape' -import { SearchApi } from './SearchApi' +import SearchApi from './SearchApi' function getSearchApi () { const documentA = {id: 1, name: 'One', description: 'The first document'} diff --git a/src/index.js b/src/index.js index 0211c25..f3030b5 100644 --- a/src/index.js +++ b/src/index.js @@ -6,12 +6,7 @@ import { import { search } from './actions' import reduxSearch from './reduxSearch' import reducer from './reducer' -import { - CapabilitiesBasedSearchApi, - SearchApi, - WorkerSearchApi -} from './SearchApi' -import { Search } from './lib' +import SearchApi from './SearchApi' export default reduxSearch export { @@ -20,8 +15,5 @@ export { reducer, reduxSearch, search as createSearchAction, - Search as SearchUtility, - CapabilitiesBasedSearchApi, - SearchApi, - WorkerSearchApi + SearchApi } diff --git a/src/lib/Search.js b/src/lib/Search.js deleted file mode 100644 index d11c48b..0000000 --- a/src/lib/Search.js +++ /dev/null @@ -1,97 +0,0 @@ -/** @flow */ -import SearchIndex from './SearchIndex' - -/** - * Client side, full text search utility. - * Forked from JS search (github.com/bvaughn/js-search). - */ -export default class Search { - - /** - * Constructor. - */ - constructor () { - this.searchIndex = new SearchIndex() - this.uids = {} - } - - /** - * Adds or updates a uid in the search index and associates it with the specified text. - * Note that at this time uids can only be added or updated in the index, not removed. - * - * @param uid Uniquely identifies a searchable object - * @param text Text to associate with uid - */ - indexDocument (uid: any, text: string): Search { - this.uids[uid] = true - - var fieldTokens: Array = this._tokenize(this._sanitize(text)) - - fieldTokens.forEach(fieldToken => { - var expandedTokens: Array = this._expandToken(fieldToken) - - expandedTokens.forEach(expandedToken => - this.searchIndex.indexDocument(expandedToken, uid) - ) - }) - - return this - } - - /** - * Searches the current index for the specified query text. - * Only uids matching all of the words within the text will be accepted. - * If an empty query string is provided all indexed uids will be returned. - * - * Document searches are case-insensitive (e.g. "search" will match "Search"). - * Document searches use substring matching (e.g. "na" and "me" will both match "name"). - * - * @param query Searchable query text - * @return Array of uids - */ - search (query: string): Array { - if (!query) { - return Object.keys(this.uids) - } else { - var tokens: Array = this._tokenize(this._sanitize(query)) - - return this.searchIndex.search(tokens) - } - } - - /** - * Index strategy based on 'all-substrings-index-strategy.ts' in github.com/bvaughn/js-search/ - * - * @private - */ - _expandToken (token: string): Array { - var expandedTokens = [] - - for (let i = 0, length = token.length; i < length; ++i) { - let prefixString: string = '' - - for (let j = i; j < length; ++j) { - prefixString += token.charAt(j) - expandedTokens.push(prefixString) - } - } - - return expandedTokens - } - - /** - * @private - */ - _sanitize (string: string): string { - return string.trim().toLocaleLowerCase() - } - - /** - * @private - */ - _tokenize (text: string): Array { - return text - .split(/\s+/) - .filter(text => text) // Remove empty tokens - } -} diff --git a/src/lib/Search.test.js b/src/lib/Search.test.js deleted file mode 100644 index e1cc498..0000000 --- a/src/lib/Search.test.js +++ /dev/null @@ -1,119 +0,0 @@ -import test from 'tape' -import Immutable from 'immutable' -import Search from './Search' - -const documentA = Immutable.fromJS({id: 1, name: 'One', description: 'The first document'}) -const documentB = Immutable.fromJS({id: 2, name: 'Two', description: 'The second document'}) -const documentC = Immutable.fromJS({id: 3, name: 'Three', description: 'The third document'}) -const documentD = Immutable.fromJS({id: 4, name: '楌ぴ', description: '堦ヴ礯 ラ蝥曣んを 檨儯饨䶧'}) -const documentE = Immutable.fromJS({id: 5, name: 'ㄨ穯ゆ姎囥', description: '楌ぴ 堦ヴ礯 ラ蝥曣んを 檨儯饨䶧䏤'}) -const documents = [ - documentA, - documentB, - documentC, - documentD, - documentE -] - -function init () { - const searchableDocuments = new Search() - - documents.forEach(doc => { - searchableDocuments.indexDocument(doc.get('id'), doc.get('name')) - searchableDocuments.indexDocument(doc.get('id'), doc.get('description')) - }) - - return searchableDocuments -} - -test('Search should return documents ids for any searchable field matching a query', t => { - const searchableDocuments = init() - let ids = searchableDocuments.search('One') - t.equal(ids.length, 1) - t.deepLooseEqual(ids, [1]) - - ids = searchableDocuments.search('Third') - t.equal(ids.length, 1) - t.deepLooseEqual(ids, [3]) - - ids = searchableDocuments.search('the') - t.equal(ids.length, 3) - t.deepLooseEqual(ids, [1, 2, 3]) - - ids = searchableDocuments.search('楌') // Tests matching of other script systems - t.equal(ids.length, 2) - t.deepLooseEqual(ids, [4, 5]) - t.end() -}) - -test('Search should return documents ids only if document matches all tokens in a query', t => { - const searchableDocuments = init() - let ids = searchableDocuments.search('the second') - t.equal(ids.length, 1) - t.equal(ids[0], 2) - - ids = searchableDocuments.search('three document') // Spans multiple fields - t.equal(ids.length, 1) - t.equal(ids[0], 3) - t.end() -}) - -test('Search should return an empty array for query without matching documents', t => { - const searchableDocuments = init() - const ids = searchableDocuments.search('four') - t.equal(ids.length, 0) - t.end() -}) - -test('Search should return all uids for an empty query', t => { - const searchableDocuments = init() - const ids = searchableDocuments.search('') - t.equal(ids.length, 5) - t.end() -}) - -test('Search should ignore case when searching', t => { - const searchableDocuments = init() - const texts = ['one', 'One', 'ONE'] - texts.forEach((text) => { - const ids = searchableDocuments.search(text) - t.equal(ids.length, 1) - t.equal(ids[0], 1) - }) - - t.end() -}) - -test('Search should use substring matching', t => { - const searchableDocuments = init() - let texts = ['sec', 'second', 'eco', 'cond'] - texts.forEach((text) => { - let ids = searchableDocuments.search(text) - t.equal(ids.length, 1) - t.equal(ids[0], 2) - }) - - texts = ['堦', '堦ヴ', '堦ヴ礯', 'ヴ', 'ヴ礯'] - texts.forEach((text) => { - let ids = searchableDocuments.search(text) - t.equal(ids.length, 2) - t.deepLooseEqual(ids, [4, 5]) - }) - - t.end() -}) - -test('Search should allow custom indexing via indexDocument', t => { - const searchableDocuments = init() - const text = 'xyz' - let ids = searchableDocuments.search(text) - t.equal(ids.length, 0) - - const id = documentA.get('id') - searchableDocuments.indexDocument(id, text) - - ids = searchableDocuments.search(text) - t.equal(ids.length, 1) - t.equal(ids[0], 1) - t.end() -}) diff --git a/src/lib/SearchIndex.js b/src/lib/SearchIndex.js deleted file mode 100644 index a23473b..0000000 --- a/src/lib/SearchIndex.js +++ /dev/null @@ -1,66 +0,0 @@ -/** @flow */ - -/** - * Maps search tokens to uids. - * This structure is used by the Search class to optimize search operations. - * Forked from JS search (github.com/bvaughn/js-search). - */ -export default class SearchIndex { - tokenToUidMap: { [token: string]: any } - - constructor () { - this.tokenToUidMap = {} - } - - /** - * Maps the specified token to a uid. - * - * @param token Searchable token (e.g. "road") - * @param uid Identifies a document within the searchable corpus - */ - indexDocument (token: string, uid: any): void { - if (!this.tokenToUidMap[token]) { - this.tokenToUidMap[token] = {} - } - - this.tokenToUidMap[token][uid] = uid - } - - /** - * Finds uids that have been mapped to the set of tokens specified. - * Only uids that have been mapped to all tokens will be returned. - * - * @param tokens Array of searchable tokens (e.g. ["long", "road"]) - * @return Array of uids that have been associated with the set of search tokens - */ - search (tokens: Array): Array { - let uidMap: {[uid: any]: any} = {} - let initialized = false - - tokens.forEach(token => { - let currentUidMap: {[uid: any]: any} = this.tokenToUidMap[token] || {} - - if (!initialized) { - initialized = true - - for (let uid in currentUidMap) { - uidMap[uid] = currentUidMap[uid] - } - } else { - for (let uid in uidMap) { - if (!currentUidMap[uid]) { - delete uidMap[uid] - } - } - } - }) - - let uids: Array = [] - - for (let uid in uidMap) { - uids.push(uidMap[uid]) - } - - return uids - } -} diff --git a/src/lib/index.js b/src/lib/index.js deleted file mode 100644 index a459182..0000000 --- a/src/lib/index.js +++ /dev/null @@ -1,7 +0,0 @@ -/** @flow */ -import Search from './Search' -import SearchIndex from './SearchIndex' -import SearchWorkerLoader from './worker/Main' - -export default SearchWorkerLoader -export { Search, SearchIndex, SearchWorkerLoader } diff --git a/src/lib/worker/Main.js b/src/lib/worker/Main.js deleted file mode 100644 index cf0b75f..0000000 --- a/src/lib/worker/Main.js +++ /dev/null @@ -1,112 +0,0 @@ -/** - * Client side, full text search utility. - * This interface exposes web worker search capabilities to the UI thread. - * @flow - */ -import uuid from 'node-uuid' - -/** - * Client side, full text search utility. - */ -export default class SearchWorkerLoader { - - /** - * Constructor. - */ - constructor (WorkerClass) { - // Defer worker import until construction to avoid testing error: - // Error: Cannot find module 'worker!./[workername]' - if (!WorkerClass) { - WorkerClass = require('worker?inline=true!./Worker') - } - - // Maintain context if references are passed around - this.indexDocument = this.indexDocument.bind(this) - this.search = this.search.bind(this) - - this.callbackQueue = [] - this.callbackIdMap = {} - - this.worker = new WorkerClass() - this.worker.onerror = event => { - const { callbackId, error } = event.data - this._updateQueue({ callbackId, error }) - } - this.worker.onmessage = event => { - const { callbackId, results } = event.data - this._updateQueue({ callbackId, results }) - } - } - - /** - * Adds or updates a uid in the search index and associates it with the specified text. - * Note that at this time uids can only be added or updated in the index, not removed. - * - * @param uid Uniquely identifies a searchable object - * @param text Text to associate with uid - */ - indexDocument (uid: any, text: string): SearchWorkerLoader { - this.worker.postMessage({ - method: 'indexDocument', - text, - uid - }) - - return this - } - - /** - * Searches the current index for the specified query text. - * Only uids matching all of the words within the text will be accepted. - * If an empty query string is provided all indexed uids will be returned. - * - * Document searches are case-insensitive (e.g. "search" will match "Search"). - * Document searches use substring matching (e.g. "na" and "me" will both match "name"). - * - * @param query Searchable query text - * @return Promise to be resolved with an array of uids - */ - search (query: string): Promise { - return new Promise((resolve, reject) => { - const callbackId = uuid.v4() - const data = { callbackId, reject, resolve } - - this.worker.postMessage({ - method: 'search', - query, - callbackId - }) - - this.callbackQueue.push(data) - this.callbackIdMap[callbackId] = data - }) - } - - /** - * Updates the queue and flushes any completed promises that are ready. - */ - _updateQueue ({ callbackId, error, results }) { - const target = this.callbackIdMap[callbackId] - target.complete = true - target.error = error - target.results = results - - while (this.callbackQueue.length) { - let data = this.callbackQueue[0] - - if (!data.complete) { - break - } - - this.callbackQueue.shift() - - delete this.callbackIdMap[data.callbackId] - - if (data.error) { - data.reject(data.error) - } else { - data.resolve(data.results) - } - } - } -} diff --git a/src/lib/worker/Main.test.js b/src/lib/worker/Main.test.js deleted file mode 100644 index 989e4b4..0000000 --- a/src/lib/worker/Main.test.js +++ /dev/null @@ -1,129 +0,0 @@ -import test from 'tape' -import SearchWorkerLoader from './Main' - -class StubWorker { - constructor () { - this.indexedDocumentMap = {} - this.searchQueue = [] - } - - postMessage (data) { - const { method, ...props } = data - - switch (method) { - case 'indexDocument': - const { uid, text } = props - if (!this.indexedDocumentMap[uid]) { - this.indexedDocumentMap[uid] = [] - } - this.indexedDocumentMap[uid].push(text) - break - case 'search': - const { callbackId, query } = props - this.searchQueue.push({ callbackId, query }) - break - } - } - - rejectSearch (index, error) { - const { callbackId } = this.searchQueue[index] - this.onerror({ data: { error, callbackId } }) - } - - resolveSearch (index, results) { - const { callbackId } = this.searchQueue[index] - this.onmessage({ data: { results, callbackId } }) - } -} - -test('SearchWorkerLoader indexDocument should index a document with the specified text(s)', t => { - const search = new SearchWorkerLoader(StubWorker) - search.indexDocument('a', 'cat') - search.indexDocument('a', 'dog') - search.indexDocument('b', 'cat') - - t.equal(Object.keys(search.worker.indexedDocumentMap).length, 2) - t.equal(search.worker.indexedDocumentMap.a.length, 2) - t.deepLooseEqual(search.worker.indexedDocumentMap.a, ['cat', 'dog']) - t.equal(search.worker.indexedDocumentMap.b.length, 1) - t.deepLooseEqual(search.worker.indexedDocumentMap.b, ['cat']) - t.end() -}) - -test('SearchWorkerLoader search should search for the specified text', t => { - const search = new SearchWorkerLoader(StubWorker) - search.search('cat') - t.equal(search.worker.searchQueue.length, 1) - t.equal(search.worker.searchQueue[0].query, 'cat') - t.end() -}) - -test('SearchWorkerLoader search should resolve the returned Promise on search completion', async t => { - const search = new SearchWorkerLoader(StubWorker) - const promise = search.search('cat') - search.worker.resolveSearch(0, ['a', 'b']) - - const result = await promise - t.deepLooseEqual(result, ['a', 'b']) - t.end() -}) - -test('SearchWorkerLoader search should resolve multiple concurrent searches', async t => { - const search = new SearchWorkerLoader(StubWorker) - const promises = Promise.all([ - search.search('cat'), - search.search('dog') - ]) - search.worker.resolveSearch(0, ['a']) - search.worker.resolveSearch(1, ['a', 'b']) - await promises - t.end() -}) - -test('SearchWorkerLoader search should resolve searches in the correct order', async t => { - const search = new SearchWorkerLoader(StubWorker) - const results = [] - const promiseList = [ - search.search('cat'), - search.search('dog'), - search.search('rat') - ].map(promise => promise.then(result => results.push(result))) - - search.worker.resolveSearch(1, ['1']) - search.worker.resolveSearch(0, ['0']) - search.worker.resolveSearch(2, ['2']) - - await Promise.all(promiseList) - const [r1, r2, r3] = results - t.deepLooseEqual(r1, ['0']) - t.deepLooseEqual(r2, ['1']) - t.deepLooseEqual(r3, ['2']) - t.end() -}) - -test('SearchWorkerLoader search should not reject all searches if one fails', async t => { - const search = new SearchWorkerLoader(StubWorker) - const errors = [] - const results = [] - const promises = [ - search.search('cat'), - search.search('dog') - ].map(promise => - promise - .then(result => results.push(result)) - .catch(error => errors.push(error)) - ) - - search.worker.rejectSearch(1, new Error('1')) - search.worker.resolveSearch(0, ['0']) - - try { - await Promise.all(promises) - } catch (err) {} - - t.equal(results.length, 1) - t.deepLooseEqual(results[0], ['0']) - t.equal(errors.length, 1) - t.equal(errors[0].message, '1') - t.end() -}) diff --git a/src/lib/worker/Worker.js b/src/lib/worker/Worker.js deleted file mode 100644 index 789f292..0000000 --- a/src/lib/worker/Worker.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Search entry point to web worker. - * Builds search index and performs searches on separate thread from the ui. - * @flow - */ -import Search from '../Search' - -const search = new Search() - -self.addEventListener('message', event => { - const { data } = event - const { method } = data - - switch (method) { - case 'indexDocument': - const { uid, text } = data - - search.indexDocument(uid, text) - break - case 'search': - const { callbackId, query } = data - - const results = search.search(query) - - self.postMessage({ callbackId, results }) - break - } -}, false) diff --git a/src/reduxSearch.js b/src/reduxSearch.js index a1b3f1b..af10eeb 100644 --- a/src/reduxSearch.js +++ b/src/reduxSearch.js @@ -3,7 +3,7 @@ import { defaultSearchStateSelector } from './selectors' import * as actions from './actions' import { SEARCH_STATE_SELECTOR } from './constants' import searchMiddleware from './searchMiddleware' -import { CapabilitiesBasedSearchApi } from './SearchApi' +import SearchApi from './SearchApi' /** * Creates higher-order search store to be composed with other store enhancers. @@ -30,7 +30,7 @@ import { CapabilitiesBasedSearchApi } from './SearchApi' export default function reduxSearch ({ resourceIndexes = {}, resourceSelector, - searchApi = new CapabilitiesBasedSearchApi(), + searchApi = new SearchApi(), searchStateSelector = defaultSearchStateSelector } = {}) { return createStore => (reducer, initialState) => {