@delali/narsil 0.3.1-next.74 → 0.3.1-next.76

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/{capabilities-CAMzmkAg.d.ts → capabilities-1lEYL3WK.d.ts} +16 -2
  2. package/dist/{chunk-Z3DQPOJQ.mjs → chunk-356UOTK2.mjs} +1 -1
  3. package/dist/chunk-4344I4MR.mjs +2217 -0
  4. package/dist/{chunk-PO53FV4Q.mjs → chunk-AMGDG4ZJ.mjs} +1 -1
  5. package/dist/{chunk-LX4JERXI.mjs → chunk-BT62MUM5.mjs} +1 -1
  6. package/dist/{chunk-NOZUQM7J.mjs → chunk-BT6BI4GV.mjs} +41 -3
  7. package/dist/{chunk-VKX2BPXD.mjs → chunk-D4EF7J3I.mjs} +127 -20
  8. package/dist/{chunk-BVBIQQKG.mjs → chunk-DFFQOA7S.mjs} +26 -67
  9. package/dist/{chunk-CTOSGRM7.mjs → chunk-G7IP3UDZ.mjs} +6 -5
  10. package/dist/chunk-GES7U2PM.mjs +68 -0
  11. package/dist/{chunk-CUZOX6CN.mjs → chunk-HH6ALOUZ.mjs} +31 -27
  12. package/dist/{chunk-LJYTW7GT.mjs → chunk-IBR6634K.mjs} +1 -1
  13. package/dist/{chunk-I23G3IZC.mjs → chunk-NO3ABFLE.mjs} +2 -63
  14. package/dist/{chunk-T5ZWEWVD.mjs → chunk-PJU6EF22.mjs} +3 -2
  15. package/dist/{chunk-TVFLHM7M.mjs → chunk-PXDY5VRT.mjs} +3 -3
  16. package/dist/{chunk-3DDUNI6N.mjs → chunk-V3B24XR7.mjs} +7 -1
  17. package/dist/{client-C-I70ArH.d.ts → client-ZXcbSViH.d.ts} +2 -2
  18. package/dist/client.d.ts +6 -6
  19. package/dist/{config-C6Iw5rCX.d.ts → config-DHL2KsfD.d.ts} +1 -1
  20. package/dist/{config-DJPH3IQB.d.ts → config-DTe5EbWW.d.ts} +4 -2
  21. package/dist/direct-executor-Q7PNCGZB.mjs +17 -0
  22. package/dist/distribution/coordinator/etcd.mjs +1 -1
  23. package/dist/distribution/coordinator/in-memory.mjs +1 -1
  24. package/dist/distribution.d.ts +5 -5
  25. package/dist/distribution.mjs +22 -15
  26. package/dist/{engine-B8eRPO7m.d.ts → engine-BjPBzWkA.d.ts} +1 -1
  27. package/dist/index.browser.mjs +158 -38
  28. package/dist/index.d.ts +5 -5
  29. package/dist/index.mjs +13 -13
  30. package/dist/invalidation/filesystem.mjs +1 -1
  31. package/dist/{memory-DJEwWoCi.d.ts → memory-DW3OJ0im.d.ts} +105 -24
  32. package/dist/partial-words.mjs +5 -4
  33. package/dist/pattern.mjs +3 -2
  34. package/dist/persistence/durability/checkpoint-worker.mjs +13 -13
  35. package/dist/react.d.ts +6 -6
  36. package/dist/server.d.ts +8 -8
  37. package/dist/server.mjs +34 -11
  38. package/dist/{tasks-BVmAKYBh.d.ts → tasks-BTbwBqar.d.ts} +1 -1
  39. package/dist/{thread-W6DPKEF6.mjs → thread-YOTNJEVY.mjs} +15 -10
  40. package/dist/vector/search-worker.mjs +2 -2
  41. package/dist/workers/entry.mjs +3 -3
  42. package/package.json +8 -7
  43. package/dist/chunk-XW25X6AU.mjs +0 -614
  44. package/dist/chunk-ZKRV5M2P.mjs +0 -75
  45. package/dist/direct-executor-KFAGILJC.mjs +0 -17
@@ -852,7 +852,7 @@ function platformPatternSearch() {
852
852
  // src/core/pattern-index/registry.ts
853
853
  var registered2 = null;
854
854
  function isPatternSearchModule(value) {
855
- return value !== null && typeof value === "object" && value.name === "pattern" && "createIndex" in value && typeof value.createIndex === "function" && "readArrays" in value && typeof value.readArrays === "function" && "mergeArrays" in value && typeof value.mergeArrays === "function" && "matchInto" in value && typeof value.matchInto === "function";
855
+ return value !== null && typeof value === "object" && value.name === "pattern" && "createIndex" in value && typeof value.createIndex === "function" && "readArrays" in value && typeof value.readArrays === "function" && "mergeArrays" in value && typeof value.mergeArrays === "function" && "matchInto" in value && typeof value.matchInto === "function" && "checkExpression" in value && typeof value.checkExpression === "function" && "expressionFootprint" in value && typeof value.expressionFootprint === "function";
856
856
  }
857
857
  function registerPatternSearch(module) {
858
858
  if (!isPatternSearchModule(module)) {
@@ -2631,7 +2631,9 @@ var MATCHER_SIMULATION_BYTES_PER_POSITION = 16;
2631
2631
  var MATCHER_STATE_OVERHEAD_BYTES = 96;
2632
2632
  var PATTERN_WORK_FLUSH_UNITS = 65536;
2633
2633
  var PATTERN_RUN_CODE_POINTS = 3;
2634
+ var MAX_PATTERN_TOLERANCE = 2;
2634
2635
  var PATTERN_TEST_OPERATORS = ["eq", "ne", "in", "nin", "startsWith", "endsWith", "contains"];
2636
+ var PATTERN_EXPRESSION_OPERATORS = ["wildcard", "regex"];
2635
2637
 
2636
2638
  // src/geo/constants.ts
2637
2639
  var DEG_TO_RAD = Math.PI / 180;
@@ -3432,8 +3434,12 @@ function fuzzyTermMatches(queryTerm, indexTerm, tolerance, prefixLength) {
3432
3434
  return boundedLevenshtein(queryTerm, indexTerm, tolerance).withinTolerance;
3433
3435
  }
3434
3436
  function boundedLevenshtein(a, b, tolerance) {
3435
- if (tolerance < 0) return { distance: -1, withinTolerance: false };
3436
- if (a === b) return { distance: 0, withinTolerance: true };
3437
+ if (tolerance < 0) return { distance: -1, withinTolerance: false, cells: 0 };
3438
+ if (a === b) return { distance: 0, withinTolerance: true, cells: 0 };
3439
+ return tabulatedLevenshtein(a, b, tolerance);
3440
+ }
3441
+ function tabulatedLevenshtein(a, b, tolerance) {
3442
+ if (tolerance < 0) return { distance: -1, withinTolerance: false, cells: 0 };
3437
3443
  if (hasSurrogate(a) || hasSurrogate(b)) return bandedDistance(Array.from(a), Array.from(b), tolerance);
3438
3444
  return bandedDistance(a, b, tolerance);
3439
3445
  }
@@ -3441,30 +3447,32 @@ function bandedDistance(a, b, tolerance) {
3441
3447
  const m = a.length;
3442
3448
  const n = b.length;
3443
3449
  const beyond = tolerance + 1;
3444
- if (Math.abs(m - n) > tolerance) return { distance: beyond, withinTolerance: false };
3450
+ if (Math.abs(m - n) > tolerance) return { distance: beyond, withinTolerance: false, cells: 0 };
3445
3451
  let previous = new Uint32Array(n + 1).fill(beyond);
3446
3452
  let current = new Uint32Array(n + 1).fill(beyond);
3447
3453
  const firstRowEnd = Math.min(n, tolerance);
3448
3454
  for (let j = 0; j <= firstRowEnd; j++) previous[j] = j;
3455
+ let cells = 0;
3449
3456
  for (let i = 1; i <= m; i++) {
3450
3457
  const low = Math.max(1, i - tolerance);
3451
3458
  const high = Math.min(n, i + tolerance);
3452
3459
  current[low - 1] = low === 1 ? i : beyond;
3453
3460
  let rowMin = current[low - 1];
3454
3461
  const character = a[i - 1];
3462
+ if (high >= low) cells += high - low + 1;
3455
3463
  for (let j = low; j <= high; j++) {
3456
3464
  const substitution = previous[j - 1] + (character === b[j - 1] ? 0 : 1);
3457
3465
  const cell = Math.min(previous[j] + 1, current[j - 1] + 1, substitution, beyond);
3458
3466
  current[j] = cell;
3459
3467
  if (cell < rowMin) rowMin = cell;
3460
3468
  }
3461
- if (rowMin > tolerance) return { distance: beyond, withinTolerance: false };
3469
+ if (rowMin > tolerance) return { distance: beyond, withinTolerance: false, cells };
3462
3470
  const finished = previous;
3463
3471
  previous = current;
3464
3472
  current = finished;
3465
3473
  }
3466
3474
  const distance = previous[n];
3467
- return { distance, withinTolerance: distance <= tolerance };
3475
+ return { distance, withinTolerance: distance <= tolerance, cells };
3468
3476
  }
3469
3477
 
3470
3478
  // src/core/posting-list.ts
@@ -4406,7 +4414,24 @@ function computeFacets(state, matched, config, schema) {
4406
4414
  }
4407
4415
 
4408
4416
  // src/core/pattern-index/host.ts
4409
- var PATTERN_HOST = { appendFoldedCodePoint };
4417
+ var NO_SOURCES = [];
4418
+ var foldSources = null;
4419
+ function sourcesByFold() {
4420
+ if (foldSources !== null) return foldSources;
4421
+ const sources = /* @__PURE__ */ new Map();
4422
+ const add = (source, folded) => {
4423
+ const known = sources.get(folded);
4424
+ if (known === void 0) sources.set(folded, [source]);
4425
+ else known.push(source);
4426
+ };
4427
+ for (const [source, folded] of singleFoldTable()) add(source, folded);
4428
+ foldSources = sources;
4429
+ return sources;
4430
+ }
4431
+ function singleFoldSources(folded) {
4432
+ return sourcesByFold().get(folded) ?? NO_SOURCES;
4433
+ }
4434
+ var PATTERN_HOST = { appendFoldedCodePoint, singleFoldSources };
4410
4435
  function patternIndexOf(indexes, fieldPath) {
4411
4436
  let index = indexes.get(fieldPath);
4412
4437
  if (index === void 0) {
@@ -5714,6 +5739,43 @@ var MAX_FILTER_TESTS = 1024;
5714
5739
  var MAX_FILTER_ARRAY_SIZE = 65536;
5715
5740
  var GEO_UNIT_METRES = { km: 1e3, mi: 1609.344, m: 1 };
5716
5741
 
5742
+ // src/core/pattern-index/typo-runs.ts
5743
+ var BASIC_PLANE_LAST = 65535;
5744
+ function isPatternTolerance(value) {
5745
+ if (value === "auto") return true;
5746
+ return typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= MAX_PATTERN_TOLERANCE;
5747
+ }
5748
+ function foldTypoText(text, host) {
5749
+ const folded = [];
5750
+ let longestFold = 0;
5751
+ for (let at = 0; at < text.length; at++) {
5752
+ const codePoint = text.codePointAt(at) ?? 0;
5753
+ if (codePoint > BASIC_PLANE_LAST) at++;
5754
+ const before = folded.length;
5755
+ host.appendFoldedCodePoint(codePoint, folded);
5756
+ longestFold = Math.max(longestFold, folded.length - before);
5757
+ }
5758
+ return { folded, longestFold };
5759
+ }
5760
+ function distinctTypoRuns(text, limit) {
5761
+ const { folded } = text;
5762
+ const seen = /* @__PURE__ */ new Set();
5763
+ const runs = [];
5764
+ for (let at = 0; at + PATTERN_RUN_CODE_POINTS <= folded.length && seen.size < limit; at++) {
5765
+ const key = String.fromCodePoint(folded[at], folded[at + 1], folded[at + 2]);
5766
+ if (seen.has(key)) continue;
5767
+ seen.add(key);
5768
+ runs.push(folded[at], folded[at + 1], folded[at + 2]);
5769
+ }
5770
+ return runs;
5771
+ }
5772
+ function typoRunsNeeded(tolerance, longestFold) {
5773
+ return tolerance * (2 + longestFold) + 1;
5774
+ }
5775
+ function distinctTypoRunCount(text, limit) {
5776
+ return distinctTypoRuns(text, limit).length / PATTERN_RUN_CODE_POINTS;
5777
+ }
5778
+
5717
5779
  // src/filters/pattern-tests.ts
5718
5780
  function excluding(context, fieldPath, excluded) {
5719
5781
  const universe = bitsetAnd(context.presentBitset(fieldPath), context.allDocIdsBitset);
@@ -5727,18 +5789,21 @@ function textsOf2(operand) {
5727
5789
  }
5728
5790
  function evaluatePatternTests(fieldPath, filter, context, bitsets) {
5729
5791
  const caseFold = filter.caseFold === true;
5730
- const matching = (operator, texts) => {
5792
+ const matching = (operator, texts, tolerance = 0) => {
5731
5793
  const into = createBitSet(context.capacity);
5732
- for (const text of texts) context.matchPattern(fieldPath, operator, text, caseFold, into);
5794
+ for (const text of texts) context.matchPattern(fieldPath, operator, text, caseFold, tolerance, into);
5733
5795
  return into;
5734
5796
  };
5735
- if (typeof filter.eq === "string") bitsets.push(matching("eq", [filter.eq]));
5797
+ const eqTolerance = isPatternTolerance(filter.tolerance) ? filter.tolerance : 0;
5798
+ if (typeof filter.eq === "string") bitsets.push(matching("eq", [filter.eq], eqTolerance));
5736
5799
  if (typeof filter.ne === "string") bitsets.push(excluding(context, fieldPath, matching("eq", [filter.ne])));
5737
5800
  if (filter.in !== void 0) bitsets.push(matching("eq", textsOf2(filter.in)));
5738
5801
  if (filter.nin !== void 0) bitsets.push(excluding(context, fieldPath, matching("eq", textsOf2(filter.nin))));
5739
5802
  if (typeof filter.startsWith === "string") bitsets.push(matching("startsWith", [filter.startsWith]));
5740
5803
  if (typeof filter.endsWith === "string") bitsets.push(matching("endsWith", [filter.endsWith]));
5741
5804
  if (typeof filter.contains === "string") bitsets.push(matching("contains", [filter.contains]));
5805
+ if (typeof filter.wildcard === "string") bitsets.push(matching("wildcard", [filter.wildcard]));
5806
+ if (typeof filter.regex === "string") bitsets.push(matching("regex", [filter.regex]));
5742
5807
  }
5743
5808
 
5744
5809
  // src/filters/field-tests.ts
@@ -5786,7 +5851,7 @@ function elementBits(context, fieldPath, parsed, value) {
5786
5851
  const { capacity } = context;
5787
5852
  if (parsed.pattern) {
5788
5853
  const matches = createBitSet(capacity);
5789
- context.matchPattern(fieldPath, "eq", value, false, matches);
5854
+ context.matchPattern(fieldPath, "eq", value, false, 0, matches);
5790
5855
  return matches;
5791
5856
  }
5792
5857
  if (typeof value === "number")
@@ -5799,7 +5864,7 @@ function anyElementBits(context, fieldPath, parsed, values) {
5799
5864
  const { capacity } = context;
5800
5865
  const union = createBitSet(capacity);
5801
5866
  if (parsed.pattern) {
5802
- for (const value of values) context.matchPattern(fieldPath, "eq", value, false, union);
5867
+ for (const value of values) context.matchPattern(fieldPath, "eq", value, false, 0, union);
5803
5868
  return union;
5804
5869
  }
5805
5870
  if (parsed.base === "enum[]") {
@@ -5943,7 +6008,10 @@ var FIELD_FILTER_OPERATORS = [
5943
6008
  "startsWith",
5944
6009
  "endsWith",
5945
6010
  "contains",
6011
+ "wildcard",
6012
+ "regex",
5946
6013
  "caseFold",
6014
+ "tolerance",
5947
6015
  "containsAll",
5948
6016
  "matchesAny",
5949
6017
  "size",
@@ -6158,14 +6226,16 @@ function requirePatternField(fieldPath, operator, type) {
6158
6226
  operator
6159
6227
  );
6160
6228
  }
6229
+ function requireTextLength(fieldPath, operator, text) {
6230
+ if (!exceedsCodePoints(text, MAX_PATTERN_TEXT_CODE_POINTS)) return;
6231
+ invalid(
6232
+ `The text of "${operator}" on pattern field "${fieldPath}" holds more than ${MAX_PATTERN_TEXT_CODE_POINTS} code points`,
6233
+ fieldPath,
6234
+ operator
6235
+ );
6236
+ }
6161
6237
  function requirePatternText(fieldPath, operator, text, caseFold) {
6162
- if (exceedsCodePoints(text, MAX_PATTERN_TEXT_CODE_POINTS)) {
6163
- invalid(
6164
- `The text of "${operator}" on pattern field "${fieldPath}" holds more than ${MAX_PATTERN_TEXT_CODE_POINTS} code points`,
6165
- fieldPath,
6166
- operator
6167
- );
6168
- }
6238
+ requireTextLength(fieldPath, operator, text);
6169
6239
  if (caseFold && foldedCodePointCount(text) > MAX_PATTERN_POSITIONS) {
6170
6240
  invalid(
6171
6241
  `The text of "${operator}" on pattern field "${fieldPath}" has a case fold of more than ${MAX_PATTERN_POSITIONS} code points, while a pattern holds at most ${MAX_PATTERN_POSITIONS} positions`,
@@ -6175,6 +6245,23 @@ function requirePatternText(fieldPath, operator, text, caseFold) {
6175
6245
  }
6176
6246
  }
6177
6247
  var ELEMENT_OPERATORS2 = ["matchesAny", "containsAll"];
6248
+ function requireExpressions(fieldPath, filter, type) {
6249
+ for (const operator of PATTERN_EXPRESSION_OPERATORS) {
6250
+ const pattern = filter[operator];
6251
+ if (pattern === void 0) continue;
6252
+ if (typeof pattern !== "string")
6253
+ invalid(`"${operator}" on field "${fieldPath}" must be a string`, fieldPath, operator);
6254
+ requirePatternField(fieldPath, operator, type);
6255
+ requireTextLength(fieldPath, operator, pattern);
6256
+ requirePatternSearch(fieldPath).checkExpression(
6257
+ operator,
6258
+ pattern,
6259
+ filter.caseFold === true,
6260
+ fieldPath,
6261
+ PATTERN_HOST
6262
+ );
6263
+ }
6264
+ }
6178
6265
  function requirePatternOperands(fieldPath, filter, type) {
6179
6266
  if (filter.contains !== void 0) {
6180
6267
  if (typeof filter.contains !== "string") {
@@ -6188,6 +6275,8 @@ function requirePatternOperands(fieldPath, filter, type) {
6188
6275
  }
6189
6276
  requirePatternField(fieldPath, "caseFold", type);
6190
6277
  }
6278
+ requireExpressions(fieldPath, filter, type);
6279
+ if (filter.tolerance !== void 0) requirePatternField(fieldPath, "tolerance", type);
6191
6280
  if (parsedTypeOf(type)?.pattern !== true) return;
6192
6281
  const caseFold = filter.caseFold === true;
6193
6282
  for (const operator of PATTERN_TEST_OPERATORS) {
@@ -6205,6 +6294,31 @@ function requirePatternOperands(fieldPath, filter, type) {
6205
6294
  if (typeof text === "string") requirePatternText(fieldPath, operator, text, false);
6206
6295
  }
6207
6296
  }
6297
+ requireTolerance(fieldPath, filter);
6298
+ }
6299
+ function requireTolerance(fieldPath, filter) {
6300
+ const { tolerance, eq } = filter;
6301
+ if (tolerance === void 0) return;
6302
+ if (!isPatternTolerance(tolerance)) {
6303
+ invalid(`"tolerance" on field "${fieldPath}" must be 0, 1, 2, or "auto"`, fieldPath, "tolerance");
6304
+ }
6305
+ if (typeof eq !== "string") {
6306
+ invalid(
6307
+ `"tolerance" applies only to an "eq" test, while the filter on field "${fieldPath}" sets no "eq" with text`,
6308
+ fieldPath,
6309
+ "tolerance"
6310
+ );
6311
+ }
6312
+ if (tolerance === 0 || tolerance === "auto") return;
6313
+ const text = foldTypoText(eq, PATTERN_HOST);
6314
+ const requiredRuns = typoRunsNeeded(tolerance, text.longestFold);
6315
+ const distinctRuns = distinctTypoRunCount(text, requiredRuns);
6316
+ if (distinctRuns >= requiredRuns) return;
6317
+ throw new NarsilError(
6318
+ ErrorCodes.SEARCH_INVALID_FILTER,
6319
+ `A tolerance of ${tolerance} on "eq" of pattern field "${fieldPath}" needs ${requiredRuns} distinct runs of three code points in the case fold of the text, while the text holds ${distinctRuns}. Lengthen the text, lower the tolerance, or set it to "auto"`,
6320
+ { fieldPath, operator: "tolerance", requiredRuns, distinctRuns }
6321
+ );
6208
6322
  }
6209
6323
  function requireWellFormedText(fieldPath, filter) {
6210
6324
  for (const [operator, operand] of Object.entries(filter)) {
@@ -6226,6 +6340,7 @@ var TEXT_OPERATORS = ["startsWith", "endsWith"];
6226
6340
  var EQUALITY_OPERATORS = ["eq", "ne"];
6227
6341
  var LIST_OPERATORS = ["in", "nin"];
6228
6342
  var GEO_OPERATORS = ["radius", "polygon"];
6343
+ var TEST_MODIFIERS = /* @__PURE__ */ new Set(["caseFold", "tolerance"]);
6229
6344
  function invalidShape(message) {
6230
6345
  throw new NarsilError(ErrorCodes.SEARCH_INVALID_FILTER, message);
6231
6346
  }
@@ -6319,7 +6434,7 @@ function requireValidOperands(fieldPath, filter, type) {
6319
6434
  function testsIn(filter) {
6320
6435
  let tests = 0;
6321
6436
  for (const [operator, operand] of Object.entries(filter)) {
6322
- if (operator === "caseFold") continue;
6437
+ if (TEST_MODIFIERS.has(operator)) continue;
6323
6438
  tests += operator === "size" && isObjectRecord(operand) ? Math.max(1, Object.keys(operand).length) : 1;
6324
6439
  }
6325
6440
  return Math.max(1, tests);
@@ -7087,13 +7202,14 @@ function buildFilterContext(state, schema, meter) {
7087
7202
  sizeIndex: (fieldPath) => state.sizeIndexes.get(fieldPath),
7088
7203
  textRangeBitset: (fieldPath, bounds) => textRangeBitsetOf(state, fieldPath, flat[fieldPath], bounds, capacity),
7089
7204
  isPatternField: (fieldPath) => patternFields.has(fieldPath),
7090
- matchPattern: (fieldPath, operator, text, caseFold, into) => {
7205
+ matchPattern: (fieldPath, operator, text, caseFold, tolerance, into) => {
7091
7206
  const index = state.patternIndexes.get(fieldPath);
7092
7207
  if (index === void 0) return;
7093
7208
  requirePatternSearch(fieldPath).matchInto({
7094
7209
  operator,
7095
7210
  text,
7096
7211
  caseFold,
7212
+ tolerance,
7097
7213
  index,
7098
7214
  matches: into,
7099
7215
  meter,
@@ -30653,57 +30769,61 @@ var POSITIONS_PER_WORD = 32;
30653
30769
  function positionWords(positions) {
30654
30770
  return Math.max(1, Math.ceil(positions / POSITIONS_PER_WORD));
30655
30771
  }
30656
- function automatonBytes(positions) {
30657
- return 2 * positionWords(positions) * Uint32Array.BYTES_PER_ELEMENT + positions * MATCHER_CODE_POINT_BYTES;
30658
- }
30659
30772
  function smallestMatcherBytes(positions) {
30660
- return automatonBytes(positions) + positions * MATCHER_SIMULATION_BYTES_PER_POSITION + MATCHER_STATE_OVERHEAD_BYTES + positions * Uint32Array.BYTES_PER_ELEMENT;
30773
+ return 2 * positionWords(positions) * Uint32Array.BYTES_PER_ELEMENT + positions * MATCHER_CODE_POINT_BYTES + positions * MATCHER_SIMULATION_BYTES_PER_POSITION + MATCHER_STATE_OVERHEAD_BYTES + positions * Uint32Array.BYTES_PER_ELEMENT;
30661
30774
  }
30662
- function widestPatternSplit(positions) {
30663
- return Math.max(1, Math.floor(MATCHER_STATE_BUDGET_BYTES / smallestMatcherBytes(positions)));
30775
+ function widestPatternSplit(footprintBytes) {
30776
+ return Math.max(1, Math.floor(MATCHER_STATE_BUDGET_BYTES / Math.max(1, footprintBytes)));
30664
30777
  }
30665
30778
 
30666
30779
  // src/core/pattern-index/query-positions.ts
30667
30780
  function isRecord6(value) {
30668
30781
  return value !== null && typeof value === "object" && !Array.isArray(value);
30669
30782
  }
30670
- function textPositions(text, caseFold) {
30671
- if (typeof text !== "string") return 0;
30672
- return caseFold ? foldedCodePointCount(text) : codePointCount(text);
30783
+ function literalFootprint(text, caseFold) {
30784
+ if (typeof text !== "string") return null;
30785
+ return smallestMatcherBytes(caseFold ? foldedCodePointCount(text) : codePointCount(text));
30673
30786
  }
30674
30787
  function larger(left, right) {
30675
30788
  if (left === null) return right;
30676
30789
  if (right === null) return left;
30677
30790
  return Math.max(left, right);
30678
30791
  }
30679
- function largestTestOf(filter) {
30792
+ function largestTestOf(filter, module) {
30680
30793
  const caseFold = filter.caseFold === true;
30681
30794
  let largest = null;
30682
30795
  for (const operator of PATTERN_TEST_OPERATORS) {
30683
30796
  const operand = filter[operator];
30684
30797
  if (operand === void 0) continue;
30685
30798
  const texts = Array.isArray(operand) ? operand : [operand];
30686
- for (const text of texts) largest = larger(largest, textPositions(text, caseFold));
30799
+ for (const text of texts) largest = larger(largest, literalFootprint(text, caseFold));
30800
+ }
30801
+ for (const operator of PATTERN_EXPRESSION_OPERATORS) {
30802
+ const pattern = filter[operator];
30803
+ if (typeof pattern !== "string") continue;
30804
+ largest = larger(largest, module.expressionFootprint(operator, pattern, caseFold, PATTERN_HOST));
30687
30805
  }
30688
30806
  return largest;
30689
30807
  }
30690
- function largestIn(expression, patternFields) {
30808
+ function largestIn(expression, patternFields, module) {
30691
30809
  let largest = null;
30692
30810
  if (isRecord6(expression.fields)) {
30693
30811
  for (const [fieldPath, filter] of Object.entries(expression.fields)) {
30694
- if (patternFields.has(fieldPath) && isRecord6(filter)) largest = larger(largest, largestTestOf(filter));
30812
+ if (patternFields.has(fieldPath) && isRecord6(filter)) largest = larger(largest, largestTestOf(filter, module));
30695
30813
  }
30696
30814
  }
30697
30815
  const nested = [...expression.and ?? [], ...expression.or ?? []];
30698
30816
  if (expression.not !== void 0) nested.push(expression.not);
30699
- for (const child of nested) largest = larger(largest, largestIn(child, patternFields));
30817
+ for (const child of nested) largest = larger(largest, largestIn(child, patternFields, module));
30700
30818
  return largest;
30701
30819
  }
30702
- function largestPatternTest(expression, schema) {
30820
+ function largestPatternFootprint(expression, schema) {
30703
30821
  if (expression === void 0) return null;
30822
+ const module = availablePatternSearch();
30823
+ if (module === null) return null;
30704
30824
  const patternFields = /* @__PURE__ */ new Set();
30705
30825
  for (const { path, pattern } of schemaFieldsOf(schema)) if (pattern) patternFields.add(path);
30706
- return patternFields.size === 0 ? null : largestIn(expression, patternFields);
30826
+ return patternFields.size === 0 ? null : largestIn(expression, patternFields, module);
30707
30827
  }
30708
30828
 
30709
30829
  // src/engine/orchestration/search.ts
@@ -30770,7 +30890,7 @@ function isWorkCapError(err) {
30770
30890
  function patternSplitWidth(state, indexName, params) {
30771
30891
  const schema = state.indexRegistry.get(indexName)?.config.schema;
30772
30892
  if (schema === void 0) return Number.POSITIVE_INFINITY;
30773
- const largest = largestPatternTest(params.filters, schema);
30893
+ const largest = largestPatternFootprint(params.filters, schema);
30774
30894
  if (largest === null) return Number.POSITIVE_INFINITY;
30775
30895
  if (typeof SharedArrayBuffer !== "function") return 1;
30776
30896
  return widestPatternSplit(largest);
package/dist/index.d.ts CHANGED
@@ -4,11 +4,11 @@ import { P as PartialWordSearch } from './partial-words-DTIHO8G4.js';
4
4
  import { P as PatternSearch } from './pattern-Cledccbl.js';
5
5
  import { L as LanguageModule } from './language-DB8KG1z5.js';
6
6
  export { T as TokenizerConfig } from './language-DB8KG1z5.js';
7
- export { A as ArrayFilter, B as BatchResult, C as ClientErrorCode, a as ClientErrorCodes, n as ComparisonFilter, D as DocumentImportResult, o as DocumentProjection, E as ErrorCode, b as ErrorCodes, F as FacetConfig, p as FacetResult, q as FieldFilter, r as FilterExpression, G as GeoFilter, s as GeoPolygonFilter, t as GeoRadiusFilter, u as GroupConfig, v as GroupReducer, w as GroupResult, H as HighlightConfig, x as HighlightMatch, y as Hit, z as HybridConfig, I as IndexInfo, g as IndexStats, L as ListParams, h as ListResult, J as ListedDocument, M as MemoryStats, N as NarsilError, c as NarsilErrorCode, f as NarsilEventHandlers, m as NarsilEventMap, P as PartitionStatsResult, j as PreflightResult, K as PresenceFilter, O as ProcessMemoryReport, R as QueryCoverage, Q as QueryParams, e as QueryResult, T as ScoreComponents, U as SearchMode, S as ServerErrorCode, d as ServerErrorCodes, W as SortField, X as SortSpec, Y as StringFilter, k as SuggestParams, l as SuggestResult, Z as TermMatchPolicy, V as VectorMaintenanceResult, _ as VectorQueryConfig, $ as WorkerCopyReport, i as isNarsilError } from './memory-DJEwWoCi.js';
8
- import { N as NarsilConfig } from './config-DJPH3IQB.js';
9
- export { A as AnalysisConfig, D as DurabilityConfig, I as IndexContext, a as IndexLifecycleConfig, b as InsertContext, M as MainCopyQueries, c as NarsilPlugin, P as PartitionContext, R as RemoveContext, S as SearchContext, d as StaleAnalysis, U as UpdateContext, W as WorkerConfig, e as WorkerContext } from './config-DJPH3IQB.js';
10
- import { N as Narsil } from './engine-B8eRPO7m.js';
11
- export { B as ByteSource, D as DocumentWriteOperations, I as IndexLifecycleOperations } from './engine-B8eRPO7m.js';
7
+ export { A as ArrayFilter, B as BatchResult, C as ClientErrorCode, a as ClientErrorCodes, n as ComparisonFilter, D as DocumentImportResult, o as DocumentProjection, E as ErrorCode, b as ErrorCodes, F as FacetConfig, p as FacetResult, q as FieldFilter, r as FilterExpression, G as GeoFilter, s as GeoPolygonFilter, t as GeoRadiusFilter, u as GroupConfig, v as GroupReducer, w as GroupResult, H as HighlightConfig, x as HighlightMatch, y as Hit, z as HybridConfig, I as IndexInfo, g as IndexStats, L as ListParams, h as ListResult, J as ListedDocument, M as MemoryStats, N as NarsilError, c as NarsilErrorCode, f as NarsilEventHandlers, m as NarsilEventMap, P as PartitionStatsResult, j as PreflightResult, K as PresenceFilter, O as ProcessMemoryReport, R as QueryCoverage, Q as QueryParams, e as QueryResult, T as ScoreComponents, U as SearchMode, S as ServerErrorCode, d as ServerErrorCodes, W as SortField, X as SortSpec, Y as StringFilter, k as SuggestParams, l as SuggestResult, Z as TermMatchPolicy, V as VectorMaintenanceResult, _ as VectorQueryConfig, $ as WorkerCopyReport, i as isNarsilError } from './memory-DW3OJ0im.js';
8
+ import { N as NarsilConfig } from './config-DTe5EbWW.js';
9
+ export { A as AnalysisConfig, D as DurabilityConfig, I as IndexContext, a as IndexLifecycleConfig, b as InsertContext, M as MainCopyQueries, c as NarsilPlugin, P as PartitionContext, R as RemoveContext, S as SearchContext, d as StaleAnalysis, U as UpdateContext, W as WorkerConfig, e as WorkerContext } from './config-DTe5EbWW.js';
10
+ import { N as Narsil } from './engine-BjPBzWkA.js';
11
+ export { B as ByteSource, D as DocumentWriteOperations, I as IndexLifecycleOperations } from './engine-BjPBzWkA.js';
12
12
  export { E as EmbeddingAdapter, I as InvalidationAdapter, a as InvalidationEvent, b as PartitionStatistics, P as PersistenceAdapter } from './adapters-79OIa4xm.js';
13
13
  export { i as isSimdAvailable } from './simd-DW5AtJ6B.js';
14
14
 
package/dist/index.mjs CHANGED
@@ -1,22 +1,22 @@
1
- export { VERSION, createNarsil } from './chunk-CUZOX6CN.mjs';
2
- import './chunk-CTOSGRM7.mjs';
1
+ export { VERSION, createNarsil } from './chunk-HH6ALOUZ.mjs';
2
+ import './chunk-G7IP3UDZ.mjs';
3
3
  import './chunk-GCM3JFVA.mjs';
4
- import './chunk-LX4JERXI.mjs';
5
- export { getStopWords, getTokenizer, hasStopWords, hasTokenizer, registerPartialWordSearch, registerPatternSearch, registerStopWords, registerTokenizer } from './chunk-BVBIQQKG.mjs';
6
- import './chunk-VKX2BPXD.mjs';
4
+ import './chunk-BT62MUM5.mjs';
5
+ export { getStopWords, getTokenizer, hasStopWords, hasTokenizer, registerPartialWordSearch, registerStopWords, registerTokenizer } from './chunk-DFFQOA7S.mjs';
6
+ export { registerPatternSearch } from './chunk-D4EF7J3I.mjs';
7
7
  export { getLanguage, registerLanguage } from './chunk-4MQ5F4IL.mjs';
8
- import './chunk-3DDUNI6N.mjs';
8
+ import './chunk-V3B24XR7.mjs';
9
9
  import './chunk-TGP5M5R7.mjs';
10
- export { isSimdAvailable } from './chunk-PO53FV4Q.mjs';
10
+ export { isSimdAvailable } from './chunk-AMGDG4ZJ.mjs';
11
11
  import './chunk-ZJ4CSAB2.mjs';
12
12
  import './chunk-2E35UM3G.mjs';
13
- import './chunk-XW25X6AU.mjs';
14
- import './chunk-ZKRV5M2P.mjs';
15
- import './chunk-T5ZWEWVD.mjs';
16
- export { clearNormalizationCache, configureNormalizationCache, getNormalizationCacheSize, resetNormalizationCache } from './chunk-I23G3IZC.mjs';
17
- import './chunk-Z3DQPOJQ.mjs';
13
+ import './chunk-4344I4MR.mjs';
14
+ import './chunk-PJU6EF22.mjs';
15
+ export { clearNormalizationCache, configureNormalizationCache, getNormalizationCacheSize, resetNormalizationCache } from './chunk-NO3ABFLE.mjs';
16
+ import './chunk-356UOTK2.mjs';
18
17
  import './chunk-KEHK2O6P.mjs';
18
+ import './chunk-GES7U2PM.mjs';
19
19
  import './chunk-U2BKYEC7.mjs';
20
- import './chunk-LJYTW7GT.mjs';
20
+ import './chunk-IBR6634K.mjs';
21
21
  import './chunk-POWOTUKJ.mjs';
22
22
  export { ClientErrorCodes, ErrorCodes, NarsilError, ServerErrorCodes, isNarsilError } from './chunk-I64LXKO3.mjs';
@@ -1,4 +1,4 @@
1
- import { compareCodePoints } from '../chunk-LJYTW7GT.mjs';
1
+ import { compareCodePoints } from '../chunk-IBR6634K.mjs';
2
2
  import { DEFAULT_POLL_INTERVAL_MS, MARKER_MAX_AGE_MS } from '../chunk-YEO6G2NG.mjs';
3
3
 
4
4
  // src/invalidation/filesystem.ts
@@ -28,33 +28,43 @@ type ComparisonFilter = {
28
28
  };
29
29
  /**
30
30
  * The engine tests a text field against this filter for equality, for a text
31
- * range, or with the set and prefix tests.
31
+ * range, or with the set, prefix, and pattern tests.
32
32
  *
33
- * The engine compares each element of a `string[]`, `verbatim[]`, or `enum[]` field, so
34
- * it matches a document with `eq`, `in`, `startsWith`, `endsWith`, or
35
- * `contains` where one element passes the test, while it matches a document
36
- * with `ne` or `nin` where every element differs from the values.
33
+ * On a `string[]`, `verbatim[]`, or `enum[]` field, the engine tests each
34
+ * element of the list on its own. A document passes `eq`, `in`, `startsWith`,
35
+ * `endsWith`, `contains`, `wildcard`, or `regex` when one element passes the
36
+ * test. However, a document passes `ne` or `nin` only when every element
37
+ * differs from the values.
37
38
  *
38
39
  * The engine applies the range tests `gt`, `gte`, `lt`, `lte`, and `between`
39
- * to a single `string` or `verbatim` field whose type includes `sortable`,
40
- * while it throws `SEARCH_INVALID_FILTER` for a text range on any other field.
41
- * It orders the values as a sort on the field does, comparing their case folds
42
- * first and their raw code points only where the folds are equal.
40
+ * to a single `string` or `verbatim` field whose type includes `sortable`. It
41
+ * throws `SEARCH_INVALID_FILTER` for a text range on any other field. It
42
+ * orders the values as a sort on the field does, so it compares their case
43
+ * folds first and compares their raw code points only where the folds are
44
+ * equal.
43
45
  *
44
46
  * A pattern field is a `verbatim` or `verbatim[]` field, or a `string` or
45
- * `string[]` field whose type includes `pattern`. On such a field, the engine
46
- * answers `eq`, `ne`, `in`, `nin`, `startsWith`, `endsWith`, and `contains`
47
- * from the field's pattern index. `caseFold` applies to each of those tests.
47
+ * `string[]` field whose type includes `pattern`. On a pattern field, the
48
+ * engine answers `eq`, `ne`, `in`, `nin`, `startsWith`, `endsWith`,
49
+ * `contains`, `wildcard`, and `regex` from the field's pattern index, and
50
+ * `caseFold` applies to each of those tests. The engine compares the code
51
+ * points of each value as they are stored, with no Unicode normalisation.
52
+ *
48
53
  * On an `enum` or `enum[]` field, the engine answers `eq`, `ne`, `in`, `nin`,
49
- * `startsWith`, and `endsWith` from the field's index of values. It throws
50
- * `SEARCH_INVALID_FILTER` for `eq`, `ne`, `in`, `nin`, `startsWith`, or
51
- * `endsWith` on a `string` or `string[]` field whose type leaves out
52
- * `pattern`, because such a field keeps no index of whole values.
53
- * The engine compares the code points of each value as stored, with no
54
- * Unicode normalisation. It throws `SEARCH_INVALID_FILTER` for `contains` or
55
- * `caseFold` on any other field, for a test text longer than 1,024 code points
56
- * on a pattern field, and for a `caseFold` test whose text has a case fold
57
- * longer than 2,048 code points.
54
+ * `startsWith`, and `endsWith` from the field's index of values. A `string` or
55
+ * `string[]` field whose type leaves out `pattern` keeps no index of whole
56
+ * values, so the engine throws `SEARCH_INVALID_FILTER` for those six tests,
57
+ * and for `matchesAny` and `containsAll`, on such a field.
58
+ *
59
+ * The engine also throws `SEARCH_INVALID_FILTER` in these cases:
60
+ *
61
+ * - The filter sets `contains`, `wildcard`, `regex`, or `caseFold` on a field
62
+ * that is not a pattern field.
63
+ * - The text or the pattern of a test on a pattern field holds more than 1,024
64
+ * code points.
65
+ * - A literal test under `caseFold` has a text whose case fold holds more than
66
+ * 2,048 code points.
67
+ * - A `wildcard` or `regex` pattern breaks its syntax or one of its limits.
58
68
  *
59
69
  * @public
60
70
  */
@@ -79,13 +89,84 @@ type StringFilter = Pick<ComparisonFilter, 'eq' | 'ne'> & {
79
89
  endsWith?: string;
80
90
  /** This matches a pattern field whose value holds the text anywhere in it. */
81
91
  contains?: string;
92
+ /**
93
+ * This matches a pattern field when the whole value matches the wildcard
94
+ * pattern. For example, `INV-*-2024` matches `INV-0042-2024`.
95
+ *
96
+ * In the pattern, `*` matches any sequence of code points, including an
97
+ * empty one, and `?` matches exactly one code point. Write `\` before a code
98
+ * point to match that code point itself, so `\*` matches an asterisk.
99
+ *
100
+ * The engine throws `SEARCH_INVALID_FILTER` for a pattern that ends in a
101
+ * `\` with no code point after it. It throws the same error for a pattern
102
+ * with more than 2,048 positions. Each `*`, each `?`, and each other code
103
+ * point counts as one position, except that under `caseFold` a code point
104
+ * counts once for each code point of its case fold.
105
+ */
106
+ wildcard?: string;
107
+ /**
108
+ * This matches a pattern field when some part of the value matches the
109
+ * regular expression. `^` and `$` match only at the start and at the end of
110
+ * the whole value.
111
+ *
112
+ * The engine accepts literal code points, `.`, `\d`, `\w`, and `\s`, sets
113
+ * such as `[a-z]` and `[^0-9]`, groups, and `|`. It also accepts the repeats
114
+ * `*`, `+`, `?`, `{n}`, `{n,}`, and `{n,m}`, the anchors `^` and `$`, the
115
+ * word edge `\b`, and number ranges such as `<1-65535>`. A number range
116
+ * matches the digits of any whole number from its low end to its high end.
117
+ * Write `\` before an ASCII punctuation mark to match the mark itself. The
118
+ * `.` matches any code point, including a line break.
119
+ *
120
+ * The matcher never backtracks, so it checks a value in at most as many
121
+ * steps as the value's code points multiplied by the pattern's positions.
122
+ *
123
+ * The engine throws `SEARCH_INVALID_FILTER` for any construct outside that
124
+ * syntax, such as a back-reference, a lookaround, a lazy repeat, or an inline
125
+ * flag. It throws the same error when the groups of a pattern nest more than
126
+ * 32 deep, when a count is above 2,048, and when a pattern holds more than
127
+ * 2,048 positions. When the operator of an HTTP server sets its `allowRegex`
128
+ * option to false, the server refuses every `regex` test.
129
+ */
130
+ regex?: string;
82
131
  /**
83
132
  * When this is true, the engine compares the case fold of each value of a
84
- * pattern field with the case fold of the text, so a test for `error` matches
85
- * `ERROR`. It applies to `eq`, `ne`, `in`, `nin`, `startsWith`, `endsWith`,
86
- * and `contains`.
133
+ * pattern field with the case fold of the text, so a test for `error`
134
+ * matches `ERROR`. It applies to `eq`, `ne`, `in`, `nin`, `startsWith`,
135
+ * `endsWith`, `contains`, `wildcard`, and `regex`.
136
+ *
137
+ * Under `caseFold`, a set that does not open with `^` can also match a
138
+ * sequence of code points. When a member that the set holds on its own folds
139
+ * to several code points, the set matches that whole fold, so `[ß]` matches
140
+ * `ss`.
87
141
  */
88
142
  caseFold?: boolean;
143
+ /**
144
+ * This lets the `eq` test of a pattern field match a value within this many
145
+ * edits of the text, so a test for `INV-2O24` with a tolerance of 1 matches
146
+ * `INV-2024`. An edit inserts, deletes, or replaces one code point, so a
147
+ * swap of two neighbouring code points counts as two edits. With `caseFold`,
148
+ * the engine counts the edits between the case folds of the value and the
149
+ * text. The tolerance applies to `eq` alone, so the other tests of the field
150
+ * still compare exactly.
151
+ *
152
+ * The engine takes its candidates from the pattern index, so the case fold
153
+ * of the text has to hold enough distinct runs of three code points. For a
154
+ * tolerance of `k`, the engine needs `k × (2 + f) + 1` runs, where `f` is
155
+ * the number of code points in the longest case fold among the text's code
156
+ * points, which is 1 for most text. A tolerance of 1 on `INV-2O24`
157
+ * therefore needs 4 of its 6 runs, while a tolerance of 2 needs 7. Under
158
+ * `'auto'`, the engine uses 2 where the text holds enough runs for 2,
159
+ * otherwise 1 where it holds enough for 1, and otherwise 0.
160
+ *
161
+ * The engine throws `SEARCH_INVALID_FILTER` for a tolerance of 1 or 2 on a
162
+ * text with too few runs, stating how many runs that tolerance needs. It
163
+ * throws the same error for any other tolerance, for a tolerance on a field
164
+ * that is not a pattern field, and for a tolerance in a filter without an
165
+ * `eq` test. Towards the pattern work cap, it counts one unit for each cell
166
+ * that it fills in the table of edit distances between the text and a
167
+ * value.
168
+ */
169
+ tolerance?: 0 | 1 | 2 | 'auto';
89
170
  };
90
171
  /**
91
172
  * Tests the contents of a list field, which is a `number[]`, `boolean[]`,
@@ -1,8 +1,9 @@
1
- import { partialWordSearchModule } from './chunk-T5ZWEWVD.mjs';
2
- import './chunk-I23G3IZC.mjs';
3
- import './chunk-Z3DQPOJQ.mjs';
1
+ import { partialWordSearchModule } from './chunk-PJU6EF22.mjs';
2
+ import './chunk-NO3ABFLE.mjs';
3
+ import './chunk-356UOTK2.mjs';
4
4
  import './chunk-KEHK2O6P.mjs';
5
- import './chunk-LJYTW7GT.mjs';
5
+ import './chunk-GES7U2PM.mjs';
6
+ import './chunk-IBR6634K.mjs';
6
7
  import './chunk-I64LXKO3.mjs';
7
8
 
8
9
  // src/partial-words/index.ts
package/dist/pattern.mjs CHANGED
@@ -1,5 +1,6 @@
1
- import { patternSearchModule } from './chunk-XW25X6AU.mjs';
2
- import './chunk-ZKRV5M2P.mjs';
1
+ import { patternSearchModule } from './chunk-4344I4MR.mjs';
2
+ import './chunk-GES7U2PM.mjs';
3
+ import './chunk-I64LXKO3.mjs';
3
4
 
4
5
  // src/pattern/index.ts
5
6
  var patternSearch = patternSearchModule;