@delali/narsil 0.1.14 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (251) hide show
  1. package/NOTICE +54 -0
  2. package/README.md +38 -972
  3. package/dist/adapters/filesystem.mjs +1 -1
  4. package/dist/adapters/indexeddb.mjs +1 -1
  5. package/dist/{chunk-WISGW24C.mjs → chunk-AUMKFYTG.mjs} +1 -1
  6. package/dist/{chunk-HRABJLPB.mjs → chunk-BKAI2WXI.mjs} +1 -0
  7. package/dist/chunk-GHJWC2E5.mjs +9 -0
  8. package/dist/chunk-KH3YGMVT.mjs +15 -0
  9. package/dist/{chunk-ALB35PRQ.mjs → chunk-M2EDG55Y.mjs} +8 -0
  10. package/dist/{chunk-HMO6OMZ3.mjs → chunk-S6IAXOBA.mjs} +1073 -506
  11. package/dist/chunk-TGP5M5R7.mjs +880 -0
  12. package/dist/chunk-U2BKYEC7.mjs +254 -0
  13. package/dist/{chunk-MEW7HD75.mjs → chunk-XAKNSWIK.mjs} +2543 -1154
  14. package/dist/{config-CojEPOgS.d.ts → config-P27AtSn4.d.ts} +28 -7
  15. package/dist/direct-executor-2FV3MEDD.mjs +5 -0
  16. package/dist/distribution/coordinator/etcd.d.ts +2 -2
  17. package/dist/distribution/coordinator/etcd.mjs +2 -2
  18. package/dist/distribution/coordinator/in-memory.d.ts +2 -2
  19. package/dist/distribution.d.ts +10 -4
  20. package/dist/distribution.mjs +1875 -1772
  21. package/dist/embeddings/openai.mjs +1 -1
  22. package/dist/index.browser.mjs +4566 -1783
  23. package/dist/index.d.ts +14 -5
  24. package/dist/index.mjs +6 -5
  25. package/dist/{language-ONQ3iVDf.d.ts → language-CmJBHf1j.d.ts} +4 -1
  26. package/dist/languages/albanian.d.ts +5 -0
  27. package/dist/languages/albanian.mjs +103 -0
  28. package/dist/languages/amharic.d.ts +5 -0
  29. package/dist/languages/amharic.mjs +240 -0
  30. package/dist/languages/arabic.d.ts +1 -1
  31. package/dist/languages/arabic.mjs +939 -154
  32. package/dist/languages/armenian.d.ts +1 -1
  33. package/dist/languages/armenian.mjs +275 -243
  34. package/dist/languages/azerbaijani.d.ts +5 -0
  35. package/dist/languages/azerbaijani.mjs +152 -0
  36. package/dist/languages/bambara.d.ts +5 -0
  37. package/dist/languages/bambara.mjs +60 -0
  38. package/dist/languages/basque.d.ts +5 -0
  39. package/dist/languages/basque.mjs +726 -0
  40. package/dist/languages/belarusian.d.ts +5 -0
  41. package/dist/languages/belarusian.mjs +100 -0
  42. package/dist/languages/bengali.d.ts +5 -0
  43. package/dist/languages/bengali.mjs +128 -0
  44. package/dist/languages/bosnian.d.ts +5 -0
  45. package/dist/languages/bosnian.mjs +197 -0
  46. package/dist/languages/breton.d.ts +5 -0
  47. package/dist/languages/breton.mjs +96 -0
  48. package/dist/languages/bulgarian.d.ts +1 -1
  49. package/dist/languages/bulgarian.mjs +11 -2
  50. package/dist/languages/burmese.d.ts +5 -0
  51. package/dist/languages/burmese.mjs +57 -0
  52. package/dist/languages/catalan.d.ts +5 -0
  53. package/dist/languages/catalan.mjs +987 -0
  54. package/dist/languages/chinese.d.ts +1 -1
  55. package/dist/languages/chinese.mjs +4 -3
  56. package/dist/languages/croatian.d.ts +5 -0
  57. package/dist/languages/croatian.mjs +191 -0
  58. package/dist/languages/czech.d.ts +5 -0
  59. package/dist/languages/czech.mjs +628 -0
  60. package/dist/languages/dagbani.d.ts +1 -1
  61. package/dist/languages/dagbani.mjs +11 -2
  62. package/dist/languages/danish.d.ts +1 -1
  63. package/dist/languages/danish.mjs +215 -110
  64. package/dist/languages/dutch.d.ts +1 -1
  65. package/dist/languages/dutch.mjs +1248 -163
  66. package/dist/languages/english.d.ts +1 -1
  67. package/dist/languages/english.mjs +2 -1
  68. package/dist/languages/esperanto.d.ts +5 -0
  69. package/dist/languages/esperanto.mjs +689 -0
  70. package/dist/languages/estonian.d.ts +5 -0
  71. package/dist/languages/estonian.mjs +2160 -0
  72. package/dist/languages/ewe.d.ts +1 -1
  73. package/dist/languages/ewe.mjs +11 -2
  74. package/dist/languages/faroese.d.ts +5 -0
  75. package/dist/languages/faroese.mjs +93 -0
  76. package/dist/languages/fijian.d.ts +5 -0
  77. package/dist/languages/fijian.mjs +55 -0
  78. package/dist/languages/finnish.d.ts +1 -1
  79. package/dist/languages/finnish.mjs +528 -263
  80. package/dist/languages/french.d.ts +1 -1
  81. package/dist/languages/french.mjs +950 -437
  82. package/dist/languages/ga.d.ts +1 -1
  83. package/dist/languages/ga.mjs +2 -1
  84. package/dist/languages/galician.d.ts +5 -0
  85. package/dist/languages/galician.mjs +172 -0
  86. package/dist/languages/georgian.d.ts +5 -0
  87. package/dist/languages/georgian.mjs +91 -0
  88. package/dist/languages/german.d.ts +1 -1
  89. package/dist/languages/german.mjs +432 -162
  90. package/dist/languages/greek.d.ts +1 -1
  91. package/dist/languages/greek.mjs +1970 -281
  92. package/dist/languages/guarani.d.ts +5 -0
  93. package/dist/languages/guarani.mjs +132 -0
  94. package/dist/languages/gujarati.d.ts +5 -0
  95. package/dist/languages/gujarati.mjs +234 -0
  96. package/dist/languages/haitian-creole.d.ts +5 -0
  97. package/dist/languages/haitian-creole.mjs +106 -0
  98. package/dist/languages/hausa.d.ts +1 -1
  99. package/dist/languages/hausa.mjs +2 -1
  100. package/dist/languages/hawaiian.d.ts +5 -0
  101. package/dist/languages/hawaiian.mjs +69 -0
  102. package/dist/languages/hebrew.d.ts +5 -0
  103. package/dist/languages/hebrew.mjs +213 -0
  104. package/dist/languages/hindi.d.ts +1 -1
  105. package/dist/languages/hindi.mjs +175 -97
  106. package/dist/languages/hungarian.d.ts +1 -1
  107. package/dist/languages/hungarian.mjs +412 -326
  108. package/dist/languages/icelandic.d.ts +5 -0
  109. package/dist/languages/icelandic.mjs +161 -0
  110. package/dist/languages/igbo.d.ts +1 -1
  111. package/dist/languages/igbo.mjs +2 -1
  112. package/dist/languages/indonesian.d.ts +1 -1
  113. package/dist/languages/indonesian.mjs +329 -137
  114. package/dist/languages/irish.d.ts +1 -1
  115. package/dist/languages/irish.mjs +223 -139
  116. package/dist/languages/italian.d.ts +1 -1
  117. package/dist/languages/italian.mjs +703 -406
  118. package/dist/languages/japanese.d.ts +1 -1
  119. package/dist/languages/japanese.mjs +5 -108
  120. package/dist/languages/kannada.d.ts +5 -0
  121. package/dist/languages/kannada.mjs +94 -0
  122. package/dist/languages/kazakh.d.ts +5 -0
  123. package/dist/languages/kazakh.mjs +90 -0
  124. package/dist/languages/khmer.d.ts +5 -0
  125. package/dist/languages/khmer.mjs +181 -0
  126. package/dist/languages/kinyarwanda.d.ts +5 -0
  127. package/dist/languages/kinyarwanda.mjs +99 -0
  128. package/dist/languages/kirundi.d.ts +5 -0
  129. package/dist/languages/kirundi.mjs +92 -0
  130. package/dist/languages/korean.d.ts +5 -0
  131. package/dist/languages/korean.mjs +252 -0
  132. package/dist/languages/kurmanji.d.ts +5 -0
  133. package/dist/languages/kurmanji.mjs +50 -0
  134. package/dist/languages/kyrgyz.d.ts +5 -0
  135. package/dist/languages/kyrgyz.mjs +108 -0
  136. package/dist/languages/lao.d.ts +5 -0
  137. package/dist/languages/lao.mjs +63 -0
  138. package/dist/languages/latin.d.ts +5 -0
  139. package/dist/languages/latin.mjs +69 -0
  140. package/dist/languages/latvian.d.ts +5 -0
  141. package/dist/languages/latvian.mjs +173 -0
  142. package/dist/languages/lingala.d.ts +5 -0
  143. package/dist/languages/lingala.mjs +61 -0
  144. package/dist/languages/lithuanian.d.ts +5 -0
  145. package/dist/languages/lithuanian.mjs +571 -0
  146. package/dist/languages/luxembourgish.d.ts +5 -0
  147. package/dist/languages/luxembourgish.mjs +214 -0
  148. package/dist/languages/macedonian.d.ts +5 -0
  149. package/dist/languages/macedonian.mjs +808 -0
  150. package/dist/languages/malagasy.d.ts +5 -0
  151. package/dist/languages/malagasy.mjs +67 -0
  152. package/dist/languages/malay.d.ts +5 -0
  153. package/dist/languages/malay.mjs +482 -0
  154. package/dist/languages/malayalam.d.ts +5 -0
  155. package/dist/languages/malayalam.mjs +122 -0
  156. package/dist/languages/maltese.d.ts +5 -0
  157. package/dist/languages/maltese.mjs +139 -0
  158. package/dist/languages/maori.d.ts +5 -0
  159. package/dist/languages/maori.mjs +79 -0
  160. package/dist/languages/marathi.d.ts +5 -0
  161. package/dist/languages/marathi.mjs +111 -0
  162. package/dist/languages/nepali.d.ts +1 -1
  163. package/dist/languages/nepali.mjs +209 -99
  164. package/dist/languages/norwegian.d.ts +1 -1
  165. package/dist/languages/norwegian.mjs +218 -85
  166. package/dist/languages/oromo.d.ts +5 -0
  167. package/dist/languages/oromo.mjs +78 -0
  168. package/dist/languages/persian.d.ts +5 -0
  169. package/dist/languages/persian.mjs +710 -0
  170. package/dist/languages/polish.d.ts +5 -0
  171. package/dist/languages/polish.mjs +614 -0
  172. package/dist/languages/portuguese.d.ts +1 -1
  173. package/dist/languages/portuguese.mjs +613 -380
  174. package/dist/languages/punjabi.d.ts +5 -0
  175. package/dist/languages/punjabi.mjs +197 -0
  176. package/dist/languages/romanian.d.ts +1 -1
  177. package/dist/languages/romanian.mjs +561 -382
  178. package/dist/languages/russian.d.ts +1 -1
  179. package/dist/languages/russian.mjs +373 -244
  180. package/dist/languages/samoan.d.ts +5 -0
  181. package/dist/languages/samoan.mjs +63 -0
  182. package/dist/languages/sanskrit.d.ts +1 -1
  183. package/dist/languages/sanskrit.mjs +2 -2
  184. package/dist/languages/scottish-gaelic.d.ts +5 -0
  185. package/dist/languages/scottish-gaelic.mjs +370 -0
  186. package/dist/languages/serbian.d.ts +1 -1
  187. package/dist/languages/serbian.mjs +3635 -155
  188. package/dist/languages/shona.d.ts +5 -0
  189. package/dist/languages/shona.mjs +91 -0
  190. package/dist/languages/sinhala.d.ts +5 -0
  191. package/dist/languages/sinhala.mjs +182 -0
  192. package/dist/languages/slovak.d.ts +5 -0
  193. package/dist/languages/slovak.mjs +430 -0
  194. package/dist/languages/slovenian.d.ts +1 -1
  195. package/dist/languages/slovenian.mjs +11 -2
  196. package/dist/languages/sorani.d.ts +5 -0
  197. package/dist/languages/sorani.mjs +156 -0
  198. package/dist/languages/spanish.d.ts +1 -1
  199. package/dist/languages/spanish.mjs +676 -374
  200. package/dist/languages/swahili.d.ts +1 -1
  201. package/dist/languages/swahili.mjs +2 -1
  202. package/dist/languages/swedish.d.ts +1 -1
  203. package/dist/languages/swedish.mjs +237 -95
  204. package/dist/languages/tagalog.d.ts +5 -0
  205. package/dist/languages/tagalog.mjs +159 -0
  206. package/dist/languages/tamil.d.ts +1 -1
  207. package/dist/languages/tamil.mjs +836 -64
  208. package/dist/languages/tatar.d.ts +5 -0
  209. package/dist/languages/tatar.mjs +1017 -0
  210. package/dist/languages/telugu.d.ts +5 -0
  211. package/dist/languages/telugu.mjs +62 -0
  212. package/dist/languages/thai.d.ts +5 -0
  213. package/dist/languages/thai.mjs +65 -0
  214. package/dist/languages/tibetan.d.ts +5 -0
  215. package/dist/languages/tibetan.mjs +119 -0
  216. package/dist/languages/tigrinya.d.ts +5 -0
  217. package/dist/languages/tigrinya.mjs +194 -0
  218. package/dist/languages/tongan.d.ts +5 -0
  219. package/dist/languages/tongan.mjs +56 -0
  220. package/dist/languages/turkish.d.ts +1 -1
  221. package/dist/languages/turkish.mjs +1246 -393
  222. package/dist/languages/twi.d.ts +1 -1
  223. package/dist/languages/twi.mjs +13 -2
  224. package/dist/languages/ukrainian.d.ts +1 -1
  225. package/dist/languages/ukrainian.mjs +125 -47
  226. package/dist/languages/urdu.d.ts +5 -0
  227. package/dist/languages/urdu.mjs +528 -0
  228. package/dist/languages/vietnamese.d.ts +5 -0
  229. package/dist/languages/vietnamese.mjs +277 -0
  230. package/dist/languages/welsh.d.ts +5 -0
  231. package/dist/languages/welsh.mjs +446 -0
  232. package/dist/languages/wolof.d.ts +5 -0
  233. package/dist/languages/wolof.mjs +79 -0
  234. package/dist/languages/xhosa.d.ts +5 -0
  235. package/dist/languages/xhosa.mjs +95 -0
  236. package/dist/languages/yoruba.d.ts +1 -1
  237. package/dist/languages/yoruba.mjs +1 -0
  238. package/dist/languages/zulu.d.ts +1 -1
  239. package/dist/languages/zulu.mjs +2 -1
  240. package/dist/narsil-CMZRPDlQ.d.ts +159 -0
  241. package/dist/persistence/durability/checkpoint-worker.d.ts +13 -0
  242. package/dist/persistence/durability/checkpoint-worker.mjs +6 -5
  243. package/dist/{schema-DCgkO1j4.d.ts → schema-BnvLvjRp.d.ts} +5 -3
  244. package/dist/server.d.ts +12 -3
  245. package/dist/server.mjs +183 -64
  246. package/dist/{types-DHX6zGeD.d.ts → types-BRTcBgAp.d.ts} +1 -1
  247. package/dist/workers/entry.mjs +2 -2
  248. package/package.json +10 -3
  249. package/dist/chunk-TW6BXTOL.mjs +0 -338
  250. package/dist/direct-executor-E52FFGDE.mjs +0 -4
  251. package/dist/narsil-D-84Nvm4.d.ts +0 -72
@@ -1,7 +1,7 @@
1
+ import { english } from './chunk-TGP5M5R7.mjs';
1
2
  import { createVectorStore, createArenaSimd, createHNSWIndex, magnitude, createBoundedMaxHeap, euclideanDistance, dotProduct, cosineSimilarityWithMagnitudes } from './chunk-S7RJJMHI.mjs';
2
- import { english } from './chunk-TW6BXTOL.mjs';
3
- import { NarsilError, ErrorCodes, createNarsilError } from './chunk-ALB35PRQ.mjs';
4
- import { decode, encode } from '@msgpack/msgpack';
3
+ import { NarsilError, ErrorCodes, createNarsilError } from './chunk-M2EDG55Y.mjs';
4
+ import { encode, decode } from '@msgpack/msgpack';
5
5
 
6
6
  // src/languages/registry.ts
7
7
  var languages = /* @__PURE__ */ new Map();
@@ -17,157 +17,68 @@ function getLanguage(name) {
17
17
  return lang;
18
18
  }
19
19
 
20
- // src/search/facets.ts
21
- function mergeFacets(partitionFacets) {
22
- const merged = /* @__PURE__ */ new Map();
23
- for (const partition of partitionFacets) {
24
- for (const [field, facetResult] of Object.entries(partition)) {
25
- let fieldMap = merged.get(field);
26
- if (!fieldMap) {
27
- fieldMap = /* @__PURE__ */ new Map();
28
- merged.set(field, fieldMap);
29
- }
30
- for (const [value, count] of Object.entries(facetResult.values)) {
31
- fieldMap.set(value, (fieldMap.get(value) ?? 0) + count);
32
- }
33
- }
34
- }
35
- const result = {};
36
- for (const [field, valueMap] of merged) {
37
- const values = {};
38
- for (const [value, count] of valueMap) {
39
- values[value] = count;
40
- }
41
- result[field] = { values, count: valueMap.size };
42
- }
43
- return result;
44
- }
45
-
46
- // src/core/bitset.ts
47
- function createBitSet(capacity) {
48
- return new Uint32Array(capacity + 31 >>> 5 || 1);
49
- }
50
- function bitsetSet(bs, index) {
51
- const word = index >>> 5;
52
- if (word < bs.length) {
53
- bs[word] |= 1 << (index & 31);
54
- }
55
- }
56
- function bitsetHas(bs, index) {
57
- const word = index >>> 5;
58
- if (word >= bs.length) return false;
59
- return (bs[word] & 1 << (index & 31)) !== 0;
60
- }
61
- function bitsetAnd(a, b) {
62
- const len = Math.min(a.length, b.length);
63
- const result = new Uint32Array(len);
64
- for (let i = 0; i < len; i++) {
65
- result[i] = a[i] & b[i];
66
- }
67
- return result;
68
- }
69
- function bitsetOr(a, b) {
70
- const longer = a.length >= b.length ? a : b;
71
- const shorter = a.length >= b.length ? b : a;
72
- const result = new Uint32Array(longer.length);
73
- for (let i = 0; i < shorter.length; i++) {
74
- result[i] = longer[i] | shorter[i];
75
- }
76
- for (let i = shorter.length; i < longer.length; i++) {
77
- result[i] = longer[i];
78
- }
79
- return result;
20
+ // src/core/tokenizer/normalize.ts
21
+ var DOTTED_CAPITAL_I = "\u0130";
22
+ var COMBINING_DOT_ABOVE = "\u0307";
23
+ var ARMENIAN_MARKS = /[\u055B-\u055F]/;
24
+ var FOLDED_FORMS = /\u0307|[\u0130\u055B-\u055F\u2018\u2019\u02BC\u02BB]/g;
25
+ var WIDTH_FORMS = /[\uFF01-\uFF5E\uFF61-\uFF9F]/;
26
+ var WIDTH_FORMS_GLOBAL = /[\uFF01-\uFF5E\uFF61-\uFF9F]/g;
27
+ var FULLWIDTH_ASCII_END = 65374;
28
+ var FULLWIDTH_ASCII_OFFSET = 65248;
29
+ var HALFWIDTH_KANA_START = 65377;
30
+ var HALFWIDTH_VOICED_MARK = 65438;
31
+ var HALFWIDTH_SEMI_VOICED_MARK = 65439;
32
+ var COMBINING_VOICED_MARK = "\u3099";
33
+ var COMBINING_SEMI_VOICED_MARK = "\u309A";
34
+ var HALFWIDTH_KANA_TARGETS = "\u3002\u300C\u300D\u3001\u30FB\u30F2\u30A1\u30A3\u30A5\u30A7\u30A9\u30E3\u30E5\u30E7\u30C3\u30FC\u30A2\u30A4\u30A6\u30A8\u30AA\u30AB\u30AD\u30AF\u30B1\u30B3\u30B5\u30B7\u30B9\u30BB\u30BD\u30BF\u30C1\u30C4\u30C6\u30C8\u30CA\u30CB\u30CC\u30CD\u30CE\u30CF\u30D2\u30D5\u30D8\u30DB\u30DE\u30DF\u30E0\u30E1\u30E2\u30E4\u30E6\u30E8\u30E9\u30EA\u30EB\u30EC\u30ED\u30EF\u30F3";
35
+ function foldForm(match) {
36
+ if (match === DOTTED_CAPITAL_I) return "i";
37
+ if (match === COMBINING_DOT_ABOVE || ARMENIAN_MARKS.test(match)) return "";
38
+ return "'";
39
+ }
40
+ function foldWidth(match) {
41
+ const codePoint = match.charCodeAt(0);
42
+ if (codePoint <= FULLWIDTH_ASCII_END) return String.fromCharCode(codePoint - FULLWIDTH_ASCII_OFFSET);
43
+ if (codePoint === HALFWIDTH_VOICED_MARK) return COMBINING_VOICED_MARK;
44
+ if (codePoint === HALFWIDTH_SEMI_VOICED_MARK) return COMBINING_SEMI_VOICED_MARK;
45
+ return HALFWIDTH_KANA_TARGETS[codePoint - HALFWIDTH_KANA_START];
80
46
  }
81
- function bitsetNot(bs, capacity) {
82
- const result = new Uint32Array(bs.length);
83
- const fullWords = capacity >>> 5;
84
- for (let i = 0; i < fullWords; i++) {
85
- result[i] = ~bs[i];
86
- }
87
- const remainder = capacity & 31;
88
- if (remainder > 0 && fullWords < bs.length) {
89
- result[fullWords] = ~bs[fullWords] & (1 << remainder) - 1;
90
- }
91
- return result;
92
- }
93
- function bitsetIsEmpty(bs) {
94
- for (let i = 0; i < bs.length; i++) {
95
- if (bs[i] !== 0) return false;
47
+ function isAscii(text) {
48
+ for (let i = 0; i < text.length; i++) {
49
+ if (text.charCodeAt(i) > 127) return false;
96
50
  }
97
51
  return true;
98
52
  }
99
- function bitsetFromSet(set, capacity) {
100
- const bs = createBitSet(capacity);
101
- for (const id of set) {
102
- bs[id >>> 5] |= 1 << (id & 31);
103
- }
104
- return bs;
53
+ function foldWidthForms(text) {
54
+ if (!WIDTH_FORMS.test(text)) return text;
55
+ return text.replace(WIDTH_FORMS_GLOBAL, foldWidth);
105
56
  }
106
-
107
- // src/core/fuzzy.ts
108
- function boundedLevenshtein(a, b, tolerance) {
109
- if (tolerance < 0) return { distance: -1, withinTolerance: false };
110
- if (a === b) return { distance: 0, withinTolerance: true };
111
- const m = a.length;
112
- const n = b.length;
113
- if (m === 0) {
114
- return n <= tolerance ? { distance: n, withinTolerance: true } : { distance: n, withinTolerance: false };
115
- }
116
- if (n === 0) {
117
- return m <= tolerance ? { distance: m, withinTolerance: true } : { distance: m, withinTolerance: false };
118
- }
119
- if (a.startsWith(b)) {
120
- const diff2 = m - n;
121
- return diff2 <= tolerance ? { distance: diff2, withinTolerance: true } : { distance: diff2, withinTolerance: false };
122
- }
123
- if (b.startsWith(a)) {
124
- return { distance: 0, withinTolerance: true };
125
- }
126
- const diff = Math.abs(m - n);
127
- if (diff > tolerance) return { distance: -1, withinTolerance: false };
128
- const matrix = [];
129
- for (let i = 0; i <= m; i++) {
130
- matrix[i] = [i];
131
- for (let j = 1; j <= n; j++) {
132
- matrix[i][j] = i === 0 ? j : 0;
133
- }
134
- }
135
- for (let i = 1; i <= m; i++) {
136
- let rowMin = Infinity;
137
- for (let j = 1; j <= n; j++) {
138
- if (a[i - 1] === b[j - 1]) {
139
- matrix[i][j] = matrix[i - 1][j - 1];
140
- } else {
141
- matrix[i][j] = Math.min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + 1);
142
- }
143
- rowMin = Math.min(rowMin, matrix[i][j]);
144
- }
145
- if (rowMin > tolerance) {
146
- return { distance: -1, withinTolerance: false };
147
- }
148
- }
149
- const finalDistance = matrix[m][n];
150
- return {
151
- distance: finalDistance,
152
- withinTolerance: finalDistance <= tolerance
153
- };
57
+ function normalizeForSplitting(text) {
58
+ if (isAscii(text)) return text.toLowerCase();
59
+ return foldWidthForms(text).normalize("NFC").replace(FOLDED_FORMS, foldForm).toLowerCase();
60
+ }
61
+ function stripDiacritics(text) {
62
+ return text.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
154
63
  }
155
64
 
156
- // src/core/tokenizer.ts
157
- var DEFAULT_SPLIT_PATTERN = /[^\p{L}\p{N}_'-]+/u;
158
- var DEFAULT_MIN_TOKEN_LENGTH = 1;
65
+ // src/core/tokenizer/cache.ts
159
66
  var CACHE_SIZE_FLOOR = 5e4;
160
67
  var CACHE_SIZE_CEILING = 2e6;
161
68
  var BYTES_PER_ENTRY = 200;
69
+ var NODE_CACHE_ENTRIES = 1e6;
70
+ var UNKNOWN_MEMORY_CACHE_ENTRIES = 2e5;
71
+ function clampCacheSize(entries) {
72
+ return Math.max(CACHE_SIZE_FLOOR, Math.min(entries, CACHE_SIZE_CEILING));
73
+ }
162
74
  function computeDefaultCacheSize() {
163
75
  try {
164
76
  if (typeof process !== "undefined" && typeof process.versions?.node === "string" && typeof window === "undefined") {
165
77
  const constrainedMemory = typeof process.constrainedMemory === "function" ? process.constrainedMemory() : 0;
166
78
  if (constrainedMemory > 0) {
167
- const budget = Math.floor(constrainedMemory * 0.05 / BYTES_PER_ENTRY);
168
- return Math.max(CACHE_SIZE_FLOOR, Math.min(budget, CACHE_SIZE_CEILING));
79
+ return clampCacheSize(Math.floor(constrainedMemory * 0.05 / BYTES_PER_ENTRY));
169
80
  }
170
- return Math.max(CACHE_SIZE_FLOOR, Math.min(1e6, CACHE_SIZE_CEILING));
81
+ return clampCacheSize(NODE_CACHE_ENTRIES);
171
82
  }
172
83
  if (typeof navigator !== "undefined") {
173
84
  const mem = navigator.deviceMemory;
@@ -176,17 +87,20 @@ function computeDefaultCacheSize() {
176
87
  if (mem <= 1) entries = 1e5;
177
88
  else if (mem <= 4) entries = 25e4;
178
89
  else entries = 5e5;
179
- return Math.max(CACHE_SIZE_FLOOR, Math.min(entries, CACHE_SIZE_CEILING));
90
+ return clampCacheSize(entries);
180
91
  }
181
- return Math.max(CACHE_SIZE_FLOOR, Math.min(2e5, CACHE_SIZE_CEILING));
182
92
  }
183
93
  } catch {
94
+ return clampCacheSize(UNKNOWN_MEMORY_CACHE_ENTRIES);
184
95
  }
185
- return Math.max(CACHE_SIZE_FLOOR, Math.min(2e5, CACHE_SIZE_CEILING));
96
+ return clampCacheSize(UNKNOWN_MEMORY_CACHE_ENTRIES);
186
97
  }
187
98
  var normalizationCache = /* @__PURE__ */ new Map();
188
99
  var normalizationCacheSize = 0;
189
100
  var maxCacheSize = computeDefaultCacheSize();
101
+ var cachedLangName = "";
102
+ var cachedFlags = "";
103
+ var cachedBucket = null;
190
104
  function configureNormalizationCache(maxSize) {
191
105
  if (!Number.isFinite(maxSize)) {
192
106
  throw createNarsilError(ErrorCodes.CONFIG_INVALID, "tokenizerCacheSize must be a finite number", {
@@ -212,26 +126,16 @@ function configureNormalizationCache(maxSize) {
212
126
  evictOldestEntries(normalizationCacheSize - maxCacheSize);
213
127
  }
214
128
  }
215
- var cachedLangName = "";
216
- var cachedFlags = "";
217
- var cachedBucket = null;
218
- function stripDiacritics(text) {
219
- return text.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
220
- }
221
- function resolveStopWords(language, override) {
222
- if (!override) return language.stopWords;
223
- if (override instanceof Set) return override;
224
- return override(language.stopWords);
225
- }
226
129
  function getCacheBucket(language, flags) {
227
- if (cachedBucket && language.name === cachedLangName && flags === cachedFlags) return cachedBucket;
228
- const key = `${language.name}:${flags}`;
130
+ const langKey = `${language.name}@${language.revision}`;
131
+ if (cachedBucket && langKey === cachedLangName && flags === cachedFlags) return cachedBucket;
132
+ const key = `${langKey}:${flags}`;
229
133
  let bucket = normalizationCache.get(key);
230
134
  if (!bucket) {
231
135
  bucket = /* @__PURE__ */ new Map();
232
136
  normalizationCache.set(key, bucket);
233
137
  }
234
- cachedLangName = language.name;
138
+ cachedLangName = langKey;
235
139
  cachedFlags = flags;
236
140
  cachedBucket = bucket;
237
141
  return bucket;
@@ -263,6 +167,9 @@ function transformToken(raw, language, stem, removeDiacritics) {
263
167
  const cached = bucket.get(raw);
264
168
  if (cached !== void 0) return cached;
265
169
  let normalized = raw;
170
+ if (language.normalizer) {
171
+ normalized = language.normalizer(normalized);
172
+ }
266
173
  if (removeDiacritics) {
267
174
  normalized = stripDiacritics(normalized);
268
175
  }
@@ -277,9 +184,173 @@ function transformToken(raw, language, stem, removeDiacritics) {
277
184
  normalizationCacheSize++;
278
185
  return normalized;
279
186
  }
187
+ function clearNormalizationCache() {
188
+ normalizationCache.clear();
189
+ normalizationCacheSize = 0;
190
+ cachedBucket = null;
191
+ }
192
+ function resetNormalizationCache() {
193
+ clearNormalizationCache();
194
+ maxCacheSize = computeDefaultCacheSize();
195
+ }
196
+ function getNormalizationCacheSize() {
197
+ return normalizationCacheSize;
198
+ }
199
+
200
+ // src/core/tokenizer/ngrams.ts
201
+ var OTHER_SCRIPT = 0;
202
+ var HAN_SCRIPT = 1;
203
+ var HIRAGANA_SCRIPT = 2;
204
+ var KATAKANA_SCRIPT = 3;
205
+ var HANGUL_SCRIPT = 4;
206
+ var THAI_SCRIPT = 5;
207
+ var LAO_SCRIPT = 6;
208
+ var KHMER_SCRIPT = 7;
209
+ var MYANMAR_SCRIPT = 8;
210
+ var PROLONGED_SOUND_MARK = 12540;
211
+ var IDEOGRAPHIC_ITERATION_MARK = 12293;
212
+ var LOWEST_NGRAM_CODE_POINT = 3584;
213
+ var LOWEST_MARK_CODE_POINT = 768;
214
+ var THAI_BLOCK_END = 3711;
215
+ var LAO_BLOCK_END = 3839;
216
+ var MYANMAR_BLOCK_START = 4096;
217
+ var MYANMAR_BLOCK_END = 4255;
218
+ var KHMER_BLOCK_START = 6016;
219
+ var KHMER_BLOCK_END = 6655;
220
+ var SOUTHEAST_ASIAN_BLOCKS_END = 6656;
221
+ var HIRAGANA_BLOCK_START = 12352;
222
+ var HIRAGANA_BLOCK_END = 12447;
223
+ var COMBINING_VOICED_MARK2 = 12441;
224
+ var COMBINING_SEMI_VOICED_MARK2 = 12442;
225
+ var KATAKANA_BLOCK_START = 12448;
226
+ var KATAKANA_BLOCK_END = 12543;
227
+ var HAN_EXTENSION_A_START = 13312;
228
+ var HAN_EXTENSION_A_END = 19903;
229
+ var HAN_UNIFIED_START = 19968;
230
+ var HAN_UNIFIED_END = 40959;
231
+ var HANGUL_SYLLABLE_START = 44032;
232
+ var HANGUL_SYLLABLE_END = 55203;
233
+ var HAN_COMPATIBILITY_START = 63744;
234
+ var HAN_COMPATIBILITY_END = 64255;
235
+ var SUPPLEMENTARY_PLANE_START = 65536;
236
+ var MARK = /\p{M}/uy;
237
+ var HAN = /\p{Script=Han}/uy;
238
+ var HIRAGANA = /\p{Script=Hiragana}/uy;
239
+ var KATAKANA = /\p{Script=Katakana}/uy;
240
+ var HANGUL = /\p{Script=Hangul}/uy;
241
+ var THAI = /\p{Script=Thai}/uy;
242
+ var LAO = /\p{Script=Lao}/uy;
243
+ var KHMER = /\p{Script=Khmer}/uy;
244
+ var MYANMAR = /\p{Script=Myanmar}/uy;
245
+ function matchesAt(pattern, part, index) {
246
+ pattern.lastIndex = index;
247
+ return pattern.test(part);
248
+ }
249
+ function isMark(part, index, codePoint) {
250
+ if (codePoint < LOWEST_MARK_CODE_POINT) return false;
251
+ if (codePoint >= HAN_UNIFIED_START && codePoint <= HAN_UNIFIED_END) return false;
252
+ if (codePoint >= HIRAGANA_BLOCK_START && codePoint <= KATAKANA_BLOCK_END) {
253
+ return codePoint === COMBINING_VOICED_MARK2 || codePoint === COMBINING_SEMI_VOICED_MARK2;
254
+ }
255
+ if (codePoint >= HANGUL_SYLLABLE_START && codePoint <= HANGUL_SYLLABLE_END) return false;
256
+ if (codePoint >= HAN_EXTENSION_A_START && codePoint <= HAN_EXTENSION_A_END) return false;
257
+ return matchesAt(MARK, part, index);
258
+ }
259
+ function classifyOutsideCommonBlocks(part, index) {
260
+ if (matchesAt(HAN, part, index)) return HAN_SCRIPT;
261
+ if (matchesAt(HIRAGANA, part, index)) return HIRAGANA_SCRIPT;
262
+ if (matchesAt(KATAKANA, part, index)) return KATAKANA_SCRIPT;
263
+ if (matchesAt(HANGUL, part, index)) return HANGUL_SCRIPT;
264
+ if (matchesAt(THAI, part, index)) return THAI_SCRIPT;
265
+ if (matchesAt(LAO, part, index)) return LAO_SCRIPT;
266
+ if (matchesAt(KHMER, part, index)) return KHMER_SCRIPT;
267
+ if (matchesAt(MYANMAR, part, index)) return MYANMAR_SCRIPT;
268
+ return OTHER_SCRIPT;
269
+ }
270
+ function classifySoutheastAsian(part, index, codePoint) {
271
+ if (codePoint <= THAI_BLOCK_END) return matchesAt(THAI, part, index) ? THAI_SCRIPT : OTHER_SCRIPT;
272
+ if (codePoint <= LAO_BLOCK_END) return matchesAt(LAO, part, index) ? LAO_SCRIPT : OTHER_SCRIPT;
273
+ if (codePoint >= MYANMAR_BLOCK_START && codePoint <= MYANMAR_BLOCK_END) {
274
+ return matchesAt(MYANMAR, part, index) ? MYANMAR_SCRIPT : OTHER_SCRIPT;
275
+ }
276
+ if (codePoint >= KHMER_BLOCK_START && codePoint <= KHMER_BLOCK_END) {
277
+ return matchesAt(KHMER, part, index) ? KHMER_SCRIPT : OTHER_SCRIPT;
278
+ }
279
+ return classifyOutsideCommonBlocks(part, index);
280
+ }
281
+ function classify(part, index, codePoint, current) {
282
+ if (codePoint < LOWEST_NGRAM_CODE_POINT) return OTHER_SCRIPT;
283
+ if (codePoint < SOUTHEAST_ASIAN_BLOCKS_END) return classifySoutheastAsian(part, index, codePoint);
284
+ if (codePoint === PROLONGED_SOUND_MARK) {
285
+ return current === HIRAGANA_SCRIPT ? HIRAGANA_SCRIPT : KATAKANA_SCRIPT;
286
+ }
287
+ if (codePoint >= HIRAGANA_BLOCK_START && codePoint <= HIRAGANA_BLOCK_END) return HIRAGANA_SCRIPT;
288
+ if (codePoint >= KATAKANA_BLOCK_START && codePoint <= KATAKANA_BLOCK_END) return KATAKANA_SCRIPT;
289
+ if (codePoint >= HAN_UNIFIED_START && codePoint <= HAN_UNIFIED_END) return HAN_SCRIPT;
290
+ if (codePoint >= HAN_EXTENSION_A_START && codePoint <= HAN_EXTENSION_A_END) return HAN_SCRIPT;
291
+ if (codePoint >= HANGUL_SYLLABLE_START && codePoint <= HANGUL_SYLLABLE_END) return HANGUL_SCRIPT;
292
+ if (codePoint >= HAN_COMPATIBILITY_START && codePoint <= HAN_COMPATIBILITY_END) return HAN_SCRIPT;
293
+ if (codePoint === IDEOGRAPHIC_ITERATION_MARK) return HAN_SCRIPT;
294
+ return classifyOutsideCommonBlocks(part, index);
295
+ }
296
+ function emitRun(part, offsets, kind, size, out) {
297
+ const count = offsets.length - 1;
298
+ if (count < 1) return;
299
+ if (kind === OTHER_SCRIPT || count <= size) {
300
+ out.push(part.slice(offsets[0], offsets[count]));
301
+ return;
302
+ }
303
+ for (let start = 0; start + size <= count; start++) {
304
+ out.push(part.slice(offsets[start], offsets[start + size]));
305
+ }
306
+ }
307
+ function expandNgrams(part, size, out) {
308
+ const length = part.length;
309
+ if (length === 0) return;
310
+ const offsets = [];
311
+ let kind = OTHER_SCRIPT;
312
+ let index = 0;
313
+ while (index < length) {
314
+ const codePoint = part.codePointAt(index);
315
+ if (codePoint === void 0) break;
316
+ const width = codePoint >= SUPPLEMENTARY_PLANE_START ? 2 : 1;
317
+ if (offsets.length > 0 && isMark(part, index, codePoint)) {
318
+ index += width;
319
+ continue;
320
+ }
321
+ const next = classify(part, index, codePoint, kind);
322
+ if (offsets.length > 0 && next !== kind) {
323
+ offsets.push(index);
324
+ emitRun(part, offsets, kind, size, out);
325
+ offsets.length = 0;
326
+ }
327
+ kind = next;
328
+ offsets.push(index);
329
+ index += width;
330
+ }
331
+ offsets.push(length);
332
+ emitRun(part, offsets, kind, size, out);
333
+ }
334
+
335
+ // src/core/tokenizer/tokenize.ts
336
+ var DEFAULT_SPLIT_PATTERN = /[^\p{L}\p{M}\p{N}_'-]+/u;
337
+ var DEFAULT_MIN_TOKEN_LENGTH = 1;
338
+ function resolveStopWords(language, override) {
339
+ if (!override) return language.stopWords;
340
+ if (override instanceof Set) return override;
341
+ return override(language.stopWords);
342
+ }
280
343
  function splitText(text, language) {
281
344
  const pattern = language.tokenizer?.splitPattern ?? DEFAULT_SPLIT_PATTERN;
282
- return text.split(pattern);
345
+ const parts = text.split(pattern);
346
+ const ngramSize = language.tokenizer?.ngramSize;
347
+ if (ngramSize === void 0 || ngramSize < 1) return parts;
348
+ const size = Math.floor(ngramSize);
349
+ const expanded = [];
350
+ for (const part of parts) {
351
+ expandNgrams(part, size, expanded);
352
+ }
353
+ return expanded;
283
354
  }
284
355
  var CHAR_APOSTROPHE = 39;
285
356
  var CHAR_S = 115;
@@ -293,12 +364,6 @@ function stripPossessive(token) {
293
364
  }
294
365
  return token;
295
366
  }
296
- function isAscii(text) {
297
- for (let i = 0; i < text.length; i++) {
298
- if (text.charCodeAt(i) > 127) return false;
299
- }
300
- return true;
301
- }
302
367
  function tokenize(text, language, options) {
303
368
  const {
304
369
  stem = true,
@@ -316,11 +381,11 @@ function tokenize(text, language, options) {
316
381
  originalTokens: originals
317
382
  };
318
383
  }
319
- const normalized = isAscii(text) ? text.toLowerCase() : text.normalize("NFC").toLowerCase();
384
+ const normalized = normalizeForSplitting(text);
320
385
  const rawParts = splitText(normalized, language);
321
386
  const minLength = language.tokenizer?.minTokenLength ?? DEFAULT_MIN_TOKEN_LENGTH;
322
387
  const effectiveDiacritics = removeDiacritics || (language.tokenizer?.normalizeDiacritics ?? false);
323
- const stemsTokens = stem && language.stemmer !== void 0;
388
+ const stemsTokens = stem && language.stemmer !== null;
324
389
  const stopWords = removeStopWords ? resolveStopWords(language, stopWordOverride) : /* @__PURE__ */ new Set();
325
390
  const stripPossessives = language.tokenizer?.stripPossessive ?? false;
326
391
  const tokens = [];
@@ -348,66 +413,287 @@ function tokenize(text, language, options) {
348
413
  }
349
414
  position++;
350
415
  }
351
- return { tokens, originalTokens, surfaces };
352
- }
353
- function* tokenizeIterator(text, language, options) {
354
- const {
355
- stem = true,
356
- removeStopWords = true,
357
- removeDiacritics = false,
358
- collectSurfaces = false,
359
- customTokenizer,
360
- stopWordOverride
361
- } = options ?? {};
362
- if (customTokenizer) {
363
- const customResult = customTokenizer.tokenize(text);
364
- for (const entry of customResult) {
365
- yield entry;
416
+ return { tokens, originalTokens, surfaces };
417
+ }
418
+ function* tokenizeIterator(text, language, options) {
419
+ const {
420
+ stem = true,
421
+ removeStopWords = true,
422
+ removeDiacritics = false,
423
+ collectSurfaces = false,
424
+ customTokenizer,
425
+ stopWordOverride
426
+ } = options ?? {};
427
+ if (customTokenizer) {
428
+ const customResult = customTokenizer.tokenize(text);
429
+ for (const entry of customResult) {
430
+ yield entry;
431
+ }
432
+ return;
433
+ }
434
+ const normalized = normalizeForSplitting(text);
435
+ const rawParts = splitText(normalized, language);
436
+ const minLength = language.tokenizer?.minTokenLength ?? DEFAULT_MIN_TOKEN_LENGTH;
437
+ const effectiveDiacritics = removeDiacritics || (language.tokenizer?.normalizeDiacritics ?? false);
438
+ const stemsTokens = stem && language.stemmer !== null;
439
+ const wantSurfaces = collectSurfaces && stemsTokens;
440
+ const stopWords = removeStopWords ? resolveStopWords(language, stopWordOverride) : /* @__PURE__ */ new Set();
441
+ const stripPossessives = language.tokenizer?.stripPossessive ?? false;
442
+ let position = 0;
443
+ for (const part of rawParts) {
444
+ const candidate = stripPossessives ? stripPossessive(part) : part;
445
+ if (candidate.length < minLength) {
446
+ position++;
447
+ continue;
448
+ }
449
+ if (stopWords.has(candidate)) {
450
+ position++;
451
+ continue;
452
+ }
453
+ const processed = transformToken(candidate, language, stem, effectiveDiacritics);
454
+ if (processed.length > 0) {
455
+ if (wantSurfaces) {
456
+ const surface = effectiveDiacritics ? transformToken(candidate, language, false, true) : candidate;
457
+ yield { token: processed, position, surface: surface === processed ? void 0 : surface };
458
+ } else {
459
+ yield { token: processed, position };
460
+ }
461
+ }
462
+ position++;
463
+ }
464
+ }
465
+
466
+ // src/partitioning/distributed-scoring.ts
467
+ function analysedQueryTokens(term, language, analysis) {
468
+ const { tokens } = tokenize(term, language, {
469
+ stem: true,
470
+ removeStopWords: true,
471
+ customTokenizer: analysis.customTokenizer,
472
+ stopWordOverride: analysis.stopWords
473
+ });
474
+ const unique = /* @__PURE__ */ new Set();
475
+ for (const { token } of tokens) {
476
+ unique.add(token);
477
+ }
478
+ return unique;
479
+ }
480
+ function copyNumericRecord(source) {
481
+ const copy = /* @__PURE__ */ Object.create(null);
482
+ for (const [key, value] of Object.entries(source)) {
483
+ if (typeof value === "number" && Number.isFinite(value)) {
484
+ copy[key] = value;
485
+ }
486
+ }
487
+ return copy;
488
+ }
489
+ function sanitizeGlobalStats(stats) {
490
+ return {
491
+ totalDocuments: typeof stats.totalDocuments === "number" && Number.isFinite(stats.totalDocuments) ? stats.totalDocuments : 0,
492
+ docFrequencies: copyNumericRecord(stats.docFrequencies),
493
+ totalFieldLengths: copyNumericRecord(stats.totalFieldLengths),
494
+ averageFieldLengths: copyNumericRecord(stats.averageFieldLengths)
495
+ };
496
+ }
497
+ function pruneStatsToQueryTerms(stats, term, language, analysis) {
498
+ const docFrequencies = /* @__PURE__ */ Object.create(null);
499
+ for (const token of analysedQueryTokens(term, language, analysis)) {
500
+ if (Object.hasOwn(stats.docFrequencies, token)) {
501
+ docFrequencies[token] = stats.docFrequencies[token];
502
+ }
503
+ }
504
+ return {
505
+ totalDocuments: stats.totalDocuments,
506
+ docFrequencies,
507
+ totalFieldLengths: stats.totalFieldLengths,
508
+ averageFieldLengths: stats.averageFieldLengths
509
+ };
510
+ }
511
+ function collectQueryTermStats(manager, term, language, analysis) {
512
+ const queryTokens = analysedQueryTokens(term, language, analysis);
513
+ let totalDocuments = 0;
514
+ const docFrequencies = /* @__PURE__ */ Object.create(null);
515
+ const totalFieldLengths = /* @__PURE__ */ Object.create(null);
516
+ for (const partition of manager.getAllPartitions()) {
517
+ const stats = partition.stats;
518
+ totalDocuments += stats.totalDocuments;
519
+ for (const [field, length] of Object.entries(stats.totalFieldLengths)) {
520
+ totalFieldLengths[field] = (totalFieldLengths[field] ?? 0) + length;
521
+ }
522
+ for (const token of queryTokens) {
523
+ if (Object.hasOwn(stats.docFrequencies, token)) {
524
+ docFrequencies[token] = (docFrequencies[token] ?? 0) + stats.docFrequencies[token];
525
+ }
526
+ }
527
+ }
528
+ const averageFieldLengths = /* @__PURE__ */ Object.create(null);
529
+ if (totalDocuments > 0) {
530
+ for (const [field, totalLength] of Object.entries(totalFieldLengths)) {
531
+ averageFieldLengths[field] = totalLength / totalDocuments;
532
+ }
533
+ }
534
+ return { totalDocuments, docFrequencies, totalFieldLengths, averageFieldLengths };
535
+ }
536
+ function mergePartitionStats(statsArray) {
537
+ let totalDocuments = 0;
538
+ const docFrequencies = /* @__PURE__ */ Object.create(null);
539
+ const totalFieldLengths = /* @__PURE__ */ Object.create(null);
540
+ for (const stats of statsArray) {
541
+ totalDocuments += stats.totalDocuments;
542
+ for (const [term, freq] of Object.entries(stats.docFrequencies)) {
543
+ docFrequencies[term] = (docFrequencies[term] ?? 0) + freq;
544
+ }
545
+ for (const [field, length] of Object.entries(stats.totalFieldLengths)) {
546
+ totalFieldLengths[field] = (totalFieldLengths[field] ?? 0) + length;
547
+ }
548
+ }
549
+ const averageFieldLengths = /* @__PURE__ */ Object.create(null);
550
+ if (totalDocuments > 0) {
551
+ for (const [field, totalLength] of Object.entries(totalFieldLengths)) {
552
+ averageFieldLengths[field] = totalLength / totalDocuments;
553
+ }
554
+ }
555
+ return {
556
+ totalDocuments,
557
+ docFrequencies,
558
+ totalFieldLengths,
559
+ averageFieldLengths
560
+ };
561
+ }
562
+
563
+ // src/search/facets.ts
564
+ function mergeFacets(partitionFacets) {
565
+ const merged = /* @__PURE__ */ new Map();
566
+ for (const partition of partitionFacets) {
567
+ for (const [field, facetResult] of Object.entries(partition)) {
568
+ let fieldMap = merged.get(field);
569
+ if (!fieldMap) {
570
+ fieldMap = /* @__PURE__ */ new Map();
571
+ merged.set(field, fieldMap);
572
+ }
573
+ for (const [value, count] of Object.entries(facetResult.values)) {
574
+ fieldMap.set(value, (fieldMap.get(value) ?? 0) + count);
575
+ }
576
+ }
577
+ }
578
+ const result = {};
579
+ for (const [field, valueMap] of merged) {
580
+ const values = {};
581
+ for (const [value, count] of valueMap) {
582
+ values[value] = count;
583
+ }
584
+ result[field] = { values, count: valueMap.size };
585
+ }
586
+ return result;
587
+ }
588
+
589
+ // src/core/bitset.ts
590
+ function createBitSet(capacity) {
591
+ return new Uint32Array(capacity + 31 >>> 5 || 1);
592
+ }
593
+ function bitsetSet(bs, index) {
594
+ const word = index >>> 5;
595
+ if (word < bs.length) {
596
+ bs[word] |= 1 << (index & 31);
597
+ }
598
+ }
599
+ function bitsetHas(bs, index) {
600
+ const word = index >>> 5;
601
+ if (word >= bs.length) return false;
602
+ return (bs[word] & 1 << (index & 31)) !== 0;
603
+ }
604
+ function bitsetAnd(a, b) {
605
+ const len = Math.min(a.length, b.length);
606
+ const result = new Uint32Array(len);
607
+ for (let i = 0; i < len; i++) {
608
+ result[i] = a[i] & b[i];
609
+ }
610
+ return result;
611
+ }
612
+ function bitsetOr(a, b) {
613
+ const longer = a.length >= b.length ? a : b;
614
+ const shorter = a.length >= b.length ? b : a;
615
+ const result = new Uint32Array(longer.length);
616
+ for (let i = 0; i < shorter.length; i++) {
617
+ result[i] = longer[i] | shorter[i];
618
+ }
619
+ for (let i = shorter.length; i < longer.length; i++) {
620
+ result[i] = longer[i];
621
+ }
622
+ return result;
623
+ }
624
+ function bitsetNot(bs, capacity) {
625
+ const result = new Uint32Array(bs.length);
626
+ const fullWords = capacity >>> 5;
627
+ for (let i = 0; i < fullWords; i++) {
628
+ result[i] = ~bs[i];
629
+ }
630
+ const remainder = capacity & 31;
631
+ if (remainder > 0 && fullWords < bs.length) {
632
+ result[fullWords] = ~bs[fullWords] & (1 << remainder) - 1;
633
+ }
634
+ return result;
635
+ }
636
+ function bitsetIsEmpty(bs) {
637
+ for (let i = 0; i < bs.length; i++) {
638
+ if (bs[i] !== 0) return false;
639
+ }
640
+ return true;
641
+ }
642
+ function bitsetFromSet(set, capacity) {
643
+ const bs = createBitSet(capacity);
644
+ for (const id of set) {
645
+ bs[id >>> 5] |= 1 << (id & 31);
646
+ }
647
+ return bs;
648
+ }
649
+
650
+ // src/core/fuzzy.ts
651
+ function boundedLevenshtein(a, b, tolerance) {
652
+ if (tolerance < 0) return { distance: -1, withinTolerance: false };
653
+ if (a === b) return { distance: 0, withinTolerance: true };
654
+ const m = a.length;
655
+ const n = b.length;
656
+ if (m === 0) {
657
+ return n <= tolerance ? { distance: n, withinTolerance: true } : { distance: n, withinTolerance: false };
658
+ }
659
+ if (n === 0) {
660
+ return m <= tolerance ? { distance: m, withinTolerance: true } : { distance: m, withinTolerance: false };
661
+ }
662
+ if (a.startsWith(b)) {
663
+ const diff2 = m - n;
664
+ return diff2 <= tolerance ? { distance: diff2, withinTolerance: true } : { distance: diff2, withinTolerance: false };
665
+ }
666
+ if (b.startsWith(a)) {
667
+ return { distance: 0, withinTolerance: true };
668
+ }
669
+ const diff = Math.abs(m - n);
670
+ if (diff > tolerance) return { distance: -1, withinTolerance: false };
671
+ const matrix = [];
672
+ for (let i = 0; i <= m; i++) {
673
+ matrix[i] = [i];
674
+ for (let j = 1; j <= n; j++) {
675
+ matrix[i][j] = i === 0 ? j : 0;
366
676
  }
367
- return;
368
677
  }
369
- const normalized = isAscii(text) ? text.toLowerCase() : text.normalize("NFC").toLowerCase();
370
- const rawParts = splitText(normalized, language);
371
- const minLength = language.tokenizer?.minTokenLength ?? DEFAULT_MIN_TOKEN_LENGTH;
372
- const effectiveDiacritics = removeDiacritics || (language.tokenizer?.normalizeDiacritics ?? false);
373
- const stemsTokens = stem && language.stemmer !== void 0;
374
- const wantSurfaces = collectSurfaces && stemsTokens;
375
- const stopWords = removeStopWords ? resolveStopWords(language, stopWordOverride) : /* @__PURE__ */ new Set();
376
- const stripPossessives = language.tokenizer?.stripPossessive ?? false;
377
- let position = 0;
378
- for (const part of rawParts) {
379
- const candidate = stripPossessives ? stripPossessive(part) : part;
380
- if (candidate.length < minLength) {
381
- position++;
382
- continue;
383
- }
384
- if (stopWords.has(candidate)) {
385
- position++;
386
- continue;
387
- }
388
- const processed = transformToken(candidate, language, stem, effectiveDiacritics);
389
- if (processed.length > 0) {
390
- if (wantSurfaces) {
391
- const surface = effectiveDiacritics ? transformToken(candidate, language, false, true) : candidate;
392
- yield { token: processed, position, surface: surface === processed ? void 0 : surface };
678
+ for (let i = 1; i <= m; i++) {
679
+ let rowMin = Infinity;
680
+ for (let j = 1; j <= n; j++) {
681
+ if (a[i - 1] === b[j - 1]) {
682
+ matrix[i][j] = matrix[i - 1][j - 1];
393
683
  } else {
394
- yield { token: processed, position };
684
+ matrix[i][j] = Math.min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, matrix[i - 1][j - 1] + 1);
395
685
  }
686
+ rowMin = Math.min(rowMin, matrix[i][j]);
687
+ }
688
+ if (rowMin > tolerance) {
689
+ return { distance: -1, withinTolerance: false };
396
690
  }
397
- position++;
398
691
  }
399
- }
400
- function clearNormalizationCache() {
401
- normalizationCache.clear();
402
- normalizationCacheSize = 0;
403
- cachedBucket = null;
404
- }
405
- function resetNormalizationCache() {
406
- clearNormalizationCache();
407
- maxCacheSize = computeDefaultCacheSize();
408
- }
409
- function getNormalizationCacheSize() {
410
- return normalizationCacheSize;
692
+ const finalDistance = matrix[m][n];
693
+ return {
694
+ distance: finalDistance,
695
+ withinTolerance: finalDistance <= tolerance
696
+ };
411
697
  }
412
698
 
413
699
  // src/schema/validator/shared.ts
@@ -979,49 +1265,6 @@ function queryTermSatisfied(queryToken, indexTokens, tolerance, exact) {
979
1265
  return false;
980
1266
  }
981
1267
 
982
- // src/partitioning/distributed-scoring.ts
983
- function collectGlobalStats(manager) {
984
- const aggregate = manager.getAggregateStats();
985
- const averageFieldLengths = {};
986
- if (aggregate.totalDocuments > 0) {
987
- for (const [field, totalLength] of Object.entries(aggregate.totalFieldLengths)) {
988
- averageFieldLengths[field] = totalLength / aggregate.totalDocuments;
989
- }
990
- }
991
- return {
992
- totalDocuments: aggregate.totalDocuments,
993
- docFrequencies: aggregate.docFrequencies,
994
- totalFieldLengths: aggregate.totalFieldLengths,
995
- averageFieldLengths
996
- };
997
- }
998
- function mergePartitionStats(statsArray) {
999
- let totalDocuments = 0;
1000
- const docFrequencies = {};
1001
- const totalFieldLengths = {};
1002
- for (const stats of statsArray) {
1003
- totalDocuments += stats.totalDocuments;
1004
- for (const [term, freq] of Object.entries(stats.docFrequencies)) {
1005
- docFrequencies[term] = (docFrequencies[term] ?? 0) + freq;
1006
- }
1007
- for (const [field, length] of Object.entries(stats.totalFieldLengths)) {
1008
- totalFieldLengths[field] = (totalFieldLengths[field] ?? 0) + length;
1009
- }
1010
- }
1011
- const averageFieldLengths = {};
1012
- if (totalDocuments > 0) {
1013
- for (const [field, totalLength] of Object.entries(totalFieldLengths)) {
1014
- averageFieldLengths[field] = totalLength / totalDocuments;
1015
- }
1016
- }
1017
- return {
1018
- totalDocuments,
1019
- docFrequencies,
1020
- totalFieldLengths,
1021
- averageFieldLengths
1022
- };
1023
- }
1024
-
1025
1268
  // src/partitioning/fan-out.ts
1026
1269
  async function fanOutQuery(manager, params, language, schema, config, searchOptions) {
1027
1270
  const partitions = config.partitionIds ? config.partitionIds.map((id) => manager.partitionAt(id)).filter((p) => p !== void 0) : manager.getAllPartitions();
@@ -1031,7 +1274,12 @@ async function fanOutQuery(manager, params, language, schema, config, searchOpti
1031
1274
  let globalStats;
1032
1275
  const effectiveMode = resolveEffectiveMode(config);
1033
1276
  if (effectiveMode === "dfs") {
1034
- globalStats = collectGlobalStats(manager);
1277
+ if (params.term !== void 0 && params.term.trim().length > 0) {
1278
+ globalStats = collectQueryTermStats(manager, params.term, language, {
1279
+ stopWords: searchOptions?.stopWords,
1280
+ customTokenizer: searchOptions?.customTokenizer
1281
+ });
1282
+ }
1035
1283
  } else if (effectiveMode === "broadcast") {
1036
1284
  globalStats = config.globalStats;
1037
1285
  }
@@ -1212,6 +1460,232 @@ function heapPop(heap) {
1212
1460
  }
1213
1461
  return top;
1214
1462
  }
1463
+
1464
+ // src/analysis/registry.ts
1465
+ var tokenizers = /* @__PURE__ */ new Map();
1466
+ var stopWordSets = /* @__PURE__ */ new Map();
1467
+ function requireName(name, kind) {
1468
+ if (typeof name !== "string" || name.trim().length === 0) {
1469
+ throw new NarsilError(ErrorCodes.CONFIG_INVALID, `A registered ${kind} needs a non-empty name`, { name });
1470
+ }
1471
+ }
1472
+ function registerTokenizer(name, tokenizer) {
1473
+ requireName(name, "tokenizer");
1474
+ if (typeof tokenizer?.tokenize !== "function") {
1475
+ throw new NarsilError(ErrorCodes.CONFIG_INVALID, `Tokenizer "${name}" needs a tokenize method`, { tokenizer: name });
1476
+ }
1477
+ tokenizers.set(name, tokenizer);
1478
+ }
1479
+ function registerStopWords(name, stopWords) {
1480
+ requireName(name, "stop word set");
1481
+ if (!(stopWords instanceof Set) && typeof stopWords !== "function") {
1482
+ throw new NarsilError(
1483
+ ErrorCodes.CONFIG_INVALID,
1484
+ `Stop word set "${name}" needs a Set of words or a function returning one`,
1485
+ { stopWords: name }
1486
+ );
1487
+ }
1488
+ stopWordSets.set(name, stopWords);
1489
+ }
1490
+ function hasTokenizer(name) {
1491
+ return tokenizers.has(name);
1492
+ }
1493
+ function hasStopWords(name) {
1494
+ return stopWordSets.has(name);
1495
+ }
1496
+ function getTokenizer(name) {
1497
+ const tokenizer = tokenizers.get(name);
1498
+ if (!tokenizer) {
1499
+ throw new NarsilError(ErrorCodes.CONFIG_INVALID, `Tokenizer "${name}" is not registered on this engine`, {
1500
+ tokenizer: name,
1501
+ available: [...tokenizers.keys()]
1502
+ });
1503
+ }
1504
+ return tokenizer;
1505
+ }
1506
+ function getStopWords(name) {
1507
+ const stopWords = stopWordSets.get(name);
1508
+ if (!stopWords) {
1509
+ throw new NarsilError(ErrorCodes.CONFIG_INVALID, `Stop word set "${name}" is not registered on this engine`, {
1510
+ stopWords: name,
1511
+ available: [...stopWordSets.keys()]
1512
+ });
1513
+ }
1514
+ return stopWords;
1515
+ }
1516
+ function resolveIndexAnalysis(config) {
1517
+ return {
1518
+ stopWords: typeof config.stopWords === "string" ? getStopWords(config.stopWords) : config.stopWords,
1519
+ customTokenizer: typeof config.tokenizer === "string" ? getTokenizer(config.tokenizer) : config.tokenizer
1520
+ };
1521
+ }
1522
+ function metadataToWire(meta) {
1523
+ const wire = {
1524
+ index_name: meta.indexName,
1525
+ schema: meta.schema,
1526
+ language: meta.language,
1527
+ partition_count: meta.partitionCount,
1528
+ bm25_params: meta.bm25Params,
1529
+ created_at: meta.createdAt,
1530
+ engine_version: meta.engineVersion
1531
+ };
1532
+ if (meta.vectorFields) {
1533
+ wire.vector_fields = meta.vectorFields;
1534
+ }
1535
+ if (meta.embedding) {
1536
+ wire.embedding = meta.embedding.adapter !== void 0 ? { adapter: meta.embedding.adapter, fields: meta.embedding.fields } : { fields: meta.embedding.fields };
1537
+ }
1538
+ if (meta.surfaceForms === true) {
1539
+ wire.surface_forms_enabled = true;
1540
+ }
1541
+ if (meta.analysisRevision !== void 0) {
1542
+ wire.analysis_revision = meta.analysisRevision;
1543
+ }
1544
+ if (meta.tokenizer !== void 0) {
1545
+ wire.tokenizer = meta.tokenizer;
1546
+ }
1547
+ if (meta.stopWords !== void 0) {
1548
+ wire.stop_words = meta.stopWords;
1549
+ }
1550
+ if (meta.stopWordList !== void 0) {
1551
+ wire.stop_word_list = meta.stopWordList;
1552
+ }
1553
+ if (meta.partitionLimits !== void 0) {
1554
+ wire.partition_limits = {
1555
+ ...meta.partitionLimits.maxDocsPerPartition !== void 0 ? { max_docs_per_partition: meta.partitionLimits.maxDocsPerPartition } : {},
1556
+ ...meta.partitionLimits.maxPartitions !== void 0 ? { max_partitions: meta.partitionLimits.maxPartitions } : {},
1557
+ ...meta.partitionLimits.watermark !== void 0 ? { watermark: meta.partitionLimits.watermark } : {}
1558
+ };
1559
+ }
1560
+ if (meta.defaultScoring !== void 0) {
1561
+ wire.default_scoring = meta.defaultScoring;
1562
+ }
1563
+ if (meta.trackPositions !== void 0) {
1564
+ wire.track_positions = meta.trackPositions;
1565
+ }
1566
+ if (meta.strict !== void 0) {
1567
+ wire.strict = meta.strict;
1568
+ }
1569
+ if (meta.required !== void 0) {
1570
+ wire.required = meta.required;
1571
+ }
1572
+ if (meta.vectorPromotion !== void 0) {
1573
+ const promotion = meta.vectorPromotion;
1574
+ wire.vector_promotion = {
1575
+ ...promotion.threshold !== void 0 ? { threshold: promotion.threshold } : {},
1576
+ ...promotion.filterThreshold !== void 0 ? { filter_threshold: promotion.filterThreshold } : {},
1577
+ ...promotion.hnswConfig !== void 0 ? {
1578
+ hnsw_config: {
1579
+ ...promotion.hnswConfig.m !== void 0 ? { m: promotion.hnswConfig.m } : {},
1580
+ ...promotion.hnswConfig.efConstruction !== void 0 ? { ef_construction: promotion.hnswConfig.efConstruction } : {},
1581
+ ...promotion.hnswConfig.metric !== void 0 ? { metric: promotion.hnswConfig.metric } : {}
1582
+ }
1583
+ } : {},
1584
+ ...promotion.quantization !== void 0 ? { quantization: promotion.quantization } : {}
1585
+ };
1586
+ }
1587
+ return wire;
1588
+ }
1589
+ function wireToMetadata(raw) {
1590
+ const meta = {
1591
+ indexName: raw.index_name,
1592
+ schema: raw.schema ?? {},
1593
+ language: raw.language ?? "english",
1594
+ partitionCount: raw.partition_count ?? 1,
1595
+ bm25Params: raw.bm25_params ?? { k1: 1.2, b: 0.75 },
1596
+ createdAt: raw.created_at ?? 0,
1597
+ engineVersion: raw.engine_version ?? "0.0.0"
1598
+ };
1599
+ if (raw.vector_fields) {
1600
+ meta.vectorFields = raw.vector_fields;
1601
+ }
1602
+ if (raw.embedding && typeof raw.embedding === "object" && typeof raw.embedding.fields === "object") {
1603
+ meta.embedding = typeof raw.embedding.adapter === "string" ? { adapter: raw.embedding.adapter, fields: raw.embedding.fields } : { fields: raw.embedding.fields };
1604
+ }
1605
+ if (raw.surface_forms_enabled === true) {
1606
+ meta.surfaceForms = true;
1607
+ }
1608
+ if (typeof raw.analysis_revision === "string") {
1609
+ meta.analysisRevision = raw.analysis_revision;
1610
+ }
1611
+ if (typeof raw.tokenizer === "string") {
1612
+ meta.tokenizer = raw.tokenizer;
1613
+ }
1614
+ if (typeof raw.stop_words === "string") {
1615
+ meta.stopWords = raw.stop_words;
1616
+ }
1617
+ if (Array.isArray(raw.stop_word_list) && raw.stop_word_list.every((word) => typeof word === "string")) {
1618
+ meta.stopWordList = raw.stop_word_list;
1619
+ }
1620
+ if (raw.partition_limits && typeof raw.partition_limits === "object") {
1621
+ const limits = {
1622
+ ...isPositiveInteger2(raw.partition_limits.max_docs_per_partition) ? { maxDocsPerPartition: raw.partition_limits.max_docs_per_partition } : {},
1623
+ ...isPositiveInteger2(raw.partition_limits.max_partitions) ? { maxPartitions: raw.partition_limits.max_partitions } : {},
1624
+ ...isWatermarkFraction(raw.partition_limits.watermark) ? { watermark: raw.partition_limits.watermark } : {}
1625
+ };
1626
+ if (Object.keys(limits).length > 0) {
1627
+ meta.partitionLimits = limits;
1628
+ }
1629
+ }
1630
+ if (raw.default_scoring === "local" || raw.default_scoring === "dfs" || raw.default_scoring === "broadcast") {
1631
+ meta.defaultScoring = raw.default_scoring;
1632
+ }
1633
+ if (typeof raw.track_positions === "boolean") {
1634
+ meta.trackPositions = raw.track_positions;
1635
+ }
1636
+ if (typeof raw.strict === "boolean") {
1637
+ meta.strict = raw.strict;
1638
+ }
1639
+ if (Array.isArray(raw.required) && raw.required.every((field) => typeof field === "string")) {
1640
+ meta.required = raw.required;
1641
+ }
1642
+ if (raw.vector_promotion && typeof raw.vector_promotion === "object") {
1643
+ const promotion = raw.vector_promotion;
1644
+ const hnsw = promotion.hnsw_config && typeof promotion.hnsw_config === "object" ? promotion.hnsw_config : void 0;
1645
+ const metric = hnsw?.metric;
1646
+ const restored = {};
1647
+ const hnswConfig = {};
1648
+ if (isPositiveInteger2(hnsw?.m)) {
1649
+ hnswConfig.m = hnsw?.m;
1650
+ }
1651
+ if (isPositiveInteger2(hnsw?.ef_construction)) {
1652
+ hnswConfig.efConstruction = hnsw?.ef_construction;
1653
+ }
1654
+ if (metric === "cosine" || metric === "dotProduct" || metric === "euclidean") {
1655
+ hnswConfig.metric = metric;
1656
+ }
1657
+ if (isPositiveInteger2(promotion.threshold)) {
1658
+ restored.threshold = promotion.threshold;
1659
+ }
1660
+ if (isPositiveInteger2(promotion.filter_threshold)) {
1661
+ restored.filterThreshold = promotion.filter_threshold;
1662
+ }
1663
+ if (Object.keys(hnswConfig).length > 0) {
1664
+ restored.hnswConfig = hnswConfig;
1665
+ }
1666
+ if (promotion.quantization === "sq8" || promotion.quantization === "none") {
1667
+ restored.quantization = promotion.quantization;
1668
+ }
1669
+ if (Object.keys(restored).length > 0) {
1670
+ meta.vectorPromotion = restored;
1671
+ }
1672
+ }
1673
+ return meta;
1674
+ }
1675
+ function isPositiveInteger2(value) {
1676
+ return typeof value === "number" && Number.isInteger(value) && value >= 1;
1677
+ }
1678
+ function isWatermarkFraction(value) {
1679
+ return typeof value === "number" && value > 0 && value <= 1;
1680
+ }
1681
+ function serializeMetadata(meta) {
1682
+ const wire = metadataToWire(meta);
1683
+ return encode(wire);
1684
+ }
1685
+ function deserializeMetadata(data) {
1686
+ const raw = decode(data);
1687
+ return wireToMetadata(raw);
1688
+ }
1215
1689
  function sanitizeSurfaceForms(raw) {
1216
1690
  if (raw === void 0 || raw === null || typeof raw !== "object" || Array.isArray(raw)) return void 0;
1217
1691
  const result = /* @__PURE__ */ Object.create(null);
@@ -1306,68 +1780,18 @@ function wireToPartition(raw) {
1306
1780
  geopoint
1307
1781
  },
1308
1782
  surfaceForms: sanitizeSurfaceForms(raw.surface_forms),
1309
- vectorData,
1310
- statistics: {
1311
- totalDocuments: raw.statistics?.total_documents ?? 0,
1312
- totalFieldLengths: raw.statistics?.total_field_lengths ?? {},
1313
- averageFieldLengths: raw.statistics?.average_field_lengths ?? {},
1314
- docFrequencies: raw.statistics?.doc_frequencies ?? {}
1315
- }
1316
- };
1317
- }
1318
- function deserializePayloadV1(data) {
1319
- const raw = decode(data);
1320
- return wireToPartition(raw);
1321
- }
1322
- function metadataToWire(meta) {
1323
- const wire = {
1324
- index_name: meta.indexName,
1325
- schema: meta.schema,
1326
- language: meta.language,
1327
- partition_count: meta.partitionCount,
1328
- bm25_params: meta.bm25Params,
1329
- created_at: meta.createdAt,
1330
- engine_version: meta.engineVersion
1331
- };
1332
- if (meta.vectorFields) {
1333
- wire.vector_fields = meta.vectorFields;
1334
- }
1335
- if (meta.embedding) {
1336
- wire.embedding = meta.embedding.adapter !== void 0 ? { adapter: meta.embedding.adapter, fields: meta.embedding.fields } : { fields: meta.embedding.fields };
1337
- }
1338
- if (meta.surfaceForms === true) {
1339
- wire.surface_forms_enabled = true;
1340
- }
1341
- return wire;
1342
- }
1343
- function wireToMetadata(raw) {
1344
- const meta = {
1345
- indexName: raw.index_name,
1346
- schema: raw.schema ?? {},
1347
- language: raw.language ?? "english",
1348
- partitionCount: raw.partition_count ?? 1,
1349
- bm25Params: raw.bm25_params ?? { k1: 1.2, b: 0.75 },
1350
- createdAt: raw.created_at ?? 0,
1351
- engineVersion: raw.engine_version ?? "0.0.0"
1783
+ vectorData,
1784
+ statistics: {
1785
+ totalDocuments: raw.statistics?.total_documents ?? 0,
1786
+ totalFieldLengths: raw.statistics?.total_field_lengths ?? {},
1787
+ averageFieldLengths: raw.statistics?.average_field_lengths ?? {},
1788
+ docFrequencies: raw.statistics?.doc_frequencies ?? {}
1789
+ }
1352
1790
  };
1353
- if (raw.vector_fields) {
1354
- meta.vectorFields = raw.vector_fields;
1355
- }
1356
- if (raw.embedding && typeof raw.embedding === "object" && typeof raw.embedding.fields === "object") {
1357
- meta.embedding = typeof raw.embedding.adapter === "string" ? { adapter: raw.embedding.adapter, fields: raw.embedding.fields } : { fields: raw.embedding.fields };
1358
- }
1359
- if (raw.surface_forms_enabled === true) {
1360
- meta.surfaceForms = true;
1361
- }
1362
- return meta;
1363
- }
1364
- function serializeMetadata(meta) {
1365
- const wire = metadataToWire(meta);
1366
- return encode(wire);
1367
1791
  }
1368
- function deserializeMetadata(data) {
1792
+ function deserializePayloadV1(data) {
1369
1793
  const raw = decode(data);
1370
- return wireToMetadata(raw);
1794
+ return wireToPartition(raw);
1371
1795
  }
1372
1796
 
1373
1797
  // src/serialization/payload-v2.ts
@@ -1571,10 +1995,77 @@ function createDocumentStore() {
1571
1995
  };
1572
1996
  }
1573
1997
 
1574
- // src/core/inverted-index.ts
1998
+ // src/core/posting-list.ts
1575
1999
  var INITIAL_CAPACITY = 8;
1576
2000
  var MAX_TERM_FREQUENCY = 65535;
1577
2001
  var COMPACTION_THRESHOLD = 0.3;
2002
+ function createPostingList() {
2003
+ return {
2004
+ length: 0,
2005
+ docIds: [],
2006
+ termFrequencies: new Uint16Array(INITIAL_CAPACITY),
2007
+ fieldNameIndices: new Uint8Array(INITIAL_CAPACITY),
2008
+ positions: null,
2009
+ docIdSet: /* @__PURE__ */ new Set(),
2010
+ deletedDocs: /* @__PURE__ */ new Set(),
2011
+ totalTermFrequency: 0
2012
+ };
2013
+ }
2014
+ function growTypedArrays(list) {
2015
+ const newCap = list.termFrequencies.length * 2;
2016
+ const newTF = new Uint16Array(newCap);
2017
+ newTF.set(list.termFrequencies);
2018
+ list.termFrequencies = newTF;
2019
+ const newFNI = new Uint8Array(newCap);
2020
+ newFNI.set(list.fieldNameIndices);
2021
+ list.fieldNameIndices = newFNI;
2022
+ }
2023
+ function compactList(list) {
2024
+ if (list.deletedDocs.size === 0) return;
2025
+ let writeIdx = 0;
2026
+ for (let i = 0; i < list.length; i++) {
2027
+ if (!list.deletedDocs.has(list.docIds[i])) {
2028
+ if (writeIdx !== i) {
2029
+ list.docIds[writeIdx] = list.docIds[i];
2030
+ list.termFrequencies[writeIdx] = list.termFrequencies[i];
2031
+ list.fieldNameIndices[writeIdx] = list.fieldNameIndices[i];
2032
+ if (list.positions) {
2033
+ list.positions[writeIdx] = list.positions[i];
2034
+ }
2035
+ }
2036
+ writeIdx++;
2037
+ } else {
2038
+ list.totalTermFrequency -= list.termFrequencies[i];
2039
+ }
2040
+ }
2041
+ list.docIds.length = writeIdx;
2042
+ if (list.positions) list.positions.length = writeIdx;
2043
+ list.length = writeIdx;
2044
+ list.deletedDocs.clear();
2045
+ }
2046
+ function compactDocEntries(list, internalId) {
2047
+ let writeIdx = 0;
2048
+ for (let i = 0; i < list.length; i++) {
2049
+ if (list.docIds[i] !== internalId) {
2050
+ if (writeIdx !== i) {
2051
+ list.docIds[writeIdx] = list.docIds[i];
2052
+ list.termFrequencies[writeIdx] = list.termFrequencies[i];
2053
+ list.fieldNameIndices[writeIdx] = list.fieldNameIndices[i];
2054
+ if (list.positions) {
2055
+ list.positions[writeIdx] = list.positions[i];
2056
+ }
2057
+ }
2058
+ writeIdx++;
2059
+ } else {
2060
+ list.totalTermFrequency -= list.termFrequencies[i];
2061
+ }
2062
+ }
2063
+ list.docIds.length = writeIdx;
2064
+ if (list.positions) list.positions.length = writeIdx;
2065
+ list.length = writeIdx;
2066
+ }
2067
+
2068
+ // src/core/inverted-index.ts
1578
2069
  function createInvertedIndex(fieldNameTable) {
1579
2070
  const index = /* @__PURE__ */ new Map();
1580
2071
  const charBuckets = /* @__PURE__ */ new Map();
@@ -1619,74 +2110,12 @@ function createInvertedIndex(fieldNameTable) {
1619
2110
  function getOrCreateList(token) {
1620
2111
  let list = index.get(token);
1621
2112
  if (!list) {
1622
- list = {
1623
- length: 0,
1624
- docIds: [],
1625
- termFrequencies: new Uint16Array(INITIAL_CAPACITY),
1626
- fieldNameIndices: new Uint8Array(INITIAL_CAPACITY),
1627
- positions: null,
1628
- docIdSet: /* @__PURE__ */ new Set(),
1629
- deletedDocs: /* @__PURE__ */ new Set(),
1630
- totalTermFrequency: 0
1631
- };
2113
+ list = createPostingList();
1632
2114
  index.set(token, list);
1633
2115
  trackToken(token);
1634
2116
  }
1635
2117
  return list;
1636
2118
  }
1637
- function growTypedArrays(list) {
1638
- const newCap = list.termFrequencies.length * 2;
1639
- const newTF = new Uint16Array(newCap);
1640
- newTF.set(list.termFrequencies);
1641
- list.termFrequencies = newTF;
1642
- const newFNI = new Uint8Array(newCap);
1643
- newFNI.set(list.fieldNameIndices);
1644
- list.fieldNameIndices = newFNI;
1645
- }
1646
- function compactList(list) {
1647
- if (list.deletedDocs.size === 0) return;
1648
- let writeIdx = 0;
1649
- for (let i = 0; i < list.length; i++) {
1650
- if (!list.deletedDocs.has(list.docIds[i])) {
1651
- if (writeIdx !== i) {
1652
- list.docIds[writeIdx] = list.docIds[i];
1653
- list.termFrequencies[writeIdx] = list.termFrequencies[i];
1654
- list.fieldNameIndices[writeIdx] = list.fieldNameIndices[i];
1655
- if (list.positions) {
1656
- list.positions[writeIdx] = list.positions[i];
1657
- }
1658
- }
1659
- writeIdx++;
1660
- } else {
1661
- list.totalTermFrequency -= list.termFrequencies[i];
1662
- }
1663
- }
1664
- list.docIds.length = writeIdx;
1665
- if (list.positions) list.positions.length = writeIdx;
1666
- list.length = writeIdx;
1667
- list.deletedDocs.clear();
1668
- }
1669
- function compactDocEntries(list, internalId) {
1670
- let writeIdx = 0;
1671
- for (let i = 0; i < list.length; i++) {
1672
- if (list.docIds[i] !== internalId) {
1673
- if (writeIdx !== i) {
1674
- list.docIds[writeIdx] = list.docIds[i];
1675
- list.termFrequencies[writeIdx] = list.termFrequencies[i];
1676
- list.fieldNameIndices[writeIdx] = list.fieldNameIndices[i];
1677
- if (list.positions) {
1678
- list.positions[writeIdx] = list.positions[i];
1679
- }
1680
- }
1681
- writeIdx++;
1682
- } else {
1683
- list.totalTermFrequency -= list.termFrequencies[i];
1684
- }
1685
- }
1686
- list.docIds.length = writeIdx;
1687
- if (list.positions) list.positions.length = writeIdx;
1688
- list.length = writeIdx;
1689
- }
1690
2119
  return {
1691
2120
  insert(token, internalId, termFrequency, fieldNameIndex, positions) {
1692
2121
  const list = getOrCreateList(token);
@@ -3489,6 +3918,64 @@ function applyPartitionFiltersBitset(state, filters, schema) {
3489
3918
  return evaluateFilters(filters, context);
3490
3919
  }
3491
3920
 
3921
+ // src/core/partition/rebuild.ts
3922
+ var EMPTY_STATS = {
3923
+ totalDocuments: 0,
3924
+ totalFieldLengths: {},
3925
+ averageFieldLengths: {},
3926
+ docFrequencies: {}
3927
+ };
3928
+ function textFieldsOf(flatSchema) {
3929
+ return Object.entries(flatSchema).filter(([, fieldType]) => fieldType === "string" || fieldType === "string[]");
3930
+ }
3931
+ function rebuildTextIndex(state, schema, language, options) {
3932
+ const textFields = textFieldsOf(getFlatSchema(state, schema));
3933
+ state.invertedIdx.clear();
3934
+ state.surfaceRegistry.clear();
3935
+ state.stats.deserialize(EMPTY_STATS);
3936
+ state.invertedIdx.beginBatch();
3937
+ try {
3938
+ for (const [docId, stored] of state.docStore.all()) {
3939
+ const internalId = state.docStore.getInternalId(docId);
3940
+ if (internalId === void 0) continue;
3941
+ const fields = stored.fields;
3942
+ const fieldLengths = {};
3943
+ const tokensByField = {};
3944
+ for (const [fieldPath, fieldType] of textFields) {
3945
+ const value = getNestedValue(fields, fieldPath);
3946
+ if (value === void 0 || value === null) continue;
3947
+ if (fieldType === "string") {
3948
+ indexStringField(
3949
+ state,
3950
+ internalId,
3951
+ fieldPath,
3952
+ value,
3953
+ language,
3954
+ options,
3955
+ fieldLengths,
3956
+ tokensByField
3957
+ );
3958
+ } else {
3959
+ indexStringArrayField(
3960
+ state,
3961
+ internalId,
3962
+ fieldPath,
3963
+ value,
3964
+ language,
3965
+ options,
3966
+ fieldLengths,
3967
+ tokensByField
3968
+ );
3969
+ }
3970
+ }
3971
+ state.docStore.storeRef(docId, stored.fields, fieldLengths);
3972
+ state.stats.addDocument(fieldLengths, tokensByField);
3973
+ }
3974
+ } finally {
3975
+ state.invertedIdx.endBatch();
3976
+ }
3977
+ }
3978
+
3492
3979
  // src/core/scorer.ts
3493
3980
  var DEFAULT_K1 = 1.2;
3494
3981
  var DEFAULT_B = 0.75;
@@ -3511,8 +3998,7 @@ function computeBM25WithGlobalStats(termFrequency, globalDocFrequency, globalTot
3511
3998
  return computeBM25(termFrequency, globalDocFrequency, globalTotalDocs, fieldLength, globalAvgFieldLength, params);
3512
3999
  }
3513
4000
 
3514
- // src/core/partition/search.ts
3515
- var DEFAULT_MAX_RESULTS = 1e3;
4001
+ // src/core/partition/scoring.ts
3516
4002
  var EMPTY_COMPONENTS = Object.freeze({});
3517
4003
  function accumulateTermScore(docScores, internalId, termScore, collect, fieldName, token, termFrequency, fieldLength, idf) {
3518
4004
  const existing = docScores.get(internalId);
@@ -3541,6 +4027,61 @@ function accumulateTermScore(docScores, internalId, termScore, collect, fieldNam
3541
4027
  });
3542
4028
  }
3543
4029
  }
4030
+ function topKFromMap(docScores, k, resolver) {
4031
+ if (k <= 0) return [];
4032
+ const heap = [];
4033
+ for (const [internalId, data] of docScores) {
4034
+ if (heap.length < k) {
4035
+ heap.push({ internalId, score: data.score });
4036
+ if (heap.length === k) buildMinHeap(heap);
4037
+ } else if (data.score > heap[0].score) {
4038
+ heap[0] = { internalId, score: data.score };
4039
+ siftDown(heap, 0);
4040
+ }
4041
+ }
4042
+ heap.sort((a, b) => b.score - a.score);
4043
+ const result = [];
4044
+ for (let i = 0; i < heap.length; i++) {
4045
+ const data = docScores.get(heap[i].internalId);
4046
+ if (!data) continue;
4047
+ const externalId = resolver.toExternal(heap[i].internalId);
4048
+ if (externalId === void 0) continue;
4049
+ result.push({
4050
+ docId: externalId,
4051
+ score: data.score,
4052
+ termFrequencies: data.termFrequencies,
4053
+ fieldLengths: data.fieldLengths,
4054
+ idf: data.idf
4055
+ });
4056
+ }
4057
+ return result;
4058
+ }
4059
+ function buildMinHeap(heap) {
4060
+ for (let i = (heap.length >> 1) - 1; i >= 0; i--) {
4061
+ siftDown(heap, i);
4062
+ }
4063
+ }
4064
+ function siftDown(heap, idx) {
4065
+ const len = heap.length;
4066
+ while (true) {
4067
+ let smallest = idx;
4068
+ const left = 2 * idx + 1;
4069
+ const right = 2 * idx + 2;
4070
+ if (left < len && heap[left].score < heap[smallest].score) smallest = left;
4071
+ if (right < len && heap[right].score < heap[smallest].score) smallest = right;
4072
+ if (smallest === idx) break;
4073
+ const tmp = heap[idx];
4074
+ heap[idx] = heap[smallest];
4075
+ heap[smallest] = tmp;
4076
+ idx = smallest;
4077
+ }
4078
+ }
4079
+
4080
+ // src/core/partition/search.ts
4081
+ var DEFAULT_MAX_RESULTS = 1e3;
4082
+ function globalDocFreqFor(docFreqs, term, fallback) {
4083
+ return Object.hasOwn(docFreqs, term) ? docFreqs[term] : fallback;
4084
+ }
3544
4085
  function searchFulltext(state, params) {
3545
4086
  const {
3546
4087
  queryTokens,
@@ -3587,7 +4128,7 @@ function searchFulltext(state, params) {
3587
4128
  seen.add(term);
3588
4129
  const postingList = state.invertedIdx.lookup(term);
3589
4130
  if (!postingList) continue;
3590
- const docFreq = globalStats ? globalDocFreqs[term] ?? postingList.docIdSet.size : postingList.docIdSet.size;
4131
+ const docFreq = globalStats ? globalDocFreqFor(globalDocFreqs, term, postingList.docIdSet.size) : postingList.docIdSet.size;
3591
4132
  found.push({ token: term, postingList, docFreq });
3592
4133
  }
3593
4134
  if (found.length === 0) return [];
@@ -3702,7 +4243,7 @@ function searchFulltext(state, params) {
3702
4243
  let totalPostings = 0;
3703
4244
  const matches = [];
3704
4245
  for (const m of rawMatches) {
3705
- const docFreq = globalStats ? globalDocFreqs[m.token] ?? m.postingList.docIdSet.size : m.postingList.docIdSet.size;
4246
+ const docFreq = globalStats ? globalDocFreqFor(globalDocFreqs, m.token, m.postingList.docIdSet.size) : m.postingList.docIdSet.size;
3706
4247
  const idf = computeIDF(docFreq, totalDocs);
3707
4248
  totalPostings += m.postingList.length;
3708
4249
  matches.push({ token: m.token, docFreq, idf, postingList: m.postingList });
@@ -3773,7 +4314,7 @@ function searchFulltext(state, params) {
3773
4314
  return postingList ? [{ token: qt.token, postingList }] : [];
3774
4315
  })() : state.invertedIdx.fuzzyLookup(qt.token, tolerance, prefixLength);
3775
4316
  for (const match of matchingPostings) {
3776
- const docFreq = globalStats ? globalDocFreqs[match.token] ?? match.postingList.docIdSet.size : match.postingList.docIdSet.size;
4317
+ const docFreq = globalStats ? globalDocFreqFor(globalDocFreqs, match.token, match.postingList.docIdSet.size) : match.postingList.docIdSet.size;
3777
4318
  const idf = computeIDF(docFreq, totalDocs);
3778
4319
  const list = match.postingList;
3779
4320
  const hasDeleted = list.deletedDocs.size > 0;
@@ -3816,55 +4357,6 @@ function searchFulltext(state, params) {
3816
4357
  const scored = topKFromMap(docScores, Math.min(k, totalMatched), resolver);
3817
4358
  return { scored, totalMatched };
3818
4359
  }
3819
- function topKFromMap(docScores, k, resolver) {
3820
- if (k <= 0) return [];
3821
- const heap = [];
3822
- for (const [internalId, data] of docScores) {
3823
- if (heap.length < k) {
3824
- heap.push({ internalId, score: data.score });
3825
- if (heap.length === k) buildMinHeap(heap);
3826
- } else if (data.score > heap[0].score) {
3827
- heap[0] = { internalId, score: data.score };
3828
- siftDown(heap, 0);
3829
- }
3830
- }
3831
- heap.sort((a, b) => b.score - a.score);
3832
- const result = [];
3833
- for (let i = 0; i < heap.length; i++) {
3834
- const data = docScores.get(heap[i].internalId);
3835
- if (!data) continue;
3836
- const externalId = resolver.toExternal(heap[i].internalId);
3837
- if (externalId === void 0) continue;
3838
- result.push({
3839
- docId: externalId,
3840
- score: data.score,
3841
- termFrequencies: data.termFrequencies,
3842
- fieldLengths: data.fieldLengths,
3843
- idf: data.idf
3844
- });
3845
- }
3846
- return result;
3847
- }
3848
- function buildMinHeap(heap) {
3849
- for (let i = (heap.length >> 1) - 1; i >= 0; i--) {
3850
- siftDown(heap, i);
3851
- }
3852
- }
3853
- function siftDown(heap, idx) {
3854
- const len = heap.length;
3855
- while (true) {
3856
- let smallest = idx;
3857
- const left = 2 * idx + 1;
3858
- const right = 2 * idx + 2;
3859
- if (left < len && heap[left].score < heap[smallest].score) smallest = left;
3860
- if (right < len && heap[right].score < heap[smallest].score) smallest = right;
3861
- if (smallest === idx) break;
3862
- const tmp = heap[idx];
3863
- heap[idx] = heap[smallest];
3864
- heap[smallest] = tmp;
3865
- idx = smallest;
3866
- }
3867
- }
3868
4360
 
3869
4361
  // src/core/partition/serialization.ts
3870
4362
  function serializePartition(state, partitionId, indexName, totalPartitions, language, schema) {
@@ -4337,6 +4829,9 @@ function createPartitionIndex(partitionId, trackPositions = true) {
4337
4829
  state.docStore.store(docId, document, stored.fieldLengths);
4338
4830
  }
4339
4831
  },
4832
+ rebuildTextIndex(schema, language, options) {
4833
+ rebuildTextIndex(state, schema, language, options);
4834
+ },
4340
4835
  get(docId) {
4341
4836
  const stored = state.docStore.get(docId);
4342
4837
  if (!stored) return void 0;
@@ -4436,6 +4931,7 @@ function setNestedValue(obj, path, value) {
4436
4931
  }
4437
4932
  function createPartitionManager(indexName, config, language, router, initialPartitionCount, vectorIndexes) {
4438
4933
  const count = initialPartitionCount ?? 1;
4934
+ const analysis = resolveIndexAnalysis(config);
4439
4935
  const trackPositions = config.trackPositions ?? true;
4440
4936
  const vecIndexes = vectorIndexes ?? /* @__PURE__ */ new Map();
4441
4937
  let partitions = [];
@@ -4454,14 +4950,14 @@ function createPartitionManager(indexName, config, language, router, initialPart
4454
4950
  }
4455
4951
  function resolveInsertOptions(options) {
4456
4952
  const applyStrict = config.strict === true;
4457
- const applyAnalyzer = config.stopWords !== void 0 || config.tokenizer !== void 0;
4953
+ const applyAnalyzer = analysis.stopWords !== void 0 || analysis.customTokenizer !== void 0;
4458
4954
  const applySurfaces = config.surfaceForms === true;
4459
4955
  if (!applyStrict && !applyAnalyzer && !applySurfaces) return options;
4460
4956
  const resolved = { ...options };
4461
4957
  if (applyStrict) resolved.strict = true;
4462
4958
  if (applyAnalyzer) {
4463
- resolved.stopWordOverride = options?.stopWordOverride ?? config.stopWords;
4464
- resolved.customTokenizer = options?.customTokenizer ?? config.tokenizer;
4959
+ resolved.stopWordOverride = options?.stopWordOverride ?? analysis.stopWords;
4960
+ resolved.customTokenizer = options?.customTokenizer ?? analysis.customTokenizer;
4465
4961
  }
4466
4962
  if (applySurfaces) resolved.collectSurfaces = true;
4467
4963
  return resolved;
@@ -4500,6 +4996,9 @@ function createPartitionManager(indexName, config, language, router, initialPart
4500
4996
  get config() {
4501
4997
  return config;
4502
4998
  },
4999
+ get analysis() {
5000
+ return analysis;
5001
+ },
4503
5002
  getPartition(partitionId) {
4504
5003
  validatePartitionId(partitionId);
4505
5004
  return partitions[partitionId];
@@ -4532,24 +5031,44 @@ function createPartitionManager(indexName, config, language, router, initialPart
4532
5031
  partitions.splice(partitionId, 1);
4533
5032
  rebuildDocPartitionMap();
4534
5033
  },
4535
- insert(docId, document, options) {
4536
- const currentMaxDocs = config.partitions?.maxDocsPerPartition;
4537
- if (currentMaxDocs !== void 0) {
4538
- const totalCapacity = currentMaxDocs * partitions.length;
4539
- if (docPartitionMap.size >= totalCapacity) {
4540
- throw new NarsilError(
4541
- ErrorCodes.PARTITION_CAPACITY_EXCEEDED,
4542
- `Index "${indexName}" has reached its capacity of ${totalCapacity} documents (${currentMaxDocs} per partition \xD7 ${partitions.length} partitions)`,
4543
- {
4544
- indexName,
4545
- currentCount: docPartitionMap.size,
4546
- totalCapacity,
4547
- maxDocsPerPartition: currentMaxDocs,
4548
- partitionCount: partitions.length
4549
- }
4550
- );
5034
+ trimPartitions(newCount) {
5035
+ if (newCount < 1) {
5036
+ throw new NarsilError(ErrorCodes.CONFIG_INVALID, `Cannot trim index "${indexName}" below one partition`, {
5037
+ indexName,
5038
+ requestedCount: newCount
5039
+ });
5040
+ }
5041
+ if (newCount >= partitions.length) {
5042
+ return;
5043
+ }
5044
+ for (const [docId, pid] of docPartitionMap) {
5045
+ if (pid >= newCount) {
5046
+ docPartitionMap.delete(docId);
4551
5047
  }
4552
5048
  }
5049
+ partitions.length = newCount;
5050
+ },
5051
+ assertCapacity(pendingWrites = 0, partitionCountCap) {
5052
+ const currentMaxDocs = config.partitions?.maxDocsPerPartition;
5053
+ if (currentMaxDocs === void 0) return;
5054
+ const effectivePartitionCount = partitionCountCap === void 0 ? partitions.length : Math.min(partitions.length, partitionCountCap);
5055
+ const totalCapacity = currentMaxDocs * effectivePartitionCount;
5056
+ if (docPartitionMap.size + pendingWrites >= totalCapacity) {
5057
+ throw new NarsilError(
5058
+ ErrorCodes.PARTITION_CAPACITY_EXCEEDED,
5059
+ `Index "${indexName}" has reached its capacity of ${totalCapacity} documents (${currentMaxDocs} per partition \xD7 ${effectivePartitionCount} partitions)`,
5060
+ {
5061
+ indexName,
5062
+ currentCount: docPartitionMap.size + pendingWrites,
5063
+ totalCapacity,
5064
+ maxDocsPerPartition: currentMaxDocs,
5065
+ partitionCount: effectivePartitionCount
5066
+ }
5067
+ );
5068
+ }
5069
+ },
5070
+ insert(docId, document, options) {
5071
+ manager.assertCapacity();
4553
5072
  const pid = router.route(docId, partitions.length);
4554
5073
  const insertOpts = resolveInsertOptions(options);
4555
5074
  partitions[pid].insert(docId, document, config.schema, language, insertOpts);
@@ -4563,6 +5082,10 @@ function createPartitionManager(indexName, config, language, router, initialPart
4563
5082
  partitions[pid].remove(docId, config.schema, language, resolveInsertOptions());
4564
5083
  docPartitionMap.delete(docId);
4565
5084
  },
5085
+ rebuildTextIndex(partitionId) {
5086
+ validatePartitionId(partitionId);
5087
+ partitions[partitionId].rebuildTextIndex(config.schema, language, resolveInsertOptions());
5088
+ },
4566
5089
  beginBatchRemove() {
4567
5090
  for (let i = 0; i < partitions.length; i++) {
4568
5091
  partitions[i].beginBatch();
@@ -4620,8 +5143,8 @@ function createPartitionManager(indexName, config, language, router, initialPart
4620
5143
  },
4621
5144
  getAggregateStats() {
4622
5145
  let totalDocuments = 0;
4623
- const docFrequencies = {};
4624
- const totalFieldLengths = {};
5146
+ const docFrequencies = /* @__PURE__ */ Object.create(null);
5147
+ const totalFieldLengths = /* @__PURE__ */ Object.create(null);
4625
5148
  for (let i = 0; i < partitions.length; i++) {
4626
5149
  const stats = partitions[i].stats;
4627
5150
  totalDocuments += stats.totalDocuments;
@@ -4706,8 +5229,33 @@ function createPartitionRouter() {
4706
5229
  return { route, routeBatch };
4707
5230
  }
4708
5231
 
4709
- // src/vector/scalar-quantization.ts
5232
+ // src/vector/scalar-quantization-calibration.ts
4710
5233
  var PADDING_FACTOR = 0.01;
5234
+ function computeCalibrationBounds(vectors, dimensions) {
5235
+ let globalMin = Number.POSITIVE_INFINITY;
5236
+ let globalMax = Number.NEGATIVE_INFINITY;
5237
+ let count = 0;
5238
+ for (const vec of vectors) {
5239
+ for (let d = 0; d < dimensions; d++) {
5240
+ const val = vec[d];
5241
+ if (val < globalMin) globalMin = val;
5242
+ if (val > globalMax) globalMax = val;
5243
+ }
5244
+ count++;
5245
+ }
5246
+ if (count === 0) return null;
5247
+ const range = globalMax - globalMin;
5248
+ const pad = range * PADDING_FACTOR;
5249
+ globalMin -= pad;
5250
+ globalMax += pad;
5251
+ if (globalMin === globalMax) {
5252
+ globalMin -= 1e-3;
5253
+ globalMax += 1e-3;
5254
+ }
5255
+ return { alpha: (globalMax - globalMin) / 255, offset: globalMin };
5256
+ }
5257
+
5258
+ // src/vector/scalar-quantization.ts
4711
5259
  var INITIAL_CAPACITY2 = 16;
4712
5260
  var PAGE_BYTES = 65536;
4713
5261
  function createScalarQuantizer(dimensions, ordinalSource) {
@@ -4811,28 +5359,10 @@ function createScalarQuantizer(dimensions, ordinalSource) {
4811
5359
  docToOrd.set(docId, ord);
4812
5360
  }
4813
5361
  function calibrateFromVectors(vectors) {
4814
- let globalMin = Number.POSITIVE_INFINITY;
4815
- let globalMax = Number.NEGATIVE_INFINITY;
4816
- let count = 0;
4817
- for (const vec of vectors) {
4818
- for (let d = 0; d < dimensions; d++) {
4819
- const val = vec[d];
4820
- if (val < globalMin) globalMin = val;
4821
- if (val > globalMax) globalMax = val;
4822
- }
4823
- count++;
4824
- }
4825
- if (count === 0) return;
4826
- const range = globalMax - globalMin;
4827
- const pad = range * PADDING_FACTOR;
4828
- globalMin -= pad;
4829
- globalMax += pad;
4830
- if (globalMin === globalMax) {
4831
- globalMin -= 1e-3;
4832
- globalMax += 1e-3;
4833
- }
4834
- alpha = (globalMax - globalMin) / 255;
4835
- offset = globalMin;
5362
+ const bounds = computeCalibrationBounds(vectors, dimensions);
5363
+ if (bounds === null) return;
5364
+ alpha = bounds.alpha;
5365
+ offset = bounds.offset;
4836
5366
  updateDerivedConstants();
4837
5367
  calibrated = true;
4838
5368
  }
@@ -5044,20 +5574,6 @@ function createScalarQuantizer(dimensions, ordinalSource) {
5044
5574
  }
5045
5575
  };
5046
5576
  }
5047
- function deserializeScalarQuantizer(data, dimensions, ordinalSource) {
5048
- const quantizer = createScalarQuantizer(dimensions, ordinalSource);
5049
- if (data.alpha === 0 && data.offset === 0 && Object.keys(data.quantizedVectors).length === 0) {
5050
- return quantizer;
5051
- }
5052
- quantizer.restoreCalibration(data.alpha, data.offset);
5053
- for (const [docId, values] of Object.entries(data.quantizedVectors)) {
5054
- const quantized = new Uint8Array(values);
5055
- const sum = data.vectorSums[docId] ?? 0;
5056
- const sumSq = data.vectorSumSqs[docId] ?? 0;
5057
- quantizer.restoreEntry(docId, quantized, sum, sumSq);
5058
- }
5059
- return quantizer;
5060
- }
5061
5577
 
5062
5578
  // src/workers/node-worker.ts
5063
5579
  async function spawnNodeWorker(entryPoint) {
@@ -5499,6 +6015,22 @@ function estimateMemoryBytes(state) {
5499
6015
  return Math.round(bytes);
5500
6016
  }
5501
6017
 
6018
+ // src/vector/scalar-quantization-restore.ts
6019
+ function deserializeScalarQuantizer(data, dimensions, ordinalSource) {
6020
+ const quantizer = createScalarQuantizer(dimensions, ordinalSource);
6021
+ if (data.alpha === 0 && data.offset === 0 && Object.keys(data.quantizedVectors).length === 0) {
6022
+ return quantizer;
6023
+ }
6024
+ quantizer.restoreCalibration(data.alpha, data.offset);
6025
+ for (const [docId, values] of Object.entries(data.quantizedVectors)) {
6026
+ const quantized = new Uint8Array(values);
6027
+ const sum = data.vectorSums[docId] ?? 0;
6028
+ const sumSq = data.vectorSumSqs[docId] ?? 0;
6029
+ quantizer.restoreEntry(docId, quantized, sum, sumSq);
6030
+ }
6031
+ return quantizer;
6032
+ }
6033
+
5502
6034
  // src/vector/vector-index/persistence.ts
5503
6035
  function serialize(state) {
5504
6036
  const vectors = [];
@@ -5846,7 +6378,17 @@ function createDirectExecutor() {
5846
6378
  vectorIndexes.set(fieldPath, createVectorIndex(fieldPath, dim, config.vectorPromotion));
5847
6379
  }
5848
6380
  const manager = createPartitionManager(indexName, config, language, router, partitionCount, vectorIndexes);
5849
- indexes.set(indexName, { manager, config, language, vectorIndexes });
6381
+ indexes.set(indexName, {
6382
+ manager,
6383
+ config,
6384
+ language,
6385
+ searchOptions: {
6386
+ bm25Params: config.bm25,
6387
+ stopWords: manager.analysis.stopWords,
6388
+ customTokenizer: manager.analysis.customTokenizer
6389
+ },
6390
+ vectorIndexes
6391
+ });
5850
6392
  }
5851
6393
  function dropIndex(indexName) {
5852
6394
  const entry = requireIndex(indexName);
@@ -5863,6 +6405,15 @@ function createDirectExecutor() {
5863
6405
  }
5864
6406
  async function execute(action) {
5865
6407
  switch (action.type) {
6408
+ case "bootstrap": {
6409
+ if (typeof action.moduleUrl !== "string" || action.moduleUrl.trim().length === 0) {
6410
+ throw new NarsilError(ErrorCodes.CONFIG_INVALID, "A bootstrap module needs a non-empty module URL", {
6411
+ moduleUrl: action.moduleUrl
6412
+ });
6413
+ }
6414
+ await import(action.moduleUrl);
6415
+ return void 0;
6416
+ }
5866
6417
  case "createIndex": {
5867
6418
  const language = getLanguage(action.config.language ?? "english");
5868
6419
  createIndex(action.indexName, action.config, language);
@@ -5889,17 +6440,30 @@ function createDirectExecutor() {
5889
6440
  }
5890
6441
  case "query": {
5891
6442
  const entry = requireIndex(action.indexName);
5892
- const result = await fanOutQuery(entry.manager, action.params, entry.language, entry.config.schema, {
5893
- scoringMode: entry.config.defaultScoring ?? "local",
5894
- partitionIds: action.partitionIds
5895
- });
6443
+ const result = await fanOutQuery(
6444
+ entry.manager,
6445
+ action.params,
6446
+ entry.language,
6447
+ entry.config.schema,
6448
+ {
6449
+ scoringMode: action.params.scoring ?? entry.config.defaultScoring ?? "local",
6450
+ partitionIds: action.partitionIds,
6451
+ ...action.globalStats !== void 0 ? { globalStats: sanitizeGlobalStats(action.globalStats) } : {}
6452
+ },
6453
+ entry.searchOptions
6454
+ );
5896
6455
  return result;
5897
6456
  }
5898
6457
  case "preflight": {
5899
6458
  const entry = requireIndex(action.indexName);
5900
- const result = await fanOutQuery(entry.manager, action.params, entry.language, entry.config.schema, {
5901
- scoringMode: entry.config.defaultScoring ?? "local"
5902
- });
6459
+ const result = await fanOutQuery(
6460
+ entry.manager,
6461
+ action.params,
6462
+ entry.language,
6463
+ entry.config.schema,
6464
+ { scoringMode: entry.config.defaultScoring ?? "local" },
6465
+ entry.searchOptions
6466
+ );
5903
6467
  return { count: result.totalMatched };
5904
6468
  }
5905
6469
  case "get": {
@@ -5945,6 +6509,9 @@ function createDirectExecutor() {
5945
6509
  }
5946
6510
  case "deserialize": {
5947
6511
  const entry = requireIndex(action.indexName);
6512
+ while (entry.manager.partitionCount <= action.partitionId) {
6513
+ entry.manager.addPartition();
6514
+ }
5948
6515
  entry.manager.deserializePartition(action.partitionId, action.data);
5949
6516
  return void 0;
5950
6517
  }
@@ -5976,4 +6543,4 @@ function createDirectExecutor() {
5976
6543
  };
5977
6544
  }
5978
6545
 
5979
- export { MAX_NESTING_DEPTH, clearNormalizationCache, configureNormalizationCache, createDirectExecutor, createPartitionIndex, createPartitionManager, createPartitionRouter, createVectorIndex, deserializeMetadata, deserializePayloadV1, deserializePayloadV2, detectRuntime, extractVectorFieldsFromSchema, fanOutQuery, flattenSchema, fnv1a, getLanguage, getNestedValue, getNormalizationCacheSize, isNodeMainThread, kWayMerge, mergePartitionStats, registerLanguage, resetNormalizationCache, serializeMetadata, spawnNodeWorker, tokenize, validateRequiredFields, validateSchema, validateVectorPromotion };
6546
+ export { MAX_NESTING_DEPTH, clearNormalizationCache, configureNormalizationCache, createDirectExecutor, createPartitionIndex, createPartitionManager, createPartitionRouter, createVectorIndex, deserializeMetadata, deserializePayloadV1, deserializePayloadV2, detectRuntime, extractVectorFieldsFromSchema, fanOutQuery, flattenSchema, fnv1a, getLanguage, getNestedValue, getNormalizationCacheSize, getStopWords, getTokenizer, hasStopWords, hasTokenizer, isNodeMainThread, kWayMerge, mergePartitionStats, pruneStatsToQueryTerms, registerLanguage, registerStopWords, registerTokenizer, resetNormalizationCache, resolveIndexAnalysis, serializeMetadata, spawnNodeWorker, tokenize, validateRequiredFields, validateSchema, validateVectorPromotion };