├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .npmignore ├── .prettierignore ├── .travis.yml ├── Makefile ├── README.md ├── addon.cc ├── annoyindexwrapper.cc ├── annoyindexwrapper.h ├── annoylib.h ├── binding.gyp ├── index.js ├── kissrandom.h ├── package-lock.json ├── package.json ├── tests ├── basic-config.js ├── basictests.js ├── data │ └── git-stub ├── smalltest-manhattan.js ├── smalltest.js ├── testworker.js ├── very-big-config.js └── worker-thread-test.js └── tools ├── build-word-index-db.js ├── test-word-index-db.js └── w2v-to-json.c /.eslintignore: -------------------------------------------------------------------------------- 1 | index.js 2 | node_modules 3 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | commonjs: true, 5 | es6: true 6 | }, 7 | extends: 'eslint:recommended', 8 | rules: { 9 | indent: ['error', 2], 10 | 'linebreak-style': ['error', 'unix'], 11 | quotes: ['error', 'single'], 12 | semi: ['error', 'always'], 13 | 'no-console': 'off' 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | tests/data/*.bin 4 | tests/data/*.annoy 5 | tests/data/*.json 6 | tools/w2v-to-json 7 | build 8 | *.swp 9 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | tests/data/*.bin 4 | tests/data/*.annoy* 5 | tests/data/*.json 6 | tests/data/*.db 7 | tools/w2v-to-json 8 | build 9 | .npmignore 10 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jimkang/annoy-node/fc9e9d490b3d04b2831b91b49887686b43775abb/.prettierignore -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: true 2 | language: node_js 3 | node_js: 4 | - "8" 5 | - "10" 6 | - "12" 7 | env: 8 | - CXX=g++-4.8 9 | 10 | addons: 11 | apt: 12 | sources: 13 | - ubuntu-toolchain-r-test 14 | packages: 15 | - g++-4.8 16 | 17 | install: 18 | - sudo ln -s /usr/bin/g++-4.8 /usr/local/bin/g++ 19 | - npm install 20 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TOOLS_CC = g++ 2 | TOOLS_CFLAGS = -lm -pthread -Ofast -march=native -Wall -funroll-loops -Wno-unused-result -std=c++11 3 | 4 | TESTDATADIR = tests/data 5 | 6 | build-wrapper: 7 | node-gyp rebuild 8 | 9 | test: tests/data/text8-vector.json 10 | node tests/smalltest.js 11 | node tests/worker-thread-test.js 12 | node tests/smalltest-manhattan.js 13 | node tests/basictests.js basic-config.js 14 | 15 | big-test: tests/data/GoogleNews-vectors-negative300.json 16 | node tests/basictests.js very-big-config.js 17 | 18 | tests/data/text8-vector.bin: 19 | wget https://github.com/jimkang/nearest-neighbor-test-data/raw/master/text8-vector.bin -O $(TESTDATADIR)/text8-vector.bin 20 | 21 | # If this fails, you end up with a 0-byte json file. 22 | # When you run this target again, make will see that the 23 | # file exists, then quit. To get it to run, delete 24 | # tests/data/GoogleNews-vectors-negative300.json 25 | # See README about where to get the bin file used in this 26 | # target. 27 | tests/data/GoogleNews-vectors-negative300.json: tools/w2v-to-json 28 | ./tools/w2v-to-json "$(TESTDATADIR)/GoogleNews-vectors-negative300.bin" tests/data/GoogleNews-vectors-negative300.json 29 | 30 | tools/w2v-to-json: 31 | $(TOOLS_CC) tools/w2v-to-json.c -o tools/w2v-to-json $(TOOLS_CFLAGS) 32 | 33 | tests/data/text8-vector.json: tests/data/text8-vector.bin tools/w2v-to-json 34 | ./tools/w2v-to-json tests/data/text8-vector.bin tests/data/text8-vector.json 35 | 36 | test-word-index-db: 37 | node tools/test-word-index-db.js tests/data/GoogleNews-vectors-negative300.json tests/data/word-index-google-news.db tests/data/very-big-test.annoy 38 | 39 | pushall: 40 | git push origin master && npm publish 41 | 42 | prettier: 43 | prettier --single-quote --write "**/*.js" 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | annoy-node 2 | ================== 3 | 4 | [![Build Status](https://travis-ci.org/jimkang/annoy-node.svg?branch=master)](https://travis-ci.org/jimkang/annoy-node) 5 | 6 | Node bindings for [Annoy](https://github.com/spotify/annoy), an efficient Approximate Nearest Neighbors implementation written in C++. 7 | 8 | Version 4.0.0 requires Node 14 or Node 16 and does not yet work on 18. 9 | 10 | Status: Tests pass, including one that loads 3 million vectors, but API coverage is not complete. Run on OS X and Linux with Node 8, 10, and 12. Not tried on Windows yet. Support for Node 6.3 and 4.6 ended at version 2.0.1 of this package. 11 | 12 | All of the [Python API](https://github.com/spotify/annoy#full-python-api) methods are implemented. The names are camel cased, JavaScript-style. 13 | 14 | - `addItem` 15 | - `build` 16 | - `save` 17 | - `load` 18 | - `unload` 19 | - `getItem` 20 | - `getNNsByVector` 21 | - `getNNsByItem` 22 | - `getNItems` 23 | - `getDistance` 24 | 25 | There are a few minor differences in behavior: 26 | 27 | - If you set the "include distances" param (the fourth param) when calling `getNNsByVector` and `getNNsByItem`, rather than returning a 2D array containing the neighbors and distances, it will return an object with the properties `neighbors` and `distances`, each of which is an array. 28 | - `get_item_vector` in with the Python API is just called `getItem` here. 29 | 30 | Installation 31 | ------------ 32 | 33 | On Linux, if you don't already have Python 2.7 and g++ 4.8, you need to install them. Here's how you do it on Ubuntu: 34 | 35 | (sudo) apt-get install python2.7 36 | (sudo) apt-get install g++-4.8 37 | npm config set python /path/to/executable/python2.7 38 | 39 | Then, symlink g++ somewhere it can be found: 40 | 41 | ln -s /usr/bin/g++-4.8 /usr/local/bin/g++ 42 | 43 | On OS X, they should already be there. 44 | 45 | Then: 46 | 47 | npm install annoy 48 | 49 | Usage 50 | ----- 51 | 52 | var Annoy = require('annoy'); 53 | var annoyIndex1 = new Annoy(10, 'Angular'); 54 | 55 | annoyIndex1.addItem(0, [-5.0, -4.5, -3.2, -2.8, -2.1, -1.5, -0.34, 0, 3.7, 6]); 56 | annoyIndex1.addItem(1, [5.0, 4.5, 3.2, 2.8, 2.1, 1.5, 0.34, 0, -3.7, -6]); 57 | annoyIndex1.addItem(2, [0, 0, 0, 0, 0, -1, -1, -0.2, 0.1, 0.8]); 58 | annoyIndex1.build(); 59 | annoyIndex1.save(annoyPath); 60 | 61 | read(); 62 | 63 | function read() { 64 | var annoyIndex2 = new Annoy(10, 'Angular'); 65 | 66 | if (annoyIndex2.load(annoyPath)) { 67 | var v1 = annoyIndex2.getItem(0); 68 | var v2 = annoyIndex2.getItem(1); 69 | console.log('Gotten vectors:', v1, v2); 70 | 71 | for (var i = 0; i < v1.length; ++i) { 72 | sum.push(v1[i] + v2[i]); 73 | } 74 | 75 | var neighbors = annoyIndex2.getNNsByVector(sum, 10, -1, false); 76 | console.log('Nearest neighbors to sum', neighbors); 77 | 78 | var neighborsAndDistances = annoyIndex2.getNNsByVector(sum, 10, -1, true); 79 | console.log('Nearest neighbors to sum with distances', neighborsAndDistances); 80 | } 81 | } 82 | 83 | Development 84 | ------------ 85 | 86 | npm install -g node-gyp 87 | node-gyp rebuild 88 | 89 | Run `eslint .` and `make prettier` before committing. 90 | 91 | Tests 92 | ----- 93 | 94 | Run tests with `make test`. 95 | 96 | You can also run tests individually: 97 | 98 | - This is a short baseline test: `node tests/smalltest.js` 99 | - This is a test that uses 70K 200-dimension vectors: `node tests/basictests.js` 100 | 101 | There is also a `big-test` target that is not a dependency of the `test` target. It loads about 3 million 300-dimension vectors. It takes about six minutes to run on good-for-2016 hardware. Before you can run it, you need to download [GoogleNews-vectors-negative300.bin](https://drive.google.com/file/d/0B7XkCwpI5KDYNlNUTTlSS21pQmM/edit?usp=sharing) to `tests/data`. 102 | 103 | Then, you can run `make tests/data/GoogleNews-vectors-negative300.json`, which takes a while, and gets the test data ready for the big test. (See comment about running that in the Makefile.) Then, `make big-test`. 104 | 105 | Contributors 106 | ------------ 107 | 108 | Thanks to: 109 | 110 | - [mbuszka](https://github.com/mbuszka) for [updating the wrapper to the latest Annoy (with Manhattan distance) and updating the random number generator](https://github.com/jimkang/annoy-node/pull/4). 111 | - [aaaton](https://github.com/aaaton) for [updating the example code so that it works](https://github.com/jimkang/annoy-node/pull/1). 112 | - [kornesh](https://github.com/kornesh) for [updating annoylib.h](https://github.com/jimkang/annoy-node/pull/10) to match the [Annoy of 2020-07-19](https://github.com/spotify/annoy/commit/7f2562add33eeb217dcdc755520c201aefc1b021). 113 | - [Benjaminrivard](https://github.com/Benjaminrivard) for [updating the wrapper for Node 14](https://github.com/jimkang/annoy-node/pull/13) and testing the thread support. 114 | - [S4N0I](https://github.com/S4N0I) for adding context-awareness so that it can be used in threads. 115 | 116 | License 117 | ------- 118 | 119 | The MIT License (MIT) 120 | 121 | Copyright (c) 2016 Jim Kang 122 | 123 | Permission is hereby granted, free of charge, to any person obtaining a copy 124 | of this software and associated documentation files (the "Software"), to deal 125 | in the Software without restriction, including without limitation the rights 126 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 127 | copies of the Software, and to permit persons to whom the Software is 128 | furnished to do so, subject to the following conditions: 129 | 130 | The above copyright notice and this permission notice shall be included in 131 | all copies or substantial portions of the Software. 132 | 133 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 134 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 135 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 136 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 137 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 138 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 139 | THE SOFTWARE. 140 | -------------------------------------------------------------------------------- /addon.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include "annoyindexwrapper.h" 3 | 4 | void InitAll(v8::Local exports) { 5 | AnnoyIndexWrapper::Init(exports); 6 | } 7 | 8 | NODE_MODULE_INIT() { 9 | InitAll(exports); 10 | } 11 | -------------------------------------------------------------------------------- /annoyindexwrapper.cc: -------------------------------------------------------------------------------- 1 | #include "annoyindexwrapper.h" 2 | #include "kissrandom.h" 3 | #include 4 | 5 | using namespace v8; 6 | using namespace Nan; 7 | 8 | Nan::Persistent AnnoyIndexWrapper::constructor; 9 | 10 | AnnoyIndexWrapper::AnnoyIndexWrapper(int dimensions, const char *metricString) : 11 | annoyDimensions(dimensions) { 12 | 13 | if (strcmp(metricString, "Angular") == 0) { 14 | annoyIndex = new AnnoyIndex(dimensions); 15 | } 16 | else if (strcmp(metricString, "Manhattan") == 0) { 17 | annoyIndex = new AnnoyIndex(dimensions); 18 | } 19 | else { 20 | annoyIndex = new AnnoyIndex(dimensions); 21 | } 22 | } 23 | 24 | AnnoyIndexWrapper::~AnnoyIndexWrapper() { 25 | delete annoyIndex; 26 | } 27 | 28 | void AnnoyIndexWrapper::Init(v8::Local exports) { 29 | Nan::HandleScope scope; 30 | 31 | // Prepare constructor template 32 | v8::Local tpl = Nan::New(New); 33 | tpl->SetClassName(Nan::New("Annoy").ToLocalChecked()); 34 | tpl->InstanceTemplate()->SetInternalFieldCount(2); 35 | 36 | // Prototype 37 | // Nan::SetPrototypeMethod(tpl, "value", GetValue); 38 | // Nan::SetPrototypeMethod(tpl, "plusOne", PlusOne); 39 | // Nan::SetPrototypeMethod(tpl, "multiply", Multiply); 40 | Nan::SetPrototypeMethod(tpl, "addItem", AddItem); 41 | Nan::SetPrototypeMethod(tpl, "build", Build); 42 | Nan::SetPrototypeMethod(tpl, "save", Save); 43 | Nan::SetPrototypeMethod(tpl, "load", Load); 44 | Nan::SetPrototypeMethod(tpl, "unload", Unload); 45 | Nan::SetPrototypeMethod(tpl, "getItem", GetItem); 46 | Nan::SetPrototypeMethod(tpl, "getNNsByVector", GetNNSByVector); 47 | Nan::SetPrototypeMethod(tpl, "getNNsByItem", GetNNSByItem); 48 | Nan::SetPrototypeMethod(tpl, "getNItems", GetNItems); 49 | Nan::SetPrototypeMethod(tpl, "getDistance", GetDistance); 50 | 51 | constructor.Reset(Nan::GetFunction(tpl).ToLocalChecked()); 52 | Nan::Set(exports, Nan::New("Annoy").ToLocalChecked(), Nan::GetFunction(tpl).ToLocalChecked()); 53 | } 54 | 55 | void AnnoyIndexWrapper::New(const Nan::FunctionCallbackInfo& info) { 56 | 57 | if (info.IsConstructCall()) { 58 | // Invoked as constructor: `new AnnoyIndexWrapper(...)` 59 | double dimensions = info[0]->IsUndefined() ? 0 : info[0]->NumberValue(Nan::GetCurrentContext()).FromJust(); 60 | Local metricString; 61 | 62 | if (!info[1]->IsUndefined()) { 63 | Nan::MaybeLocal s = Nan::To(info[1]); 64 | if (!s.IsEmpty()) { 65 | metricString = s.ToLocalChecked(); 66 | } 67 | } 68 | 69 | AnnoyIndexWrapper* obj = new AnnoyIndexWrapper( 70 | (int)dimensions, *Nan::Utf8String(metricString) 71 | ); 72 | obj->Wrap(info.This()); 73 | info.GetReturnValue().Set(info.This()); 74 | } 75 | } 76 | 77 | void AnnoyIndexWrapper::AddItem(const Nan::FunctionCallbackInfo& info) { 78 | // Get out object. 79 | AnnoyIndexWrapper* obj = ObjectWrap::Unwrap(info.Holder()); 80 | // Get out index. 81 | int index = info[0]->IsUndefined() ? 1 : info[0]->NumberValue(Nan::GetCurrentContext()).FromJust(); 82 | // Get out array. 83 | float vec[obj->getDimensions()]; 84 | if (getFloatArrayParam(info, 1, vec)) { 85 | obj->annoyIndex->add_item(index, vec); 86 | } 87 | } 88 | 89 | void AnnoyIndexWrapper::Build(const Nan::FunctionCallbackInfo& info) { 90 | // Get out object. 91 | AnnoyIndexWrapper* obj = ObjectWrap::Unwrap(info.Holder()); 92 | // Get out numberOfTrees. 93 | int numberOfTrees = info[0]->IsUndefined() ? 1 : info[0]->NumberValue(Nan::GetCurrentContext()).FromJust(); 94 | // printf("%s\n", "Calling build"); 95 | obj->annoyIndex->build(numberOfTrees); 96 | } 97 | 98 | void AnnoyIndexWrapper::Save(const Nan::FunctionCallbackInfo& info) { 99 | bool result = false; 100 | 101 | // Get out object. 102 | AnnoyIndexWrapper* obj = ObjectWrap::Unwrap(info.Holder()); 103 | // Get out file path. 104 | if (!info[0]->IsUndefined()) { 105 | Nan::MaybeLocal maybeStr = Nan::To(info[0]); 106 | v8::Local str; 107 | if (maybeStr.ToLocal(&str)) { 108 | result = obj->annoyIndex->save(*Nan::Utf8String(str)); 109 | } 110 | } 111 | info.GetReturnValue().Set(Nan::New(result)); 112 | } 113 | 114 | void AnnoyIndexWrapper::Load(const Nan::FunctionCallbackInfo& info) { 115 | bool result = false; 116 | // Get out object. 117 | AnnoyIndexWrapper* obj = ObjectWrap::Unwrap(info.Holder()); 118 | // Get out file path. 119 | if (!info[0]->IsUndefined()) { 120 | Nan::MaybeLocal maybeStr = Nan::To(info[0]); 121 | v8::Local str; 122 | if (maybeStr.ToLocal(&str)) { 123 | result = obj->annoyIndex->load(*Nan::Utf8String(str)); 124 | } 125 | } 126 | info.GetReturnValue().Set(Nan::New(result)); 127 | } 128 | 129 | void AnnoyIndexWrapper::Unload(const Nan::FunctionCallbackInfo& info) { 130 | AnnoyIndexWrapper* obj = ObjectWrap::Unwrap(info.Holder()); 131 | obj->annoyIndex->unload(); 132 | } 133 | 134 | void AnnoyIndexWrapper::GetItem(const Nan::FunctionCallbackInfo& info) { 135 | Nan::HandleScope scope; 136 | 137 | // Get out object. 138 | AnnoyIndexWrapper* obj = ObjectWrap::Unwrap(info.Holder()); 139 | 140 | // Get out index. 141 | int index = info[0]->IsUndefined() ? 1 : info[0]->NumberValue(Nan::GetCurrentContext()).FromJust(); 142 | 143 | // Get the vector. 144 | int length = obj->getDimensions(); 145 | float vec[length]; 146 | obj->annoyIndex->get_item(index, vec); 147 | 148 | // Allocate the return array. 149 | Local results = Nan::New(length); 150 | for (int i = 0; i < length; ++i) { 151 | // printf("Adding to array: %f\n", vec[i]); 152 | Nan::Set(results, i, Nan::New(vec[i])); 153 | } 154 | 155 | info.GetReturnValue().Set(results); 156 | } 157 | 158 | void AnnoyIndexWrapper::GetDistance(const Nan::FunctionCallbackInfo& info) { 159 | // Get out object. 160 | AnnoyIndexWrapper* obj = ObjectWrap::Unwrap(info.Holder()); 161 | 162 | // Get out indexes. 163 | int indexA = info[0]->IsUndefined() ? 0 : info[0]->NumberValue(Nan::GetCurrentContext()).FromJust(); 164 | int indexB = info[1]->IsUndefined() ? 0 : info[1]->NumberValue(Nan::GetCurrentContext()).FromJust(); 165 | 166 | // Return the distances. 167 | info.GetReturnValue().Set(obj->annoyIndex->get_distance(indexA, indexB)); 168 | } 169 | 170 | void AnnoyIndexWrapper::GetNNSByVector(const Nan::FunctionCallbackInfo& info) { 171 | Nan::HandleScope scope; 172 | 173 | int numberOfNeighbors, searchK; 174 | bool includeDistances; 175 | getSupplementaryGetNNsParams(info, numberOfNeighbors, searchK, includeDistances); 176 | 177 | // Get out object. 178 | AnnoyIndexWrapper* obj = ObjectWrap::Unwrap(info.Holder()); 179 | // Get out array. 180 | float vec[obj->getDimensions()]; 181 | if (!getFloatArrayParam(info, 0, vec)) { 182 | return; 183 | } 184 | 185 | std::vector nnIndexes; 186 | std::vector distances; 187 | std::vector *distancesPtr = nullptr; 188 | 189 | if (includeDistances) { 190 | distancesPtr = &distances; 191 | } 192 | 193 | // Make the call. 194 | obj->annoyIndex->get_nns_by_vector( 195 | vec, numberOfNeighbors, searchK, &nnIndexes, distancesPtr 196 | ); 197 | 198 | setNNReturnValues(numberOfNeighbors, includeDistances, nnIndexes, distances, info); 199 | } 200 | 201 | void AnnoyIndexWrapper::GetNNSByItem(const Nan::FunctionCallbackInfo& info) { 202 | Nan::HandleScope scope; 203 | 204 | // Get out object. 205 | AnnoyIndexWrapper* obj = ObjectWrap::Unwrap(info.Holder()); 206 | 207 | if (info[0]->IsUndefined()) { 208 | return; 209 | } 210 | 211 | // Get out params. 212 | int index = info[0]->NumberValue(Nan::GetCurrentContext()).FromJust(); 213 | int numberOfNeighbors, searchK; 214 | bool includeDistances; 215 | getSupplementaryGetNNsParams(info, numberOfNeighbors, searchK, includeDistances); 216 | 217 | std::vector nnIndexes; 218 | std::vector distances; 219 | std::vector *distancesPtr = nullptr; 220 | 221 | if (includeDistances) { 222 | distancesPtr = &distances; 223 | } 224 | 225 | // Make the call. 226 | obj->annoyIndex->get_nns_by_item( 227 | index, numberOfNeighbors, searchK, &nnIndexes, distancesPtr 228 | ); 229 | 230 | setNNReturnValues(numberOfNeighbors, includeDistances, nnIndexes, distances, info); 231 | } 232 | 233 | void AnnoyIndexWrapper::getSupplementaryGetNNsParams( 234 | const Nan::FunctionCallbackInfo& info, 235 | int& numberOfNeighbors, int& searchK, bool& includeDistances) { 236 | 237 | v8::Local context = Nan::GetCurrentContext(); 238 | 239 | // Get out number of neighbors. 240 | numberOfNeighbors = info[1]->IsUndefined() ? 1 : info[1]->NumberValue(context).FromJust(); 241 | 242 | // Get out searchK. 243 | searchK = info[2]->IsUndefined() ? -1 : info[2]->NumberValue(context).FromJust(); 244 | 245 | // Get out include distances flag. 246 | includeDistances = info[3]->IsUndefined() ? false : Nan::To(info[3]).FromJust(); 247 | } 248 | 249 | void AnnoyIndexWrapper::setNNReturnValues( 250 | int numberOfNeighbors, bool includeDistances, 251 | const std::vector& nnIndexes, const std::vector& distances, 252 | const Nan::FunctionCallbackInfo& info) { 253 | 254 | // Allocate the neighbors array. 255 | Local jsNNIndexes = Nan::New(numberOfNeighbors); 256 | for (int i = 0; i < numberOfNeighbors; ++i) { 257 | // printf("Adding to neighbors array: %d\n", nnIndexes[i]); 258 | Nan::Set(jsNNIndexes, i, Nan::New(nnIndexes[i])); 259 | } 260 | 261 | Local jsResultObject; 262 | Local jsDistancesArray; 263 | 264 | if (includeDistances) { 265 | // Allocate the distances array. 266 | jsDistancesArray = Nan::New(numberOfNeighbors); 267 | for (int i = 0; i < numberOfNeighbors; ++i) { 268 | // printf("Adding to distances array: %f\n", distances[i]); 269 | Nan::Set(jsDistancesArray, i, Nan::New(distances[i])); 270 | } 271 | 272 | jsResultObject = Nan::New(); 273 | Nan::Set(jsResultObject, Nan::New("neighbors").ToLocalChecked(), jsNNIndexes); 274 | Nan::Set(jsResultObject, Nan::New("distances").ToLocalChecked(), jsDistancesArray); 275 | } 276 | else { 277 | jsResultObject = jsNNIndexes.As(); 278 | } 279 | 280 | info.GetReturnValue().Set(jsResultObject); 281 | } 282 | 283 | void AnnoyIndexWrapper::GetNItems(const Nan::FunctionCallbackInfo& info) { 284 | // Get out object. 285 | AnnoyIndexWrapper* obj = ObjectWrap::Unwrap(info.Holder()); 286 | Local count = Nan::New(obj->annoyIndex->get_n_items()); 287 | info.GetReturnValue().Set(count); 288 | } 289 | 290 | // Returns true if it was able to get items out of the array. false, if not. 291 | bool AnnoyIndexWrapper::getFloatArrayParam( 292 | const Nan::FunctionCallbackInfo& info, int paramIndex, float *vec) { 293 | 294 | bool succeeded = false; 295 | 296 | Local val; 297 | if (info[paramIndex]->IsArray()) { 298 | // TODO: Make sure it really is OK to use Local instead of Handle here. 299 | Local jsArray = Local::Cast(info[paramIndex]); 300 | Local val; 301 | for (unsigned int i = 0; i < jsArray->Length(); i++) { 302 | val = Nan::Get(jsArray, i).ToLocalChecked(); 303 | // printf("Adding item to array: %f\n", (float)val->NumberValue(Nan::GetCurrentContext()).FromJust()); 304 | vec[i] = (float)val->NumberValue(Nan::GetCurrentContext()).FromJust(); 305 | } 306 | succeeded = true; 307 | } 308 | 309 | return succeeded; 310 | } 311 | 312 | int AnnoyIndexWrapper::getDimensions() { 313 | return annoyDimensions; 314 | } 315 | 316 | -------------------------------------------------------------------------------- /annoyindexwrapper.h: -------------------------------------------------------------------------------- 1 | #ifndef ANNOYINDEXWRAPPER_H 2 | #define ANNOYINDEXWRAPPER_H 3 | 4 | #include 5 | #include "annoylib.h" 6 | 7 | class AnnoyIndexWrapper : public Nan::ObjectWrap { 8 | public: 9 | static void Init(v8::Local exports); 10 | int getDimensions(); 11 | AnnoyIndexInterface *annoyIndex; 12 | 13 | private: 14 | explicit AnnoyIndexWrapper(int dimensions, const char *metricString); 15 | virtual ~AnnoyIndexWrapper(); 16 | 17 | static void New(const Nan::FunctionCallbackInfo& info); 18 | static void AddItem(const Nan::FunctionCallbackInfo& info); 19 | static void Build(const Nan::FunctionCallbackInfo& info); 20 | static void Save(const Nan::FunctionCallbackInfo& info); 21 | static void Load(const Nan::FunctionCallbackInfo& info); 22 | static void Unload(const Nan::FunctionCallbackInfo& info); 23 | static void GetItem(const Nan::FunctionCallbackInfo& info); 24 | static void GetNNSByVector(const Nan::FunctionCallbackInfo& info); 25 | static void GetNNSByItem(const Nan::FunctionCallbackInfo& info); 26 | static void GetNItems(const Nan::FunctionCallbackInfo& info); 27 | static void GetDistance(const Nan::FunctionCallbackInfo& info); 28 | 29 | static Nan::Persistent constructor; 30 | static bool getFloatArrayParam(const Nan::FunctionCallbackInfo& info, 31 | int paramIndex, float *vec); 32 | static void setNNReturnValues( 33 | int numberOfNeighbors, bool includeDistances, 34 | const std::vector& nnIndexes, const std::vector& distances, 35 | const Nan::FunctionCallbackInfo& info); 36 | static void getSupplementaryGetNNsParams( 37 | const Nan::FunctionCallbackInfo& info, 38 | int& numberOfNeighbors, int& searchK, bool& includeDistances); 39 | 40 | int annoyDimensions; 41 | }; 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /annoylib.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2013 Spotify AB 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | // use this file except in compliance with the License. You may obtain a copy of 5 | // the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | // License for the specific language governing permissions and limitations under 13 | // the License. 14 | 15 | 16 | #ifndef ANNOYLIB_H 17 | #define ANNOYLIB_H 18 | 19 | #include 20 | #include 21 | #ifndef _MSC_VER 22 | #include 23 | #endif 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #if defined(_MSC_VER) && _MSC_VER == 1500 31 | typedef unsigned char uint8_t; 32 | typedef signed __int32 int32_t; 33 | typedef unsigned __int64 uint64_t; 34 | typedef signed __int64 int64_t; 35 | #else 36 | #include 37 | #endif 38 | 39 | #if defined(_MSC_VER) || defined(__MINGW32__) 40 | // a bit hacky, but override some definitions to support 64 bit 41 | #define off_t int64_t 42 | #define lseek_getsize(fd) _lseeki64(fd, 0, SEEK_END) 43 | #ifndef NOMINMAX 44 | #define NOMINMAX 45 | #endif 46 | #include "mman.h" 47 | #include 48 | #else 49 | #include 50 | #define lseek_getsize(fd) lseek(fd, 0, SEEK_END) 51 | #endif 52 | 53 | #include 54 | #include 55 | #include 56 | #include 57 | #include 58 | #include 59 | #include 60 | 61 | #ifdef _MSC_VER 62 | // Needed for Visual Studio to disable runtime checks for mempcy 63 | #pragma runtime_checks("s", off) 64 | #endif 65 | 66 | // This allows others to supply their own logger / error printer without 67 | // requiring Annoy to import their headers. See RcppAnnoy for a use case. 68 | #ifndef __ERROR_PRINTER_OVERRIDE__ 69 | #define showUpdate(...) { fprintf(stderr, __VA_ARGS__ ); } 70 | #else 71 | #define showUpdate(...) { __ERROR_PRINTER_OVERRIDE__( __VA_ARGS__ ); } 72 | #endif 73 | 74 | // Portable alloc definition, cf Writing R Extensions, Section 1.6.4 75 | #ifdef __GNUC__ 76 | // Includes GCC, clang and Intel compilers 77 | # undef alloca 78 | # define alloca(x) __builtin_alloca((x)) 79 | #elif defined(__sun) || defined(_AIX) 80 | // this is necessary (and sufficient) for Solaris 10 and AIX 6: 81 | # include 82 | #endif 83 | 84 | inline void set_error_from_errno(char **error, const char* msg) { 85 | showUpdate("%s: %s (%d)\n", msg, strerror(errno), errno); 86 | if (error) { 87 | *error = (char *)malloc(256); // TODO: win doesn't support snprintf 88 | sprintf(*error, "%s: %s (%d)", msg, strerror(errno), errno); 89 | } 90 | } 91 | 92 | inline void set_error_from_string(char **error, const char* msg) { 93 | showUpdate("%s\n", msg); 94 | if (error) { 95 | *error = (char *)malloc(strlen(msg) + 1); 96 | strcpy(*error, msg); 97 | } 98 | } 99 | 100 | // We let the v array in the Node struct take whatever space is needed, so this is a mostly insignificant number. 101 | // Compilers need *some* size defined for the v array, and some memory checking tools will flag for buffer overruns if this is set too low. 102 | #define V_ARRAY_SIZE 65536 103 | 104 | #ifndef _MSC_VER 105 | #define popcount __builtin_popcountll 106 | #else // See #293, #358 107 | #define isnan(x) _isnan(x) 108 | #define popcount cole_popcount 109 | #endif 110 | 111 | #if !defined(NO_MANUAL_VECTORIZATION) && defined(__GNUC__) && (__GNUC__ >6) && defined(__AVX512F__) // See #402 112 | #define USE_AVX512 113 | #elif !defined(NO_MANUAL_VECTORIZATION) && defined(__AVX__) && defined (__SSE__) && defined(__SSE2__) && defined(__SSE3__) 114 | #define USE_AVX 115 | #else 116 | #endif 117 | 118 | #if defined(USE_AVX) || defined(USE_AVX512) 119 | #if defined(_MSC_VER) 120 | #include 121 | #elif defined(__GNUC__) 122 | #include 123 | #endif 124 | #endif 125 | 126 | 127 | using std::vector; 128 | using std::pair; 129 | using std::numeric_limits; 130 | using std::make_pair; 131 | 132 | inline void* remap_memory(void* _ptr, int _fd, size_t old_size, size_t new_size) { 133 | #ifdef __linux__ 134 | _ptr = mremap(_ptr, old_size, new_size, MREMAP_MAYMOVE); 135 | #else 136 | munmap(_ptr, old_size); 137 | #ifdef MAP_POPULATE 138 | _ptr = mmap(_ptr, new_size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, _fd, 0); 139 | #else 140 | _ptr = mmap(_ptr, new_size, PROT_READ | PROT_WRITE, MAP_SHARED, _fd, 0); 141 | #endif 142 | #endif 143 | return _ptr; 144 | } 145 | 146 | namespace { 147 | 148 | template 149 | inline Node* get_node_ptr(const void* _nodes, const size_t _s, const S i) { 150 | return (Node*)((uint8_t *)_nodes + (_s * i)); 151 | } 152 | 153 | template 154 | inline T dot(const T* x, const T* y, int f) { 155 | T s = 0; 156 | for (int z = 0; z < f; z++) { 157 | s += (*x) * (*y); 158 | x++; 159 | y++; 160 | } 161 | return s; 162 | } 163 | 164 | template 165 | inline T manhattan_distance(const T* x, const T* y, int f) { 166 | T d = 0.0; 167 | for (int i = 0; i < f; i++) 168 | d += fabs(x[i] - y[i]); 169 | return d; 170 | } 171 | 172 | template 173 | inline T euclidean_distance(const T* x, const T* y, int f) { 174 | // Don't use dot-product: avoid catastrophic cancellation in #314. 175 | T d = 0.0; 176 | for (int i = 0; i < f; ++i) { 177 | const T tmp=*x - *y; 178 | d += tmp * tmp; 179 | ++x; 180 | ++y; 181 | } 182 | return d; 183 | } 184 | 185 | #ifdef USE_AVX 186 | // Horizontal single sum of 256bit vector. 187 | inline float hsum256_ps_avx(__m256 v) { 188 | const __m128 x128 = _mm_add_ps(_mm256_extractf128_ps(v, 1), _mm256_castps256_ps128(v)); 189 | const __m128 x64 = _mm_add_ps(x128, _mm_movehl_ps(x128, x128)); 190 | const __m128 x32 = _mm_add_ss(x64, _mm_shuffle_ps(x64, x64, 0x55)); 191 | return _mm_cvtss_f32(x32); 192 | } 193 | 194 | template<> 195 | inline float dot(const float* x, const float *y, int f) { 196 | float result = 0; 197 | if (f > 7) { 198 | __m256 d = _mm256_setzero_ps(); 199 | for (; f > 7; f -= 8) { 200 | d = _mm256_add_ps(d, _mm256_mul_ps(_mm256_loadu_ps(x), _mm256_loadu_ps(y))); 201 | x += 8; 202 | y += 8; 203 | } 204 | // Sum all floats in dot register. 205 | result += hsum256_ps_avx(d); 206 | } 207 | // Don't forget the remaining values. 208 | for (; f > 0; f--) { 209 | result += *x * *y; 210 | x++; 211 | y++; 212 | } 213 | return result; 214 | } 215 | 216 | template<> 217 | inline float manhattan_distance(const float* x, const float* y, int f) { 218 | float result = 0; 219 | int i = f; 220 | if (f > 7) { 221 | __m256 manhattan = _mm256_setzero_ps(); 222 | __m256 minus_zero = _mm256_set1_ps(-0.0f); 223 | for (; i > 7; i -= 8) { 224 | const __m256 x_minus_y = _mm256_sub_ps(_mm256_loadu_ps(x), _mm256_loadu_ps(y)); 225 | const __m256 distance = _mm256_andnot_ps(minus_zero, x_minus_y); // Absolute value of x_minus_y (forces sign bit to zero) 226 | manhattan = _mm256_add_ps(manhattan, distance); 227 | x += 8; 228 | y += 8; 229 | } 230 | // Sum all floats in manhattan register. 231 | result = hsum256_ps_avx(manhattan); 232 | } 233 | // Don't forget the remaining values. 234 | for (; i > 0; i--) { 235 | result += fabsf(*x - *y); 236 | x++; 237 | y++; 238 | } 239 | return result; 240 | } 241 | 242 | template<> 243 | inline float euclidean_distance(const float* x, const float* y, int f) { 244 | float result=0; 245 | if (f > 7) { 246 | __m256 d = _mm256_setzero_ps(); 247 | for (; f > 7; f -= 8) { 248 | const __m256 diff = _mm256_sub_ps(_mm256_loadu_ps(x), _mm256_loadu_ps(y)); 249 | d = _mm256_add_ps(d, _mm256_mul_ps(diff, diff)); // no support for fmadd in AVX... 250 | x += 8; 251 | y += 8; 252 | } 253 | // Sum all floats in dot register. 254 | result = hsum256_ps_avx(d); 255 | } 256 | // Don't forget the remaining values. 257 | for (; f > 0; f--) { 258 | float tmp = *x - *y; 259 | result += tmp * tmp; 260 | x++; 261 | y++; 262 | } 263 | return result; 264 | } 265 | 266 | #endif 267 | 268 | #ifdef USE_AVX512 269 | template<> 270 | inline float dot(const float* x, const float *y, int f) { 271 | float result = 0; 272 | if (f > 15) { 273 | __m512 d = _mm512_setzero_ps(); 274 | for (; f > 15; f -= 16) { 275 | //AVX512F includes FMA 276 | d = _mm512_fmadd_ps(_mm512_loadu_ps(x), _mm512_loadu_ps(y), d); 277 | x += 16; 278 | y += 16; 279 | } 280 | // Sum all floats in dot register. 281 | result += _mm512_reduce_add_ps(d); 282 | } 283 | // Don't forget the remaining values. 284 | for (; f > 0; f--) { 285 | result += *x * *y; 286 | x++; 287 | y++; 288 | } 289 | return result; 290 | } 291 | 292 | template<> 293 | inline float manhattan_distance(const float* x, const float* y, int f) { 294 | float result = 0; 295 | int i = f; 296 | if (f > 15) { 297 | __m512 manhattan = _mm512_setzero_ps(); 298 | for (; i > 15; i -= 16) { 299 | const __m512 x_minus_y = _mm512_sub_ps(_mm512_loadu_ps(x), _mm512_loadu_ps(y)); 300 | manhattan = _mm512_add_ps(manhattan, _mm512_abs_ps(x_minus_y)); 301 | x += 16; 302 | y += 16; 303 | } 304 | // Sum all floats in manhattan register. 305 | result = _mm512_reduce_add_ps(manhattan); 306 | } 307 | // Don't forget the remaining values. 308 | for (; i > 0; i--) { 309 | result += fabsf(*x - *y); 310 | x++; 311 | y++; 312 | } 313 | return result; 314 | } 315 | 316 | template<> 317 | inline float euclidean_distance(const float* x, const float* y, int f) { 318 | float result=0; 319 | if (f > 15) { 320 | __m512 d = _mm512_setzero_ps(); 321 | for (; f > 15; f -= 16) { 322 | const __m512 diff = _mm512_sub_ps(_mm512_loadu_ps(x), _mm512_loadu_ps(y)); 323 | d = _mm512_fmadd_ps(diff, diff, d); 324 | x += 16; 325 | y += 16; 326 | } 327 | // Sum all floats in dot register. 328 | result = _mm512_reduce_add_ps(d); 329 | } 330 | // Don't forget the remaining values. 331 | for (; f > 0; f--) { 332 | float tmp = *x - *y; 333 | result += tmp * tmp; 334 | x++; 335 | y++; 336 | } 337 | return result; 338 | } 339 | 340 | #endif 341 | 342 | 343 | template 344 | inline T get_norm(T* v, int f) { 345 | return sqrt(dot(v, v, f)); 346 | } 347 | 348 | template 349 | inline void two_means(const vector& nodes, int f, Random& random, bool cosine, Node* p, Node* q) { 350 | /* 351 | This algorithm is a huge heuristic. Empirically it works really well, but I 352 | can't motivate it well. The basic idea is to keep two centroids and assign 353 | points to either one of them. We weight each centroid by the number of points 354 | assigned to it, so to balance it. 355 | */ 356 | static int iteration_steps = 200; 357 | size_t count = nodes.size(); 358 | 359 | size_t i = random.index(count); 360 | size_t j = random.index(count-1); 361 | j += (j >= i); // ensure that i != j 362 | 363 | Distance::template copy_node(p, nodes[i], f); 364 | Distance::template copy_node(q, nodes[j], f); 365 | 366 | if (cosine) { Distance::template normalize(p, f); Distance::template normalize(q, f); } 367 | Distance::init_node(p, f); 368 | Distance::init_node(q, f); 369 | 370 | int ic = 1, jc = 1; 371 | for (int l = 0; l < iteration_steps; l++) { 372 | size_t k = random.index(count); 373 | T di = ic * Distance::distance(p, nodes[k], f), 374 | dj = jc * Distance::distance(q, nodes[k], f); 375 | T norm = cosine ? get_norm(nodes[k]->v, f) : 1; 376 | if (!(norm > T(0))) { 377 | continue; 378 | } 379 | if (di < dj) { 380 | for (int z = 0; z < f; z++) 381 | p->v[z] = (p->v[z] * ic + nodes[k]->v[z] / norm) / (ic + 1); 382 | Distance::init_node(p, f); 383 | ic++; 384 | } else if (dj < di) { 385 | for (int z = 0; z < f; z++) 386 | q->v[z] = (q->v[z] * jc + nodes[k]->v[z] / norm) / (jc + 1); 387 | Distance::init_node(q, f); 388 | jc++; 389 | } 390 | } 391 | } 392 | } // namespace 393 | 394 | struct Base { 395 | template 396 | static inline void preprocess(void* nodes, size_t _s, const S node_count, const int f) { 397 | // Override this in specific metric structs below if you need to do any pre-processing 398 | // on the entire set of nodes passed into this index. 399 | } 400 | 401 | template 402 | static inline void zero_value(Node* dest) { 403 | // Initialize any fields that require sane defaults within this node. 404 | } 405 | 406 | template 407 | static inline void copy_node(Node* dest, const Node* source, const int f) { 408 | memcpy(dest->v, source->v, f * sizeof(T)); 409 | } 410 | 411 | template 412 | static inline void normalize(Node* node, int f) { 413 | T norm = get_norm(node->v, f); 414 | if (norm > 0) { 415 | for (int z = 0; z < f; z++) 416 | node->v[z] /= norm; 417 | } 418 | } 419 | }; 420 | 421 | struct Angular : Base { 422 | template 423 | struct Node { 424 | /* 425 | * We store a binary tree where each node has two things 426 | * - A vector associated with it 427 | * - Two children 428 | * All nodes occupy the same amount of memory 429 | * All nodes with n_descendants == 1 are leaf nodes. 430 | * A memory optimization is that for nodes with 2 <= n_descendants <= K, 431 | * we skip the vector. Instead we store a list of all descendants. K is 432 | * determined by the number of items that fits in the space of the vector. 433 | * For nodes with n_descendants == 1 the vector is a data point. 434 | * For nodes with n_descendants > K the vector is the normal of the split plane. 435 | * Note that we can't really do sizeof(node) because we cheat and allocate 436 | * more memory to be able to fit the vector outside 437 | */ 438 | S n_descendants; 439 | union { 440 | S children[2]; // Will possibly store more than 2 441 | T norm; 442 | }; 443 | T v[V_ARRAY_SIZE]; 444 | }; 445 | template 446 | static inline T distance(const Node* x, const Node* y, int f) { 447 | // want to calculate (a/|a| - b/|b|)^2 448 | // = a^2 / a^2 + b^2 / b^2 - 2ab/|a||b| 449 | // = 2 - 2cos 450 | T pp = x->norm ? x->norm : dot(x->v, x->v, f); // For backwards compatibility reasons, we need to fall back and compute the norm here 451 | T qq = y->norm ? y->norm : dot(y->v, y->v, f); 452 | T pq = dot(x->v, y->v, f); 453 | T ppqq = pp * qq; 454 | if (ppqq > 0) return 2.0 - 2.0 * pq / sqrt(ppqq); 455 | else return 2.0; // cos is 0 456 | } 457 | template 458 | static inline T margin(const Node* n, const T* y, int f) { 459 | return dot(n->v, y, f); 460 | } 461 | template 462 | static inline bool side(const Node* n, const T* y, int f, Random& random) { 463 | T dot = margin(n, y, f); 464 | if (dot != 0) 465 | return (dot > 0); 466 | else 467 | return (bool)random.flip(); 468 | } 469 | template 470 | static inline void create_split(const vector*>& nodes, int f, size_t s, Random& random, Node* n) { 471 | Node* p = (Node*)alloca(s); 472 | Node* q = (Node*)alloca(s); 473 | two_means >(nodes, f, random, true, p, q); 474 | for (int z = 0; z < f; z++) 475 | n->v[z] = p->v[z] - q->v[z]; 476 | Base::normalize >(n, f); 477 | } 478 | template 479 | static inline T normalized_distance(T distance) { 480 | // Used when requesting distances from Python layer 481 | // Turns out sometimes the squared distance is -0.0 482 | // so we have to make sure it's a positive number. 483 | return sqrt(std::max(distance, T(0))); 484 | } 485 | template 486 | static inline T pq_distance(T distance, T margin, int child_nr) { 487 | if (child_nr == 0) 488 | margin = -margin; 489 | return std::min(distance, margin); 490 | } 491 | template 492 | static inline T pq_initial_value() { 493 | return numeric_limits::infinity(); 494 | } 495 | template 496 | static inline void init_node(Node* n, int f) { 497 | n->norm = dot(n->v, n->v, f); 498 | } 499 | static const char* name() { 500 | return "angular"; 501 | } 502 | }; 503 | 504 | 505 | struct DotProduct : Angular { 506 | template 507 | struct Node { 508 | /* 509 | * This is an extension of the Angular node with an extra attribute for the scaled norm. 510 | */ 511 | S n_descendants; 512 | S children[2]; // Will possibly store more than 2 513 | T dot_factor; 514 | T v[V_ARRAY_SIZE]; 515 | }; 516 | 517 | static const char* name() { 518 | return "dot"; 519 | } 520 | template 521 | static inline T distance(const Node* x, const Node* y, int f) { 522 | return -dot(x->v, y->v, f); 523 | } 524 | 525 | template 526 | static inline void zero_value(Node* dest) { 527 | dest->dot_factor = 0; 528 | } 529 | 530 | template 531 | static inline void init_node(Node* n, int f) { 532 | } 533 | 534 | template 535 | static inline void copy_node(Node* dest, const Node* source, const int f) { 536 | memcpy(dest->v, source->v, f * sizeof(T)); 537 | dest->dot_factor = source->dot_factor; 538 | } 539 | 540 | template 541 | static inline void create_split(const vector*>& nodes, int f, size_t s, Random& random, Node* n) { 542 | Node* p = (Node*)alloca(s); 543 | Node* q = (Node*)alloca(s); 544 | DotProduct::zero_value(p); 545 | DotProduct::zero_value(q); 546 | two_means >(nodes, f, random, true, p, q); 547 | for (int z = 0; z < f; z++) 548 | n->v[z] = p->v[z] - q->v[z]; 549 | n->dot_factor = p->dot_factor - q->dot_factor; 550 | DotProduct::normalize >(n, f); 551 | } 552 | 553 | template 554 | static inline void normalize(Node* node, int f) { 555 | T norm = sqrt(dot(node->v, node->v, f) + pow(node->dot_factor, 2)); 556 | if (norm > 0) { 557 | for (int z = 0; z < f; z++) 558 | node->v[z] /= norm; 559 | node->dot_factor /= norm; 560 | } 561 | } 562 | 563 | template 564 | static inline T margin(const Node* n, const T* y, int f) { 565 | return dot(n->v, y, f) + (n->dot_factor * n->dot_factor); 566 | } 567 | 568 | template 569 | static inline bool side(const Node* n, const T* y, int f, Random& random) { 570 | T dot = margin(n, y, f); 571 | if (dot != 0) 572 | return (dot > 0); 573 | else 574 | return (bool)random.flip(); 575 | } 576 | 577 | template 578 | static inline T normalized_distance(T distance) { 579 | return -distance; 580 | } 581 | 582 | template 583 | static inline void preprocess(void* nodes, size_t _s, const S node_count, const int f) { 584 | // This uses a method from Microsoft Research for transforming inner product spaces to cosine/angular-compatible spaces. 585 | // (Bachrach et al., 2014, see https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/XboxInnerProduct.pdf) 586 | 587 | // Step one: compute the norm of each vector and store that in its extra dimension (f-1) 588 | for (S i = 0; i < node_count; i++) { 589 | Node* node = get_node_ptr(nodes, _s, i); 590 | T norm = sqrt(dot(node->v, node->v, f)); 591 | if (isnan(norm)) norm = 0; 592 | node->dot_factor = norm; 593 | } 594 | 595 | // Step two: find the maximum norm 596 | T max_norm = 0; 597 | for (S i = 0; i < node_count; i++) { 598 | Node* node = get_node_ptr(nodes, _s, i); 599 | if (node->dot_factor > max_norm) { 600 | max_norm = node->dot_factor; 601 | } 602 | } 603 | 604 | // Step three: set each vector's extra dimension to sqrt(max_norm^2 - norm^2) 605 | for (S i = 0; i < node_count; i++) { 606 | Node* node = get_node_ptr(nodes, _s, i); 607 | T node_norm = node->dot_factor; 608 | 609 | T dot_factor = sqrt(pow(max_norm, static_cast(2.0)) - pow(node_norm, static_cast(2.0))); 610 | if (isnan(dot_factor)) dot_factor = 0; 611 | 612 | node->dot_factor = dot_factor; 613 | } 614 | } 615 | }; 616 | 617 | struct Hamming : Base { 618 | template 619 | struct Node { 620 | S n_descendants; 621 | S children[2]; 622 | T v[V_ARRAY_SIZE]; 623 | }; 624 | 625 | static const size_t max_iterations = 20; 626 | 627 | template 628 | static inline T pq_distance(T distance, T margin, int child_nr) { 629 | return distance - (margin != (unsigned int) child_nr); 630 | } 631 | 632 | template 633 | static inline T pq_initial_value() { 634 | return numeric_limits::max(); 635 | } 636 | template 637 | static inline int cole_popcount(T v) { 638 | // Note: Only used with MSVC 9, which lacks intrinsics and fails to 639 | // calculate std::bitset::count for v > 32bit. Uses the generalized 640 | // approach by Eric Cole. 641 | // See https://graphics.stanford.edu/~seander/bithacks.html#CountBitsSet64 642 | v = v - ((v >> 1) & (T)~(T)0/3); 643 | v = (v & (T)~(T)0/15*3) + ((v >> 2) & (T)~(T)0/15*3); 644 | v = (v + (v >> 4)) & (T)~(T)0/255*15; 645 | return (T)(v * ((T)~(T)0/255)) >> (sizeof(T) - 1) * 8; 646 | } 647 | template 648 | static inline T distance(const Node* x, const Node* y, int f) { 649 | size_t dist = 0; 650 | for (int i = 0; i < f; i++) { 651 | dist += popcount(x->v[i] ^ y->v[i]); 652 | } 653 | return dist; 654 | } 655 | template 656 | static inline bool margin(const Node* n, const T* y, int f) { 657 | static const size_t n_bits = sizeof(T) * 8; 658 | T chunk = n->v[0] / n_bits; 659 | return (y[chunk] & (static_cast(1) << (n_bits - 1 - (n->v[0] % n_bits)))) != 0; 660 | } 661 | template 662 | static inline bool side(const Node* n, const T* y, int f, Random& random) { 663 | return margin(n, y, f); 664 | } 665 | template 666 | static inline void create_split(const vector*>& nodes, int f, size_t s, Random& random, Node* n) { 667 | size_t cur_size = 0; 668 | size_t i = 0; 669 | int dim = f * 8 * sizeof(T); 670 | for (; i < max_iterations; i++) { 671 | // choose random position to split at 672 | n->v[0] = random.index(dim); 673 | cur_size = 0; 674 | for (typename vector*>::const_iterator it = nodes.begin(); it != nodes.end(); ++it) { 675 | if (margin(n, (*it)->v, f)) { 676 | cur_size++; 677 | } 678 | } 679 | if (cur_size > 0 && cur_size < nodes.size()) { 680 | break; 681 | } 682 | } 683 | // brute-force search for splitting coordinate 684 | if (i == max_iterations) { 685 | int j = 0; 686 | for (; j < dim; j++) { 687 | n->v[0] = j; 688 | cur_size = 0; 689 | for (typename vector*>::const_iterator it = nodes.begin(); it != nodes.end(); ++it) { 690 | if (margin(n, (*it)->v, f)) { 691 | cur_size++; 692 | } 693 | } 694 | if (cur_size > 0 && cur_size < nodes.size()) { 695 | break; 696 | } 697 | } 698 | } 699 | } 700 | template 701 | static inline T normalized_distance(T distance) { 702 | return distance; 703 | } 704 | template 705 | static inline void init_node(Node* n, int f) { 706 | } 707 | static const char* name() { 708 | return "hamming"; 709 | } 710 | }; 711 | 712 | 713 | struct Minkowski : Base { 714 | template 715 | struct Node { 716 | S n_descendants; 717 | T a; // need an extra constant term to determine the offset of the plane 718 | S children[2]; 719 | T v[V_ARRAY_SIZE]; 720 | }; 721 | template 722 | static inline T margin(const Node* n, const T* y, int f) { 723 | return n->a + dot(n->v, y, f); 724 | } 725 | template 726 | static inline bool side(const Node* n, const T* y, int f, Random& random) { 727 | T dot = margin(n, y, f); 728 | if (dot != 0) 729 | return (dot > 0); 730 | else 731 | return (bool)random.flip(); 732 | } 733 | template 734 | static inline T pq_distance(T distance, T margin, int child_nr) { 735 | if (child_nr == 0) 736 | margin = -margin; 737 | return std::min(distance, margin); 738 | } 739 | template 740 | static inline T pq_initial_value() { 741 | return numeric_limits::infinity(); 742 | } 743 | }; 744 | 745 | 746 | struct Euclidean : Minkowski { 747 | template 748 | static inline T distance(const Node* x, const Node* y, int f) { 749 | return euclidean_distance(x->v, y->v, f); 750 | } 751 | template 752 | static inline void create_split(const vector*>& nodes, int f, size_t s, Random& random, Node* n) { 753 | Node* p = (Node*)alloca(s); 754 | Node* q = (Node*)alloca(s); 755 | two_means >(nodes, f, random, false, p, q); 756 | 757 | for (int z = 0; z < f; z++) 758 | n->v[z] = p->v[z] - q->v[z]; 759 | Base::normalize >(n, f); 760 | n->a = 0.0; 761 | for (int z = 0; z < f; z++) 762 | n->a += -n->v[z] * (p->v[z] + q->v[z]) / 2; 763 | } 764 | template 765 | static inline T normalized_distance(T distance) { 766 | return sqrt(std::max(distance, T(0))); 767 | } 768 | template 769 | static inline void init_node(Node* n, int f) { 770 | } 771 | static const char* name() { 772 | return "euclidean"; 773 | } 774 | 775 | }; 776 | 777 | struct Manhattan : Minkowski { 778 | template 779 | static inline T distance(const Node* x, const Node* y, int f) { 780 | return manhattan_distance(x->v, y->v, f); 781 | } 782 | template 783 | static inline void create_split(const vector*>& nodes, int f, size_t s, Random& random, Node* n) { 784 | Node* p = (Node*)alloca(s); 785 | Node* q = (Node*)alloca(s); 786 | two_means >(nodes, f, random, false, p, q); 787 | 788 | for (int z = 0; z < f; z++) 789 | n->v[z] = p->v[z] - q->v[z]; 790 | Base::normalize >(n, f); 791 | n->a = 0.0; 792 | for (int z = 0; z < f; z++) 793 | n->a += -n->v[z] * (p->v[z] + q->v[z]) / 2; 794 | } 795 | template 796 | static inline T normalized_distance(T distance) { 797 | return std::max(distance, T(0)); 798 | } 799 | template 800 | static inline void init_node(Node* n, int f) { 801 | } 802 | static const char* name() { 803 | return "manhattan"; 804 | } 805 | }; 806 | 807 | template 808 | class AnnoyIndexInterface { 809 | public: 810 | // Note that the methods with an **error argument will allocate memory and write the pointer to that string if error is non-NULL 811 | virtual ~AnnoyIndexInterface() {}; 812 | virtual bool add_item(S item, const T* w, char** error=NULL) = 0; 813 | virtual bool build(int q, char** error=NULL) = 0; 814 | virtual bool unbuild(char** error=NULL) = 0; 815 | virtual bool save(const char* filename, bool prefault=false, char** error=NULL) = 0; 816 | virtual void unload() = 0; 817 | virtual bool load(const char* filename, bool prefault=false, char** error=NULL) = 0; 818 | virtual T get_distance(S i, S j) const = 0; 819 | virtual void get_nns_by_item(S item, size_t n, int search_k, vector* result, vector* distances) const = 0; 820 | virtual void get_nns_by_vector(const T* w, size_t n, int search_k, vector* result, vector* distances) const = 0; 821 | virtual S get_n_items() const = 0; 822 | virtual S get_n_trees() const = 0; 823 | virtual void verbose(bool v) = 0; 824 | virtual void get_item(S item, T* v) const = 0; 825 | virtual void set_seed(int q) = 0; 826 | virtual bool on_disk_build(const char* filename, char** error=NULL) = 0; 827 | }; 828 | 829 | template 830 | class AnnoyIndex : public AnnoyIndexInterface { 831 | /* 832 | * We use random projection to build a forest of binary trees of all items. 833 | * Basically just split the hyperspace into two sides by a hyperplane, 834 | * then recursively split each of those subtrees etc. 835 | * We create a tree like this q times. The default q is determined automatically 836 | * in such a way that we at most use 2x as much memory as the vectors take. 837 | */ 838 | public: 839 | typedef Distance D; 840 | typedef typename D::template Node Node; 841 | 842 | protected: 843 | const int _f; 844 | size_t _s; 845 | S _n_items; 846 | Random _random; 847 | void* _nodes; // Could either be mmapped, or point to a memory buffer that we reallocate 848 | S _n_nodes; 849 | S _nodes_size; 850 | vector _roots; 851 | S _K; 852 | bool _loaded; 853 | bool _verbose; 854 | int _fd; 855 | bool _on_disk; 856 | bool _built; 857 | public: 858 | 859 | AnnoyIndex(int f) : _f(f), _random() { 860 | _s = offsetof(Node, v) + _f * sizeof(T); // Size of each node 861 | _verbose = false; 862 | _built = false; 863 | _K = (S) (((size_t) (_s - offsetof(Node, children))) / sizeof(S)); // Max number of descendants to fit into node 864 | reinitialize(); // Reset everything 865 | } 866 | ~AnnoyIndex() { 867 | unload(); 868 | } 869 | 870 | int get_f() const { 871 | return _f; 872 | } 873 | 874 | bool add_item(S item, const T* w, char** error=NULL) { 875 | return add_item_impl(item, w, error); 876 | } 877 | 878 | template 879 | bool add_item_impl(S item, const W& w, char** error=NULL) { 880 | if (_loaded) { 881 | set_error_from_string(error, "You can't add an item to a loaded index"); 882 | return false; 883 | } 884 | _allocate_size(item + 1); 885 | Node* n = _get(item); 886 | 887 | D::zero_value(n); 888 | 889 | n->children[0] = 0; 890 | n->children[1] = 0; 891 | n->n_descendants = 1; 892 | 893 | for (int z = 0; z < _f; z++) 894 | n->v[z] = w[z]; 895 | 896 | D::init_node(n, _f); 897 | 898 | if (item >= _n_items) 899 | _n_items = item + 1; 900 | 901 | return true; 902 | } 903 | 904 | bool on_disk_build(const char* file, char** error=NULL) { 905 | _on_disk = true; 906 | _fd = open(file, O_RDWR | O_CREAT | O_TRUNC, (int) 0600); 907 | if (_fd == -1) { 908 | set_error_from_errno(error, "Unable to open"); 909 | _fd = 0; 910 | return false; 911 | } 912 | _nodes_size = 1; 913 | if (ftruncate(_fd, _s * _nodes_size) == -1) { 914 | set_error_from_errno(error, "Unable to truncate"); 915 | return false; 916 | } 917 | #ifdef MAP_POPULATE 918 | _nodes = (Node*) mmap(0, _s * _nodes_size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_POPULATE, _fd, 0); 919 | #else 920 | _nodes = (Node*) mmap(0, _s * _nodes_size, PROT_READ | PROT_WRITE, MAP_SHARED, _fd, 0); 921 | #endif 922 | return true; 923 | } 924 | 925 | bool build(int q, char** error=NULL) { 926 | if (_loaded) { 927 | set_error_from_string(error, "You can't build a loaded index"); 928 | return false; 929 | } 930 | 931 | if (_built) { 932 | set_error_from_string(error, "You can't build a built index"); 933 | return false; 934 | } 935 | 936 | D::template preprocess(_nodes, _s, _n_items, _f); 937 | 938 | _n_nodes = _n_items; 939 | while (1) { 940 | if (q == -1 && _n_nodes >= _n_items * 2) 941 | break; 942 | if (q != -1 && _roots.size() >= (size_t)q) 943 | break; 944 | if (_verbose) showUpdate("pass %zd...\n", _roots.size()); 945 | 946 | vector indices; 947 | for (S i = 0; i < _n_items; i++) { 948 | if (_get(i)->n_descendants >= 1) // Issue #223 949 | indices.push_back(i); 950 | } 951 | 952 | _roots.push_back(_make_tree(indices, true)); 953 | } 954 | 955 | // Also, copy the roots into the last segment of the array 956 | // This way we can load them faster without reading the whole file 957 | _allocate_size(_n_nodes + (S)_roots.size()); 958 | for (size_t i = 0; i < _roots.size(); i++) 959 | memcpy(_get(_n_nodes + (S)i), _get(_roots[i]), _s); 960 | _n_nodes += _roots.size(); 961 | 962 | if (_verbose) showUpdate("has %d nodes\n", _n_nodes); 963 | 964 | if (_on_disk) { 965 | _nodes = remap_memory(_nodes, _fd, _s * _nodes_size, _s * _n_nodes); 966 | if (ftruncate(_fd, _s * _n_nodes)) { 967 | // TODO: this probably creates an index in a corrupt state... not sure what to do 968 | set_error_from_errno(error, "Unable to truncate"); 969 | return false; 970 | } 971 | _nodes_size = _n_nodes; 972 | } 973 | _built = true; 974 | return true; 975 | } 976 | 977 | bool unbuild(char** error=NULL) { 978 | if (_loaded) { 979 | set_error_from_string(error, "You can't unbuild a loaded index"); 980 | return false; 981 | } 982 | 983 | _roots.clear(); 984 | _n_nodes = _n_items; 985 | _built = false; 986 | 987 | return true; 988 | } 989 | 990 | bool save(const char* filename, bool prefault=false, char** error=NULL) { 991 | if (!_built) { 992 | set_error_from_string(error, "You can't save an index that hasn't been built"); 993 | return false; 994 | } 995 | if (_on_disk) { 996 | return true; 997 | } else { 998 | // Delete file if it already exists (See issue #335) 999 | unlink(filename); 1000 | 1001 | FILE *f = fopen(filename, "wb"); 1002 | if (f == NULL) { 1003 | set_error_from_errno(error, "Unable to open"); 1004 | return false; 1005 | } 1006 | 1007 | if (fwrite(_nodes, _s, _n_nodes, f) != (size_t) _n_nodes) { 1008 | set_error_from_errno(error, "Unable to write"); 1009 | return false; 1010 | } 1011 | 1012 | if (fclose(f) == EOF) { 1013 | set_error_from_errno(error, "Unable to close"); 1014 | return false; 1015 | } 1016 | 1017 | unload(); 1018 | return load(filename, prefault, error); 1019 | } 1020 | } 1021 | 1022 | void reinitialize() { 1023 | _fd = 0; 1024 | _nodes = NULL; 1025 | _loaded = false; 1026 | _n_items = 0; 1027 | _n_nodes = 0; 1028 | _nodes_size = 0; 1029 | _on_disk = false; 1030 | _roots.clear(); 1031 | } 1032 | 1033 | void unload() { 1034 | if (_on_disk && _fd) { 1035 | close(_fd); 1036 | munmap(_nodes, _s * _nodes_size); 1037 | } else { 1038 | if (_fd) { 1039 | // we have mmapped data 1040 | close(_fd); 1041 | munmap(_nodes, _n_nodes * _s); 1042 | } else if (_nodes) { 1043 | // We have heap allocated data 1044 | free(_nodes); 1045 | } 1046 | } 1047 | reinitialize(); 1048 | if (_verbose) showUpdate("unloaded\n"); 1049 | } 1050 | 1051 | bool load(const char* filename, bool prefault=false, char** error=NULL) { 1052 | _fd = open(filename, O_RDONLY, (int)0400); 1053 | if (_fd == -1) { 1054 | set_error_from_errno(error, "Unable to open"); 1055 | _fd = 0; 1056 | return false; 1057 | } 1058 | off_t size = lseek_getsize(_fd); 1059 | if (size == -1) { 1060 | set_error_from_errno(error, "Unable to get size"); 1061 | return false; 1062 | } else if (size == 0) { 1063 | set_error_from_errno(error, "Size of file is zero"); 1064 | return false; 1065 | } else if (size % _s) { 1066 | // Something is fishy with this index! 1067 | set_error_from_errno(error, "Index size is not a multiple of vector size. Ensure you are opening using the same metric you used to create the index."); 1068 | return false; 1069 | } 1070 | 1071 | int flags = MAP_SHARED; 1072 | if (prefault) { 1073 | #ifdef MAP_POPULATE 1074 | flags |= MAP_POPULATE; 1075 | #else 1076 | showUpdate("prefault is set to true, but MAP_POPULATE is not defined on this platform"); 1077 | #endif 1078 | } 1079 | _nodes = (Node*)mmap(0, size, PROT_READ, flags, _fd, 0); 1080 | _n_nodes = (S)(size / _s); 1081 | 1082 | // Find the roots by scanning the end of the file and taking the nodes with most descendants 1083 | _roots.clear(); 1084 | S m = -1; 1085 | for (S i = _n_nodes - 1; i >= 0; i--) { 1086 | S k = _get(i)->n_descendants; 1087 | if (m == -1 || k == m) { 1088 | _roots.push_back(i); 1089 | m = k; 1090 | } else { 1091 | break; 1092 | } 1093 | } 1094 | // hacky fix: since the last root precedes the copy of all roots, delete it 1095 | if (_roots.size() > 1 && _get(_roots.front())->children[0] == _get(_roots.back())->children[0]) 1096 | _roots.pop_back(); 1097 | _loaded = true; 1098 | _built = true; 1099 | _n_items = m; 1100 | if (_verbose) showUpdate("found %lu roots with degree %d\n", _roots.size(), m); 1101 | return true; 1102 | } 1103 | 1104 | T get_distance(S i, S j) const { 1105 | return D::normalized_distance(D::distance(_get(i), _get(j), _f)); 1106 | } 1107 | 1108 | void get_nns_by_item(S item, size_t n, int search_k, vector* result, vector* distances) const { 1109 | // TODO: handle OOB 1110 | const Node* m = _get(item); 1111 | _get_all_nns(m->v, n, search_k, result, distances); 1112 | } 1113 | 1114 | void get_nns_by_vector(const T* w, size_t n, int search_k, vector* result, vector* distances) const { 1115 | _get_all_nns(w, n, search_k, result, distances); 1116 | } 1117 | 1118 | S get_n_items() const { 1119 | return _n_items; 1120 | } 1121 | 1122 | S get_n_trees() const { 1123 | return (S)_roots.size(); 1124 | } 1125 | 1126 | void verbose(bool v) { 1127 | _verbose = v; 1128 | } 1129 | 1130 | void get_item(S item, T* v) const { 1131 | // TODO: handle OOB 1132 | Node* m = _get(item); 1133 | memcpy(v, m->v, (_f) * sizeof(T)); 1134 | } 1135 | 1136 | void set_seed(int seed) { 1137 | _random.set_seed(seed); 1138 | } 1139 | 1140 | protected: 1141 | void _allocate_size(S n) { 1142 | if (n > _nodes_size) { 1143 | const double reallocation_factor = 1.3; 1144 | S new_nodes_size = std::max(n, (S) ((_nodes_size + 1) * reallocation_factor)); 1145 | void *old = _nodes; 1146 | 1147 | if (_on_disk) { 1148 | int rc = ftruncate(_fd, _s * new_nodes_size); 1149 | if (_verbose && rc) showUpdate("File truncation error\n"); 1150 | _nodes = remap_memory(_nodes, _fd, _s * _nodes_size, _s * new_nodes_size); 1151 | } else { 1152 | _nodes = realloc(_nodes, _s * new_nodes_size); 1153 | memset((char *) _nodes + (_nodes_size * _s) / sizeof(char), 0, (new_nodes_size - _nodes_size) * _s); 1154 | } 1155 | 1156 | _nodes_size = new_nodes_size; 1157 | if (_verbose) showUpdate("Reallocating to %d nodes: old_address=%p, new_address=%p\n", new_nodes_size, old, _nodes); 1158 | } 1159 | } 1160 | 1161 | inline Node* _get(const S i) const { 1162 | return get_node_ptr(_nodes, _s, i); 1163 | } 1164 | 1165 | S _make_tree(const vector& indices, bool is_root) { 1166 | // The basic rule is that if we have <= _K items, then it's a leaf node, otherwise it's a split node. 1167 | // There's some regrettable complications caused by the problem that root nodes have to be "special": 1168 | // 1. We identify root nodes by the arguable logic that _n_items == n->n_descendants, regardless of how many descendants they actually have 1169 | // 2. Root nodes with only 1 child need to be a "dummy" parent 1170 | // 3. Due to the _n_items "hack", we need to be careful with the cases where _n_items <= _K or _n_items > _K 1171 | if (indices.size() == 1 && !is_root) 1172 | return indices[0]; 1173 | 1174 | if (indices.size() <= (size_t)_K && (!is_root || (size_t)_n_items <= (size_t)_K || indices.size() == 1)) { 1175 | _allocate_size(_n_nodes + 1); 1176 | S item = _n_nodes++; 1177 | Node* m = _get(item); 1178 | m->n_descendants = is_root ? _n_items : (S)indices.size(); 1179 | 1180 | // Using std::copy instead of a loop seems to resolve issues #3 and #13, 1181 | // probably because gcc 4.8 goes overboard with optimizations. 1182 | // Using memcpy instead of std::copy for MSVC compatibility. #235 1183 | // Only copy when necessary to avoid crash in MSVC 9. #293 1184 | if (!indices.empty()) 1185 | memcpy(m->children, &indices[0], indices.size() * sizeof(S)); 1186 | return item; 1187 | } 1188 | 1189 | vector children; 1190 | for (size_t i = 0; i < indices.size(); i++) { 1191 | S j = indices[i]; 1192 | Node* n = _get(j); 1193 | if (n) 1194 | children.push_back(n); 1195 | } 1196 | 1197 | vector children_indices[2]; 1198 | Node* m = (Node*)alloca(_s); 1199 | D::create_split(children, _f, _s, _random, m); 1200 | 1201 | for (size_t i = 0; i < indices.size(); i++) { 1202 | S j = indices[i]; 1203 | Node* n = _get(j); 1204 | if (n) { 1205 | bool side = D::side(m, n->v, _f, _random); 1206 | children_indices[side].push_back(j); 1207 | } else { 1208 | showUpdate("No node for index %d?\n", j); 1209 | } 1210 | } 1211 | 1212 | // If we didn't find a hyperplane, just randomize sides as a last option 1213 | while (children_indices[0].size() == 0 || children_indices[1].size() == 0) { 1214 | if (_verbose) 1215 | showUpdate("\tNo hyperplane found (left has %ld children, right has %ld children)\n", 1216 | children_indices[0].size(), children_indices[1].size()); 1217 | if (_verbose && indices.size() > 100000) 1218 | showUpdate("Failed splitting %lu items\n", indices.size()); 1219 | 1220 | children_indices[0].clear(); 1221 | children_indices[1].clear(); 1222 | 1223 | // Set the vector to 0.0 1224 | for (int z = 0; z < _f; z++) 1225 | m->v[z] = 0; 1226 | 1227 | for (size_t i = 0; i < indices.size(); i++) { 1228 | S j = indices[i]; 1229 | // Just randomize... 1230 | children_indices[_random.flip()].push_back(j); 1231 | } 1232 | } 1233 | 1234 | int flip = (children_indices[0].size() > children_indices[1].size()); 1235 | 1236 | m->n_descendants = is_root ? _n_items : (S)indices.size(); 1237 | for (int side = 0; side < 2; side++) { 1238 | // run _make_tree for the smallest child first (for cache locality) 1239 | m->children[side^flip] = _make_tree(children_indices[side^flip], false); 1240 | } 1241 | 1242 | _allocate_size(_n_nodes + 1); 1243 | S item = _n_nodes++; 1244 | memcpy(_get(item), m, _s); 1245 | 1246 | return item; 1247 | } 1248 | 1249 | void _get_all_nns(const T* v, size_t n, int search_k, vector* result, vector* distances) const { 1250 | Node* v_node = (Node *)alloca(_s); 1251 | D::template zero_value(v_node); 1252 | memcpy(v_node->v, v, sizeof(T) * _f); 1253 | D::init_node(v_node, _f); 1254 | 1255 | std::priority_queue > q; 1256 | 1257 | if (search_k == -1) { 1258 | search_k = n * _roots.size(); 1259 | } 1260 | 1261 | for (size_t i = 0; i < _roots.size(); i++) { 1262 | q.push(make_pair(Distance::template pq_initial_value(), _roots[i])); 1263 | } 1264 | 1265 | std::vector nns; 1266 | while (nns.size() < (size_t)search_k && !q.empty()) { 1267 | const pair& top = q.top(); 1268 | T d = top.first; 1269 | S i = top.second; 1270 | Node* nd = _get(i); 1271 | q.pop(); 1272 | if (nd->n_descendants == 1 && i < _n_items) { 1273 | nns.push_back(i); 1274 | } else if (nd->n_descendants <= _K) { 1275 | const S* dst = nd->children; 1276 | nns.insert(nns.end(), dst, &dst[nd->n_descendants]); 1277 | } else { 1278 | T margin = D::margin(nd, v, _f); 1279 | q.push(make_pair(D::pq_distance(d, margin, 1), static_cast(nd->children[1]))); 1280 | q.push(make_pair(D::pq_distance(d, margin, 0), static_cast(nd->children[0]))); 1281 | } 1282 | } 1283 | 1284 | // Get distances for all items 1285 | // To avoid calculating distance multiple times for any items, sort by id 1286 | std::sort(nns.begin(), nns.end()); 1287 | vector > nns_dist; 1288 | S last = -1; 1289 | for (size_t i = 0; i < nns.size(); i++) { 1290 | S j = nns[i]; 1291 | if (j == last) 1292 | continue; 1293 | last = j; 1294 | if (_get(j)->n_descendants == 1) // This is only to guard a really obscure case, #284 1295 | nns_dist.push_back(make_pair(D::distance(v_node, _get(j), _f), j)); 1296 | } 1297 | 1298 | size_t m = nns_dist.size(); 1299 | size_t p = n < m ? n : m; // Return this many items 1300 | std::partial_sort(nns_dist.begin(), nns_dist.begin() + p, nns_dist.end()); 1301 | for (size_t i = 0; i < p; i++) { 1302 | if (distances) 1303 | distances->push_back(D::normalized_distance(nns_dist[i].first)); 1304 | result->push_back(nns_dist[i].second); 1305 | } 1306 | } 1307 | }; 1308 | 1309 | #endif 1310 | // vim: tabstop=2 shiftwidth=2 -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "targets": [ 3 | { 4 | "target_name": "addon", 5 | "sources": [ "addon.cc", "annoyindexwrapper.cc" ], 6 | "include_dirs": [ 7 | " 9 | #endif 10 | 11 | // KISS = "keep it simple, stupid", but high quality random number generator 12 | // http://www0.cs.ucl.ac.uk/staff/d.jones/GoodPracticeRNG.pdf -> "Use a good RNG and build it into your code" 13 | // http://mathforum.org/kb/message.jspa?messageID=6627731 14 | // https://de.wikipedia.org/wiki/KISS_(Zufallszahlengenerator) 15 | 16 | // 32 bit KISS 17 | struct Kiss32Random { 18 | uint32_t x; 19 | uint32_t y; 20 | uint32_t z; 21 | uint32_t c; 22 | 23 | // seed must be != 0 24 | Kiss32Random(uint32_t seed = 123456789) { 25 | x = seed; 26 | y = 362436000; 27 | z = 521288629; 28 | c = 7654321; 29 | } 30 | 31 | uint32_t kiss() { 32 | // Linear congruence generator 33 | x = 69069 * x + 12345; 34 | 35 | // Xor shift 36 | y ^= y << 13; 37 | y ^= y >> 17; 38 | y ^= y << 5; 39 | 40 | // Multiply-with-carry 41 | uint64_t t = 698769069ULL * z + c; 42 | c = t >> 32; 43 | z = (uint32_t) t; 44 | 45 | return x + y + z; 46 | } 47 | inline int flip() { 48 | // Draw random 0 or 1 49 | return kiss() & 1; 50 | } 51 | inline size_t index(size_t n) { 52 | // Draw random integer between 0 and n-1 where n is at most the number of data points you have 53 | return kiss() % n; 54 | } 55 | inline void set_seed(uint32_t seed) { 56 | x = seed; 57 | } 58 | }; 59 | 60 | // 64 bit KISS. Use this if you have more than about 2^24 data points ("big data" ;) ) 61 | struct Kiss64Random { 62 | uint64_t x; 63 | uint64_t y; 64 | uint64_t z; 65 | uint64_t c; 66 | 67 | // seed must be != 0 68 | Kiss64Random(uint64_t seed = 1234567890987654321ULL) { 69 | x = seed; 70 | y = 362436362436362436ULL; 71 | z = 1066149217761810ULL; 72 | c = 123456123456123456ULL; 73 | } 74 | 75 | uint64_t kiss() { 76 | // Linear congruence generator 77 | z = 6906969069LL*z+1234567; 78 | 79 | // Xor shift 80 | y ^= (y<<13); 81 | y ^= (y>>17); 82 | y ^= (y<<43); 83 | 84 | // Multiply-with-carry (uint128_t t = (2^58 + 1) * x + c; c = t >> 64; x = (uint64_t) t) 85 | uint64_t t = (x<<58)+c; 86 | c = (x>>6); 87 | x += t; 88 | c += (x= 0.4" 36 | } 37 | }, 38 | "node_modules/@ljharb/through": { 39 | "version": "2.3.9", 40 | "resolved": "https://registry.npmjs.org/@ljharb/through/-/through-2.3.9.tgz", 41 | "integrity": "sha512-yN599ZBuMPPK4tdoToLlvgJB4CLK8fGl7ntfy0Wn7U6ttNvHYurd81bfUiK/6sMkiIwm65R6ck4L6+Y3DfVbNQ==", 42 | "dev": true, 43 | "engines": { 44 | "node": ">= 0.4" 45 | } 46 | }, 47 | "node_modules/abstract-leveldown": { 48 | "version": "6.2.2", 49 | "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.2.2.tgz", 50 | "integrity": "sha512-/a+Iwj0rn//CX0EJOasNyZJd2o8xur8Ce9C57Sznti/Ilt/cb6Qd8/k98A4ZOklXgTG+iAYYUs1OTG0s1eH+zQ==", 51 | "dependencies": { 52 | "level-concat-iterator": "~2.0.0", 53 | "level-supports": "~1.0.0", 54 | "xtend": "~4.0.0" 55 | }, 56 | "engines": { 57 | "node": ">=6" 58 | } 59 | }, 60 | "node_modules/array-buffer-byte-length": { 61 | "version": "1.0.0", 62 | "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", 63 | "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", 64 | "dev": true, 65 | "dependencies": { 66 | "call-bind": "^1.0.2", 67 | "is-array-buffer": "^3.0.1" 68 | }, 69 | "funding": { 70 | "url": "https://github.com/sponsors/ljharb" 71 | } 72 | }, 73 | "node_modules/array.prototype.every": { 74 | "version": "1.1.4", 75 | "resolved": "https://registry.npmjs.org/array.prototype.every/-/array.prototype.every-1.1.4.tgz", 76 | "integrity": "sha512-Aui35iRZk1HHLRAyF7QP0KAnOnduaQ6fo6k1NVWfRc0xTs2AZ70ytlXvOmkC6Di4JmUs2Wv3DYzGtCQFSk5uGg==", 77 | "dev": true, 78 | "dependencies": { 79 | "call-bind": "^1.0.2", 80 | "define-properties": "^1.1.4", 81 | "es-abstract": "^1.20.4", 82 | "is-string": "^1.0.7" 83 | }, 84 | "engines": { 85 | "node": ">= 0.4" 86 | }, 87 | "funding": { 88 | "url": "https://github.com/sponsors/ljharb" 89 | } 90 | }, 91 | "node_modules/arraybuffer.prototype.slice": { 92 | "version": "1.0.1", 93 | "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.1.tgz", 94 | "integrity": "sha512-09x0ZWFEjj4WD8PDbykUwo3t9arLn8NIzmmYEJFpYekOAQjpkGSyrQhNoRTcwwcFRu+ycWF78QZ63oWTqSjBcw==", 95 | "dev": true, 96 | "dependencies": { 97 | "array-buffer-byte-length": "^1.0.0", 98 | "call-bind": "^1.0.2", 99 | "define-properties": "^1.2.0", 100 | "get-intrinsic": "^1.2.1", 101 | "is-array-buffer": "^3.0.2", 102 | "is-shared-array-buffer": "^1.0.2" 103 | }, 104 | "engines": { 105 | "node": ">= 0.4" 106 | }, 107 | "funding": { 108 | "url": "https://github.com/sponsors/ljharb" 109 | } 110 | }, 111 | "node_modules/assert-no-error": { 112 | "version": "1.0.0", 113 | "resolved": "https://registry.npmjs.org/assert-no-error/-/assert-no-error-1.0.0.tgz", 114 | "integrity": "sha1-8sFo58NRTnRxqpfYd6/RWiqdW88=", 115 | "dev": true 116 | }, 117 | "node_modules/available-typed-arrays": { 118 | "version": "1.0.5", 119 | "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", 120 | "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", 121 | "dev": true, 122 | "engines": { 123 | "node": ">= 0.4" 124 | }, 125 | "funding": { 126 | "url": "https://github.com/sponsors/ljharb" 127 | } 128 | }, 129 | "node_modules/balanced-match": { 130 | "version": "1.0.2", 131 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 132 | "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 133 | "dev": true 134 | }, 135 | "node_modules/bindings": { 136 | "version": "1.2.1", 137 | "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz", 138 | "integrity": "sha1-FK1hE4EtLTfXLme0ystLtyZQXxE=" 139 | }, 140 | "node_modules/brace-expansion": { 141 | "version": "1.1.11", 142 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 143 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 144 | "dev": true, 145 | "dependencies": { 146 | "balanced-match": "^1.0.0", 147 | "concat-map": "0.0.1" 148 | } 149 | }, 150 | "node_modules/bytewise": { 151 | "version": "1.1.0", 152 | "resolved": "https://registry.npmjs.org/bytewise/-/bytewise-1.1.0.tgz", 153 | "integrity": "sha1-HRPL/3F65xWAlKqIGzXQgbOHJT4=", 154 | "dev": true, 155 | "dependencies": { 156 | "bytewise-core": "^1.2.2", 157 | "typewise": "^1.0.3" 158 | } 159 | }, 160 | "node_modules/bytewise-core": { 161 | "version": "1.2.3", 162 | "resolved": "https://registry.npmjs.org/bytewise-core/-/bytewise-core-1.2.3.tgz", 163 | "integrity": "sha1-P7QQx+kVWOsasiqCg0V3qmvWHUI=", 164 | "dev": true, 165 | "dependencies": { 166 | "typewise-core": "^1.2" 167 | } 168 | }, 169 | "node_modules/call-bind": { 170 | "version": "1.0.2", 171 | "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", 172 | "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", 173 | "dev": true, 174 | "dependencies": { 175 | "function-bind": "^1.1.1", 176 | "get-intrinsic": "^1.0.2" 177 | }, 178 | "funding": { 179 | "url": "https://github.com/sponsors/ljharb" 180 | } 181 | }, 182 | "node_modules/concat-map": { 183 | "version": "0.0.1", 184 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 185 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 186 | "dev": true 187 | }, 188 | "node_modules/core-util-is": { 189 | "version": "1.0.2", 190 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", 191 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", 192 | "dev": true 193 | }, 194 | "node_modules/d3-queue": { 195 | "version": "3.0.3", 196 | "resolved": "https://registry.npmjs.org/d3-queue/-/d3-queue-3.0.3.tgz", 197 | "integrity": "sha1-EO5N0FdKGv+qv7kx0LpPEXkm7cY=", 198 | "dev": true 199 | }, 200 | "node_modules/deep-equal": { 201 | "version": "2.2.2", 202 | "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.2.tgz", 203 | "integrity": "sha512-xjVyBf0w5vH0I42jdAZzOKVldmPgSulmiyPRywoyq7HXC9qdgo17kxJE+rdnif5Tz6+pIrpJI8dCpMNLIGkUiA==", 204 | "dev": true, 205 | "dependencies": { 206 | "array-buffer-byte-length": "^1.0.0", 207 | "call-bind": "^1.0.2", 208 | "es-get-iterator": "^1.1.3", 209 | "get-intrinsic": "^1.2.1", 210 | "is-arguments": "^1.1.1", 211 | "is-array-buffer": "^3.0.2", 212 | "is-date-object": "^1.0.5", 213 | "is-regex": "^1.1.4", 214 | "is-shared-array-buffer": "^1.0.2", 215 | "isarray": "^2.0.5", 216 | "object-is": "^1.1.5", 217 | "object-keys": "^1.1.1", 218 | "object.assign": "^4.1.4", 219 | "regexp.prototype.flags": "^1.5.0", 220 | "side-channel": "^1.0.4", 221 | "which-boxed-primitive": "^1.0.2", 222 | "which-collection": "^1.0.1", 223 | "which-typed-array": "^1.1.9" 224 | }, 225 | "funding": { 226 | "url": "https://github.com/sponsors/ljharb" 227 | } 228 | }, 229 | "node_modules/deep-equal/node_modules/isarray": { 230 | "version": "2.0.5", 231 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", 232 | "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", 233 | "dev": true 234 | }, 235 | "node_modules/deferred-leveldown": { 236 | "version": "5.3.0", 237 | "resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-5.3.0.tgz", 238 | "integrity": "sha512-a59VOT+oDy7vtAbLRCZwWgxu2BaCfd5Hk7wxJd48ei7I+nsg8Orlb9CLG0PMZienk9BSUKgeAqkO2+Lw+1+Ukw==", 239 | "dependencies": { 240 | "abstract-leveldown": "~6.2.1", 241 | "inherits": "^2.0.3" 242 | }, 243 | "engines": { 244 | "node": ">=6" 245 | } 246 | }, 247 | "node_modules/define-properties": { 248 | "version": "1.2.0", 249 | "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", 250 | "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", 251 | "dev": true, 252 | "dependencies": { 253 | "has-property-descriptors": "^1.0.0", 254 | "object-keys": "^1.1.1" 255 | }, 256 | "engines": { 257 | "node": ">= 0.4" 258 | }, 259 | "funding": { 260 | "url": "https://github.com/sponsors/ljharb" 261 | } 262 | }, 263 | "node_modules/defined": { 264 | "version": "1.0.1", 265 | "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.1.tgz", 266 | "integrity": "sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q==", 267 | "dev": true, 268 | "funding": { 269 | "url": "https://github.com/sponsors/ljharb" 270 | } 271 | }, 272 | "node_modules/dotignore": { 273 | "version": "0.1.2", 274 | "resolved": "https://registry.npmjs.org/dotignore/-/dotignore-0.1.2.tgz", 275 | "integrity": "sha512-UGGGWfSauusaVJC+8fgV+NVvBXkCTmVv7sk6nojDZZvuOUNGUy0Zk4UpHQD6EDjS0jpBwcACvH4eofvyzBcRDw==", 276 | "dev": true, 277 | "dependencies": { 278 | "minimatch": "^3.0.4" 279 | }, 280 | "bin": { 281 | "ignored": "bin/ignored" 282 | } 283 | }, 284 | "node_modules/encoding-down": { 285 | "version": "6.3.0", 286 | "resolved": "https://registry.npmjs.org/encoding-down/-/encoding-down-6.3.0.tgz", 287 | "integrity": "sha512-QKrV0iKR6MZVJV08QY0wp1e7vF6QbhnbQhb07bwpEyuz4uZiZgPlEGdkCROuFkUwdxlFaiPIhjyarH1ee/3vhw==", 288 | "dependencies": { 289 | "abstract-leveldown": "^6.2.1", 290 | "inherits": "^2.0.3", 291 | "level-codec": "^9.0.0", 292 | "level-errors": "^2.0.0" 293 | }, 294 | "engines": { 295 | "node": ">=6" 296 | } 297 | }, 298 | "node_modules/errno": { 299 | "version": "0.1.7", 300 | "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", 301 | "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", 302 | "dependencies": { 303 | "prr": "~1.0.1" 304 | }, 305 | "bin": { 306 | "errno": "cli.js" 307 | } 308 | }, 309 | "node_modules/es-abstract": { 310 | "version": "1.22.1", 311 | "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.22.1.tgz", 312 | "integrity": "sha512-ioRRcXMO6OFyRpyzV3kE1IIBd4WG5/kltnzdxSCqoP8CMGs/Li+M1uF5o7lOkZVFjDs+NLesthnF66Pg/0q0Lw==", 313 | "dev": true, 314 | "dependencies": { 315 | "array-buffer-byte-length": "^1.0.0", 316 | "arraybuffer.prototype.slice": "^1.0.1", 317 | "available-typed-arrays": "^1.0.5", 318 | "call-bind": "^1.0.2", 319 | "es-set-tostringtag": "^2.0.1", 320 | "es-to-primitive": "^1.2.1", 321 | "function.prototype.name": "^1.1.5", 322 | "get-intrinsic": "^1.2.1", 323 | "get-symbol-description": "^1.0.0", 324 | "globalthis": "^1.0.3", 325 | "gopd": "^1.0.1", 326 | "has": "^1.0.3", 327 | "has-property-descriptors": "^1.0.0", 328 | "has-proto": "^1.0.1", 329 | "has-symbols": "^1.0.3", 330 | "internal-slot": "^1.0.5", 331 | "is-array-buffer": "^3.0.2", 332 | "is-callable": "^1.2.7", 333 | "is-negative-zero": "^2.0.2", 334 | "is-regex": "^1.1.4", 335 | "is-shared-array-buffer": "^1.0.2", 336 | "is-string": "^1.0.7", 337 | "is-typed-array": "^1.1.10", 338 | "is-weakref": "^1.0.2", 339 | "object-inspect": "^1.12.3", 340 | "object-keys": "^1.1.1", 341 | "object.assign": "^4.1.4", 342 | "regexp.prototype.flags": "^1.5.0", 343 | "safe-array-concat": "^1.0.0", 344 | "safe-regex-test": "^1.0.0", 345 | "string.prototype.trim": "^1.2.7", 346 | "string.prototype.trimend": "^1.0.6", 347 | "string.prototype.trimstart": "^1.0.6", 348 | "typed-array-buffer": "^1.0.0", 349 | "typed-array-byte-length": "^1.0.0", 350 | "typed-array-byte-offset": "^1.0.0", 351 | "typed-array-length": "^1.0.4", 352 | "unbox-primitive": "^1.0.2", 353 | "which-typed-array": "^1.1.10" 354 | }, 355 | "engines": { 356 | "node": ">= 0.4" 357 | }, 358 | "funding": { 359 | "url": "https://github.com/sponsors/ljharb" 360 | } 361 | }, 362 | "node_modules/es-get-iterator": { 363 | "version": "1.1.3", 364 | "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", 365 | "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", 366 | "dev": true, 367 | "dependencies": { 368 | "call-bind": "^1.0.2", 369 | "get-intrinsic": "^1.1.3", 370 | "has-symbols": "^1.0.3", 371 | "is-arguments": "^1.1.1", 372 | "is-map": "^2.0.2", 373 | "is-set": "^2.0.2", 374 | "is-string": "^1.0.7", 375 | "isarray": "^2.0.5", 376 | "stop-iteration-iterator": "^1.0.0" 377 | }, 378 | "funding": { 379 | "url": "https://github.com/sponsors/ljharb" 380 | } 381 | }, 382 | "node_modules/es-get-iterator/node_modules/isarray": { 383 | "version": "2.0.5", 384 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", 385 | "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", 386 | "dev": true 387 | }, 388 | "node_modules/es-set-tostringtag": { 389 | "version": "2.0.1", 390 | "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", 391 | "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", 392 | "dev": true, 393 | "dependencies": { 394 | "get-intrinsic": "^1.1.3", 395 | "has": "^1.0.3", 396 | "has-tostringtag": "^1.0.0" 397 | }, 398 | "engines": { 399 | "node": ">= 0.4" 400 | } 401 | }, 402 | "node_modules/es-to-primitive": { 403 | "version": "1.2.1", 404 | "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", 405 | "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", 406 | "dev": true, 407 | "dependencies": { 408 | "is-callable": "^1.1.4", 409 | "is-date-object": "^1.0.1", 410 | "is-symbol": "^1.0.2" 411 | }, 412 | "engines": { 413 | "node": ">= 0.4" 414 | }, 415 | "funding": { 416 | "url": "https://github.com/sponsors/ljharb" 417 | } 418 | }, 419 | "node_modules/for-each": { 420 | "version": "0.3.3", 421 | "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", 422 | "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", 423 | "dev": true, 424 | "dependencies": { 425 | "is-callable": "^1.1.3" 426 | } 427 | }, 428 | "node_modules/fs.realpath": { 429 | "version": "1.0.0", 430 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 431 | "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", 432 | "dev": true 433 | }, 434 | "node_modules/function-bind": { 435 | "version": "1.1.1", 436 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 437 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 438 | "dev": true 439 | }, 440 | "node_modules/function.prototype.name": { 441 | "version": "1.1.5", 442 | "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", 443 | "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", 444 | "dev": true, 445 | "dependencies": { 446 | "call-bind": "^1.0.2", 447 | "define-properties": "^1.1.3", 448 | "es-abstract": "^1.19.0", 449 | "functions-have-names": "^1.2.2" 450 | }, 451 | "engines": { 452 | "node": ">= 0.4" 453 | }, 454 | "funding": { 455 | "url": "https://github.com/sponsors/ljharb" 456 | } 457 | }, 458 | "node_modules/functions-have-names": { 459 | "version": "1.2.3", 460 | "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", 461 | "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", 462 | "dev": true, 463 | "funding": { 464 | "url": "https://github.com/sponsors/ljharb" 465 | } 466 | }, 467 | "node_modules/get-intrinsic": { 468 | "version": "1.2.1", 469 | "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", 470 | "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", 471 | "dev": true, 472 | "dependencies": { 473 | "function-bind": "^1.1.1", 474 | "has": "^1.0.3", 475 | "has-proto": "^1.0.1", 476 | "has-symbols": "^1.0.3" 477 | }, 478 | "funding": { 479 | "url": "https://github.com/sponsors/ljharb" 480 | } 481 | }, 482 | "node_modules/get-package-type": { 483 | "version": "0.1.0", 484 | "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", 485 | "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", 486 | "dev": true, 487 | "engines": { 488 | "node": ">=8.0.0" 489 | } 490 | }, 491 | "node_modules/get-symbol-description": { 492 | "version": "1.0.0", 493 | "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", 494 | "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", 495 | "dev": true, 496 | "dependencies": { 497 | "call-bind": "^1.0.2", 498 | "get-intrinsic": "^1.1.1" 499 | }, 500 | "engines": { 501 | "node": ">= 0.4" 502 | }, 503 | "funding": { 504 | "url": "https://github.com/sponsors/ljharb" 505 | } 506 | }, 507 | "node_modules/glob": { 508 | "version": "7.2.3", 509 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", 510 | "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", 511 | "dev": true, 512 | "dependencies": { 513 | "fs.realpath": "^1.0.0", 514 | "inflight": "^1.0.4", 515 | "inherits": "2", 516 | "minimatch": "^3.1.1", 517 | "once": "^1.3.0", 518 | "path-is-absolute": "^1.0.0" 519 | }, 520 | "engines": { 521 | "node": "*" 522 | }, 523 | "funding": { 524 | "url": "https://github.com/sponsors/isaacs" 525 | } 526 | }, 527 | "node_modules/globalthis": { 528 | "version": "1.0.3", 529 | "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", 530 | "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", 531 | "dev": true, 532 | "dependencies": { 533 | "define-properties": "^1.1.3" 534 | }, 535 | "engines": { 536 | "node": ">= 0.4" 537 | }, 538 | "funding": { 539 | "url": "https://github.com/sponsors/ljharb" 540 | } 541 | }, 542 | "node_modules/gopd": { 543 | "version": "1.0.1", 544 | "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", 545 | "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", 546 | "dev": true, 547 | "dependencies": { 548 | "get-intrinsic": "^1.1.3" 549 | }, 550 | "funding": { 551 | "url": "https://github.com/sponsors/ljharb" 552 | } 553 | }, 554 | "node_modules/has": { 555 | "version": "1.0.3", 556 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 557 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 558 | "dev": true, 559 | "dependencies": { 560 | "function-bind": "^1.1.1" 561 | }, 562 | "engines": { 563 | "node": ">= 0.4.0" 564 | } 565 | }, 566 | "node_modules/has-bigints": { 567 | "version": "1.0.2", 568 | "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", 569 | "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", 570 | "dev": true, 571 | "funding": { 572 | "url": "https://github.com/sponsors/ljharb" 573 | } 574 | }, 575 | "node_modules/has-dynamic-import": { 576 | "version": "2.0.1", 577 | "resolved": "https://registry.npmjs.org/has-dynamic-import/-/has-dynamic-import-2.0.1.tgz", 578 | "integrity": "sha512-X3fbtsZmwb6W7fJGR9o7x65fZoodygCrZ3TVycvghP62yYQfS0t4RS0Qcz+j5tQYUKeSWS09tHkWW6WhFV3XhQ==", 579 | "dev": true, 580 | "dependencies": { 581 | "call-bind": "^1.0.2", 582 | "get-intrinsic": "^1.1.1" 583 | }, 584 | "funding": { 585 | "url": "https://github.com/sponsors/ljharb" 586 | } 587 | }, 588 | "node_modules/has-property-descriptors": { 589 | "version": "1.0.0", 590 | "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", 591 | "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", 592 | "dev": true, 593 | "dependencies": { 594 | "get-intrinsic": "^1.1.1" 595 | }, 596 | "funding": { 597 | "url": "https://github.com/sponsors/ljharb" 598 | } 599 | }, 600 | "node_modules/has-proto": { 601 | "version": "1.0.1", 602 | "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", 603 | "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", 604 | "dev": true, 605 | "engines": { 606 | "node": ">= 0.4" 607 | }, 608 | "funding": { 609 | "url": "https://github.com/sponsors/ljharb" 610 | } 611 | }, 612 | "node_modules/has-symbols": { 613 | "version": "1.0.3", 614 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", 615 | "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", 616 | "dev": true, 617 | "engines": { 618 | "node": ">= 0.4" 619 | }, 620 | "funding": { 621 | "url": "https://github.com/sponsors/ljharb" 622 | } 623 | }, 624 | "node_modules/has-tostringtag": { 625 | "version": "1.0.0", 626 | "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", 627 | "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", 628 | "dev": true, 629 | "dependencies": { 630 | "has-symbols": "^1.0.2" 631 | }, 632 | "engines": { 633 | "node": ">= 0.4" 634 | }, 635 | "funding": { 636 | "url": "https://github.com/sponsors/ljharb" 637 | } 638 | }, 639 | "node_modules/immediate": { 640 | "version": "3.2.3", 641 | "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.2.3.tgz", 642 | "integrity": "sha1-0UD6j2FGWb1lQSMwl92qwlzdmRw=" 643 | }, 644 | "node_modules/inflight": { 645 | "version": "1.0.6", 646 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 647 | "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", 648 | "dev": true, 649 | "dependencies": { 650 | "once": "^1.3.0", 651 | "wrappy": "1" 652 | } 653 | }, 654 | "node_modules/inherits": { 655 | "version": "2.0.3", 656 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 657 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 658 | }, 659 | "node_modules/internal-slot": { 660 | "version": "1.0.5", 661 | "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", 662 | "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", 663 | "dev": true, 664 | "dependencies": { 665 | "get-intrinsic": "^1.2.0", 666 | "has": "^1.0.3", 667 | "side-channel": "^1.0.4" 668 | }, 669 | "engines": { 670 | "node": ">= 0.4" 671 | } 672 | }, 673 | "node_modules/is-arguments": { 674 | "version": "1.1.1", 675 | "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", 676 | "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", 677 | "dev": true, 678 | "dependencies": { 679 | "call-bind": "^1.0.2", 680 | "has-tostringtag": "^1.0.0" 681 | }, 682 | "engines": { 683 | "node": ">= 0.4" 684 | }, 685 | "funding": { 686 | "url": "https://github.com/sponsors/ljharb" 687 | } 688 | }, 689 | "node_modules/is-array-buffer": { 690 | "version": "3.0.2", 691 | "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", 692 | "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", 693 | "dev": true, 694 | "dependencies": { 695 | "call-bind": "^1.0.2", 696 | "get-intrinsic": "^1.2.0", 697 | "is-typed-array": "^1.1.10" 698 | }, 699 | "funding": { 700 | "url": "https://github.com/sponsors/ljharb" 701 | } 702 | }, 703 | "node_modules/is-bigint": { 704 | "version": "1.0.4", 705 | "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", 706 | "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", 707 | "dev": true, 708 | "dependencies": { 709 | "has-bigints": "^1.0.1" 710 | }, 711 | "funding": { 712 | "url": "https://github.com/sponsors/ljharb" 713 | } 714 | }, 715 | "node_modules/is-boolean-object": { 716 | "version": "1.1.2", 717 | "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", 718 | "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", 719 | "dev": true, 720 | "dependencies": { 721 | "call-bind": "^1.0.2", 722 | "has-tostringtag": "^1.0.0" 723 | }, 724 | "engines": { 725 | "node": ">= 0.4" 726 | }, 727 | "funding": { 728 | "url": "https://github.com/sponsors/ljharb" 729 | } 730 | }, 731 | "node_modules/is-callable": { 732 | "version": "1.2.7", 733 | "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", 734 | "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", 735 | "dev": true, 736 | "engines": { 737 | "node": ">= 0.4" 738 | }, 739 | "funding": { 740 | "url": "https://github.com/sponsors/ljharb" 741 | } 742 | }, 743 | "node_modules/is-core-module": { 744 | "version": "2.13.0", 745 | "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", 746 | "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", 747 | "dev": true, 748 | "dependencies": { 749 | "has": "^1.0.3" 750 | }, 751 | "funding": { 752 | "url": "https://github.com/sponsors/ljharb" 753 | } 754 | }, 755 | "node_modules/is-date-object": { 756 | "version": "1.0.5", 757 | "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", 758 | "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", 759 | "dev": true, 760 | "dependencies": { 761 | "has-tostringtag": "^1.0.0" 762 | }, 763 | "engines": { 764 | "node": ">= 0.4" 765 | }, 766 | "funding": { 767 | "url": "https://github.com/sponsors/ljharb" 768 | } 769 | }, 770 | "node_modules/is-map": { 771 | "version": "2.0.2", 772 | "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", 773 | "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", 774 | "dev": true, 775 | "funding": { 776 | "url": "https://github.com/sponsors/ljharb" 777 | } 778 | }, 779 | "node_modules/is-negative-zero": { 780 | "version": "2.0.2", 781 | "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", 782 | "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", 783 | "dev": true, 784 | "engines": { 785 | "node": ">= 0.4" 786 | }, 787 | "funding": { 788 | "url": "https://github.com/sponsors/ljharb" 789 | } 790 | }, 791 | "node_modules/is-number-object": { 792 | "version": "1.0.7", 793 | "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", 794 | "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", 795 | "dev": true, 796 | "dependencies": { 797 | "has-tostringtag": "^1.0.0" 798 | }, 799 | "engines": { 800 | "node": ">= 0.4" 801 | }, 802 | "funding": { 803 | "url": "https://github.com/sponsors/ljharb" 804 | } 805 | }, 806 | "node_modules/is-regex": { 807 | "version": "1.1.4", 808 | "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", 809 | "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", 810 | "dev": true, 811 | "dependencies": { 812 | "call-bind": "^1.0.2", 813 | "has-tostringtag": "^1.0.0" 814 | }, 815 | "engines": { 816 | "node": ">= 0.4" 817 | }, 818 | "funding": { 819 | "url": "https://github.com/sponsors/ljharb" 820 | } 821 | }, 822 | "node_modules/is-set": { 823 | "version": "2.0.2", 824 | "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", 825 | "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", 826 | "dev": true, 827 | "funding": { 828 | "url": "https://github.com/sponsors/ljharb" 829 | } 830 | }, 831 | "node_modules/is-shared-array-buffer": { 832 | "version": "1.0.2", 833 | "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", 834 | "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", 835 | "dev": true, 836 | "dependencies": { 837 | "call-bind": "^1.0.2" 838 | }, 839 | "funding": { 840 | "url": "https://github.com/sponsors/ljharb" 841 | } 842 | }, 843 | "node_modules/is-string": { 844 | "version": "1.0.7", 845 | "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", 846 | "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", 847 | "dev": true, 848 | "dependencies": { 849 | "has-tostringtag": "^1.0.0" 850 | }, 851 | "engines": { 852 | "node": ">= 0.4" 853 | }, 854 | "funding": { 855 | "url": "https://github.com/sponsors/ljharb" 856 | } 857 | }, 858 | "node_modules/is-symbol": { 859 | "version": "1.0.4", 860 | "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", 861 | "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", 862 | "dev": true, 863 | "dependencies": { 864 | "has-symbols": "^1.0.2" 865 | }, 866 | "engines": { 867 | "node": ">= 0.4" 868 | }, 869 | "funding": { 870 | "url": "https://github.com/sponsors/ljharb" 871 | } 872 | }, 873 | "node_modules/is-typed-array": { 874 | "version": "1.1.12", 875 | "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.12.tgz", 876 | "integrity": "sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==", 877 | "dev": true, 878 | "dependencies": { 879 | "which-typed-array": "^1.1.11" 880 | }, 881 | "engines": { 882 | "node": ">= 0.4" 883 | }, 884 | "funding": { 885 | "url": "https://github.com/sponsors/ljharb" 886 | } 887 | }, 888 | "node_modules/is-weakmap": { 889 | "version": "2.0.1", 890 | "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", 891 | "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", 892 | "dev": true, 893 | "funding": { 894 | "url": "https://github.com/sponsors/ljharb" 895 | } 896 | }, 897 | "node_modules/is-weakref": { 898 | "version": "1.0.2", 899 | "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", 900 | "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", 901 | "dev": true, 902 | "dependencies": { 903 | "call-bind": "^1.0.2" 904 | }, 905 | "funding": { 906 | "url": "https://github.com/sponsors/ljharb" 907 | } 908 | }, 909 | "node_modules/is-weakset": { 910 | "version": "2.0.2", 911 | "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", 912 | "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", 913 | "dev": true, 914 | "dependencies": { 915 | "call-bind": "^1.0.2", 916 | "get-intrinsic": "^1.1.1" 917 | }, 918 | "funding": { 919 | "url": "https://github.com/sponsors/ljharb" 920 | } 921 | }, 922 | "node_modules/isarray": { 923 | "version": "1.0.0", 924 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 925 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", 926 | "dev": true 927 | }, 928 | "node_modules/json-stringify-safe": { 929 | "version": "5.0.1", 930 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", 931 | "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", 932 | "dev": true 933 | }, 934 | "node_modules/level": { 935 | "version": "6.0.0", 936 | "resolved": "https://registry.npmjs.org/level/-/level-6.0.0.tgz", 937 | "integrity": "sha512-3oAi7gXLLNr7pHj8c4vbI6lHkXf35m8qb7zWMrNTrOax6CXBVggQAwL1xnC/1CszyYrW3BsLXsY5TMgTxtKfFA==", 938 | "hasInstallScript": true, 939 | "dependencies": { 940 | "level-js": "^5.0.0", 941 | "level-packager": "^5.1.0", 942 | "leveldown": "^5.4.0", 943 | "opencollective-postinstall": "^2.0.0" 944 | }, 945 | "engines": { 946 | "node": ">=8.6.0" 947 | } 948 | }, 949 | "node_modules/level-codec": { 950 | "version": "9.0.1", 951 | "resolved": "https://registry.npmjs.org/level-codec/-/level-codec-9.0.1.tgz", 952 | "integrity": "sha512-ajFP0kJ+nyq4i6kptSM+mAvJKLOg1X5FiFPtLG9M5gCEZyBmgDi3FkDrvlMkEzrUn1cWxtvVmrvoS4ASyO/q+Q==", 953 | "engines": { 954 | "node": ">=6" 955 | } 956 | }, 957 | "node_modules/level-concat-iterator": { 958 | "version": "2.0.1", 959 | "resolved": "https://registry.npmjs.org/level-concat-iterator/-/level-concat-iterator-2.0.1.tgz", 960 | "integrity": "sha512-OTKKOqeav2QWcERMJR7IS9CUo1sHnke2C0gkSmcR7QuEtFNLLzHQAvnMw8ykvEcv0Qtkg0p7FOwP1v9e5Smdcw==", 961 | "engines": { 962 | "node": ">=6" 963 | } 964 | }, 965 | "node_modules/level-errors": { 966 | "version": "2.0.1", 967 | "resolved": "https://registry.npmjs.org/level-errors/-/level-errors-2.0.1.tgz", 968 | "integrity": "sha512-UVprBJXite4gPS+3VznfgDSU8PTRuVX0NXwoWW50KLxd2yw4Y1t2JUR5In1itQnudZqRMT9DlAM3Q//9NCjCFw==", 969 | "dependencies": { 970 | "errno": "~0.1.1" 971 | }, 972 | "engines": { 973 | "node": ">=6" 974 | } 975 | }, 976 | "node_modules/level-iterator-stream": { 977 | "version": "4.0.2", 978 | "resolved": "https://registry.npmjs.org/level-iterator-stream/-/level-iterator-stream-4.0.2.tgz", 979 | "integrity": "sha512-ZSthfEqzGSOMWoUGhTXdX9jv26d32XJuHz/5YnuHZzH6wldfWMOVwI9TBtKcya4BKTyTt3XVA0A3cF3q5CY30Q==", 980 | "dependencies": { 981 | "inherits": "^2.0.4", 982 | "readable-stream": "^3.4.0", 983 | "xtend": "^4.0.2" 984 | }, 985 | "engines": { 986 | "node": ">=6" 987 | } 988 | }, 989 | "node_modules/level-iterator-stream/node_modules/inherits": { 990 | "version": "2.0.4", 991 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 992 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" 993 | }, 994 | "node_modules/level-iterator-stream/node_modules/readable-stream": { 995 | "version": "3.4.0", 996 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz", 997 | "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==", 998 | "dependencies": { 999 | "inherits": "^2.0.3", 1000 | "string_decoder": "^1.1.1", 1001 | "util-deprecate": "^1.0.1" 1002 | }, 1003 | "engines": { 1004 | "node": ">= 6" 1005 | } 1006 | }, 1007 | "node_modules/level-iterator-stream/node_modules/string_decoder": { 1008 | "version": "1.3.0", 1009 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", 1010 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", 1011 | "dependencies": { 1012 | "safe-buffer": "~5.2.0" 1013 | } 1014 | }, 1015 | "node_modules/level-iterator-stream/node_modules/xtend": { 1016 | "version": "4.0.2", 1017 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", 1018 | "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", 1019 | "engines": { 1020 | "node": ">=0.4" 1021 | } 1022 | }, 1023 | "node_modules/level-js": { 1024 | "version": "5.0.0", 1025 | "resolved": "https://registry.npmjs.org/level-js/-/level-js-5.0.0.tgz", 1026 | "integrity": "sha512-BIevs/NlfX1DCbuzt8+p2LCumiuDf6IHq5ehktqmcJVRxzfVdc6lXV2FB5cGkTYg4KAr7WfVkeUBAiTgevy19g==", 1027 | "dependencies": { 1028 | "abstract-leveldown": "~6.2.1", 1029 | "immediate": "~3.2.3", 1030 | "inherits": "^2.0.3", 1031 | "ltgt": "^2.1.2" 1032 | } 1033 | }, 1034 | "node_modules/level-packager": { 1035 | "version": "5.1.0", 1036 | "resolved": "https://registry.npmjs.org/level-packager/-/level-packager-5.1.0.tgz", 1037 | "integrity": "sha512-3pbJmDgGvp/lUQNULPoYQZtUbhMI8KoViYDw7Sa0kWl1mPeHWWJF7T/9upWI/NTMuEikkEE/cd6wBvmrW1+ZnQ==", 1038 | "dependencies": { 1039 | "encoding-down": "^6.3.0", 1040 | "levelup": "^4.3.2" 1041 | }, 1042 | "engines": { 1043 | "node": ">=6" 1044 | } 1045 | }, 1046 | "node_modules/level-post": { 1047 | "version": "1.0.7", 1048 | "resolved": "https://registry.npmjs.org/level-post/-/level-post-1.0.7.tgz", 1049 | "integrity": "sha512-PWYqG4Q00asOrLhX7BejSajByB4EmG2GaKHfj3h5UmmZ2duciXLPGYWIjBzLECFWUGOZWlm5B20h/n3Gs3HKew==", 1050 | "dev": true, 1051 | "dependencies": { 1052 | "ltgt": "^2.1.2" 1053 | } 1054 | }, 1055 | "node_modules/level-sublevel": { 1056 | "version": "6.6.4", 1057 | "resolved": "https://registry.npmjs.org/level-sublevel/-/level-sublevel-6.6.4.tgz", 1058 | "integrity": "sha512-pcCrTUOiO48+Kp6F1+UAzF/OtWqLcQVTVF39HLdZ3RO8XBoXt+XVPKZO1vVr1aUoxHZA9OtD2e1v7G+3S5KFDA==", 1059 | "dev": true, 1060 | "dependencies": { 1061 | "bytewise": "~1.1.0", 1062 | "level-codec": "^9.0.0", 1063 | "level-errors": "^2.0.0", 1064 | "level-iterator-stream": "^2.0.3", 1065 | "ltgt": "~2.1.1", 1066 | "pull-defer": "^0.2.2", 1067 | "pull-level": "^2.0.3", 1068 | "pull-stream": "^3.6.8", 1069 | "typewiselite": "~1.0.0", 1070 | "xtend": "~4.0.0" 1071 | } 1072 | }, 1073 | "node_modules/level-sublevel/node_modules/isarray": { 1074 | "version": "1.0.0", 1075 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 1076 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", 1077 | "dev": true 1078 | }, 1079 | "node_modules/level-sublevel/node_modules/level-iterator-stream": { 1080 | "version": "2.0.3", 1081 | "resolved": "https://registry.npmjs.org/level-iterator-stream/-/level-iterator-stream-2.0.3.tgz", 1082 | "integrity": "sha512-I6Heg70nfF+e5Y3/qfthJFexhRw/Gi3bIymCoXAlijZdAcLaPuWSJs3KXyTYf23ID6g0o2QF62Yh+grOXY3Rig==", 1083 | "dev": true, 1084 | "dependencies": { 1085 | "inherits": "^2.0.1", 1086 | "readable-stream": "^2.0.5", 1087 | "xtend": "^4.0.0" 1088 | }, 1089 | "engines": { 1090 | "node": ">=4" 1091 | } 1092 | }, 1093 | "node_modules/level-sublevel/node_modules/process-nextick-args": { 1094 | "version": "2.0.1", 1095 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 1096 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", 1097 | "dev": true 1098 | }, 1099 | "node_modules/level-sublevel/node_modules/readable-stream": { 1100 | "version": "2.3.6", 1101 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", 1102 | "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", 1103 | "dev": true, 1104 | "dependencies": { 1105 | "core-util-is": "~1.0.0", 1106 | "inherits": "~2.0.3", 1107 | "isarray": "~1.0.0", 1108 | "process-nextick-args": "~2.0.0", 1109 | "safe-buffer": "~5.1.1", 1110 | "string_decoder": "~1.1.1", 1111 | "util-deprecate": "~1.0.1" 1112 | } 1113 | }, 1114 | "node_modules/level-sublevel/node_modules/safe-buffer": { 1115 | "version": "5.1.2", 1116 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 1117 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", 1118 | "dev": true 1119 | }, 1120 | "node_modules/level-sublevel/node_modules/string_decoder": { 1121 | "version": "1.1.1", 1122 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 1123 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 1124 | "dev": true, 1125 | "dependencies": { 1126 | "safe-buffer": "~5.1.0" 1127 | } 1128 | }, 1129 | "node_modules/level-supports": { 1130 | "version": "1.0.1", 1131 | "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-1.0.1.tgz", 1132 | "integrity": "sha512-rXM7GYnW8gsl1vedTJIbzOrRv85c/2uCMpiiCzO2fndd06U/kUXEEU9evYn4zFggBOg36IsBW8LzqIpETwwQzg==", 1133 | "dependencies": { 1134 | "xtend": "^4.0.2" 1135 | }, 1136 | "engines": { 1137 | "node": ">=6" 1138 | } 1139 | }, 1140 | "node_modules/level-supports/node_modules/xtend": { 1141 | "version": "4.0.2", 1142 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", 1143 | "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", 1144 | "engines": { 1145 | "node": ">=0.4" 1146 | } 1147 | }, 1148 | "node_modules/leveldown": { 1149 | "version": "5.4.1", 1150 | "resolved": "https://registry.npmjs.org/leveldown/-/leveldown-5.4.1.tgz", 1151 | "integrity": "sha512-3lMPc7eU3yj5g+qF1qlALInzIYnkySIosR1AsUKFjL9D8fYbTLuENBAeDRZXIG4qeWOAyqRItOoLu2v2avWiMA==", 1152 | "hasInstallScript": true, 1153 | "dependencies": { 1154 | "abstract-leveldown": "~6.2.1", 1155 | "napi-macros": "~2.0.0", 1156 | "node-gyp-build": "~4.1.0" 1157 | }, 1158 | "engines": { 1159 | "node": ">=8.6.0" 1160 | } 1161 | }, 1162 | "node_modules/levelup": { 1163 | "version": "4.3.2", 1164 | "resolved": "https://registry.npmjs.org/levelup/-/levelup-4.3.2.tgz", 1165 | "integrity": "sha512-cRTjU4ktWo59wf13PHEiOayHC3n0dOh4i5+FHr4tv4MX9+l7mqETicNq3Aj07HKlLdk0z5muVoDL2RD+ovgiyA==", 1166 | "dependencies": { 1167 | "deferred-leveldown": "~5.3.0", 1168 | "level-errors": "~2.0.0", 1169 | "level-iterator-stream": "~4.0.0", 1170 | "level-supports": "~1.0.0", 1171 | "xtend": "~4.0.0" 1172 | }, 1173 | "engines": { 1174 | "node": ">=6" 1175 | } 1176 | }, 1177 | "node_modules/looper": { 1178 | "version": "2.0.0", 1179 | "resolved": "https://registry.npmjs.org/looper/-/looper-2.0.0.tgz", 1180 | "integrity": "sha1-Zs0Md0rz1P7axTeU90LbVtqPCew=", 1181 | "dev": true 1182 | }, 1183 | "node_modules/ltgt": { 1184 | "version": "2.1.3", 1185 | "resolved": "https://registry.npmjs.org/ltgt/-/ltgt-2.1.3.tgz", 1186 | "integrity": "sha1-EIUaBtmWS5cReEQcI8nlJpjuzjQ=" 1187 | }, 1188 | "node_modules/minimatch": { 1189 | "version": "3.1.2", 1190 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 1191 | "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 1192 | "dev": true, 1193 | "dependencies": { 1194 | "brace-expansion": "^1.1.7" 1195 | }, 1196 | "engines": { 1197 | "node": "*" 1198 | } 1199 | }, 1200 | "node_modules/minimist": { 1201 | "version": "1.2.8", 1202 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", 1203 | "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", 1204 | "dev": true, 1205 | "funding": { 1206 | "url": "https://github.com/sponsors/ljharb" 1207 | } 1208 | }, 1209 | "node_modules/nan": { 1210 | "version": "2.14.0", 1211 | "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", 1212 | "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==" 1213 | }, 1214 | "node_modules/napi-macros": { 1215 | "version": "2.0.0", 1216 | "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.0.0.tgz", 1217 | "integrity": "sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg==" 1218 | }, 1219 | "node_modules/ndjson": { 1220 | "version": "1.5.0", 1221 | "resolved": "https://registry.npmjs.org/ndjson/-/ndjson-1.5.0.tgz", 1222 | "integrity": "sha1-rmA7NrE0vOw0e0UkIrC/mNWDLsg=", 1223 | "dev": true, 1224 | "dependencies": { 1225 | "json-stringify-safe": "^5.0.1", 1226 | "minimist": "^1.2.0", 1227 | "split2": "^2.1.0", 1228 | "through2": "^2.0.3" 1229 | }, 1230 | "bin": { 1231 | "ndjson": "cli.js" 1232 | } 1233 | }, 1234 | "node_modules/ndjson/node_modules/through2": { 1235 | "version": "2.0.5", 1236 | "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", 1237 | "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", 1238 | "dev": true, 1239 | "dependencies": { 1240 | "readable-stream": "~2.3.6", 1241 | "xtend": "~4.0.1" 1242 | } 1243 | }, 1244 | "node_modules/node-gyp-build": { 1245 | "version": "4.1.1", 1246 | "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.1.1.tgz", 1247 | "integrity": "sha512-dSq1xmcPDKPZ2EED2S6zw/b9NKsqzXRE6dVr8TVQnI3FJOTteUMuqF3Qqs6LZg+mLGYJWqQzMbIjMtJqTv87nQ==", 1248 | "bin": { 1249 | "node-gyp-build": "bin.js", 1250 | "node-gyp-build-optional": "optional.js", 1251 | "node-gyp-build-test": "build-test.js" 1252 | } 1253 | }, 1254 | "node_modules/node-worker-threads-pool": { 1255 | "version": "1.5.1", 1256 | "resolved": "https://registry.npmjs.org/node-worker-threads-pool/-/node-worker-threads-pool-1.5.1.tgz", 1257 | "integrity": "sha512-7TXAhpMm+jO4MfESxYLtMGSnJWv+itdNHMdaFmeZuPXxwFGU90mtEB42BciUULXOUAxYBfXILAuvrSG3rQZ7mw==", 1258 | "dev": true 1259 | }, 1260 | "node_modules/object-inspect": { 1261 | "version": "1.12.3", 1262 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", 1263 | "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", 1264 | "dev": true, 1265 | "funding": { 1266 | "url": "https://github.com/sponsors/ljharb" 1267 | } 1268 | }, 1269 | "node_modules/object-is": { 1270 | "version": "1.1.5", 1271 | "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", 1272 | "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", 1273 | "dev": true, 1274 | "dependencies": { 1275 | "call-bind": "^1.0.2", 1276 | "define-properties": "^1.1.3" 1277 | }, 1278 | "engines": { 1279 | "node": ">= 0.4" 1280 | }, 1281 | "funding": { 1282 | "url": "https://github.com/sponsors/ljharb" 1283 | } 1284 | }, 1285 | "node_modules/object-keys": { 1286 | "version": "1.1.1", 1287 | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", 1288 | "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", 1289 | "dev": true, 1290 | "engines": { 1291 | "node": ">= 0.4" 1292 | } 1293 | }, 1294 | "node_modules/object.assign": { 1295 | "version": "4.1.4", 1296 | "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", 1297 | "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", 1298 | "dev": true, 1299 | "dependencies": { 1300 | "call-bind": "^1.0.2", 1301 | "define-properties": "^1.1.4", 1302 | "has-symbols": "^1.0.3", 1303 | "object-keys": "^1.1.1" 1304 | }, 1305 | "engines": { 1306 | "node": ">= 0.4" 1307 | }, 1308 | "funding": { 1309 | "url": "https://github.com/sponsors/ljharb" 1310 | } 1311 | }, 1312 | "node_modules/once": { 1313 | "version": "1.4.0", 1314 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1315 | "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 1316 | "dev": true, 1317 | "dependencies": { 1318 | "wrappy": "1" 1319 | } 1320 | }, 1321 | "node_modules/opencollective-postinstall": { 1322 | "version": "2.0.2", 1323 | "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.2.tgz", 1324 | "integrity": "sha512-pVOEP16TrAO2/fjej1IdOyupJY8KDUM1CvsaScRbw6oddvpQoOfGk4ywha0HKKVAD6RkW4x6Q+tNBwhf3Bgpuw==", 1325 | "bin": { 1326 | "opencollective-postinstall": "index.js" 1327 | } 1328 | }, 1329 | "node_modules/path-is-absolute": { 1330 | "version": "1.0.1", 1331 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 1332 | "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", 1333 | "dev": true, 1334 | "engines": { 1335 | "node": ">=0.10.0" 1336 | } 1337 | }, 1338 | "node_modules/path-parse": { 1339 | "version": "1.0.7", 1340 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", 1341 | "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", 1342 | "dev": true 1343 | }, 1344 | "node_modules/process-nextick-args": { 1345 | "version": "1.0.7", 1346 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", 1347 | "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", 1348 | "dev": true 1349 | }, 1350 | "node_modules/prr": { 1351 | "version": "1.0.1", 1352 | "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", 1353 | "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=" 1354 | }, 1355 | "node_modules/pull-cat": { 1356 | "version": "1.1.11", 1357 | "resolved": "https://registry.npmjs.org/pull-cat/-/pull-cat-1.1.11.tgz", 1358 | "integrity": "sha1-tkLdElXaN2pwa220+pYvX9t0wxs=", 1359 | "dev": true 1360 | }, 1361 | "node_modules/pull-defer": { 1362 | "version": "0.2.3", 1363 | "resolved": "https://registry.npmjs.org/pull-defer/-/pull-defer-0.2.3.tgz", 1364 | "integrity": "sha512-/An3KE7mVjZCqNhZsr22k1Tx8MACnUnHZZNPSJ0S62td8JtYr/AiRG42Vz7Syu31SoTLUzVIe61jtT/pNdjVYA==", 1365 | "dev": true 1366 | }, 1367 | "node_modules/pull-level": { 1368 | "version": "2.0.4", 1369 | "resolved": "https://registry.npmjs.org/pull-level/-/pull-level-2.0.4.tgz", 1370 | "integrity": "sha512-fW6pljDeUThpq5KXwKbRG3X7Ogk3vc75d5OQU/TvXXui65ykm+Bn+fiktg+MOx2jJ85cd+sheufPL+rw9QSVZg==", 1371 | "dev": true, 1372 | "dependencies": { 1373 | "level-post": "^1.0.7", 1374 | "pull-cat": "^1.1.9", 1375 | "pull-live": "^1.0.1", 1376 | "pull-pushable": "^2.0.0", 1377 | "pull-stream": "^3.4.0", 1378 | "pull-window": "^2.1.4", 1379 | "stream-to-pull-stream": "^1.7.1" 1380 | } 1381 | }, 1382 | "node_modules/pull-live": { 1383 | "version": "1.0.1", 1384 | "resolved": "https://registry.npmjs.org/pull-live/-/pull-live-1.0.1.tgz", 1385 | "integrity": "sha1-pOzuAeMwFV6RJLu89HYfIbOPUfU=", 1386 | "dev": true, 1387 | "dependencies": { 1388 | "pull-cat": "^1.1.9", 1389 | "pull-stream": "^3.4.0" 1390 | } 1391 | }, 1392 | "node_modules/pull-pushable": { 1393 | "version": "2.2.0", 1394 | "resolved": "https://registry.npmjs.org/pull-pushable/-/pull-pushable-2.2.0.tgz", 1395 | "integrity": "sha1-Xy867UethpGfAbEqLpnW8b13ZYE=", 1396 | "dev": true 1397 | }, 1398 | "node_modules/pull-stream": { 1399 | "version": "3.6.14", 1400 | "resolved": "https://registry.npmjs.org/pull-stream/-/pull-stream-3.6.14.tgz", 1401 | "integrity": "sha512-KIqdvpqHHaTUA2mCYcLG1ibEbu/LCKoJZsBWyv9lSYtPkJPBq8m3Hxa103xHi6D2thj5YXa0TqK3L3GUkwgnew==", 1402 | "dev": true 1403 | }, 1404 | "node_modules/pull-window": { 1405 | "version": "2.1.4", 1406 | "resolved": "https://registry.npmjs.org/pull-window/-/pull-window-2.1.4.tgz", 1407 | "integrity": "sha1-/DuG/uvRkgx64pdpHiP3BfiFUvA=", 1408 | "dev": true, 1409 | "dependencies": { 1410 | "looper": "^2.0.0" 1411 | } 1412 | }, 1413 | "node_modules/readable-stream": { 1414 | "version": "2.3.7", 1415 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", 1416 | "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", 1417 | "dev": true, 1418 | "dependencies": { 1419 | "core-util-is": "~1.0.0", 1420 | "inherits": "~2.0.3", 1421 | "isarray": "~1.0.0", 1422 | "process-nextick-args": "~2.0.0", 1423 | "safe-buffer": "~5.1.1", 1424 | "string_decoder": "~1.1.1", 1425 | "util-deprecate": "~1.0.1" 1426 | } 1427 | }, 1428 | "node_modules/readable-stream/node_modules/process-nextick-args": { 1429 | "version": "2.0.1", 1430 | "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", 1431 | "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", 1432 | "dev": true 1433 | }, 1434 | "node_modules/readable-stream/node_modules/safe-buffer": { 1435 | "version": "5.1.2", 1436 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", 1437 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", 1438 | "dev": true 1439 | }, 1440 | "node_modules/readable-stream/node_modules/string_decoder": { 1441 | "version": "1.1.1", 1442 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", 1443 | "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", 1444 | "dev": true, 1445 | "dependencies": { 1446 | "safe-buffer": "~5.1.0" 1447 | } 1448 | }, 1449 | "node_modules/regexp.prototype.flags": { 1450 | "version": "1.5.0", 1451 | "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", 1452 | "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", 1453 | "dev": true, 1454 | "dependencies": { 1455 | "call-bind": "^1.0.2", 1456 | "define-properties": "^1.2.0", 1457 | "functions-have-names": "^1.2.3" 1458 | }, 1459 | "engines": { 1460 | "node": ">= 0.4" 1461 | }, 1462 | "funding": { 1463 | "url": "https://github.com/sponsors/ljharb" 1464 | } 1465 | }, 1466 | "node_modules/resolve": { 1467 | "version": "2.0.0-next.4", 1468 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz", 1469 | "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==", 1470 | "dev": true, 1471 | "dependencies": { 1472 | "is-core-module": "^2.9.0", 1473 | "path-parse": "^1.0.7", 1474 | "supports-preserve-symlinks-flag": "^1.0.0" 1475 | }, 1476 | "bin": { 1477 | "resolve": "bin/resolve" 1478 | }, 1479 | "funding": { 1480 | "url": "https://github.com/sponsors/ljharb" 1481 | } 1482 | }, 1483 | "node_modules/safe-array-concat": { 1484 | "version": "1.0.0", 1485 | "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.0.0.tgz", 1486 | "integrity": "sha512-9dVEFruWIsnie89yym+xWTAYASdpw3CJV7Li/6zBewGf9z2i1j31rP6jnY0pHEO4QZh6N0K11bFjWmdR8UGdPQ==", 1487 | "dev": true, 1488 | "dependencies": { 1489 | "call-bind": "^1.0.2", 1490 | "get-intrinsic": "^1.2.0", 1491 | "has-symbols": "^1.0.3", 1492 | "isarray": "^2.0.5" 1493 | }, 1494 | "engines": { 1495 | "node": ">=0.4" 1496 | }, 1497 | "funding": { 1498 | "url": "https://github.com/sponsors/ljharb" 1499 | } 1500 | }, 1501 | "node_modules/safe-array-concat/node_modules/isarray": { 1502 | "version": "2.0.5", 1503 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", 1504 | "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", 1505 | "dev": true 1506 | }, 1507 | "node_modules/safe-buffer": { 1508 | "version": "5.2.0", 1509 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz", 1510 | "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==" 1511 | }, 1512 | "node_modules/safe-regex-test": { 1513 | "version": "1.0.0", 1514 | "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", 1515 | "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", 1516 | "dev": true, 1517 | "dependencies": { 1518 | "call-bind": "^1.0.2", 1519 | "get-intrinsic": "^1.1.3", 1520 | "is-regex": "^1.1.4" 1521 | }, 1522 | "funding": { 1523 | "url": "https://github.com/sponsors/ljharb" 1524 | } 1525 | }, 1526 | "node_modules/side-channel": { 1527 | "version": "1.0.4", 1528 | "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", 1529 | "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", 1530 | "dev": true, 1531 | "dependencies": { 1532 | "call-bind": "^1.0.0", 1533 | "get-intrinsic": "^1.0.2", 1534 | "object-inspect": "^1.9.0" 1535 | }, 1536 | "funding": { 1537 | "url": "https://github.com/sponsors/ljharb" 1538 | } 1539 | }, 1540 | "node_modules/split2": { 1541 | "version": "2.2.0", 1542 | "resolved": "https://registry.npmjs.org/split2/-/split2-2.2.0.tgz", 1543 | "integrity": "sha512-RAb22TG39LhI31MbreBgIuKiIKhVsawfTgEGqKHTK87aG+ul/PB8Sqoi3I7kVdRWiCfrKxK3uo4/YUkpNvhPbw==", 1544 | "dev": true, 1545 | "dependencies": { 1546 | "through2": "^2.0.2" 1547 | } 1548 | }, 1549 | "node_modules/split2/node_modules/through2": { 1550 | "version": "2.0.5", 1551 | "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", 1552 | "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", 1553 | "dev": true, 1554 | "dependencies": { 1555 | "readable-stream": "~2.3.6", 1556 | "xtend": "~4.0.1" 1557 | } 1558 | }, 1559 | "node_modules/stop-iteration-iterator": { 1560 | "version": "1.0.0", 1561 | "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", 1562 | "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", 1563 | "dev": true, 1564 | "dependencies": { 1565 | "internal-slot": "^1.0.4" 1566 | }, 1567 | "engines": { 1568 | "node": ">= 0.4" 1569 | } 1570 | }, 1571 | "node_modules/stream-to-pull-stream": { 1572 | "version": "1.7.3", 1573 | "resolved": "https://registry.npmjs.org/stream-to-pull-stream/-/stream-to-pull-stream-1.7.3.tgz", 1574 | "integrity": "sha512-6sNyqJpr5dIOQdgNy/xcDWwDuzAsAwVzhzrWlAPAQ7Lkjx/rv0wgvxEyKwTq6FmNd5rjTrELt/CLmaSw7crMGg==", 1575 | "dev": true, 1576 | "dependencies": { 1577 | "looper": "^3.0.0", 1578 | "pull-stream": "^3.2.3" 1579 | } 1580 | }, 1581 | "node_modules/stream-to-pull-stream/node_modules/looper": { 1582 | "version": "3.0.0", 1583 | "resolved": "https://registry.npmjs.org/looper/-/looper-3.0.0.tgz", 1584 | "integrity": "sha1-LvpUw7HLq6m5Su4uWRSwvlf7t0k=", 1585 | "dev": true 1586 | }, 1587 | "node_modules/string_decoder": { 1588 | "version": "0.10.31", 1589 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", 1590 | "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", 1591 | "dev": true 1592 | }, 1593 | "node_modules/string.prototype.trim": { 1594 | "version": "1.2.7", 1595 | "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz", 1596 | "integrity": "sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==", 1597 | "dev": true, 1598 | "dependencies": { 1599 | "call-bind": "^1.0.2", 1600 | "define-properties": "^1.1.4", 1601 | "es-abstract": "^1.20.4" 1602 | }, 1603 | "engines": { 1604 | "node": ">= 0.4" 1605 | }, 1606 | "funding": { 1607 | "url": "https://github.com/sponsors/ljharb" 1608 | } 1609 | }, 1610 | "node_modules/string.prototype.trimend": { 1611 | "version": "1.0.6", 1612 | "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", 1613 | "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", 1614 | "dev": true, 1615 | "dependencies": { 1616 | "call-bind": "^1.0.2", 1617 | "define-properties": "^1.1.4", 1618 | "es-abstract": "^1.20.4" 1619 | }, 1620 | "funding": { 1621 | "url": "https://github.com/sponsors/ljharb" 1622 | } 1623 | }, 1624 | "node_modules/string.prototype.trimstart": { 1625 | "version": "1.0.6", 1626 | "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", 1627 | "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", 1628 | "dev": true, 1629 | "dependencies": { 1630 | "call-bind": "^1.0.2", 1631 | "define-properties": "^1.1.4", 1632 | "es-abstract": "^1.20.4" 1633 | }, 1634 | "funding": { 1635 | "url": "https://github.com/sponsors/ljharb" 1636 | } 1637 | }, 1638 | "node_modules/supports-preserve-symlinks-flag": { 1639 | "version": "1.0.0", 1640 | "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", 1641 | "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", 1642 | "dev": true, 1643 | "engines": { 1644 | "node": ">= 0.4" 1645 | }, 1646 | "funding": { 1647 | "url": "https://github.com/sponsors/ljharb" 1648 | } 1649 | }, 1650 | "node_modules/tape": { 1651 | "version": "5.6.6", 1652 | "resolved": "https://registry.npmjs.org/tape/-/tape-5.6.6.tgz", 1653 | "integrity": "sha512-rGp2cZ3rfZ6QfTBm6yvohf8aXmDqPyzMKZwTMV12w4i+b/N2Adwlg8PlW8jLqWzlJUZhglyYaLOSrMt/ZlZkAA==", 1654 | "dev": true, 1655 | "dependencies": { 1656 | "@ljharb/resumer": "^0.0.1", 1657 | "@ljharb/through": "^2.3.9", 1658 | "array.prototype.every": "^1.1.4", 1659 | "call-bind": "^1.0.2", 1660 | "deep-equal": "^2.2.2", 1661 | "defined": "^1.0.1", 1662 | "dotignore": "^0.1.2", 1663 | "for-each": "^0.3.3", 1664 | "get-package-type": "^0.1.0", 1665 | "glob": "^7.2.3", 1666 | "has": "^1.0.3", 1667 | "has-dynamic-import": "^2.0.1", 1668 | "inherits": "^2.0.4", 1669 | "is-regex": "^1.1.4", 1670 | "minimist": "^1.2.8", 1671 | "object-inspect": "^1.12.3", 1672 | "object-is": "^1.1.5", 1673 | "object-keys": "^1.1.1", 1674 | "object.assign": "^4.1.4", 1675 | "resolve": "^2.0.0-next.4", 1676 | "string.prototype.trim": "^1.2.7" 1677 | }, 1678 | "bin": { 1679 | "tape": "bin/tape" 1680 | }, 1681 | "funding": { 1682 | "url": "https://github.com/sponsors/ljharb" 1683 | } 1684 | }, 1685 | "node_modules/tape/node_modules/inherits": { 1686 | "version": "2.0.4", 1687 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 1688 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 1689 | "dev": true 1690 | }, 1691 | "node_modules/through2": { 1692 | "version": "2.0.1", 1693 | "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.1.tgz", 1694 | "integrity": "sha1-OE51MU1J8y3hLuu4E2uOtrXVnak=", 1695 | "dev": true, 1696 | "dependencies": { 1697 | "readable-stream": "~2.0.0", 1698 | "xtend": "~4.0.0" 1699 | } 1700 | }, 1701 | "node_modules/through2/node_modules/isarray": { 1702 | "version": "1.0.0", 1703 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", 1704 | "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", 1705 | "dev": true 1706 | }, 1707 | "node_modules/through2/node_modules/readable-stream": { 1708 | "version": "2.0.6", 1709 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", 1710 | "integrity": "sha1-j5A0HmilPMySh4jaz80Rs265t44=", 1711 | "dev": true, 1712 | "dependencies": { 1713 | "core-util-is": "~1.0.0", 1714 | "inherits": "~2.0.1", 1715 | "isarray": "~1.0.0", 1716 | "process-nextick-args": "~1.0.6", 1717 | "string_decoder": "~0.10.x", 1718 | "util-deprecate": "~1.0.1" 1719 | } 1720 | }, 1721 | "node_modules/typed-array-buffer": { 1722 | "version": "1.0.0", 1723 | "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.0.tgz", 1724 | "integrity": "sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==", 1725 | "dev": true, 1726 | "dependencies": { 1727 | "call-bind": "^1.0.2", 1728 | "get-intrinsic": "^1.2.1", 1729 | "is-typed-array": "^1.1.10" 1730 | }, 1731 | "engines": { 1732 | "node": ">= 0.4" 1733 | } 1734 | }, 1735 | "node_modules/typed-array-byte-length": { 1736 | "version": "1.0.0", 1737 | "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.0.tgz", 1738 | "integrity": "sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==", 1739 | "dev": true, 1740 | "dependencies": { 1741 | "call-bind": "^1.0.2", 1742 | "for-each": "^0.3.3", 1743 | "has-proto": "^1.0.1", 1744 | "is-typed-array": "^1.1.10" 1745 | }, 1746 | "engines": { 1747 | "node": ">= 0.4" 1748 | }, 1749 | "funding": { 1750 | "url": "https://github.com/sponsors/ljharb" 1751 | } 1752 | }, 1753 | "node_modules/typed-array-byte-offset": { 1754 | "version": "1.0.0", 1755 | "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.0.tgz", 1756 | "integrity": "sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==", 1757 | "dev": true, 1758 | "dependencies": { 1759 | "available-typed-arrays": "^1.0.5", 1760 | "call-bind": "^1.0.2", 1761 | "for-each": "^0.3.3", 1762 | "has-proto": "^1.0.1", 1763 | "is-typed-array": "^1.1.10" 1764 | }, 1765 | "engines": { 1766 | "node": ">= 0.4" 1767 | }, 1768 | "funding": { 1769 | "url": "https://github.com/sponsors/ljharb" 1770 | } 1771 | }, 1772 | "node_modules/typed-array-length": { 1773 | "version": "1.0.4", 1774 | "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", 1775 | "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", 1776 | "dev": true, 1777 | "dependencies": { 1778 | "call-bind": "^1.0.2", 1779 | "for-each": "^0.3.3", 1780 | "is-typed-array": "^1.1.9" 1781 | }, 1782 | "funding": { 1783 | "url": "https://github.com/sponsors/ljharb" 1784 | } 1785 | }, 1786 | "node_modules/typewise": { 1787 | "version": "1.0.3", 1788 | "resolved": "https://registry.npmjs.org/typewise/-/typewise-1.0.3.tgz", 1789 | "integrity": "sha1-EGeTZUCvl5N8xdz5kiSG6fooRlE=", 1790 | "dev": true, 1791 | "dependencies": { 1792 | "typewise-core": "^1.2.0" 1793 | } 1794 | }, 1795 | "node_modules/typewise-core": { 1796 | "version": "1.2.0", 1797 | "resolved": "https://registry.npmjs.org/typewise-core/-/typewise-core-1.2.0.tgz", 1798 | "integrity": "sha1-l+uRgFx/VdL5QXSPpQ0xXZke8ZU=", 1799 | "dev": true 1800 | }, 1801 | "node_modules/typewiselite": { 1802 | "version": "1.0.0", 1803 | "resolved": "https://registry.npmjs.org/typewiselite/-/typewiselite-1.0.0.tgz", 1804 | "integrity": "sha1-yIgvobsQksBgBal/NO9chQjjZk4=", 1805 | "dev": true 1806 | }, 1807 | "node_modules/unbox-primitive": { 1808 | "version": "1.0.2", 1809 | "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", 1810 | "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", 1811 | "dev": true, 1812 | "dependencies": { 1813 | "call-bind": "^1.0.2", 1814 | "has-bigints": "^1.0.2", 1815 | "has-symbols": "^1.0.3", 1816 | "which-boxed-primitive": "^1.0.2" 1817 | }, 1818 | "funding": { 1819 | "url": "https://github.com/sponsors/ljharb" 1820 | } 1821 | }, 1822 | "node_modules/util-deprecate": { 1823 | "version": "1.0.2", 1824 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", 1825 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" 1826 | }, 1827 | "node_modules/which-boxed-primitive": { 1828 | "version": "1.0.2", 1829 | "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", 1830 | "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", 1831 | "dev": true, 1832 | "dependencies": { 1833 | "is-bigint": "^1.0.1", 1834 | "is-boolean-object": "^1.1.0", 1835 | "is-number-object": "^1.0.4", 1836 | "is-string": "^1.0.5", 1837 | "is-symbol": "^1.0.3" 1838 | }, 1839 | "funding": { 1840 | "url": "https://github.com/sponsors/ljharb" 1841 | } 1842 | }, 1843 | "node_modules/which-collection": { 1844 | "version": "1.0.1", 1845 | "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", 1846 | "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", 1847 | "dev": true, 1848 | "dependencies": { 1849 | "is-map": "^2.0.1", 1850 | "is-set": "^2.0.1", 1851 | "is-weakmap": "^2.0.1", 1852 | "is-weakset": "^2.0.1" 1853 | }, 1854 | "funding": { 1855 | "url": "https://github.com/sponsors/ljharb" 1856 | } 1857 | }, 1858 | "node_modules/which-typed-array": { 1859 | "version": "1.1.11", 1860 | "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.11.tgz", 1861 | "integrity": "sha512-qe9UWWpkeG5yzZ0tNYxDmd7vo58HDBc39mZ0xWWpolAGADdFOzkfamWLDxkOWcvHQKVmdTyQdLD4NOfjLWTKew==", 1862 | "dev": true, 1863 | "dependencies": { 1864 | "available-typed-arrays": "^1.0.5", 1865 | "call-bind": "^1.0.2", 1866 | "for-each": "^0.3.3", 1867 | "gopd": "^1.0.1", 1868 | "has-tostringtag": "^1.0.0" 1869 | }, 1870 | "engines": { 1871 | "node": ">= 0.4" 1872 | }, 1873 | "funding": { 1874 | "url": "https://github.com/sponsors/ljharb" 1875 | } 1876 | }, 1877 | "node_modules/wrappy": { 1878 | "version": "1.0.2", 1879 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1880 | "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", 1881 | "dev": true 1882 | }, 1883 | "node_modules/xtend": { 1884 | "version": "4.0.1", 1885 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", 1886 | "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", 1887 | "engines": { 1888 | "node": ">=0.4" 1889 | } 1890 | } 1891 | } 1892 | } 1893 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "annoy", 3 | "version": "4.0.0", 4 | "description": "Node bindings for Annoy, an efficient Approximate Nearest Neighbors implementation written in C++.", 5 | "main": "index.js", 6 | "gypfile": true, 7 | "scripts": { 8 | "test": "make test" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git@github.com:jimkang/annoy-node.git" 13 | }, 14 | "keywords": [ 15 | "annoy", 16 | "approximate", 17 | "nearest", 18 | "neighbor", 19 | "search", 20 | "vector", 21 | "machine learning" 22 | ], 23 | "author": "Jim Kang", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/jimkang/annoy-node/issues" 27 | }, 28 | "homepage": "https://github.com/jimkang/annoy-node", 29 | "devDependencies": { 30 | "assert-no-error": "^1.0.0", 31 | "d3-queue": "^3.0.3", 32 | "level-sublevel": "^6.6.4", 33 | "ndjson": "^1.5.0", 34 | "node-worker-threads-pool": "^1.5.1", 35 | "tape": "^5.6.6", 36 | "through2": "^2.0.1" 37 | }, 38 | "dependencies": { 39 | "bindings": "^1.2.1", 40 | "level": "^6.0.0", 41 | "nan": "^2.14.0" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/basic-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | vectorJSONFile: 'text8-vector.json', 3 | annoyFile: 'test.annoy', 4 | dimensions: 200, 5 | lookupWord1: 'big', 6 | lookupWord2: 'dog', 7 | distanceBetweenWord1And2: 3.046463, 8 | indexLookupWord: 'fire' 9 | }; 10 | -------------------------------------------------------------------------------- /tests/basictests.js: -------------------------------------------------------------------------------- 1 | /* global __dirname process */ 2 | 3 | var test = require('tape'); 4 | var Annoy = require('../index'); 5 | var fs = require('fs'); 6 | var ndjson = require('ndjson'); 7 | 8 | if (process.argv.length < 3) { 9 | console.log('Usage: node tests/basictests.js '); 10 | process.exit(); 11 | } 12 | 13 | var config = require('./' + process.argv[2]); 14 | 15 | const vectorJSONPath = __dirname + '/data/' + config.vectorJSONFile; 16 | const annoyIndexPath = __dirname + '/data/' + config.annoyFile; 17 | const dimensions = config.dimensions; 18 | 19 | var indexesForWords = {}; 20 | var wordsForIndexes = {}; 21 | var vectorCount = 0; 22 | 23 | test('Adding vectors to Annoy', addTest); 24 | test('Using vectors from Annoy', usingTest); 25 | 26 | function addTest(t) { 27 | var annoyIndex = new Annoy(dimensions, 'Euclidean'); 28 | 29 | fs 30 | .createReadStream(vectorJSONPath) 31 | .pipe(ndjson.parse({ strict: false })) 32 | .on('data', addToAnnoy) 33 | .on('end', checkAdded); 34 | 35 | function addToAnnoy(wordVectorPair) { 36 | indexesForWords[wordVectorPair.word] = vectorCount; 37 | wordsForIndexes[vectorCount] = wordVectorPair.word; 38 | annoyIndex.addItem(vectorCount, wordVectorPair.vector); 39 | // process.stdout.write('+'); 40 | vectorCount += 1; 41 | // console.log(wordVectorPair.word, wordVectorPair.vector); 42 | } 43 | 44 | function checkAdded() { 45 | t.ok(vectorCount > 0, 'More than one vector was added to the index.'); 46 | t.equal( 47 | annoyIndex.getNItems(), 48 | vectorCount, 49 | "The index's total vector count is correct." 50 | ); 51 | annoyIndex.build(); 52 | t.ok(annoyIndex.save(annoyIndexPath), 'Saved successfully.'); 53 | annoyIndex.unload(); 54 | t.end(); 55 | } 56 | } 57 | 58 | function usingTest(t) { 59 | var annoyIndex = new Annoy(dimensions, 'Euclidean'); 60 | t.ok(annoyIndex.load(annoyIndexPath), 'Loaded successfully.'); 61 | 62 | t.equal( 63 | annoyIndex.getNItems(), 64 | vectorCount, 65 | "The loaded index's total vector count is correct: " + vectorCount 66 | ); 67 | 68 | t.equal( 69 | annoyIndex 70 | .getDistance( 71 | indexesForWords[config.lookupWord1], 72 | indexesForWords[config.lookupWord2] 73 | ) 74 | .toPrecision(7), 75 | config.distanceBetweenWord1And2.toString(), 76 | 'getDistance calculates correct distance between items for ' + 77 | config.lookupWord1 + 78 | ' and ' + 79 | config.lookupWord2 80 | ); 81 | 82 | var v1 = annoyIndex.getItem(indexesForWords[config.lookupWord1]); 83 | var v2 = annoyIndex.getItem(indexesForWords[config.lookupWord2]); 84 | checkVector(v1); 85 | checkVector(v2); 86 | 87 | var sumVector = []; 88 | for (var i = 0; i < v1.length; ++i) { 89 | sumVector.push(v1[i] + v2[i]); 90 | } 91 | 92 | // console.log('Sum:', sumVector); 93 | var nnResult = annoyIndex.getNNsByVector(sumVector, 100, -1, true); 94 | // console.log('Neighbors and distances:', nnResult); 95 | 96 | checkNNResult('nnResult', nnResult); 97 | 98 | // console.log('Third closest neighbor:', wordsForIndexes[nnResult.neighbors[2]]); 99 | 100 | var nnResultByItem = annoyIndex.getNNsByItem( 101 | indexesForWords[config.indexLookupWord], 102 | 100, 103 | -1, 104 | true 105 | ); 106 | checkNNResult('nnResultByItem', nnResultByItem); 107 | 108 | t.end(); 109 | 110 | function checkNNResult(resultName, result) { 111 | t.equal(typeof result, 'object', resultName + ' is an object.'); 112 | t.ok( 113 | Array.isArray(result.neighbors), 114 | resultName + ' has a neighbors array.' 115 | ); 116 | t.equal( 117 | result.neighbors.length, 118 | 100, 119 | 'Correct number of neighbors is returned.' 120 | ); 121 | t.ok( 122 | result.neighbors.every(val => typeof val === 'number'), 123 | 'Neighbors contains all numbers.' 124 | ); 125 | 126 | t.ok( 127 | Array.isArray(result.distances), 128 | resultName + ' has a distances array.' 129 | ); 130 | t.equal( 131 | result.distances.length, 132 | 100, 133 | 'Correct number of distances is returned.' 134 | ); 135 | t.ok( 136 | result.distances.every(val => typeof val === 'number'), 137 | 'Distances contains all numbers.' 138 | ); 139 | } 140 | 141 | function checkVector(vector) { 142 | // console.log(vector); 143 | t.equal( 144 | vector.length, 145 | dimensions, 146 | 'Vector has correct number of dimensions.' 147 | ); 148 | t.ok( 149 | vector.every(val => typeof val === 'number'), 150 | 'Vector contains all numbers.' 151 | ); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /tests/data/git-stub: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jimkang/annoy-node/fc9e9d490b3d04b2831b91b49887686b43775abb/tests/data/git-stub -------------------------------------------------------------------------------- /tests/smalltest-manhattan.js: -------------------------------------------------------------------------------- 1 | /* global __dirname */ 2 | 3 | var test = require('tape'); 4 | var Annoy = require('../index'); 5 | 6 | var annoyPath = __dirname + '/data/test.annoy'; 7 | 8 | items = [ 9 | [-5.0, -4.5, -3.2, -2.8, -2.1, -1.5, -0.34, 0, 3.7, 6], 10 | [5.0, 4.5, 3.2, 2.8, 2.1, 1.5, 0.34, 0, -3.7, -6], 11 | [0, 0, 0, 0, 0, -1, -1, -0.2, 0.1, 0.8] 12 | ]; 13 | 14 | test('Add test', addTest); 15 | test('Load test', loadTest); 16 | 17 | function addTest(t) { 18 | var obj = new Annoy(10, 'Manhattan'); 19 | 20 | obj.addItem(0, items[0]); 21 | obj.addItem(1, items[1]); 22 | obj.addItem(2, items[2]); 23 | 24 | t.equal(obj.getNItems(), 3, 'Index has all the added items.'); 25 | 26 | obj.build(); 27 | t.ok(obj.save(annoyPath), 'Saved successfully.'); 28 | obj.unload(); 29 | t.end(); 30 | } 31 | 32 | function loadTest(t) { 33 | var obj2 = new Annoy(10, 'Manhattan'); 34 | var loadResult = obj2.load(annoyPath); 35 | t.ok(loadResult, 'Loads successfully.'); 36 | 37 | if (loadResult) { 38 | t.equal(obj2.getNItems(), 3, 'Number of items in index is correct.'); 39 | 40 | var dist = 0; 41 | for (var i = 0; i < items[0].length; i++) { 42 | dist += Math.abs(items[0][i] - items[1][i]); 43 | } 44 | 45 | t.equal( 46 | obj2.getDistance(0, 1).toPrecision(2), 47 | dist.toPrecision(2), 48 | 'getDistance calculates correct distance between items 0 and 1.' 49 | ); 50 | 51 | var v1 = obj2.getItem(0); 52 | var v2 = obj2.getItem(1); 53 | 54 | var sum = []; 55 | for (var i = 0; i < v1.length; ++i) { 56 | sum.push(v1[i] + v2[i]); 57 | } 58 | // console.log('Sum:', sum); 59 | var neighbors = obj2.getNNsByVector(sum, 10, -1, false); 60 | t.ok(Array.isArray(neighbors), 'getNNsByVector result is an array.'); 61 | // console.log('Nearest neighbors to sum', neighbors); 62 | 63 | var nnResult = obj2.getNNsByVector(sum, 10, -1, true); 64 | checkNeighborsAndDistancesResult(nnResult); 65 | 66 | var neighborsByItem = obj2.getNNsByItem(1, 10, -1, false); 67 | t.ok(Array.isArray(neighborsByItem), 'NN by item result is an array.'); 68 | var nnResultByItem = obj2.getNNsByItem(1, 10, -1, true); 69 | checkNeighborsAndDistancesResult(nnResultByItem); 70 | } 71 | 72 | t.end(); 73 | 74 | function checkNeighborsAndDistancesResult(result) { 75 | t.equal(typeof result, 'object', 'NN result is an object.'); 76 | t.ok(Array.isArray(result.neighbors), 'NN result has a neighbors array.'); 77 | t.ok(Array.isArray(result.distances), 'NN result has a distances array.'); 78 | // console.log('Nearest neighbors to sum with distances', result); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /tests/smalltest.js: -------------------------------------------------------------------------------- 1 | /* global __dirname */ 2 | 3 | var test = require('tape'); 4 | var Annoy = require('../index'); 5 | 6 | var annoyPath = __dirname + '/data/test.annoy'; 7 | 8 | test('Add test', addTest); 9 | test('Load test', loadTest); 10 | 11 | function addTest(t) { 12 | var obj = new Annoy(10, 'Angular'); 13 | 14 | obj.addItem(0, [-5.0, -4.5, -3.2, -2.8, -2.1, -1.5, -0.34, 0, 3.7, 6]); 15 | obj.addItem(1, [5.0, 4.5, 3.2, 2.8, 2.1, 1.5, 0.34, 0, -3.7, -6]); 16 | obj.addItem(2, [0, 0, 0, 0, 0, -1, -1, -0.2, 0.1, 0.8]); 17 | 18 | t.equal(obj.getNItems(), 3, 'Index has all the added items.'); 19 | 20 | obj.build(); 21 | t.ok(obj.save(annoyPath), 'Saved successfully.'); 22 | obj.unload(); 23 | t.end(); 24 | } 25 | 26 | function loadTest(t) { 27 | var obj2 = new Annoy(10, 'Angular'); 28 | var loadResult = obj2.load(annoyPath); 29 | t.ok(loadResult, 'Loads successfully.'); 30 | 31 | if (loadResult) { 32 | t.equal(obj2.getNItems(), 3, 'Number of items in index is correct.'); 33 | 34 | t.equal( 35 | obj2.getDistance(0, 1), 36 | 2.0, 37 | 'getDistance calculates correct distance between items 0 and 1.' 38 | ); 39 | 40 | var v1 = obj2.getItem(0); 41 | var v2 = obj2.getItem(1); 42 | 43 | var sum = []; 44 | for (var i = 0; i < v1.length; ++i) { 45 | sum.push(v1[i] + v2[i]); 46 | } 47 | // console.log('Sum:', sum); 48 | var neighbors = obj2.getNNsByVector(sum, 10, -1, false); 49 | t.ok(Array.isArray(neighbors), 'getNNsByVector result is an array.'); 50 | // console.log('Nearest neighbors to sum', neighbors); 51 | 52 | var nnResult = obj2.getNNsByVector(sum, 10, -1, true); 53 | checkNeighborsAndDistancesResult(nnResult); 54 | 55 | var neighborsByItem = obj2.getNNsByItem(1, 10, -1, false); 56 | t.ok(Array.isArray(neighborsByItem), 'NN by item result is an array.'); 57 | var nnResultByItem = obj2.getNNsByItem(1, 10, -1, true); 58 | checkNeighborsAndDistancesResult(nnResultByItem); 59 | } 60 | 61 | t.end(); 62 | 63 | function checkNeighborsAndDistancesResult(result) { 64 | t.equal(typeof result, 'object', 'NN result is an object.'); 65 | t.ok(Array.isArray(result.neighbors), 'NN result has a neighbors array.'); 66 | t.ok(Array.isArray(result.distances), 'NN result has a distances array.'); 67 | // console.log('Nearest neighbors to sum with distances', result); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /tests/testworker.js: -------------------------------------------------------------------------------- 1 | const { parentPort } = require('worker_threads'); 2 | var Annoy = require('../index'); 3 | 4 | var annoyPath = __dirname + '/data/test.annoy'; 5 | 6 | const annoyIndex = new Annoy(10, 'Angular') 7 | 8 | if(!annoyIndex.load(annoyPath)) { 9 | throw new Error('Loading annoy index failed!') 10 | } else { 11 | console.log('Successfully loaded annoy index.') 12 | } 13 | 14 | parentPort.on('message', async (id) => { 15 | // return the result to the main thread 16 | const result = annoyIndex.getNNsByItem(id, 10, -1, false) 17 | parentPort.postMessage(result); 18 | }); -------------------------------------------------------------------------------- /tests/very-big-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | vectorJSONFile: 'GoogleNews-vectors-negative300.json', 3 | annoyFile: 'very-big-test.annoy', 4 | dimensions: 300, 5 | lookupWord1: 'king', 6 | lookupWord2: 'woman', 7 | distanceBetweenWord1And2: 3.673551, 8 | indexLookupWord: 'perpendicular' 9 | }; 10 | -------------------------------------------------------------------------------- /tests/worker-thread-test.js: -------------------------------------------------------------------------------- 1 | var test = require('tape'); 2 | const { StaticPool } = require('node-worker-threads-pool') 3 | 4 | var workerPath = __dirname + '/testworker.js'; 5 | 6 | test('Worker thread test', workerThreadTest); 7 | 8 | async function workerThreadTest(t) { 9 | const workerPool = new StaticPool({ 10 | size: 2, 11 | task: workerPath 12 | }); 13 | 14 | const idsToLookUp = [0, 1] 15 | const indexLookups = idsToLookUp.map(id => workerPool.exec(id)) 16 | console.log("Lookup results: ", (await Promise.all(indexLookups))) 17 | workerPool.destroy() 18 | t.end() 19 | } 20 | -------------------------------------------------------------------------------- /tools/build-word-index-db.js: -------------------------------------------------------------------------------- 1 | /* global process */ 2 | 3 | var fs = require('fs'); 4 | var ndjson = require('ndjson'); 5 | var level = require('level'); 6 | var Sublevel = require('level-sublevel'); 7 | var through2 = require('through2'); 8 | var queue = require('d3-queue').queue; 9 | 10 | if (process.argv.length < 4) { 11 | console.log( 12 | 'Usage: node tools/build-word-index-db.js ' 13 | ); 14 | process.exit(); 15 | } 16 | 17 | const vectorJSONPath = process.argv[2]; 18 | const dbPath = process.argv[3]; 19 | 20 | var db = Sublevel(level(dbPath)); 21 | var indexesForWords = db.sublevel('indexes'); 22 | var wordsForIndexes = db.sublevel('words'); 23 | 24 | var vectorCount = 0; 25 | 26 | fs 27 | .createReadStream(vectorJSONPath) 28 | .pipe(ndjson.parse({ strict: false })) 29 | .pipe(through2({ objectMode: true }, addToDb)) 30 | .on('end', closeDb); 31 | 32 | function addToDb(wordVectorPair, enc, done) { 33 | var q = queue(); 34 | q.defer(indexesForWords.put, wordVectorPair.word, vectorCount); 35 | q.defer(wordsForIndexes.put, vectorCount, wordVectorPair.word); 36 | q.await(incrementCount); 37 | 38 | function incrementCount(error) { 39 | if (error) { 40 | throw error; 41 | } else { 42 | vectorCount += 1; 43 | done(); 44 | } 45 | } 46 | } 47 | 48 | function closeDb() { 49 | db.close(logDone); 50 | 51 | function logDone(error) { 52 | if (error) { 53 | console.error(error); 54 | } else { 55 | console.log('Done building db.'); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /tools/test-word-index-db.js: -------------------------------------------------------------------------------- 1 | /* global process */ 2 | 3 | var fs = require('fs'); 4 | var ndjson = require('ndjson'); 5 | var level = require('level'); 6 | var Sublevel = require('level-sublevel'); 7 | var through2 = require('through2'); 8 | var test = require('tape'); 9 | var assertNoError = require('assert-no-error'); 10 | var Annoy = require('../index'); 11 | 12 | if (process.argv.length < 5) { 13 | console.log( 14 | 'Usage: node tools/test-word-index-db.js ' 15 | ); 16 | process.exit(); 17 | } 18 | 19 | const vectorJSONPath = process.argv[2]; 20 | const dbPath = process.argv[3]; 21 | const annoyPath = process.argv[4]; 22 | 23 | var db = Sublevel(level(dbPath)); 24 | var indexesForWords = db.sublevel('indexes'); 25 | var wordsForIndexes = db.sublevel('words'); 26 | var annoyIndex = new Annoy(300, 'Euclidean'); 27 | annoyIndex.load(annoyPath); 28 | 29 | var vectorCount = 0; 30 | 31 | fs 32 | .createReadStream(vectorJSONPath) 33 | .pipe(ndjson.parse({ strict: false })) 34 | .pipe(through2({ objectMode: true }, runTestOnPair)) 35 | .on('end', closeDb); 36 | 37 | function runTestOnPair(wordVectorPair, enc, done) { 38 | test(wordVectorPair.word + '/' + vectorCount, testPair); 39 | 40 | function testPair(t) { 41 | indexesForWords.get(wordVectorPair.word, checkIndex); 42 | 43 | function checkIndex(error, index) { 44 | assertNoError(t.ok, error, 'No error while getting index for word.'); 45 | t.equal(parseInt(index, 10), vectorCount, 'Index matches index in JSON.'); 46 | var vector = annoyIndex.getItem(index); 47 | t.deepEqual( 48 | vector.map(toFixedDecimalString), 49 | wordVectorPair.vector, 50 | 'Vector for index matches vector for same index in JSON.' 51 | ); 52 | 53 | wordsForIndexes.get(index, checkWord); 54 | } 55 | 56 | function checkWord(error, word) { 57 | assertNoError(t.ok, error, 'No error while getting word for index.'); 58 | t.equal(word, wordVectorPair.word, 'Word matches word in JSON.'); 59 | 60 | vectorCount += 1; 61 | 62 | t.end(); 63 | done(); 64 | } 65 | } 66 | } 67 | 68 | function closeDb() { 69 | db.close(logDone); 70 | 71 | function logDone(error) { 72 | if (error) { 73 | console.error(error); 74 | } else { 75 | console.log('Done testing db.'); 76 | } 77 | } 78 | } 79 | 80 | function toFixedDecimalString(num) { 81 | return num.toFixed(6); 82 | } 83 | -------------------------------------------------------------------------------- /tools/w2v-to-json.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | const long long max_w = 2000; 5 | 6 | void w2vToJSON(char *binPath, char *jsonPath) { 7 | 8 | FILE *fi = fopen(binPath, "rb"); 9 | // FILE *fWordIndex = fopen(wordIndexPath, "wb"); 10 | FILE *fJson = fopen(jsonPath, "wb"); 11 | 12 | long long words, dimensions; 13 | fscanf(fi, "%lld", &words); 14 | fscanf(fi, "%lld", &dimensions); 15 | fscanf(fi, "%*[ ]"); 16 | fscanf(fi, "%*[\n]"); 17 | 18 | // *numberOfDimensionsPerVector = dimensions; 19 | 20 | char word[max_w]; 21 | char ch; 22 | float value; 23 | int b, a; 24 | 25 | // Start outer array. 26 | // fprintf(fJson, "%s\n,", "["); 27 | 28 | for (b = 0; b < words; b++) { 29 | if(feof(fi)) 30 | break; 31 | 32 | word[0] = 0; 33 | fscanf(fi, "%[^ ]", word); 34 | fscanf(fi, "%c", &ch); 35 | 36 | // fprintf(fWordIndex, "%s:%d\n", word, b); 37 | fprintf(fJson, "{\"word\": \"%s\", \"vector\": [", word); 38 | // fprintf(fJson, "%s\n", "["); 39 | 40 | for (a = 0; a < dimensions; a++) { 41 | fread(&value, sizeof(float), 1, fi); 42 | if (a < dimensions - 1) { 43 | fprintf(fJson, "\"%f\",", value); 44 | } 45 | else { 46 | fprintf(fJson, "\"%f\"", value); 47 | } 48 | } 49 | 50 | fprintf(fJson, "%s\n", "]}"); 51 | 52 | fscanf(fi, "%*[\n]"); 53 | } 54 | 55 | // End outer array. 56 | // fprintf(fJson, "%s\n,", "]"); 57 | 58 | fclose(fi); 59 | fclose(fJson); 60 | } 61 | 62 | int main(int argc, char **argv) { 63 | if (argc < 3) { 64 | printf("Usage: w2v-to-json \n"); 65 | return -1; 66 | } 67 | 68 | char *binPath = argv[1]; 69 | char *jsonPath = argv[2]; 70 | 71 | w2vToJSON(binPath, jsonPath); 72 | } 73 | --------------------------------------------------------------------------------