├── .babelrc ├── .eslintrc ├── .flowconfig ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── benchmarks ├── .gitignore ├── README.md ├── books.json ├── create-index.js ├── package.json ├── search.js └── yarn.lock ├── flow-defs.js ├── package.json ├── src ├── SearchApi.js ├── SearchApi.test.js ├── index.js ├── types.js ├── util │ ├── SearchIndex.js │ ├── SearchUtility.js │ ├── SearchUtility.test.js │ ├── constants.js │ └── index.js └── worker │ ├── SearchWorkerLoader.js │ ├── SearchWorkerLoader.test.js │ ├── Worker.js │ └── index.js ├── webpack.config.dev.js ├── webpack.config.dist.js └── yarn.lock /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["syntax-flow", "transform-flow-strip-types"], 3 | "presets": ["es2015", "stage-0"] 4 | } 5 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "prettier" 4 | ], 5 | "parser": "babel-eslint", 6 | "parserOptions": { 7 | "sourceType": "module" 8 | }, 9 | "plugins": [ 10 | "flowtype", 11 | "prettier" 12 | ], 13 | "rules": { 14 | "prettier/prettier": "error" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | .*/benchmarks/.* 3 | 4 | [include] 5 | src/**/*.js 6 | 7 | [libs] 8 | ./flow-defs.js 9 | 10 | [options] 11 | 12 | [lints] 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | 3 | # Logs 4 | /log/* 5 | !/log/.keep 6 | /tmp 7 | *.log 8 | 9 | # Dependency directory 10 | node_modules 11 | 12 | # Test stuff 13 | coverage 14 | 15 | # OS X 16 | .DS_Store 17 | 18 | # Misc 19 | node_modules 20 | npm-debug.log 21 | build 22 | .watchmanconfig 23 | .vscode -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Changelog 2 | ----- 3 | #### 2.0.0 4 | * Bumped uuid from 2.0.1 to 8.3.2 to support ES modules 5 | 6 | #### 1.4.1 7 | * Removed source maps from inline worker blob to avoid errors ([@jahed](https://github.com/jahed) - [#23](https://github.com/bvaughn/js-worker-search/pull/23)) 8 | 9 | #### 1.4.0 10 | * Added support for optional OR searches (documents matching only some of the search tokens) ([@dlebech](https://github.com/dlebech) - [#19](https://github.com/bvaughn/js-worker-search/pull/19)) 11 | 12 | #### 1.3.0 13 | * Added `terminate` method to enable library users to kill the web worker ([@LrsK](https://github.com/LrsK) - [#15](https://github.com/bvaughn/js-worker-search/pull/15)) 14 | 15 | #### 1.2.1 16 | Worker `onerror` method properly handles undefined `event.data` for errors during eg `indexDocument`. (This avoids causing a secondary error, as reported in [bvaughn/redux-search/issues/69)](https://github.com/bvaughn/redux-search/issues/69).) 17 | 18 | #### 1.2.0 19 | Added support for custom tokenizer patterns and case-sensitive search. 20 | 21 | ```js 22 | // Case-sensitive exact word search with custom tokenizer RegExp 23 | // to include all non alphanumerics as delimeters 24 | // ex: searching "Swift" will match "Thomas Swift" and "Thomas (Swift)" but not "the swift dog" 25 | const searchApi = new SearchApi({ 26 | indexMode: INDEX_MODES.EXACT_WORDS, 27 | tokenizePattern: /[^a-z0-9]+/, 28 | caseSensitive: true 29 | }) 30 | ``` 31 | 32 | #### 1.1.1 33 | * 🐛 Replaced `for..of` with `forEach` in order to support IE 11. ([@jrubins](https://github.com/jrubins) - [#6](https://github.com/bvaughn/js-worker-search/pull/6)) 34 | 35 | #### 1.1.0 36 | Added support for custom index strategies. 37 | By default, a prefix matching strategy is still used but it can be overridden like so: 38 | 39 | ```js 40 | import SearchApi, { INDEX_MODES } from 'js-worker-search' 41 | 42 | // all-substrings match by default; same as current 43 | // eg "c", "ca", "a", "at", "cat" match "cat" 44 | const searchApi = new SearchApi() 45 | 46 | // prefix matching (eg "c", "ca", "cat" match "cat") 47 | const searchApi = new SearchApi({ 48 | indexMode: INDEX_MODES.PREFIXES 49 | }) 50 | 51 | // exact words matching (eg only "cat" matches "cat") 52 | const searchApi = new SearchApi({ 53 | indexMode: INDEX_MODES.EXACT_WORDS 54 | }) 55 | ``` 56 | 57 | #### 1.0.2 58 | Wrapped `String.prototype.charAt` usage in a `try/catch` to avoid erroring when handling surrogate halves. 59 | In the context of a web-worker, non-BMP characters seem to cause Chrome 49.0 to crash. 60 | 61 | #### 1.0.1 62 | Bound indexSearch and search methods to prevent them from losing context when passed around. 63 | 64 | # 1.0.0 65 | Initial release; forked from [redux-search](https://github.com/treasure-data/redux-search). 66 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Treasure Data 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # js-worker-search 2 | 3 | ![NPM version](https://img.shields.io/npm/v/js-worker-search.svg) 4 | ![NPM license](https://img.shields.io/npm/l/js-worker-search.svg) 5 | ![NPM total downloads](https://img.shields.io/npm/dt/js-worker-search.svg) 6 | ![NPM monthly downloads](https://img.shields.io/npm/dm/js-worker-search.svg) 7 | 8 | --- 9 | ### 🎉 [Become a sponsor](https://github.com/sponsors/bvaughn/) or ☕ [Buy me a coffee](http://givebrian.coffee/) 10 | --- 11 | 12 | Full text client-side search based on [js-search](https://github.com/bvaughn/js-search) but with added web-worker support for better performance. 13 | 14 | Check out the [redux-search](https://bvaughn.github.io/redux-search/) for an example integration. 15 | 16 | Or install it yourself with NPM: 17 | 18 | ``` 19 | npm install --save js-worker-search 20 | ``` 21 | 22 | SearchApi Documentation 23 | ------ 24 | 25 | Forked from [JS search](github.com/bvaughn/js-search), this utility builds a search index and runs actual searches. It auto-detects the capabilities of the current environment (browser or Node) and uses a web-worker based implementation when possible. When no web-worker support is available searching is done on the main (UI) thread. 26 | 27 | SearchApi defines the following public methods: 28 | 29 | ##### `constructor ({ caseSensitive, indexMode, tokenizePattern })` 30 | By default, `SearchApi` builds an index to match all substrings. 31 | You can override this behavior by passing an named `indexMode` parameter. 32 | Valid values are `INDEX_MODES.ALL_SUBSTRINGS`, `INDEX_MODES.EXACT_WORDS`, and `INDEX_MODES.PREFIXES`. 33 | 34 | Searches are case insensitive by default and split on all whitespace characters. Read below for more information on customizing default options. 35 | 36 | ##### `indexDocument (uid, text)` 37 | 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. 38 | 39 | Parameters: 40 | * **uid**: Uniquely identifies a searchable object 41 | * **text**: Searchable text to associate with the uid 42 | 43 | ##### `search(query)` 44 | 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. 45 | 46 | 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"). 47 | 48 | Parameters: 49 | * **query**: Searchable query text 50 | 51 | This method will return an array of uids. 52 | 53 | ##### `terminate()` 54 | If search is running in a web worker, this will terminate the worker to allow for garbage collection. 55 | 56 | Example Usage 57 | ------ 58 | 59 | Use the API like so: 60 | 61 | ```javascript 62 | import SearchApi from 'js-worker-search' 63 | 64 | const searchApi = new SearchApi() 65 | 66 | // Index as many objects as you want. 67 | // Objects are identified by an id (the first parameter). 68 | // Each Object can be indexed multiple times (once per string of related text). 69 | searchApi.indexDocument('foo', 'Text describing an Object identified as "foo"') 70 | searchApi.indexDocument('bar', 'Text describing an Object identified as "bar"') 71 | 72 | // Search for matching documents using the `search` method. 73 | // In this case the promise will be resolved with the Array ['foo', 'bar']. 74 | // This is because the word "describing" appears in both indices. 75 | const promise = searchApi.search('describing') 76 | ``` 77 | 78 | ### Custom index mode 79 | By default, `SearchApi` builds an index to match all substrings. 80 | You can override this behavior by passing an `indexMode` parameter to the constructor like so: 81 | 82 | ```js 83 | import SearchApi, { INDEX_MODES } from 'js-worker-search' 84 | 85 | // all-substrings match by default; same as current 86 | // eg "c", "ca", "a", "at", "cat" match "cat" 87 | const searchApi = new SearchApi() 88 | 89 | // prefix matching (eg "c", "ca", "cat" match "cat") 90 | const searchApi = new SearchApi({ 91 | indexMode: INDEX_MODES.PREFIXES 92 | }) 93 | 94 | // exact words matching (eg only "cat" matches "cat") 95 | const searchApi = new SearchApi({ 96 | indexMode: INDEX_MODES.EXACT_WORDS 97 | }) 98 | ``` 99 | 100 | ### Custom tokenizer patterns 101 | By default, `SearchApi` breaks text into words (tokenizes) using spaces and newlines 102 | as the delimiting character. If you want to provide your own splitting rule, pass a 103 | RegExp to the constructor that defines the pattern , like so: 104 | 105 | ```js 106 | // Custom tokenizer pattern to include all non alphanumerics as delimeters 107 | // ex: searching "Swift" matches "Thomas Swift" and "Thomas (Swift)" but not "swiftly tilting" 108 | const searchApi = new SearchApi({ 109 | indexMode: INDEX_MODES.EXACT_WORDS, 110 | tokenizePattern: /[^a-z0-9]+/, 111 | }) 112 | ``` 113 | 114 | ### Case-sensitive searches 115 | The default sanitizer performs a case-insensitive search. If you want to override that 116 | behavior and do a case-sensitive search, set the caseSensitive bit to true, like so: 117 | 118 | ```js 119 | // custom sanitizer for case-sensitive searches 120 | const searchApi = new SearchApi({ 121 | caseSensitive: true 122 | }) 123 | ``` 124 | 125 | ### Partial matches 126 | By default, the search utility only returns documents containing every search token. 127 | It can be configured to return documents containing any search token. 128 | 129 | ```js 130 | // Change search behavior from AND to OR 131 | const searchApi = new SearchApi({ 132 | matchAnyToken: true 133 | }) 134 | ``` 135 | 136 | Changelog 137 | --------- 138 | 139 | Changes are tracked in the [changelog](CHANGELOG.md). 140 | 141 | License 142 | --------- 143 | 144 | js-worker-search is available under the MIT License. 145 | -------------------------------------------------------------------------------- /benchmarks/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | /.bower 3 | /.idea 4 | /bower_components 5 | /node_modules 6 | /npm-debug.log 7 | /dist 8 | -------------------------------------------------------------------------------- /benchmarks/README.md: -------------------------------------------------------------------------------- 1 | These benchmark tests measure real usage of js-search. 2 | They enable simple performance comparisons between the latest released build and the local build. 3 | To run a benchmark: 4 | 5 | ```bash 6 | cd /path/to/js-search 7 | yarn install 8 | 9 | # Build local js-search (to incorporate any changes you've made) 10 | npm run build 11 | 12 | cd ./benchmarks 13 | yarn install 14 | 15 | # Run a benchmark of your choice 16 | # eg Compare your local build of js-search to the latest released build 17 | node ./create-index.js 18 | node ./search.js 19 | ``` 20 | 21 | You can also compare two local builds of js-search: 22 | 23 | ```bash 24 | # Assumes you've run `yarn install` in both directories prior 25 | 26 | cd /path/to/js-search 27 | 28 | # Make some changes and then build js-search 29 | npm run build 30 | 31 | # Move your build into the benchmarks folder as the 'latest' 32 | cp -r ./dist ./benchmarks/node_modules/js-search/ 33 | 34 | # Make more changes and then re-build js-search 35 | npm run build 36 | 37 | # Compare your most recent build to the previous one 38 | cd ./benchmarks 39 | node ./create-index.js 40 | node ./search.js 41 | ``` -------------------------------------------------------------------------------- /benchmarks/books.json: -------------------------------------------------------------------------------- 1 | { 2 | "books": [ 3 | { 4 | "isbn": "0520203259", 5 | "title": "Hellenistic history and culture", 6 | "author": "Peter Green" 7 | }, 8 | { 9 | "isbn": "0300088108", 10 | "title": "Plutarch", 11 | "author": "Robert Lamberton" 12 | }, 13 | { 14 | "isbn": "0520204832", 15 | "title": "Studies in Greek culture and Roman policy", 16 | "author": "Erich S. Gruen" 17 | }, 18 | { 19 | "isbn": "0819163015", 20 | "title": "Past and future in ancient history", 21 | "author": "Chester G. Starr" 22 | }, 23 | { 24 | "isbn": "0140445625", 25 | "title": "A short account of the destruction of the Indies", 26 | "author": "Bartolomé de las Casas" 27 | }, 28 | { 29 | "isbn": "0521228298", 30 | "title": "The Hellenistic world from Alexander to the Roman conquest : a selection of ancient sources in transla", 31 | "author": "M. M Austin" 32 | }, 33 | { 34 | "isbn": "0500400288", 35 | "title": "Philip II and Macedonian imperialism", 36 | "author": "John R. Ellis" 37 | }, 38 | { 39 | "isbn": "0713990597", 40 | "title": "The historical figure of Jesus", 41 | "author": "E. P. Sanders" 42 | }, 43 | { 44 | "isbn": "1555406211", 45 | "title": "Latin in American schools : teaching the ancient world", 46 | "author": "Sally Davis" 47 | }, 48 | { 49 | "isbn": "0691055491", 50 | "title": "In the shadow of Olympus : the emergence of Macedon", 51 | "author": "Eugene N. Borza" 52 | }, 53 | { 54 | "isbn": "0486206459", 55 | "title": "Virtruvius, the ten books on architecture,", 56 | "author": "Vitruvius Pollio" 57 | }, 58 | { 59 | "isbn": "0801846196", 60 | "title": "The Search for the ancient novel", 61 | "author": "James Tatum" 62 | }, 63 | { 64 | "isbn": "0800631641", 65 | "title": "Herod : king of the Jews and friend of the Romans", 66 | "author": "Peter Richardson" 67 | }, 68 | { 69 | "isbn": "0773508376", 70 | "title": "Theopompus the historian", 71 | "author": "Gordon Spencer Shrimpton" 72 | }, 73 | { 74 | "isbn": "0472064185", 75 | "title": "Hellenism in Late Antiquity", 76 | "author": "G. W. Bowersock" 77 | }, 78 | { 79 | "isbn": "0198148283", 80 | "title": "A historical commentary on Arrian's History of Alexander", 81 | "author": "A. B. Bosworth" 82 | }, 83 | { 84 | "isbn": "0195076184", 85 | "title": "The Oxford companion to archaeology", 86 | "author": "Brian M. Fagan" 87 | }, 88 | { 89 | "isbn": "0691067570", 90 | "title": "Xenophon's imperial fiction : on the education of Cyrus", 91 | "author": "James Tatum" 92 | }, 93 | { 94 | "isbn": "0691040605", 95 | "title": "The Periplus Maris Erythraei : text with introduction, translation, and commentary", 96 | "author": "Lionel Casson" 97 | }, 98 | { 99 | "isbn": "0520085205", 100 | "title": "Moral vision in the Histories of Polybius", 101 | "author": "Arthur M. Eckstein" 102 | }, 103 | { 104 | "isbn": "080706792X", 105 | "title": "The myth of matriarchal prehistory : why an invented past won't give women a future", 106 | "author": "Cynthia Eller" 107 | }, 108 | { 109 | "isbn": "037550432X", 110 | "title": "Route 66 A.D. : on the trail of ancient Roman tourists", 111 | "author": "Tony Perrottet" 112 | }, 113 | { 114 | "isbn": "0520063198", 115 | "title": "A history of Macedonia", 116 | "author": "R. M. Errington" 117 | }, 118 | { 119 | "isbn": "0631174729", 120 | "title": "A history of ancient Egypt", 121 | "author": "Nicolas-Christophe Grimal" 122 | }, 123 | { 124 | "isbn": "0674474910", 125 | "title": "The Jews in the Greek Age", 126 | "author": "E. J. Bickerman" 127 | }, 128 | { 129 | "isbn": "0520069811", 130 | "title": "Polybius, by F. W. Walbank", 131 | "author": "F. W. Walbank" 132 | }, 133 | { 134 | "isbn": "0292750315", 135 | "title": "Magic, witchcraft, and curing", 136 | "author": "John Middleton, comp" 137 | }, 138 | { 139 | "isbn": "0815550294", 140 | "title": "The interpretation of dreams = Oneirocritica", 141 | "author": "Daldianus Artemidorus" 142 | }, 143 | { 144 | "isbn": "0517277956", 145 | "title": "The lost books of the Bible : being all the Gospels, Epistles, and other pieces now extant attributed", 146 | "author": "William Hone" 147 | }, 148 | { 149 | "isbn": "0684106027", 150 | "title": "Religion and the decline of magic [by] Keith Thomas", 151 | "author": "Keith Thomas" 152 | }, 153 | { 154 | "isbn": "0395549671", 155 | "title": "Oracle bones, stars, and wheelbarrows : ancient Chinese science and technology", 156 | "author": "Frank Xavier Ross" 157 | }, 158 | { 159 | "isbn": "0691000921", 160 | "title": "The hieroglyphics of Horapollo", 161 | "author": "Horapollo" 162 | }, 163 | { 164 | "isbn": "0851154468", 165 | "title": "A history of western astrology", 166 | "author": "S. J. Tester" 167 | }, 168 | { 169 | "isbn": "0393005445", 170 | "title": "The Hellenistic age; aspects of Hellenistic civilization, treated by J. B. Bury ... E. A. Barber ...", 171 | "author": "J. B. Bury" 172 | }, 173 | { 174 | "isbn": "0691057508", 175 | "title": "Religions of late antiquity in practice", 176 | "author": "Richard Valantasis" 177 | }, 178 | { 179 | "isbn": "0415202078", 180 | "title": "Magic in the Roman world : pagans, Jews, and Christians", 181 | "author": "Naomi Janowitz" 182 | }, 183 | { 184 | "isbn": "0520020219", 185 | "title": "The occult sciences in the Renaissance; a study in intellectual patterns", 186 | "author": "Wayne Shumaker" 187 | }, 188 | { 189 | "isbn": "0226075915", 190 | "title": "Love between women : early Christian responses to female homoeroticism", 191 | "author": "Bernadette J. Brooten" 192 | }, 193 | { 194 | "isbn": "0891303464", 195 | "title": "Sources for the study of Greek religion", 196 | "author": "David Gerard Rice" 197 | }, 198 | { 199 | "isbn": "0807611379", 200 | "title": "Witchcraft at Salem", 201 | "author": "Chadwick Hansen" 202 | }, 203 | { 204 | "isbn": "0394554957", 205 | "title": "Pagans and Christians", 206 | "author": "Robin Lane Fox" 207 | }, 208 | { 209 | "isbn": "0674936868", 210 | "title": "The Victorians and ancient Greece", 211 | "author": "Richard Jenkyns" 212 | }, 213 | { 214 | "isbn": "0226067270", 215 | "title": "Mesopotamia : writing, reasoning, and the gods", 216 | "author": "Jean Bottéro" 217 | }, 218 | { 219 | "isbn": "067464364X", 220 | "title": "The orientalizing revolution : Near Eastern influence on Greek culture in the early archaic age", 221 | "author": "Walter Burkert" 222 | }, 223 | { 224 | "isbn": "0472087622", 225 | "title": "The rise and fall of states according to Greek authors", 226 | "author": "Jacqueline de Romilly" 227 | }, 228 | { 229 | "isbn": "0521785766", 230 | "title": "Magic in the Middle Ages", 231 | "author": "Richard Kieckhefer" 232 | }, 233 | { 234 | "isbn": "0890895759", 235 | "title": "The world of classical myth : gods and goddesses, heroines and heroes", 236 | "author": "Carl A. P. Ruck" 237 | }, 238 | { 239 | "isbn": "1395564727", 240 | "title": "The origin of consciousness in the breakdown of the bicameral mind", 241 | "author": "Julian Jaynes" 242 | }, 243 | { 244 | "isbn": "0415249821", 245 | "title": "Magic and magicians in the Greco-Roman world", 246 | "author": "Matthew Dickie" 247 | }, 248 | { 249 | "isbn": "0195141830", 250 | "title": "Lost Christianities : the battle for Scripture and the faiths we never knew", 251 | "author": "Bart D. Ehrman" 252 | }, 253 | { 254 | "isbn": "0819551732", 255 | "title": "On pagans, Jews, and Christians", 256 | "author": "Arnaldo Momigliano" 257 | }, 258 | { 259 | "isbn": "0195141822", 260 | "title": "Lost scriptures : books that did not make it into the New Testament", 261 | "author": "Bart D Ehrman" 262 | }, 263 | { 264 | "isbn": "0674750756", 265 | "title": "Rebecca's children : Judaism and Christianity in the Roman world", 266 | "author": "Alan F. Segal" 267 | }, 268 | { 269 | "isbn": "0195115678", 270 | "title": "A preface to Mark : notes on the Gospel in its literary and cultural settings", 271 | "author": "Christopher Bryan" 272 | }, 273 | { 274 | "isbn": "019825153X", 275 | "title": "Roman society and Roman law in the New Testament", 276 | "author": "A. N. Sherwin-White" 277 | }, 278 | { 279 | "isbn": "0517055902", 280 | "title": "How to read the Bible : two volumes in one", 281 | "author": "Etienne Charpentier" 282 | }, 283 | { 284 | "isbn": "0195141571", 285 | "title": "Saint Saul : a skeleton key to the historical Jesus", 286 | "author": "Donald H. Akenson" 287 | }, 288 | { 289 | "isbn": "0195124731", 290 | "title": "Jesus, apocalyptic prophet of the new millennium", 291 | "author": "Bart D. Ehrman" 292 | }, 293 | { 294 | "isbn": "3110146932", 295 | "title": "Introduction to the New Testament", 296 | "author": "Helmut Koester" 297 | }, 298 | { 299 | "isbn": "0385264259", 300 | "title": "A marginal Jew : rethinking the historical Jesus", 301 | "author": "John P. Meier" 302 | }, 303 | { 304 | "isbn": "0830815449", 305 | "title": "The Jesus quest : the third search for the Jew of Nazareth", 306 | "author": "Ben Witherington, III" 307 | }, 308 | { 309 | "isbn": "0195126394", 310 | "title": "The New Testament : a historical introduction to the early Christian writings", 311 | "author": "Bart D. Ehrman" 312 | }, 313 | { 314 | "isbn": "0809125366", 315 | "title": "In the days of Jesus : the Jewish background and unique teaching of Jesus", 316 | "author": "Anthony J. Tambasco" 317 | }, 318 | { 319 | "isbn": "0140135022", 320 | "title": "The Romans", 321 | "author": "R. H. Barrow, b. 1893" 322 | }, 323 | { 324 | "isbn": "0198810016", 325 | "title": "The Roman revolution", 326 | "author": "Ronald Syme, Sir" 327 | }, 328 | { 329 | "isbn": "0895220261", 330 | "title": "The Ides of March", 331 | "author": "Naphtali Lewis" 332 | }, 333 | { 334 | "isbn": "0404574661", 335 | "title": "Sexual life in ancient Rome", 336 | "author": "Otto Kiefer" 337 | }, 338 | { 339 | "isbn": "9754790892", 340 | "title": "Lycia: Western Section of the Southern Coast of Turkey", 341 | "author": "Ülgür Önen" 342 | }, 343 | { 344 | "isbn": "0674663241", 345 | "title": "Persuasions of the witch's craft : ritual magic in contemporary England", 346 | "author": "T. M. Luhrmann" 347 | }, 348 | { 349 | "isbn": "0715617478", 350 | "title": "Suetonius, the scholar and his Caesars", 351 | "author": "Andrew Wallace-Hadrill" 352 | }, 353 | { 354 | "isbn": "067451193X", 355 | "title": "The later Roman empire, AD 284-430", 356 | "author": "Averil Cameron" 357 | }, 358 | { 359 | "isbn": "0801837014", 360 | "title": "The social history of Rome", 361 | "author": "Géza Alföldy" 362 | }, 363 | { 364 | "isbn": "0674777700", 365 | "title": "The Roman Empire", 366 | "author": "C. M. Wells" 367 | }, 368 | { 369 | "isbn": "0415096782", 370 | "title": "Spectacles of death in ancient Rome", 371 | "author": "Donald G. Kyle" 372 | }, 373 | { 374 | "isbn": "0520201531", 375 | "title": "The last generation of the Roman Republic [by] Erich S. Gruen", 376 | "author": "Erich S. Gruen" 377 | }, 378 | { 379 | "isbn": "080184200X", 380 | "title": "The Roman family", 381 | "author": "Suzanne Dixon" 382 | }, 383 | { 384 | "isbn": "0674779274", 385 | "title": "The Roman republic", 386 | "author": "Michael H. Crawford" 387 | }, 388 | { 389 | "isbn": "0312199589", 390 | "title": "Romans and Barbarians : four views from the empire's edge, 1st century A.D.", 391 | "author": "Derek Williams" 392 | }, 393 | { 394 | "isbn": "039587596X", 395 | "title": "The road to Ubar : finding the Atlantis of the sands", 396 | "author": "Nicholas Clapp" 397 | }, 398 | { 399 | "isbn": "1556112556", 400 | "title": "The Marvell College murders : a novel", 401 | "author": "Sophie Belfort" 402 | }, 403 | { 404 | "isbn": "1555831680", 405 | "title": "In the land of Alexander : gay travels, with history and politics, in Hungary, Yugoslavia, Turkey, and", 406 | "author": "Keith Hale" 407 | }, 408 | { 409 | "isbn": "0674010191", 410 | "title": "The Pantheon : design, meaning, and progeny", 411 | "author": "William Lloyd MacDonald" 412 | }, 413 | { 414 | "isbn": "0385486472", 415 | "title": "When in Rome : a journal of life in Vatican City", 416 | "author": "Robert J. Hutchinson" 417 | }, 418 | { 419 | "isbn": "0060799048", 420 | "title": "Walking the Bible : a photographic journey", 421 | "author": "Bruce S. Feiler" 422 | }, 423 | { 424 | "isbn": "0714119105", 425 | "title": "How to read Egyptian hieroglyphs : a step-by-step guide to teach yourself", 426 | "author": "Mark Collier" 427 | }, 428 | { 429 | "isbn": "0684810522", 430 | "title": "Noah's flood : the new scientific discoveries about the event that changed history", 431 | "author": "William B. F. Ryan" 432 | }, 433 | { 434 | "isbn": "0140244611", 435 | "title": "Istanbul", 436 | "author": "John Freely" 437 | }, 438 | { 439 | "isbn": "1858288770", 440 | "title": "The rough guide to Portugal", 441 | "author": "Mark Ellingham" 442 | }, 443 | { 444 | "isbn": "0671779974", 445 | "title": "Turkish reflections : a biography of a place", 446 | "author": "Mary Lee Settle" 447 | }, 448 | { 449 | "isbn": "0670800074", 450 | "title": "Journey to Kars", 451 | "author": "Philip Glazebrook" 452 | }, 453 | { 454 | "isbn": "0198140959", 455 | "title": "The kingdom of the Hittites", 456 | "author": "Trevor Bryce" 457 | }, 458 | { 459 | "isbn": "1565412176", 460 | "title": "Life in Alanya : Turkish delight", 461 | "author": "George Crews McGhee" 462 | }, 463 | { 464 | "isbn": "0486212181", 465 | "title": "Personal narrative of a pilgrimage to al-Madinah & Meccah, by Sir Richard F. Burton. Edited by his wif", 466 | "author": "Richard Francis Burton, Sir" 467 | }, 468 | { 469 | "isbn": "0521633842", 470 | "title": "The rise and rule of Tamerlane", 471 | "author": "Beatrice Forbes Manz" 472 | }, 473 | { 474 | "isbn": "0192802992", 475 | "title": "Sacred signs : hieroglyphs in ancient Egypt", 476 | "author": "Penelope Wilson" 477 | }, 478 | { 479 | "isbn": "0140062718", 480 | "title": "The rebel angels", 481 | "author": "Robertson Davies" 482 | }, 483 | { 484 | "isbn": "0156001314", 485 | "title": "The name of the rose", 486 | "author": "Umberto Eco" 487 | }, 488 | { 489 | "isbn": "1400031427", 490 | "title": "In search of Zarathustra : the first prophet and the ideas that changed the world", 491 | "author": "Paul Kriwaczek" 492 | }, 493 | { 494 | "isbn": "0716730901", 495 | "title": "Why people believe weird things : pseudoscience, superstition, and other confusions of our time", 496 | "author": "Michael Shermer" 497 | }, 498 | { 499 | "isbn": "0802116116", 500 | "title": "One thousand roads to Mecca : ten centuries of travelers writing about the Muslim pilgrimage", 501 | "author": "Michael Wolfe" 502 | }, 503 | { 504 | "isbn": "0195116674", 505 | "title": "Oliver Wendell Holmes : sage of the Supreme Court", 506 | "author": "G. Edward White" 507 | }, 508 | { 509 | "isbn": "0802713424", 510 | "title": "null", 511 | "author": "Tom Standage" 512 | }, 513 | { 514 | "isbn": "071954663X", 515 | "title": "Turkey beyond the Maeander", 516 | "author": "George Ewart Bean" 517 | }, 518 | { 519 | "isbn": "0395328047", 520 | "title": "A history of Western society", 521 | "author": "John P. McKay" 522 | }, 523 | { 524 | "isbn": "0810928191", 525 | "title": "Knossos : searching for the legendary palace of King Minos", 526 | "author": "Alexandre Farnoux" 527 | }, 528 | { 529 | "isbn": "0810928396", 530 | "title": "In search of ancient Rome", 531 | "author": "Claude Moatti" 532 | }, 533 | { 534 | "isbn": "0460107887", 535 | "title": "Leadership and the cult of the personality", 536 | "author": "Jane F Gardner" 537 | }, 538 | { 539 | "isbn": "0520231236", 540 | "title": "Spartan reflections", 541 | "author": "Paul Cartledge" 542 | }, 543 | { 544 | "isbn": "0684828154", 545 | "title": "The landmark Thucydides : a comprehensive guide to the Peloponnesian War", 546 | "author": "Thucydides" 547 | }, 548 | { 549 | "isbn": "0801413672", 550 | "title": "The Peace of Nicias and the Sicilian Expedition", 551 | "author": "Donald Kagan" 552 | }, 553 | { 554 | "isbn": "0684863952", 555 | "title": "Pericles of Athens and the birth of democracy", 556 | "author": "Donald Kagan" 557 | }, 558 | { 559 | "isbn": "0674587383", 560 | "title": "Moses the Egyptian : the memory of Egypt in western monotheism", 561 | "author": "Jan Assmann" 562 | }, 563 | { 564 | "isbn": "0810928868", 565 | "title": "Mummies : a voyage through eternity", 566 | "author": "Françoise Dunand" 567 | }, 568 | { 569 | "isbn": "0816027617", 570 | "title": "Pirates! : brigands, buccaneers, and privateers in fact, fiction, and legend", 571 | "author": "Jan Rogoziânski" 572 | }, 573 | { 574 | "isbn": "157392251X", 575 | "title": "The love songs of Sappho", 576 | "author": "Sappho" 577 | }, 578 | { 579 | "isbn": "0404146678", 580 | "title": "The mercenaries of the Hellenistic world", 581 | "author": "G. T. Griffith" 582 | }, 583 | { 584 | "isbn": "1400040930", 585 | "title": "Of paradise and power : America and Europe in the new world order", 586 | "author": "Robert Kagan" 587 | }, 588 | { 589 | "isbn": "0312261764", 590 | "title": "The illustrated story of copyright", 591 | "author": "Edward B. Samuels" 592 | }, 593 | { 594 | "isbn": "0764504606", 595 | "title": "Perl for dummies", 596 | "author": "Paul Hoffman" 597 | }, 598 | { 599 | "isbn": "0735712565", 600 | "title": "Search engine visibility", 601 | "author": "Shari Thurow" 602 | }, 603 | { 604 | "isbn": "0451527933", 605 | "title": "The metamorphoses", 606 | "author": "Ovid" 607 | }, 608 | { 609 | "isbn": "0888444702", 610 | "title": "The Vulgate commentary on Ovid's Metamorphoses : the creation myth and the story of Orpheus", 611 | "author": "Frank Thomas Coulson" 612 | }, 613 | { 614 | "isbn": "0870230395", 615 | "title": "The Symposium of Plato. Translated by Suzy Q. Groden. Edited by John A. Brentlinger. Drawings by Leona", 616 | "author": "Plato" 617 | }, 618 | { 619 | "isbn": "0767917367", 620 | "title": "The telephone booth Indian", 621 | "author": "A. J. Liebling" 622 | }, 623 | { 624 | "isbn": "0385419945", 625 | "title": "Silicon snake oil : second thoughts on the information highway", 626 | "author": "Clifford Stoll" 627 | }, 628 | { 629 | "isbn": "0618319387", 630 | "title": "The nature of science : an A-Z guide to the laws and principles governing our universe", 631 | "author": "James S. Trefil" 632 | }, 633 | { 634 | "isbn": "067400602X", 635 | "title": "Oversold and underused : computers in the classroom", 636 | "author": "Larry Cuban" 637 | }, 638 | { 639 | "isbn": "1587991039", 640 | "title": "Technomanifestos : visions from the information revolutionaries", 641 | "author": "Adam Brate" 642 | }, 643 | { 644 | "isbn": "0281055513", 645 | "title": "The resurrection of the Son of God", 646 | "author": "N. T. Wright" 647 | }, 648 | { 649 | "isbn": "0738205435", 650 | "title": "Small pieces, loosely joined : a unified theory of the web", 651 | "author": "David Weinberger" 652 | }, 653 | { 654 | "isbn": "0851709680", 655 | "title": "The Shawshank redemption", 656 | "author": "Mark Kermode" 657 | }, 658 | { 659 | "isbn": "0395891698", 660 | "title": "The war against parents : what we can do for America's beleaguered moms and dads", 661 | "author": "Sylvia Ann Hewlett" 662 | }, 663 | { 664 | "isbn": "0374525749", 665 | "title": "The Odyssey", 666 | "author": "Homer" 667 | }, 668 | { 669 | "isbn": "1568984308", 670 | "title": "You are here : personal geographies and other maps of the imagination", 671 | "author": "Katharine A. Harmon" 672 | }, 673 | { 674 | "isbn": "0671793853", 675 | "title": "Time detectives : how archeologists use technology to recapture the past", 676 | "author": "Brian M. Fagan" 677 | }, 678 | { 679 | "isbn": "0802713912", 680 | "title": "The Turk : the life and times of the famous eighteenth-century chess-playing machine", 681 | "author": "Tom Standage" 682 | }, 683 | { 684 | "isbn": "0345372050", 685 | "title": "You just don't understand : women and men in conversation", 686 | "author": "Deborah Tannen" 687 | }, 688 | { 689 | "isbn": "0375501517", 690 | "title": "The island of lost maps : a true story of cartographic crime", 691 | "author": "Miles Harvey" 692 | }, 693 | { 694 | "isbn": "019512023X", 695 | "title": "Plutarch's Advice to the bride and groom, and A consolation to his wife : English translations, commen", 696 | "author": "Sarah B. Pomeroy" 697 | }, 698 | { 699 | "isbn": "0060984031", 700 | "title": "The third chimpanzee : the evolution and future of the human animal", 701 | "author": "Jared M. Diamond" 702 | }, 703 | { 704 | "isbn": "0155948911", 705 | "title": "Vice & virtue in everyday life : introductory readings in ethics", 706 | "author": "Christina Hoff Sommers" 707 | }, 708 | { 709 | "isbn": "0618313370", 710 | "title": "Toward a new Catholic Church : the promise of reform", 711 | "author": "James Carroll" 712 | }, 713 | { 714 | "isbn": "0192852108", 715 | "title": "A history of heresy", 716 | "author": "David Christie-Murray" 717 | }, 718 | { 719 | "isbn": "0877936536", 720 | "title": "This is our faith : a Catholic catechism for adults", 721 | "author": "Michael Pennock" 722 | }, 723 | { 724 | "isbn": "0618226478", 725 | "title": "The new dictionary of cultural literacy", 726 | "author": "E. D. Hirsch" 727 | }, 728 | { 729 | "isbn": "1565927699", 730 | "title": "PHP pocket reference", 731 | "author": "Rasmus Lerdorf" 732 | }, 733 | { 734 | "isbn": "0195283481", 735 | "title": "The new Oxford annotated Bible with the Apocrypha : Revised standard version, containing the second ed", 736 | "author": "Herbert Gordon May" 737 | }, 738 | { 739 | "isbn": "081090117X", 740 | "title": "The new illustrated encyclopedia of world history", 741 | "author": "William L. Langer" 742 | }, 743 | { 744 | "isbn": "0078825598", 745 | "title": "HTML programmer's reference", 746 | "author": "Thomas A. Powell" 747 | }, 748 | { 749 | "isbn": "0596000480", 750 | "title": "JavaScript : the definitive guide", 751 | "author": "David Flanagan" 752 | }, 753 | { 754 | "isbn": "1565926102", 755 | "title": "Programming PHP", 756 | "author": "Rasmus Lerdorf" 757 | }, 758 | { 759 | "isbn": "1565926811", 760 | "title": "PHP cookbook", 761 | "author": "David Sklar" 762 | }, 763 | { 764 | "isbn": "0596006365", 765 | "title": "Upgrading to PHP 5", 766 | "author": "Adam Trachtenberg" 767 | }, 768 | { 769 | "isbn": "0674387260", 770 | "title": "The hellenistic world", 771 | "author": "F. W. Walbank" 772 | }, 773 | { 774 | "isbn": "0195065883", 775 | "title": "The Western way of war : infantry battle in classical Greece", 776 | "author": "Victor Davis Hanson" 777 | }, 778 | { 779 | "isbn": "0140132678", 780 | "title": "The life of Andrew Jackson", 781 | "author": "Robert Vincent Remini" 782 | }, 783 | { 784 | "isbn": "0198219156", 785 | "title": "The Legacy of Greece : a new appraisal", 786 | "author": "M. I. Finley" 787 | }, 788 | { 789 | "isbn": "0030785006", 790 | "title": "The nature of historical inquiry. Edited by Leonard M. Marsak", 791 | "author": "Leonard Mendes Marsak" 792 | }, 793 | { 794 | "isbn": "014013686X", 795 | "title": "The world of Odysseus [by] M.I. Finley", 796 | "author": "M. I. Finley" 797 | }, 798 | { 799 | "isbn": "0801486335", 800 | "title": "Writing ancient history", 801 | "author": "Neville Morley" 802 | }, 803 | { 804 | "isbn": "0140552251", 805 | "title": "The use and abuse of history", 806 | "author": "M. I. Finley" 807 | }, 808 | { 809 | "isbn": "0520024478", 810 | "title": "Researching and writing in history; a practical handbook for students, by F. N. McCoy", 811 | "author": "Florence N. McCoy" 812 | }, 813 | { 814 | "isbn": "0806119381", 815 | "title": "The historians of Greece and Rome", 816 | "author": "Stephen Usher" 817 | }, 818 | { 819 | "isbn": "0801411165", 820 | "title": "Knowledge and explanation in history : an introduction to the philosophy of history", 821 | "author": "R. F. Atkinson" 822 | }, 823 | { 824 | "isbn": "0684844451", 825 | "title": "The killing of history : how literary critics and social theorists are murdering our past", 826 | "author": "Keith Windschuttle" 827 | }, 828 | { 829 | "isbn": "0393046877", 830 | "title": "In defense of history", 831 | "author": "Richard J. Evans" 832 | }, 833 | { 834 | "isbn": "0807842621", 835 | "title": "Women and law in classical Greece", 836 | "author": "Raphael Sealey" 837 | }, 838 | { 839 | "isbn": "0807823090", 840 | "title": "News and society in the Greek polis", 841 | "author": "Sian Lewis" 842 | }, 843 | { 844 | "isbn": "0226072789", 845 | "title": "Historiography : ancient, medieval & modern", 846 | "author": "Ernst Breisach" 847 | }, 848 | { 849 | "isbn": "0674615808", 850 | "title": "The new history and the old", 851 | "author": "Gertrude Himmelfarb" 852 | }, 853 | { 854 | "isbn": "0764555898", 855 | "title": "PHP and MySQL for dummies", 856 | "author": "Janet Valade" 857 | }, 858 | { 859 | "isbn": "0521406730", 860 | "title": "Politics in the ancient world", 861 | "author": "M. I. Finley" 862 | }, 863 | { 864 | "isbn": "0879518987", 865 | "title": "Turkey unveiled : a history of modern Turkey", 866 | "author": "Nicole Pope" 867 | }, 868 | { 869 | "isbn": "0688088058", 870 | "title": "The hole in the flag : a Romanian exile's story of return and revolution", 871 | "author": "Andrei Codrescu" 872 | }, 873 | { 874 | "isbn": "0738209015", 875 | "title": "The last best league : one summer, one season, one dream", 876 | "author": "Jim Collins" 877 | }, 878 | { 879 | "isbn": "0679759239", 880 | "title": "On looking into the Abyss : untimely thoughts on culture and society", 881 | "author": "Gertrude Himmelfarb" 882 | }, 883 | { 884 | "isbn": "039545185X", 885 | "title": "Landslide : the unmaking of the President, 1984-1988", 886 | "author": "Jane Mayer" 887 | }, 888 | { 889 | "isbn": "0192852949", 890 | "title": "The Oxford illustrated history of the Crusades", 891 | "author": "Jonathan Simon Christopher Riley-Smith" 892 | }, 893 | { 894 | "isbn": "0385504713", 895 | "title": "Reagan's war : the epic story of his forty year struggle and final triumph over communism", 896 | "author": "Peter Schweizer" 897 | }, 898 | { 899 | "isbn": "0715631942", 900 | "title": "The oracles of the ancient world : a complete guide", 901 | "author": "Trevor Curnow" 902 | }, 903 | { 904 | "isbn": "0821211080", 905 | "title": "The Search for Alexander : an exhibition", 906 | "author": "Nikolaos Gialoures" 907 | }, 908 | { 909 | "isbn": "0520074319", 910 | "title": "Reading the past : ancient writing from cuneiform to the alphabet", 911 | "author": "J. T Hooker" 912 | }, 913 | { 914 | "isbn": "0688080936", 915 | "title": "The Ottoman centuries : the rise and fall of the Turkish Empire", 916 | "author": "Patrick Balfour Kinross, Baron" 917 | }, 918 | { 919 | "isbn": "006621288X", 920 | "title": "Love in the asylum : a novel", 921 | "author": "Lisa Carey" 922 | }, 923 | { 924 | "isbn": "0380976749", 925 | "title": "The mermaids singing", 926 | "author": "Lisa Carey" 927 | }, 928 | { 929 | "isbn": "075370000X", 930 | "title": "Persian Mythology", 931 | "author": "John Hinnells" 932 | }, 933 | { 934 | "isbn": "0596006179", 935 | "title": "Learning UNIX for Mac OS X Panther", 936 | "author": "Dave Taylor" 937 | }, 938 | { 939 | "isbn": "0674404254", 940 | "title": "Hitlers World View : A Blueprint for Power", 941 | "author": "Herbert Arnold" 942 | }, 943 | { 944 | "isbn": "0674991753", 945 | "title": "The histories, with an English translation by W. R. Paton. In six volumes", 946 | "author": "Polybius" 947 | }, 948 | { 949 | "isbn": "0140440399", 950 | "title": "Thucydides, with an English translation by C. Forster Smith ... in four volumes ... History of the", 951 | "author": "Thucydides" 952 | }, 953 | { 954 | "isbn": "067499535X", 955 | "title": "Historical miscellany", 956 | "author": "3rd cent Aelian" 957 | }, 958 | { 959 | "isbn": "0521306965", 960 | "title": "A Hellenistic anthology", 961 | "author": "Neil Hopkinson" 962 | }, 963 | { 964 | "isbn": "0385248326", 965 | "title": "The New Jerusalem Bible : reader's edition", 966 | "author": "" 967 | }, 968 | { 969 | "isbn": "0879737018", 970 | "title": "Relics", 971 | "author": "Joan Carroll Cruz" 972 | }, 973 | { 974 | "isbn": "069108923X", 975 | "title": "One true God : historical consequences of monotheism", 976 | "author": "Rodney Stark" 977 | }, 978 | { 979 | "isbn": "0060633980", 980 | "title": "What makes us Catholic : eight gifts for life", 981 | "author": "Thomas H. Groome" 982 | }, 983 | { 984 | "isbn": "0618134298", 985 | "title": "Why I am a Catholic", 986 | "author": "Garry Wills" 987 | }, 988 | { 989 | "isbn": "0912141824", 990 | "title": "Wrath of God: The days of the Antichrist", 991 | "author": "Livio Fanzaga" 992 | }, 993 | { 994 | "isbn": "0393004759", 995 | "title": "Pagan Mysteries in the Renaissance.", 996 | "author": "Edgar Wind" 997 | }, 998 | { 999 | "isbn": "0679450319", 1000 | "title": "The kidnapping of Edgardo Mortara", 1001 | "author": "David I. Kertzer" 1002 | }, 1003 | { 1004 | "isbn": "051732170X", 1005 | "title": "Who's who in the Old Testament / Who's who in the Bible : two volumes in one", 1006 | "author": "Joan Comay" 1007 | }, 1008 | { 1009 | "isbn": "0674932609", 1010 | "title": "Inside the Vatican : the politics and organization of the Catholic Church", 1011 | "author": "Thomas J. Reese" 1012 | }, 1013 | { 1014 | "isbn": "0395709369", 1015 | "title": "Women in scripture : a dictionary of named and unnamed women in the Hebrew Bible, the", 1016 | "author": "Carol L. Meyers" 1017 | }, 1018 | { 1019 | "isbn": "067483996X", 1020 | "title": "Strategy : the logic of war and peace", 1021 | "author": "Edward Luttwak" 1022 | }, 1023 | { 1024 | "isbn": "0750914378", 1025 | "title": "Jacobite Spy Wars", 1026 | "author": "Hugh Douglas" 1027 | }, 1028 | { 1029 | "isbn": "156619430X", 1030 | "title": "Sparta [by] A. H. M. Jones", 1031 | "author": "A. H. M. Jones" 1032 | }, 1033 | { 1034 | "isbn": "1578517087", 1035 | "title": "The social life of information", 1036 | "author": "John Seely Brown" 1037 | }, 1038 | { 1039 | "isbn": "0674541510", 1040 | "title": "Magic in the ancient world", 1041 | "author": "Fritz Graf" 1042 | }, 1043 | { 1044 | "isbn": "0596001320", 1045 | "title": "Learning Perl", 1046 | "author": "Randal L. Schwartz" 1047 | }, 1048 | { 1049 | "isbn": "0764506927", 1050 | "title": "XML for dummies", 1051 | "author": "Ed Tittel" 1052 | }, 1053 | { 1054 | "isbn": "067232525X", 1055 | "title": "PHP and MySQL Web development", 1056 | "author": "Luke Welling" 1057 | }, 1058 | { 1059 | "isbn": "0520031830", 1060 | "title": "Latin: An Intensive Course", 1061 | "author": "Rita Fleischer" 1062 | }, 1063 | { 1064 | "isbn": "0500276161", 1065 | "title": "In search of the Indo-Europeans : language, archaeology, and myth", 1066 | "author": "J. P. Mallory" 1067 | }, 1068 | { 1069 | "isbn": "0520231929", 1070 | "title": "In the Footsteps of Alexander The Great: A Journey from Greece to Asia", 1071 | "author": "Michael Wood" 1072 | }, 1073 | { 1074 | "isbn": "0394491130", 1075 | "title": "The nature of Alexander", 1076 | "author": "Mary Renault" 1077 | }, 1078 | { 1079 | "isbn": "091514820X", 1080 | "title": "The road to Eleusis : unveiling the secret of the mysteries", 1081 | "author": "R. Gordon Wasson" 1082 | }, 1083 | { 1084 | "isbn": "0714120782", 1085 | "title": "Mesopotamia", 1086 | "author": "Julian Reade" 1087 | }, 1088 | { 1089 | "isbn": "0192852485", 1090 | "title": "The Oxford history of the Roman world", 1091 | "author": "John Boardman" 1092 | }, 1093 | { 1094 | "isbn": "1401902405", 1095 | "title": "How to ruin your love life", 1096 | "author": "Benjamin Stein" 1097 | }, 1098 | { 1099 | "isbn": "0393318648", 1100 | "title": "Shackleton's Boat Journey", 1101 | "author": "Frank Arthur Worsley" 1102 | }, 1103 | { 1104 | "isbn": "0486277844", 1105 | "title": "Selected poems", 1106 | "author": "George Gordon Byron Byron, Baron" 1107 | }, 1108 | { 1109 | "isbn": "0385501145", 1110 | "title": "The songs of the kings", 1111 | "author": "Barry Unsworth" 1112 | }, 1113 | { 1114 | "isbn": "0380976757", 1115 | "title": "In the country of the young", 1116 | "author": "Lisa Carey" 1117 | }, 1118 | { 1119 | "isbn": "0819151505", 1120 | "title": "The poems of Catullus : a teaching text", 1121 | "author": "Phyllis Young Forsyth" 1122 | }, 1123 | { 1124 | "isbn": "053111693X", 1125 | "title": "Slavery in ancient Greece and Rome", 1126 | "author": "Jacqueline Dembar Greene" 1127 | }, 1128 | { 1129 | "isbn": "0674543203", 1130 | "title": "The making of late antiquity", 1131 | "author": "Peter Robert Lamont Brown" 1132 | }, 1133 | { 1134 | "isbn": "0596000359", 1135 | "title": "Information architecture for the World Wide Web", 1136 | "author": "Louis Rosenfeld" 1137 | }, 1138 | { 1139 | "isbn": "0195087070", 1140 | "title": "The Oxford history of the biblical world", 1141 | "author": "Michael David Coogan" 1142 | }, 1143 | { 1144 | "isbn": "0195046455", 1145 | "title": "The Oxford companion to the Bible", 1146 | "author": "Bruce Manning Metzger" 1147 | }, 1148 | { 1149 | "isbn": "0805040811", 1150 | "title": "Lords of the horizons : a history of the Ottoman Empire", 1151 | "author": "Jason Goodwin" 1152 | }, 1153 | { 1154 | "isbn": "037571457X", 1155 | "title": "Persepolis: The Story of a Childhood", 1156 | "author": "Marjane Satrapi" 1157 | }, 1158 | { 1159 | "isbn": "0395952832", 1160 | "title": "Sheba : through the desert in search of the legendary queen", 1161 | "author": "Nicholas Clapp" 1162 | }, 1163 | { 1164 | "isbn": "048626551X", 1165 | "title": "The Napoleon of Notting Hill", 1166 | "author": "G. K. Chesterton" 1167 | }, 1168 | { 1169 | "isbn": "0061315451", 1170 | "title": "Historians' Fallacies : Toward a Logic of Historical Thought", 1171 | "author": "David H. Fischer" 1172 | }, 1173 | { 1174 | "isbn": "1874166382", 1175 | "title": "Introducing Derrida", 1176 | "author": "Jeff Collins" 1177 | }, 1178 | { 1179 | "isbn": "0394580486", 1180 | "title": "The Romanovs : the final chapter", 1181 | "author": "Robert K. Massie" 1182 | }, 1183 | { 1184 | "isbn": "0596002114", 1185 | "title": "Managing and Using MySQL (2nd Edition)", 1186 | "author": "George Reese" 1187 | }, 1188 | { 1189 | "isbn": "0596003064", 1190 | "title": "High performance MySQL : optimization, backups, replication, and load balancing", 1191 | "author": "Jeremy D. Zawodny" 1192 | }, 1193 | { 1194 | "isbn": "0156495899", 1195 | "title": "Lectures on literature", 1196 | "author": "Vladimir Vladimirovich Nabokov" 1197 | }, 1198 | { 1199 | "isbn": "0156495910", 1200 | "title": "Lectures on Russian Literature", 1201 | "author": "Vladimir Nabokov" 1202 | }, 1203 | { 1204 | "isbn": "0060953497", 1205 | "title": "The Keys of Egypt: The Race to Crack the Hieroglyph Code", 1206 | "author": "Lesley Adkins" 1207 | }, 1208 | { 1209 | "isbn": "0060937319", 1210 | "title": "A People's History of the United States: 1492 to Present", 1211 | "author": "Howard Zinn" 1212 | }, 1213 | { 1214 | "isbn": "141162551X", 1215 | "title": "The Web Developer's Guide to Amazon E-commerce Service: Developing Web Applications Using Amazon Web Services And Php", 1216 | "author": "Jason Levitt" 1217 | }, 1218 | { 1219 | "isbn": "0749306459", 1220 | "title": "Tiger! Tiger!", 1221 | "author": "Alfred Bester" 1222 | }, 1223 | { 1224 | "isbn": "0716744732", 1225 | "title": "The power of Babel : a natural history of language", 1226 | "author": "John H. McWhorter" 1227 | }, 1228 | { 1229 | "isbn": "0140167935", 1230 | "title": "The Manticore (Deptford Trilogy)", 1231 | "author": "Robertson Davies" 1232 | }, 1233 | { 1234 | "isbn": "074323491X", 1235 | "title": "Perelandra (Space Trilogy (Paperback))", 1236 | "author": "C.S. Lewis" 1237 | }, 1238 | { 1239 | "isbn": "1585674028", 1240 | "title": "The Spartans", 1241 | "author": "Paul Cartledge" 1242 | }, 1243 | { 1244 | "isbn": "1413302157", 1245 | "title": "Whoops! I'm In Business: A Crash Course In Business Basics", 1246 | "author": "Richard Stim" 1247 | }, 1248 | { 1249 | "isbn": "0764552775", 1250 | "title": "Public Relations Kit for Dummies", 1251 | "author": "Eric Yaverbaum" 1252 | }, 1253 | { 1254 | "isbn": "0764550934", 1255 | "title": "Small Business Kit for Dummies", 1256 | "author": "Richard D. Harroch" 1257 | }, 1258 | { 1259 | "isbn": "0672326736", 1260 | "title": "MySQL (3rd Edition) (Developer's Library)", 1261 | "author": "Paul DuBois" 1262 | }, 1263 | { 1264 | "isbn": "0764541471", 1265 | "title": "UNIX for Dummies", 1266 | "author": "John R. Levine" 1267 | }, 1268 | { 1269 | "isbn": "0553562614", 1270 | "title": "Snow crash", 1271 | "author": "Neal Stephenson" 1272 | }, 1273 | { 1274 | "isbn": "014016796X", 1275 | "title": "World of wonders", 1276 | "author": "Robertson Davies" 1277 | }, 1278 | { 1279 | "isbn": "0553804022", 1280 | "title": "No True Glory : A Frontline Account of the Battle for Fallujah", 1281 | "author": "Bing West" 1282 | }, 1283 | { 1284 | "isbn": "1401302378", 1285 | "title": "The Long Tail: Why the Future of Business Is Selling Less of More", 1286 | "author": "Chris Anderson" 1287 | }, 1288 | { 1289 | "isbn": "1893329186", 1290 | "title": "Hungry? Boston: The Lowdown on Where the Real People Eat!", 1291 | "author": "Esti Iturralde" 1292 | }, 1293 | { 1294 | "isbn": "0299198006", 1295 | "title": "Legendary Ireland : Journey Through Celtic Places and Myths", 1296 | "author": "Eithne Massey" 1297 | }, 1298 | { 1299 | "isbn": "0761120149", 1300 | "title": "How to Grill: The Complete Illustrated Book of Barbecue Techniques", 1301 | "author": "Steven Raichlen" 1302 | }, 1303 | { 1304 | "isbn": "1881957322", 1305 | "title": "Macperl: Power and Ease", 1306 | "author": "Vicki Brown" 1307 | }, 1308 | { 1309 | "isbn": "1565924878", 1310 | "title": "Java in a Nutshell : A Desktop Quick Reference (Java Series) (3rd Edition)", 1311 | "author": "David Flanagan" 1312 | }, 1313 | { 1314 | "isbn": "0735712859", 1315 | "title": "Inside JavaScript", 1316 | "author": "Steven Holzner" 1317 | }, 1318 | { 1319 | "isbn": "1565924789", 1320 | "title": "Programming Web Graphics with Perl & GNU Software (O'Reilly Nutshell)", 1321 | "author": "Shawn Wallace" 1322 | }, 1323 | { 1324 | "isbn": "1595940146", 1325 | "title": "Your Safari Dragons: In Search of the Real Komodo Dragon", 1326 | "author": "Daniel White" 1327 | }, 1328 | { 1329 | "isbn": "0596002890", 1330 | "title": "Mastering Regular Expressions, Second Edition", 1331 | "author": "Jeffrey E F Friedl" 1332 | }, 1333 | { 1334 | "isbn": "0253325552", 1335 | "title": "Turkish traditional art today", 1336 | "author": "Henry H. Glassie" 1337 | }, 1338 | { 1339 | "isbn": "0395177111", 1340 | "title": "The Hobbit", 1341 | "author": "J.R.R. Tolkien" 1342 | }, 1343 | { 1344 | "isbn": "0151957479", 1345 | "title": "The Western Canon: The Books and School of the Ages", 1346 | "author": "Harold Bloom" 1347 | }, 1348 | { 1349 | "isbn": "395177111", 1350 | "title": "The hobbit, or, There and back again", 1351 | "author": "J. R. R. Tolkien" 1352 | }, 1353 | { 1354 | "isbn": "034542204X", 1355 | "title": "Tales of the Cthulhu mythos", 1356 | "author": "H. P. Lovecraft" 1357 | }, 1358 | { 1359 | "isbn": "0375727345", 1360 | "title": "House of Sand and Fog (Oprah's Book Club)", 1361 | "author": "Andre Dubus III" 1362 | }, 1363 | { 1364 | "isbn": "0246974710", 1365 | "title": "The poems of Catullus;", 1366 | "author": "Gaius Valerius Catullus" 1367 | }, 1368 | { 1369 | "isbn": "0671005073", 1370 | "title": "Plays of Sophocles", 1371 | "author": "William Walter" 1372 | }, 1373 | { 1374 | "isbn": "0486295060", 1375 | "title": "The war of the worlds", 1376 | "author": "H. G. Wells" 1377 | }, 1378 | { 1379 | "isbn": "0684844532", 1380 | "title": "Who killed Homer? : the demise of classical education and the recovery of Greek wisdom", 1381 | "author": "Victor Davis Hanson" 1382 | }, 1383 | { 1384 | "isbn": "0786866594", 1385 | "title": "Wings of madness : Alberto Santos-Dumont and the invention of flight", 1386 | "author": "Paul Hoffman" 1387 | }, 1388 | { 1389 | "isbn": "0715612581", 1390 | "title": "Primer of Greek Grammar", 1391 | "author": "Evelyn Abbott" 1392 | }, 1393 | { 1394 | "isbn": "0064601048", 1395 | "title": "Latin an Introductory Course", 1396 | "author": "Frederic M Wheelock" 1397 | }, 1398 | { 1399 | "isbn": "0715614703", 1400 | "title": "Latin phrase-book", 1401 | "author": "Carl Meissner" 1402 | }, 1403 | { 1404 | "isbn": "0817303502", 1405 | "title": "A Latin grammar", 1406 | "author": "William Gardner Hale" 1407 | }, 1408 | { 1409 | "isbn": "0140443185", 1410 | "title": "Rome and the Mediterranean : books XXXI-XLV of The history of Rome from its foundation", 1411 | "author": "Livy" 1412 | }, 1413 | { 1414 | "isbn": "0140443622", 1415 | "title": "The rise of the Roman Empire", 1416 | "author": "Polybius" 1417 | }, 1418 | { 1419 | "isbn": "0140440070", 1420 | "title": "The Persian expedition", 1421 | "author": "Xenophon" 1422 | }, 1423 | { 1424 | "isbn": "0140443088", 1425 | "title": "Lives of the later Caesars : the first part of the Augustan history : with newly compiled Lives of Nerva and Trajan", 1426 | "author": "" 1427 | }, 1428 | { 1429 | "isbn": "0140441743", 1430 | "title": "Thyestes; Phaedra; The Trojan women; Oedipus; with, Octavia;", 1431 | "author": "Lucius Annaeus Seneca" 1432 | }, 1433 | { 1434 | "isbn": "0140440720", 1435 | "title": "The twelve Caesars", 1436 | "author": "Suetonius" 1437 | }, 1438 | { 1439 | "isbn": "0140442790", 1440 | "title": "The satires of Horace and Persius;", 1441 | "author": "Horace" 1442 | }, 1443 | { 1444 | "isbn": "1892284901", 1445 | "title": "Night Moves and Other Stories", 1446 | "author": "Tim Powers" 1447 | }, 1448 | { 1449 | "isbn": "0140444637", 1450 | "title": "Plutarch on Sparta (Penguin Classics)", 1451 | "author": "Plutarch" 1452 | }, 1453 | { 1454 | "isbn": "0226469409", 1455 | "title": "The Iliad", 1456 | "author": "Homer" 1457 | }, 1458 | { 1459 | "isbn": "0345440919", 1460 | "title": "Pavane", 1461 | "author": "Keith Roberts" 1462 | }, 1463 | { 1464 | "isbn": "0140444254", 1465 | "title": "The three Theban plays", 1466 | "author": "Sophocles" 1467 | }, 1468 | { 1469 | "isbn": "0140441328", 1470 | "title": "The Jugurthine War & The Conspiracy of Catiline", 1471 | "author": "Sallust" 1472 | }, 1473 | { 1474 | "isbn": "0140445234", 1475 | "title": "The Idylls", 1476 | "author": "Theocritus" 1477 | }, 1478 | { 1479 | "isbn": "0460871528", 1480 | "title": "The republic", 1481 | "author": "Plato" 1482 | }, 1483 | { 1484 | "isbn": "0140440488", 1485 | "title": "The Republic", 1486 | "author": "Plato" 1487 | }, 1488 | { 1489 | "isbn": "0192831208", 1490 | "title": "Seven commentaries on the Gallic war", 1491 | "author": "Julius Caesar" 1492 | }, 1493 | { 1494 | "isbn": "0140441492", 1495 | "title": "The Pot of Gold and Other Plays (Classics S.)", 1496 | "author": "Plautus" 1497 | }, 1498 | { 1499 | "isbn": "0140440585", 1500 | "title": "Metamorphoses (Penguin Classics ed.)", 1501 | "author": "Ovid" 1502 | }, 1503 | { 1504 | "isbn": "0140441948", 1505 | "title": "Sixteen Satires (Classics S.)", 1506 | "author": "Juvenal" 1507 | }, 1508 | { 1509 | "isbn": "0140440038", 1510 | "title": "The Theban plays", 1511 | "author": "Sophocles" 1512 | }, 1513 | { 1514 | "isbn": "0140441166", 1515 | "title": "Les liaisons dangereuses", 1516 | "author": "Choderlos de Laclos" 1517 | }, 1518 | { 1519 | "isbn": "1853260258", 1520 | "title": "The Odyssey (Wordsworth Classics)", 1521 | "author": "Homer" 1522 | }, 1523 | { 1524 | "isbn": "0140621318", 1525 | "title": "The Prisoner of Zenda (Penguin Popular Classics)", 1526 | "author": "Anthony Hope" 1527 | }, 1528 | { 1529 | "isbn": "1853266175", 1530 | "title": "Priapeia", 1531 | "author": "L C Smithers" 1532 | }, 1533 | { 1534 | "isbn": "0198140983", 1535 | "title": "The Oxford history of Byzantium", 1536 | "author": "" 1537 | }, 1538 | { 1539 | "isbn": "0691008566", 1540 | "title": "Islamic history : a framework for inquiry", 1541 | "author": "R. Stephen Humphreys" 1542 | }, 1543 | { 1544 | "isbn": "0393046729", 1545 | "title": "A rage to live : a biography of Richard and Isabel Burton", 1546 | "author": "Mary S. Lovell" 1547 | }, 1548 | { 1549 | "isbn": "0812925750", 1550 | "title": "A struggle for power : the American revolution", 1551 | "author": "Theodore Draper" 1552 | }, 1553 | { 1554 | "isbn": "0691017549", 1555 | "title": "The Muqaddimah, an introduction to history", 1556 | "author": "Ibn Khaldˆun" 1557 | }, 1558 | { 1559 | "isbn": "0312239246", 1560 | "title": "The Sappho companion", 1561 | "author": "" 1562 | }, 1563 | { 1564 | "isbn": "0679311017", 1565 | "title": "Spoken here : travels among threatened languages", 1566 | "author": "Mark Abley" 1567 | }, 1568 | { 1569 | "isbn": "0195144201", 1570 | "title": "What went wrong? : Western impact and Middle Eastern response", 1571 | "author": "Bernard Lewis" 1572 | }, 1573 | { 1574 | "isbn": "0801859557", 1575 | "title": "The Portuguese empire, 1415-1808 : a world on the move", 1576 | "author": "A. J. R. Russell-Wood" 1577 | }, 1578 | { 1579 | "isbn": "0618127380", 1580 | "title": "The Italian Renaissance", 1581 | "author": "J. H. Plumb" 1582 | }, 1583 | { 1584 | "isbn": "0140077022", 1585 | "title": "White noise", 1586 | "author": "Don DeLillo" 1587 | }, 1588 | { 1589 | "isbn": "0813511984", 1590 | "title": "History of the Byzantine State", 1591 | "author": "Georgije Ostrogorski" 1592 | }, 1593 | { 1594 | "isbn": "0486255948", 1595 | "title": "Treasury of Turkish designs : 670 motifs from Iznik pottery", 1596 | "author": "Azade Akar" 1597 | }, 1598 | { 1599 | "isbn": "0151007098", 1600 | "title": "The object-lesson", 1601 | "author": "Edward Gorey" 1602 | }, 1603 | { 1604 | "isbn": "0140442367", 1605 | "title": "The pillow book of Sei Shonagon", 1606 | "author": "Sei Shonagon" 1607 | }, 1608 | { 1609 | "isbn": "0486426858", 1610 | "title": "The Master of Ballantrae", 1611 | "author": "Robert Louis Stevenson" 1612 | }, 1613 | { 1614 | "isbn": "0385418957", 1615 | "title": "Seven Pillars of Wisdom", 1616 | "author": "T. E. Lawrence" 1617 | }, 1618 | { 1619 | "isbn": "0460877992", 1620 | "title": "Robert Herrick (Everyman Poetry Library)", 1621 | "author": "Robert Herrick" 1622 | }, 1623 | { 1624 | "isbn": "1565921496", 1625 | "title": "Programming Perl", 1626 | "author": "Larry Wall" 1627 | }, 1628 | { 1629 | "isbn": "1400049636", 1630 | "title": "There Must Be a Pony in Here Somewhere: The AOL Time Warner Debacle and the Quest for a Digital Future", 1631 | "author": "Kara Swisher" 1632 | }, 1633 | { 1634 | "isbn": "0812690699", 1635 | "title": "The machinery of freedom : guide to a radical capitalism", 1636 | "author": "David D. Friedman" 1637 | }, 1638 | { 1639 | "isbn": "047206035X", 1640 | "title": "The Life of Charlemagne (Ann Arbor Paperbacks)", 1641 | "author": "Einhard" 1642 | }, 1643 | { 1644 | "isbn": "0060392339", 1645 | "title": "Jen-X : Jenny McCarthy's open book", 1646 | "author": "Jenny McCarthy" 1647 | }, 1648 | { 1649 | "isbn": "0674471741", 1650 | "title": "Jane Austen", 1651 | "author": "Tony Tanner" 1652 | }, 1653 | { 1654 | "isbn": "067974102X", 1655 | "title": "My Life as Author and Editor", 1656 | "author": "H.L. Mencken" 1657 | }, 1658 | { 1659 | "isbn": "0809437228", 1660 | "title": "In Defense of Women (Time Reading Program Special Edition)", 1661 | "author": "H. L. Mencken" 1662 | }, 1663 | { 1664 | "isbn": "0460110977", 1665 | "title": "Mabinogian (2nd Edition)", 1666 | "author": "Jones" 1667 | }, 1668 | { 1669 | "isbn": "0195055373", 1670 | "title": "Revolutionary Dreams: Utopian Vision and Experimental Life in the Russian Revolution", 1671 | "author": "Richard Stites" 1672 | }, 1673 | { 1674 | "isbn": "0811200124", 1675 | "title": "Labyrinths: Selected Stories and Other Writings (New Directions Paperbook, 186)", 1676 | "author": "Jorge Luis Borges" 1677 | }, 1678 | { 1679 | "isbn": "0140440267", 1680 | "title": "The Lusiads", 1681 | "author": "Luís de Camões" 1682 | }, 1683 | { 1684 | "isbn": "0140442405", 1685 | "title": "Praise of folly; and, Letter to Martin Dorp, 1515", 1686 | "author": "Desiderius Erasmus" 1687 | }, 1688 | { 1689 | "isbn": "0674690052", 1690 | "title": "Population, Disease, and Land in Early Japan, 645-900 (Harvard-Yenching Institute Monograph Series)", 1691 | "author": "William Wayne Farris" 1692 | }, 1693 | { 1694 | "isbn": "0028461703", 1695 | "title": "MORAL AND POLITICAL PHILOSOPHY (Hafner Library of Classics)", 1696 | "author": "David Hume" 1697 | }, 1698 | { 1699 | "isbn": "0140047840", 1700 | "title": "Nabokov, his life in part", 1701 | "author": "Andrew Field" 1702 | }, 1703 | { 1704 | "isbn": "0801491398", 1705 | "title": "Medieval political philosophy : a sourcebook", 1706 | "author": "Ralph Lerner" 1707 | }, 1708 | { 1709 | "isbn": "0449906043", 1710 | "title": "Karma Cola", 1711 | "author": "Gita Mehta" 1712 | }, 1713 | { 1714 | "isbn": "0060916400", 1715 | "title": "The other path : the invisible revolution in the Third World", 1716 | "author": "Hernando de Soto" 1717 | }, 1718 | { 1719 | "isbn": "0395735297", 1720 | "title": "War As I Knew It", 1721 | "author": "George S. Patton" 1722 | }, 1723 | { 1724 | "isbn": "0140441832", 1725 | "title": "King Harald's Saga: Harald Hardradi of Norway: From Snorri Sturluson's Heimskringla (Classics S.)", 1726 | "author": "Snorri Sturluson" 1727 | }, 1728 | { 1729 | "isbn": "0060914033", 1730 | "title": "The ides of March", 1731 | "author": "Thornton Wilder" 1732 | }, 1733 | { 1734 | "isbn": "0452264510", 1735 | "title": "The Prime of Miss Jean Brodie", 1736 | "author": "Muriel Spark" 1737 | }, 1738 | { 1739 | "isbn": "0451527631", 1740 | "title": "Looking backward : 2000-1887", 1741 | "author": "Edward Bellamy" 1742 | }, 1743 | { 1744 | "isbn": "039417299X", 1745 | "title": "Three Novels by Samuel Beckett: Molloy Malone Dies the Unnamable", 1746 | "author": "Samuel Beckett" 1747 | }, 1748 | { 1749 | "isbn": "043590003X", 1750 | "title": "No longer at ease", 1751 | "author": "Chinua Achebe" 1752 | }, 1753 | { 1754 | "isbn": "0449239195", 1755 | "title": "Things fall apart", 1756 | "author": "Chinua Achebe" 1757 | }, 1758 | { 1759 | "isbn": "0226468046", 1760 | "title": "Women, Fire, and Dangerous Things", 1761 | "author": "George Lakoff" 1762 | }, 1763 | { 1764 | "isbn": "0262522950", 1765 | "title": "Sorting Things Out: Classification and Its Consequences (Inside Technology)", 1766 | "author": "Geoffrey C. Bowker" 1767 | }, 1768 | { 1769 | "isbn": "0233972935", 1770 | "title": "Ordering the world : a history of classifying man", 1771 | "author": "David M. Knight" 1772 | }, 1773 | { 1774 | "isbn": "0425129616", 1775 | "title": "The Mysterious Affair at Styles", 1776 | "author": "Agatha Christie" 1777 | }, 1778 | { 1779 | "isbn": "0385500998", 1780 | "title": "The virtues of war : a novel of Alexander the Great", 1781 | "author": "Steven Pressfield" 1782 | }, 1783 | { 1784 | "isbn": "0140189866", 1785 | "title": "Species of Spaces and Other Pieces (Twentieth Century Classics)", 1786 | "author": "Georges Perec" 1787 | }, 1788 | { 1789 | "isbn": "0915179016", 1790 | "title": "How to start your own country : how you can profit from the coming decline of the nation state", 1791 | "author": "Erwin S. Strauss" 1792 | }, 1793 | { 1794 | "isbn": "0316164933", 1795 | "title": "The Perfect Store: Inside eBay", 1796 | "author": "Adam Cohen" 1797 | }, 1798 | { 1799 | "isbn": "0385489765", 1800 | "title": "High-Tech Heretic: Reflections of a Computer Contrarian", 1801 | "author": "Clifford Stoll" 1802 | }, 1803 | { 1804 | "isbn": "0781803950", 1805 | "title": "Treasury of Arabic Love: Poems, Quotations & Proverbs in Arabic and English", 1806 | "author": "" 1807 | }, 1808 | { 1809 | "isbn": "0198148291", 1810 | "title": "A Historical Commentary on Arrian's History of Alexander: Volume II: Commentary on Books IV-V", 1811 | "author": "A. B. Bosworth" 1812 | }, 1813 | { 1814 | "isbn": "0399239545", 1815 | "title": "Slowly, Slowly, Slowly Said The Sloth", 1816 | "author": "" 1817 | }, 1818 | { 1819 | "isbn": "0805079769", 1820 | "title": "Hidden Iran : paradox and power in the Islamic Republic", 1821 | "author": "Ray Takeyh" 1822 | }, 1823 | { 1824 | "isbn": "0226458075", 1825 | "title": "The Structure of Scientific Revolutions", 1826 | "author": "Thomas S. Kuhn" 1827 | }, 1828 | { 1829 | "isbn": "0345421949", 1830 | "title": "Roads not taken : tales of alternate history", 1831 | "author": "Stanley Schmidt" 1832 | }, 1833 | { 1834 | "isbn": "047009608X", 1835 | "title": "Second Life: The Official Guide", 1836 | "author": "Michael Rymaszewski" 1837 | }, 1838 | { 1839 | "isbn": "9789078779018", 1840 | "title": "Risen - Why libraries are here to stay", 1841 | "author": "Bastiaan F. Zwaan" 1842 | }, 1843 | { 1844 | "isbn": "0596529325", 1845 | "title": "Programming Collective Intelligence: Building Smart Web 2.0 Applications", 1846 | "author": "Toby Segaran" 1847 | }, 1848 | { 1849 | "isbn": "0674018621", 1850 | "title": "The highly civilized man : Richard Burton and the Victorian world", 1851 | "author": "Dane Keith. Kennedy" 1852 | }, 1853 | { 1854 | "isbn": "0321529170", 1855 | "title": "Tagging: People-powered Metadata for the Social Web (Voices That Matter)", 1856 | "author": "Gene Smith" 1857 | }, 1858 | { 1859 | "isbn": "0596002815", 1860 | "title": "Learning Python", 1861 | "author": "Mark Lutz" 1862 | }, 1863 | { 1864 | "isbn": "0691058873", 1865 | "title": "The Horse, the Wheel, and Language: How Bronze-Age Riders from the Eurasian Steppes Shaped the Modern World", 1866 | "author": "David W. Anthony" 1867 | }, 1868 | { 1869 | "isbn": "1594201536", 1870 | "title": "Here Comes Everybody: The Power of Organizing Without Organizations", 1871 | "author": "Clay Shirky" 1872 | }, 1873 | { 1874 | "isbn": "0674013859", 1875 | "title": "Historical atlas of Islam", 1876 | "author": "Malise Ruthven" 1877 | }, 1878 | { 1879 | "isbn": "9780199262137", 1880 | "title": "War in human civilization", 1881 | "author": "Azar Gat" 1882 | }, 1883 | { 1884 | "isbn": "158234518X", 1885 | "title": "Zeus: A Journey Through Greece in the Footsteps of a God", 1886 | "author": "Tom Stone" 1887 | }, 1888 | { 1889 | "isbn": "0500051410", 1890 | "title": "The Leopard's Tale: Revealing the Mysteries of Catalhoyuk", 1891 | "author": "Ian Hodder" 1892 | }, 1893 | { 1894 | "isbn": "0394800095", 1895 | "title": "The Whales Go By", 1896 | "author": "Phleger" 1897 | }, 1898 | { 1899 | "isbn": "0393064778", 1900 | "title": "The Hemingses of Monticello: An American Family", 1901 | "author": "Annette Gordon-Reed" 1902 | }, 1903 | { 1904 | "isbn": "0199285152", 1905 | "title": "The Legacy of Alexander: Politics, Warfare and Propaganda under the Successors", 1906 | "author": "A. B. Bosworth" 1907 | }, 1908 | { 1909 | "isbn": "1586833316", 1910 | "title": "Library Blogging", 1911 | "author": "Karen A. Coombs" 1912 | }, 1913 | { 1914 | "isbn": "9776163416", 1915 | "title": "Reflections on our digital future", 1916 | "author": "Ismail Serageldin" 1917 | }, 1918 | { 1919 | "isbn": "9776163483", 1920 | "title": "Women in Science: Time to Recognize the Obvious", 1921 | "author": "Ismail Serageldin" 1922 | }, 1923 | { 1924 | "isbn": "9776163998", 1925 | "title": "Much more than a building-- : reclaiming the legacy of the Bibliotheca Alexandrina", 1926 | "author": "Ismail Serageldin" 1927 | }, 1928 | { 1929 | "isbn": "1892391236", 1930 | "title": "Strange Itineraries", 1931 | "author": "Tim Powers" 1932 | }, 1933 | { 1934 | "isbn": "0385721706", 1935 | "title": "The Wisdom of Crowds", 1936 | "author": "James Surowiecki" 1937 | }, 1938 | { 1939 | "isbn": "0691024715", 1940 | "title": "Vladimir Nabokov : The American Years", 1941 | "author": "Brian Boyd" 1942 | }, 1943 | { 1944 | "isbn": "0691099006", 1945 | "title": "Mehmed the Conqueror and his time", 1946 | "author": "Franz Babinger" 1947 | }, 1948 | { 1949 | "isbn": "0060959355", 1950 | "title": "More Than You Know: A Novel", 1951 | "author": "Beth Gutcheon" 1952 | }, 1953 | { 1954 | "isbn": "0767420489", 1955 | "title": "How to Think About Weird Things: Critical Thinking for a New Age", 1956 | "author": "Theodore Schick" 1957 | }, 1958 | { 1959 | "isbn": "9780071549677", 1960 | "title": "How to do everything: Facebook applications", 1961 | "author": "Jesse Feiler" 1962 | }, 1963 | { 1964 | "isbn": "071561262X", 1965 | "title": "Julian the Apostate", 1966 | "author": "G. W. Bowersock" 1967 | }, 1968 | { 1969 | "isbn": "1400041988", 1970 | "title": "Pnin (Everyman's Library Classics & Contemporary Classics)", 1971 | "author": "Vladimir Nabokov" 1972 | }, 1973 | { 1974 | "isbn": "1883011191", 1975 | "title": "Nabokov: Novels 1955-1962: Lolita / Pnin / Pale Fire (Library of America)", 1976 | "author": "Vladimir Nabokov" 1977 | }, 1978 | { 1979 | "isbn": "0316015849", 1980 | "title": "Twilight (The Twilight Saga, Book 1)", 1981 | "author": "Stephenie Meyer" 1982 | }, 1983 | { 1984 | "isbn": "0756639808", 1985 | "title": "Real Sex for Real Women", 1986 | "author": "Laura Berman" 1987 | }, 1988 | { 1989 | "isbn": "0316043133", 1990 | "title": "Twilight: The Complete Illustrated Movie Companion", 1991 | "author": "" 1992 | }, 1993 | { 1994 | "isbn": "0307269639", 1995 | "title": "Nothing to Be Frightened Of", 1996 | "author": "Julian Barnes" 1997 | }, 1998 | { 1999 | "isbn": "0446579939", 2000 | "title": "The Lucky One", 2001 | "author": "Nicholas Sparks" 2002 | }, 2003 | { 2004 | "isbn": "1895523184", 2005 | "title": "The presidents' rap from Washington to Bush", 2006 | "author": "Sara Jordan" 2007 | }, 2008 | { 2009 | "isbn": "0545128285", 2010 | "title": "The Tales of Beedle the Bard, Standard Edition", 2011 | "author": "J. K. Rowling" 2012 | }, 2013 | { 2014 | "isbn": "0714848743", 2015 | "title": "Phaidon Atlas of 21st Century World Architecture", 2016 | "author": "Editors of Phaidon Press" 2017 | }, 2018 | { 2019 | "isbn": "8806193112", 2020 | "title": "Zona disagio", 2021 | "author": "Jonathan Franzen" 2022 | }, 2023 | { 2024 | "isbn": "8820046733", 2025 | "title": "La legione delle bambole. Un'inchiesta di Nathan Love", 2026 | "author": "Philip Le Roy" 2027 | }, 2028 | { 2029 | "isbn": "0385524382", 2030 | "title": "Sway: The Irresistible Pull of Irrational Behavior", 2031 | "author": "Ori Brafman" 2032 | }, 2033 | { 2034 | "isbn": "1573229156", 2035 | "title": "Three Apples Fell From Heaven", 2036 | "author": "Micheline Aharonian Marcom" 2037 | }, 2038 | { 2039 | "isbn": "0307101002", 2040 | "title": "Pooh Just Be Nice...to Your Little Friends! (Pooh - Just Be Nice Series)", 2041 | "author": "Caroline Kenneth" 2042 | }, 2043 | { 2044 | "isbn": "1400033888", 2045 | "title": "Istanbul: Memories and the City", 2046 | "author": "Orhan Pamuk" 2047 | }, 2048 | { 2049 | "isbn": "1931282528", 2050 | "title": "Incredible 5-Point Scale ¿ Assisting Students with Autism Spectrum Disorders in Understanding Social Interactions and Controlling Their Emotional Responses", 2051 | "author": "Kari Dunn Buron" 2052 | }, 2053 | { 2054 | "isbn": "0316043125", 2055 | "title": "The Twilight Saga: The Official Guide", 2056 | "author": "Stephenie Meyer" 2057 | }, 2058 | { 2059 | "isbn": "0061687200", 2060 | "title": "Marley & Me: Life and Love with the World's Worst Dog", 2061 | "author": "John Grogan" 2062 | }, 2063 | { 2064 | "isbn": "1894953479", 2065 | "title": "Installing Linux on a Dead Badger", 2066 | "author": "Lucy A. Snyder" 2067 | }, 2068 | { 2069 | "isbn": "0316031844", 2070 | "title": "The Twilight Saga: Slipcased", 2071 | "author": "Stephenie Meyer" 2072 | }, 2073 | { 2074 | "isbn": "0961392126", 2075 | "title": "Visual Explanations: Images and Quantities, Evidence and Narrative", 2076 | "author": "Edward R. Tufte" 2077 | }, 2078 | { 2079 | "isbn": "0961392142", 2080 | "title": "The Visual Display of Quantitative Information, 2nd edition", 2081 | "author": "Edward R. Tufte" 2082 | }, 2083 | { 2084 | "isbn": "0970661126", 2085 | "title": "Tickle His Pickle: Your Hands-On Guide to Penis Pleasing", 2086 | "author": "Sadie Allison" 2087 | }, 2088 | { 2089 | "isbn": "0520252667", 2090 | "title": "Mesopotamia: Assyrians, Sumerians, Babylonians", 2091 | "author": "Enrico Ascalone" 2092 | }, 2093 | { 2094 | "isbn": "0802713394", 2095 | "title": "Zarafa: a giraffe's true story, from deep in Africa to the heart of Paris", 2096 | "author": "Michael Allin" 2097 | }, 2098 | { 2099 | "isbn": "0844273341", 2100 | "title": "Practice Makes Perfect: Spanish Verb Tenses", 2101 | "author": "Dorothy Richmond" 2102 | }, 2103 | { 2104 | "isbn": "0199543305", 2105 | "title": "Little Oxford Dictionary of Quotations", 2106 | "author": "Susan Ratcliffe" 2107 | }, 2108 | { 2109 | "isbn": "0312263953", 2110 | "title": "Slightly Chipped: Footnotes in Booklore", 2111 | "author": "Lawrence Goldstone" 2112 | }, 2113 | { 2114 | "isbn": "1888553251", 2115 | "title": "Powder: Writing by Women in the Ranks, from Vietnam to Iraq", 2116 | "author": "Shannon Cain" 2117 | }, 2118 | { 2119 | "isbn": "1591842425", 2120 | "title": "Problem Solving 101: A Simple Book for Smart People", 2121 | "author": "Ken Watanabe" 2122 | }, 2123 | { 2124 | "isbn": "0143058754", 2125 | "title": "Mayflower: A Story of Courage, Community, and War", 2126 | "author": "Nathaniel Philbrick" 2127 | }, 2128 | { 2129 | "isbn": "0061686549", 2130 | "title": "What Have You Changed Your Mind About?: Today's Leading Minds Rethink Everything", 2131 | "author": "John Brockman" 2132 | }, 2133 | { 2134 | "isbn": "0978935403", 2135 | "title": "Not in a Thousand Years: The Uniqueness of the 20th Century", 2136 | "author": "Geoff Fernald" 2137 | }, 2138 | { 2139 | "isbn": "1847246869", 2140 | "title": "The landmark Herodotus: the histories", 2141 | "author": "Herodotus" 2142 | }, 2143 | { 2144 | "isbn": "019821913X", 2145 | "title": "The Legacy of Islam", 2146 | "author": "Joseph Schacht" 2147 | }, 2148 | { 2149 | "isbn": "0300120796", 2150 | "title": "Philip II of Macedonia", 2151 | "author": "Ian Worthington" 2152 | }, 2153 | { 2154 | "isbn": "0472110845", 2155 | "title": "Mountain and Plain: From the Lycian Coast to the Phrygian Plateau in the Late Roman and Early Byzantine Period", 2156 | "author": "Martin Harrison" 2157 | }, 2158 | { 2159 | "isbn": "1594743347", 2160 | "title": "Pride and Prejudice and Zombies: The Classic Regency Romance - Now with Ultraviolent Zombie Mayhem!", 2161 | "author": "Jane Austen" 2162 | }, 2163 | { 2164 | "isbn": "0307346609", 2165 | "title": "World War Z : an oral history of the zombie war", 2166 | "author": "Max Brooks" 2167 | }, 2168 | { 2169 | "isbn": "0156904365", 2170 | "title": "Till We Have Faces: A Myth Retold", 2171 | "author": "C.S. Lewis" 2172 | }, 2173 | { 2174 | "isbn": "0802141447", 2175 | "title": "Monster: The Autobiography of an L.A. Gang Member", 2176 | "author": "Sanyika Shakur" 2177 | }, 2178 | { 2179 | "isbn": "0345502833", 2180 | "title": "The Wednesday Sisters: A Novel", 2181 | "author": "Meg Waite Clayton" 2182 | }, 2183 | { 2184 | "isbn": "0747587035", 2185 | "title": "The ladies of Grace Adieu : and other stories", 2186 | "author": "Susanna Clarke" 2187 | }, 2188 | { 2189 | "isbn": "0981803989", 2190 | "title": "Just Fuck Me! - What Women Want Men to Know About Taking Control in the Bedroom (A Guide for Couples)", 2191 | "author": "Eve Kingsley" 2192 | }, 2193 | { 2194 | "isbn": "0316346624", 2195 | "title": "The Tipping Point: How Little Things Can Make a Big Difference", 2196 | "author": "Malcolm Gladwell" 2197 | }, 2198 | { 2199 | "isbn": "0451225856", 2200 | "title": "Lover Avenged (Black Dagger Brotherhood, Book 7)", 2201 | "author": "J.R. Ward" 2202 | }, 2203 | { 2204 | "isbn": "0786805838", 2205 | "title": "Whaley Whale (Thingy Things)", 2206 | "author": "Chris Raschka" 2207 | }, 2208 | { 2209 | "isbn": "9063531478", 2210 | "title": "Een huwelijk vol liefde", 2211 | "author": "Ed Wheat" 2212 | }, 2213 | { 2214 | "isbn": "0691006326", 2215 | "title": "The magician's doubts : Nabokov and the risks of fiction", 2216 | "author": "Michael Wood" 2217 | }, 2218 | { 2219 | "isbn": "0964380307", 2220 | "title": "Leif the lucky", 2221 | "author": "Ingri D'Aulaire" 2222 | }, 2223 | { 2224 | "isbn": "0807820202", 2225 | "title": "In the hands of Providence : Joshua L. Chamberlain and the American Civil War", 2226 | "author": "Alice Rains Trulock" 2227 | }, 2228 | { 2229 | "isbn": "0199552355", 2230 | "title": "The Last Man (Oxford World's Classics)", 2231 | "author": "Mary Wollstonecraft Shelley" 2232 | }, 2233 | { 2234 | "isbn": "0394588010", 2235 | "title": "A history of warfare", 2236 | "author": "John Keegan" 2237 | }, 2238 | { 2239 | "isbn": "0596517742", 2240 | "title": "JavaScript: The Good Parts", 2241 | "author": "Douglas Crockford" 2242 | }, 2243 | { 2244 | "isbn": "0520057376", 2245 | "title": "The Hellenistic World and the Coming of Rome", 2246 | "author": "Erich S. Gruen" 2247 | }, 2248 | { 2249 | "isbn": "1856079678", 2250 | "title": "Kenny's Choice: 101 Irish Books You Must Read", 2251 | "author": "Des Kenny" 2252 | }, 2253 | { 2254 | "isbn": "0486270556", 2255 | "title": "Six great Sherlock Holmes stories", 2256 | "author": "Sir Arthur Conan Doyle" 2257 | }, 2258 | { 2259 | "isbn": "0425030679", 2260 | "title": "Stranger In A Strange Land", 2261 | "author": "Robert A. Heinlein" 2262 | }, 2263 | { 2264 | "isbn": "9781934620069", 2265 | "title": "Make a zine! : when worlds and graphics collide", 2266 | "author": "Bill Brent" 2267 | }, 2268 | { 2269 | "isbn": "9780980200485", 2270 | "title": "So you want to be a librarian!", 2271 | "author": "Lauren Pressley" 2272 | }, 2273 | { 2274 | "isbn": "0981794106", 2275 | "title": "Stolen Sharpie Revolution 2: a DIY resource to zines and zine culture", 2276 | "author": "Alex Wrekk" 2277 | }, 2278 | { 2279 | "isbn": "0061142026", 2280 | "title": "Stardust", 2281 | "author": "Neil Gaiman" 2282 | }, 2283 | { 2284 | "isbn": "0299118045", 2285 | "title": "Wit and the writing of history : the rhetoric of historiography in imperial Rome", 2286 | "author": "Paul Plass" 2287 | }, 2288 | { 2289 | "isbn": "9781591842231", 2290 | "title": "Reality check : the irreverent guide to outsmarting, outmanaging, and outmarketing your competition", 2291 | "author": "Guy Kawasaki" 2292 | }, 2293 | { 2294 | "isbn": "0231148143", 2295 | "title": "The Late Age of Print: Everyday Book Culture from Consumerism to Control", 2296 | "author": "Ted Striphas" 2297 | }, 2298 | { 2299 | "isbn": "0140148221", 2300 | "title": "An imperial possession : Britain in the Roman Empire, 54 BC-AD 409", 2301 | "author": "D. J. Mattingly" 2302 | }, 2303 | { 2304 | "isbn": "0143114948", 2305 | "title": "Here Comes Everybody: The Power of Organizing Without Organizations", 2306 | "author": "Clay Shirky" 2307 | }, 2308 | { 2309 | "isbn": "0316826200", 2310 | "title": "Of Beetles and Angels: A Boy's Remarkable Journey from a Refugee Camp to Harvard", 2311 | "author": "Mawi Asgedom" 2312 | }, 2313 | { 2314 | "isbn": "0307346617", 2315 | "title": "World War Z: An Oral History of the Zombie War", 2316 | "author": "Max Brooks" 2317 | }, 2318 | { 2319 | "isbn": "0446509337", 2320 | "title": "Soul Survivor: The Reincarnation of a World War II Fighter Pilot", 2321 | "author": "Andrea Leininger" 2322 | }, 2323 | { 2324 | "isbn": "0785126708", 2325 | "title": "World War Hulk", 2326 | "author": "Greg Pak" 2327 | }, 2328 | { 2329 | "isbn": "0471488550", 2330 | "title": "Robust Regression and Outlier Detection (Wiley Series in Probability and Statistics)", 2331 | "author": "Peter J. Rousseeuw" 2332 | }, 2333 | { 2334 | "isbn": "0595132820", 2335 | "title": "The Outlier", 2336 | "author": "RJ Stanton" 2337 | }, 2338 | { 2339 | "isbn": "075663007X", 2340 | "title": "World War I (DK Eyewitness Books)", 2341 | "author": "Simon Adams" 2342 | }, 2343 | { 2344 | "isbn": "1574889990", 2345 | "title": "Untold Valor: Forgotten Stories of American Bomber Crews over Europe in World War II", 2346 | "author": "Rob Morris" 2347 | }, 2348 | { 2349 | "isbn": "0915572168", 2350 | "title": "Here comes everybody : new & selected poems", 2351 | "author": "Madeline Gleason" 2352 | }, 2353 | { 2354 | "isbn": "1400034728", 2355 | "title": "When Bad Things Happen to Good People", 2356 | "author": "Harold S. Kushner" 2357 | }, 2358 | { 2359 | "isbn": "0767902890", 2360 | "title": "The Things They Carried", 2361 | "author": "Tim O'Brien" 2362 | }, 2363 | { 2364 | "isbn": "0972545425", 2365 | "title": "Sparkling Gems From The Greek: 365 Greek Word Studies For Every Day Of The Year To Sharpen Your Understanding Of God's Word", 2366 | "author": "Rick Renner" 2367 | }, 2368 | { 2369 | "isbn": "1557047952", 2370 | "title": "Michael Clayton: The Shooting Script (Newmarket Shooting Scripts)", 2371 | "author": "Tony Gilroy" 2372 | }, 2373 | { 2374 | "isbn": "1414325460", 2375 | "title": "The Secret Holocaust Diaries: The Untold Story of Nonna Bannister", 2376 | "author": "Nonna Bannister" 2377 | }, 2378 | { 2379 | "isbn": "0323053459", 2380 | "title": "Mosby's Diagnostic and Laboratory Test Reference", 2381 | "author": "Kathleen Deska Pagana PhD RN" 2382 | }, 2383 | { 2384 | "isbn": "1846590671", 2385 | "title": "Many and Many A Year Ago", 2386 | "author": "Selcuk Altun" 2387 | }, 2388 | { 2389 | "isbn": "0078271282", 2390 | "title": "Mathematics for Grob Basic Electronics", 2391 | "author": "Bernard Grob" 2392 | }, 2393 | { 2394 | "isbn": "0821258109", 2395 | "title": "Picasso & Lump: A Dachshund's Odyssey", 2396 | "author": "David Douglas Duncan" 2397 | }, 2398 | { 2399 | "isbn": "0061574287", 2400 | "title": "The Lump of Coal", 2401 | "author": "Lemony Snicket" 2402 | }, 2403 | { 2404 | "isbn": "0531108651", 2405 | "title": "Lumps, Bumps, and Rashes (A First Book)", 2406 | "author": "Alan Edward Nourse" 2407 | }, 2408 | { 2409 | "isbn": "0789737051", 2410 | "title": "NCLEX-RN Exam Cram (2nd Edition)", 2411 | "author": "Wilda Rinehart" 2412 | }, 2413 | { 2414 | "isbn": "0805443908", 2415 | "title": "Simple Church: Returning to God's Process for Making Disciples", 2416 | "author": "Thom S. Rainer" 2417 | }, 2418 | { 2419 | "isbn": "1416566112", 2420 | "title": "Ratio: The Simple Codes Behind the Craft of Everyday Cooking", 2421 | "author": "Michael Ruhlman" 2422 | }, 2423 | { 2424 | "isbn": "0979106613", 2425 | "title": "No-Nonsense Craps: The Consummate Guide to Winning at the Crap Table", 2426 | "author": "Richard Orlyn" 2427 | }, 2428 | { 2429 | "isbn": "1581809662", 2430 | "title": "Mr. Funky's Super Crochet Wonderful", 2431 | "author": "Narumi Ogawa" 2432 | }, 2433 | { 2434 | "isbn": "0974345407", 2435 | "title": "Xero: Turn-of-the-Millenia (Zero)", 2436 | "author": "La Ruocco" 2437 | }, 2438 | { 2439 | "isbn": "2890216152", 2440 | "title": "Le Temple De Xeros (Roman Jeunesse, 125) (Spanish Edition)", 2441 | "author": "Raymond Plante" 2442 | }, 2443 | { 2444 | "isbn": "1402742126", 2445 | "title": "Living with Books", 2446 | "author": "Alan Powers" 2447 | }, 2448 | { 2449 | "isbn": "383279204X", 2450 | "title": "Library Design", 2451 | "author": "teNeues" 2452 | }, 2453 | { 2454 | "isbn": "0385607024", 2455 | "title": "The Prester Quest", 2456 | "author": "Nicholas Jubber" 2457 | }, 2458 | { 2459 | "isbn": "050028816X", 2460 | "title": "Lost Languages: The Enigma of the World's Undeciphered Scripts", 2461 | "author": "Andrew Robinson" 2462 | }, 2463 | { 2464 | "isbn": "0786435437", 2465 | "title": "Radical Cataloging: Essays at the Front", 2466 | "author": "K. R. Roberto" 2467 | }, 2468 | { 2469 | "isbn": "0385504225", 2470 | "title": "The Lost Symbol", 2471 | "author": "Dan Brown" 2472 | }, 2473 | { 2474 | "isbn": "0313323569", 2475 | "title": "The History of New Zealand (The Greenwood Histories of the Modern Nations)", 2476 | "author": "Tom Brooking" 2477 | }, 2478 | { 2479 | "isbn": "0143018671", 2480 | "title": "The Penguin History of New Zealand", 2481 | "author": "Michael King" 2482 | }, 2483 | { 2484 | "isbn": "1594132984", 2485 | "title": "A Voyage Long and Strange: Rediscovering the New World", 2486 | "author": "Tony Horwitz" 2487 | }, 2488 | { 2489 | "isbn": "0061177571", 2490 | "title": "post office: A Novel", 2491 | "author": "Charles Bukowski" 2492 | }, 2493 | { 2494 | "isbn": "0141189827", 2495 | "title": "Junky: The Definitive Text of 'Junk' (Penguin Modern Classics)", 2496 | "author": "William S. Burroughs" 2497 | }, 2498 | { 2499 | "isbn": "0898706408", 2500 | "title": "Salt of the Earth: The Church at the End of the Millennium: An Interview With Peter Seewald", 2501 | "author": "Joseph Cardinal Ratzinger" 2502 | }, 2503 | { 2504 | "isbn": "0618057021", 2505 | "title": "J.R.R. Tolkien: A Biography", 2506 | "author": "Humphrey Carpenter" 2507 | }, 2508 | { 2509 | "isbn": "0823210502", 2510 | "title": "The secret of world history : selected writings on the art and science of history", 2511 | "author": "Leopold von Ranke" 2512 | }, 2513 | { 2514 | "isbn": "3446412190", 2515 | "title": "Wikinomics", 2516 | "author": "Anthony D. Williams" 2517 | }, 2518 | { 2519 | "isbn": "0691126836", 2520 | "title": "The Poison King: The Life and Legend of Mithradates, Rome's Deadliest Enemy", 2521 | "author": "Adrienne Mayor" 2522 | }, 2523 | { 2524 | "isbn": "0875010601", 2525 | "title": "Phantom of fact : a guide to Nabokov's Pnin", 2526 | "author": "Gennady Barabtarlo" 2527 | }, 2528 | { 2529 | "isbn": "0307269647", 2530 | "title": "You Are Not a Gadget: A Manifesto", 2531 | "author": "Jaron Lanier" 2532 | }, 2533 | { 2534 | "isbn": "0061472808", 2535 | "title": "The lost history of Christianity : the thousand-year golden age of the church in the Middle East, Africa, and Asia- and how it died", 2536 | "author": "Philip Jenkins" 2537 | }, 2538 | { 2539 | "isbn": "0312254199", 2540 | "title": "Signposts in a Strange Land: Essays", 2541 | "author": "Walker Percy" 2542 | }, 2543 | { 2544 | "isbn": "0977872610", 2545 | "title": "Love Yourself and Let the Other Person Have It Your Way", 2546 | "author": "Lawrence Crane" 2547 | }, 2548 | { 2549 | "isbn": "0415299098", 2550 | "title": "The History of Zonaras: From Alexander Severus to the Death of Theodosius the Great (Routledge Classical Translations)", 2551 | "author": "Thomas Banchich" 2552 | }, 2553 | { 2554 | "isbn": "0670069280", 2555 | "title": "The War Memoirs Of Hrh Wallis Duchess Of Windsor", 2556 | "author": "Kate Auspitz" 2557 | }, 2558 | { 2559 | "isbn": "0521095174", 2560 | "title": "Interpretation of the fourth Gospel", 2561 | "author": "C. H. Dodd" 2562 | }, 2563 | { 2564 | "isbn": "0142402575", 2565 | "title": "The House With a Clock In Its Walls (Lewis Barnavelt)", 2566 | "author": "John Bellairs" 2567 | }, 2568 | { 2569 | "isbn": "9781596299368", 2570 | "title": "Strange Maine : true tales from the Pine Tree State", 2571 | "author": "Michelle Souliere" 2572 | }, 2573 | { 2574 | "isbn": "0674005066", 2575 | "title": "History of the Florentine people", 2576 | "author": "Leonardo Bruni" 2577 | }, 2578 | { 2579 | "isbn": "9788844033590", 2580 | "title": "Tuscan cuisine: book of recipes", 2581 | "author": "Guido Pedrittoni" 2582 | }, 2583 | { 2584 | "isbn": "1894031911", 2585 | "title": "The Logogryph: A Bibliography Of Imaginary Books", 2586 | "author": "Thomas Wharton" 2587 | }, 2588 | { 2589 | "isbn": "0300115962", 2590 | "title": "Voting About God in Early Church Councils", 2591 | "author": "Professor Ramsay MacMullen" 2592 | }, 2593 | { 2594 | "isbn": "0195153855", 2595 | "title": "The Mandaeans: Ancient Texts and Modern People (Aar the Religions (Unnumbered).)", 2596 | "author": "Jorunn Jacobsen Buckley" 2597 | }, 2598 | { 2599 | "isbn": "0140442340", 2600 | "title": "Life of Appollonius", 2601 | "author": "Philostratus.," 2602 | }, 2603 | { 2604 | "isbn": "1889119040", 2605 | "title": "The Quest: The Search for the Historical Jesus and Muhammad", 2606 | "author": "F.E. Peters" 2607 | }, 2608 | { 2609 | "isbn": "0786711922", 2610 | "title": "The real Eve : modern man's journey out of Africa", 2611 | "author": "Stephen Oppenheimer" 2612 | }, 2613 | { 2614 | "isbn": "1565859480", 2615 | "title": "The story of human language", 2616 | "author": "John H McWhorter" 2617 | }, 2618 | { 2619 | "isbn": "1904233805", 2620 | "title": "Twilight", 2621 | "author": "Stephenie Meyer" 2622 | }, 2623 | { 2624 | "isbn": "0764291009", 2625 | "title": "Love's Unending Legacy/Love's Unfolding Dream/Love Takes Wing/Love Finds a Home (Love Comes Softly Series 5-8)", 2626 | "author": "Janette Oke" 2627 | }, 2628 | { 2629 | "isbn": "0195182499", 2630 | "title": "Lost Christianities: The Battles for Scripture and the Faiths We Never Knew", 2631 | "author": "Bart D. Ehrman" 2632 | }, 2633 | { 2634 | "isbn": "0312605390", 2635 | "title": "Love in the Afternoon (Hathaways, Book 5)", 2636 | "author": "Lisa Kleypas" 2637 | }, 2638 | { 2639 | "isbn": "0802841805", 2640 | "title": "The Scandal of the Evangelical Mind", 2641 | "author": "Mark A. Noll" 2642 | }, 2643 | { 2644 | "isbn": "0553446398", 2645 | "title": "Hidden treasure", 2646 | "author": "Cheryln Biggs" 2647 | }, 2648 | { 2649 | "isbn": "0520075641", 2650 | "title": "Hellenistic history and culture", 2651 | "author": "Peter Green" 2652 | }, 2653 | { 2654 | "isbn": "1400066409", 2655 | "title": "Super Sad True Love Story: A Novel", 2656 | "author": "Gary Shteyngart" 2657 | }, 2658 | { 2659 | "isbn": "0865540489", 2660 | "title": "Introduction to Sahidic Coptic", 2661 | "author": "Thomas Oden Lambdin" 2662 | }, 2663 | { 2664 | "isbn": "0786166800", 2665 | "title": "What Paul Meant", 2666 | "author": "Garry Wills" 2667 | }, 2668 | { 2669 | "isbn": "0670034967", 2670 | "title": "What Jesus Meant", 2671 | "author": "Garry Wills" 2672 | }, 2673 | { 2674 | "isbn": "014311512X", 2675 | "title": "What the Gospels Meant", 2676 | "author": "Garry Wills" 2677 | }, 2678 | { 2679 | "isbn": "141654335X", 2680 | "title": "Under God: Religion and American Politics", 2681 | "author": "Garry Wills" 2682 | }, 2683 | { 2684 | "isbn": "9781415961759", 2685 | "title": "Passage", 2686 | "author": "" 2687 | }, 2688 | { 2689 | "isbn": "9780307738325", 2690 | "title": "Turtle in paradise", 2691 | "author": "Jennifer L. Holm" 2692 | }, 2693 | { 2694 | "isbn": "9780307710949", 2695 | "title": "The water seeker", 2696 | "author": "Kimberly Willis Holt" 2697 | }, 2698 | { 2699 | "isbn": "9780307737236", 2700 | "title": "The vigilantes", 2701 | "author": "W. E. B. Griffin" 2702 | }, 2703 | { 2704 | "isbn": "9780307715630", 2705 | "title": "This body of death", 2706 | "author": "Elizabeth George" 2707 | }, 2708 | { 2709 | "isbn": "9780307735270", 2710 | "title": "My name is memory : [a novel]", 2711 | "author": "Ann Brashares" 2712 | }, 2713 | { 2714 | "isbn": "9780307736055", 2715 | "title": "Lucid intervals", 2716 | "author": "Stuart Woods" 2717 | }, 2718 | { 2719 | "isbn": "9780307749260", 2720 | "title": "Oprah : [a biography]", 2721 | "author": "Kitty Kelley" 2722 | }, 2723 | { 2724 | "isbn": "0802714986", 2725 | "title": "Sea of faith : Islam and Christianity in the medieval Mediterranean world", 2726 | "author": "Stephen O'Shea" 2727 | }, 2728 | { 2729 | "isbn": "014144178X", 2730 | "title": "The Talmud: A Selection (Penguin Classics)", 2731 | "author": "Norman Solomon" 2732 | }, 2733 | { 2734 | "isbn": "0199291535", 2735 | "title": "Seeing the Face, Seeing the Soul: Polemon's Physiognomy from Classical Antiquity to Medieval Islam", 2736 | "author": "George Boys-Stones" 2737 | }, 2738 | { 2739 | "isbn": "9783438054012", 2740 | "title": "Novum testamentum Graece et Latine", 2741 | "author": "Eberhard Nestle" 2742 | }, 2743 | { 2744 | "isbn": "0374272387", 2745 | "title": "The Talmud and the Internet : a journey between worlds", 2746 | "author": "Jonathan Rosen" 2747 | }, 2748 | { 2749 | "isbn": "0664220754", 2750 | "title": "The Israelites in history and tradition", 2751 | "author": "Niels Peter Lemche" 2752 | }, 2753 | { 2754 | "isbn": "0596806027", 2755 | "title": "HTML5: Up and Running", 2756 | "author": "Mark Pilgrim" 2757 | }, 2758 | { 2759 | "isbn": "1562762400", 2760 | "title": "The Internet by E-mail", 2761 | "author": "Clay Shirky" 2762 | }, 2763 | { 2764 | "isbn": "0380815931", 2765 | "title": "In the beginning ...was the command line", 2766 | "author": "Neal Stephenson" 2767 | }, 2768 | { 2769 | "isbn": "0520058763", 2770 | "title": "The making of Citizen Kane", 2771 | "author": "Robert L. Carringer" 2772 | }, 2773 | { 2774 | "isbn": "9780316017923", 2775 | "title": "Outliers : the story of success", 2776 | "author": "Malcolm Gladwell" 2777 | }, 2778 | { 2779 | "isbn": "0066620694", 2780 | "title": "The innovator's dilemma : when new technologies cause great firms to fail", 2781 | "author": "Clayton M. Christensen" 2782 | }, 2783 | { 2784 | "isbn": "0394748808", 2785 | "title": "Oracles and Divination", 2786 | "author": "Michael Loewe" 2787 | }, 2788 | { 2789 | "isbn": "0585350701", 2790 | "title": "A religious history of the American people", 2791 | "author": "Sydney E. Ahlstrom" 2792 | }, 2793 | { 2794 | "isbn": "0399536493", 2795 | "title": "Images You Should Not Masturbate To", 2796 | "author": "Graham Johnson" 2797 | }, 2798 | { 2799 | "isbn": "0801862655", 2800 | "title": "The Smoke of Satan: Conservative and Traditionalist Dissent in Contemporary American Catholicism", 2801 | "author": "Michael W. Cuneo" 2802 | }, 2803 | { 2804 | "isbn": "0821413325", 2805 | "title": "Lord Of Visible World: Autobiography In Letters", 2806 | "author": "H.P. Lovecraft" 2807 | }, 2808 | { 2809 | "isbn": "0451037529", 2810 | "title": "The Puppet Masters (Signet SF, T3752)", 2811 | "author": "Robert A. Heinlein" 2812 | }, 2813 | { 2814 | "isbn": "0465023975", 2815 | "title": "Osman's Dream: The History of the Ottoman Empire", 2816 | "author": "Caroline Finkel" 2817 | }, 2818 | { 2819 | "isbn": "0786891076", 2820 | "title": "Shopgirl: A Novella", 2821 | "author": "Steve Martin" 2822 | }, 2823 | { 2824 | "isbn": "0446557021", 2825 | "title": "Late for School", 2826 | "author": "Steve Martin" 2827 | }, 2828 | { 2829 | "isbn": "1601420617", 2830 | "title": "Redeeming Love", 2831 | "author": "Francine Rivers" 2832 | }, 2833 | { 2834 | "isbn": "0674049683", 2835 | "title": "Reading and Writing in Babylon", 2836 | "author": "Dominique Charpin" 2837 | }, 2838 | { 2839 | "isbn": "1404861084", 2840 | "title": "My Friend Has ADHD (Friends With Disabilities)", 2841 | "author": "Amanda Doering Tourville" 2842 | }, 2843 | { 2844 | "isbn": "1404861106", 2845 | "title": "My Friend Has Down Syndrome (Friends With Disabilities)", 2846 | "author": "Amanda Doering Tourville" 2847 | }, 2848 | { 2849 | "isbn": "160942168X", 2850 | "title": "War and Peace", 2851 | "author": "Leo Tolstoy" 2852 | }, 2853 | { 2854 | "isbn": "1400079985", 2855 | "title": "War and Peace (Vintage Classics)", 2856 | "author": "Leo Tolstoy" 2857 | }, 2858 | { 2859 | "isbn": "158731455X", 2860 | "title": "The Latin Letters of C.S. Lewis", 2861 | "author": "C.S. Lewis" 2862 | }, 2863 | { 2864 | "isbn": "0395938473", 2865 | "title": "The New Way Things Work", 2866 | "author": "David Macaulay" 2867 | }, 2868 | { 2869 | "isbn": "0812550757", 2870 | "title": "Speaker for the Dead (Ender, Book 2)", 2871 | "author": "Orson Scott Card" 2872 | }, 2873 | { 2874 | "isbn": "9780061947032", 2875 | "title": "Revelation of the Magi : the lost tale of the Three Wise Men's journey to Bethlehem", 2876 | "author": "Brent Landau" 2877 | }, 2878 | { 2879 | "isbn": "9780470568439", 2880 | "title": "iPhone application development for dummies", 2881 | "author": "Neal L. Goldstein" 2882 | }, 2883 | { 2884 | "isbn": "9781586176068", 2885 | "title": "Light of the world : the pope, the church, and the signs of the times: a conversation with peter seewald", 2886 | "author": "Peter Seewald" 2887 | }, 2888 | { 2889 | "isbn": "0801879523", 2890 | "title": "Tasmanian tiger : the tragic tale of how the world lost its most mysterious predator", 2891 | "author": "David Owen" 2892 | }, 2893 | { 2894 | "isbn": "0006273297", 2895 | "title": "Letters of C.S. Lewis", 2896 | "author": "C. S. Lewis" 2897 | }, 2898 | { 2899 | "isbn": "9780674047495", 2900 | "title": "What Happened at Vatican II", 2901 | "author": "John W. O'Malley S. J." 2902 | }, 2903 | { 2904 | "isbn": "0814317464", 2905 | "title": "Ovid's games of love", 2906 | "author": "Molly Myerowitz" 2907 | }, 2908 | { 2909 | "isbn": "030726839X", 2910 | "title": "Winston's War: Churchill, 1940-1945", 2911 | "author": "Max Hastings" 2912 | }, 2913 | { 2914 | "isbn": "159629955X", 2915 | "title": "Portlands Greatest Conflagration: The 1866 Fire Disaster (ME)", 2916 | "author": "Michael Daicy" 2917 | }, 2918 | { 2919 | "isbn": "0385533853", 2920 | "title": "Robopocalypse: A Novel", 2921 | "author": "Daniel H. Wilson" 2922 | }, 2923 | { 2924 | "isbn": "0380794802", 2925 | "title": "One True Love", 2926 | "author": "Barbara Freethy" 2927 | }, 2928 | { 2929 | "isbn": "0800601270", 2930 | "title": "The Worship of the Early Church", 2931 | "author": "Ferdinand Hahn" 2932 | }, 2933 | { 2934 | "isbn": "080520413X", 2935 | "title": "The Jewish Festivals: History and Observance (English and Hebrew Edition)", 2936 | "author": "Hayyim Schauss" 2937 | }, 2938 | { 2939 | "isbn": "039333869X", 2940 | "title": "Liar's Poker", 2941 | "author": "Michael Lewis" 2942 | }, 2943 | { 2944 | "isbn": "1571780793", 2945 | "title": "Twilight of the clockwork God : conversations on science and spirituality at the end of an age", 2946 | "author": "John David Ebert" 2947 | }, 2948 | { 2949 | "isbn": "9788799197965", 2950 | "title": "Linda Love", 2951 | "author": "Asbjørn Auring Grimm" 2952 | }, 2953 | { 2954 | "isbn": "1565970551", 2955 | "title": "Love in bloom (Kismet)", 2956 | "author": "Karen Rose Smith" 2957 | }, 2958 | { 2959 | "isbn": "0805422994", 2960 | "title": "We Remember C. S. Lewis: Essays and Memoirs by Philip Yancey, J. I.Packer, Charles Colson, George Sayer, James Houston, Don Bede Griffiths and Others", 2961 | "author": "David Graham" 2962 | }, 2963 | { 2964 | "isbn": "0199573506", 2965 | "title": "Planets: A Very Short Introduction (Very Short Introductions)", 2966 | "author": "David A. Rothery" 2967 | }, 2968 | { 2969 | "isbn": "1592406254", 2970 | "title": "What Language Is: And What It Isn’t and What It Could Be", 2971 | "author": "John McWhorter" 2972 | }, 2973 | { 2974 | "isbn": "1861978375", 2975 | "title": "We-Think: Mass innovation, not mass production", 2976 | "author": "Charles Leadbeater" 2977 | }, 2978 | { 2979 | "isbn": "0446601977", 2980 | "title": "Parable of the Sower", 2981 | "author": "Octavia E. Butler" 2982 | }, 2983 | { 2984 | "isbn": "0380006650", 2985 | "title": "The Investigation", 2986 | "author": "Stanislaw Lem" 2987 | }, 2988 | { 2989 | "isbn": "0143016695", 2990 | "title": "Ysabel", 2991 | "author": "Guy Gavriel Kay" 2992 | }, 2993 | { 2994 | "isbn": "0060692545", 2995 | "title": "The Triumph of the Meek: Why Early Christianity Succeeded", 2996 | "author": "Michael J. Walsh" 2997 | }, 2998 | { 2999 | "isbn": "9780765316738", 3000 | "title": "The trade of queens", 3001 | "author": "Charles Stross" 3002 | }, 3003 | { 3004 | "isbn": "8838436355", 3005 | "title": "Il mistero dell'Isola del Drago", 3006 | "author": "Renato Giovannoli" 3007 | }, 3008 | { 3009 | "isbn": "0877854106", 3010 | "title": "A Swedenborg Sampler: Selections from Heaven and Hell, Divine Love and Wisdom, Divine Providence, True Christianity, and Secrets of Heaven", 3011 | "author": "Emanuel Swedenborg" 3012 | }, 3013 | { 3014 | "isbn": "0300176880", 3015 | "title": "Ten Popes Who Shook the World", 3016 | "author": "Eamon Duffy" 3017 | }, 3018 | { 3019 | "isbn": "014311302X", 3020 | "title": "Pope John XXIII: A Life (Penguin Lives)", 3021 | "author": "Thomas Cahill" 3022 | }, 3023 | { 3024 | "isbn": "0486424758", 3025 | "title": "Simplified grammar of Arabic, Persian, and Hindustani", 3026 | "author": "Edward Henry Palmer" 3027 | }, 3028 | { 3029 | "isbn": "0394438957", 3030 | "title": "Of Plymouth Plantation, 1620-1647", 3031 | "author": "William Bradford" 3032 | }, 3033 | { 3034 | "isbn": "0195297709", 3035 | "title": "The Jewish Annotated New Testament", 3036 | "author": "Amy-Jill Levine" 3037 | }, 3038 | { 3039 | "isbn": "0810993465", 3040 | "title": "Library Mouse", 3041 | "author": "Daniel Kirk" 3042 | }, 3043 | { 3044 | "isbn": "076363784X", 3045 | "title": "Library Lion", 3046 | "author": "Michelle Knudsen" 3047 | }, 3048 | { 3049 | "isbn": "0618968636", 3050 | "title": "The Hobbit", 3051 | "author": "J.R.R. Tolkien" 3052 | }, 3053 | { 3054 | "isbn": "0802839312", 3055 | "title": "Jesus Remembered (Christianity in the Making)", 3056 | "author": "James D. G. Dunn" 3057 | }, 3058 | { 3059 | "isbn": "1567505198", 3060 | "title": "Sorting Out the Web: Approaches to Subject Access (Contemporary Studies in Information Management, Policy, and)", 3061 | "author": "Candy Schwartz" 3062 | }, 3063 | { 3064 | "isbn": "1936383632", 3065 | "title": "Jimmy Plush, Teddy Bear Detective", 3066 | "author": "Garrett Cook" 3067 | }, 3068 | { 3069 | "isbn": "1582406197", 3070 | "title": "The Walking Dead, Book 1 (Bk. 1)", 3071 | "author": "Robert Kirkman" 3072 | }, 3073 | { 3074 | "isbn": "9038826842", 3075 | "title": "Serieuze poging tot een volledige bibliografie van de zelfstandige en verspreide geschriften van Arnon Grunberg waarin opgenomen diens Interview met mijn bibliograaf & In ieder mens schuilt een maniak", 3076 | "author": "Wuijts" 3077 | }, 3078 | { 3079 | "isbn": "1451648537", 3080 | "title": "Steve Jobs", 3081 | "author": "Walter Isaacson" 3082 | }, 3083 | { 3084 | "isbn": "0375706399", 3085 | "title": "Test Book", 3086 | "author": "Test Book" 3087 | }, 3088 | { 3089 | "isbn": "0061431605", 3090 | "title": "This Book Is Overdue!: How Librarians and Cybrarians Can Save Us All", 3091 | "author": "Marilyn Johnson" 3092 | }, 3093 | { 3094 | "isbn": "059529362X", 3095 | "title": "HIP Recollections of Darby Hicks: (The Musings of a Senior Bronx Homeboy)", 3096 | "author": "Bob Washington" 3097 | }, 3098 | { 3099 | "isbn": "1592769446", 3100 | "title": "Holiness for Everyone: The Practical Spirituality of St. Josemaria Escriva", 3101 | "author": "Eric Sammons" 3102 | }, 3103 | { 3104 | "isbn": "9781892391896", 3105 | "title": "Test book", 3106 | "author": "Test Book" 3107 | }, 3108 | { 3109 | "isbn": "1580512283", 3110 | "title": "A History of the Popes: From Peter to the Present", 3111 | "author": "John O'Malley" 3112 | }, 3113 | { 3114 | "isbn": "0674066979", 3115 | "title": "Trent: What Happened at the Council", 3116 | "author": "John W. O'Malley" 3117 | }, 3118 | { 3119 | "isbn": "0800625242", 3120 | "title": "Introduction to the Talmud and Midrash", 3121 | "author": "Hermann L. Strack" 3122 | }, 3123 | { 3124 | "isbn": "0099286246", 3125 | "title": "The lawless roads", 3126 | "author": "Graham Greene" 3127 | }, 3128 | { 3129 | "isbn": "0814680291", 3130 | "title": "My Journal of the Council", 3131 | "author": "Yves Congar" 3132 | }, 3133 | { 3134 | "isbn": "0140010750", 3135 | "title": "Kraken Wakes", 3136 | "author": "John Wyndham" 3137 | }, 3138 | { 3139 | "isbn": "0575047674", 3140 | "title": "Wulfsyarn", 3141 | "author": "Phillip Mann" 3142 | }, 3143 | { 3144 | "isbn": "0898707021", 3145 | "title": "Milestones : memoirs, 1927-1977", 3146 | "author": "Pope Benedict" 3147 | }, 3148 | { 3149 | "isbn": "9780441015146", 3150 | "title": "Plague year", 3151 | "author": "Jeff Carlson" 3152 | }, 3153 | { 3154 | "isbn": "0821768050", 3155 | "title": "Lady May's folly", 3156 | "author": "Donna Simpson" 3157 | }, 3158 | { 3159 | "isbn": "0664236995", 3160 | "title": "Tokens of Trust: An Introduction to Christian Belief", 3161 | "author": "Rowan Williams" 3162 | }, 3163 | { 3164 | "isbn": "0385497938", 3165 | "title": "Rabbi Jesus: An Intimate Biography", 3166 | "author": "Bruce Chilton" 3167 | }, 3168 | { 3169 | "isbn": "1619697688", 3170 | "title": "The Onion Book of Known Knowledge", 3171 | "author": "The Onion" 3172 | }, 3173 | { 3174 | "isbn": "0780786068", 3175 | "title": "Merlin and the Dragons", 3176 | "author": "Jane Yolen" 3177 | }, 3178 | { 3179 | "isbn": "0567292061", 3180 | "title": "Rome and the Eastern Churches", 3181 | "author": "Aidan Nichols" 3182 | }, 3183 | { 3184 | "isbn": "0199754381", 3185 | "title": "Leaves from the Garden of Eden", 3186 | "author": "Howard Schwartz" 3187 | }, 3188 | { 3189 | "isbn": "1461057205", 3190 | "title": "Wool", 3191 | "author": "Hugh Howey" 3192 | }, 3193 | { 3194 | "isbn": "019504391X", 3195 | "title": "Hellenistic Religions: An Introduction", 3196 | "author": "Luther H. Martin" 3197 | }, 3198 | { 3199 | "isbn": "0300056400", 3200 | "title": "The origins of Christian morality : the first two centuries", 3201 | "author": "Wayne A. Meeks" 3202 | }, 3203 | { 3204 | "isbn": "0060182148", 3205 | "title": "Mariette in Ecstasy", 3206 | "author": "Ron Hansen" 3207 | }, 3208 | { 3209 | "isbn": "0691081948", 3210 | "title": "Why big fierce animals are rare : an ecologist's perspective", 3211 | "author": "Paul A. Colinvaux" 3212 | }, 3213 | { 3214 | "isbn": "0674996364", 3215 | "title": "Hellenistic Collection: Philitas. Alexander of Aetolia. Hermesianax. Euphorion. Parthenius (Loeb Classical Library)", 3216 | "author": "J. L. Lightfoot" 3217 | }, 3218 | { 3219 | "isbn": "0674073142", 3220 | "title": "Homer's Turk: How Classics Shaped Ideas of the East", 3221 | "author": "Jerry Toner" 3222 | }, 3223 | { 3224 | "isbn": "080104538X", 3225 | "title": "Jesus the Temple", 3226 | "author": "Nicholas Perrin" 3227 | }, 3228 | { 3229 | "isbn": "0801488729", 3230 | "title": "The Origin of Sin: An English Translation of the Hamartigenia (Cornell Studies in Classical Philology)", 3231 | "author": "Prudentius" 3232 | }, 3233 | { 3234 | "isbn": "1844130533", 3235 | "title": "How to Read a Church", 3236 | "author": "Richard Taylor" 3237 | }, 3238 | { 3239 | "isbn": "2266182714", 3240 | "title": "Hunger Games 3/LA Revolte (French Edition)", 3241 | "author": "Suzanne Collins" 3242 | }, 3243 | { 3244 | "isbn": "143915354X", 3245 | "title": "The Judas Gospel: A Novel", 3246 | "author": "Bill Myers" 3247 | }, 3248 | { 3249 | "isbn": "1570759936", 3250 | "title": "Vatican II: Fifty Personal Stories", 3251 | "author": "William Madges" 3252 | }, 3253 | { 3254 | "isbn": "1856077896", 3255 | "title": "Reaping the Harvest: Fifty Years After Vatican II", 3256 | "author": "Suzanne Mulligan" 3257 | }, 3258 | { 3259 | "isbn": "0670024872", 3260 | "title": "Why Priests?: A Failed Tradition", 3261 | "author": "Garry Wills" 3262 | }, 3263 | { 3264 | "isbn": "037542346X", 3265 | "title": "The Landmark Arrian: The Campaigns of Alexander", 3266 | "author": "James Romm" 3267 | }, 3268 | { 3269 | "isbn": "9780393071955", 3270 | "title": "Naked statistics : stripping the dread from the data", 3271 | "author": "Charles J. Wheelan" 3272 | }, 3273 | { 3274 | "isbn": "1851682899", 3275 | "title": "The New Testament : a short introduction : a guide to early Christianity and the Synoptic Gospels", 3276 | "author": "William Telford" 3277 | }, 3278 | { 3279 | "isbn": "0224061658", 3280 | "title": "The occult tradition : from the Renaissance to the present day", 3281 | "author": "David S. Katz" 3282 | }, 3283 | { 3284 | "isbn": "9780062292742", 3285 | "title": "Pope Francis: From the End of the Earth to Rome", 3286 | "author": "The Staff of The Wall Street Journal" 3287 | }, 3288 | { 3289 | "isbn": "0531099474", 3290 | "title": "The Jesuits, a history", 3291 | "author": "David J. Mitchell" 3292 | }, 3293 | { 3294 | "isbn": "1470899000", 3295 | "title": "The Vatican Diaries: A Behind-the-Scenes Look at the Power, Personalities, and Politics at the Heart of the Catholic Church", 3296 | "author": "John Thavis" 3297 | }, 3298 | { 3299 | "isbn": "0486275590", 3300 | "title": "Treasure Island (Dover Thrift Editions)", 3301 | "author": "Robert Louis Stevenson" 3302 | }, 3303 | { 3304 | "isbn": "0486447162", 3305 | "title": "The World's Greatest Short Stories (Dover Thrift Editions)", 3306 | "author": "James Daley" 3307 | }, 3308 | { 3309 | "isbn": "9781400067664", 3310 | "title": "Thomas Jefferson : the art of power", 3311 | "author": "Jon Meacham" 3312 | }, 3313 | { 3314 | "isbn": "1586482734", 3315 | "title": "John F. Kerry : the complete biography by the Boston Globe reporters who know him best", 3316 | "author": "Michael Kranish" 3317 | }, 3318 | { 3319 | "isbn": "9780199588077", 3320 | "title": "Networks : a very short introduction", 3321 | "author": "Guido Caldarelli" 3322 | }, 3323 | { 3324 | "isbn": "0195123018", 3325 | "title": "Monopolies in America : empire builders and their enemies, from Jay Gould to Bill Gates", 3326 | "author": "Charles R. Geisst" 3327 | }, 3328 | { 3329 | "isbn": "9780307592217", 3330 | "title": "The murder of the century : the Gilded Age crime that scandalized a city and sparked the tabloid wars", 3331 | "author": "Paul Collins" 3332 | }, 3333 | { 3334 | "isbn": "0140057463", 3335 | "title": "How far can you go?", 3336 | "author": "David Lodge" 3337 | }, 3338 | { 3339 | "isbn": "158617021X", 3340 | "title": "The Meaning of Tradition", 3341 | "author": "Yves Congar" 3342 | }, 3343 | { 3344 | "isbn": "9780061186479", 3345 | "title": "M is for magic", 3346 | "author": "Neil Gaiman" 3347 | }, 3348 | { 3349 | "isbn": "9780192805805", 3350 | "title": "What makes civilization? : the ancient near east and the future of the west", 3351 | "author": "David Wengrow" 3352 | }, 3353 | { 3354 | "isbn": "1599951509", 3355 | "title": "The Monuments Men: Allied Heroes, Nazi Thieves and the Greatest Treasure Hunt in History", 3356 | "author": "Robert M. Edsel" 3357 | }, 3358 | { 3359 | "isbn": "081463558X", 3360 | "title": "The Social Media Gospel: Sharing the Good News in New Ways", 3361 | "author": "Meredith Gould" 3362 | }, 3363 | { 3364 | "isbn": "9781476733951", 3365 | "title": "Wool", 3366 | "author": "Hugh Howey" 3367 | }, 3368 | { 3369 | "isbn": "0836231007", 3370 | "title": "The making of the Popes 1978 : the politics of intrigue in the Vatican", 3371 | "author": "Andrew M. Greeley" 3372 | }, 3373 | { 3374 | "isbn": "0297844156", 3375 | "title": "Hudson's English history : a compendium", 3376 | "author": "Roger Hudson" 3377 | }, 3378 | { 3379 | "isbn": "9780143106326", 3380 | "title": "The riddle of the sands : a record of secret service", 3381 | "author": "Erskine Childers" 3382 | }, 3383 | { 3384 | "isbn": "9781620400418", 3385 | "title": "What matters in Jane Austen? : twenty crucial puzzles solved", 3386 | "author": "John Mullan" 3387 | }, 3388 | { 3389 | "isbn": "3001160462", 3390 | "title": "What the World is Reading", 3391 | "author": "Penguin Group" 3392 | }, 3393 | { 3394 | "isbn": "0393082067", 3395 | "title": "Intuition Pumps And Other Tools for Thinking", 3396 | "author": "Daniel C. Dennett" 3397 | }, 3398 | { 3399 | "isbn": "0393039250", 3400 | "title": "Why the allies won", 3401 | "author": "R. J. Overy" 3402 | }, 3403 | { 3404 | "isbn": "0983476470", 3405 | "title": "The Other Room", 3406 | "author": "Kim Triedman" 3407 | }, 3408 | { 3409 | "isbn": "0814660118", 3410 | "title": "The Rites of Christian Initiation: Their Evolution and Interpretation (Michael Glazier Books)", 3411 | "author": "Maxwell E. Johnson" 3412 | }, 3413 | { 3414 | "isbn": "075092246X", 3415 | "title": "The last conquistador : Mansio Serra de Lequizamón and the conquest of the Incas", 3416 | "author": "Stuart Stirling" 3417 | }, 3418 | { 3419 | "isbn": "0195040473", 3420 | "title": "Paideia: The Ideals of Greek Culture: Volume II: In Search of the Divine Center", 3421 | "author": "Werner Jaeger" 3422 | }, 3423 | { 3424 | "isbn": "1455520020", 3425 | "title": "Without Their Permission: How the 21st Century Will Be Made, Not Managed", 3426 | "author": "Alexis Ohanian" 3427 | }, 3428 | { 3429 | "isbn": "0385496591", 3430 | "title": "The Lamb's Supper: The Mass as Heaven on Earth", 3431 | "author": "Scott Hahn" 3432 | }, 3433 | { 3434 | "isbn": "0300065159", 3435 | "title": "Understanding Religious Conversion", 3436 | "author": "Lewis R. Rambo" 3437 | }, 3438 | { 3439 | "isbn": "015676248X", 3440 | "title": "Reflections on the Psalms", 3441 | "author": "C. S. Lewis" 3442 | }, 3443 | { 3444 | "isbn": "0394437039", 3445 | "title": "The moviegoer", 3446 | "author": "Walker Percy" 3447 | }, 3448 | { 3449 | "isbn": "0575119519", 3450 | "title": "Riddley Walker (SF Masterworks)", 3451 | "author": "Russell Hoban" 3452 | }, 3453 | { 3454 | "isbn": "0199781729", 3455 | "title": "When God Spoke Greek: The Septuagint and the Making of the Christian Bible", 3456 | "author": "Timothy Michael Law" 3457 | }, 3458 | { 3459 | "isbn": "9780674058071", 3460 | "title": "Latin : story of a world language", 3461 | "author": "Jürgen Leonhardt" 3462 | }, 3463 | { 3464 | "isbn": "0449912183", 3465 | "title": "Roger's Version: A Novel", 3466 | "author": "John Updike" 3467 | }, 3468 | { 3469 | "isbn": "069115290X", 3470 | "title": "Through the Eye of a Needle: Wealth, the Fall of Rome, and the Making of Christianity in the West, 350-550 AD", 3471 | "author": "Peter Brown" 3472 | }, 3473 | { 3474 | "isbn": "0886825016", 3475 | "title": "The ones who walk away from Omelas", 3476 | "author": "Ursula K. Le Guin" 3477 | }, 3478 | { 3479 | "isbn": "0920668372", 3480 | "title": "Love You Forever", 3481 | "author": "Robert Munsch" 3482 | }, 3483 | { 3484 | "isbn": "0307389731", 3485 | "title": "Love in the Time of Cholera (Oprah's Book Club)", 3486 | "author": "Gabriel Garcia Marquez" 3487 | }, 3488 | { 3489 | "isbn": "9791021002596", 3490 | "title": "Test", 3491 | "author": "Test" 3492 | }, 3493 | { 3494 | "isbn": "0809106094", 3495 | "title": "Mercy: The Essence of the Gospel and the Key to Christian Life", 3496 | "author": "Cardinal Walter Kasper" 3497 | }, 3498 | { 3499 | "isbn": "0814680585", 3500 | "title": "Jesus of Nazareth: What He Wanted, Who He Was", 3501 | "author": "Gerhard Lohfink" 3502 | }, 3503 | { 3504 | "isbn": "0742548716", 3505 | "title": "How Do Catholics Read the Bible? (The Come & See Series)", 3506 | "author": "S. J. Daniel J. Harrington" 3507 | }, 3508 | { 3509 | "isbn": "0486445089", 3510 | "title": "Kim (Dover Thrift Editions)", 3511 | "author": "Rudyard Kipling" 3512 | }, 3513 | { 3514 | "isbn": "1878424424", 3515 | "title": "The Mastery of Love: A Practical Guide to the Art of Relationship: A Toltec Wisdom Book", 3516 | "author": "Don Miguel Ruiz" 3517 | }, 3518 | { 3519 | "isbn": "0140440240", 3520 | "title": "The symposium", 3521 | "author": "Plato." 3522 | }, 3523 | { 3524 | "isbn": "0764554336", 3525 | "title": "Physics For Dummies", 3526 | "author": "Steve Holzner" 3527 | }, 3528 | { 3529 | "isbn": "0785250506", 3530 | "title": "Josephus: The Complete Works", 3531 | "author": "Josephus" 3532 | }, 3533 | { 3534 | "isbn": "0590323180", 3535 | "title": "The Mad Scientists' Club", 3536 | "author": "Bertrand R Brinley" 3537 | }, 3538 | { 3539 | "isbn": "0316125407", 3540 | "title": "The Wonderful Flight to the Mushroom Planet", 3541 | "author": "Eleanor Cameron" 3542 | }, 3543 | { 3544 | "isbn": "1591846587", 3545 | "title": "The Launch ||Pad: Inside Y Combinator", 3546 | "author": "Randall Stross" 3547 | }, 3548 | { 3549 | "isbn": "1594161976", 3550 | "title": "The Lost Book of Alexander the Great", 3551 | "author": "Andrew Young" 3552 | }, 3553 | { 3554 | "isbn": "1590170482", 3555 | "title": "The Little Bookroom", 3556 | "author": "Eleanor Farjeon" 3557 | }, 3558 | { 3559 | "isbn": "0765326787", 3560 | "title": "Stand on Zanzibar", 3561 | "author": "John Brunner" 3562 | }, 3563 | { 3564 | "isbn": "0416789501", 3565 | "title": "Tintin and the Lake of Sharks (Tintin Film Book) (The Adventures of Tintin)", 3566 | "author": "A. Herge" 3567 | }, 3568 | { 3569 | "isbn": "0452296293", 3570 | "title": "The Magicians: A Novel (Magicians Trilogy)", 3571 | "author": "Lev Grossman" 3572 | }, 3573 | { 3574 | "isbn": "0792166825", 3575 | "title": "The Virgin Suicides", 3576 | "author": "Kirsten Dunst" 3577 | }, 3578 | { 3579 | "isbn": "0140286829", 3580 | "title": "The Third Man", 3581 | "author": "Graham Greene" 3582 | }, 3583 | { 3584 | "isbn": "0780622251", 3585 | "title": "The Sweet Hereafter", 3586 | "author": "Ian Holm" 3587 | }, 3588 | { 3589 | "isbn": "0140441824", 3590 | "title": "Procopius: The Secret History", 3591 | "author": "Procopius" 3592 | }, 3593 | { 3594 | "isbn": "1840591137", 3595 | "title": "The other side of the mountain", 3596 | "author": "Erendiz Atasü" 3597 | }, 3598 | { 3599 | "isbn": "0671244175", 3600 | "title": "Simon and Schuster's Guide to Rocks and Minerals", 3601 | "author": "Martin Prinz" 3602 | }, 3603 | { 3604 | "isbn": "0446377686", 3605 | "title": "The joy of pigging out", 3606 | "author": "David Hoffman" 3607 | }, 3608 | { 3609 | "isbn": "0790757303", 3610 | "title": "Red planet", 3611 | "author": "Val Kilmer" 3612 | }, 3613 | { 3614 | "isbn": "1400156882", 3615 | "title": "Wuthering Heights (Tantor Unabridged Classics)", 3616 | "author": "Emily Bronte" 3617 | }, 3618 | { 3619 | "isbn": "0790742616", 3620 | "title": "The Matrix", 3621 | "author": "Andy Wachowski" 3622 | }, 3623 | { 3624 | "isbn": "1419873997", 3625 | "title": "Terminator Salvation", 3626 | "author": "" 3627 | }, 3628 | { 3629 | "isbn": "0792188926", 3630 | "title": "The Little Bear Movie", 3631 | "author": "Raymond Jafelice" 3632 | }, 3633 | { 3634 | "isbn": "0792850769", 3635 | "title": "The Princess Bride (Special Edition)", 3636 | "author": "Rob Reiner" 3637 | }, 3638 | { 3639 | "isbn": "1419809792", 3640 | "title": "The invasion", 3641 | "author": "" 3642 | }, 3643 | { 3644 | "isbn": "0783297807", 3645 | "title": "Lost in Translation", 3646 | "author": "Sofia Coppola" 3647 | }, 3648 | { 3649 | "isbn": "0767856120", 3650 | "title": "Stand By Me (Special Edition)", 3651 | "author": "" 3652 | }, 3653 | { 3654 | "isbn": "0783273355", 3655 | "title": "Possession", 3656 | "author": "Neil LaBute" 3657 | }, 3658 | { 3659 | "isbn": "0788826905", 3660 | "title": "Unbreakable (Two-Disc Vista Series)", 3661 | "author": "M. Night Shyamalan" 3662 | }, 3663 | { 3664 | "isbn": "076789880X", 3665 | "title": "Lawrence of Arabia (Single-Disc Edition)", 3666 | "author": "David Lean" 3667 | }, 3668 | { 3669 | "isbn": "0782010032", 3670 | "title": "It's a Wonderful Life", 3671 | "author": "Frank Capra" 3672 | }, 3673 | { 3674 | "isbn": "1566056543", 3675 | "title": "Quadrophenia (Special Edition)", 3676 | "author": "Franc Roddam" 3677 | }, 3678 | { 3679 | "isbn": "0782008372", 3680 | "title": "Highlander: Director's Cut 10th Anniversary Edition", 3681 | "author": "Russell Mulcahy" 3682 | }, 3683 | { 3684 | "isbn": "1580812368", 3685 | "title": "Seven Days In May", 3686 | "author": "Kristin Sergel" 3687 | }, 3688 | { 3689 | "isbn": "0788604546", 3690 | "title": "The Great Communicator (Complete Set)", 3691 | "author": "Ronald Reagan" 3692 | }, 3693 | { 3694 | "isbn": "141703050X", 3695 | "title": "Serenity (Widescreen Edition)", 3696 | "author": "Joss Whedon" 3697 | } 3698 | ] 3699 | } -------------------------------------------------------------------------------- /benchmarks/create-index.js: -------------------------------------------------------------------------------- 1 | const Benchmark = require("benchmark"); 2 | const bb = require("beautify-benchmark"); 3 | const lunr = require("lunr"); 4 | const JsSearchLatest = require("js-worker-search").default; 5 | const JsSearchLocal = require("../dist/js-worker-search").default; 6 | 7 | const indexMode = "EXACT_WORDS"; 8 | 9 | let books; 10 | function loadBooks() { 11 | const fs = require("fs"); 12 | fs.readFile("books.json", "utf8", (err, data) => { 13 | books = JSON.parse(data).books; 14 | runTests(); 15 | }); 16 | } 17 | 18 | function buildIndex(SearchApi) { 19 | var search = new SearchApi({ indexMode }); 20 | books.forEach(book => { 21 | const { author, isbn, title } = book; 22 | search.indexDocument(isbn, author); 23 | search.indexDocument(isbn, title); 24 | }); 25 | } 26 | 27 | function runTests() { 28 | console.log("Testing search index creation ..."); 29 | 30 | new Benchmark.Suite() 31 | .on("cycle", event => { 32 | bb.add(event.target); 33 | }) 34 | .on("complete", () => { 35 | bb.log(); 36 | }) 37 | .add("lunr", () => { 38 | var lunrJsIndex = new lunr.Index(); 39 | lunrJsIndex.field("title"); 40 | lunrJsIndex.field("author"); 41 | lunrJsIndex.ref("isbn"); 42 | for (var i = 0, length = books.length; i < length; i++) { 43 | lunrJsIndex.add(books[i]); 44 | } 45 | }) 46 | .add("js-worker-search:latest", () => { 47 | buildIndex(JsSearchLatest); 48 | }) 49 | .add("js-worker-search:local", () => { 50 | buildIndex(JsSearchLocal); 51 | }) 52 | .run({ async: true }); 53 | } 54 | 55 | loadBooks(); 56 | -------------------------------------------------------------------------------- /benchmarks/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-search-benchmark", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "create-index.js", 6 | "dependencies": { 7 | "beautify-benchmark": "^0.2.4", 8 | "benchmark": "^2.1.3", 9 | "lodash": "^4.17.4", 10 | "lunr": "^0.7.2" 11 | }, 12 | "devDependencies": { 13 | "beautify-benchmark": "^0.2.4", 14 | "benchmark": "^2.1.3", 15 | "js-worker-search": "^1.2.0", 16 | "lunr": "^0.7.2", 17 | "microtime": "^2.1.2" 18 | }, 19 | "scripts": { 20 | "test": "echo \"Error: no test specified\" && exit 1" 21 | }, 22 | "author": "", 23 | "license": "ISC" 24 | } 25 | -------------------------------------------------------------------------------- /benchmarks/search.js: -------------------------------------------------------------------------------- 1 | const Benchmark = require("benchmark"); 2 | const bb = require("beautify-benchmark"); 3 | const lunr = require("lunr"); 4 | const JsSearchLatest = require("js-worker-search").default; 5 | const JsSearchLocal = require("../dist/js-worker-search").default; 6 | 7 | const indexMode = "EXACT_WORDS"; 8 | 9 | let books; 10 | function loadBooks() { 11 | const fs = require("fs"); 12 | fs.readFile("books.json", "utf8", (err, data) => { 13 | books = JSON.parse(data).books; 14 | setupTest(); 15 | }); 16 | } 17 | 18 | var lunrJsIndex; 19 | var searchLatest; 20 | var searchLocal; 21 | var searchTerms = ["letter", "world", "wife", "love", "foobar"]; 22 | var searchTermsLength = searchTerms.length; 23 | 24 | function setupTest() { 25 | lunrJsIndex = new lunr.Index(); 26 | lunrJsIndex.field("title"); 27 | lunrJsIndex.field("author"); 28 | lunrJsIndex.ref("isbn"); 29 | for (var i = 0, length = books.length; i < length; i++) { 30 | lunrJsIndex.add(books[i]); 31 | } 32 | 33 | searchLatest = buildIndex(JsSearchLatest); 34 | searchLocal = buildIndex(JsSearchLocal); 35 | 36 | runTests(); 37 | } 38 | 39 | function buildIndex(SearchApi) { 40 | var search = new SearchApi({ indexMode }); 41 | books.forEach(book => { 42 | const { author, isbn, title } = book; 43 | search.indexDocument(isbn, author); 44 | search.indexDocument(isbn, title); 45 | }); 46 | 47 | return search; 48 | } 49 | 50 | function doSearch(search) { 51 | for (var i = 0, length = searchTermsLength; i < length; i++) { 52 | search.search(searchTerms[i]); 53 | } 54 | } 55 | 56 | function runTests() { 57 | console.log("Testing search speeds ..."); 58 | 59 | new Benchmark.Suite() 60 | .on("cycle", event => { 61 | bb.add(event.target); 62 | }) 63 | .on("complete", () => { 64 | bb.log(); 65 | }) 66 | .add("lunr", () => { 67 | doSearch(lunrJsIndex); 68 | }) 69 | .add("js-search:latest", () => { 70 | doSearch(searchLatest); 71 | }) 72 | .add("js-search:local", () => { 73 | doSearch(searchLocal); 74 | }) 75 | .run({ async: true }); 76 | } 77 | 78 | loadBooks(); 79 | -------------------------------------------------------------------------------- /benchmarks/yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | beautify-benchmark@^0.2.4: 6 | version "0.2.4" 7 | resolved "https://registry.yarnpkg.com/beautify-benchmark/-/beautify-benchmark-0.2.4.tgz#3151def14c1a2e0d07ff2e476861c7ed0e1ae39b" 8 | 9 | benchmark@^2.1.3: 10 | version "2.1.3" 11 | resolved "https://registry.yarnpkg.com/benchmark/-/benchmark-2.1.3.tgz#e10e40e4d53d0e1c9d77a834fde593994dca7f0c" 12 | dependencies: 13 | lodash "^4.17.3" 14 | platform "^1.3.3" 15 | 16 | bindings@1.2.x: 17 | version "1.2.1" 18 | resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.2.1.tgz#14ad6113812d2d37d72e67b4cacb4bb726505f11" 19 | 20 | flow-bin@^0.50.0: 21 | version "0.50.0" 22 | resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.50.0.tgz#d4cdb2430dee1a3599f0eb6fe551146e3027256a" 23 | 24 | js-worker-search@^1.2.0: 25 | version "1.2.0" 26 | resolved "https://registry.yarnpkg.com/js-worker-search/-/js-worker-search-1.2.0.tgz#ef18d029bda0d8ef412d4f680d2d0b041b5f8e62" 27 | dependencies: 28 | flow-bin "^0.50.0" 29 | uuid "^2.0.1" 30 | 31 | lodash@^4.17.3, lodash@^4.17.4: 32 | version "4.17.4" 33 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" 34 | 35 | lunr@^0.7.2: 36 | version "0.7.2" 37 | resolved "https://registry.yarnpkg.com/lunr/-/lunr-0.7.2.tgz#79a30e932e216cba163541ee37a3607c12cd7281" 38 | 39 | microtime@^2.1.2: 40 | version "2.1.2" 41 | resolved "https://registry.yarnpkg.com/microtime/-/microtime-2.1.2.tgz#9c955d0781961ab13a1b6f9a82b080f5d7ecd83b" 42 | dependencies: 43 | bindings "1.2.x" 44 | nan "2.4.x" 45 | 46 | nan@2.4.x: 47 | version "2.4.0" 48 | resolved "https://registry.yarnpkg.com/nan/-/nan-2.4.0.tgz#fb3c59d45fe4effe215f0b890f8adf6eb32d2232" 49 | 50 | platform@^1.3.3: 51 | version "1.3.3" 52 | resolved "https://registry.yarnpkg.com/platform/-/platform-1.3.3.tgz#646c77011899870b6a0903e75e997e8e51da7461" 53 | 54 | uuid@^2.0.1: 55 | version "2.0.3" 56 | resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.3.tgz#67e2e863797215530dff318e5bf9dcebfd47b21a" 57 | -------------------------------------------------------------------------------- /flow-defs.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | declare var test: any; 3 | declare var expect: any; 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "js-worker-search", 3 | "version": "2.0.0", 4 | "description": "JavaScript client-side search API with web-worker support", 5 | "author": "Brian Vaughn (brian.david.vaughn@gmail.com)", 6 | "license": "MIT", 7 | "main": "dist/js-worker-search.js", 8 | "bugs": { 9 | "url": "https://github.com/bvaughn/js-worker-search/issues" 10 | }, 11 | "homepage": "https://github.com/bvaughn/js-worker-search#readme", 12 | "scripts": { 13 | "build": "yarn run clean && NODE_ENV=production webpack --config webpack.config.dist.js --bail", 14 | "clean": "rimraf dist", 15 | "flow": "flow src", 16 | "lint": "eslint 'src/**/*.js'", 17 | "prebuild": "yarn run lint", 18 | "prepublish": "yarn run build", 19 | "pretest": "yarn run flow && yarn run prettier && yarn run lint", 20 | "prettier": "prettier --write '{src/**,.}/*.js'", 21 | "start": "watch 'clear && yarn run lint -s && yarn run test -s' src", 22 | "test": "jest" 23 | }, 24 | "keywords": [ 25 | "search", 26 | "filter", 27 | "database", 28 | "solr", 29 | "worker", 30 | "webworker" 31 | ], 32 | "files": [ 33 | "dist" 34 | ], 35 | "standard": { 36 | "parser": "babel-eslint", 37 | "ignore": [ 38 | "build", 39 | "dist", 40 | "node_modules" 41 | ], 42 | "global": [ 43 | "describe", 44 | "expect", 45 | "it", 46 | "self", 47 | "test" 48 | ] 49 | }, 50 | "devDependencies": { 51 | "babel-cli": "^6.24.1", 52 | "babel-core": "^6.25.0", 53 | "babel-eslint": "^7.2.3", 54 | "babel-jest": "^22.1.0", 55 | "babel-loader": "^6.2.0", 56 | "babel-plugin-syntax-flow": "^6.3.13", 57 | "babel-plugin-transform-flow-strip-types": "^6.3.15", 58 | "babel-preset-es2015": "^6.3.13", 59 | "babel-preset-stage-0": "^6.3.13", 60 | "babylon": "^6.17.4", 61 | "eslint": "^4.2.0", 62 | "eslint-config-prettier": "^2.3.0", 63 | "eslint-plugin-flowtype": "^2.35.0", 64 | "eslint-plugin-prettier": "^2.1.2", 65 | "immutable": "^3.8.1", 66 | "jest": "^22.1.1", 67 | "prettier": "^1.5.3", 68 | "rimraf": "^2.5.0", 69 | "watch": "^1.0.2", 70 | "webpack": "^1.12.9", 71 | "worker-loader": "^0.8.1", 72 | "flow-bin": "^0.50.0" 73 | }, 74 | "dependencies": { 75 | "uuid": "^8.3.2" 76 | } 77 | } -------------------------------------------------------------------------------- /src/SearchApi.js: -------------------------------------------------------------------------------- 1 | /** @flow */ 2 | 3 | import { SearchUtility } from "./util"; 4 | import SearchWorkerLoader from "./worker"; 5 | 6 | import type { IndexMode } from "./util"; 7 | 8 | /** 9 | * Search API that uses web workers when available. 10 | * Indexing and searching is performed in the UI thread as a fallback when web workers aren't supported. 11 | */ 12 | export default class SearchApi { 13 | _search: any; // TODO 14 | 15 | constructor( 16 | { 17 | caseSensitive, 18 | indexMode, 19 | matchAnyToken, 20 | tokenizePattern 21 | }: { 22 | caseSensitive?: boolean, 23 | indexMode?: IndexMode, 24 | matchAnyToken?: boolean, 25 | tokenizePattern?: RegExp 26 | } = {} 27 | ) { 28 | // Based on https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers 29 | // But with added check for Node environment 30 | if (typeof window !== "undefined" && window.Worker) { 31 | this._search = new SearchWorkerLoader({ 32 | indexMode, 33 | matchAnyToken, 34 | tokenizePattern, 35 | caseSensitive 36 | }); 37 | } else { 38 | this._search = new SearchUtility({ 39 | indexMode, 40 | matchAnyToken, 41 | tokenizePattern, 42 | caseSensitive 43 | }); 44 | } 45 | } 46 | 47 | /** 48 | * Adds or updates a uid in the search index and associates it with the specified text. 49 | * Note that at this time uids can only be added or updated in the index, not removed. 50 | * 51 | * @param uid Uniquely identifies a searchable object 52 | * @param text Text to associate with uid 53 | */ 54 | indexDocument = (uid: any, text: string): SearchApi => { 55 | this._search.indexDocument(uid, text); 56 | 57 | return this; 58 | }; 59 | 60 | /** 61 | * Searches the current index for the specified query text. 62 | * Only uids matching all of the words within the text will be accepted. 63 | * If an empty query string is provided all indexed uids will be returned. 64 | * 65 | * Document searches are case-insensitive (e.g. "search" will match "Search"). 66 | * Document searches use substring matching (e.g. "na" and "me" will both match "name"). 67 | * 68 | * @param query Searchable query text 69 | * @return Promise to be resolved with an Array of matching uids 70 | */ 71 | search = (query: string): Promise> => { 72 | // Promise.resolve handles both synchronous and web-worker Search utilities 73 | return this._search.search(query); 74 | }; 75 | 76 | /** 77 | * Stops and retires the worker in the search API. Used for cleanup. 78 | */ 79 | terminate = () => { 80 | this._search.terminate(); 81 | }; 82 | } 83 | -------------------------------------------------------------------------------- /src/SearchApi.test.js: -------------------------------------------------------------------------------- 1 | import SearchApi from "./"; 2 | import Immutable from "immutable"; 3 | 4 | const documentA = Immutable.fromJS({ 5 | id: 1, 6 | name: "One", 7 | description: "The first document" 8 | }); 9 | const documentB = Immutable.fromJS({ 10 | id: 2, 11 | name: "Two", 12 | description: "The second document" 13 | }); 14 | const documentC = Immutable.fromJS({ 15 | id: 3, 16 | name: "Three", 17 | description: "The third document" 18 | }); 19 | 20 | const documents = [documentA, documentB, documentC]; 21 | 22 | test("SearchApi should return documents ids for any searchable field matching an AND query", async done => { 23 | const searchApi = new SearchApi(); 24 | documents.forEach(doc => { 25 | searchApi.indexDocument(doc.get("id"), doc.get("name")); 26 | searchApi.indexDocument(doc.get("id"), doc.get("description")); 27 | }); 28 | const ids = await searchApi.search("ir"); 29 | expect(ids.length).toBe(2); 30 | expect(ids).toEqual([1, 3]); 31 | done(); 32 | }); 33 | 34 | test("SearchApi should return documents ids for any searchable field matching an OR query", async done => { 35 | const searchApi = new SearchApi({ 36 | matchAnyToken: true 37 | }); 38 | documents.forEach(doc => { 39 | searchApi.indexDocument(doc.get("id"), doc.get("name")); 40 | searchApi.indexDocument(doc.get("id"), doc.get("description")); 41 | }); 42 | const ids = await searchApi.search("first third"); 43 | expect(ids.length).toBe(2); 44 | expect(ids).toEqual([1, 3]); 45 | done(); 46 | }); 47 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | /** @flow */ 2 | 3 | import SearchApi from "./SearchApi"; 4 | import { INDEX_MODES } from "./util"; 5 | 6 | export default SearchApi; 7 | export { INDEX_MODES }; 8 | -------------------------------------------------------------------------------- /src/types.js: -------------------------------------------------------------------------------- 1 | /** @flow */ 2 | 3 | /** 4 | * Maps search tokens to uids using a trie structure. 5 | */ 6 | export interface SearchApiIndex { 7 | /** 8 | * Maps the specified token to a uid. 9 | * 10 | * @param token Searchable token (e.g. "road") 11 | * @param uid Identifies a document within the searchable corpus 12 | */ 13 | indexDocument: (token: string, uid: any) => SearchApiIndex, 14 | 15 | /** 16 | * Finds uids that have been mapped to the set of tokens specified. 17 | * Only uids that have been mapped to all tokens will be returned by default. 18 | * 19 | * @param tokens Array of searchable tokens (e.g. ["long", "road"]) 20 | * @return Array of uids that have been associated with the set of search tokens 21 | */ 22 | search: (query: string) => Array | Promise>, 23 | 24 | /** 25 | * Provides cleanup code for the search API 26 | */ 27 | terminate: () => void 28 | } 29 | -------------------------------------------------------------------------------- /src/util/SearchIndex.js: -------------------------------------------------------------------------------- 1 | /** @flow */ 2 | 3 | /** 4 | * Maps search tokens to uids using a trie structure. 5 | */ 6 | export default class SearchIndex { 7 | tokenToUidMap: { [token: string]: any }; 8 | 9 | constructor() { 10 | this.tokenToUidMap = {}; 11 | } 12 | 13 | /** 14 | * Maps the specified token to a uid. 15 | * 16 | * @param token Searchable token (e.g. "road") 17 | * @param uid Identifies a document within the searchable corpus 18 | */ 19 | indexDocument(token: string, uid: any): void { 20 | if (!this.tokenToUidMap[token]) { 21 | this.tokenToUidMap[token] = {}; 22 | } 23 | 24 | this.tokenToUidMap[token][uid] = uid; 25 | } 26 | 27 | /** 28 | * Finds uids that have been mapped to the set of tokens specified. 29 | * Only uids that have been mapped to all tokens will be returned. 30 | * 31 | * @param tokens Array of searchable tokens (e.g. ["long", "road"]) 32 | * @param matchAnyToken Whether to match any token. Default is false. 33 | * @return Array of uids that have been associated with the set of search tokens 34 | */ 35 | search(tokens: Array, matchAnyToken: boolean): Array { 36 | let uidMap: { [uid: any]: any } = {}; 37 | let uidMatches: { [uid: any]: number } = {}; 38 | let initialized = false; 39 | 40 | tokens.forEach(token => { 41 | let currentUidMap: { [uid: any]: any } = this.tokenToUidMap[token] || {}; 42 | 43 | if (!initialized) { 44 | initialized = true; 45 | 46 | for (let uid in currentUidMap) { 47 | uidMap[uid] = currentUidMap[uid]; 48 | uidMatches[uid] = 1; 49 | } 50 | } else { 51 | // Delete existing matches if using and AND query (the default) 52 | // Otherwise add new results to the matches 53 | if (!matchAnyToken) { 54 | for (let uid in uidMap) { 55 | if (!currentUidMap[uid]) { 56 | delete uidMap[uid]; 57 | } 58 | } 59 | } else { 60 | for (let uid in currentUidMap) { 61 | uidMap[uid] = currentUidMap[uid]; 62 | uidMatches[uid] = (uidMatches[uid] || 0) + 1; 63 | } 64 | } 65 | } 66 | }); 67 | 68 | let uids: Array = []; 69 | for (let uid in uidMap) { 70 | uids.push(uidMap[uid]); 71 | } 72 | 73 | // Sort according to most matches, if match any token is set. 74 | if (matchAnyToken) { 75 | uids.sort((a, b) => { 76 | return uidMatches[b] - uidMatches[a]; 77 | }); 78 | } 79 | 80 | return uids; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/util/SearchUtility.js: -------------------------------------------------------------------------------- 1 | /** @flow */ 2 | 3 | import { INDEX_MODES } from "./constants"; 4 | import SearchIndex from "./SearchIndex"; 5 | 6 | import type { IndexMode } from "./constants"; 7 | import type { SearchApiIndex } from "../types"; 8 | 9 | type UidMap = { 10 | [uid: string]: boolean 11 | }; 12 | 13 | /** 14 | * Synchronous client-side full-text search utility. 15 | * Forked from JS search (github.com/bvaughn/js-search). 16 | */ 17 | export default class SearchUtility implements SearchApiIndex { 18 | _caseSensitive: boolean; 19 | _indexMode: IndexMode; 20 | _matchAnyToken: boolean; 21 | _searchIndex: SearchIndex; 22 | _tokenizePattern: RegExp; 23 | _uids: UidMap; 24 | 25 | /** 26 | * Constructor. 27 | * 28 | * @param indexMode See #setIndexMode 29 | * @param tokenizePattern See #setTokenizePattern 30 | * @param caseSensitive See #setCaseSensitive 31 | * @param matchAnyToken See #setMatchAnyToken 32 | */ 33 | constructor( 34 | { 35 | caseSensitive = false, 36 | indexMode = INDEX_MODES.ALL_SUBSTRINGS, 37 | matchAnyToken = false, 38 | tokenizePattern = /\s+/ 39 | }: { 40 | caseSensitive?: boolean, 41 | indexMode?: IndexMode, 42 | matchAnyToken?: boolean, 43 | tokenizePattern?: RegExp 44 | } = {} 45 | ) { 46 | this._caseSensitive = caseSensitive; 47 | this._indexMode = indexMode; 48 | this._matchAnyToken = matchAnyToken; 49 | this._tokenizePattern = tokenizePattern; 50 | 51 | this._searchIndex = new SearchIndex(); 52 | this._uids = {}; 53 | } 54 | 55 | /** 56 | * Returns a constant representing the current case-sensitive bit. 57 | */ 58 | getCaseSensitive(): boolean { 59 | return this._caseSensitive; 60 | } 61 | 62 | /** 63 | * Returns a constant representing the current index mode. 64 | */ 65 | getIndexMode(): string { 66 | return this._indexMode; 67 | } 68 | 69 | /** 70 | * Returns a constant representing the current match-any-token bit. 71 | */ 72 | getMatchAnyToken(): boolean { 73 | return this._matchAnyToken; 74 | } 75 | 76 | /** 77 | * Returns a constant representing the current tokenize pattern. 78 | */ 79 | getTokenizePattern(): RegExp { 80 | return this._tokenizePattern; 81 | } 82 | 83 | /** 84 | * Adds or updates a uid in the search index and associates it with the specified text. 85 | * Note that at this time uids can only be added or updated in the index, not removed. 86 | * 87 | * @param uid Uniquely identifies a searchable object 88 | * @param text Text to associate with uid 89 | */ 90 | indexDocument = (uid: any, text: string): SearchApiIndex => { 91 | this._uids[uid] = true; 92 | 93 | var fieldTokens: Array = this._tokenize(this._sanitize(text)); 94 | 95 | fieldTokens.forEach(fieldToken => { 96 | var expandedTokens: Array = this._expandToken(fieldToken); 97 | 98 | expandedTokens.forEach(expandedToken => { 99 | this._searchIndex.indexDocument(expandedToken, uid); 100 | }); 101 | }); 102 | 103 | return this; 104 | }; 105 | 106 | /** 107 | * Searches the current index for the specified query text. 108 | * Only uids matching all of the words within the text will be accepted, 109 | * unless matchAny is set to true. 110 | * If an empty query string is provided all indexed uids will be returned. 111 | * 112 | * Document searches are case-insensitive by default (e.g. "search" will match "Search"). 113 | * Document searches use substring matching by default (e.g. "na" and "me" will both match "name"). 114 | * 115 | * @param query Searchable query text 116 | * @return Array of uids 117 | */ 118 | search = (query: string): Array => { 119 | if (!query) { 120 | return Object.keys(this._uids); 121 | } else { 122 | var tokens: Array = this._tokenize(this._sanitize(query)); 123 | 124 | return this._searchIndex.search(tokens, this._matchAnyToken); 125 | } 126 | }; 127 | 128 | /** 129 | * Sets a new case-sensitive bit 130 | */ 131 | setCaseSensitive(caseSensitive: boolean): void { 132 | this._caseSensitive = caseSensitive; 133 | } 134 | 135 | /** 136 | * Sets a new index mode. 137 | * See util/constants/INDEX_MODES 138 | */ 139 | setIndexMode(indexMode: IndexMode): void { 140 | if (Object.keys(this._uids).length > 0) { 141 | throw Error( 142 | "indexMode cannot be changed once documents have been indexed" 143 | ); 144 | } 145 | 146 | this._indexMode = indexMode; 147 | } 148 | 149 | /** 150 | * Sets a new match-any-token bit 151 | */ 152 | setMatchAnyToken(matchAnyToken: boolean): void { 153 | this._matchAnyToken = matchAnyToken; 154 | } 155 | 156 | /** 157 | * Sets a new tokenize pattern (regular expression) 158 | */ 159 | setTokenizePattern(pattern: RegExp): void { 160 | this._tokenizePattern = pattern; 161 | } 162 | 163 | /** 164 | * Added to make class adhere to interface. Add cleanup code as needed. 165 | */ 166 | terminate = () => {}; 167 | 168 | /** 169 | * Index strategy based on 'all-substrings-index-strategy.ts' in github.com/bvaughn/js-search/ 170 | * 171 | * @private 172 | */ 173 | _expandToken(token: string): Array { 174 | switch (this._indexMode) { 175 | case INDEX_MODES.EXACT_WORDS: 176 | return [token]; 177 | case INDEX_MODES.PREFIXES: 178 | return this._expandPrefixTokens(token); 179 | case INDEX_MODES.ALL_SUBSTRINGS: 180 | default: 181 | return this._expandAllSubstringTokens(token); 182 | } 183 | } 184 | 185 | _expandAllSubstringTokens(token: string): Array { 186 | const expandedTokens = []; 187 | 188 | // String.prototype.charAt() may return surrogate halves instead of whole characters. 189 | // When this happens in the context of a web-worker it can cause Chrome to crash. 190 | // Catching the error is a simple solution for now; in the future I may try to better support non-BMP characters. 191 | // Resources: 192 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/charAt 193 | // https://mathiasbynens.be/notes/javascript-unicode 194 | try { 195 | for (let i = 0, length = token.length; i < length; ++i) { 196 | let substring: string = ""; 197 | 198 | for (let j = i; j < length; ++j) { 199 | substring += token.charAt(j); 200 | expandedTokens.push(substring); 201 | } 202 | } 203 | } catch (error) { 204 | console.error(`Unable to parse token "${token}" ${error}`); 205 | } 206 | 207 | return expandedTokens; 208 | } 209 | 210 | _expandPrefixTokens(token: string): Array { 211 | const expandedTokens = []; 212 | 213 | // String.prototype.charAt() may return surrogate halves instead of whole characters. 214 | // When this happens in the context of a web-worker it can cause Chrome to crash. 215 | // Catching the error is a simple solution for now; in the future I may try to better support non-BMP characters. 216 | // Resources: 217 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/charAt 218 | // https://mathiasbynens.be/notes/javascript-unicode 219 | try { 220 | for (let i = 0, length = token.length; i < length; ++i) { 221 | expandedTokens.push(token.substr(0, i + 1)); 222 | } 223 | } catch (error) { 224 | console.error(`Unable to parse token "${token}" ${error}`); 225 | } 226 | 227 | return expandedTokens; 228 | } 229 | 230 | /** 231 | * @private 232 | */ 233 | _sanitize(string: string): string { 234 | return this._caseSensitive 235 | ? string.trim() 236 | : string.trim().toLocaleLowerCase(); 237 | } 238 | 239 | /** 240 | * @private 241 | */ 242 | _tokenize(text: string): Array { 243 | return text.split(this._tokenizePattern).filter(text => text); // Remove empty tokens 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /src/util/SearchUtility.test.js: -------------------------------------------------------------------------------- 1 | /** @flow */ 2 | 3 | import { fromJS } from "immutable"; 4 | import SearchUtility from "./SearchUtility"; 5 | import { INDEX_MODES } from "./constants"; 6 | import type { IndexMode } from "./constants"; 7 | 8 | const documentA = fromJS({ 9 | id: 1, 10 | name: "One", 11 | description: "The first document" 12 | }); 13 | const documentB = fromJS({ 14 | id: 2, 15 | name: "Two", 16 | description: "The second document" 17 | }); 18 | const documentC = fromJS({ 19 | id: 3, 20 | name: "Three", 21 | description: "The third document" 22 | }); 23 | const documentD = fromJS({ 24 | id: 4, 25 | name: "楌ぴ", 26 | description: "堦ヴ礯 ラ蝥曣んを 檨儯饨䶧" 27 | }); 28 | const documentE = fromJS({ 29 | id: 5, 30 | name: "ㄨ穯ゆ姎囥", 31 | description: "楌ぴ 堦ヴ礯 ラ蝥曣んを 檨儯饨䶧䏤" 32 | }); 33 | const documentF = fromJS({ 34 | id: 6, 35 | name: "Six", 36 | description: "Este es el sexto/6o documento" 37 | }); 38 | const documentG = fromJS({ 39 | id: 7, 40 | name: "Seven", 41 | description: "ქართული ენა" 42 | }); 43 | 44 | const documents = [ 45 | documentA, 46 | documentB, 47 | documentC, 48 | documentD, 49 | documentE, 50 | documentF, 51 | documentG 52 | ]; 53 | 54 | function init( 55 | { 56 | indexMode, 57 | tokenizePattern, 58 | caseSensitive, 59 | matchAnyToken 60 | }: { 61 | indexMode?: IndexMode, 62 | tokenizePattern?: RegExp, 63 | caseSensitive?: boolean, 64 | matchAnyToken?: boolean 65 | } = {} 66 | ) { 67 | const searchUtility = new SearchUtility({ 68 | indexMode, 69 | tokenizePattern, 70 | caseSensitive, 71 | matchAnyToken 72 | }); 73 | 74 | documents.forEach(doc => { 75 | searchUtility.indexDocument(doc.get("id"), doc.get("name")); 76 | searchUtility.indexDocument(doc.get("id"), doc.get("description")); 77 | }); 78 | 79 | return searchUtility; 80 | } 81 | 82 | test("SearchUtility should return documents ids for any searchable field matching a query", async done => { 83 | const searchUtility = init(); 84 | let ids = await searchUtility.search("One"); 85 | expect(ids.length).toBe(1); 86 | expect(ids).toEqual([1]); 87 | 88 | ids = await searchUtility.search("Third"); 89 | expect(ids.length).toBe(1); 90 | expect(ids).toEqual([3]); 91 | 92 | ids = await searchUtility.search("the"); 93 | expect(ids.length).toBe(3); 94 | expect(ids).toEqual([1, 2, 3]); 95 | 96 | ids = await searchUtility.search("楌"); // Tests matching of other script systems, eg Japanese 97 | expect(ids.length).toBe(2); 98 | expect(ids).toEqual([4, 5]); 99 | 100 | ids = await searchUtility.search("ენ"); // Tests matching of other script systems, eg Georgian 101 | expect(ids.length).toBe(1); 102 | expect(ids).toEqual([7]); 103 | 104 | done(); 105 | }); 106 | 107 | test("SearchUtility should return documents ids only if document matches all tokens in a query", async done => { 108 | const searchUtility = init(); 109 | let ids = await searchUtility.search("the second"); 110 | expect(ids.length).toBe(1); 111 | expect(ids[0]).toBe(2); 112 | 113 | ids = await searchUtility.search("three document"); // Spans multiple fields 114 | expect(ids.length).toBe(1); 115 | expect(ids[0]).toBe(3); 116 | done(); 117 | }); 118 | 119 | test("SearchUtility should return an empty array for query without matching documents", async done => { 120 | const searchUtility = init(); 121 | const ids = await searchUtility.search("four"); 122 | expect(ids.length).toBe(0); 123 | done(); 124 | }); 125 | 126 | test("SearchUtility should return all uids for an empty query", async done => { 127 | const searchUtility = init(); 128 | const ids = await searchUtility.search(""); 129 | expect(ids.length).toBe(documents.length); 130 | done(); 131 | }); 132 | 133 | test("SearchUtility should ignore case when searching", async done => { 134 | const searchUtility = init(); 135 | const texts = ["one", "One", "ONE"]; 136 | texts.forEach(async text => { 137 | const ids = await searchUtility.search(text); 138 | expect(ids.length).toBe(1); 139 | expect(ids[0]).toBe(1); 140 | }); 141 | 142 | done(); 143 | }); 144 | 145 | test("SearchUtility should use substring matching", async done => { 146 | const searchUtility = init(); 147 | let texts = ["sec", "second", "eco", "cond"]; 148 | texts.forEach(async text => { 149 | let ids = await searchUtility.search(text); 150 | expect(ids.length).toBe(1); 151 | expect(ids[0]).toBe(2); 152 | }); 153 | 154 | texts = ["堦", "堦ヴ", "堦ヴ礯", "ヴ", "ヴ礯"]; 155 | texts.forEach(async text => { 156 | let ids = await searchUtility.search(text); 157 | expect(ids.length).toBe(2); 158 | expect(ids).toEqual([4, 5]); 159 | }); 160 | 161 | done(); 162 | }); 163 | 164 | test("SearchUtility should allow custom indexing via indexDocument", async done => { 165 | const searchUtility = init(); 166 | const text = "xyz"; 167 | let ids = await searchUtility.search(text); 168 | expect(ids.length).toBe(0); 169 | 170 | const id = documentA.get("id"); 171 | searchUtility.indexDocument(id, text); 172 | 173 | ids = await searchUtility.search(text); 174 | expect(ids.length).toBe(1); 175 | expect(ids[0]).toBe(1); 176 | done(); 177 | }); 178 | 179 | test("SearchUtility should recognize an :indexMode constructor param", () => { 180 | const searchUtility = new SearchUtility({ 181 | indexMode: INDEX_MODES.EXACT_WORDS 182 | }); 183 | expect(searchUtility.getIndexMode()).toBe(INDEX_MODES.EXACT_WORDS); 184 | }); 185 | 186 | test("SearchUtility should update its default :indexMode when :setIndexMode() is called", () => { 187 | const searchUtility = new SearchUtility(); 188 | searchUtility.setIndexMode(INDEX_MODES.EXACT_WORDS); 189 | expect(searchUtility.getIndexMode()).toBe(INDEX_MODES.EXACT_WORDS); 190 | }); 191 | 192 | test("SearchUtility should should error if :setIndexMode() is called after an index has been created", () => { 193 | let errored = false; 194 | const searchUtility = init(); 195 | try { 196 | searchUtility.indexDocument("foo", "bar"); 197 | searchUtility.setIndexMode(INDEX_MODES.EXACT_WORDS); 198 | } catch (error) { 199 | errored = true; 200 | } 201 | expect(errored).toBe(true); 202 | }); 203 | 204 | test("SearchUtility should support PREFIXES :indexMode", async done => { 205 | const searchUtility = init({ indexMode: INDEX_MODES.PREFIXES }); 206 | const match1 = ["fir", "first"]; 207 | const match2 = ["sec", "second"]; 208 | match1.forEach(async token => { 209 | const ids = await searchUtility.search(token); 210 | expect(ids).toEqual([1]); 211 | }); 212 | match2.forEach(async token => { 213 | const ids = await searchUtility.search(token); 214 | expect(ids).toEqual([2]); 215 | }); 216 | const noMatch = ["irst", "rst", "st", "irs", "ond", "econd", "eco"]; 217 | noMatch.forEach(async token => { 218 | const ids = await searchUtility.search(token); 219 | expect(ids.length).toEqual(0); 220 | }); 221 | done(); 222 | }); 223 | 224 | test("SearchUtility should support EXACT_WORDS :indexMode", async done => { 225 | const searchUtility = init({ indexMode: INDEX_MODES.EXACT_WORDS }); 226 | expect(await searchUtility.search("first")).toEqual([1]); 227 | expect(await searchUtility.search("second")).toEqual([2]); 228 | const noMatch = ["irst", "rst", "st", "irs", "ond", "econd", "eco"]; 229 | noMatch.forEach(async token => { 230 | const ids = await searchUtility.search(token); 231 | expect(ids.length).toBe(0); 232 | }); 233 | done(); 234 | }); 235 | 236 | test("SearchUtility should update its default :caseSensitive bit when :setCaseSensitive() is called", () => { 237 | const searchUtility = new SearchUtility(); 238 | expect(searchUtility.getCaseSensitive()).toBe(false); 239 | searchUtility.setCaseSensitive(true); 240 | expect(searchUtility.getCaseSensitive()).toBe(true); 241 | }); 242 | 243 | test("SearchUtility should update its default :matchAnyToken bit when :setMatchAnyToken() is called", () => { 244 | const searchUtility = new SearchUtility(); 245 | expect(searchUtility.getMatchAnyToken()).toBe(false); 246 | searchUtility.setMatchAnyToken(true); 247 | expect(searchUtility.getMatchAnyToken()).toBe(true); 248 | }); 249 | 250 | test("SearchUtility should update its default :tokenizePattern when :setTokenizePattern() is called", () => { 251 | const searchUtility = new SearchUtility(); 252 | searchUtility.setTokenizePattern(/[^a-z0-9]/); 253 | expect(searchUtility.getTokenizePattern()).toEqual(/[^a-z0-9]/); 254 | }); 255 | 256 | test("SearchUtility should support case sensitive search", async done => { 257 | const searchUtility = init({ 258 | indexMode: INDEX_MODES.EXACT_WORDS, 259 | caseSensitive: true 260 | }); 261 | expect((await searchUtility.search("First")).length).toBe(0); 262 | expect(await searchUtility.search("first")).toEqual([1]); 263 | done(); 264 | }); 265 | 266 | test("SearchUtility should support an OS search (match any) search", async done => { 267 | const searchUtility = init({ matchAnyToken: true }); 268 | let ids = await searchUtility.search("first two second"); 269 | expect(ids.length).toBe(2); 270 | expect(ids[0]).toBe(2); // The second document has most matches 271 | expect(ids[1]).toBe(1); 272 | done(); 273 | }); 274 | 275 | test("SearchUtility should support custom tokenizer pattern", async done => { 276 | const searchUtility = init({ 277 | indexMode: INDEX_MODES.EXACT_WORDS, 278 | tokenizePattern: /[^a-z0-9]+/ 279 | }); 280 | expect(await searchUtility.search("sexto")).toEqual([6]); 281 | expect(await searchUtility.search("6o")).toEqual([6]); 282 | done(); 283 | }); 284 | -------------------------------------------------------------------------------- /src/util/constants.js: -------------------------------------------------------------------------------- 1 | /** @flow */ 2 | 3 | export const INDEX_MODES = { 4 | // Indexes for all substring searches (e.g. the term "cat" is indexed as "c", "ca", "cat", "a", "at", and "t"). 5 | // Based on 'all-substrings-index-strategy' from js-search; 6 | // github.com/bvaughn/js-search/blob/master/source/index-strategy/all-substrings-index-strategy.ts 7 | ALL_SUBSTRINGS: "ALL_SUBSTRINGS", 8 | 9 | // Indexes for exact word matches only. 10 | // Based on 'exact-word-index-strategy' from js-search; 11 | // github.com/bvaughn/js-search/blob/master/source/index-strategy/exact-word-index-strategy.ts 12 | EXACT_WORDS: "EXACT_WORDS", 13 | 14 | // Indexes for prefix searches (e.g. the term "cat" is indexed as "c", "ca", and "cat" allowing prefix search lookups). 15 | // Based on 'prefix-index-strategy' from js-search; 16 | // github.com/bvaughn/js-search/blob/master/source/index-strategy/prefix-index-strategy.ts 17 | PREFIXES: "PREFIXES" 18 | }; 19 | 20 | export type IndexMode = $Keys; 21 | -------------------------------------------------------------------------------- /src/util/index.js: -------------------------------------------------------------------------------- 1 | /** @flow */ 2 | 3 | import SearchUtility from "./SearchUtility"; 4 | import { INDEX_MODES } from "./constants"; 5 | 6 | export type { IndexMode } from "./constants"; 7 | 8 | export default SearchUtility; 9 | export { INDEX_MODES, SearchUtility }; 10 | -------------------------------------------------------------------------------- /src/worker/SearchWorkerLoader.js: -------------------------------------------------------------------------------- 1 | /** @flow */ 2 | 3 | import { v4 as uuidv4 } from "uuid"; 4 | 5 | import type { IndexMode } from "../util"; 6 | import type { SearchApiIndex } from "../types"; 7 | 8 | type Results = Array; 9 | type RejectFn = (error: Error) => void; 10 | type ResolveFn = (results: Results) => void; 11 | export type CallbackData = { 12 | callbackId: string, 13 | complete: boolean, 14 | error: ?Error, 15 | reject: RejectFn, 16 | resolve: ResolveFn, 17 | results: ?Results 18 | }; 19 | type CallbackIdMap = { [callbackId: string]: CallbackData }; 20 | type CallbackQueue = Array; 21 | 22 | type Worker = any; // TODO 23 | 24 | /** 25 | * Client side, full text search utility. 26 | * This interface exposes web worker search capabilities to the UI thread. 27 | */ 28 | export default class SearchWorkerLoader implements SearchApiIndex { 29 | _callbackIdMap: CallbackIdMap; 30 | _callbackQueue: CallbackQueue; 31 | _worker: Worker; 32 | 33 | /** 34 | * Constructor. 35 | */ 36 | constructor( 37 | { 38 | caseSensitive, 39 | indexMode, 40 | matchAnyToken, 41 | tokenizePattern, 42 | WorkerClass 43 | }: { 44 | caseSensitive?: boolean, 45 | indexMode?: IndexMode, 46 | matchAnyToken?: boolean, 47 | tokenizePattern?: RegExp, 48 | WorkerClass?: Class 49 | } = {} 50 | ) { 51 | // Defer worker import until construction to avoid testing error: 52 | // Error: Cannot find module 'worker!./[workername]' 53 | if (!WorkerClass) { 54 | // $FlowFixMe eslint-disable-next-line 55 | WorkerClass = require("worker?inline=true!./Worker"); 56 | } 57 | 58 | this._callbackQueue = []; 59 | this._callbackIdMap = {}; 60 | 61 | this._worker = new WorkerClass(); 62 | this._worker.onerror = event => { 63 | if (event.data) { 64 | const { callbackId, error } = event.data; 65 | this._updateQueue({ callbackId, error }); 66 | } else { 67 | console.error(event); 68 | } 69 | }; 70 | this._worker.onmessage = event => { 71 | const { callbackId, results } = event.data; 72 | this._updateQueue({ callbackId, results }); 73 | }; 74 | 75 | // Override default :caseSensitive bit if a specific one has been requested 76 | if (caseSensitive) { 77 | this._worker.postMessage({ 78 | method: "setCaseSensitive", 79 | caseSensitive 80 | }); 81 | } 82 | 83 | // Override default :indexMode if a specific one has been requested 84 | if (indexMode) { 85 | this._worker.postMessage({ 86 | method: "setIndexMode", 87 | indexMode 88 | }); 89 | } 90 | 91 | // Override default :matchAnyToken bit if a specific one has been requested 92 | if (matchAnyToken) { 93 | this._worker.postMessage({ 94 | method: "setMatchAnyToken", 95 | matchAnyToken 96 | }); 97 | } 98 | 99 | // Override default :tokenizePattern if a specific one has been requested 100 | if (tokenizePattern) { 101 | this._worker.postMessage({ 102 | method: "setTokenizePattern", 103 | tokenizePattern 104 | }); 105 | } 106 | } 107 | 108 | /** 109 | * Adds or updates a uid in the search index and associates it with the specified text. 110 | * Note that at this time uids can only be added or updated in the index, not removed. 111 | * 112 | * @param uid Uniquely identifies a searchable object 113 | * @param text Text to associate with uid 114 | */ 115 | indexDocument = (uid: any, text: string): SearchApiIndex => { 116 | this._worker.postMessage({ 117 | method: "indexDocument", 118 | text, 119 | uid 120 | }); 121 | 122 | return this; 123 | }; 124 | 125 | /** 126 | * Searches the current index for the specified query text. 127 | * Only uids matching all of the words within the text will be accepted. 128 | * If an empty query string is provided all indexed uids will be returned. 129 | * 130 | * Document searches are case-insensitive (e.g. "search" will match "Search"). 131 | * Document searches use substring matching (e.g. "na" and "me" will both match "name"). 132 | * 133 | * @param query Searchable query text 134 | * @return Promise to be resolved with an array of uids 135 | */ 136 | search = (query: string): Promise> => { 137 | return new Promise((resolve: ResolveFn, reject: RejectFn) => { 138 | const callbackId = uuidv4(); 139 | const data = { 140 | callbackId, 141 | complete: false, 142 | error: null, 143 | reject, 144 | resolve, 145 | results: null 146 | }; 147 | 148 | this._worker.postMessage({ 149 | method: "search", 150 | query, 151 | callbackId 152 | }); 153 | 154 | this._callbackQueue.push(data); 155 | this._callbackIdMap[callbackId] = data; 156 | }); 157 | }; 158 | 159 | /** 160 | * Stops and retires the worker. Used for cleanup. 161 | */ 162 | terminate = () => { 163 | this._worker.terminate(); 164 | }; 165 | 166 | /** 167 | * Updates the queue and flushes any completed promises that are ready. 168 | */ 169 | _updateQueue({ 170 | callbackId, 171 | error, 172 | results 173 | }: { 174 | callbackId: string, 175 | error?: Error, 176 | results?: Results 177 | }) { 178 | const target = this._callbackIdMap[callbackId]; 179 | target.complete = true; 180 | target.error = error; 181 | target.results = results; 182 | 183 | while (this._callbackQueue.length) { 184 | let data = this._callbackQueue[0]; 185 | 186 | if (!data.complete) { 187 | break; 188 | } 189 | 190 | this._callbackQueue.shift(); 191 | 192 | delete this._callbackIdMap[data.callbackId]; 193 | 194 | if (data.error) { 195 | data.reject(data.error); 196 | } else { 197 | // This type will always be defined in this case; 198 | // This casting lets Flow know it's safe. 199 | data.resolve((data.results: any)); 200 | } 201 | } 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /src/worker/SearchWorkerLoader.test.js: -------------------------------------------------------------------------------- 1 | /** @flow */ 2 | 3 | import SearchWorkerLoader from "./SearchWorkerLoader"; 4 | import { INDEX_MODES } from "../util"; 5 | 6 | import type { CallbackData } from "./SearchWorkerLoader"; 7 | 8 | function noop() {} 9 | 10 | class StubWorker { 11 | onerror: ({ data: CallbackData }) => void; 12 | onmessage: ({ data: CallbackData }) => void; 13 | 14 | _indexedDocumentMap = {}; 15 | _searchQueue = []; 16 | _setCaseSensitiveQueue = []; 17 | _setIndexModeQueue = []; 18 | _setMatchAnyTokenQueue = []; 19 | _setTokenizePatternQueue = []; 20 | 21 | postMessage(data) { 22 | const { method, ...props } = data; 23 | 24 | switch (method) { 25 | case "indexDocument": 26 | const { uid, text } = props; 27 | if (!this._indexedDocumentMap[uid]) { 28 | this._indexedDocumentMap[uid] = []; 29 | } 30 | this._indexedDocumentMap[uid].push(text); 31 | break; 32 | case "search": 33 | const { callbackId, query } = props; 34 | this._searchQueue.push({ callbackId, query }); 35 | break; 36 | case "setCaseSensitive": 37 | const { caseSensitive } = props; 38 | this._setCaseSensitiveQueue.push({ caseSensitive }); 39 | break; 40 | case "setIndexMode": 41 | const { indexMode } = props; 42 | this._setIndexModeQueue.push({ indexMode }); 43 | break; 44 | case "setMatchAnyToken": 45 | const { matchAnyToken } = props; 46 | this._setMatchAnyTokenQueue.push({ matchAnyToken }); 47 | break; 48 | case "setTokenizePattern": 49 | const { tokenizePattern } = props; 50 | this._setTokenizePatternQueue.push({ tokenizePattern }); 51 | break; 52 | } 53 | } 54 | 55 | rejectSearch(index: number, error: Error) { 56 | const { callbackId } = this._searchQueue[index]; 57 | this.onerror({ 58 | data: { 59 | error, 60 | callbackId, 61 | complete: true, 62 | reject: noop, 63 | resolve: noop, 64 | results: null 65 | } 66 | }); 67 | } 68 | 69 | resolveSearch(index: number, results: Array) { 70 | const { callbackId } = this._searchQueue[index]; 71 | this.onmessage({ 72 | data: { 73 | callbackId, 74 | complete: true, 75 | error: null, 76 | reject: noop, 77 | resolve: noop, 78 | results 79 | } 80 | }); 81 | } 82 | } 83 | 84 | test("SearchWorkerLoader indexDocument should index a document with the specified text(s)", () => { 85 | const search = new SearchWorkerLoader({ WorkerClass: StubWorker }); 86 | search.indexDocument("a", "cat"); 87 | search.indexDocument("a", "dog"); 88 | search.indexDocument("b", "cat"); 89 | 90 | expect(Object.keys(search._worker._indexedDocumentMap).length).toBe(2); 91 | expect(search._worker._indexedDocumentMap.a.length).toBe(2); 92 | expect(search._worker._indexedDocumentMap.a).toEqual(["cat", "dog"]); 93 | expect(search._worker._indexedDocumentMap.b.length).toBe(1); 94 | expect(search._worker._indexedDocumentMap.b).toEqual(["cat"]); 95 | }); 96 | 97 | test("SearchWorkerLoader search should search for the specified text", () => { 98 | const search = new SearchWorkerLoader({ WorkerClass: StubWorker }); 99 | search.search("cat"); 100 | expect(search._worker._searchQueue.length).toEqual(1); 101 | expect(search._worker._searchQueue[0].query).toEqual("cat"); 102 | }); 103 | 104 | test("SearchWorkerLoader search should resolve the returned Promise on search completion", async done => { 105 | const search = new SearchWorkerLoader({ WorkerClass: StubWorker }); 106 | const promise = search.search("cat"); 107 | search._worker.resolveSearch(0, ["a", "b"]); 108 | 109 | const result = await promise; 110 | expect(result).toEqual(["a", "b"]); 111 | done(); 112 | }); 113 | 114 | test("SearchWorkerLoader search should resolve multiple concurrent searches", async done => { 115 | const search = new SearchWorkerLoader({ WorkerClass: StubWorker }); 116 | const promises = Promise.all([search.search("cat"), search.search("dog")]); 117 | search._worker.resolveSearch(0, ["a"]); 118 | search._worker.resolveSearch(1, ["a", "b"]); 119 | await promises; 120 | done(); 121 | }); 122 | 123 | test("SearchWorkerLoader search should resolve searches in the correct order", async done => { 124 | const search = new SearchWorkerLoader({ WorkerClass: StubWorker }); 125 | const results = []; 126 | const promiseList = [ 127 | Promise.resolve(search.search("cat")), 128 | Promise.resolve(search.search("dog")), 129 | Promise.resolve(search.search("rat")) 130 | ].map(promise => promise.then(result => results.push(result))); 131 | 132 | search._worker.resolveSearch(1, ["1"]); 133 | search._worker.resolveSearch(0, ["0"]); 134 | search._worker.resolveSearch(2, ["2"]); 135 | 136 | await Promise.all(promiseList); 137 | const [r1, r2, r3] = results; 138 | expect(r1).toEqual(["0"]); 139 | expect(r2).toEqual(["1"]); 140 | expect(r3).toEqual(["2"]); 141 | done(); 142 | }); 143 | 144 | test("SearchWorkerLoader search should not reject all searches if one fails", async done => { 145 | const search = new SearchWorkerLoader({ WorkerClass: StubWorker }); 146 | const errors = []; 147 | const results = []; 148 | const promises = [ 149 | Promise.resolve(search.search("cat")), 150 | Promise.resolve(search.search("dog")) 151 | ].map(promise => 152 | promise 153 | .then(result => results.push(result)) 154 | .catch(error => errors.push(error)) 155 | ); 156 | 157 | search._worker.rejectSearch(1, new Error("1")); 158 | search._worker.resolveSearch(0, ["0"]); 159 | 160 | try { 161 | await Promise.all(promises); 162 | } catch (err) {} 163 | 164 | expect(results.length).toBe(1); 165 | expect(results[0]).toEqual(["0"]); 166 | expect(errors.length).toBe(1); 167 | expect(errors[0].message).toBe("1"); 168 | done(); 169 | }); 170 | 171 | test("SearchWorkerLoader should pass the specified :indexMode to the WorkerClass", () => { 172 | const search = new SearchWorkerLoader({ 173 | indexMode: INDEX_MODES.EXACT_WORDS, 174 | WorkerClass: StubWorker 175 | }); 176 | expect(search._worker._setIndexModeQueue.length).toBe(1); 177 | expect(search._worker._setIndexModeQueue[0].indexMode).toBe( 178 | INDEX_MODES.EXACT_WORDS 179 | ); 180 | }); 181 | 182 | test("SearchWorkerLoader should not override the default :indexMode in the WorkerClass if an override is not requested", () => { 183 | const search = new SearchWorkerLoader({ WorkerClass: StubWorker }); 184 | expect(search._worker._setIndexModeQueue.length).toBe(0); 185 | }); 186 | 187 | test("SearchWorkerLoader should pass the specified :tokenizePattern to the WorkerClass", () => { 188 | const search = new SearchWorkerLoader({ 189 | tokenizePattern: /[^a-z0-9]+/, 190 | WorkerClass: StubWorker 191 | }); 192 | expect(search._worker._setTokenizePatternQueue.length).toBe(1); 193 | expect(search._worker._setTokenizePatternQueue[0].tokenizePattern).toEqual( 194 | /[^a-z0-9]+/ 195 | ); 196 | }); 197 | 198 | test("SearchWorkerLoader should pass the specified :caseSensitive bit to the WorkerClass", () => { 199 | const search = new SearchWorkerLoader({ 200 | caseSensitive: true, 201 | WorkerClass: StubWorker 202 | }); 203 | expect(search._worker._setCaseSensitiveQueue.length).toBe(1); 204 | expect(search._worker._setCaseSensitiveQueue[0].caseSensitive).toBe(true); 205 | }); 206 | 207 | test("SearchWorkerLoader should pass the specified :matchAnyToken bit to the WorkerClass", () => { 208 | const search = new SearchWorkerLoader({ 209 | matchAnyToken: true, 210 | WorkerClass: StubWorker 211 | }); 212 | expect(search._worker._setMatchAnyTokenQueue.length).toBe(1); 213 | expect(search._worker._setMatchAnyTokenQueue[0].matchAnyToken).toBe(true); 214 | }); 215 | -------------------------------------------------------------------------------- /src/worker/Worker.js: -------------------------------------------------------------------------------- 1 | /** @flow */ 2 | 3 | import { SearchUtility } from "../util"; 4 | 5 | /** 6 | * Search entry point to web worker. 7 | * Builds search index and performs searches on separate thread from the ui. 8 | */ 9 | 10 | const searchUtility = new SearchUtility(); 11 | 12 | self.addEventListener( 13 | "message", 14 | event => { 15 | const { data } = event; 16 | const { method } = data; 17 | 18 | switch (method) { 19 | case "indexDocument": 20 | const { uid, text } = data; 21 | 22 | searchUtility.indexDocument(uid, text); 23 | break; 24 | case "search": 25 | const { callbackId, query } = data; 26 | 27 | const results = searchUtility.search(query); 28 | 29 | self.postMessage({ callbackId, results }); 30 | break; 31 | case "setCaseSensitive": 32 | const { caseSensitive } = data; 33 | 34 | searchUtility.setCaseSensitive(caseSensitive); 35 | break; 36 | case "setIndexMode": 37 | const { indexMode } = data; 38 | 39 | searchUtility.setIndexMode(indexMode); 40 | break; 41 | case "setMatchAnyToken": 42 | const { matchAnyToken } = data; 43 | 44 | searchUtility.setMatchAnyToken(matchAnyToken); 45 | break; 46 | case "setTokenizePattern": 47 | const { tokenizePattern } = data; 48 | 49 | searchUtility.setTokenizePattern(tokenizePattern); 50 | break; 51 | } 52 | }, 53 | false 54 | ); 55 | -------------------------------------------------------------------------------- /src/worker/index.js: -------------------------------------------------------------------------------- 1 | /** @flow */ 2 | 3 | import SearchWorkerLoader from "./SearchWorkerLoader"; 4 | 5 | export default SearchWorkerLoader; 6 | -------------------------------------------------------------------------------- /webpack.config.dev.js: -------------------------------------------------------------------------------- 1 | const HtmlWebpackPlugin = require("html-webpack-plugin"); 2 | const path = require("path"); 3 | const webpack = require("webpack"); 4 | 5 | module.exports = { 6 | devtool: "eval", 7 | entry: ["babel/polyfill", "./website/index.js"], 8 | output: { 9 | path: "build", 10 | filename: "/static/[name].js" 11 | }, 12 | plugins: [ 13 | new HtmlWebpackPlugin({ 14 | filename: "index.html", 15 | inject: true, 16 | template: "./website/index.html" 17 | }), 18 | new webpack.NoErrorsPlugin() 19 | ], 20 | module: { 21 | loaders: [ 22 | { 23 | test: /\.js$/, 24 | loader: "babel", 25 | exclude: path.join(__dirname, "node_modules") 26 | }, 27 | { 28 | test: /\.css$/, 29 | loaders: ["style", "css?modules&importLoaders=1", "cssnext"], 30 | exclude: path.join(__dirname, "node_modules") 31 | } 32 | ] 33 | }, 34 | devServer: { 35 | contentBase: "build", 36 | port: 3456 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /webpack.config.dist.js: -------------------------------------------------------------------------------- 1 | const path = require("path"); 2 | const webpack = require("webpack"); 3 | 4 | module.exports = { 5 | entry: { 6 | "js-worker-search": "./src/index.js" 7 | }, 8 | output: { 9 | path: "dist", 10 | filename: "[name].js", 11 | libraryTarget: "commonjs2", 12 | library: "redux-search" 13 | }, 14 | plugins: [ 15 | new webpack.SourceMapDevToolPlugin({ 16 | exclude: /.*worker\.js$/, 17 | filename: "[name].js.map" 18 | }) 19 | ], 20 | module: { 21 | loaders: [ 22 | { 23 | test: /\.js$/, 24 | loader: "babel", 25 | include: path.join(__dirname, "src") 26 | } 27 | ] 28 | } 29 | }; 30 | --------------------------------------------------------------------------------