Change from pnpm to npm, add ./link.sh shortcut for npm style package linking
[node_zettair.git] / zettair.cpp
1 /*
2  * Copyright (C) 2022 Nick Downing <nick@ndcode.org>
3  * SPDX-License-Identifier: MIT
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining a copy
6  * of this software and associated documentation files (the "Software"), to
7  * deal in the Software without restriction, including without limitation the
8  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9  * sell copies of the Software, and to permit persons to whom the Software is
10  * furnished to do so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be included in
13  * all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21  * IN THE SOFTWARE.
22  */
23
24 #include <napi.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <zettair/def.h>
28 #include <zettair/index.h>
29
30 namespace zettair {
31
32 static Napi::Object result_to_object(
33   Napi::Env env,
34   struct index_result* result
35 );
36 static Napi::Object results_to_object(
37   Napi::Env env,
38   struct index_result* results,
39   unsigned int n_results,
40   double/*unsigned long int*/ total_results
41 );
42
43 class Index : public Napi::ObjectWrap<Index> {
44 public:
45   Index(const Napi::CallbackInfo& info);
46   Napi::Value search(const Napi::CallbackInfo& info);
47
48   struct index* idx;
49 };
50
51 Index::Index(const Napi::CallbackInfo& info) : Napi::ObjectWrap<Index>(info) {
52   Napi::Env env = info.Env();
53   Napi::String prefix = info[0].As<Napi::String>();
54
55   int lopts = INDEX_LOAD_NOOPT;
56   struct index_load_opt lopt;
57   memset(&lopt, 0, sizeof(struct index_load_opt));
58
59   lopts |= INDEX_LOAD_IGNORE_VERSION;  /* quick hack */
60   idx = index_load(prefix.Utf8Value().c_str(), MEMORY_DEFAULT, lopts, &lopt);
61   if (idx == NULL)
62     Napi::Error::New(
63       env,
64       "Unable to load index"
65     ).ThrowAsJavaScriptException();
66 }
67
68 Napi::Value Index::search(const Napi::CallbackInfo& info){
69   Napi::Env env = info.Env();
70   std::string query = info[0].As<Napi::String>().Utf8Value();
71   uint32_t startdoc = info[1].As<Napi::Number>().Uint32Value();
72   uint32_t len = info[2].As<Napi::Number>().Uint32Value();
73   std::string opt_type =
74     info[3] == env.Undefined() ?
75       std::string() :
76       info[3].As<Napi::String>().Utf8Value();
77   Napi::Array opt_args_tuple =
78     info[4] == env.Undefined() ?
79       Napi::Array::New(env) :
80       info[4].As<Napi::Array>();
81   uint32_t accumulator_limit =
82     info[5] == env.Undefined() ?
83       0 :
84       info[5].As<Napi::Number>().Uint32Value();
85
86   struct index_result* results = (struct index_result*)malloc(
87     len * sizeof(struct index_result)
88   );
89   if (results == NULL) {
90     Napi::Error::New(
91       env,
92       "Unable to allocate results"
93     ).ThrowAsJavaScriptException();
94     return env.Null();
95   }
96
97   unsigned int n_results;
98   double/*unsigned long int*/ total_results;
99   int est;
100   int opts = INDEX_SEARCH_NOOPT;
101   struct index_search_opt opt;
102   memset(&opt, 0, sizeof(struct index_search_opt));
103
104   opt.u.okapi_k3.k1 = 1.2;
105   opt.u.okapi_k3.k3 = 1e10;
106   opt.u.okapi_k3.b = 0.75;
107
108   if (info[3] != env.Undefined()) {
109     if (opt_type == "COSINE")
110       opts = INDEX_SEARCH_COSINE_RANK;
111     else if (opt_type == "OKAPI")
112       opts = INDEX_SEARCH_OKAPI_RANK;
113     else if (opt_type == "OKAPI_K3") {
114       if (opt_args_tuple.Length() < 3U) {
115         Napi::Error::New(
116           env,
117           "Must supply args to search type"
118         ).ThrowAsJavaScriptException();
119         free(results);
120         return env.Null();
121       }
122       opts = INDEX_SEARCH_OKAPI_RANK;
123       opt.u.okapi_k3.k1 = opt_args_tuple.Get(0U).As<Napi::Number>().FloatValue();
124       opt.u.okapi_k3.k3 = opt_args_tuple.Get(1U).As<Napi::Number>().FloatValue();
125       opt.u.okapi_k3.b = opt_args_tuple.Get(2U).As<Napi::Number>().FloatValue();
126     }
127     else if (opt_type == "HAWKAPI") {
128       if (opt_args_tuple.Length() < 2U) {
129         Napi::Error::New(
130           env,
131           "Must supply args to search type"
132         ).ThrowAsJavaScriptException();
133         free(results);
134         return env.Null();
135       }
136       opts = INDEX_SEARCH_HAWKAPI_RANK;
137       opt.u.hawkapi.alpha = opt_args_tuple.Get(0U).As<Napi::Number>().FloatValue();
138       opt.u.hawkapi.k3 = opt_args_tuple.Get(1U).As<Napi::Number>().FloatValue();
139     }
140     else if (opt_type == "DIRICHLET") {
141       opts = INDEX_SEARCH_DIRICHLET_RANK;
142       opt.u.dirichlet.mu =
143         opt_args_tuple.Length() < 1U ?
144           2500.0 :
145           opt_args_tuple.Get(0U).As<Napi::Number>().FloatValue();
146     }
147     else {
148       Napi::Error::New(
149         env,
150         "Unknown search type"
151       ).ThrowAsJavaScriptException();
152       free(results);
153       return env.Null();
154     }
155   }
156   if (accumulator_limit != 0) {
157     opts |= INDEX_SEARCH_ACCUMULATOR_LIMIT;
158     opt.accumulator_limit = accumulator_limit;
159   }
160 #if 1 /* Nick */
161  opts |= INDEX_SEARCH_SUMMARY_TYPE;
162  opt.summary_type = INDEX_SUMMARISE_TAG;
163 #endif
164   if (
165     !index_search(
166       idx,
167       query.c_str(),
168       startdoc,
169       len,
170       results,
171       &n_results,
172       &total_results,
173       &est,
174       opts,
175       &opt
176     )
177   ) {
178     char err_buf[1024];
179     snprintf(
180       err_buf,
181       sizeof(err_buf),
182       "Unable to perform search for query '%s'; system error is '%s'\n",
183       query.c_str(),
184       strerror(errno)
185     );
186     Napi::Error::New(env, err_buf).ThrowAsJavaScriptException();
187     free(results);
188     return env.Null();
189   }
190   Napi::Object object =
191     results_to_object(env, results, n_results, total_results);
192   free(results);
193   return object;
194 }
195
196 static Napi::Object result_to_object(
197   Napi::Env env,
198   struct index_result* result
199 ) {
200   Napi::Object object = Napi::Object::New(env);
201   object.Set(
202     Napi::String::New(env, "docno"),
203     Napi::Number::New(env, result->docno)
204   );
205   object.Set(
206     Napi::String::New(env, "score"),
207     Napi::Number::New(env, result->score)
208   );
209   object.Set(
210     Napi::String::New(env, "summary"),
211     Napi::String::New(env, result->summary)
212   );
213   object.Set(
214     Napi::String::New(env, "title"),
215     Napi::String::New(env, result->title)
216   );
217   object.Set(
218     Napi::String::New(env, "auxiliary"),
219     Napi::String::New(env, result->auxilliary)
220   );
221   return object;
222 }
223
224 static Napi::Object results_to_object(
225   Napi::Env env,
226   struct index_result* results,
227   unsigned int n_results,
228   double/*unsigned long int*/ total_results
229 ) {
230   Napi::Object object = Napi::Object::New(env);
231   Napi::Array array = Napi::Array::New(env, n_results);
232   for (unsigned int i = 0; i < n_results; ++i)
233     array.Set(i, result_to_object(env, results + i));
234   object.Set(
235     Napi::String::New(env, "results"),
236     array
237   );
238   object.Set(
239     Napi::String::New(env, "total_results"),
240     Napi::Number::New(env, total_results)
241   );
242   return object;
243 }
244
245 Napi::Object Init(Napi::Env env, Napi::Object exports) {
246   exports.Set(
247     "Index",
248     Napi::ObjectWrap<Index>::DefineClass(
249       env,
250       "Index",
251       {
252         Napi::ObjectWrap<Index>::InstanceMethod<&Index::search>(
253           "search",
254           static_cast<napi_property_attributes>(
255             napi_writable | napi_configurable
256           )
257         )
258       }
259     )
260   );
261   return exports;
262 }
263
264 NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init)
265
266 }