Socket
Socket
Sign inDemoInstall

js-worker-search

Package Overview
Dependencies
2
Maintainers
1
Versions
12
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 1.0.2 to 1.1.0

dist/a6e8d10e25215827e7c5.worker.js

22

CHANGELOG.md
Changelog
-----
#### 1.1.0
Added support for custom index strategies.
By default, a prefix matching strategy is still used but it can be overridden like so:
```js
import SearchApi, { INDEX_MODES } from 'js-worker-search'
// all-substrings match by default; same as current
// eg "c", "ca", "a", "at", "cat" match "cat"
const searchApi = new SearchApi()
// prefix matching (eg "c", "ca", "cat" match "cat")
const searchApi = new SearchApi({
indexMode: INDEX_MODES.PREFIXES
})
// exact words matching (eg only "cat" matches "cat")
const searchApi = new SearchApi({
indexMode: INDEX_MODES.EXACT_WORDS
})
```
#### 1.0.2

@@ -5,0 +27,0 @@ Wrapped `String.prototype.charAt` usage in a `try/catch` to avoid erroring when handling surrogate halves.

2

package.json
{
"name": "js-worker-search",
"version": "1.0.2",
"version": "1.1.0",
"description": "JavaScript client-side search API with web-worker support",

@@ -5,0 +5,0 @@ "author": "Brian Vaughn (brian.david.vaughn@gmail.com)",

@@ -26,2 +26,7 @@ js-worker-search

##### `constructor ({ indexMode })`
By default, `SearchApi` builds an index to match all substrings.
You can override this behavior by passing an named `indexMode` parameter.
Valid values are `INDEX_MODES.ALL_SUBSTRINGS`, `INDEX_MODES.EXACT_WORDS`, and `INDEX_MODES.PREFIXES`.
##### `indexDocument (uid, text)`

@@ -66,2 +71,23 @@ 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.

By default, `SearchApi` builds an index to match all substrings.
You can override this behavior by passing an `indexMode` parameter to the constructor like so:
```js
import SearchApi, { INDEX_MODES } from 'js-worker-search'
// all-substrings match by default; same as current
// eg "c", "ca", "a", "at", "cat" match "cat"
const searchApi = new SearchApi()
// prefix matching (eg "c", "ca", "cat" match "cat")
const searchApi = new SearchApi({
indexMode: INDEX_MODES.PREFIXES
})
// exact words matching (eg only "cat" matches "cat")
const searchApi = new SearchApi({
indexMode: INDEX_MODES.EXACT_WORDS
})
```
Changelog

@@ -68,0 +94,0 @@ ---------

export default from './SearchApi'
export { INDEX_MODES } from './util'

@@ -0,1 +1,2 @@

import SearchUtility from './util'

@@ -9,9 +10,9 @@ import SearchWorkerLoader from './worker'

export default class SearchApi {
constructor () {
constructor ({ indexMode } = {}) {
// 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) {
this._search = new SearchWorkerLoader()
this._search = new SearchWorkerLoader({ indexMode })
} else {
this._search = new SearchUtility()
this._search = new SearchUtility({ indexMode })
}

@@ -18,0 +19,0 @@

export default from './SearchUtility'
export { INDEX_MODES } from './constants'

@@ -38,3 +38,3 @@ /**

tokens.forEach(token => {
for (let token of tokens) {
let currentUidMap: {[uid: any]: any} = this.tokenToUidMap[token] || {}

@@ -55,3 +55,3 @@

}
})
}

@@ -58,0 +58,0 @@ let uids: Array<any> = []

@@ -0,1 +1,2 @@

import { INDEX_MODES } from './constants'
import SearchIndex from './SearchIndex'

@@ -11,4 +12,10 @@

* Constructor.
*
* @param indexMode See #setIndexMode
*/
constructor () {
constructor ({
indexMode = INDEX_MODES.ALL_SUBSTRINGS
} = {}) {
this._indexMode = indexMode
this.searchIndex = new SearchIndex()

@@ -19,2 +26,9 @@ this.uids = {}

/**
* Returns a constant representing the current index mode.
*/
getIndexMode (): string {
return this._indexMode
}
/**
* Adds or updates a uid in the search index and associates it with the specified text.

@@ -31,9 +45,9 @@ * Note that at this time uids can only be added or updated in the index, not removed.

fieldTokens.forEach(fieldToken => {
for (let fieldToken of fieldTokens) {
var expandedTokens: Array<string> = this._expandToken(fieldToken)
expandedTokens.forEach(expandedToken =>
for (let expandedToken of expandedTokens) {
this.searchIndex.indexDocument(expandedToken, uid)
)
})
}
}

@@ -65,2 +79,14 @@ return this

/**
* Sets a new index mode.
* See util/constants/INDEX_MODES
*/
setIndexMode (indexMode: string): void {
if (Object.keys(this.uids).length > 0) {
throw Error('indexMode cannot be changed once documents have been indexed')
}
this._indexMode = indexMode
}
/**
* Index strategy based on 'all-substrings-index-strategy.ts' in github.com/bvaughn/js-search/

@@ -71,4 +97,16 @@ *

_expandToken (token: string): Array<string> {
var expandedTokens = []
switch (this._indexMode) {
case INDEX_MODES.EXACT_WORDS:
return [token]
case INDEX_MODES.PREFIXES:
return this._expandPrefixTokens(token)
case INDEX_MODES.ALL_SUBSTRINGS:
default:
return this._expandAllSubstringTokens(token)
}
}
_expandAllSubstringTokens (token: string): Array<string> {
const expandedTokens = []
// String.prototype.charAt() may return surrogate halves instead of whole characters.

@@ -82,7 +120,7 @@ // When this happens in the context of a web-worker it can cause Chrome to crash.

for (let i = 0, length = token.length; i < length; ++i) {
let prefixString: string = ''
let substring: string = ''
for (let j = i; j < length; ++j) {
prefixString += token.charAt(j)
expandedTokens.push(prefixString)
substring += token.charAt(j)
expandedTokens.push(substring)
}

@@ -97,2 +135,22 @@ }

_expandPrefixTokens (token: string): Array<string> {
const expandedTokens = []
// String.prototype.charAt() may return surrogate halves instead of whole characters.
// When this happens in the context of a web-worker it can cause Chrome to crash.
// Catching the error is a simple solution for now; in the future I may try to better support non-BMP characters.
// Resources:
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/charAt
// https://mathiasbynens.be/notes/javascript-unicode
try {
for (let i = 0, length = token.length; i < length; ++i) {
expandedTokens.push(token.substr(0, i + 1))
}
} catch (error) {
console.error(`Unable to parse token "${token}" ${error}`)
}
return expandedTokens
}
/**

@@ -99,0 +157,0 @@ * @private

import test from 'tape'
import Immutable from 'immutable'
import SearchUtility from './SearchUtility'
import { INDEX_MODES } from './constants'

@@ -18,28 +19,28 @@ const documentA = Immutable.fromJS({id: 1, name: 'One', description: 'The first document'})

function init () {
const searchableDocuments = new SearchUtility()
function init ({ indexMode } = {}) {
const searchUtility = new SearchUtility({ indexMode })
documents.forEach(doc => {
searchableDocuments.indexDocument(doc.get('id'), doc.get('name'))
searchableDocuments.indexDocument(doc.get('id'), doc.get('description'))
searchUtility.indexDocument(doc.get('id'), doc.get('name'))
searchUtility.indexDocument(doc.get('id'), doc.get('description'))
})
return searchableDocuments
return searchUtility
}
test('SearchUtility should return documents ids for any searchable field matching a query', t => {
const searchableDocuments = init()
let ids = searchableDocuments.search('One')
const searchUtility = init()
let ids = searchUtility.search('One')
t.equal(ids.length, 1)
t.deepLooseEqual(ids, [1])
ids = searchableDocuments.search('Third')
ids = searchUtility.search('Third')
t.equal(ids.length, 1)
t.deepLooseEqual(ids, [3])
ids = searchableDocuments.search('the')
ids = searchUtility.search('the')
t.equal(ids.length, 3)
t.deepLooseEqual(ids, [1, 2, 3])
ids = searchableDocuments.search('楌') // Tests matching of other script systems
ids = searchUtility.search('楌') // Tests matching of other script systems
t.equal(ids.length, 2)

@@ -51,8 +52,8 @@ t.deepLooseEqual(ids, [4, 5])

test('SearchUtility should return documents ids only if document matches all tokens in a query', t => {
const searchableDocuments = init()
let ids = searchableDocuments.search('the second')
const searchUtility = init()
let ids = searchUtility.search('the second')
t.equal(ids.length, 1)
t.equal(ids[0], 2)
ids = searchableDocuments.search('three document') // Spans multiple fields
ids = searchUtility.search('three document') // Spans multiple fields
t.equal(ids.length, 1)

@@ -64,4 +65,4 @@ t.equal(ids[0], 3)

test('SearchUtility should return an empty array for query without matching documents', t => {
const searchableDocuments = init()
const ids = searchableDocuments.search('four')
const searchUtility = init()
const ids = searchUtility.search('four')
t.equal(ids.length, 0)

@@ -72,4 +73,4 @@ t.end()

test('SearchUtility should return all uids for an empty query', t => {
const searchableDocuments = init()
const ids = searchableDocuments.search('')
const searchUtility = init()
const ids = searchUtility.search('')
t.equal(ids.length, 5)

@@ -80,6 +81,6 @@ t.end()

test('SearchUtility should ignore case when searching', t => {
const searchableDocuments = init()
const searchUtility = init()
const texts = ['one', 'One', 'ONE']
texts.forEach((text) => {
const ids = searchableDocuments.search(text)
const ids = searchUtility.search(text)
t.equal(ids.length, 1)

@@ -93,6 +94,6 @@ t.equal(ids[0], 1)

test('SearchUtility should use substring matching', t => {
const searchableDocuments = init()
const searchUtility = init()
let texts = ['sec', 'second', 'eco', 'cond']
texts.forEach((text) => {
let ids = searchableDocuments.search(text)
let ids = searchUtility.search(text)
t.equal(ids.length, 1)

@@ -104,3 +105,3 @@ t.equal(ids[0], 2)

texts.forEach((text) => {
let ids = searchableDocuments.search(text)
let ids = searchUtility.search(text)
t.equal(ids.length, 2)

@@ -114,11 +115,11 @@ t.deepLooseEqual(ids, [4, 5])

test('SearchUtility should allow custom indexing via indexDocument', t => {
const searchableDocuments = init()
const searchUtility = init()
const text = 'xyz'
let ids = searchableDocuments.search(text)
let ids = searchUtility.search(text)
t.equal(ids.length, 0)
const id = documentA.get('id')
searchableDocuments.indexDocument(id, text)
searchUtility.indexDocument(id, text)
ids = searchableDocuments.search(text)
ids = searchUtility.search(text)
t.equal(ids.length, 1)

@@ -128,1 +129,57 @@ t.equal(ids[0], 1)

})
test('SearchUtility should recognize an :indexMode constructor param', t => {
const searchUtility = new SearchUtility({
indexMode: INDEX_MODES.EXACT_WORDS
})
t.equal(searchUtility.getIndexMode(), INDEX_MODES.EXACT_WORDS)
t.end()
})
test('SearchUtility should update its default :indexMode when :setIndexMode() is called', t => {
const searchUtility = new SearchUtility()
searchUtility.setIndexMode(INDEX_MODES.EXACT_WORDS)
t.equal(searchUtility.getIndexMode(), INDEX_MODES.EXACT_WORDS)
t.end()
})
test('SearchUtility should should error if :setIndexMode() is called after an index has been created', t => {
let errored = false
const searchUtility = init()
try {
searchUtility.indexDocument
searchUtility.setIndexMode(INDEX_MODES.EXACT_WORDS)
} catch (error) {
errored = true
}
t.equal(errored, true)
t.end()
})
test('SearchUtility should support PREFIXES :indexMode', t => {
const searchUtility = init({ indexMode: INDEX_MODES.PREFIXES })
const match1 = ['fir', 'first']
const match2 = ['sec', 'second']
match1.forEach((token) => {
t.deepLooseEqual(searchUtility.search(token), [1])
})
match2.forEach((token) => {
t.deepLooseEqual(searchUtility.search(token), [2])
})
const noMatch = ['irst', 'rst', 'st', 'irs', 'ond', 'econd', 'eco']
noMatch.forEach((token) => {
t.equal(searchUtility.search(token).length, 0)
})
t.end()
})
test('SearchUtility should support EXACT_WORDS :indexMode', t => {
const searchUtility = init({ indexMode: INDEX_MODES.EXACT_WORDS })
t.deepLooseEqual(searchUtility.search('first'), [1])
t.deepLooseEqual(searchUtility.search('second'), [2])
const noMatch = ['irst', 'rst', 'st', 'irs', 'ond', 'econd', 'eco']
noMatch.forEach((token) => {
t.equal(searchUtility.search(token).length, 0)
})
t.end()
})

@@ -12,3 +12,6 @@ import uuid from 'uuid'

*/
constructor (WorkerClass) {
constructor ({
indexMode,
WorkerClass
} = {}) {
// Defer worker import until construction to avoid testing error:

@@ -36,2 +39,10 @@ // Error: Cannot find module 'worker!./[workername]'

}
// Override default :indexMode if a specific one has been requested
if (indexMode) {
this.worker.postMessage({
method: 'setIndexMode',
indexMode
})
}
}

@@ -38,0 +49,0 @@

import test from 'tape'
import SearchWorkerLoader from './SearchWorkerLoader'
import { INDEX_MODES } from '../util'

@@ -8,2 +9,3 @@ class StubWorker {

this.searchQueue = []
this.setIndexModeQueue = []
}

@@ -26,2 +28,6 @@

break
case 'setIndexMode':
const { indexMode } = props
this.setIndexModeQueue.push({ indexMode })
break
}

@@ -42,3 +48,3 @@ }

test('SearchWorkerLoader indexDocument should index a document with the specified text(s)', t => {
const search = new SearchWorkerLoader(StubWorker)
const search = new SearchWorkerLoader({ WorkerClass: StubWorker })
search.indexDocument('a', 'cat')

@@ -57,3 +63,3 @@ search.indexDocument('a', 'dog')

test('SearchWorkerLoader search should search for the specified text', t => {
const search = new SearchWorkerLoader(StubWorker)
const search = new SearchWorkerLoader({ WorkerClass: StubWorker })
search.search('cat')

@@ -66,3 +72,3 @@ t.equal(search.worker.searchQueue.length, 1)

test('SearchWorkerLoader search should resolve the returned Promise on search completion', async t => {
const search = new SearchWorkerLoader(StubWorker)
const search = new SearchWorkerLoader({ WorkerClass: StubWorker })
const promise = search.search('cat')

@@ -77,3 +83,3 @@ search.worker.resolveSearch(0, ['a', 'b'])

test('SearchWorkerLoader search should resolve multiple concurrent searches', async t => {
const search = new SearchWorkerLoader(StubWorker)
const search = new SearchWorkerLoader({ WorkerClass: StubWorker })
const promises = Promise.all([

@@ -90,3 +96,3 @@ search.search('cat'),

test('SearchWorkerLoader search should resolve searches in the correct order', async t => {
const search = new SearchWorkerLoader(StubWorker)
const search = new SearchWorkerLoader({ WorkerClass: StubWorker })
const results = []

@@ -112,3 +118,3 @@ const promiseList = [

test('SearchWorkerLoader search should not reject all searches if one fails', async t => {
const search = new SearchWorkerLoader(StubWorker)
const search = new SearchWorkerLoader({ WorkerClass: StubWorker })
const errors = []

@@ -138,1 +144,17 @@ const results = []

})
test('SearchWorkerLoader should pass the specified :indexMode to the WorkerClass', t => {
const search = new SearchWorkerLoader({
indexMode: INDEX_MODES.EXACT_WORDS,
WorkerClass: StubWorker
})
t.equal(search.worker.setIndexModeQueue.length, 1)
t.equal(search.worker.setIndexModeQueue[0].indexMode, INDEX_MODES.EXACT_WORDS)
t.end()
})
test('SearchWorkerLoader should not override the default :indexMode in the WorkerClass if an override is not requested', t => {
const search = new SearchWorkerLoader({ WorkerClass: StubWorker })
t.equal(search.worker.setIndexModeQueue.length, 0)
t.end()
})

@@ -27,3 +27,8 @@ import SearchUtility from '../util'

break
case 'setIndexMode':
const { indexMode } = data
searchUtility.setIndexMode(indexMode)
break
}
}, false)

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc