@delali/narsil 0.1.11 → 0.1.13

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.
@@ -185,6 +185,7 @@ function computeDefaultCacheSize() {
185
185
  return Math.max(CACHE_SIZE_FLOOR, Math.min(2e5, CACHE_SIZE_CEILING));
186
186
  }
187
187
  var normalizationCache = /* @__PURE__ */ new Map();
188
+ var normalizationCacheSize = 0;
188
189
  var maxCacheSize = computeDefaultCacheSize();
189
190
  function configureNormalizationCache(maxSize) {
190
191
  if (!Number.isFinite(maxSize)) {
@@ -207,19 +208,13 @@ function configureNormalizationCache(maxSize) {
207
208
  );
208
209
  }
209
210
  maxCacheSize = Math.max(CACHE_SIZE_FLOOR, Math.min(Math.floor(maxSize), CACHE_SIZE_CEILING));
210
- if (normalizationCache.size > maxCacheSize) {
211
- const excess = normalizationCache.size - maxCacheSize;
212
- let count = 0;
213
- for (const key of normalizationCache.keys()) {
214
- if (count >= excess) break;
215
- normalizationCache.delete(key);
216
- count++;
217
- }
211
+ if (normalizationCacheSize > maxCacheSize) {
212
+ evictOldestEntries(normalizationCacheSize - maxCacheSize);
218
213
  }
219
214
  }
220
215
  var cachedLangName = "";
221
216
  var cachedFlags = "";
222
- var cachedPrefix = "";
217
+ var cachedBucket = null;
223
218
  function stripDiacritics(text) {
224
219
  return text.normalize("NFD").replace(/[\u0300-\u036f]/g, "");
225
220
  }
@@ -228,18 +223,44 @@ function resolveStopWords(language, override) {
228
223
  if (override instanceof Set) return override;
229
224
  return override(language.stopWords);
230
225
  }
231
- function getCachePrefix(language, flags) {
232
- if (language.name === cachedLangName && flags === cachedFlags) return cachedPrefix;
226
+ function getCacheBucket(language, flags) {
227
+ if (cachedBucket && language.name === cachedLangName && flags === cachedFlags) return cachedBucket;
228
+ const key = `${language.name}:${flags}`;
229
+ let bucket = normalizationCache.get(key);
230
+ if (!bucket) {
231
+ bucket = /* @__PURE__ */ new Map();
232
+ normalizationCache.set(key, bucket);
233
+ }
233
234
  cachedLangName = language.name;
234
235
  cachedFlags = flags;
235
- cachedPrefix = `${language.name}:${flags}:`;
236
- return cachedPrefix;
236
+ cachedBucket = bucket;
237
+ return bucket;
238
+ }
239
+ function evictOldestEntries(count) {
240
+ let remaining = count;
241
+ for (const [key, bucket] of normalizationCache) {
242
+ if (remaining <= 0) break;
243
+ if (bucket.size <= remaining) {
244
+ remaining -= bucket.size;
245
+ normalizationCacheSize -= bucket.size;
246
+ normalizationCache.delete(key);
247
+ if (bucket === cachedBucket) cachedBucket = null;
248
+ } else {
249
+ let deleted = 0;
250
+ for (const raw of bucket.keys()) {
251
+ if (deleted >= remaining) break;
252
+ bucket.delete(raw);
253
+ deleted++;
254
+ }
255
+ normalizationCacheSize -= deleted;
256
+ remaining = 0;
257
+ }
258
+ }
237
259
  }
238
260
  function transformToken(raw, language, stem, removeDiacritics) {
239
261
  const flags = (stem ? "s" : "") + (removeDiacritics ? "d" : "");
240
- const prefix = getCachePrefix(language, flags);
241
- const cacheKey = prefix + raw;
242
- const cached = normalizationCache.get(cacheKey);
262
+ let bucket = getCacheBucket(language, flags);
263
+ const cached = bucket.get(raw);
243
264
  if (cached !== void 0) return cached;
244
265
  let normalized = raw;
245
266
  if (removeDiacritics) {
@@ -248,16 +269,12 @@ function transformToken(raw, language, stem, removeDiacritics) {
248
269
  if (stem && language.stemmer) {
249
270
  normalized = language.stemmer(normalized);
250
271
  }
251
- if (normalizationCache.size >= maxCacheSize) {
252
- const evictCount = Math.max(1, maxCacheSize >>> 2);
253
- let count = 0;
254
- for (const key of normalizationCache.keys()) {
255
- if (count >= evictCount) break;
256
- normalizationCache.delete(key);
257
- count++;
258
- }
272
+ if (normalizationCacheSize >= maxCacheSize) {
273
+ evictOldestEntries(Math.max(1, maxCacheSize >>> 2));
274
+ bucket = getCacheBucket(language, flags);
259
275
  }
260
- normalizationCache.set(cacheKey, normalized);
276
+ bucket.set(raw, normalized);
277
+ normalizationCacheSize++;
261
278
  return normalized;
262
279
  }
263
280
  function splitText(text, language) {
@@ -287,24 +304,28 @@ function tokenize(text, language, options) {
287
304
  stem = true,
288
305
  removeStopWords = true,
289
306
  removeDiacritics = false,
307
+ collectSurfaces = false,
290
308
  customTokenizer,
291
309
  stopWordOverride
292
310
  } = options ?? {};
293
311
  if (customTokenizer) {
294
312
  const customResult = customTokenizer.tokenize(text);
313
+ const originals = customResult.map((t) => t.token);
295
314
  return {
296
315
  tokens: customResult,
297
- originalTokens: customResult.map((t) => t.token)
316
+ originalTokens: originals
298
317
  };
299
318
  }
300
319
  const normalized = isAscii(text) ? text.toLowerCase() : text.normalize("NFC").toLowerCase();
301
320
  const rawParts = splitText(normalized, language);
302
321
  const minLength = language.tokenizer?.minTokenLength ?? DEFAULT_MIN_TOKEN_LENGTH;
303
322
  const effectiveDiacritics = removeDiacritics || (language.tokenizer?.normalizeDiacritics ?? false);
323
+ const stemsTokens = stem && language.stemmer !== void 0;
304
324
  const stopWords = removeStopWords ? resolveStopWords(language, stopWordOverride) : /* @__PURE__ */ new Set();
305
325
  const stripPossessives = language.tokenizer?.stripPossessive ?? false;
306
326
  const tokens = [];
307
327
  const originalTokens = [];
328
+ const surfaces = collectSurfaces && stemsTokens ? [] : void 0;
308
329
  let position = 0;
309
330
  for (const part of rawParts) {
310
331
  const candidate = stripPossessives ? stripPossessive(part) : part;
@@ -320,16 +341,21 @@ function tokenize(text, language, options) {
320
341
  if (processed.length > 0) {
321
342
  tokens.push({ token: processed, position });
322
343
  originalTokens.push(part);
344
+ if (surfaces) {
345
+ const surface = effectiveDiacritics ? transformToken(candidate, language, false, true) : candidate;
346
+ surfaces.push(surface === processed ? void 0 : surface);
347
+ }
323
348
  }
324
349
  position++;
325
350
  }
326
- return { tokens, originalTokens };
351
+ return { tokens, originalTokens, surfaces };
327
352
  }
328
353
  function* tokenizeIterator(text, language, options) {
329
354
  const {
330
355
  stem = true,
331
356
  removeStopWords = true,
332
357
  removeDiacritics = false,
358
+ collectSurfaces = false,
333
359
  customTokenizer,
334
360
  stopWordOverride
335
361
  } = options ?? {};
@@ -344,6 +370,8 @@ function* tokenizeIterator(text, language, options) {
344
370
  const rawParts = splitText(normalized, language);
345
371
  const minLength = language.tokenizer?.minTokenLength ?? DEFAULT_MIN_TOKEN_LENGTH;
346
372
  const effectiveDiacritics = removeDiacritics || (language.tokenizer?.normalizeDiacritics ?? false);
373
+ const stemsTokens = stem && language.stemmer !== void 0;
374
+ const wantSurfaces = collectSurfaces && stemsTokens;
347
375
  const stopWords = removeStopWords ? resolveStopWords(language, stopWordOverride) : /* @__PURE__ */ new Set();
348
376
  const stripPossessives = language.tokenizer?.stripPossessive ?? false;
349
377
  let position = 0;
@@ -359,20 +387,27 @@ function* tokenizeIterator(text, language, options) {
359
387
  }
360
388
  const processed = transformToken(candidate, language, stem, effectiveDiacritics);
361
389
  if (processed.length > 0) {
362
- yield { token: processed, position };
390
+ if (wantSurfaces) {
391
+ const surface = effectiveDiacritics ? transformToken(candidate, language, false, true) : candidate;
392
+ yield { token: processed, position, surface: surface === processed ? void 0 : surface };
393
+ } else {
394
+ yield { token: processed, position };
395
+ }
363
396
  }
364
397
  position++;
365
398
  }
366
399
  }
367
400
  function clearNormalizationCache() {
368
401
  normalizationCache.clear();
402
+ normalizationCacheSize = 0;
403
+ cachedBucket = null;
369
404
  }
370
405
  function resetNormalizationCache() {
371
- normalizationCache.clear();
406
+ clearNormalizationCache();
372
407
  maxCacheSize = computeDefaultCacheSize();
373
408
  }
374
409
  function getNormalizationCacheSize() {
375
- return normalizationCache.size;
410
+ return normalizationCacheSize;
376
411
  }
377
412
 
378
413
  // src/schema/validator/shared.ts
@@ -771,6 +806,36 @@ function validateVectorPromotion(config) {
771
806
  }
772
807
 
773
808
  // src/search/fulltext.ts
809
+ var PREFIX_MAX_EXPANSIONS = 50;
810
+ function resolvePrefixExpansion(partition, params, language, options, queryTokens, lastToken) {
811
+ const term = params.term;
812
+ if (term === void 0) return void 0;
813
+ const unstemmed = tokenize(term, language, {
814
+ stem: false,
815
+ removeStopWords: true,
816
+ customTokenizer: options?.customTokenizer,
817
+ stopWordOverride: options?.stopWords
818
+ });
819
+ let surfacePrefix;
820
+ for (const t of unstemmed.tokens) {
821
+ if (t.position === lastToken.position) {
822
+ surfacePrefix = t.token;
823
+ break;
824
+ }
825
+ }
826
+ if (surfacePrefix === void 0 || surfacePrefix.length === 0) return void 0;
827
+ const expansions = partition.expandTermPrefix(surfacePrefix, lastToken.token, PREFIX_MAX_EXPANSIONS);
828
+ const terms = [];
829
+ for (const expanded of expansions) {
830
+ if (expanded !== lastToken.token) terms.push(expanded);
831
+ }
832
+ for (const qt of queryTokens) {
833
+ if (qt.token === lastToken.token) {
834
+ return { token: qt.token, terms };
835
+ }
836
+ }
837
+ return void 0;
838
+ }
774
839
  function fulltextSearch(partition, params, language, schema, options) {
775
840
  if (!params.term || params.term.trim().length === 0) {
776
841
  return { scored: [], totalMatched: 0 };
@@ -792,6 +857,11 @@ function fulltextSearch(partition, params, language, schema, options) {
792
857
  return { scored: [], totalMatched: 0 };
793
858
  }
794
859
  const queryTokens = deduplicateTokens(queryTokenResult.tokens);
860
+ let prefixExpansion;
861
+ if (params.prefix === true && params.exact !== true) {
862
+ const lastToken = queryTokenResult.tokens[queryTokenResult.tokens.length - 1];
863
+ prefixExpansion = resolvePrefixExpansion(partition, params, language, options, queryTokens, lastToken);
864
+ }
795
865
  let filterBitset;
796
866
  if (params.filters) {
797
867
  filterBitset = partition.applyFiltersBitset(params.filters, schema);
@@ -805,6 +875,7 @@ function fulltextSearch(partition, params, language, schema, options) {
805
875
  const collectComponents = params.includeScoreComponents === true || params.termMatch !== void 0 && params.termMatch !== "any";
806
876
  const rawResult = partition.searchFulltext({
807
877
  queryTokens,
878
+ prefixExpansion,
808
879
  fields: params.fields,
809
880
  boost: params.boost,
810
881
  tolerance: params.tolerance ?? 0,
@@ -820,7 +891,14 @@ function fulltextSearch(partition, params, language, schema, options) {
820
891
  let scored = rawResult.scored;
821
892
  const termMatch = params.termMatch ?? "any";
822
893
  if (termMatch !== "any") {
823
- scored = filterByTermCoverage(scored, queryTokens, termMatch, params.tolerance ?? 0, params.exact ?? false);
894
+ scored = filterByTermCoverage(
895
+ scored,
896
+ queryTokens,
897
+ termMatch,
898
+ params.tolerance ?? 0,
899
+ params.exact ?? false,
900
+ prefixExpansion
901
+ );
824
902
  }
825
903
  if (params.minScore !== void 0 && params.minScore > 0) {
826
904
  const threshold = params.minScore;
@@ -855,26 +933,40 @@ function deduplicateTokens(tokens) {
855
933
  }
856
934
  return result;
857
935
  }
858
- function filterByTermCoverage(scored, queryTokens, policy, tolerance, exact) {
936
+ function filterByTermCoverage(scored, queryTokens, policy, tolerance, exact, prefixExpansion) {
859
937
  const requiredCount = policy === "all" ? queryTokens.length : policy;
860
938
  if (requiredCount <= 0) return scored;
861
939
  if (requiredCount > queryTokens.length) return [];
940
+ const expansionTerms = prefixExpansion ? new Set(prefixExpansion.terms) : void 0;
862
941
  return scored.filter((doc) => {
863
- const matched = countDocTermMatches(doc, queryTokens, tolerance, exact);
942
+ const matched = countDocTermMatches(doc, queryTokens, tolerance, exact, prefixExpansion?.token, expansionTerms);
864
943
  return matched >= requiredCount;
865
944
  });
866
945
  }
867
- function countDocTermMatches(doc, queryTokens, tolerance, exact) {
946
+ function countDocTermMatches(doc, queryTokens, tolerance, exact, prefixToken, expansionTerms) {
868
947
  const indexTokens = Object.keys(doc.idf);
869
948
  if (indexTokens.length === 0) return 0;
870
949
  let count = 0;
871
950
  for (const qt of queryTokens) {
951
+ if (prefixToken !== void 0 && qt.token === prefixToken) {
952
+ if (prefixTokenSatisfied(qt.token, indexTokens, expansionTerms)) {
953
+ count++;
954
+ }
955
+ continue;
956
+ }
872
957
  if (queryTermSatisfied(qt.token, indexTokens, tolerance, exact)) {
873
958
  count++;
874
959
  }
875
960
  }
876
961
  return count;
877
962
  }
963
+ function prefixTokenSatisfied(prefixToken, indexTokens, expansionTerms) {
964
+ for (const indexToken of indexTokens) {
965
+ if (indexToken === prefixToken) return true;
966
+ if (expansionTerms?.has(indexToken)) return true;
967
+ }
968
+ return false;
969
+ }
878
970
  function queryTermSatisfied(queryToken, indexTokens, tolerance, exact) {
879
971
  if (exact || tolerance === 0) {
880
972
  return indexTokens.includes(queryToken);
@@ -1120,8 +1212,20 @@ function heapPop(heap) {
1120
1212
  }
1121
1213
  return top;
1122
1214
  }
1123
- function encodeRawPayloadV2(wire) {
1124
- return encode(wire);
1215
+ function sanitizeSurfaceForms(raw) {
1216
+ if (raw === void 0 || raw === null || typeof raw !== "object" || Array.isArray(raw)) return void 0;
1217
+ const result = /* @__PURE__ */ Object.create(null);
1218
+ let count = 0;
1219
+ for (const [surface, value] of Object.entries(raw)) {
1220
+ if (typeof value === "number") {
1221
+ result[surface] = value;
1222
+ count++;
1223
+ } else if (Array.isArray(value) && typeof value[0] === "number" && typeof value[1] === "string") {
1224
+ result[surface] = [value[0], value[1]];
1225
+ count++;
1226
+ }
1227
+ }
1228
+ return count > 0 ? result : void 0;
1125
1229
  }
1126
1230
  var VALID_HNSW_METRICS = /* @__PURE__ */ new Set(["cosine", "dotProduct", "euclidean"]);
1127
1231
  function validateHnswMetric(value) {
@@ -1131,6 +1235,153 @@ function validateHnswMetric(value) {
1131
1235
  }
1132
1236
  return void 0;
1133
1237
  }
1238
+ function wireToPartition(raw) {
1239
+ const documents = {};
1240
+ for (const [docId, doc] of Object.entries(raw.documents ?? {})) {
1241
+ documents[docId] = {
1242
+ fields: doc.fields,
1243
+ fieldLengths: doc.field_lengths ?? {}
1244
+ };
1245
+ }
1246
+ const invertedIndex = {};
1247
+ for (const [token, list] of Object.entries(raw.inverted_index ?? {})) {
1248
+ invertedIndex[token] = {
1249
+ docFrequency: list.doc_freq,
1250
+ postings: (list.postings ?? []).map((p) => ({
1251
+ docId: p.doc_id,
1252
+ termFrequency: p.term_freq,
1253
+ field: p.field,
1254
+ positions: p.positions ?? []
1255
+ }))
1256
+ };
1257
+ }
1258
+ const numeric = {};
1259
+ for (const [field, entries] of Object.entries(raw.field_indexes?.numeric ?? {})) {
1260
+ numeric[field] = entries.map((e) => ({ value: e.value, docId: e.doc_id }));
1261
+ }
1262
+ const boolean = {};
1263
+ for (const [field, idx] of Object.entries(raw.field_indexes?.boolean ?? {})) {
1264
+ boolean[field] = { trueDocs: idx.true_docs ?? [], falseDocs: idx.false_docs ?? [] };
1265
+ }
1266
+ const geopoint = {};
1267
+ for (const [field, entries] of Object.entries(raw.field_indexes?.geopoint ?? {})) {
1268
+ geopoint[field] = entries.map((e) => ({ lat: e.lat, lon: e.lon, docId: e.doc_id }));
1269
+ }
1270
+ const vectorData = {};
1271
+ for (const [field, data] of Object.entries(raw.vector_data ?? {})) {
1272
+ vectorData[field] = {
1273
+ dimension: data.dimension,
1274
+ vectors: (data.vectors ?? []).map((v) => ({ docId: v.doc_id, vector: v.vector })),
1275
+ hnswGraph: data.hnsw_graph ? {
1276
+ entryPoint: data.hnsw_graph.entry_point,
1277
+ maxLayer: data.hnsw_graph.max_layer,
1278
+ m: data.hnsw_graph.m,
1279
+ efConstruction: data.hnsw_graph.ef_construction,
1280
+ metric: validateHnswMetric(data.hnsw_graph.metric),
1281
+ nodes: data.hnsw_graph.nodes
1282
+ } : null,
1283
+ sq8: data.sq8 ? {
1284
+ alpha: data.sq8.alpha,
1285
+ offset: data.sq8.offset,
1286
+ quantizedVectors: data.sq8.quantized_vectors,
1287
+ vectorSums: data.sq8.vector_sums,
1288
+ vectorSumSqs: data.sq8.vector_sum_sqs
1289
+ } : null
1290
+ };
1291
+ }
1292
+ return {
1293
+ indexName: raw.index_name,
1294
+ partitionId: raw.partition_id,
1295
+ totalPartitions: raw.total_partitions,
1296
+ language: raw.language ?? "english",
1297
+ schema: raw.schema ?? {},
1298
+ docCount: raw.doc_count ?? 0,
1299
+ avgDocLength: raw.avg_doc_length ?? 0,
1300
+ documents,
1301
+ invertedIndex,
1302
+ fieldIndexes: {
1303
+ numeric,
1304
+ boolean,
1305
+ enum: raw.field_indexes?.enum ?? {},
1306
+ geopoint
1307
+ },
1308
+ 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"
1352
+ };
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
+ }
1368
+ function deserializeMetadata(data) {
1369
+ const raw = decode(data);
1370
+ return wireToMetadata(raw);
1371
+ }
1372
+
1373
+ // src/serialization/payload-v2.ts
1374
+ function encodeRawPayloadV2(wire) {
1375
+ return encode(wire);
1376
+ }
1377
+ var VALID_HNSW_METRICS2 = /* @__PURE__ */ new Set(["cosine", "dotProduct", "euclidean"]);
1378
+ function validateHnswMetric2(value) {
1379
+ if (value === void 0 || value === null) return void 0;
1380
+ if (typeof value === "string" && VALID_HNSW_METRICS2.has(value)) {
1381
+ return value;
1382
+ }
1383
+ return void 0;
1384
+ }
1134
1385
  function deserializePayloadV2(data) {
1135
1386
  const raw = decode(data);
1136
1387
  const documents = {};
@@ -1180,7 +1431,7 @@ function deserializePayloadV2(data) {
1180
1431
  maxLayer: data2.hnsw_graph.max_layer,
1181
1432
  m: data2.hnsw_graph.m,
1182
1433
  efConstruction: data2.hnsw_graph.ef_construction,
1183
- metric: validateHnswMetric(data2.hnsw_graph.metric),
1434
+ metric: validateHnswMetric2(data2.hnsw_graph.metric),
1184
1435
  nodes: data2.hnsw_graph.nodes
1185
1436
  } : null,
1186
1437
  sq8: data2.sq8 ? {
@@ -1208,6 +1459,7 @@ function deserializePayloadV2(data) {
1208
1459
  enum: raw.field_indexes?.enum ?? {},
1209
1460
  geopoint
1210
1461
  },
1462
+ surfaceForms: sanitizeSurfaceForms(raw.surface_forms),
1211
1463
  vectorData,
1212
1464
  statistics: {
1213
1465
  totalDocuments: raw.statistics?.total_documents ?? 0,
@@ -1374,7 +1626,8 @@ function createInvertedIndex(fieldNameTable) {
1374
1626
  fieldNameIndices: new Uint8Array(INITIAL_CAPACITY),
1375
1627
  positions: null,
1376
1628
  docIdSet: /* @__PURE__ */ new Set(),
1377
- deletedDocs: /* @__PURE__ */ new Set()
1629
+ deletedDocs: /* @__PURE__ */ new Set(),
1630
+ totalTermFrequency: 0
1378
1631
  };
1379
1632
  index.set(token, list);
1380
1633
  trackToken(token);
@@ -1404,6 +1657,8 @@ function createInvertedIndex(fieldNameTable) {
1404
1657
  }
1405
1658
  }
1406
1659
  writeIdx++;
1660
+ } else {
1661
+ list.totalTermFrequency -= list.termFrequencies[i];
1407
1662
  }
1408
1663
  }
1409
1664
  list.docIds.length = writeIdx;
@@ -1424,6 +1679,8 @@ function createInvertedIndex(fieldNameTable) {
1424
1679
  }
1425
1680
  }
1426
1681
  writeIdx++;
1682
+ } else {
1683
+ list.totalTermFrequency -= list.termFrequencies[i];
1427
1684
  }
1428
1685
  }
1429
1686
  list.docIds.length = writeIdx;
@@ -1444,6 +1701,7 @@ function createInvertedIndex(fieldNameTable) {
1444
1701
  list.docIds.push(internalId);
1445
1702
  list.termFrequencies[idx] = termFrequency > MAX_TERM_FREQUENCY ? MAX_TERM_FREQUENCY : termFrequency;
1446
1703
  list.fieldNameIndices[idx] = fieldNameIndex;
1704
+ list.totalTermFrequency += list.termFrequencies[idx];
1447
1705
  if (positions !== null) {
1448
1706
  if (!list.positions) {
1449
1707
  list.positions = [];
@@ -1571,12 +1829,14 @@ function createInvertedIndex(fieldNameTable) {
1571
1829
  const fieldNameIndices = new Uint8Array(count);
1572
1830
  let hasPositions = false;
1573
1831
  let validCount = 0;
1832
+ let totalTermFrequency = 0;
1574
1833
  for (let i = 0; i < count; i++) {
1575
1834
  const p = src.postings[i];
1576
1835
  const internalId = resolver.toInternal(p.docId);
1577
1836
  if (internalId === void 0) continue;
1578
1837
  docIds[validCount] = internalId;
1579
1838
  termFrequencies[validCount] = p.termFrequency > MAX_TERM_FREQUENCY ? MAX_TERM_FREQUENCY : p.termFrequency;
1839
+ totalTermFrequency += termFrequencies[validCount];
1580
1840
  let fnIndex = fieldNameTable.indexMap.get(p.fieldName);
1581
1841
  if (fnIndex === void 0) {
1582
1842
  fnIndex = fieldNameTable.names.length;
@@ -1610,7 +1870,8 @@ function createInvertedIndex(fieldNameTable) {
1610
1870
  fieldNameIndices: finalFNI,
1611
1871
  positions,
1612
1872
  docIdSet,
1613
- deletedDocs: /* @__PURE__ */ new Set()
1873
+ deletedDocs: /* @__PURE__ */ new Set(),
1874
+ totalTermFrequency
1614
1875
  });
1615
1876
  trackToken(token);
1616
1877
  }
@@ -1752,6 +2013,115 @@ function createPartitionStats() {
1752
2013
  return stats;
1753
2014
  }
1754
2015
 
2016
+ // src/core/surface-registry.ts
2017
+ function createSurfaceRegistry() {
2018
+ const entries = /* @__PURE__ */ new Map();
2019
+ const buckets = /* @__PURE__ */ new Map();
2020
+ let totalsByToken = null;
2021
+ function track(surface) {
2022
+ const ch = surface[0];
2023
+ let bucket = buckets.get(ch);
2024
+ if (!bucket) {
2025
+ bucket = /* @__PURE__ */ new Set();
2026
+ buckets.set(ch, bucket);
2027
+ }
2028
+ bucket.add(surface);
2029
+ }
2030
+ function untrack(surface) {
2031
+ const ch = surface[0];
2032
+ const bucket = buckets.get(ch);
2033
+ if (bucket) {
2034
+ bucket.delete(surface);
2035
+ if (bucket.size === 0) buckets.delete(ch);
2036
+ }
2037
+ }
2038
+ function adjustTotals(token, delta) {
2039
+ if (!totalsByToken || delta === 0) return;
2040
+ const next = (totalsByToken.get(token) ?? 0) + delta;
2041
+ if (next <= 0) totalsByToken.delete(token);
2042
+ else totalsByToken.set(token, next);
2043
+ }
2044
+ return {
2045
+ add(surface, token, occurrences) {
2046
+ if (surface.length === 0 || token.length === 0 || occurrences <= 0) return;
2047
+ if (surface === token) return;
2048
+ const existing = entries.get(surface);
2049
+ if (existing) {
2050
+ existing.count += occurrences;
2051
+ adjustTotals(existing.token, occurrences);
2052
+ return;
2053
+ }
2054
+ entries.set(surface, { token, count: occurrences });
2055
+ track(surface);
2056
+ adjustTotals(token, occurrences);
2057
+ },
2058
+ subtract(surface, occurrences) {
2059
+ if (occurrences <= 0) return;
2060
+ const existing = entries.get(surface);
2061
+ if (!existing) return;
2062
+ const removed = Math.min(occurrences, existing.count);
2063
+ existing.count -= occurrences;
2064
+ adjustTotals(existing.token, -removed);
2065
+ if (existing.count <= 0) {
2066
+ entries.delete(surface);
2067
+ untrack(surface);
2068
+ }
2069
+ },
2070
+ candidatesForPrefix(prefix) {
2071
+ if (prefix.length === 0) return [];
2072
+ const bucket = buckets.get(prefix[0]);
2073
+ if (!bucket) return [];
2074
+ const results = [];
2075
+ for (const surface of bucket) {
2076
+ if (surface.length < prefix.length || !surface.startsWith(prefix)) continue;
2077
+ const entry = entries.get(surface);
2078
+ if (!entry) continue;
2079
+ results.push({ surface, token: entry.token, occurrences: entry.count });
2080
+ }
2081
+ return results;
2082
+ },
2083
+ stemChangedTotalFor(token) {
2084
+ if (!totalsByToken) {
2085
+ totalsByToken = /* @__PURE__ */ new Map();
2086
+ for (const entry of entries.values()) {
2087
+ totalsByToken.set(entry.token, (totalsByToken.get(entry.token) ?? 0) + entry.count);
2088
+ }
2089
+ }
2090
+ return totalsByToken.get(token) ?? 0;
2091
+ },
2092
+ size() {
2093
+ return entries.size;
2094
+ },
2095
+ clear() {
2096
+ entries.clear();
2097
+ buckets.clear();
2098
+ totalsByToken = null;
2099
+ },
2100
+ serialize() {
2101
+ const result = /* @__PURE__ */ Object.create(null);
2102
+ for (const [surface, entry] of entries) {
2103
+ result[surface] = [entry.count, entry.token];
2104
+ }
2105
+ return result;
2106
+ },
2107
+ deserialize(data) {
2108
+ entries.clear();
2109
+ buckets.clear();
2110
+ totalsByToken = null;
2111
+ for (const surface of Object.keys(data)) {
2112
+ if (surface.length === 0) continue;
2113
+ const value = data[surface];
2114
+ if (!Array.isArray(value) || typeof value[0] !== "number" || typeof value[1] !== "string") continue;
2115
+ const count = value[0];
2116
+ const token = value[1];
2117
+ if (!Number.isFinite(count) || count <= 0 || token.length === 0 || token === surface) continue;
2118
+ entries.set(surface, { token, count: Math.floor(count) });
2119
+ track(surface);
2120
+ }
2121
+ }
2122
+ };
2123
+ }
2124
+
1755
2125
  // src/core/partition/utils.ts
1756
2126
  function getOrCreateFieldNameIndex(table, fieldName) {
1757
2127
  const existing = table.indexMap.get(fieldName);
@@ -1796,6 +2166,7 @@ function tokenizeOptions(options) {
1796
2166
  return {
1797
2167
  stem: true,
1798
2168
  removeStopWords: true,
2169
+ collectSurfaces: options?.collectSurfaces === true,
1799
2170
  stopWordOverride: options?.stopWordOverride,
1800
2171
  customTokenizer: options?.customTokenizer
1801
2172
  };
@@ -1881,1197 +2252,1241 @@ function computeFacets(state, docIds, config, schema) {
1881
2252
  return result;
1882
2253
  }
1883
2254
 
1884
- // src/filters/combinators.ts
1885
- function applyAndBitset(bitsets) {
1886
- if (bitsets.length === 0) return new Uint32Array(0);
1887
- if (bitsets.length === 1) return bitsets[0];
1888
- let result = bitsets[0];
1889
- for (let i = 1; i < bitsets.length; i++) {
1890
- result = bitsetAnd(result, bitsets[i]);
1891
- }
1892
- return result;
1893
- }
1894
- function applyOrBitset(bitsets) {
1895
- if (bitsets.length === 0) return new Uint32Array(0);
1896
- if (bitsets.length === 1) return bitsets[0];
1897
- let result = bitsets[0];
1898
- for (let i = 1; i < bitsets.length; i++) {
1899
- result = bitsetOr(result, bitsets[i]);
1900
- }
1901
- return result;
1902
- }
1903
- function applyNotBitset(excluded, capacity) {
1904
- return bitsetNot(excluded, capacity);
1905
- }
1906
-
1907
- // src/filters/operators/shared.ts
1908
- var METERS_PER_KM = 1e3;
1909
- var METERS_PER_MI = 1609.344;
1910
- function convertToMeters(distance, unit) {
1911
- if (unit === "km") return distance * METERS_PER_KM;
1912
- if (unit === "mi") return distance * METERS_PER_MI;
1913
- return distance;
1914
- }
1915
- function matchesNumericComparison(value, filter) {
1916
- if (filter.eq !== void 0 && value !== filter.eq) return false;
1917
- if (filter.ne !== void 0 && value === filter.ne) return false;
1918
- if (filter.gt !== void 0 && !(value > filter.gt)) return false;
1919
- if (filter.lt !== void 0 && !(value < filter.lt)) return false;
1920
- if (filter.gte !== void 0 && !(value >= filter.gte)) return false;
1921
- if (filter.lte !== void 0 && !(value <= filter.lte)) return false;
1922
- if (filter.between !== void 0 && !(value >= filter.between[0] && value <= filter.between[1])) return false;
1923
- return true;
2255
+ // src/geo/haversine.ts
2256
+ var EARTH_RADIUS_METERS = 63710088e-1;
2257
+ var DEG_TO_RAD = Math.PI / 180;
2258
+ function haversineDistance(lat1, lon1, lat2, lon2) {
2259
+ const phi1 = lat1 * DEG_TO_RAD;
2260
+ const phi2 = lat2 * DEG_TO_RAD;
2261
+ const deltaPhi = (lat2 - lat1) * DEG_TO_RAD;
2262
+ const deltaLambda = (lon2 - lon1) * DEG_TO_RAD;
2263
+ const halfDeltaPhi = Math.sin(deltaPhi / 2);
2264
+ const halfDeltaLambda = Math.sin(deltaLambda / 2);
2265
+ const a = halfDeltaPhi * halfDeltaPhi + Math.cos(phi1) * Math.cos(phi2) * halfDeltaLambda * halfDeltaLambda;
2266
+ const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
2267
+ return EARTH_RADIUS_METERS * c;
1924
2268
  }
1925
2269
 
1926
- // src/filters/operators/bitset.ts
1927
- function resolveBitset(bs) {
1928
- return typeof bs === "function" ? bs() : bs;
1929
- }
1930
- function scanToBitset(docIds, capacity, getValue, predicate) {
1931
- const resolved = resolveBitset(docIds);
1932
- const result = createBitSet(capacity);
1933
- for (let wi = 0; wi < resolved.length; wi++) {
1934
- let word = resolved[wi];
1935
- if (word === 0) continue;
1936
- const base = wi << 5;
1937
- while (word !== 0) {
1938
- const tz = Math.clz32(word & -word) ^ 31;
1939
- const id = base + tz;
1940
- if (predicate(getValue(id))) {
1941
- bitsetSet(result, id);
2270
+ // src/geo/polygon.ts
2271
+ function isPointInPolygon(lat, lon, polygon) {
2272
+ if (polygon.length < 3) return false;
2273
+ let inside = false;
2274
+ const n = polygon.length;
2275
+ for (let i = 0, j = n - 1; i < n; j = i++) {
2276
+ const lonI = polygon[i].lon;
2277
+ const latI = polygon[i].lat;
2278
+ const lonJ = polygon[j].lon;
2279
+ const latJ = polygon[j].lat;
2280
+ if (lonI > lon !== lonJ > lon) {
2281
+ const slope = (latJ - latI) / (lonJ - lonI);
2282
+ const intersectLat = latI + slope * (lon - lonI);
2283
+ if (lat < intersectLat) {
2284
+ inside = !inside;
1942
2285
  }
1943
- word &= word - 1;
1944
2286
  }
1945
2287
  }
1946
- return result;
2288
+ return inside;
1947
2289
  }
1948
- function applyEqBitset(value, allDocsBitset, capacity, getValue, fieldIndex) {
1949
- if (fieldIndex?.type === "numeric" && typeof value === "number") {
1950
- return fieldIndex.index.eqBitset(value, capacity);
1951
- }
1952
- if (fieldIndex?.type === "boolean" && typeof value === "boolean") {
1953
- return value ? fieldIndex.index.getTrueBitset(capacity) : fieldIndex.index.getFalseBitset(capacity);
1954
- }
1955
- if (fieldIndex?.type === "enum" && typeof value === "string") {
1956
- return fieldIndex.index.getDocIdsBitset(value, capacity);
1957
- }
1958
- return scanToBitset(allDocsBitset, capacity, getValue, (v) => v === value);
1959
- }
1960
- function applyNeBitset(value, allDocsBitset, capacity, getValue, fieldIndex) {
1961
- if (fieldIndex?.type === "numeric" && typeof value === "number") {
1962
- const all = fieldIndex.index.allDocIdsBitset(capacity);
1963
- const eq = fieldIndex.index.eqBitset(value, capacity);
1964
- return bitsetAnd(all, bitsetNot(eq, capacity));
1965
- }
1966
- if (fieldIndex?.type === "boolean" && typeof value === "boolean") {
1967
- return value ? fieldIndex.index.getFalseBitset(capacity) : fieldIndex.index.getTrueBitset(capacity);
1968
- }
1969
- if (fieldIndex?.type === "enum" && typeof value === "string") {
1970
- const all = fieldIndex.index.allDocIdsBitset(capacity);
1971
- const eq = fieldIndex.index.getDocIdsBitset(value, capacity);
1972
- return bitsetAnd(all, bitsetNot(eq, capacity));
1973
- }
1974
- return scanToBitset(allDocsBitset, capacity, getValue, (v) => v !== void 0 && v !== null && v !== value);
1975
- }
1976
- function applyGtBitset(value, allDocsBitset, capacity, getValue, fieldIndex) {
1977
- if (fieldIndex?.type === "numeric") {
1978
- return fieldIndex.index.gtBitset(value, capacity);
1979
- }
1980
- return scanToBitset(allDocsBitset, capacity, getValue, (v) => typeof v === "number" && v > value);
1981
- }
1982
- function applyLtBitset(value, allDocsBitset, capacity, getValue, fieldIndex) {
1983
- if (fieldIndex?.type === "numeric") {
1984
- return fieldIndex.index.ltBitset(value, capacity);
1985
- }
1986
- return scanToBitset(allDocsBitset, capacity, getValue, (v) => typeof v === "number" && v < value);
1987
- }
1988
- function applyGteBitset(value, allDocsBitset, capacity, getValue, fieldIndex) {
1989
- if (fieldIndex?.type === "numeric") {
1990
- return fieldIndex.index.gteBitset(value, capacity);
1991
- }
1992
- return scanToBitset(allDocsBitset, capacity, getValue, (v) => typeof v === "number" && v >= value);
1993
- }
1994
- function applyLteBitset(value, allDocsBitset, capacity, getValue, fieldIndex) {
1995
- if (fieldIndex?.type === "numeric") {
1996
- return fieldIndex.index.lteBitset(value, capacity);
1997
- }
1998
- return scanToBitset(allDocsBitset, capacity, getValue, (v) => typeof v === "number" && v <= value);
1999
- }
2000
- function applyBetweenBitset(range, allDocsBitset, capacity, getValue, fieldIndex) {
2001
- if (fieldIndex?.type === "numeric") {
2002
- return fieldIndex.index.betweenBitset(range[0], range[1], capacity);
2003
- }
2004
- return scanToBitset(allDocsBitset, capacity, getValue, (v) => typeof v === "number" && v >= range[0] && v <= range[1]);
2005
- }
2006
- function applyInBitset(values, allDocsBitset, capacity, getValue, fieldIndex) {
2007
- if (fieldIndex?.type === "enum") {
2008
- return fieldIndex.index.getDocIdsInBitset(values, capacity);
2009
- }
2010
- const valSet = new Set(values);
2011
- return scanToBitset(allDocsBitset, capacity, getValue, (v) => typeof v === "string" && valSet.has(v));
2012
- }
2013
- function applyNinBitset(values, allDocsBitset, capacity, getValue, fieldIndex) {
2014
- if (fieldIndex?.type === "enum") {
2015
- const all = fieldIndex.index.allDocIdsBitset(capacity);
2016
- const matched = fieldIndex.index.getDocIdsInBitset(values, capacity);
2017
- return bitsetAnd(all, bitsetNot(matched, capacity));
2290
+
2291
+ // src/geo/vincenty.ts
2292
+ var WGS84_A = 6378137;
2293
+ var WGS84_F = 1 / 298.257223563;
2294
+ var WGS84_B = WGS84_A * (1 - WGS84_F);
2295
+ var DEG_TO_RAD2 = Math.PI / 180;
2296
+ var MAX_ITERATIONS = 200;
2297
+ var CONVERGENCE_THRESHOLD = 1e-12;
2298
+ function vincentyDistance(lat1, lon1, lat2, lon2) {
2299
+ const phi1 = lat1 * DEG_TO_RAD2;
2300
+ const phi2 = lat2 * DEG_TO_RAD2;
2301
+ const L = (lon2 - lon1) * DEG_TO_RAD2;
2302
+ const U1 = Math.atan((1 - WGS84_F) * Math.tan(phi1));
2303
+ const U2 = Math.atan((1 - WGS84_F) * Math.tan(phi2));
2304
+ const sinU1 = Math.sin(U1);
2305
+ const cosU1 = Math.cos(U1);
2306
+ const sinU2 = Math.sin(U2);
2307
+ const cosU2 = Math.cos(U2);
2308
+ let lambda = L;
2309
+ let prevLambda;
2310
+ let sinSigma = 0;
2311
+ let cosSigma = 0;
2312
+ let sigma = 0;
2313
+ let sinAlpha = 0;
2314
+ let cos2Alpha = 0;
2315
+ let cos2SigmaM = 0;
2316
+ let iterations = MAX_ITERATIONS;
2317
+ do {
2318
+ const sinLambda = Math.sin(lambda);
2319
+ const cosLambda = Math.cos(lambda);
2320
+ const term1 = cosU2 * sinLambda;
2321
+ const term2 = cosU1 * sinU2 - sinU1 * cosU2 * cosLambda;
2322
+ sinSigma = Math.sqrt(term1 * term1 + term2 * term2);
2323
+ if (sinSigma === 0) return 0;
2324
+ cosSigma = sinU1 * sinU2 + cosU1 * cosU2 * cosLambda;
2325
+ sigma = Math.atan2(sinSigma, cosSigma);
2326
+ sinAlpha = cosU1 * cosU2 * sinLambda / sinSigma;
2327
+ cos2Alpha = 1 - sinAlpha * sinAlpha;
2328
+ cos2SigmaM = cos2Alpha === 0 ? 0 : cosSigma - 2 * sinU1 * sinU2 / cos2Alpha;
2329
+ const C = WGS84_F / 16 * cos2Alpha * (4 + WGS84_F * (4 - 3 * cos2Alpha));
2330
+ prevLambda = lambda;
2331
+ lambda = L + (1 - C) * WGS84_F * sinAlpha * (sigma + C * sinSigma * (cos2SigmaM + C * cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM)));
2332
+ } while (Math.abs(lambda - prevLambda) > CONVERGENCE_THRESHOLD && --iterations > 0);
2333
+ if (iterations === 0) {
2334
+ return haversineDistance(lat1, lon1, lat2, lon2);
2018
2335
  }
2019
- const valSet = new Set(values);
2020
- return scanToBitset(
2021
- allDocsBitset,
2022
- capacity,
2023
- getValue,
2024
- (v) => v !== void 0 && v !== null && typeof v === "string" && !valSet.has(v)
2025
- );
2026
- }
2027
- function applyStartsWithBitset(prefix, allDocsBitset, capacity, getValue) {
2028
- return scanToBitset(allDocsBitset, capacity, getValue, (v) => typeof v === "string" && v.startsWith(prefix));
2336
+ const uSq = cos2Alpha * (WGS84_A * WGS84_A - WGS84_B * WGS84_B) / (WGS84_B * WGS84_B);
2337
+ const A = 1 + uSq / 16384 * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq)));
2338
+ const B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq)));
2339
+ const deltaSigma = B * sinSigma * (cos2SigmaM + B / 4 * (cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM) - B / 6 * cos2SigmaM * (-3 + 4 * sinSigma * sinSigma) * (-3 + 4 * cos2SigmaM * cos2SigmaM)));
2340
+ return WGS84_B * A * (sigma - deltaSigma);
2029
2341
  }
2030
- function applyEndsWithBitset(suffix, allDocsBitset, capacity, getValue) {
2031
- return scanToBitset(allDocsBitset, capacity, getValue, (v) => typeof v === "string" && v.endsWith(suffix));
2342
+
2343
+ // src/geo/geo-index.ts
2344
+ function createGeoIndex() {
2345
+ const entries = [];
2346
+ return {
2347
+ get entries() {
2348
+ return entries;
2349
+ },
2350
+ insert(internalId, lat, lon) {
2351
+ entries.push({ lat, lon, docId: internalId });
2352
+ },
2353
+ remove(internalId) {
2354
+ for (let i = entries.length - 1; i >= 0; i--) {
2355
+ if (entries[i].docId === internalId) {
2356
+ entries.splice(i, 1);
2357
+ return;
2358
+ }
2359
+ }
2360
+ },
2361
+ radiusQuery(lat, lon, distanceMeters, inside, highPrecision) {
2362
+ const result = /* @__PURE__ */ new Set();
2363
+ const distanceFn = highPrecision ? vincentyDistance : haversineDistance;
2364
+ for (let i = 0; i < entries.length; i++) {
2365
+ const entry = entries[i];
2366
+ const dist = distanceFn(lat, lon, entry.lat, entry.lon);
2367
+ const withinRadius = dist <= distanceMeters;
2368
+ if (inside ? withinRadius : !withinRadius) {
2369
+ result.add(entry.docId);
2370
+ }
2371
+ }
2372
+ return result;
2373
+ },
2374
+ polygonQuery(points, inside) {
2375
+ const result = /* @__PURE__ */ new Set();
2376
+ for (let i = 0; i < entries.length; i++) {
2377
+ const entry = entries[i];
2378
+ const withinPolygon = isPointInPolygon(entry.lat, entry.lon, points);
2379
+ if (inside ? withinPolygon : !withinPolygon) {
2380
+ result.add(entry.docId);
2381
+ }
2382
+ }
2383
+ return result;
2384
+ },
2385
+ clear() {
2386
+ entries.length = 0;
2387
+ },
2388
+ serialize() {
2389
+ return entries.map((e) => ({ ...e }));
2390
+ },
2391
+ deserialize(data) {
2392
+ entries.length = 0;
2393
+ for (const entry of data) {
2394
+ entries.push({ lat: entry.lat, lon: entry.lon, docId: entry.docId });
2395
+ }
2396
+ }
2397
+ };
2032
2398
  }
2033
- function applyContainsAllBitset(values, allDocsBitset, capacity, getValue) {
2034
- return scanToBitset(allDocsBitset, capacity, getValue, (v) => {
2035
- if (!Array.isArray(v)) return false;
2036
- const arr = v;
2037
- for (const val of values) {
2038
- if (!arr.includes(val)) return false;
2399
+
2400
+ // src/core/field-index/boolean.ts
2401
+ function createBooleanIndex() {
2402
+ const trueDocs = /* @__PURE__ */ new Set();
2403
+ const falseDocs = /* @__PURE__ */ new Set();
2404
+ return {
2405
+ insert(internalId, value) {
2406
+ if (value) trueDocs.add(internalId);
2407
+ else falseDocs.add(internalId);
2408
+ },
2409
+ remove(internalId, value) {
2410
+ if (value) trueDocs.delete(internalId);
2411
+ else falseDocs.delete(internalId);
2412
+ },
2413
+ queryEq(value) {
2414
+ return new Set(value ? trueDocs : falseDocs);
2415
+ },
2416
+ queryNe(value) {
2417
+ return new Set(value ? falseDocs : trueDocs);
2418
+ },
2419
+ getAllDocIds() {
2420
+ const all = new Set(trueDocs);
2421
+ for (const id of falseDocs) all.add(id);
2422
+ return all;
2423
+ },
2424
+ queryEqBitset(value, capacity) {
2425
+ const source = value ? trueDocs : falseDocs;
2426
+ const bs = createBitSet(capacity);
2427
+ for (const id of source) {
2428
+ bitsetSet(bs, id);
2429
+ }
2430
+ return bs;
2431
+ },
2432
+ getAllDocIdsBitset(capacity) {
2433
+ const bs = createBitSet(capacity);
2434
+ for (const id of trueDocs) bitsetSet(bs, id);
2435
+ for (const id of falseDocs) bitsetSet(bs, id);
2436
+ return bs;
2437
+ },
2438
+ count() {
2439
+ return trueDocs.size + falseDocs.size;
2440
+ },
2441
+ clear() {
2442
+ trueDocs.clear();
2443
+ falseDocs.clear();
2444
+ },
2445
+ serialize() {
2446
+ return {
2447
+ trueDocs: Array.from(trueDocs),
2448
+ falseDocs: Array.from(falseDocs)
2449
+ };
2450
+ },
2451
+ deserialize(data) {
2452
+ trueDocs.clear();
2453
+ falseDocs.clear();
2454
+ for (const id of data.trueDocs) trueDocs.add(id);
2455
+ for (const id of data.falseDocs) falseDocs.add(id);
2039
2456
  }
2040
- return true;
2041
- });
2457
+ };
2042
2458
  }
2043
- function applyMatchesAnyBitset(values, allDocsBitset, capacity, getValue) {
2044
- const valSet = new Set(values);
2045
- return scanToBitset(allDocsBitset, capacity, getValue, (v) => {
2046
- if (!Array.isArray(v)) return false;
2047
- for (const item of v) {
2048
- if (valSet.has(item)) return true;
2459
+
2460
+ // src/core/field-index/enum.ts
2461
+ function createEnumIndex() {
2462
+ const valueMap = /* @__PURE__ */ new Map();
2463
+ function allDocs() {
2464
+ const result = /* @__PURE__ */ new Set();
2465
+ for (const docSet of valueMap.values()) {
2466
+ for (const id of docSet) result.add(id);
2467
+ }
2468
+ return result;
2469
+ }
2470
+ return {
2471
+ insert(internalId, value) {
2472
+ let docSet = valueMap.get(value);
2473
+ if (!docSet) {
2474
+ docSet = /* @__PURE__ */ new Set();
2475
+ valueMap.set(value, docSet);
2476
+ }
2477
+ docSet.add(internalId);
2478
+ },
2479
+ remove(internalId, value) {
2480
+ const docSet = valueMap.get(value);
2481
+ if (!docSet) return;
2482
+ docSet.delete(internalId);
2483
+ if (docSet.size === 0) valueMap.delete(value);
2484
+ },
2485
+ queryEq(value) {
2486
+ const docSet = valueMap.get(value);
2487
+ return docSet ? new Set(docSet) : /* @__PURE__ */ new Set();
2488
+ },
2489
+ queryNe(value) {
2490
+ const excluded = valueMap.get(value);
2491
+ const result = allDocs();
2492
+ if (excluded) {
2493
+ for (const id of excluded) result.delete(id);
2494
+ }
2495
+ return result;
2496
+ },
2497
+ queryIn(values) {
2498
+ const result = /* @__PURE__ */ new Set();
2499
+ for (const val of values) {
2500
+ const docSet = valueMap.get(val);
2501
+ if (docSet) {
2502
+ for (const id of docSet) result.add(id);
2503
+ }
2504
+ }
2505
+ return result;
2506
+ },
2507
+ queryNin(values) {
2508
+ const excluded = this.queryIn(values);
2509
+ const result = allDocs();
2510
+ for (const id of excluded) result.delete(id);
2511
+ return result;
2512
+ },
2513
+ getAllDocIds() {
2514
+ return allDocs();
2515
+ },
2516
+ queryEqBitset(value, capacity) {
2517
+ const bs = createBitSet(capacity);
2518
+ const docSet = valueMap.get(value);
2519
+ if (docSet) {
2520
+ for (const id of docSet) bitsetSet(bs, id);
2521
+ }
2522
+ return bs;
2523
+ },
2524
+ queryInBitset(values, capacity) {
2525
+ const bs = createBitSet(capacity);
2526
+ for (const val of values) {
2527
+ const docSet = valueMap.get(val);
2528
+ if (docSet) {
2529
+ for (const id of docSet) bitsetSet(bs, id);
2530
+ }
2531
+ }
2532
+ return bs;
2533
+ },
2534
+ getAllDocIdsBitset(capacity) {
2535
+ const bs = createBitSet(capacity);
2536
+ for (const docSet of valueMap.values()) {
2537
+ for (const id of docSet) bitsetSet(bs, id);
2538
+ }
2539
+ return bs;
2540
+ },
2541
+ count() {
2542
+ let total = 0;
2543
+ for (const docSet of valueMap.values()) total += docSet.size;
2544
+ return total;
2545
+ },
2546
+ clear() {
2547
+ valueMap.clear();
2548
+ },
2549
+ serialize() {
2550
+ const result = /* @__PURE__ */ Object.create(null);
2551
+ for (const [value, docSet] of valueMap) {
2552
+ result[value] = Array.from(docSet);
2553
+ }
2554
+ return result;
2555
+ },
2556
+ deserialize(data) {
2557
+ valueMap.clear();
2558
+ for (const value of Object.keys(data)) {
2559
+ valueMap.set(value, new Set(data[value]));
2560
+ }
2049
2561
  }
2050
- return false;
2051
- });
2052
- }
2053
- function applySizeBitset(sizeFilter, allDocsBitset, capacity, getValue) {
2054
- return scanToBitset(
2055
- allDocsBitset,
2056
- capacity,
2057
- getValue,
2058
- (v) => Array.isArray(v) && matchesNumericComparison(v.length, sizeFilter)
2059
- );
2060
- }
2061
- function applyExistsBitset(allDocsBitset, capacity, getValue) {
2062
- return scanToBitset(allDocsBitset, capacity, getValue, (v) => v !== void 0 && v !== null);
2063
- }
2064
- function applyNotExistsBitset(allDocsBitset, capacity, getValue) {
2065
- return scanToBitset(allDocsBitset, capacity, getValue, (v) => v === void 0 || v === null);
2066
- }
2067
- function applyIsEmptyBitset(allDocsBitset, capacity, getValue) {
2068
- return scanToBitset(allDocsBitset, capacity, getValue, (v) => {
2069
- if (v === void 0 || v === null) return true;
2070
- if (typeof v === "string" && v === "") return true;
2071
- if (Array.isArray(v) && v.length === 0) return true;
2072
- return false;
2073
- });
2074
- }
2075
- function applyIsNotEmptyBitset(allDocsBitset, capacity, getValue) {
2076
- return scanToBitset(allDocsBitset, capacity, getValue, (v) => {
2077
- if (v === void 0 || v === null) return false;
2078
- if (typeof v === "string" && v === "") return false;
2079
- if (Array.isArray(v) && v.length === 0) return false;
2080
- return true;
2081
- });
2082
- }
2083
- function applyGeoRadiusBitset(filter, geoIndex, capacity) {
2084
- const distanceMeters = convertToMeters(filter.distance, filter.unit);
2085
- const setResult = geoIndex.radiusQuery(
2086
- filter.lat,
2087
- filter.lon,
2088
- distanceMeters,
2089
- filter.inside ?? true,
2090
- filter.highPrecision ?? false
2091
- );
2092
- return bitsetFromSet(setResult, capacity);
2093
- }
2094
- function applyGeoPolygonBitset(filter, geoIndex, capacity) {
2095
- const setResult = geoIndex.polygonQuery(filter.points, filter.inside ?? true);
2096
- return bitsetFromSet(setResult, capacity);
2562
+ };
2097
2563
  }
2098
2564
 
2099
- // src/filters/evaluator.ts
2100
- function evaluateFilters(expression, context) {
2101
- const bitsets = [];
2102
- if (expression.fields) {
2103
- for (const [fieldPath, filter] of Object.entries(expression.fields)) {
2104
- bitsets.push(evaluateFieldFilter(fieldPath, filter, context));
2105
- }
2565
+ // src/core/field-index/numeric.ts
2566
+ function lowerBound(entries, value) {
2567
+ let lo = 0;
2568
+ let hi = entries.length;
2569
+ while (lo < hi) {
2570
+ const mid = lo + hi >>> 1;
2571
+ if (entries[mid].value < value) lo = mid + 1;
2572
+ else hi = mid;
2106
2573
  }
2107
- if (expression.and?.length) {
2108
- const andBitsets = expression.and.map((expr) => evaluateFilters(expr, context));
2109
- bitsets.push(applyAndBitset(andBitsets));
2574
+ return lo;
2575
+ }
2576
+ function upperBound(entries, value) {
2577
+ let lo = 0;
2578
+ let hi = entries.length;
2579
+ while (lo < hi) {
2580
+ const mid = lo + hi >>> 1;
2581
+ if (entries[mid].value <= value) lo = mid + 1;
2582
+ else hi = mid;
2110
2583
  }
2111
- if (expression.or?.length) {
2112
- const orBitsets = expression.or.map((expr) => evaluateFilters(expr, context));
2113
- bitsets.push(applyOrBitset(orBitsets));
2584
+ return lo;
2585
+ }
2586
+ function collectDocIds(entries, from, to) {
2587
+ const result = /* @__PURE__ */ new Set();
2588
+ for (let i = from; i < to; i++) {
2589
+ result.add(entries[i].docId);
2114
2590
  }
2115
- if (expression.not) {
2116
- const excluded = evaluateFilters(expression.not, context);
2117
- const universe = context.allDocIdsBitset;
2118
- bitsets.push(applyAndBitset([universe, applyNotBitset(excluded, context.capacity)]));
2591
+ return result;
2592
+ }
2593
+ function collectDocIdsBitset(entries, from, to, capacity) {
2594
+ const bs = createBitSet(capacity);
2595
+ for (let i = from; i < to; i++) {
2596
+ bitsetSet(bs, entries[i].docId);
2119
2597
  }
2120
- if (bitsets.length === 0) return context.allDocIdsBitset;
2121
- if (bitsets.length === 1) return bitsets[0];
2122
- return applyAndBitset(bitsets);
2598
+ return bs;
2123
2599
  }
2124
- function evaluateFieldFilter(fieldPath, filter, context) {
2125
- const fieldIndex = context.fieldIndexes[fieldPath];
2126
- const getValue = (internalId) => context.getFieldValue(internalId, fieldPath);
2127
- const f = filter;
2128
- const bitsets = [];
2129
- const { capacity } = context;
2130
- let allDocsBitset = null;
2131
- const getAllDocsBitset = () => {
2132
- if (allDocsBitset === null) {
2133
- allDocsBitset = context.allDocIdsBitset;
2600
+ function createNumericIndex() {
2601
+ let entries = [];
2602
+ let sorted = true;
2603
+ function ensureSorted() {
2604
+ if (!sorted) {
2605
+ entries.sort((a, b) => a.value - b.value);
2606
+ sorted = true;
2134
2607
  }
2135
- return allDocsBitset;
2136
- };
2137
- if ("radius" in f && f.radius) {
2138
- const geoIndex = resolveGeoIndex(fieldPath, fieldIndex);
2139
- bitsets.push(applyGeoRadiusBitset(filter.radius, geoIndex, capacity));
2140
- }
2141
- if ("polygon" in f && f.polygon) {
2142
- const geoIndex = resolveGeoIndex(fieldPath, fieldIndex);
2143
- bitsets.push(applyGeoPolygonBitset(filter.polygon, geoIndex, capacity));
2144
- }
2145
- if ("eq" in f && f.eq !== void 0) {
2146
- bitsets.push(applyEqBitset(f.eq, getAllDocsBitset, capacity, getValue, fieldIndex));
2147
- }
2148
- if ("ne" in f && f.ne !== void 0) {
2149
- bitsets.push(applyNeBitset(f.ne, getAllDocsBitset, capacity, getValue, fieldIndex));
2150
- }
2151
- if ("gt" in f && f.gt !== void 0) {
2152
- bitsets.push(applyGtBitset(f.gt, getAllDocsBitset, capacity, getValue, fieldIndex));
2153
- }
2154
- if ("lt" in f && f.lt !== void 0) {
2155
- bitsets.push(applyLtBitset(f.lt, getAllDocsBitset, capacity, getValue, fieldIndex));
2156
- }
2157
- if ("gte" in f && f.gte !== void 0) {
2158
- bitsets.push(applyGteBitset(f.gte, getAllDocsBitset, capacity, getValue, fieldIndex));
2159
- }
2160
- if ("lte" in f && f.lte !== void 0) {
2161
- bitsets.push(applyLteBitset(f.lte, getAllDocsBitset, capacity, getValue, fieldIndex));
2162
- }
2163
- if ("between" in f && f.between !== void 0) {
2164
- bitsets.push(applyBetweenBitset(f.between, getAllDocsBitset, capacity, getValue, fieldIndex));
2165
- }
2166
- if ("in" in f && f.in !== void 0) {
2167
- bitsets.push(applyInBitset(f.in, getAllDocsBitset, capacity, getValue, fieldIndex));
2168
- }
2169
- if ("nin" in f && f.nin !== void 0) {
2170
- bitsets.push(applyNinBitset(f.nin, getAllDocsBitset, capacity, getValue, fieldIndex));
2171
- }
2172
- if ("startsWith" in f && f.startsWith !== void 0) {
2173
- bitsets.push(applyStartsWithBitset(f.startsWith, getAllDocsBitset, capacity, getValue));
2174
- }
2175
- if ("endsWith" in f && f.endsWith !== void 0) {
2176
- bitsets.push(applyEndsWithBitset(f.endsWith, getAllDocsBitset, capacity, getValue));
2177
- }
2178
- if ("containsAll" in f && f.containsAll !== void 0) {
2179
- bitsets.push(
2180
- applyContainsAllBitset(f.containsAll, getAllDocsBitset, capacity, getValue)
2181
- );
2182
- }
2183
- if ("matchesAny" in f && f.matchesAny !== void 0) {
2184
- bitsets.push(
2185
- applyMatchesAnyBitset(f.matchesAny, getAllDocsBitset, capacity, getValue)
2186
- );
2187
2608
  }
2188
- if ("size" in f && f.size !== void 0) {
2189
- bitsets.push(applySizeBitset(f.size, getAllDocsBitset, capacity, getValue));
2190
- }
2191
- if ("exists" in f && f.exists !== void 0) {
2192
- bitsets.push(
2193
- f.exists ? applyExistsBitset(getAllDocsBitset, capacity, getValue) : applyNotExistsBitset(getAllDocsBitset, capacity, getValue)
2194
- );
2609
+ return {
2610
+ insert(internalId, value) {
2611
+ entries.push({ value, docId: internalId });
2612
+ sorted = false;
2613
+ },
2614
+ remove(internalId, value) {
2615
+ ensureSorted();
2616
+ const start = lowerBound(entries, value);
2617
+ const end = upperBound(entries, value);
2618
+ for (let i = start; i < end; i++) {
2619
+ if (entries[i].docId === internalId) {
2620
+ entries.splice(i, 1);
2621
+ return;
2622
+ }
2623
+ }
2624
+ },
2625
+ queryEq(value) {
2626
+ ensureSorted();
2627
+ return collectDocIds(entries, lowerBound(entries, value), upperBound(entries, value));
2628
+ },
2629
+ queryNe(value) {
2630
+ ensureSorted();
2631
+ const excluded = this.queryEq(value);
2632
+ const result = /* @__PURE__ */ new Set();
2633
+ for (const entry of entries) {
2634
+ if (!excluded.has(entry.docId)) result.add(entry.docId);
2635
+ }
2636
+ return result;
2637
+ },
2638
+ queryGt(value) {
2639
+ ensureSorted();
2640
+ return collectDocIds(entries, upperBound(entries, value), entries.length);
2641
+ },
2642
+ queryGte(value) {
2643
+ ensureSorted();
2644
+ return collectDocIds(entries, lowerBound(entries, value), entries.length);
2645
+ },
2646
+ queryLt(value) {
2647
+ ensureSorted();
2648
+ return collectDocIds(entries, 0, lowerBound(entries, value));
2649
+ },
2650
+ queryLte(value) {
2651
+ ensureSorted();
2652
+ return collectDocIds(entries, 0, upperBound(entries, value));
2653
+ },
2654
+ queryBetween(min, max) {
2655
+ ensureSorted();
2656
+ return collectDocIds(entries, lowerBound(entries, min), upperBound(entries, max));
2657
+ },
2658
+ getAllDocIds() {
2659
+ return collectDocIds(entries, 0, entries.length);
2660
+ },
2661
+ queryEqBitset(value, capacity) {
2662
+ ensureSorted();
2663
+ return collectDocIdsBitset(entries, lowerBound(entries, value), upperBound(entries, value), capacity);
2664
+ },
2665
+ queryGtBitset(value, capacity) {
2666
+ ensureSorted();
2667
+ return collectDocIdsBitset(entries, upperBound(entries, value), entries.length, capacity);
2668
+ },
2669
+ queryGteBitset(value, capacity) {
2670
+ ensureSorted();
2671
+ return collectDocIdsBitset(entries, lowerBound(entries, value), entries.length, capacity);
2672
+ },
2673
+ queryLtBitset(value, capacity) {
2674
+ ensureSorted();
2675
+ return collectDocIdsBitset(entries, 0, lowerBound(entries, value), capacity);
2676
+ },
2677
+ queryLteBitset(value, capacity) {
2678
+ ensureSorted();
2679
+ return collectDocIdsBitset(entries, 0, upperBound(entries, value), capacity);
2680
+ },
2681
+ queryBetweenBitset(min, max, capacity) {
2682
+ ensureSorted();
2683
+ return collectDocIdsBitset(entries, lowerBound(entries, min), upperBound(entries, max), capacity);
2684
+ },
2685
+ getAllDocIdsBitset(capacity) {
2686
+ return collectDocIdsBitset(entries, 0, entries.length, capacity);
2687
+ },
2688
+ count() {
2689
+ return entries.length;
2690
+ },
2691
+ clear() {
2692
+ entries = [];
2693
+ sorted = true;
2694
+ },
2695
+ serialize() {
2696
+ ensureSorted();
2697
+ return entries.slice();
2698
+ },
2699
+ deserialize(data) {
2700
+ entries = data.slice();
2701
+ sorted = true;
2702
+ }
2703
+ };
2704
+ }
2705
+
2706
+ // src/core/partition/indexing.ts
2707
+ function ensureFieldIndex(state, fieldPath, fieldType) {
2708
+ if ((fieldType === "number" || fieldType === "number[]") && !state.numericIndexes.has(fieldPath)) {
2709
+ state.numericIndexes.set(fieldPath, createNumericIndex());
2710
+ } else if ((fieldType === "boolean" || fieldType === "boolean[]") && !state.booleanIndexes.has(fieldPath)) {
2711
+ state.booleanIndexes.set(fieldPath, createBooleanIndex());
2712
+ } else if ((fieldType === "enum" || fieldType === "enum[]") && !state.enumIndexes.has(fieldPath)) {
2713
+ state.enumIndexes.set(fieldPath, createEnumIndex());
2714
+ } else if (fieldType === "geopoint" && !state.geoIndexes.has(fieldPath)) {
2715
+ state.geoIndexes.set(fieldPath, createGeoIndex());
2195
2716
  }
2196
- if ("notExists" in f && f.notExists !== void 0) {
2197
- bitsets.push(
2198
- f.notExists ? applyNotExistsBitset(getAllDocsBitset, capacity, getValue) : applyExistsBitset(getAllDocsBitset, capacity, getValue)
2199
- );
2717
+ }
2718
+ function getOrCreateNumericIndex(state, fieldPath) {
2719
+ let idx = state.numericIndexes.get(fieldPath);
2720
+ if (!idx) {
2721
+ idx = createNumericIndex();
2722
+ state.numericIndexes.set(fieldPath, idx);
2200
2723
  }
2201
- if ("isEmpty" in f && f.isEmpty !== void 0) {
2202
- bitsets.push(
2203
- f.isEmpty ? applyIsEmptyBitset(getAllDocsBitset, capacity, getValue) : applyIsNotEmptyBitset(getAllDocsBitset, capacity, getValue)
2204
- );
2724
+ return idx;
2725
+ }
2726
+ function getOrCreateBooleanIndex(state, fieldPath) {
2727
+ let idx = state.booleanIndexes.get(fieldPath);
2728
+ if (!idx) {
2729
+ idx = createBooleanIndex();
2730
+ state.booleanIndexes.set(fieldPath, idx);
2205
2731
  }
2206
- if ("isNotEmpty" in f && f.isNotEmpty !== void 0) {
2207
- bitsets.push(
2208
- f.isNotEmpty ? applyIsNotEmptyBitset(getAllDocsBitset, capacity, getValue) : applyIsEmptyBitset(getAllDocsBitset, capacity, getValue)
2209
- );
2732
+ return idx;
2733
+ }
2734
+ function resolveInternalId(state, docId) {
2735
+ const internalId = state.docStore.getInternalId(docId);
2736
+ if (internalId === void 0) {
2737
+ throw new Error(`Internal ID not found for document "${docId}"`);
2210
2738
  }
2211
- if (bitsets.length === 0) return context.allDocIdsBitset;
2212
- if (bitsets.length === 1) return bitsets[0];
2213
- return applyAndBitset(bitsets);
2739
+ return internalId;
2214
2740
  }
2215
- function resolveGeoIndex(fieldPath, fieldIndex) {
2216
- if (fieldIndex?.type !== "geopoint") {
2217
- throw new NarsilError(
2218
- ErrorCodes.SEARCH_INVALID_FILTER,
2219
- `Field "${fieldPath}" requires a geopoint index for geo filters`,
2220
- { fieldPath }
2221
- );
2741
+ function countSurface(counts, surface, token) {
2742
+ if (surface === void 0 || surface.length === 0) return;
2743
+ const existing = counts.get(surface);
2744
+ if (existing) {
2745
+ existing.count++;
2746
+ } else {
2747
+ counts.set(surface, { token, count: 1 });
2222
2748
  }
2223
- return fieldIndex.index;
2224
2749
  }
2225
-
2226
- // src/core/partition/filters.ts
2227
- function buildFilterContext(state, schema) {
2228
- const flat = getFlatSchema(state, schema);
2229
- const fieldIndexes = {};
2230
- const capacity = state.docStore.internalIdCapacity();
2231
- for (const [fieldPath, fieldType] of Object.entries(flat)) {
2232
- if (fieldType === "number" || fieldType === "number[]") {
2233
- const numIdx = state.numericIndexes.get(fieldPath);
2234
- if (numIdx) {
2235
- fieldIndexes[fieldPath] = {
2236
- type: "numeric",
2237
- index: {
2238
- eq: (v) => numIdx.queryEq(v),
2239
- gt: (v) => numIdx.queryGt(v),
2240
- gte: (v) => numIdx.queryGte(v),
2241
- lt: (v) => numIdx.queryLt(v),
2242
- lte: (v) => numIdx.queryLte(v),
2243
- between: (min, max) => numIdx.queryBetween(min, max),
2244
- allDocIds: () => numIdx.getAllDocIds(),
2245
- eqBitset: (v, cap) => numIdx.queryEqBitset(v, cap),
2246
- gtBitset: (v, cap) => numIdx.queryGtBitset(v, cap),
2247
- gteBitset: (v, cap) => numIdx.queryGteBitset(v, cap),
2248
- ltBitset: (v, cap) => numIdx.queryLtBitset(v, cap),
2249
- lteBitset: (v, cap) => numIdx.queryLteBitset(v, cap),
2250
- betweenBitset: (min, max, cap) => numIdx.queryBetweenBitset(min, max, cap),
2251
- allDocIdsBitset: (cap) => numIdx.getAllDocIdsBitset(cap)
2252
- }
2253
- };
2254
- }
2255
- } else if (fieldType === "boolean" || fieldType === "boolean[]") {
2256
- const boolIdx = state.booleanIndexes.get(fieldPath);
2257
- if (boolIdx) {
2258
- fieldIndexes[fieldPath] = {
2259
- type: "boolean",
2260
- index: {
2261
- getTrue: () => boolIdx.queryEq(true),
2262
- getFalse: () => boolIdx.queryEq(false),
2263
- allDocIds: () => boolIdx.getAllDocIds(),
2264
- getTrueBitset: (cap) => boolIdx.queryEqBitset(true, cap),
2265
- getFalseBitset: (cap) => boolIdx.queryEqBitset(false, cap),
2266
- allDocIdsBitset: (cap) => boolIdx.getAllDocIdsBitset(cap)
2267
- }
2268
- };
2269
- }
2270
- } else if (fieldType === "enum" || fieldType === "enum[]") {
2271
- const enumIdx = state.enumIndexes.get(fieldPath);
2272
- if (enumIdx) {
2273
- fieldIndexes[fieldPath] = {
2274
- type: "enum",
2275
- index: {
2276
- getDocIds: (v) => enumIdx.queryEq(v),
2277
- allDocIds: () => enumIdx.getAllDocIds(),
2278
- getDocIdsBitset: (v, cap) => enumIdx.queryEqBitset(v, cap),
2279
- getDocIdsInBitset: (values, cap) => enumIdx.queryInBitset(values, cap),
2280
- allDocIdsBitset: (cap) => enumIdx.getAllDocIdsBitset(cap)
2281
- }
2282
- };
2283
- }
2284
- } else if (fieldType === "geopoint") {
2285
- const geoIdx = state.geoIndexes.get(fieldPath);
2286
- if (geoIdx) {
2287
- fieldIndexes[fieldPath] = {
2288
- type: "geopoint",
2289
- index: geoIdx
2290
- };
2291
- }
2750
+ function applySurfaceCounts(state, counts, direction) {
2751
+ for (const [surface, entry] of counts) {
2752
+ if (direction === 1) {
2753
+ state.surfaceRegistry.add(surface, entry.token, entry.count);
2754
+ } else {
2755
+ state.surfaceRegistry.subtract(surface, entry.count);
2292
2756
  }
2293
2757
  }
2294
- let cachedAllDocIds = null;
2295
- let cachedAllDocIdsBitset = null;
2296
- return {
2297
- fieldIndexes,
2298
- getFieldValue: (internalId, fieldPath) => getFieldValueByInternalId(state.docStore, internalId, fieldPath),
2299
- get allDocIds() {
2300
- if (!cachedAllDocIds) {
2301
- cachedAllDocIds = getAllInternalDocIds(state.docStore);
2302
- }
2303
- return cachedAllDocIds;
2304
- },
2305
- capacity,
2306
- get allDocIdsBitset() {
2307
- if (!cachedAllDocIdsBitset) {
2308
- cachedAllDocIdsBitset = bitsetFromSet(this.allDocIds, capacity);
2309
- }
2310
- return cachedAllDocIdsBitset;
2311
- }
2312
- };
2313
2758
  }
2314
- function applyPartitionFilters(state, filters, schema) {
2315
- const context = buildFilterContext(state, schema);
2316
- const resultBitset = evaluateFilters(filters, context);
2317
- const resolver = state.docStore.resolver();
2318
- const externalResult = /* @__PURE__ */ new Set();
2319
- for (let wi = 0; wi < resultBitset.length; wi++) {
2320
- let word = resultBitset[wi];
2321
- if (word === 0) continue;
2322
- const base = wi << 5;
2323
- while (word !== 0) {
2324
- const tz = Math.clz32(word & -word) ^ 31;
2325
- const internalId = base + tz;
2326
- const externalId = resolver.toExternal(internalId);
2327
- if (externalId !== void 0) {
2328
- externalResult.add(externalId);
2759
+ function indexStringField(state, internalId, fieldPath, text, language, options, fieldLengths, tokensByField) {
2760
+ const fieldTokenList = [];
2761
+ const fieldNameIndex = getOrCreateFieldNameIndex(state.fieldNameTable, fieldPath);
2762
+ const opts = tokenizeOptions(options);
2763
+ const surfaceCounts = /* @__PURE__ */ new Map();
2764
+ if (state.trackPositions) {
2765
+ const tokenFreqs = /* @__PURE__ */ new Map();
2766
+ for (const t of tokenizeIterator(text, language, opts)) {
2767
+ const existing = tokenFreqs.get(t.token);
2768
+ if (existing) {
2769
+ existing.count++;
2770
+ existing.positions.push(t.position);
2771
+ } else {
2772
+ tokenFreqs.set(t.token, { count: 1, positions: [t.position] });
2329
2773
  }
2330
- word &= word - 1;
2774
+ fieldTokenList.push(t.token);
2775
+ countSurface(surfaceCounts, t.surface, t.token);
2776
+ }
2777
+ for (const [token, freq] of tokenFreqs) {
2778
+ state.invertedIdx.insert(token, internalId, freq.count, fieldNameIndex, freq.positions);
2779
+ }
2780
+ } else {
2781
+ const tokenCounts = /* @__PURE__ */ new Map();
2782
+ for (const t of tokenizeIterator(text, language, opts)) {
2783
+ tokenCounts.set(t.token, (tokenCounts.get(t.token) ?? 0) + 1);
2784
+ fieldTokenList.push(t.token);
2785
+ countSurface(surfaceCounts, t.surface, t.token);
2786
+ }
2787
+ for (const [token, count] of tokenCounts) {
2788
+ state.invertedIdx.insert(token, internalId, count, fieldNameIndex, null);
2331
2789
  }
2332
2790
  }
2333
- return externalResult;
2334
- }
2335
- function applyPartitionFiltersBitset(state, filters, schema) {
2336
- const context = buildFilterContext(state, schema);
2337
- return evaluateFilters(filters, context);
2338
- }
2339
-
2340
- // src/geo/haversine.ts
2341
- var EARTH_RADIUS_METERS = 63710088e-1;
2342
- var DEG_TO_RAD = Math.PI / 180;
2343
- function haversineDistance(lat1, lon1, lat2, lon2) {
2344
- const phi1 = lat1 * DEG_TO_RAD;
2345
- const phi2 = lat2 * DEG_TO_RAD;
2346
- const deltaPhi = (lat2 - lat1) * DEG_TO_RAD;
2347
- const deltaLambda = (lon2 - lon1) * DEG_TO_RAD;
2348
- const halfDeltaPhi = Math.sin(deltaPhi / 2);
2349
- const halfDeltaLambda = Math.sin(deltaLambda / 2);
2350
- const a = halfDeltaPhi * halfDeltaPhi + Math.cos(phi1) * Math.cos(phi2) * halfDeltaLambda * halfDeltaLambda;
2351
- const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
2352
- return EARTH_RADIUS_METERS * c;
2791
+ applySurfaceCounts(state, surfaceCounts, 1);
2792
+ fieldLengths[fieldPath] = fieldTokenList.length;
2793
+ tokensByField[fieldPath] = fieldTokenList;
2353
2794
  }
2354
-
2355
- // src/geo/polygon.ts
2356
- function isPointInPolygon(lat, lon, polygon) {
2357
- if (polygon.length < 3) return false;
2358
- let inside = false;
2359
- const n = polygon.length;
2360
- for (let i = 0, j = n - 1; i < n; j = i++) {
2361
- const lonI = polygon[i].lon;
2362
- const latI = polygon[i].lat;
2363
- const lonJ = polygon[j].lon;
2364
- const latJ = polygon[j].lat;
2365
- if (lonI > lon !== lonJ > lon) {
2366
- const slope = (latJ - latI) / (lonJ - lonI);
2367
- const intersectLat = latI + slope * (lon - lonI);
2368
- if (lat < intersectLat) {
2369
- inside = !inside;
2795
+ function indexStringArrayField(state, internalId, fieldPath, arr, language, options, fieldLengths, tokensByField) {
2796
+ const fieldTokenList = [];
2797
+ const fieldNameIndex = getOrCreateFieldNameIndex(state.fieldNameTable, fieldPath);
2798
+ const opts = tokenizeOptions(options);
2799
+ const surfaceCounts = /* @__PURE__ */ new Map();
2800
+ if (state.trackPositions) {
2801
+ const tokenFreqs = /* @__PURE__ */ new Map();
2802
+ let positionOffset = 0;
2803
+ for (const item of arr) {
2804
+ let itemTokenCount = 0;
2805
+ for (const t of tokenizeIterator(item, language, opts)) {
2806
+ const existing = tokenFreqs.get(t.token);
2807
+ if (existing) {
2808
+ existing.count++;
2809
+ existing.positions.push(positionOffset + t.position);
2810
+ } else {
2811
+ tokenFreqs.set(t.token, { count: 1, positions: [positionOffset + t.position] });
2812
+ }
2813
+ fieldTokenList.push(t.token);
2814
+ countSurface(surfaceCounts, t.surface, t.token);
2815
+ itemTokenCount++;
2816
+ }
2817
+ positionOffset += itemTokenCount;
2818
+ }
2819
+ fieldLengths[fieldPath] = fieldTokenList.length;
2820
+ for (const [token, freq] of tokenFreqs) {
2821
+ state.invertedIdx.insert(token, internalId, freq.count, fieldNameIndex, freq.positions);
2822
+ }
2823
+ } else {
2824
+ const tokenCounts = /* @__PURE__ */ new Map();
2825
+ for (const item of arr) {
2826
+ for (const t of tokenizeIterator(item, language, opts)) {
2827
+ tokenCounts.set(t.token, (tokenCounts.get(t.token) ?? 0) + 1);
2828
+ fieldTokenList.push(t.token);
2829
+ countSurface(surfaceCounts, t.surface, t.token);
2370
2830
  }
2371
2831
  }
2832
+ fieldLengths[fieldPath] = fieldTokenList.length;
2833
+ for (const [token, count] of tokenCounts) {
2834
+ state.invertedIdx.insert(token, internalId, count, fieldNameIndex, null);
2835
+ }
2372
2836
  }
2373
- return inside;
2837
+ applySurfaceCounts(state, surfaceCounts, 1);
2838
+ tokensByField[fieldPath] = fieldTokenList;
2374
2839
  }
2375
-
2376
- // src/geo/vincenty.ts
2377
- var WGS84_A = 6378137;
2378
- var WGS84_F = 1 / 298.257223563;
2379
- var WGS84_B = WGS84_A * (1 - WGS84_F);
2380
- var DEG_TO_RAD2 = Math.PI / 180;
2381
- var MAX_ITERATIONS = 200;
2382
- var CONVERGENCE_THRESHOLD = 1e-12;
2383
- function vincentyDistance(lat1, lon1, lat2, lon2) {
2384
- const phi1 = lat1 * DEG_TO_RAD2;
2385
- const phi2 = lat2 * DEG_TO_RAD2;
2386
- const L = (lon2 - lon1) * DEG_TO_RAD2;
2387
- const U1 = Math.atan((1 - WGS84_F) * Math.tan(phi1));
2388
- const U2 = Math.atan((1 - WGS84_F) * Math.tan(phi2));
2389
- const sinU1 = Math.sin(U1);
2390
- const cosU1 = Math.cos(U1);
2391
- const sinU2 = Math.sin(U2);
2392
- const cosU2 = Math.cos(U2);
2393
- let lambda = L;
2394
- let prevLambda;
2395
- let sinSigma = 0;
2396
- let cosSigma = 0;
2397
- let sigma = 0;
2398
- let sinAlpha = 0;
2399
- let cos2Alpha = 0;
2400
- let cos2SigmaM = 0;
2401
- let iterations = MAX_ITERATIONS;
2402
- do {
2403
- const sinLambda = Math.sin(lambda);
2404
- const cosLambda = Math.cos(lambda);
2405
- const term1 = cosU2 * sinLambda;
2406
- const term2 = cosU1 * sinU2 - sinU1 * cosU2 * cosLambda;
2407
- sinSigma = Math.sqrt(term1 * term1 + term2 * term2);
2408
- if (sinSigma === 0) return 0;
2409
- cosSigma = sinU1 * sinU2 + cosU1 * cosU2 * cosLambda;
2410
- sigma = Math.atan2(sinSigma, cosSigma);
2411
- sinAlpha = cosU1 * cosU2 * sinLambda / sinSigma;
2412
- cos2Alpha = 1 - sinAlpha * sinAlpha;
2413
- cos2SigmaM = cos2Alpha === 0 ? 0 : cosSigma - 2 * sinU1 * sinU2 / cos2Alpha;
2414
- const C = WGS84_F / 16 * cos2Alpha * (4 + WGS84_F * (4 - 3 * cos2Alpha));
2415
- prevLambda = lambda;
2416
- lambda = L + (1 - C) * WGS84_F * sinAlpha * (sigma + C * sinSigma * (cos2SigmaM + C * cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM)));
2417
- } while (Math.abs(lambda - prevLambda) > CONVERGENCE_THRESHOLD && --iterations > 0);
2418
- if (iterations === 0) {
2419
- return haversineDistance(lat1, lon1, lat2, lon2);
2840
+ function indexDocument(state, docId, document, flatSchema, language, options) {
2841
+ const fieldLengths = {};
2842
+ const tokensByField = {};
2843
+ const internalId = resolveInternalId(state, docId);
2844
+ for (const [fieldPath, fieldType] of Object.entries(flatSchema)) {
2845
+ const value = getNestedValue(document, fieldPath);
2846
+ if (value === void 0 || value === null) continue;
2847
+ ensureFieldIndex(state, fieldPath, fieldType);
2848
+ if (fieldType === "string") {
2849
+ indexStringField(state, internalId, fieldPath, value, language, options, fieldLengths, tokensByField);
2850
+ } else if (fieldType === "number") {
2851
+ getOrCreateNumericIndex(state, fieldPath).insert(internalId, value);
2852
+ } else if (fieldType === "boolean") {
2853
+ getOrCreateBooleanIndex(state, fieldPath).insert(internalId, value);
2854
+ } else if (fieldType === "enum") {
2855
+ state.enumIndexes.get(fieldPath)?.insert(internalId, value);
2856
+ } else if (fieldType === "geopoint") {
2857
+ const geo = value;
2858
+ state.geoIndexes.get(fieldPath)?.insert(internalId, geo.lat, geo.lon);
2859
+ } else if (fieldType === "string[]") {
2860
+ indexStringArrayField(
2861
+ state,
2862
+ internalId,
2863
+ fieldPath,
2864
+ value,
2865
+ language,
2866
+ options,
2867
+ fieldLengths,
2868
+ tokensByField
2869
+ );
2870
+ } else if (fieldType === "number[]") {
2871
+ const numIdx = getOrCreateNumericIndex(state, fieldPath);
2872
+ for (const num of value) numIdx.insert(internalId, num);
2873
+ } else if (fieldType === "boolean[]") {
2874
+ const boolIdx = getOrCreateBooleanIndex(state, fieldPath);
2875
+ for (const b of value) boolIdx.insert(internalId, b);
2876
+ } else if (fieldType === "enum[]") {
2877
+ const enumIdx = state.enumIndexes.get(fieldPath);
2878
+ if (enumIdx) {
2879
+ for (const e of value) enumIdx.insert(internalId, e);
2880
+ }
2881
+ }
2420
2882
  }
2421
- const uSq = cos2Alpha * (WGS84_A * WGS84_A - WGS84_B * WGS84_B) / (WGS84_B * WGS84_B);
2422
- const A = 1 + uSq / 16384 * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq)));
2423
- const B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq)));
2424
- const deltaSigma = B * sinSigma * (cos2SigmaM + B / 4 * (cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM) - B / 6 * cos2SigmaM * (-3 + 4 * sinSigma * sinSigma) * (-3 + 4 * cos2SigmaM * cos2SigmaM)));
2425
- return WGS84_B * A * (sigma - deltaSigma);
2883
+ return { fieldLengths, tokensByField };
2426
2884
  }
2427
-
2428
- // src/geo/geo-index.ts
2429
- function createGeoIndex() {
2430
- const entries = [];
2431
- return {
2432
- get entries() {
2433
- return entries;
2434
- },
2435
- insert(internalId, lat, lon) {
2436
- entries.push({ lat, lon, docId: internalId });
2437
- },
2438
- remove(internalId) {
2439
- for (let i = entries.length - 1; i >= 0; i--) {
2440
- if (entries[i].docId === internalId) {
2441
- entries.splice(i, 1);
2442
- return;
2443
- }
2885
+ function removeFromIndexes(state, docId, storedDoc, flatSchema, language, options) {
2886
+ const fieldLengths = {};
2887
+ const tokensByField = {};
2888
+ const fields = storedDoc.fields;
2889
+ const internalId = resolveInternalId(state, docId);
2890
+ const opts = tokenizeOptions(options);
2891
+ for (const [fieldPath, fieldType] of Object.entries(flatSchema)) {
2892
+ const value = getNestedValue(fields, fieldPath);
2893
+ if (value === void 0 || value === null) continue;
2894
+ if (fieldType === "string") {
2895
+ const result = tokenize(value, language, opts);
2896
+ fieldLengths[fieldPath] = result.tokens.length;
2897
+ const uniqueTokens = /* @__PURE__ */ new Set();
2898
+ const fieldTokenList = [];
2899
+ const surfaceCounts = /* @__PURE__ */ new Map();
2900
+ for (let i = 0; i < result.tokens.length; i++) {
2901
+ const t = result.tokens[i];
2902
+ uniqueTokens.add(t.token);
2903
+ fieldTokenList.push(t.token);
2904
+ countSurface(surfaceCounts, result.surfaces?.[i], t.token);
2444
2905
  }
2445
- },
2446
- radiusQuery(lat, lon, distanceMeters, inside, highPrecision) {
2447
- const result = /* @__PURE__ */ new Set();
2448
- const distanceFn = highPrecision ? vincentyDistance : haversineDistance;
2449
- for (let i = 0; i < entries.length; i++) {
2450
- const entry = entries[i];
2451
- const dist = distanceFn(lat, lon, entry.lat, entry.lon);
2452
- const withinRadius = dist <= distanceMeters;
2453
- if (inside ? withinRadius : !withinRadius) {
2454
- result.add(entry.docId);
2906
+ for (const token of uniqueTokens) state.invertedIdx.remove(token, internalId);
2907
+ applySurfaceCounts(state, surfaceCounts, -1);
2908
+ tokensByField[fieldPath] = fieldTokenList;
2909
+ } else if (fieldType === "number") {
2910
+ state.numericIndexes.get(fieldPath)?.remove(internalId, value);
2911
+ } else if (fieldType === "boolean") {
2912
+ state.booleanIndexes.get(fieldPath)?.remove(internalId, value);
2913
+ } else if (fieldType === "enum") {
2914
+ state.enumIndexes.get(fieldPath)?.remove(internalId, value);
2915
+ } else if (fieldType === "geopoint") {
2916
+ state.geoIndexes.get(fieldPath)?.remove(internalId);
2917
+ } else if (fieldType === "string[]") {
2918
+ const arr = value;
2919
+ const uniqueTokens = /* @__PURE__ */ new Set();
2920
+ const fieldTokenList = [];
2921
+ const surfaceCounts = /* @__PURE__ */ new Map();
2922
+ for (const item of arr) {
2923
+ const result = tokenize(item, language, opts);
2924
+ for (let i = 0; i < result.tokens.length; i++) {
2925
+ const t = result.tokens[i];
2926
+ uniqueTokens.add(t.token);
2927
+ fieldTokenList.push(t.token);
2928
+ countSurface(surfaceCounts, result.surfaces?.[i], t.token);
2455
2929
  }
2456
2930
  }
2457
- return result;
2458
- },
2459
- polygonQuery(points, inside) {
2460
- const result = /* @__PURE__ */ new Set();
2461
- for (let i = 0; i < entries.length; i++) {
2462
- const entry = entries[i];
2463
- const withinPolygon = isPointInPolygon(entry.lat, entry.lon, points);
2464
- if (inside ? withinPolygon : !withinPolygon) {
2465
- result.add(entry.docId);
2466
- }
2931
+ fieldLengths[fieldPath] = fieldTokenList.length;
2932
+ for (const token of uniqueTokens) state.invertedIdx.remove(token, internalId);
2933
+ applySurfaceCounts(state, surfaceCounts, -1);
2934
+ tokensByField[fieldPath] = fieldTokenList;
2935
+ } else if (fieldType === "number[]") {
2936
+ const numIdx = state.numericIndexes.get(fieldPath);
2937
+ if (numIdx) {
2938
+ for (const num of value) numIdx.remove(internalId, num);
2467
2939
  }
2468
- return result;
2469
- },
2470
- clear() {
2471
- entries.length = 0;
2472
- },
2473
- serialize() {
2474
- return entries.map((e) => ({ ...e }));
2475
- },
2476
- deserialize(data) {
2477
- entries.length = 0;
2478
- for (const entry of data) {
2479
- entries.push({ lat: entry.lat, lon: entry.lon, docId: entry.docId });
2940
+ } else if (fieldType === "boolean[]") {
2941
+ const boolIdx = state.booleanIndexes.get(fieldPath);
2942
+ if (boolIdx) {
2943
+ for (const b of value) boolIdx.remove(internalId, b);
2480
2944
  }
2481
- }
2482
- };
2483
- }
2484
-
2485
- // src/core/field-index/boolean.ts
2486
- function createBooleanIndex() {
2487
- const trueDocs = /* @__PURE__ */ new Set();
2488
- const falseDocs = /* @__PURE__ */ new Set();
2489
- return {
2490
- insert(internalId, value) {
2491
- if (value) trueDocs.add(internalId);
2492
- else falseDocs.add(internalId);
2493
- },
2494
- remove(internalId, value) {
2495
- if (value) trueDocs.delete(internalId);
2496
- else falseDocs.delete(internalId);
2497
- },
2498
- queryEq(value) {
2499
- return new Set(value ? trueDocs : falseDocs);
2500
- },
2501
- queryNe(value) {
2502
- return new Set(value ? falseDocs : trueDocs);
2503
- },
2504
- getAllDocIds() {
2505
- const all = new Set(trueDocs);
2506
- for (const id of falseDocs) all.add(id);
2507
- return all;
2508
- },
2509
- queryEqBitset(value, capacity) {
2510
- const source = value ? trueDocs : falseDocs;
2511
- const bs = createBitSet(capacity);
2512
- for (const id of source) {
2513
- bitsetSet(bs, id);
2945
+ } else if (fieldType === "enum[]") {
2946
+ const enumIdx = state.enumIndexes.get(fieldPath);
2947
+ if (enumIdx) {
2948
+ for (const e of value) enumIdx.remove(internalId, e);
2514
2949
  }
2515
- return bs;
2516
- },
2517
- getAllDocIdsBitset(capacity) {
2518
- const bs = createBitSet(capacity);
2519
- for (const id of trueDocs) bitsetSet(bs, id);
2520
- for (const id of falseDocs) bitsetSet(bs, id);
2521
- return bs;
2522
- },
2523
- count() {
2524
- return trueDocs.size + falseDocs.size;
2525
- },
2526
- clear() {
2527
- trueDocs.clear();
2528
- falseDocs.clear();
2529
- },
2530
- serialize() {
2531
- return {
2532
- trueDocs: Array.from(trueDocs),
2533
- falseDocs: Array.from(falseDocs)
2534
- };
2535
- },
2536
- deserialize(data) {
2537
- trueDocs.clear();
2538
- falseDocs.clear();
2539
- for (const id of data.trueDocs) trueDocs.add(id);
2540
- for (const id of data.falseDocs) falseDocs.add(id);
2541
2950
  }
2542
- };
2951
+ }
2952
+ return { fieldLengths, tokensByField };
2543
2953
  }
2544
2954
 
2545
- // src/core/field-index/enum.ts
2546
- function createEnumIndex() {
2547
- const valueMap = /* @__PURE__ */ new Map();
2548
- function allDocs() {
2549
- const result = /* @__PURE__ */ new Set();
2550
- for (const docSet of valueMap.values()) {
2551
- for (const id of docSet) result.add(id);
2552
- }
2553
- return result;
2955
+ // src/core/partition/field-updates.ts
2956
+ function resolveInternalId2(state, docId) {
2957
+ const internalId = state.docStore.getInternalId(docId);
2958
+ if (internalId === void 0) {
2959
+ throw new Error(`Internal ID not found for document "${docId}"`);
2554
2960
  }
2555
- return {
2556
- insert(internalId, value) {
2557
- let docSet = valueMap.get(value);
2558
- if (!docSet) {
2559
- docSet = /* @__PURE__ */ new Set();
2560
- valueMap.set(value, docSet);
2961
+ return internalId;
2962
+ }
2963
+ function updateFieldIndexOnly(state, docId, oldFields, newDoc, flatSchema) {
2964
+ const internalId = resolveInternalId2(state, docId);
2965
+ for (const [fieldPath, fieldType] of Object.entries(flatSchema)) {
2966
+ if (fieldType === "string" || fieldType === "string[]") continue;
2967
+ const oldVal = getNestedValue(oldFields, fieldPath);
2968
+ const newVal = getNestedValue(newDoc, fieldPath);
2969
+ if (oldVal === newVal) continue;
2970
+ if (oldVal !== void 0 && oldVal !== null && newVal !== void 0 && newVal !== null && typeof oldVal === typeof newVal && JSON.stringify(oldVal) === JSON.stringify(newVal))
2971
+ continue;
2972
+ if (newVal !== void 0 && newVal !== null) {
2973
+ ensureFieldIndex(state, fieldPath, fieldType);
2974
+ }
2975
+ if (fieldType === "number") {
2976
+ const numIdx = state.numericIndexes.get(fieldPath);
2977
+ if (numIdx) {
2978
+ if (oldVal !== void 0 && oldVal !== null) numIdx.remove(internalId, oldVal);
2979
+ if (newVal !== void 0 && newVal !== null) numIdx.insert(internalId, newVal);
2561
2980
  }
2562
- docSet.add(internalId);
2563
- },
2564
- remove(internalId, value) {
2565
- const docSet = valueMap.get(value);
2566
- if (!docSet) return;
2567
- docSet.delete(internalId);
2568
- if (docSet.size === 0) valueMap.delete(value);
2569
- },
2570
- queryEq(value) {
2571
- const docSet = valueMap.get(value);
2572
- return docSet ? new Set(docSet) : /* @__PURE__ */ new Set();
2573
- },
2574
- queryNe(value) {
2575
- const excluded = valueMap.get(value);
2576
- const result = allDocs();
2577
- if (excluded) {
2578
- for (const id of excluded) result.delete(id);
2981
+ } else if (fieldType === "boolean") {
2982
+ const boolIdx = state.booleanIndexes.get(fieldPath);
2983
+ if (boolIdx) {
2984
+ if (oldVal !== void 0 && oldVal !== null) boolIdx.remove(internalId, oldVal);
2985
+ if (newVal !== void 0 && newVal !== null) boolIdx.insert(internalId, newVal);
2579
2986
  }
2580
- return result;
2581
- },
2582
- queryIn(values) {
2583
- const result = /* @__PURE__ */ new Set();
2584
- for (const val of values) {
2585
- const docSet = valueMap.get(val);
2586
- if (docSet) {
2587
- for (const id of docSet) result.add(id);
2588
- }
2987
+ } else if (fieldType === "enum") {
2988
+ const enumIdx = state.enumIndexes.get(fieldPath);
2989
+ if (enumIdx) {
2990
+ if (oldVal !== void 0 && oldVal !== null) enumIdx.remove(internalId, oldVal);
2991
+ if (newVal !== void 0 && newVal !== null) enumIdx.insert(internalId, newVal);
2589
2992
  }
2590
- return result;
2591
- },
2592
- queryNin(values) {
2593
- const excluded = this.queryIn(values);
2594
- const result = allDocs();
2595
- for (const id of excluded) result.delete(id);
2596
- return result;
2597
- },
2598
- getAllDocIds() {
2599
- return allDocs();
2600
- },
2601
- queryEqBitset(value, capacity) {
2602
- const bs = createBitSet(capacity);
2603
- const docSet = valueMap.get(value);
2604
- if (docSet) {
2605
- for (const id of docSet) bitsetSet(bs, id);
2993
+ } else if (fieldType === "geopoint") {
2994
+ const geoIdx = state.geoIndexes.get(fieldPath);
2995
+ if (geoIdx) {
2996
+ if (oldVal !== void 0 && oldVal !== null) geoIdx.remove(internalId);
2997
+ if (newVal !== void 0 && newVal !== null) {
2998
+ const geo = newVal;
2999
+ geoIdx.insert(internalId, geo.lat, geo.lon);
3000
+ }
2606
3001
  }
2607
- return bs;
2608
- },
2609
- queryInBitset(values, capacity) {
2610
- const bs = createBitSet(capacity);
2611
- for (const val of values) {
2612
- const docSet = valueMap.get(val);
2613
- if (docSet) {
2614
- for (const id of docSet) bitsetSet(bs, id);
3002
+ } else if (fieldType === "number[]") {
3003
+ const numIdx = state.numericIndexes.get(fieldPath);
3004
+ if (numIdx) {
3005
+ if (oldVal !== void 0 && oldVal !== null) {
3006
+ for (const n of oldVal) numIdx.remove(internalId, n);
3007
+ }
3008
+ if (newVal !== void 0 && newVal !== null) {
3009
+ for (const n of newVal) numIdx.insert(internalId, n);
2615
3010
  }
2616
3011
  }
2617
- return bs;
2618
- },
2619
- getAllDocIdsBitset(capacity) {
2620
- const bs = createBitSet(capacity);
2621
- for (const docSet of valueMap.values()) {
2622
- for (const id of docSet) bitsetSet(bs, id);
3012
+ } else if (fieldType === "boolean[]") {
3013
+ const boolIdx = state.booleanIndexes.get(fieldPath);
3014
+ if (boolIdx) {
3015
+ if (oldVal !== void 0 && oldVal !== null) {
3016
+ for (const b of oldVal) boolIdx.remove(internalId, b);
3017
+ }
3018
+ if (newVal !== void 0 && newVal !== null) {
3019
+ for (const b of newVal) boolIdx.insert(internalId, b);
3020
+ }
2623
3021
  }
2624
- return bs;
2625
- },
2626
- count() {
2627
- let total = 0;
2628
- for (const docSet of valueMap.values()) total += docSet.size;
2629
- return total;
2630
- },
2631
- clear() {
2632
- valueMap.clear();
2633
- },
2634
- serialize() {
2635
- const result = /* @__PURE__ */ Object.create(null);
2636
- for (const [value, docSet] of valueMap) {
2637
- result[value] = Array.from(docSet);
3022
+ } else if (fieldType === "enum[]") {
3023
+ const enumIdx = state.enumIndexes.get(fieldPath);
3024
+ if (enumIdx) {
3025
+ if (oldVal !== void 0 && oldVal !== null) {
3026
+ for (const e of oldVal) enumIdx.remove(internalId, e);
3027
+ }
3028
+ if (newVal !== void 0 && newVal !== null) {
3029
+ for (const e of newVal) enumIdx.insert(internalId, e);
3030
+ }
2638
3031
  }
2639
- return result;
2640
- },
2641
- deserialize(data) {
2642
- valueMap.clear();
2643
- for (const value of Object.keys(data)) {
2644
- valueMap.set(value, new Set(data[value]));
3032
+ }
3033
+ }
3034
+ }
3035
+
3036
+ // src/filters/combinators.ts
3037
+ function applyAndBitset(bitsets) {
3038
+ if (bitsets.length === 0) return new Uint32Array(0);
3039
+ if (bitsets.length === 1) return bitsets[0];
3040
+ let result = bitsets[0];
3041
+ for (let i = 1; i < bitsets.length; i++) {
3042
+ result = bitsetAnd(result, bitsets[i]);
3043
+ }
3044
+ return result;
3045
+ }
3046
+ function applyOrBitset(bitsets) {
3047
+ if (bitsets.length === 0) return new Uint32Array(0);
3048
+ if (bitsets.length === 1) return bitsets[0];
3049
+ let result = bitsets[0];
3050
+ for (let i = 1; i < bitsets.length; i++) {
3051
+ result = bitsetOr(result, bitsets[i]);
3052
+ }
3053
+ return result;
3054
+ }
3055
+ function applyNotBitset(excluded, capacity) {
3056
+ return bitsetNot(excluded, capacity);
3057
+ }
3058
+
3059
+ // src/filters/operators/shared.ts
3060
+ var METERS_PER_KM = 1e3;
3061
+ var METERS_PER_MI = 1609.344;
3062
+ function convertToMeters(distance, unit) {
3063
+ if (unit === "km") return distance * METERS_PER_KM;
3064
+ if (unit === "mi") return distance * METERS_PER_MI;
3065
+ return distance;
3066
+ }
3067
+ function matchesNumericComparison(value, filter) {
3068
+ if (filter.eq !== void 0 && value !== filter.eq) return false;
3069
+ if (filter.ne !== void 0 && value === filter.ne) return false;
3070
+ if (filter.gt !== void 0 && !(value > filter.gt)) return false;
3071
+ if (filter.lt !== void 0 && !(value < filter.lt)) return false;
3072
+ if (filter.gte !== void 0 && !(value >= filter.gte)) return false;
3073
+ if (filter.lte !== void 0 && !(value <= filter.lte)) return false;
3074
+ if (filter.between !== void 0 && !(value >= filter.between[0] && value <= filter.between[1])) return false;
3075
+ return true;
3076
+ }
3077
+
3078
+ // src/filters/operators/bitset.ts
3079
+ function resolveBitset(bs) {
3080
+ return typeof bs === "function" ? bs() : bs;
3081
+ }
3082
+ function scanToBitset(docIds, capacity, getValue, predicate) {
3083
+ const resolved = resolveBitset(docIds);
3084
+ const result = createBitSet(capacity);
3085
+ for (let wi = 0; wi < resolved.length; wi++) {
3086
+ let word = resolved[wi];
3087
+ if (word === 0) continue;
3088
+ const base = wi << 5;
3089
+ while (word !== 0) {
3090
+ const tz = Math.clz32(word & -word) ^ 31;
3091
+ const id = base + tz;
3092
+ if (predicate(getValue(id))) {
3093
+ bitsetSet(result, id);
2645
3094
  }
3095
+ word &= word - 1;
2646
3096
  }
2647
- };
3097
+ }
3098
+ return result;
3099
+ }
3100
+ function applyEqBitset(value, allDocsBitset, capacity, getValue, fieldIndex) {
3101
+ if (fieldIndex?.type === "numeric" && typeof value === "number") {
3102
+ return fieldIndex.index.eqBitset(value, capacity);
3103
+ }
3104
+ if (fieldIndex?.type === "boolean" && typeof value === "boolean") {
3105
+ return value ? fieldIndex.index.getTrueBitset(capacity) : fieldIndex.index.getFalseBitset(capacity);
3106
+ }
3107
+ if (fieldIndex?.type === "enum" && typeof value === "string") {
3108
+ return fieldIndex.index.getDocIdsBitset(value, capacity);
3109
+ }
3110
+ return scanToBitset(allDocsBitset, capacity, getValue, (v) => v === value);
2648
3111
  }
2649
-
2650
- // src/core/field-index/numeric.ts
2651
- function lowerBound(entries, value) {
2652
- let lo = 0;
2653
- let hi = entries.length;
2654
- while (lo < hi) {
2655
- const mid = lo + hi >>> 1;
2656
- if (entries[mid].value < value) lo = mid + 1;
2657
- else hi = mid;
3112
+ function applyNeBitset(value, allDocsBitset, capacity, getValue, fieldIndex) {
3113
+ if (fieldIndex?.type === "numeric" && typeof value === "number") {
3114
+ const all = fieldIndex.index.allDocIdsBitset(capacity);
3115
+ const eq = fieldIndex.index.eqBitset(value, capacity);
3116
+ return bitsetAnd(all, bitsetNot(eq, capacity));
2658
3117
  }
2659
- return lo;
3118
+ if (fieldIndex?.type === "boolean" && typeof value === "boolean") {
3119
+ return value ? fieldIndex.index.getFalseBitset(capacity) : fieldIndex.index.getTrueBitset(capacity);
3120
+ }
3121
+ if (fieldIndex?.type === "enum" && typeof value === "string") {
3122
+ const all = fieldIndex.index.allDocIdsBitset(capacity);
3123
+ const eq = fieldIndex.index.getDocIdsBitset(value, capacity);
3124
+ return bitsetAnd(all, bitsetNot(eq, capacity));
3125
+ }
3126
+ return scanToBitset(allDocsBitset, capacity, getValue, (v) => v !== void 0 && v !== null && v !== value);
2660
3127
  }
2661
- function upperBound(entries, value) {
2662
- let lo = 0;
2663
- let hi = entries.length;
2664
- while (lo < hi) {
2665
- const mid = lo + hi >>> 1;
2666
- if (entries[mid].value <= value) lo = mid + 1;
2667
- else hi = mid;
3128
+ function applyGtBitset(value, allDocsBitset, capacity, getValue, fieldIndex) {
3129
+ if (fieldIndex?.type === "numeric") {
3130
+ return fieldIndex.index.gtBitset(value, capacity);
2668
3131
  }
2669
- return lo;
3132
+ return scanToBitset(allDocsBitset, capacity, getValue, (v) => typeof v === "number" && v > value);
2670
3133
  }
2671
- function collectDocIds(entries, from, to) {
2672
- const result = /* @__PURE__ */ new Set();
2673
- for (let i = from; i < to; i++) {
2674
- result.add(entries[i].docId);
3134
+ function applyLtBitset(value, allDocsBitset, capacity, getValue, fieldIndex) {
3135
+ if (fieldIndex?.type === "numeric") {
3136
+ return fieldIndex.index.ltBitset(value, capacity);
2675
3137
  }
2676
- return result;
3138
+ return scanToBitset(allDocsBitset, capacity, getValue, (v) => typeof v === "number" && v < value);
2677
3139
  }
2678
- function collectDocIdsBitset(entries, from, to, capacity) {
2679
- const bs = createBitSet(capacity);
2680
- for (let i = from; i < to; i++) {
2681
- bitsetSet(bs, entries[i].docId);
3140
+ function applyGteBitset(value, allDocsBitset, capacity, getValue, fieldIndex) {
3141
+ if (fieldIndex?.type === "numeric") {
3142
+ return fieldIndex.index.gteBitset(value, capacity);
2682
3143
  }
2683
- return bs;
3144
+ return scanToBitset(allDocsBitset, capacity, getValue, (v) => typeof v === "number" && v >= value);
2684
3145
  }
2685
- function createNumericIndex() {
2686
- let entries = [];
2687
- let sorted = true;
2688
- function ensureSorted() {
2689
- if (!sorted) {
2690
- entries.sort((a, b) => a.value - b.value);
2691
- sorted = true;
2692
- }
3146
+ function applyLteBitset(value, allDocsBitset, capacity, getValue, fieldIndex) {
3147
+ if (fieldIndex?.type === "numeric") {
3148
+ return fieldIndex.index.lteBitset(value, capacity);
2693
3149
  }
2694
- return {
2695
- insert(internalId, value) {
2696
- entries.push({ value, docId: internalId });
2697
- sorted = false;
2698
- },
2699
- remove(internalId, value) {
2700
- ensureSorted();
2701
- const start = lowerBound(entries, value);
2702
- const end = upperBound(entries, value);
2703
- for (let i = start; i < end; i++) {
2704
- if (entries[i].docId === internalId) {
2705
- entries.splice(i, 1);
2706
- return;
2707
- }
2708
- }
2709
- },
2710
- queryEq(value) {
2711
- ensureSorted();
2712
- return collectDocIds(entries, lowerBound(entries, value), upperBound(entries, value));
2713
- },
2714
- queryNe(value) {
2715
- ensureSorted();
2716
- const excluded = this.queryEq(value);
2717
- const result = /* @__PURE__ */ new Set();
2718
- for (const entry of entries) {
2719
- if (!excluded.has(entry.docId)) result.add(entry.docId);
2720
- }
2721
- return result;
2722
- },
2723
- queryGt(value) {
2724
- ensureSorted();
2725
- return collectDocIds(entries, upperBound(entries, value), entries.length);
2726
- },
2727
- queryGte(value) {
2728
- ensureSorted();
2729
- return collectDocIds(entries, lowerBound(entries, value), entries.length);
2730
- },
2731
- queryLt(value) {
2732
- ensureSorted();
2733
- return collectDocIds(entries, 0, lowerBound(entries, value));
2734
- },
2735
- queryLte(value) {
2736
- ensureSorted();
2737
- return collectDocIds(entries, 0, upperBound(entries, value));
2738
- },
2739
- queryBetween(min, max) {
2740
- ensureSorted();
2741
- return collectDocIds(entries, lowerBound(entries, min), upperBound(entries, max));
2742
- },
2743
- getAllDocIds() {
2744
- return collectDocIds(entries, 0, entries.length);
2745
- },
2746
- queryEqBitset(value, capacity) {
2747
- ensureSorted();
2748
- return collectDocIdsBitset(entries, lowerBound(entries, value), upperBound(entries, value), capacity);
2749
- },
2750
- queryGtBitset(value, capacity) {
2751
- ensureSorted();
2752
- return collectDocIdsBitset(entries, upperBound(entries, value), entries.length, capacity);
2753
- },
2754
- queryGteBitset(value, capacity) {
2755
- ensureSorted();
2756
- return collectDocIdsBitset(entries, lowerBound(entries, value), entries.length, capacity);
2757
- },
2758
- queryLtBitset(value, capacity) {
2759
- ensureSorted();
2760
- return collectDocIdsBitset(entries, 0, lowerBound(entries, value), capacity);
2761
- },
2762
- queryLteBitset(value, capacity) {
2763
- ensureSorted();
2764
- return collectDocIdsBitset(entries, 0, upperBound(entries, value), capacity);
2765
- },
2766
- queryBetweenBitset(min, max, capacity) {
2767
- ensureSorted();
2768
- return collectDocIdsBitset(entries, lowerBound(entries, min), upperBound(entries, max), capacity);
2769
- },
2770
- getAllDocIdsBitset(capacity) {
2771
- return collectDocIdsBitset(entries, 0, entries.length, capacity);
2772
- },
2773
- count() {
2774
- return entries.length;
2775
- },
2776
- clear() {
2777
- entries = [];
2778
- sorted = true;
2779
- },
2780
- serialize() {
2781
- ensureSorted();
2782
- return entries.slice();
2783
- },
2784
- deserialize(data) {
2785
- entries = data.slice();
2786
- sorted = true;
3150
+ return scanToBitset(allDocsBitset, capacity, getValue, (v) => typeof v === "number" && v <= value);
3151
+ }
3152
+ function applyBetweenBitset(range, allDocsBitset, capacity, getValue, fieldIndex) {
3153
+ if (fieldIndex?.type === "numeric") {
3154
+ return fieldIndex.index.betweenBitset(range[0], range[1], capacity);
3155
+ }
3156
+ return scanToBitset(allDocsBitset, capacity, getValue, (v) => typeof v === "number" && v >= range[0] && v <= range[1]);
3157
+ }
3158
+ function applyInBitset(values, allDocsBitset, capacity, getValue, fieldIndex) {
3159
+ if (fieldIndex?.type === "enum") {
3160
+ return fieldIndex.index.getDocIdsInBitset(values, capacity);
3161
+ }
3162
+ const valSet = new Set(values);
3163
+ return scanToBitset(allDocsBitset, capacity, getValue, (v) => typeof v === "string" && valSet.has(v));
3164
+ }
3165
+ function applyNinBitset(values, allDocsBitset, capacity, getValue, fieldIndex) {
3166
+ if (fieldIndex?.type === "enum") {
3167
+ const all = fieldIndex.index.allDocIdsBitset(capacity);
3168
+ const matched = fieldIndex.index.getDocIdsInBitset(values, capacity);
3169
+ return bitsetAnd(all, bitsetNot(matched, capacity));
3170
+ }
3171
+ const valSet = new Set(values);
3172
+ return scanToBitset(
3173
+ allDocsBitset,
3174
+ capacity,
3175
+ getValue,
3176
+ (v) => v !== void 0 && v !== null && typeof v === "string" && !valSet.has(v)
3177
+ );
3178
+ }
3179
+ function applyStartsWithBitset(prefix, allDocsBitset, capacity, getValue) {
3180
+ return scanToBitset(allDocsBitset, capacity, getValue, (v) => typeof v === "string" && v.startsWith(prefix));
3181
+ }
3182
+ function applyEndsWithBitset(suffix, allDocsBitset, capacity, getValue) {
3183
+ return scanToBitset(allDocsBitset, capacity, getValue, (v) => typeof v === "string" && v.endsWith(suffix));
3184
+ }
3185
+ function applyContainsAllBitset(values, allDocsBitset, capacity, getValue) {
3186
+ return scanToBitset(allDocsBitset, capacity, getValue, (v) => {
3187
+ if (!Array.isArray(v)) return false;
3188
+ const arr = v;
3189
+ for (const val of values) {
3190
+ if (!arr.includes(val)) return false;
2787
3191
  }
2788
- };
3192
+ return true;
3193
+ });
3194
+ }
3195
+ function applyMatchesAnyBitset(values, allDocsBitset, capacity, getValue) {
3196
+ const valSet = new Set(values);
3197
+ return scanToBitset(allDocsBitset, capacity, getValue, (v) => {
3198
+ if (!Array.isArray(v)) return false;
3199
+ for (const item of v) {
3200
+ if (valSet.has(item)) return true;
3201
+ }
3202
+ return false;
3203
+ });
3204
+ }
3205
+ function applySizeBitset(sizeFilter, allDocsBitset, capacity, getValue) {
3206
+ return scanToBitset(
3207
+ allDocsBitset,
3208
+ capacity,
3209
+ getValue,
3210
+ (v) => Array.isArray(v) && matchesNumericComparison(v.length, sizeFilter)
3211
+ );
3212
+ }
3213
+ function applyExistsBitset(allDocsBitset, capacity, getValue) {
3214
+ return scanToBitset(allDocsBitset, capacity, getValue, (v) => v !== void 0 && v !== null);
3215
+ }
3216
+ function applyNotExistsBitset(allDocsBitset, capacity, getValue) {
3217
+ return scanToBitset(allDocsBitset, capacity, getValue, (v) => v === void 0 || v === null);
3218
+ }
3219
+ function applyIsEmptyBitset(allDocsBitset, capacity, getValue) {
3220
+ return scanToBitset(allDocsBitset, capacity, getValue, (v) => {
3221
+ if (v === void 0 || v === null) return true;
3222
+ if (typeof v === "string" && v === "") return true;
3223
+ if (Array.isArray(v) && v.length === 0) return true;
3224
+ return false;
3225
+ });
3226
+ }
3227
+ function applyIsNotEmptyBitset(allDocsBitset, capacity, getValue) {
3228
+ return scanToBitset(allDocsBitset, capacity, getValue, (v) => {
3229
+ if (v === void 0 || v === null) return false;
3230
+ if (typeof v === "string" && v === "") return false;
3231
+ if (Array.isArray(v) && v.length === 0) return false;
3232
+ return true;
3233
+ });
3234
+ }
3235
+ function applyGeoRadiusBitset(filter, geoIndex, capacity) {
3236
+ const distanceMeters = convertToMeters(filter.distance, filter.unit);
3237
+ const setResult = geoIndex.radiusQuery(
3238
+ filter.lat,
3239
+ filter.lon,
3240
+ distanceMeters,
3241
+ filter.inside ?? true,
3242
+ filter.highPrecision ?? false
3243
+ );
3244
+ return bitsetFromSet(setResult, capacity);
3245
+ }
3246
+ function applyGeoPolygonBitset(filter, geoIndex, capacity) {
3247
+ const setResult = geoIndex.polygonQuery(filter.points, filter.inside ?? true);
3248
+ return bitsetFromSet(setResult, capacity);
2789
3249
  }
2790
3250
 
2791
- // src/core/partition/indexing.ts
2792
- function ensureFieldIndex(state, fieldPath, fieldType) {
2793
- if ((fieldType === "number" || fieldType === "number[]") && !state.numericIndexes.has(fieldPath)) {
2794
- state.numericIndexes.set(fieldPath, createNumericIndex());
2795
- } else if ((fieldType === "boolean" || fieldType === "boolean[]") && !state.booleanIndexes.has(fieldPath)) {
2796
- state.booleanIndexes.set(fieldPath, createBooleanIndex());
2797
- } else if ((fieldType === "enum" || fieldType === "enum[]") && !state.enumIndexes.has(fieldPath)) {
2798
- state.enumIndexes.set(fieldPath, createEnumIndex());
2799
- } else if (fieldType === "geopoint" && !state.geoIndexes.has(fieldPath)) {
2800
- state.geoIndexes.set(fieldPath, createGeoIndex());
3251
+ // src/filters/evaluator.ts
3252
+ function evaluateFilters(expression, context) {
3253
+ const bitsets = [];
3254
+ if (expression.fields) {
3255
+ for (const [fieldPath, filter] of Object.entries(expression.fields)) {
3256
+ bitsets.push(evaluateFieldFilter(fieldPath, filter, context));
3257
+ }
2801
3258
  }
2802
- }
2803
- function getOrCreateNumericIndex(state, fieldPath) {
2804
- let idx = state.numericIndexes.get(fieldPath);
2805
- if (!idx) {
2806
- idx = createNumericIndex();
2807
- state.numericIndexes.set(fieldPath, idx);
3259
+ if (expression.and?.length) {
3260
+ const andBitsets = expression.and.map((expr) => evaluateFilters(expr, context));
3261
+ bitsets.push(applyAndBitset(andBitsets));
2808
3262
  }
2809
- return idx;
2810
- }
2811
- function getOrCreateBooleanIndex(state, fieldPath) {
2812
- let idx = state.booleanIndexes.get(fieldPath);
2813
- if (!idx) {
2814
- idx = createBooleanIndex();
2815
- state.booleanIndexes.set(fieldPath, idx);
3263
+ if (expression.or?.length) {
3264
+ const orBitsets = expression.or.map((expr) => evaluateFilters(expr, context));
3265
+ bitsets.push(applyOrBitset(orBitsets));
2816
3266
  }
2817
- return idx;
2818
- }
2819
- function resolveInternalId(state, docId) {
2820
- const internalId = state.docStore.getInternalId(docId);
2821
- if (internalId === void 0) {
2822
- throw new Error(`Internal ID not found for document "${docId}"`);
3267
+ if (expression.not) {
3268
+ const excluded = evaluateFilters(expression.not, context);
3269
+ const universe = context.allDocIdsBitset;
3270
+ bitsets.push(applyAndBitset([universe, applyNotBitset(excluded, context.capacity)]));
2823
3271
  }
2824
- return internalId;
3272
+ if (bitsets.length === 0) return context.allDocIdsBitset;
3273
+ if (bitsets.length === 1) return bitsets[0];
3274
+ return applyAndBitset(bitsets);
2825
3275
  }
2826
- function indexStringField(state, internalId, fieldPath, text, language, options, fieldLengths, tokensByField) {
2827
- const fieldTokenList = [];
2828
- const fieldNameIndex = getOrCreateFieldNameIndex(state.fieldNameTable, fieldPath);
2829
- const opts = tokenizeOptions(options);
2830
- if (state.trackPositions) {
2831
- const tokenFreqs = /* @__PURE__ */ new Map();
2832
- for (const t of tokenizeIterator(text, language, opts)) {
2833
- const existing = tokenFreqs.get(t.token);
2834
- if (existing) {
2835
- existing.count++;
2836
- existing.positions.push(t.position);
2837
- } else {
2838
- tokenFreqs.set(t.token, { count: 1, positions: [t.position] });
2839
- }
2840
- fieldTokenList.push(t.token);
2841
- }
2842
- for (const [token, freq] of tokenFreqs) {
2843
- state.invertedIdx.insert(token, internalId, freq.count, fieldNameIndex, freq.positions);
2844
- }
2845
- } else {
2846
- const tokenCounts = /* @__PURE__ */ new Map();
2847
- for (const t of tokenizeIterator(text, language, opts)) {
2848
- tokenCounts.set(t.token, (tokenCounts.get(t.token) ?? 0) + 1);
2849
- fieldTokenList.push(t.token);
2850
- }
2851
- for (const [token, count] of tokenCounts) {
2852
- state.invertedIdx.insert(token, internalId, count, fieldNameIndex, null);
3276
+ function evaluateFieldFilter(fieldPath, filter, context) {
3277
+ const fieldIndex = context.fieldIndexes[fieldPath];
3278
+ const getValue = (internalId) => context.getFieldValue(internalId, fieldPath);
3279
+ const f = filter;
3280
+ const bitsets = [];
3281
+ const { capacity } = context;
3282
+ let allDocsBitset = null;
3283
+ const getAllDocsBitset = () => {
3284
+ if (allDocsBitset === null) {
3285
+ allDocsBitset = context.allDocIdsBitset;
2853
3286
  }
3287
+ return allDocsBitset;
3288
+ };
3289
+ if ("radius" in f && f.radius) {
3290
+ const geoIndex = resolveGeoIndex(fieldPath, fieldIndex);
3291
+ bitsets.push(applyGeoRadiusBitset(filter.radius, geoIndex, capacity));
2854
3292
  }
2855
- fieldLengths[fieldPath] = fieldTokenList.length;
2856
- tokensByField[fieldPath] = fieldTokenList;
2857
- }
2858
- function indexStringArrayField(state, internalId, fieldPath, arr, language, options, fieldLengths, tokensByField) {
2859
- const fieldTokenList = [];
2860
- const fieldNameIndex = getOrCreateFieldNameIndex(state.fieldNameTable, fieldPath);
2861
- const opts = tokenizeOptions(options);
2862
- if (state.trackPositions) {
2863
- const tokenFreqs = /* @__PURE__ */ new Map();
2864
- let positionOffset = 0;
2865
- for (const item of arr) {
2866
- let itemTokenCount = 0;
2867
- for (const t of tokenizeIterator(item, language, opts)) {
2868
- const existing = tokenFreqs.get(t.token);
2869
- if (existing) {
2870
- existing.count++;
2871
- existing.positions.push(positionOffset + t.position);
2872
- } else {
2873
- tokenFreqs.set(t.token, { count: 1, positions: [positionOffset + t.position] });
2874
- }
2875
- fieldTokenList.push(t.token);
2876
- itemTokenCount++;
2877
- }
2878
- positionOffset += itemTokenCount;
2879
- }
2880
- fieldLengths[fieldPath] = fieldTokenList.length;
2881
- for (const [token, freq] of tokenFreqs) {
2882
- state.invertedIdx.insert(token, internalId, freq.count, fieldNameIndex, freq.positions);
2883
- }
2884
- } else {
2885
- const tokenCounts = /* @__PURE__ */ new Map();
2886
- for (const item of arr) {
2887
- for (const t of tokenizeIterator(item, language, opts)) {
2888
- tokenCounts.set(t.token, (tokenCounts.get(t.token) ?? 0) + 1);
2889
- fieldTokenList.push(t.token);
2890
- }
2891
- }
2892
- fieldLengths[fieldPath] = fieldTokenList.length;
2893
- for (const [token, count] of tokenCounts) {
2894
- state.invertedIdx.insert(token, internalId, count, fieldNameIndex, null);
2895
- }
3293
+ if ("polygon" in f && f.polygon) {
3294
+ const geoIndex = resolveGeoIndex(fieldPath, fieldIndex);
3295
+ bitsets.push(applyGeoPolygonBitset(filter.polygon, geoIndex, capacity));
2896
3296
  }
2897
- tokensByField[fieldPath] = fieldTokenList;
2898
- }
2899
- function indexDocument(state, docId, document, flatSchema, language, options) {
2900
- const fieldLengths = {};
2901
- const tokensByField = {};
2902
- const internalId = resolveInternalId(state, docId);
2903
- for (const [fieldPath, fieldType] of Object.entries(flatSchema)) {
2904
- const value = getNestedValue(document, fieldPath);
2905
- if (value === void 0 || value === null) continue;
2906
- ensureFieldIndex(state, fieldPath, fieldType);
2907
- if (fieldType === "string") {
2908
- indexStringField(state, internalId, fieldPath, value, language, options, fieldLengths, tokensByField);
2909
- } else if (fieldType === "number") {
2910
- getOrCreateNumericIndex(state, fieldPath).insert(internalId, value);
2911
- } else if (fieldType === "boolean") {
2912
- getOrCreateBooleanIndex(state, fieldPath).insert(internalId, value);
2913
- } else if (fieldType === "enum") {
2914
- state.enumIndexes.get(fieldPath)?.insert(internalId, value);
2915
- } else if (fieldType === "geopoint") {
2916
- const geo = value;
2917
- state.geoIndexes.get(fieldPath)?.insert(internalId, geo.lat, geo.lon);
2918
- } else if (fieldType === "string[]") {
2919
- indexStringArrayField(
2920
- state,
2921
- internalId,
2922
- fieldPath,
2923
- value,
2924
- language,
2925
- options,
2926
- fieldLengths,
2927
- tokensByField
2928
- );
2929
- } else if (fieldType === "number[]") {
2930
- const numIdx = getOrCreateNumericIndex(state, fieldPath);
2931
- for (const num of value) numIdx.insert(internalId, num);
2932
- } else if (fieldType === "boolean[]") {
2933
- const boolIdx = getOrCreateBooleanIndex(state, fieldPath);
2934
- for (const b of value) boolIdx.insert(internalId, b);
2935
- } else if (fieldType === "enum[]") {
2936
- const enumIdx = state.enumIndexes.get(fieldPath);
2937
- if (enumIdx) {
2938
- for (const e of value) enumIdx.insert(internalId, e);
2939
- }
2940
- }
3297
+ if ("eq" in f && f.eq !== void 0) {
3298
+ bitsets.push(applyEqBitset(f.eq, getAllDocsBitset, capacity, getValue, fieldIndex));
2941
3299
  }
2942
- return { fieldLengths, tokensByField };
3300
+ if ("ne" in f && f.ne !== void 0) {
3301
+ bitsets.push(applyNeBitset(f.ne, getAllDocsBitset, capacity, getValue, fieldIndex));
3302
+ }
3303
+ if ("gt" in f && f.gt !== void 0) {
3304
+ bitsets.push(applyGtBitset(f.gt, getAllDocsBitset, capacity, getValue, fieldIndex));
3305
+ }
3306
+ if ("lt" in f && f.lt !== void 0) {
3307
+ bitsets.push(applyLtBitset(f.lt, getAllDocsBitset, capacity, getValue, fieldIndex));
3308
+ }
3309
+ if ("gte" in f && f.gte !== void 0) {
3310
+ bitsets.push(applyGteBitset(f.gte, getAllDocsBitset, capacity, getValue, fieldIndex));
3311
+ }
3312
+ if ("lte" in f && f.lte !== void 0) {
3313
+ bitsets.push(applyLteBitset(f.lte, getAllDocsBitset, capacity, getValue, fieldIndex));
3314
+ }
3315
+ if ("between" in f && f.between !== void 0) {
3316
+ bitsets.push(applyBetweenBitset(f.between, getAllDocsBitset, capacity, getValue, fieldIndex));
3317
+ }
3318
+ if ("in" in f && f.in !== void 0) {
3319
+ bitsets.push(applyInBitset(f.in, getAllDocsBitset, capacity, getValue, fieldIndex));
3320
+ }
3321
+ if ("nin" in f && f.nin !== void 0) {
3322
+ bitsets.push(applyNinBitset(f.nin, getAllDocsBitset, capacity, getValue, fieldIndex));
3323
+ }
3324
+ if ("startsWith" in f && f.startsWith !== void 0) {
3325
+ bitsets.push(applyStartsWithBitset(f.startsWith, getAllDocsBitset, capacity, getValue));
3326
+ }
3327
+ if ("endsWith" in f && f.endsWith !== void 0) {
3328
+ bitsets.push(applyEndsWithBitset(f.endsWith, getAllDocsBitset, capacity, getValue));
3329
+ }
3330
+ if ("containsAll" in f && f.containsAll !== void 0) {
3331
+ bitsets.push(
3332
+ applyContainsAllBitset(f.containsAll, getAllDocsBitset, capacity, getValue)
3333
+ );
3334
+ }
3335
+ if ("matchesAny" in f && f.matchesAny !== void 0) {
3336
+ bitsets.push(
3337
+ applyMatchesAnyBitset(f.matchesAny, getAllDocsBitset, capacity, getValue)
3338
+ );
3339
+ }
3340
+ if ("size" in f && f.size !== void 0) {
3341
+ bitsets.push(applySizeBitset(f.size, getAllDocsBitset, capacity, getValue));
3342
+ }
3343
+ if ("exists" in f && f.exists !== void 0) {
3344
+ bitsets.push(
3345
+ f.exists ? applyExistsBitset(getAllDocsBitset, capacity, getValue) : applyNotExistsBitset(getAllDocsBitset, capacity, getValue)
3346
+ );
3347
+ }
3348
+ if ("notExists" in f && f.notExists !== void 0) {
3349
+ bitsets.push(
3350
+ f.notExists ? applyNotExistsBitset(getAllDocsBitset, capacity, getValue) : applyExistsBitset(getAllDocsBitset, capacity, getValue)
3351
+ );
3352
+ }
3353
+ if ("isEmpty" in f && f.isEmpty !== void 0) {
3354
+ bitsets.push(
3355
+ f.isEmpty ? applyIsEmptyBitset(getAllDocsBitset, capacity, getValue) : applyIsNotEmptyBitset(getAllDocsBitset, capacity, getValue)
3356
+ );
3357
+ }
3358
+ if ("isNotEmpty" in f && f.isNotEmpty !== void 0) {
3359
+ bitsets.push(
3360
+ f.isNotEmpty ? applyIsNotEmptyBitset(getAllDocsBitset, capacity, getValue) : applyIsEmptyBitset(getAllDocsBitset, capacity, getValue)
3361
+ );
3362
+ }
3363
+ if (bitsets.length === 0) return context.allDocIdsBitset;
3364
+ if (bitsets.length === 1) return bitsets[0];
3365
+ return applyAndBitset(bitsets);
2943
3366
  }
2944
- function removeFromIndexes(state, docId, storedDoc, flatSchema, language, options) {
2945
- const fieldLengths = {};
2946
- const tokensByField = {};
2947
- const fields = storedDoc.fields;
2948
- const internalId = resolveInternalId(state, docId);
2949
- for (const [fieldPath, fieldType] of Object.entries(flatSchema)) {
2950
- const value = getNestedValue(fields, fieldPath);
2951
- if (value === void 0 || value === null) continue;
2952
- if (fieldType === "string") {
2953
- const result = tokenize(value, language, tokenizeOptions(options));
2954
- fieldLengths[fieldPath] = result.tokens.length;
2955
- const uniqueTokens = /* @__PURE__ */ new Set();
2956
- const fieldTokenList = [];
2957
- for (const t of result.tokens) {
2958
- uniqueTokens.add(t.token);
2959
- fieldTokenList.push(t.token);
2960
- }
2961
- for (const token of uniqueTokens) state.invertedIdx.remove(token, internalId);
2962
- tokensByField[fieldPath] = fieldTokenList;
2963
- } else if (fieldType === "number") {
2964
- state.numericIndexes.get(fieldPath)?.remove(internalId, value);
2965
- } else if (fieldType === "boolean") {
2966
- state.booleanIndexes.get(fieldPath)?.remove(internalId, value);
2967
- } else if (fieldType === "enum") {
2968
- state.enumIndexes.get(fieldPath)?.remove(internalId, value);
2969
- } else if (fieldType === "geopoint") {
2970
- state.geoIndexes.get(fieldPath)?.remove(internalId);
2971
- } else if (fieldType === "string[]") {
2972
- const arr = value;
2973
- const uniqueTokens = /* @__PURE__ */ new Set();
2974
- const fieldTokenList = [];
2975
- for (const item of arr) {
2976
- const result = tokenize(item, language, tokenizeOptions(options));
2977
- for (const t of result.tokens) {
2978
- uniqueTokens.add(t.token);
2979
- fieldTokenList.push(t.token);
2980
- }
2981
- }
2982
- fieldLengths[fieldPath] = fieldTokenList.length;
2983
- for (const token of uniqueTokens) state.invertedIdx.remove(token, internalId);
2984
- tokensByField[fieldPath] = fieldTokenList;
2985
- } else if (fieldType === "number[]") {
2986
- const numIdx = state.numericIndexes.get(fieldPath);
2987
- if (numIdx) {
2988
- for (const num of value) numIdx.remove(internalId, num);
2989
- }
2990
- } else if (fieldType === "boolean[]") {
2991
- const boolIdx = state.booleanIndexes.get(fieldPath);
2992
- if (boolIdx) {
2993
- for (const b of value) boolIdx.remove(internalId, b);
2994
- }
2995
- } else if (fieldType === "enum[]") {
2996
- const enumIdx = state.enumIndexes.get(fieldPath);
2997
- if (enumIdx) {
2998
- for (const e of value) enumIdx.remove(internalId, e);
2999
- }
3000
- }
3367
+ function resolveGeoIndex(fieldPath, fieldIndex) {
3368
+ if (fieldIndex?.type !== "geopoint") {
3369
+ throw new NarsilError(
3370
+ ErrorCodes.SEARCH_INVALID_FILTER,
3371
+ `Field "${fieldPath}" requires a geopoint index for geo filters`,
3372
+ { fieldPath }
3373
+ );
3001
3374
  }
3002
- return { fieldLengths, tokensByField };
3375
+ return fieldIndex.index;
3003
3376
  }
3004
- function updateFieldIndexOnly(state, docId, oldFields, newDoc, flatSchema) {
3005
- const internalId = resolveInternalId(state, docId);
3006
- for (const [fieldPath, fieldType] of Object.entries(flatSchema)) {
3007
- if (fieldType === "string" || fieldType === "string[]") continue;
3008
- const oldVal = getNestedValue(oldFields, fieldPath);
3009
- const newVal = getNestedValue(newDoc, fieldPath);
3010
- if (oldVal === newVal) continue;
3011
- if (oldVal !== void 0 && oldVal !== null && newVal !== void 0 && newVal !== null && typeof oldVal === typeof newVal && JSON.stringify(oldVal) === JSON.stringify(newVal))
3012
- continue;
3013
- if (newVal !== void 0 && newVal !== null) {
3014
- ensureFieldIndex(state, fieldPath, fieldType);
3015
- }
3016
- if (fieldType === "number") {
3377
+
3378
+ // src/core/partition/filters.ts
3379
+ function buildFilterContext(state, schema) {
3380
+ const flat = getFlatSchema(state, schema);
3381
+ const fieldIndexes = {};
3382
+ const capacity = state.docStore.internalIdCapacity();
3383
+ for (const [fieldPath, fieldType] of Object.entries(flat)) {
3384
+ if (fieldType === "number" || fieldType === "number[]") {
3017
3385
  const numIdx = state.numericIndexes.get(fieldPath);
3018
3386
  if (numIdx) {
3019
- if (oldVal !== void 0 && oldVal !== null) numIdx.remove(internalId, oldVal);
3020
- if (newVal !== void 0 && newVal !== null) numIdx.insert(internalId, newVal);
3387
+ fieldIndexes[fieldPath] = {
3388
+ type: "numeric",
3389
+ index: {
3390
+ eq: (v) => numIdx.queryEq(v),
3391
+ gt: (v) => numIdx.queryGt(v),
3392
+ gte: (v) => numIdx.queryGte(v),
3393
+ lt: (v) => numIdx.queryLt(v),
3394
+ lte: (v) => numIdx.queryLte(v),
3395
+ between: (min, max) => numIdx.queryBetween(min, max),
3396
+ allDocIds: () => numIdx.getAllDocIds(),
3397
+ eqBitset: (v, cap) => numIdx.queryEqBitset(v, cap),
3398
+ gtBitset: (v, cap) => numIdx.queryGtBitset(v, cap),
3399
+ gteBitset: (v, cap) => numIdx.queryGteBitset(v, cap),
3400
+ ltBitset: (v, cap) => numIdx.queryLtBitset(v, cap),
3401
+ lteBitset: (v, cap) => numIdx.queryLteBitset(v, cap),
3402
+ betweenBitset: (min, max, cap) => numIdx.queryBetweenBitset(min, max, cap),
3403
+ allDocIdsBitset: (cap) => numIdx.getAllDocIdsBitset(cap)
3404
+ }
3405
+ };
3021
3406
  }
3022
- } else if (fieldType === "boolean") {
3407
+ } else if (fieldType === "boolean" || fieldType === "boolean[]") {
3023
3408
  const boolIdx = state.booleanIndexes.get(fieldPath);
3024
3409
  if (boolIdx) {
3025
- if (oldVal !== void 0 && oldVal !== null) boolIdx.remove(internalId, oldVal);
3026
- if (newVal !== void 0 && newVal !== null) boolIdx.insert(internalId, newVal);
3410
+ fieldIndexes[fieldPath] = {
3411
+ type: "boolean",
3412
+ index: {
3413
+ getTrue: () => boolIdx.queryEq(true),
3414
+ getFalse: () => boolIdx.queryEq(false),
3415
+ allDocIds: () => boolIdx.getAllDocIds(),
3416
+ getTrueBitset: (cap) => boolIdx.queryEqBitset(true, cap),
3417
+ getFalseBitset: (cap) => boolIdx.queryEqBitset(false, cap),
3418
+ allDocIdsBitset: (cap) => boolIdx.getAllDocIdsBitset(cap)
3419
+ }
3420
+ };
3027
3421
  }
3028
- } else if (fieldType === "enum") {
3422
+ } else if (fieldType === "enum" || fieldType === "enum[]") {
3029
3423
  const enumIdx = state.enumIndexes.get(fieldPath);
3030
3424
  if (enumIdx) {
3031
- if (oldVal !== void 0 && oldVal !== null) enumIdx.remove(internalId, oldVal);
3032
- if (newVal !== void 0 && newVal !== null) enumIdx.insert(internalId, newVal);
3425
+ fieldIndexes[fieldPath] = {
3426
+ type: "enum",
3427
+ index: {
3428
+ getDocIds: (v) => enumIdx.queryEq(v),
3429
+ allDocIds: () => enumIdx.getAllDocIds(),
3430
+ getDocIdsBitset: (v, cap) => enumIdx.queryEqBitset(v, cap),
3431
+ getDocIdsInBitset: (values, cap) => enumIdx.queryInBitset(values, cap),
3432
+ allDocIdsBitset: (cap) => enumIdx.getAllDocIdsBitset(cap)
3433
+ }
3434
+ };
3033
3435
  }
3034
3436
  } else if (fieldType === "geopoint") {
3035
3437
  const geoIdx = state.geoIndexes.get(fieldPath);
3036
3438
  if (geoIdx) {
3037
- if (oldVal !== void 0 && oldVal !== null) geoIdx.remove(internalId);
3038
- if (newVal !== void 0 && newVal !== null) {
3039
- const geo = newVal;
3040
- geoIdx.insert(internalId, geo.lat, geo.lon);
3041
- }
3439
+ fieldIndexes[fieldPath] = {
3440
+ type: "geopoint",
3441
+ index: geoIdx
3442
+ };
3042
3443
  }
3043
- } else if (fieldType === "number[]") {
3044
- const numIdx = state.numericIndexes.get(fieldPath);
3045
- if (numIdx) {
3046
- if (oldVal !== void 0 && oldVal !== null) {
3047
- for (const n of oldVal) numIdx.remove(internalId, n);
3048
- }
3049
- if (newVal !== void 0 && newVal !== null) {
3050
- for (const n of newVal) numIdx.insert(internalId, n);
3051
- }
3444
+ }
3445
+ }
3446
+ let cachedAllDocIds = null;
3447
+ let cachedAllDocIdsBitset = null;
3448
+ return {
3449
+ fieldIndexes,
3450
+ getFieldValue: (internalId, fieldPath) => getFieldValueByInternalId(state.docStore, internalId, fieldPath),
3451
+ get allDocIds() {
3452
+ if (!cachedAllDocIds) {
3453
+ cachedAllDocIds = getAllInternalDocIds(state.docStore);
3052
3454
  }
3053
- } else if (fieldType === "boolean[]") {
3054
- const boolIdx = state.booleanIndexes.get(fieldPath);
3055
- if (boolIdx) {
3056
- if (oldVal !== void 0 && oldVal !== null) {
3057
- for (const b of oldVal) boolIdx.remove(internalId, b);
3058
- }
3059
- if (newVal !== void 0 && newVal !== null) {
3060
- for (const b of newVal) boolIdx.insert(internalId, b);
3061
- }
3455
+ return cachedAllDocIds;
3456
+ },
3457
+ capacity,
3458
+ get allDocIdsBitset() {
3459
+ if (!cachedAllDocIdsBitset) {
3460
+ cachedAllDocIdsBitset = bitsetFromSet(this.allDocIds, capacity);
3062
3461
  }
3063
- } else if (fieldType === "enum[]") {
3064
- const enumIdx = state.enumIndexes.get(fieldPath);
3065
- if (enumIdx) {
3066
- if (oldVal !== void 0 && oldVal !== null) {
3067
- for (const e of oldVal) enumIdx.remove(internalId, e);
3068
- }
3069
- if (newVal !== void 0 && newVal !== null) {
3070
- for (const e of newVal) enumIdx.insert(internalId, e);
3071
- }
3462
+ return cachedAllDocIdsBitset;
3463
+ }
3464
+ };
3465
+ }
3466
+ function applyPartitionFilters(state, filters, schema) {
3467
+ const context = buildFilterContext(state, schema);
3468
+ const resultBitset = evaluateFilters(filters, context);
3469
+ const resolver = state.docStore.resolver();
3470
+ const externalResult = /* @__PURE__ */ new Set();
3471
+ for (let wi = 0; wi < resultBitset.length; wi++) {
3472
+ let word = resultBitset[wi];
3473
+ if (word === 0) continue;
3474
+ const base = wi << 5;
3475
+ while (word !== 0) {
3476
+ const tz = Math.clz32(word & -word) ^ 31;
3477
+ const internalId = base + tz;
3478
+ const externalId = resolver.toExternal(internalId);
3479
+ if (externalId !== void 0) {
3480
+ externalResult.add(externalId);
3072
3481
  }
3482
+ word &= word - 1;
3073
3483
  }
3074
3484
  }
3485
+ return externalResult;
3486
+ }
3487
+ function applyPartitionFiltersBitset(state, filters, schema) {
3488
+ const context = buildFilterContext(state, schema);
3489
+ return evaluateFilters(filters, context);
3075
3490
  }
3076
3491
 
3077
3492
  // src/core/scorer.ts
@@ -3129,6 +3544,7 @@ function accumulateTermScore(docScores, internalId, termScore, collect, fieldNam
3129
3544
  function searchFulltext(state, params) {
3130
3545
  const {
3131
3546
  queryTokens,
3547
+ prefixExpansion,
3132
3548
  fields,
3133
3549
  boost,
3134
3550
  tolerance = 0,
@@ -3153,9 +3569,132 @@ function searchFulltext(state, params) {
3153
3569
  const useIntersection = termMatch === "all" && queryTokens.length > 1;
3154
3570
  const fieldNames = state.fieldNameTable.names;
3155
3571
  const resolver = state.docStore.resolver();
3572
+ function resolveFieldLength(internalId, fieldName, avgLen) {
3573
+ let cachedLengths = fieldLengthCache.get(internalId);
3574
+ if (cachedLengths === void 0) {
3575
+ const externalId = resolver.toExternal(internalId);
3576
+ const storedDoc = externalId !== void 0 ? state.docStore.get(externalId) : void 0;
3577
+ cachedLengths = storedDoc?.fieldLengths ?? null;
3578
+ fieldLengthCache.set(internalId, cachedLengths);
3579
+ }
3580
+ return cachedLengths?.[fieldName] ?? avgLen;
3581
+ }
3582
+ function resolvePrefixMatches(token, expansionTerms) {
3583
+ const found = [];
3584
+ const seen = /* @__PURE__ */ new Set();
3585
+ for (const term of [token, ...expansionTerms]) {
3586
+ if (seen.has(term)) continue;
3587
+ seen.add(term);
3588
+ const postingList = state.invertedIdx.lookup(term);
3589
+ if (!postingList) continue;
3590
+ const docFreq = globalStats ? globalDocFreqs[term] ?? postingList.docIdSet.size : postingList.docIdSet.size;
3591
+ found.push({ token: term, postingList, docFreq });
3592
+ }
3593
+ if (found.length === 0) return [];
3594
+ let blendedDf = 0;
3595
+ for (const f of found) {
3596
+ if (f.docFreq > blendedDf) blendedDf = f.docFreq;
3597
+ }
3598
+ const blendedIdf = computeIDF(blendedDf, totalDocs);
3599
+ return found.map((f) => ({
3600
+ token: f.token,
3601
+ factor: Math.min(1, token.length / f.token.length),
3602
+ postingList: f.postingList,
3603
+ docFreq: blendedDf,
3604
+ idf: blendedIdf
3605
+ }));
3606
+ }
3607
+ function computePrefixContributions(matches, collect) {
3608
+ const best = /* @__PURE__ */ new Map();
3609
+ for (const match of matches) {
3610
+ const perTerm = /* @__PURE__ */ new Map();
3611
+ const list = match.postingList;
3612
+ const hasDeleted = list.deletedDocs.size > 0;
3613
+ for (let pi = 0; pi < list.length; pi++) {
3614
+ const internalId = list.docIds[pi];
3615
+ if (hasDeleted && list.deletedDocs.has(internalId)) continue;
3616
+ if (filterBitset && !bitsetHas(filterBitset, internalId)) continue;
3617
+ const fieldName = fieldNames[list.fieldNameIndices[pi]];
3618
+ if (fields && !fields.includes(fieldName)) continue;
3619
+ const termFrequency = list.termFrequencies[pi];
3620
+ const fieldBoost = boost?.[fieldName] ?? 1;
3621
+ const avgLen = avgFieldLengths[fieldName] ?? 1;
3622
+ const actualFieldLength = resolveFieldLength(internalId, fieldName, avgLen);
3623
+ const termScore = scoreFn(termFrequency, match.docFreq, totalDocs, actualFieldLength, avgLen, bm25Params) * fieldBoost * match.factor;
3624
+ const existing = perTerm.get(internalId);
3625
+ if (existing) {
3626
+ existing.score += termScore;
3627
+ if (collect) {
3628
+ existing.termFrequencies[`${fieldName}:${match.token}`] = termFrequency;
3629
+ existing.fieldLengths[fieldName] = actualFieldLength;
3630
+ }
3631
+ } else if (collect) {
3632
+ perTerm.set(internalId, {
3633
+ score: termScore,
3634
+ token: match.token,
3635
+ idf: match.idf,
3636
+ termFrequencies: { [`${fieldName}:${match.token}`]: termFrequency },
3637
+ fieldLengths: { [fieldName]: actualFieldLength }
3638
+ });
3639
+ } else {
3640
+ perTerm.set(internalId, {
3641
+ score: termScore,
3642
+ token: match.token,
3643
+ idf: match.idf,
3644
+ termFrequencies: EMPTY_COMPONENTS,
3645
+ fieldLengths: EMPTY_COMPONENTS
3646
+ });
3647
+ }
3648
+ }
3649
+ for (const [internalId, contribution] of perTerm) {
3650
+ const current = best.get(internalId);
3651
+ if (!current || contribution.score > current.score) {
3652
+ best.set(internalId, contribution);
3653
+ }
3654
+ }
3655
+ }
3656
+ return best;
3657
+ }
3658
+ function mergePrefixContribution(internalId, contribution, collect) {
3659
+ const existing = docScores.get(internalId);
3660
+ if (existing) {
3661
+ existing.score += contribution.score;
3662
+ if (collect) {
3663
+ Object.assign(existing.termFrequencies, contribution.termFrequencies);
3664
+ Object.assign(existing.fieldLengths, contribution.fieldLengths);
3665
+ existing.idf[contribution.token] = contribution.idf;
3666
+ }
3667
+ return;
3668
+ }
3669
+ if (collect) {
3670
+ docScores.set(internalId, {
3671
+ score: contribution.score,
3672
+ termFrequencies: contribution.termFrequencies,
3673
+ fieldLengths: contribution.fieldLengths,
3674
+ idf: { [contribution.token]: contribution.idf }
3675
+ });
3676
+ } else {
3677
+ docScores.set(internalId, {
3678
+ score: contribution.score,
3679
+ termFrequencies: EMPTY_COMPONENTS,
3680
+ fieldLengths: EMPTY_COMPONENTS,
3681
+ idf: EMPTY_COMPONENTS
3682
+ });
3683
+ }
3684
+ }
3156
3685
  if (useIntersection) {
3157
3686
  const resolved = [];
3687
+ let prefixMatches = [];
3158
3688
  for (const qt of queryTokens) {
3689
+ if (prefixExpansion && qt.token === prefixExpansion.token) {
3690
+ prefixMatches = resolvePrefixMatches(qt.token, prefixExpansion.terms);
3691
+ let totalPostings2 = 0;
3692
+ for (const m of prefixMatches) {
3693
+ totalPostings2 += m.postingList.length;
3694
+ }
3695
+ resolved.push({ token: qt.token, matches: [], totalPostings: totalPostings2, isPrefix: true });
3696
+ continue;
3697
+ }
3159
3698
  const rawMatches = exact ? (() => {
3160
3699
  const postingList = state.invertedIdx.lookup(qt.token);
3161
3700
  return postingList ? [{ token: qt.token, postingList }] : [];
@@ -3172,6 +3711,14 @@ function searchFulltext(state, params) {
3172
3711
  }
3173
3712
  resolved.sort((a, b) => a.totalPostings - b.totalPostings);
3174
3713
  for (let tokenIndex = 0; tokenIndex < resolved.length; tokenIndex++) {
3714
+ if (resolved[tokenIndex].isPrefix) {
3715
+ const contributions = computePrefixContributions(prefixMatches, collectComponents);
3716
+ for (const [internalId, contribution] of contributions) {
3717
+ if (tokenIndex > 0 && !docScores.has(internalId)) continue;
3718
+ mergePrefixContribution(internalId, contribution, collectComponents);
3719
+ }
3720
+ continue;
3721
+ }
3175
3722
  for (const match of resolved[tokenIndex].matches) {
3176
3723
  const list = match.postingList;
3177
3724
  const hasDeleted = list.deletedDocs.size > 0;
@@ -3211,6 +3758,16 @@ function searchFulltext(state, params) {
3211
3758
  }
3212
3759
  } else {
3213
3760
  for (const qt of queryTokens) {
3761
+ if (prefixExpansion && qt.token === prefixExpansion.token) {
3762
+ const contributions = computePrefixContributions(
3763
+ resolvePrefixMatches(qt.token, prefixExpansion.terms),
3764
+ collectComponents
3765
+ );
3766
+ for (const [internalId, contribution] of contributions) {
3767
+ mergePrefixContribution(internalId, contribution, collectComponents);
3768
+ }
3769
+ continue;
3770
+ }
3214
3771
  const matchingPostings = exact ? (() => {
3215
3772
  const postingList = state.invertedIdx.lookup(qt.token);
3216
3773
  return postingList ? [{ token: qt.token, postingList }] : [];
@@ -3397,6 +3954,7 @@ function serializePartition(state, partitionId, indexName, totalPartitions, lang
3397
3954
  enum: serializedEnum,
3398
3955
  geopoint: serializedGeo
3399
3956
  },
3957
+ surfaceForms: state.surfaceRegistry.serialize(),
3400
3958
  statistics: serializedStats
3401
3959
  };
3402
3960
  }
@@ -3456,6 +4014,7 @@ function deserializePartition(state, data, clearFn, schema) {
3456
4014
  geoIdx.deserialize(converted);
3457
4015
  state.geoIndexes.set(path, geoIdx);
3458
4016
  }
4017
+ state.surfaceRegistry.deserialize(data.surfaceForms ?? {});
3459
4018
  state.stats.deserialize(data.statistics);
3460
4019
  const flatSchema = getFlatSchema(state, schema);
3461
4020
  const serializedFields = data.schema;
@@ -3473,6 +4032,72 @@ function deserializePartition(state, data, clearFn, schema) {
3473
4032
  }
3474
4033
  }
3475
4034
 
4035
+ // src/core/partition/suggestions.ts
4036
+ function verbatimOccurrences(state, token) {
4037
+ const postingList = state.invertedIdx.lookup(token);
4038
+ if (!postingList) return 0;
4039
+ const derived = postingList.totalTermFrequency - state.surfaceRegistry.stemChangedTotalFor(token);
4040
+ return derived > 0 ? derived : 0;
4041
+ }
4042
+ function suggestDisplayTerms(state, surfacePrefix, stemmedPrefix, limit) {
4043
+ if (limit <= 0 || surfacePrefix.length === 0) return [];
4044
+ const groups = /* @__PURE__ */ new Map();
4045
+ for (const candidate of state.surfaceRegistry.candidatesForPrefix(surfacePrefix)) {
4046
+ const postingList = state.invertedIdx.lookup(candidate.token);
4047
+ if (!postingList || postingList.docIdSet.size === 0) continue;
4048
+ let group = groups.get(candidate.token);
4049
+ if (!group) {
4050
+ group = { documentFrequency: postingList.docIdSet.size, surfaces: [] };
4051
+ groups.set(candidate.token, group);
4052
+ }
4053
+ group.surfaces.push({ surface: candidate.surface, occurrences: candidate.occurrences });
4054
+ }
4055
+ const termPrefixes = state.surfaceRegistry.size() === 0 && stemmedPrefix !== surfacePrefix && stemmedPrefix.length > 0 ? [surfacePrefix, stemmedPrefix] : [surfacePrefix];
4056
+ for (const prefix of termPrefixes) {
4057
+ for (const suggestion of state.invertedIdx.prefixSearch(prefix, Number.MAX_SAFE_INTEGER)) {
4058
+ const occurrences = verbatimOccurrences(state, suggestion.term);
4059
+ if (occurrences === 0) continue;
4060
+ let group = groups.get(suggestion.term);
4061
+ if (!group) {
4062
+ group = { documentFrequency: suggestion.documentFrequency, surfaces: [] };
4063
+ groups.set(suggestion.term, group);
4064
+ } else if (group.surfaces.some((s) => s.surface === suggestion.term)) {
4065
+ continue;
4066
+ }
4067
+ group.surfaces.push({ surface: suggestion.term, occurrences });
4068
+ }
4069
+ }
4070
+ const results = [];
4071
+ for (const [token, group] of groups) {
4072
+ results.push({ token, documentFrequency: group.documentFrequency, surfaces: group.surfaces });
4073
+ }
4074
+ results.sort((a, b) => b.documentFrequency - a.documentFrequency || (a.token < b.token ? -1 : 1));
4075
+ if (results.length > limit) results.length = limit;
4076
+ return results;
4077
+ }
4078
+ function expandTermPrefix(state, surfacePrefix, stemmedToken, maxExpansions) {
4079
+ if (maxExpansions <= 0 || surfacePrefix.length === 0) return [];
4080
+ const dfByToken = /* @__PURE__ */ new Map();
4081
+ for (const candidate of state.surfaceRegistry.candidatesForPrefix(surfacePrefix)) {
4082
+ if (dfByToken.has(candidate.token)) continue;
4083
+ const postingList = state.invertedIdx.lookup(candidate.token);
4084
+ if (!postingList || postingList.docIdSet.size === 0) continue;
4085
+ dfByToken.set(candidate.token, postingList.docIdSet.size);
4086
+ }
4087
+ const termPrefixes = state.surfaceRegistry.size() === 0 && stemmedToken !== surfacePrefix && stemmedToken.length > 0 ? [surfacePrefix, stemmedToken] : [surfacePrefix];
4088
+ for (const prefix of termPrefixes) {
4089
+ for (const suggestion of state.invertedIdx.prefixSearch(prefix, Number.MAX_SAFE_INTEGER)) {
4090
+ if (dfByToken.has(suggestion.term)) continue;
4091
+ if (verbatimOccurrences(state, suggestion.term) === 0) continue;
4092
+ dfByToken.set(suggestion.term, suggestion.documentFrequency);
4093
+ }
4094
+ }
4095
+ const entries = Array.from(dfByToken.entries());
4096
+ entries.sort((a, b) => b[1] - a[1] || (a[0] < b[0] ? -1 : 1));
4097
+ if (entries.length > maxExpansions) entries.length = maxExpansions;
4098
+ return entries.map((e) => e[0]);
4099
+ }
4100
+
3476
4101
  // src/core/partition/wire-payload.ts
3477
4102
  function serializePartitionToWirePayloadV2(state, partitionId, indexName, totalPartitions, language, schema) {
3478
4103
  const flatSchema = getFlatSchema(state, schema);
@@ -3569,6 +4194,7 @@ function serializePartitionToWirePayloadV2(state, partitionId, indexName, totalP
3569
4194
  enum: wireEnum,
3570
4195
  geopoint: wireGeo
3571
4196
  },
4197
+ surface_forms: state.surfaceRegistry.serialize(),
3572
4198
  statistics: {
3573
4199
  total_documents: serializedStats.totalDocuments,
3574
4200
  total_field_lengths: serializedStats.totalFieldLengths,
@@ -3585,6 +4211,7 @@ function createPartitionIndex(partitionId, trackPositions = true) {
3585
4211
  invertedIdx: createInvertedIndex(fieldNameTable),
3586
4212
  docStore: createDocumentStore(),
3587
4213
  stats: createPartitionStats(),
4214
+ surfaceRegistry: createSurfaceRegistry(),
3588
4215
  numericIndexes: /* @__PURE__ */ new Map(),
3589
4216
  booleanIndexes: /* @__PURE__ */ new Map(),
3590
4217
  enumIndexes: /* @__PURE__ */ new Map(),
@@ -3597,6 +4224,7 @@ function createPartitionIndex(partitionId, trackPositions = true) {
3597
4224
  function clearAll() {
3598
4225
  state.invertedIdx.clear();
3599
4226
  state.docStore.clear();
4227
+ state.surfaceRegistry.clear();
3600
4228
  for (const idx of state.numericIndexes.values()) idx.clear();
3601
4229
  for (const idx of state.booleanIndexes.values()) idx.clear();
3602
4230
  for (const idx of state.enumIndexes.values()) idx.clear();
@@ -3747,6 +4375,8 @@ function createPartitionIndex(partitionId, trackPositions = true) {
3747
4375
  }
3748
4376
  bytes += totalPostings * POSTING_ENTRY_SIZE;
3749
4377
  bytes += termCount * PER_TERM_OVERHEAD;
4378
+ const SURFACE_ENTRY_OVERHEAD = 140;
4379
+ bytes += state.surfaceRegistry.size() * SURFACE_ENTRY_OVERHEAD;
3750
4380
  const FIELD_ENTRY_OVERHEAD = 42;
3751
4381
  bytes += docCount * state.numericIndexes.size * FIELD_ENTRY_OVERHEAD;
3752
4382
  bytes += docCount * state.booleanIndexes.size * FIELD_ENTRY_OVERHEAD;
@@ -3766,8 +4396,11 @@ function createPartitionIndex(partitionId, trackPositions = true) {
3766
4396
  computeFacets(docIds, config, schema) {
3767
4397
  return computeFacets(state, docIds, config, schema);
3768
4398
  },
3769
- suggestTerms(prefix, limit) {
3770
- return state.invertedIdx.prefixSearch(prefix, limit);
4399
+ suggestTerms(surfacePrefix, stemmedPrefix, limit) {
4400
+ return suggestDisplayTerms(state, surfacePrefix, stemmedPrefix, limit);
4401
+ },
4402
+ expandTermPrefix(surfacePrefix, stemmedToken, maxExpansions) {
4403
+ return expandTermPrefix(state, surfacePrefix, stemmedToken, maxExpansions);
3771
4404
  },
3772
4405
  serialize(indexName, totalPartitions, language, schema) {
3773
4406
  return serializePartition(state, partitionId, indexName, totalPartitions, language, schema);
@@ -3822,13 +4455,15 @@ function createPartitionManager(indexName, config, language, router, initialPart
3822
4455
  function resolveInsertOptions(options) {
3823
4456
  const applyStrict = config.strict === true;
3824
4457
  const applyAnalyzer = config.stopWords !== void 0 || config.tokenizer !== void 0;
3825
- if (!applyStrict && !applyAnalyzer) return options;
4458
+ const applySurfaces = config.surfaceForms === true;
4459
+ if (!applyStrict && !applyAnalyzer && !applySurfaces) return options;
3826
4460
  const resolved = { ...options };
3827
4461
  if (applyStrict) resolved.strict = true;
3828
4462
  if (applyAnalyzer) {
3829
4463
  resolved.stopWordOverride = options?.stopWordOverride ?? config.stopWords;
3830
4464
  resolved.customTokenizer = options?.customTokenizer ?? config.tokenizer;
3831
4465
  }
4466
+ if (applySurfaces) resolved.collectSurfaces = true;
3832
4467
  return resolved;
3833
4468
  }
3834
4469
  function rebuildDocPartitionMap() {
@@ -3925,7 +4560,7 @@ function createPartitionManager(indexName, config, language, router, initialPart
3925
4560
  if (pid === void 0) {
3926
4561
  throw new NarsilError(ErrorCodes.DOC_NOT_FOUND, `Document "${docId}" not found in any partition`, { docId });
3927
4562
  }
3928
- partitions[pid].remove(docId, config.schema, language);
4563
+ partitions[pid].remove(docId, config.schema, language, resolveInsertOptions());
3929
4564
  docPartitionMap.delete(docId);
3930
4565
  },
3931
4566
  beginBatchRemove() {
@@ -5341,4 +5976,4 @@ function createDirectExecutor() {
5341
5976
  };
5342
5977
  }
5343
5978
 
5344
- export { MAX_NESTING_DEPTH, clearNormalizationCache, configureNormalizationCache, createDirectExecutor, createPartitionIndex, createPartitionManager, createPartitionRouter, createVectorIndex, deserializePayloadV2, detectRuntime, extractVectorFieldsFromSchema, fanOutQuery, flattenSchema, fnv1a, getLanguage, getNestedValue, getNormalizationCacheSize, isNodeMainThread, kWayMerge, mergePartitionStats, registerLanguage, resetNormalizationCache, spawnNodeWorker, tokenize, validateRequiredFields, validateSchema, validateVectorPromotion };
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 };