@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
package/dist/server.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { NarsilError, ErrorCodes } from './chunk-ALB35PRQ.mjs';
1
+ import { NarsilError, ErrorCodes } from './chunk-M2EDG55Y.mjs';
2
2
  import { randomUUID } from 'crypto';
3
3
 
4
4
  // src/server/cors.ts
@@ -81,7 +81,18 @@ var STATUS_BY_CODE = {
81
81
  QUERY_ROUTING_FAILED: 503,
82
82
  QUERY_PARTIAL_FAILURE: 503,
83
83
  QUERY_NODE_TIMEOUT: 503,
84
- QUERY_NO_ACTIVE_REPLICA: 503
84
+ QUERY_NO_ACTIVE_REPLICA: 503,
85
+ REPLICATION_ENTRY_INVALID: 503,
86
+ REPLICATION_INSYNC_REMOVAL_FAILED: 503,
87
+ REPLICATION_ROLLBACK_FAILED: 503,
88
+ REPLICATION_LOG_FULL: 503,
89
+ REPLICATION_ENTRY_CORRUPT: 503,
90
+ REPLICATION_SNAPSHOT_CORRUPT: 503,
91
+ REPLICATION_TERM_MISMATCH: 503,
92
+ REPLICATION_SYNC_FAILED: 503,
93
+ PARTITION_NOT_PRIMARY: 503,
94
+ PARTITION_UNASSIGNED: 503,
95
+ INSUFFICIENT_REPLICAS: 503
85
96
  };
86
97
  function httpStatusForNarsilError(code) {
87
98
  return STATUS_BY_CODE[code] ?? 500;
@@ -118,11 +129,31 @@ var STATUS_TEXT = {
118
129
  500: "500 Internal Server Error",
119
130
  503: "503 Service Unavailable"
120
131
  };
132
+ var STREAM_THRESHOLD = 64 * 1024;
121
133
  function statusLine(status) {
122
134
  return STATUS_TEXT[status] ?? `${status}`;
123
135
  }
124
- function sendJson(res, data, status = 200) {
136
+ function streamBody(res, abort, body, status, contentType) {
137
+ if (abort.aborted) return;
138
+ const totalSize = body.byteLength;
139
+ res.cork(() => {
140
+ res.writeStatus(statusLine(status)).writeHeader("Content-Type", contentType);
141
+ const startOffset = res.getWriteOffset();
142
+ const [, done] = res.tryEnd(body, totalSize);
143
+ if (done) return;
144
+ res.onWritable((offset) => {
145
+ if (abort.aborted) return true;
146
+ const [retryOk] = res.tryEnd(body.subarray(offset - startOffset), totalSize);
147
+ return retryOk;
148
+ });
149
+ });
150
+ }
151
+ function sendJson(res, data, status = 200, abort) {
125
152
  const payload = JSON.stringify(data);
153
+ if (abort && payload !== void 0 && Buffer.byteLength(payload) >= STREAM_THRESHOLD) {
154
+ streamBody(res, abort, Buffer.from(payload, "utf8"), status, "application/json");
155
+ return;
156
+ }
126
157
  res.cork(() => {
127
158
  res.writeStatus(statusLine(status)).writeHeader("Content-Type", "application/json").end(payload);
128
159
  });
@@ -134,10 +165,8 @@ function sendError(res, status, code, message, details) {
134
165
  res.writeStatus(statusLine(status)).writeHeader("Content-Type", "application/json").end(payload);
135
166
  });
136
167
  }
137
- function sendBinary(res, data, status = 200) {
138
- res.cork(() => {
139
- res.writeStatus(statusLine(status)).writeHeader("Content-Type", "application/octet-stream").end(data);
140
- });
168
+ function sendBinary(res, data, abort, status = 200) {
169
+ streamBody(res, abort, data, status, "application/octet-stream");
141
170
  }
142
171
 
143
172
  // src/server/handler-utils.ts
@@ -171,11 +200,15 @@ function respondError(ctx, err) {
171
200
  }
172
201
  function respondJson(ctx, data, status = 200) {
173
202
  if (ctx.abort.aborted) return;
174
- sendJson(ctx.res, data, status);
203
+ sendJson(ctx.res, data, status, ctx.abort);
175
204
  }
176
205
  function badRequest(res, message, details) {
177
206
  sendError(res, 400, ServerErrorCodes.INVALID_REQUEST, message, details);
178
207
  }
208
+ function rejectInvalid(ctx, failure) {
209
+ if (ctx.abort.aborted) return;
210
+ badRequest(ctx.res, failure.message, failure.details);
211
+ }
179
212
  function serializeBatchResult(result) {
180
213
  return {
181
214
  succeeded: result.succeeded,
@@ -186,6 +219,98 @@ function serializeBatchResult(result) {
186
219
  };
187
220
  }
188
221
 
222
+ // src/server/validation.ts
223
+ function isPlainObject(value) {
224
+ return typeof value === "object" && value !== null && !Array.isArray(value);
225
+ }
226
+ function overWindow(field, value, maxWindow) {
227
+ if (typeof value === "number" && value > maxWindow) {
228
+ return {
229
+ message: `Field "${field}" exceeds the maximum result window of ${maxWindow}`,
230
+ details: { value, limit: maxWindow }
231
+ };
232
+ }
233
+ return null;
234
+ }
235
+ function validateQuery(params, maxWindow) {
236
+ if (params.group && typeof params.group === "object" && "reduce" in params.group) {
237
+ return {
238
+ message: 'Custom group reducers are not available over HTTP; use "group.fields" and "group.maxPerGroup" only'
239
+ };
240
+ }
241
+ const rowBound = overWindow("limit", params.limit, maxWindow) ?? overWindow("offset", params.offset, maxWindow);
242
+ if (rowBound) return rowBound;
243
+ if (params.group && typeof params.group === "object") {
244
+ const grouped = overWindow("group.maxPerGroup", params.group.maxPerGroup, maxWindow);
245
+ if (grouped) return grouped;
246
+ }
247
+ if (params.facets && typeof params.facets === "object") {
248
+ for (const [field, facet] of Object.entries(params.facets)) {
249
+ if (facet && typeof facet === "object") {
250
+ const faceted = overWindow(`facets.${field}.limit`, facet.limit, maxWindow);
251
+ if (faceted) return faceted;
252
+ }
253
+ }
254
+ }
255
+ return null;
256
+ }
257
+ function validateSuggest(params) {
258
+ if (typeof params.prefix !== "string") {
259
+ return { message: 'Field "prefix" is required and must be a string' };
260
+ }
261
+ return null;
262
+ }
263
+ function validateDocumentBody(body) {
264
+ if (!isPlainObject(body.document)) {
265
+ return { message: 'Field "document" is required and must be an object' };
266
+ }
267
+ return null;
268
+ }
269
+ function validateMultiGet(body, maxFetch) {
270
+ if (!Array.isArray(body.docIds)) {
271
+ return { message: 'Field "docIds" is required and must be an array of strings' };
272
+ }
273
+ if (body.docIds.length > maxFetch) {
274
+ return {
275
+ message: `Field "docIds" exceeds the maximum of ${maxFetch} ids per request`,
276
+ details: { count: body.docIds.length, limit: maxFetch }
277
+ };
278
+ }
279
+ return null;
280
+ }
281
+ function validateBatch(body) {
282
+ const action = body.action ?? "insert";
283
+ if (action === "insert") {
284
+ if (!Array.isArray(body.documents)) return { message: 'Batch insert requires a "documents" array' };
285
+ return null;
286
+ }
287
+ if (action === "update") {
288
+ if (!Array.isArray(body.updates)) return { message: 'Batch update requires an "updates" array' };
289
+ return null;
290
+ }
291
+ if (action === "delete") {
292
+ if (!Array.isArray(body.docIds)) return { message: 'Batch delete requires a "docIds" array' };
293
+ return null;
294
+ }
295
+ return { message: 'Field "action" must be one of "insert", "update", or "delete"' };
296
+ }
297
+ function validateCreateIndex(body) {
298
+ if (typeof body.name !== "string" || body.name.length === 0) {
299
+ return { message: 'Field "name" is required and must be a non-empty string' };
300
+ }
301
+ if (!isPlainObject(body.config)) {
302
+ return { message: 'Field "config" is required and must be an object' };
303
+ }
304
+ return null;
305
+ }
306
+ function validateRebalance(body) {
307
+ const target = body.targetPartitionCount;
308
+ if (typeof target !== "number" || !Number.isInteger(target) || target <= 0) {
309
+ return { message: 'Field "targetPartitionCount" is required and must be a positive integer' };
310
+ }
311
+ return null;
312
+ }
313
+
189
314
  // src/server/handlers/admin.ts
190
315
  function taskResponse(record) {
191
316
  return { taskId: record.id, type: record.type, status: record.status };
@@ -204,7 +329,7 @@ function createAdminHandlers(deps) {
204
329
  try {
205
330
  const bytes = await engine.snapshot(ctx.params[0]);
206
331
  if (ctx.abort.aborted) return;
207
- sendBinary(ctx.res, bytes);
332
+ sendBinary(ctx.res, bytes, ctx.abort);
208
333
  } catch (err) {
209
334
  respondError(ctx, err);
210
335
  }
@@ -262,11 +387,12 @@ function createAdminHandlers(deps) {
262
387
  async function rebalance(ctx) {
263
388
  const body = parseJsonOptional(ctx);
264
389
  if (!body) return;
265
- const target = body.targetPartitionCount;
266
- if (typeof target !== "number" || !Number.isInteger(target) || target <= 0) {
267
- badRequest(ctx.res, 'Field "targetPartitionCount" is required and must be a positive integer');
390
+ const failure = validateRebalance(body);
391
+ if (failure) {
392
+ rejectInvalid(ctx, failure);
268
393
  return;
269
394
  }
395
+ const target = body.targetPartitionCount;
270
396
  const name = ctx.params[0];
271
397
  try {
272
398
  requireIndexExists(name);
@@ -315,16 +441,14 @@ function createAdminHandlers(deps) {
315
441
  }
316
442
 
317
443
  // src/server/handlers/documents.ts
318
- function isPlainObject(value) {
319
- return typeof value === "object" && value !== null && !Array.isArray(value);
320
- }
321
444
  function createDocumentHandlers(deps) {
322
- const { engine } = deps;
445
+ const { engine, limits } = deps;
323
446
  async function insert(ctx) {
324
447
  const body = parseJson(ctx);
325
448
  if (!body) return;
326
- if (!isPlainObject(body.document)) {
327
- badRequest(ctx.res, 'Field "document" is required and must be an object');
449
+ const failure = validateDocumentBody(body);
450
+ if (failure) {
451
+ rejectInvalid(ctx, failure);
328
452
  return;
329
453
  }
330
454
  try {
@@ -356,8 +480,9 @@ function createDocumentHandlers(deps) {
356
480
  async function put(ctx) {
357
481
  const body = parseJson(ctx);
358
482
  if (!body) return;
359
- if (!isPlainObject(body.document)) {
360
- badRequest(ctx.res, 'Field "document" is required and must be an object');
483
+ const failure = validateDocumentBody(body);
484
+ if (failure) {
485
+ rejectInvalid(ctx, failure);
361
486
  return;
362
487
  }
363
488
  const [name, id] = ctx.params;
@@ -376,8 +501,9 @@ function createDocumentHandlers(deps) {
376
501
  async function patch(ctx) {
377
502
  const body = parseJson(ctx);
378
503
  if (!body) return;
379
- if (!isPlainObject(body.document)) {
380
- badRequest(ctx.res, 'Field "document" is required and must be an object');
504
+ const failure = validateDocumentBody(body);
505
+ if (failure) {
506
+ rejectInvalid(ctx, failure);
381
507
  return;
382
508
  }
383
509
  try {
@@ -405,8 +531,9 @@ function createDocumentHandlers(deps) {
405
531
  async function multiGet(ctx) {
406
532
  const body = parseJson(ctx);
407
533
  if (!body) return;
408
- if (!Array.isArray(body.docIds)) {
409
- badRequest(ctx.res, 'Field "docIds" is required and must be an array of strings');
534
+ const failure = validateMultiGet(body, limits.maxFetchDocuments);
535
+ if (failure) {
536
+ rejectInvalid(ctx, failure);
410
537
  return;
411
538
  }
412
539
  try {
@@ -421,28 +548,22 @@ function createDocumentHandlers(deps) {
421
548
  async function batch(ctx) {
422
549
  const body = parseJson(ctx);
423
550
  if (!body) return;
551
+ const failure = validateBatch(body);
552
+ if (failure) {
553
+ rejectInvalid(ctx, failure);
554
+ return;
555
+ }
424
556
  const action = body.action ?? "insert";
425
557
  try {
426
- if (action === "insert") {
427
- if (!Array.isArray(body.documents)) {
428
- badRequest(ctx.res, 'Batch insert requires a "documents" array');
429
- return;
430
- }
431
- respondJson(ctx, serializeBatchResult(await engine.insertBatch(ctx.params[0], body.documents, body.options)));
432
- } else if (action === "update") {
433
- if (!Array.isArray(body.updates)) {
434
- badRequest(ctx.res, 'Batch update requires an "updates" array');
435
- return;
436
- }
437
- respondJson(ctx, serializeBatchResult(await engine.updateBatch(ctx.params[0], body.updates)));
558
+ if (action === "update") {
559
+ respondJson(ctx, serializeBatchResult(await engine.updateBatch(ctx.params[0], body.updates ?? [])));
438
560
  } else if (action === "delete") {
439
- if (!Array.isArray(body.docIds)) {
440
- badRequest(ctx.res, 'Batch delete requires a "docIds" array');
441
- return;
442
- }
443
- respondJson(ctx, serializeBatchResult(await engine.removeBatch(ctx.params[0], body.docIds)));
561
+ respondJson(ctx, serializeBatchResult(await engine.removeBatch(ctx.params[0], body.docIds ?? [])));
444
562
  } else {
445
- badRequest(ctx.res, 'Field "action" must be one of "insert", "update", or "delete"');
563
+ respondJson(
564
+ ctx,
565
+ serializeBatchResult(await engine.insertBatch(ctx.params[0], body.documents ?? [], body.options))
566
+ );
446
567
  }
447
568
  } catch (err) {
448
569
  respondError(ctx, err);
@@ -627,12 +748,9 @@ function createIndexHandlers(deps) {
627
748
  async function create(ctx) {
628
749
  const body = parseJson(ctx);
629
750
  if (!body) return;
630
- if (typeof body.name !== "string" || body.name.length === 0) {
631
- badRequest(ctx.res, 'Field "name" is required and must be a non-empty string');
632
- return;
633
- }
634
- if (typeof body.config !== "object" || body.config === null) {
635
- badRequest(ctx.res, 'Field "config" is required and must be an object');
751
+ const failure = validateCreateIndex(body);
752
+ if (failure) {
753
+ rejectInvalid(ctx, failure);
636
754
  return;
637
755
  }
638
756
  try {
@@ -684,22 +802,16 @@ function createIndexHandlers(deps) {
684
802
  }
685
803
 
686
804
  // src/server/handlers/search.ts
687
- function rejectsCustomReducer(ctx, params) {
688
- if (params.group && typeof params.group === "object" && "reduce" in params.group) {
689
- badRequest(
690
- ctx.res,
691
- 'Custom group reducers are not available over HTTP; use "group.fields" and "group.maxPerGroup" only'
692
- );
693
- return true;
694
- }
695
- return false;
696
- }
697
805
  function createSearchHandlers(deps) {
698
- const { engine } = deps;
806
+ const { engine, limits } = deps;
699
807
  async function search(ctx) {
700
808
  const params = parseJson(ctx);
701
809
  if (!params) return;
702
- if (rejectsCustomReducer(ctx, params)) return;
810
+ const failure = validateQuery(params, limits.maxResultWindow);
811
+ if (failure) {
812
+ rejectInvalid(ctx, failure);
813
+ return;
814
+ }
703
815
  try {
704
816
  respondJson(ctx, await engine.query(ctx.params[0], params));
705
817
  } catch (err) {
@@ -709,7 +821,11 @@ function createSearchHandlers(deps) {
709
821
  async function preflight(ctx) {
710
822
  const params = parseJson(ctx);
711
823
  if (!params) return;
712
- if (rejectsCustomReducer(ctx, params)) return;
824
+ const failure = validateQuery(params, limits.maxResultWindow);
825
+ if (failure) {
826
+ rejectInvalid(ctx, failure);
827
+ return;
828
+ }
713
829
  try {
714
830
  respondJson(ctx, await engine.preflight(ctx.params[0], params));
715
831
  } catch (err) {
@@ -719,8 +835,9 @@ function createSearchHandlers(deps) {
719
835
  async function suggest(ctx) {
720
836
  const params = parseJson(ctx);
721
837
  if (!params) return;
722
- if (typeof params.prefix !== "string") {
723
- badRequest(ctx.res, 'Field "prefix" is required and must be a string');
838
+ const failure = validateSuggest(params);
839
+ if (failure) {
840
+ rejectInvalid(ctx, failure);
724
841
  return;
725
842
  }
726
843
  try {
@@ -1007,7 +1124,9 @@ function resolveLimits(limits) {
1007
1124
  maxImportBytes: limits?.maxImportBytes ?? 100 * MB,
1008
1125
  maxLineBytes: limits?.maxLineBytes ?? 4 * MB,
1009
1126
  importBatchSize: limits?.importBatchSize ?? 1e3,
1010
- maxConcurrentRequests: limits?.maxConcurrentRequests ?? 0
1127
+ maxConcurrentRequests: limits?.maxConcurrentRequests ?? 0,
1128
+ maxResultWindow: limits?.maxResultWindow ?? 1e4,
1129
+ maxFetchDocuments: limits?.maxFetchDocuments ?? 1e4
1011
1130
  };
1012
1131
  }
1013
1132
  function resolveBuild(build) {
@@ -1,4 +1,4 @@
1
- import { S as SchemaDefinition } from './schema-DCgkO1j4.js';
1
+ import { b as SchemaDefinition } from './schema-BnvLvjRp.js';
2
2
 
3
3
  type NodeRole = 'data' | 'coordinator' | 'controller';
4
4
  type PartitionState = 'UNASSIGNED' | 'INITIALISING' | 'ACTIVE' | 'MIGRATING' | 'DECOMMISSIONING';
@@ -1,4 +1,4 @@
1
- import { isValidWorkerAction } from '../chunk-HRABJLPB.mjs';
1
+ import { isValidWorkerAction } from '../chunk-BKAI2WXI.mjs';
2
2
 
3
3
  // src/workers/worker-entry.ts
4
4
  function startWorker() {
@@ -15,7 +15,7 @@ async function setup() {
15
15
  } catch {
16
16
  parentPort = null;
17
17
  }
18
- const { createDirectExecutor } = await import('../direct-executor-E52FFGDE.mjs');
18
+ const { createDirectExecutor } = await import('../direct-executor-2FV3MEDD.mjs');
19
19
  const executor = createDirectExecutor();
20
20
  function buildErrorResponse(requestId, code, message) {
21
21
  return { type: "error", requestId, code, message };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@delali/narsil",
3
- "version": "0.1.14",
3
+ "version": "0.2.0",
4
4
  "type": "module",
5
5
  "description": "A distributed full-text search engine with cross-language portable binary format.",
6
6
  "author": "Delali (https://sondelali.com)",
@@ -109,7 +109,8 @@
109
109
  },
110
110
  "files": [
111
111
  "dist/",
112
- "LICENSE"
112
+ "LICENSE",
113
+ "NOTICE"
113
114
  ],
114
115
  "dependencies": {
115
116
  "@msgpack/msgpack": "3.1.1"
@@ -122,6 +123,7 @@
122
123
  "fflate": "0.8.3",
123
124
  "selfsigned": "5.5.0",
124
125
  "tsup": "8.5.1",
126
+ "tsx": "4.21.0",
125
127
  "typescript": "5.9.3",
126
128
  "uWebSockets.js": "github:uNetworking/uWebSockets.js#v20.58.0",
127
129
  "vitest": "4.0.18",
@@ -149,10 +151,15 @@
149
151
  "test:recall": "vitest run src/__tests__/vector/recall.test.ts",
150
152
  "test:embeddings": "NARSIL_TEST_EMBEDDINGS=1 vitest run embedding-e2e",
151
153
  "bench": "vitest bench",
154
+ "stemmers": "tsx snowball/build.ts",
155
+ "revisions": "tsx scripts/analysis-revisions.ts",
156
+ "revisions:write": "tsx scripts/analysis-revisions.ts --write",
157
+ "lookalikes": "tsx scripts/lookalike-counts.ts",
158
+ "lookalikes:collect": "tsx scripts/lookalike-counts.ts --collect",
152
159
  "lint": "biome check src/",
153
160
  "typecheck:src": "tsc --noEmit",
154
161
  "typecheck:tests": "tsc --noEmit -p tsconfig.test.json",
155
- "typecheck:tooling": "tsc --noEmit -p scripts/tsconfig.json && tsc --noEmit -p benchmarks/tsconfig.json && tsc --noEmit -p wasm/tsconfig.json",
162
+ "typecheck:tooling": "tsc --noEmit -p scripts/tsconfig.json && tsc --noEmit -p benchmarks/tsconfig.json && tsc --noEmit -p wasm/tsconfig.json && tsc --noEmit -p snowball/tsconfig.json",
156
163
  "typecheck": "pnpm run typecheck:src && pnpm run typecheck:tests && pnpm run typecheck:tooling"
157
164
  }
158
165
  }