@vielzeug/scout 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +11 -4
  2. package/dist/_index-state.cjs +2 -0
  3. package/dist/_index-state.cjs.map +1 -0
  4. package/dist/_index-state.d.ts +4 -0
  5. package/dist/_index-state.d.ts.map +1 -0
  6. package/dist/_index-state.js +12 -0
  7. package/dist/_index-state.js.map +1 -0
  8. package/dist/adapters.cjs +1 -1
  9. package/dist/adapters.cjs.map +1 -1
  10. package/dist/adapters.d.ts.map +1 -1
  11. package/dist/adapters.js +9 -5
  12. package/dist/adapters.js.map +1 -1
  13. package/dist/errors.cjs +1 -1
  14. package/dist/errors.cjs.map +1 -1
  15. package/dist/errors.d.ts +2 -2
  16. package/dist/errors.d.ts.map +1 -1
  17. package/dist/errors.js +1 -1
  18. package/dist/errors.js.map +1 -1
  19. package/dist/highlight.cjs +1 -1
  20. package/dist/highlight.cjs.map +1 -1
  21. package/dist/highlight.d.ts +1 -1
  22. package/dist/highlight.d.ts.map +1 -1
  23. package/dist/highlight.js +18 -17
  24. package/dist/highlight.js.map +1 -1
  25. package/dist/index.cjs +1 -1
  26. package/dist/index.d.ts +1 -1
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +2 -2
  29. package/dist/reactive.cjs +1 -1
  30. package/dist/reactive.cjs.map +1 -1
  31. package/dist/reactive.d.ts +2 -2
  32. package/dist/reactive.d.ts.map +1 -1
  33. package/dist/reactive.js +47 -45
  34. package/dist/reactive.js.map +1 -1
  35. package/dist/scout-index.cjs +1 -1
  36. package/dist/scout-index.cjs.map +1 -1
  37. package/dist/scout-index.d.ts +11 -4
  38. package/dist/scout-index.d.ts.map +1 -1
  39. package/dist/scout-index.js +104 -75
  40. package/dist/scout-index.js.map +1 -1
  41. package/dist/scout.cjs +1 -1
  42. package/dist/scout.cjs.map +1 -1
  43. package/dist/scout.iife.js +1 -1
  44. package/dist/scout.iife.js.map +1 -1
  45. package/dist/scout.js +1 -1
  46. package/dist/scout.js.map +1 -1
  47. package/dist/types.d.ts +10 -8
  48. package/dist/types.d.ts.map +1 -1
  49. package/package.json +9 -4
package/README.md CHANGED
@@ -4,12 +4,12 @@ Fast fuzzy-search. Builds a trigram inverted index at construction — O(candida
4
4
 
5
5
  ## Features
6
6
 
7
- - **Trigram index** — fast candidate lookup; Dice coefficient scoring
7
+ - **Trigram index** — fast candidate lookup; overlap-coefficient scoring
8
8
  - **Multi-field weighted ranking** — per-field weights, custom stringifiers
9
9
  - **Match highlighting** — character-range offsets for UI rendering
10
10
  - **Reactive layer** — `createSearch()` wraps any index in `ripple` signals with debounce
11
11
  - **Framework adapters** — `toSearchMatcher()` for sourcerer, `toFilterPredicate()` for filter pipelines
12
- - **Incremental updates** — `add()`, `remove()`, `reindex()` patch the index in O(field_length)
12
+ - **Corpus reconciliation** — `setItems()` reconciles reference-based additions, removals, reindexes, and order in one notification
13
13
  - **Unsegmented-script helper** — `segmentWords()` pre-splits CJK/Thai text into words via `Intl.Segmenter`
14
14
  - **Devtools** — `@vielzeug/scout/devtools`'s `debugSearch()` logs query/results transitions
15
15
 
@@ -24,6 +24,11 @@ pnpm add @vielzeug/scout
24
24
  ```ts
25
25
  import { createIndex } from '@vielzeug/scout';
26
26
 
27
+ const users = [
28
+ { email: 'ada@example.com', name: 'Ada Lovelace' },
29
+ { email: 'grace@example.com', name: 'Grace Hopper' },
30
+ ];
31
+
27
32
  const index = createIndex(users, {
28
33
  fields: [
29
34
  { field: 'name', weight: 2 },
@@ -31,8 +36,7 @@ const index = createIndex(users, {
31
36
  ],
32
37
  });
33
38
 
34
- const results = index.search('alice');
35
- // [{ item: { name: 'Alice', email: '...' }, score: 0.85, matches: [...] }]
39
+ console.log(index.search('ada')[0]?.item.name); // Ada Lovelace
36
40
  ```
37
41
 
38
42
  ## Reactive search
@@ -41,6 +45,7 @@ const results = index.search('alice');
41
45
  import { createIndex, createSearch } from '@vielzeug/scout';
42
46
  import { effect } from '@vielzeug/ripple';
43
47
 
48
+ const users = [{ name: 'Ada Lovelace' }, { name: 'Grace Hopper' }];
44
49
  const index = createIndex(users, { fields: ['name'] });
45
50
  const search = createSearch(index, { debounce: 150 });
46
51
 
@@ -55,7 +60,9 @@ search.query.value = 'alice';
55
60
 
56
61
  ```ts
57
62
  import { createIndex, toSearchMatcher } from '@vielzeug/scout';
63
+ import { createLocalSource } from '@vielzeug/sourcerer';
58
64
 
65
+ const users = [{ email: 'ada@example.com', name: 'Ada Lovelace' }];
59
66
  const index = createIndex(users, { fields: ['name', 'email'] });
60
67
  const source = createLocalSource(users, { match: toSearchMatcher(index) });
61
68
  ```
@@ -0,0 +1,2 @@
1
+ var e=new WeakMap;function t(t){return e.get(t)?.()??0}function n(t,n){e.set(t,n)}exports.getIndexRevision=t,exports.registerIndexRevision=n;
2
+ //# sourceMappingURL=_index-state.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_index-state.cjs","names":[],"sources":["../src/_index-state.ts"],"sourcesContent":["import type { ScoutIndex } from './scout-index';\n\nconst revisions = new WeakMap<object, () => number>();\n\nexport function getIndexRevision<T>(index: ScoutIndex<T>): number {\n return revisions.get(index)?.() ?? 0;\n}\n\nexport function registerIndexRevision<T>(index: ScoutIndex<T>, getRevision: () => number): void {\n revisions.set(index, getRevision);\n}\n"],"mappings":"AAEA,IAAM,EAAY,IAAI,QAEtB,SAAgB,EAAoB,EAA8B,CAChE,OAAO,EAAU,IAAI,CAAK,CAAC,GAAG,GAAK,CACrC,CAEA,SAAgB,EAAyB,EAAsB,EAAiC,CAC9F,EAAU,IAAI,EAAO,CAAW,CAClC"}
@@ -0,0 +1,4 @@
1
+ import type { ScoutIndex } from './scout-index';
2
+ export declare function getIndexRevision<T>(index: ScoutIndex<T>): number;
3
+ export declare function registerIndexRevision<T>(index: ScoutIndex<T>, getRevision: () => number): void;
4
+ //# sourceMappingURL=_index-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_index-state.d.ts","sourceRoot":"","sources":["../src/_index-state.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAIhD,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,GAAG,MAAM,CAEhE;AAED,wBAAgB,qBAAqB,CAAC,CAAC,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,MAAM,MAAM,GAAG,IAAI,CAE9F"}
@@ -0,0 +1,12 @@
1
+ //#region src/_index-state.ts
2
+ var e = /* @__PURE__ */ new WeakMap();
3
+ function t(t) {
4
+ return e.get(t)?.() ?? 0;
5
+ }
6
+ function n(t, n) {
7
+ e.set(t, n);
8
+ }
9
+ //#endregion
10
+ export { t as getIndexRevision, n as registerIndexRevision };
11
+
12
+ //# sourceMappingURL=_index-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_index-state.js","names":[],"sources":["../src/_index-state.ts"],"sourcesContent":["import type { ScoutIndex } from './scout-index';\n\nconst revisions = new WeakMap<object, () => number>();\n\nexport function getIndexRevision<T>(index: ScoutIndex<T>): number {\n return revisions.get(index)?.() ?? 0;\n}\n\nexport function registerIndexRevision<T>(index: ScoutIndex<T>, getRevision: () => number): void {\n revisions.set(index, getRevision);\n}\n"],"mappings":";AAEA,IAAM,oBAAY,IAAI,QAA8B;AAEpD,SAAgB,EAAoB,GAA8B;CAChE,OAAO,EAAU,IAAI,CAAK,CAAC,GAAG,KAAK;AACrC;AAEA,SAAgB,EAAyB,GAAsB,GAAiC;CAC9F,EAAU,IAAI,GAAO,CAAW;AAClC"}
package/dist/adapters.cjs CHANGED
@@ -1,2 +1,2 @@
1
- function e(e,t){let n,r=new Set;return(i,a)=>(a!==n&&(n=a,r=new Set(e.search(a,t).map(e=>e.item))),r.has(i))}function t(e,t,n){let r=new Set(e.search(t,n).map(e=>e.item));return e=>r.has(e)}exports.toFilterPredicate=t,exports.toSearchMatcher=e;
1
+ const e=require("./_index-state.cjs");function t(t,n){let r,i=-1,a=new Set;return(o,s)=>{let c=e.getIndexRevision(t);return(s!==r||c!==i)&&(r=s,i=c,a=new Set(t.search(s,n).map(e=>e.item))),a.has(o)}}function n(e,t,n){let r=new Set(e.search(t,n).map(e=>e.item));return e=>r.has(e)}exports.toFilterPredicate=n,exports.toSearchMatcher=t;
2
2
  //# sourceMappingURL=adapters.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"adapters.cjs","names":[],"sources":["../src/adapters.ts"],"sourcesContent":["import type { ScoutIndex } from './scout-index';\nimport type { SearchConstraints } from './types';\n\n/**\n * Adapts a `ScoutIndex` to sourcerer's explicit local `match` callback.\n * Caches one match set per query so local filtering does not repeat index work per item.\n */\nexport function toSearchMatcher<T>(\n index: ScoutIndex<T>,\n options?: SearchConstraints,\n): (item: T, query: string) => boolean {\n let lastQuery: string | undefined;\n let matches = new Set<T>();\n\n return (item, query) => {\n if (query !== lastQuery) {\n lastQuery = query;\n matches = new Set(index.search(query, options).map((result) => result.item));\n }\n\n return matches.has(item);\n };\n}\n\n/**\n * Returns a predicate that returns `true` for items matching `query` in the given index.\n *\n * The predicate is computed once at call time — call `toFilterPredicate` again if the\n * query or corpus changes.\n *\n * Compatible with `Array.filter`, `vault`'s `query.filter()`, or any predicate pipeline.\n */\nexport function toFilterPredicate<T>(\n index: ScoutIndex<T>,\n query: string,\n options?: SearchConstraints,\n): (item: T) => boolean {\n const matchSet = new Set(index.search(query, options).map((result) => result.item));\n\n return (item) => matchSet.has(item);\n}\n"],"mappings":"AAOA,SAAgB,EACd,EACA,EACqC,CACrC,IAAI,EACA,EAAU,IAAI,IAElB,OAAQ,EAAM,KACR,IAAU,IACZ,EAAY,EACZ,EAAU,IAAI,IAAI,EAAM,OAAO,EAAO,CAAO,CAAC,CAAC,IAAK,GAAW,EAAO,IAAI,CAAC,GAGtE,EAAQ,IAAI,CAAI,EAE3B,CAUA,SAAgB,EACd,EACA,EACA,EACsB,CACtB,IAAM,EAAW,IAAI,IAAI,EAAM,OAAO,EAAO,CAAO,CAAC,CAAC,IAAK,GAAW,EAAO,IAAI,CAAC,EAElF,MAAQ,IAAS,EAAS,IAAI,CAAI,CACpC"}
1
+ {"version":3,"file":"adapters.cjs","names":[],"sources":["../src/adapters.ts"],"sourcesContent":["import type { ScoutIndex } from './scout-index';\nimport type { SearchConstraints } from './types';\n\nimport { getIndexRevision } from './_index-state';\n\n/**\n * Adapts a `ScoutIndex` to sourcerer's explicit local `match` callback.\n * Caches one match set per query so local filtering does not repeat index work per item.\n */\nexport function toSearchMatcher<T>(\n index: ScoutIndex<T>,\n options?: SearchConstraints,\n): (item: T, query: string) => boolean {\n let lastQuery: string | undefined;\n let lastRevision = -1;\n let matches = new Set<T>();\n\n return (item, query) => {\n const revision = getIndexRevision(index);\n\n if (query !== lastQuery || revision !== lastRevision) {\n lastQuery = query;\n lastRevision = revision;\n matches = new Set(index.search(query, options).map((result) => result.item));\n }\n\n return matches.has(item);\n };\n}\n\n/**\n * Returns a predicate that returns `true` for items matching `query` in the given index.\n *\n * The predicate is computed once at call time — call `toFilterPredicate` again if the\n * query or corpus changes.\n *\n * Compatible with `Array.filter`, `vault`'s `query.filter()`, or any predicate pipeline.\n */\nexport function toFilterPredicate<T>(\n index: ScoutIndex<T>,\n query: string,\n options?: SearchConstraints,\n): (item: T) => boolean {\n const matchSet = new Set(index.search(query, options).map((result) => result.item));\n\n return (item) => matchSet.has(item);\n}\n"],"mappings":"sCASA,SAAgB,EACd,EACA,EACqC,CACrC,IAAI,EACA,EAAe,GACf,EAAU,IAAI,IAElB,OAAQ,EAAM,IAAU,CACtB,IAAM,EAAW,EAAA,iBAAiB,CAAK,EAQvC,OANI,IAAU,GAAa,IAAa,KACtC,EAAY,EACZ,EAAe,EACf,EAAU,IAAI,IAAI,EAAM,OAAO,EAAO,CAAO,CAAC,CAAC,IAAK,GAAW,EAAO,IAAI,CAAC,GAGtE,EAAQ,IAAI,CAAI,CACzB,CACF,CAUA,SAAgB,EACd,EACA,EACA,EACsB,CACtB,IAAM,EAAW,IAAI,IAAI,EAAM,OAAO,EAAO,CAAO,CAAC,CAAC,IAAK,GAAW,EAAO,IAAI,CAAC,EAElF,MAAQ,IAAS,EAAS,IAAI,CAAI,CACpC"}
@@ -1 +1 @@
1
- {"version":3,"file":"adapters.d.ts","sourceRoot":"","sources":["../src/adapters.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAEjD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAC/B,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,EACpB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAYrC;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EACjC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,EACpB,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,iBAAiB,GAC1B,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,CAItB"}
1
+ {"version":3,"file":"adapters.d.ts","sourceRoot":"","sources":["../src/adapters.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAIjD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAC/B,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,EACpB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,OAAO,CAgBrC;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EACjC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,EACpB,KAAK,EAAE,MAAM,EACb,OAAO,CAAC,EAAE,iBAAiB,GAC1B,CAAC,IAAI,EAAE,CAAC,KAAK,OAAO,CAItB"}
package/dist/adapters.js CHANGED
@@ -1,13 +1,17 @@
1
+ import { getIndexRevision as e } from "./_index-state.js";
1
2
  //#region src/adapters.ts
2
- function e(e, t) {
3
- let n, r = /* @__PURE__ */ new Set();
4
- return (i, a) => (a !== n && (n = a, r = new Set(e.search(a, t).map((e) => e.item))), r.has(i));
3
+ function t(t, n) {
4
+ let r, i = -1, a = /* @__PURE__ */ new Set();
5
+ return (o, s) => {
6
+ let c = e(t);
7
+ return (s !== r || c !== i) && (r = s, i = c, a = new Set(t.search(s, n).map((e) => e.item))), a.has(o);
8
+ };
5
9
  }
6
- function t(e, t, n) {
10
+ function n(e, t, n) {
7
11
  let r = new Set(e.search(t, n).map((e) => e.item));
8
12
  return (e) => r.has(e);
9
13
  }
10
14
  //#endregion
11
- export { t as toFilterPredicate, e as toSearchMatcher };
15
+ export { n as toFilterPredicate, t as toSearchMatcher };
12
16
 
13
17
  //# sourceMappingURL=adapters.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"adapters.js","names":[],"sources":["../src/adapters.ts"],"sourcesContent":["import type { ScoutIndex } from './scout-index';\nimport type { SearchConstraints } from './types';\n\n/**\n * Adapts a `ScoutIndex` to sourcerer's explicit local `match` callback.\n * Caches one match set per query so local filtering does not repeat index work per item.\n */\nexport function toSearchMatcher<T>(\n index: ScoutIndex<T>,\n options?: SearchConstraints,\n): (item: T, query: string) => boolean {\n let lastQuery: string | undefined;\n let matches = new Set<T>();\n\n return (item, query) => {\n if (query !== lastQuery) {\n lastQuery = query;\n matches = new Set(index.search(query, options).map((result) => result.item));\n }\n\n return matches.has(item);\n };\n}\n\n/**\n * Returns a predicate that returns `true` for items matching `query` in the given index.\n *\n * The predicate is computed once at call time — call `toFilterPredicate` again if the\n * query or corpus changes.\n *\n * Compatible with `Array.filter`, `vault`'s `query.filter()`, or any predicate pipeline.\n */\nexport function toFilterPredicate<T>(\n index: ScoutIndex<T>,\n query: string,\n options?: SearchConstraints,\n): (item: T) => boolean {\n const matchSet = new Set(index.search(query, options).map((result) => result.item));\n\n return (item) => matchSet.has(item);\n}\n"],"mappings":";AAOA,SAAgB,EACd,GACA,GACqC;CACrC,IAAI,GACA,oBAAU,IAAI,IAAO;CAEzB,QAAQ,GAAM,OACR,MAAU,MACZ,IAAY,GACZ,IAAU,IAAI,IAAI,EAAM,OAAO,GAAO,CAAO,CAAC,CAAC,KAAK,MAAW,EAAO,IAAI,CAAC,IAGtE,EAAQ,IAAI,CAAI;AAE3B;AAUA,SAAgB,EACd,GACA,GACA,GACsB;CACtB,IAAM,IAAW,IAAI,IAAI,EAAM,OAAO,GAAO,CAAO,CAAC,CAAC,KAAK,MAAW,EAAO,IAAI,CAAC;CAElF,QAAQ,MAAS,EAAS,IAAI,CAAI;AACpC"}
1
+ {"version":3,"file":"adapters.js","names":[],"sources":["../src/adapters.ts"],"sourcesContent":["import type { ScoutIndex } from './scout-index';\nimport type { SearchConstraints } from './types';\n\nimport { getIndexRevision } from './_index-state';\n\n/**\n * Adapts a `ScoutIndex` to sourcerer's explicit local `match` callback.\n * Caches one match set per query so local filtering does not repeat index work per item.\n */\nexport function toSearchMatcher<T>(\n index: ScoutIndex<T>,\n options?: SearchConstraints,\n): (item: T, query: string) => boolean {\n let lastQuery: string | undefined;\n let lastRevision = -1;\n let matches = new Set<T>();\n\n return (item, query) => {\n const revision = getIndexRevision(index);\n\n if (query !== lastQuery || revision !== lastRevision) {\n lastQuery = query;\n lastRevision = revision;\n matches = new Set(index.search(query, options).map((result) => result.item));\n }\n\n return matches.has(item);\n };\n}\n\n/**\n * Returns a predicate that returns `true` for items matching `query` in the given index.\n *\n * The predicate is computed once at call time — call `toFilterPredicate` again if the\n * query or corpus changes.\n *\n * Compatible with `Array.filter`, `vault`'s `query.filter()`, or any predicate pipeline.\n */\nexport function toFilterPredicate<T>(\n index: ScoutIndex<T>,\n query: string,\n options?: SearchConstraints,\n): (item: T) => boolean {\n const matchSet = new Set(index.search(query, options).map((result) => result.item));\n\n return (item) => matchSet.has(item);\n}\n"],"mappings":";;AASA,SAAgB,EACd,GACA,GACqC;CACrC,IAAI,GACA,IAAe,IACf,oBAAU,IAAI,IAAO;CAEzB,QAAQ,GAAM,MAAU;EACtB,IAAM,IAAW,EAAiB,CAAK;EAQvC,QANI,MAAU,KAAa,MAAa,OACtC,IAAY,GACZ,IAAe,GACf,IAAU,IAAI,IAAI,EAAM,OAAO,GAAO,CAAO,CAAC,CAAC,KAAK,MAAW,EAAO,IAAI,CAAC,IAGtE,EAAQ,IAAI,CAAI;CACzB;AACF;AAUA,SAAgB,EACd,GACA,GACA,GACsB;CACtB,IAAM,IAAW,IAAI,IAAI,EAAM,OAAO,GAAO,CAAO,CAAC,CAAC,KAAK,MAAW,EAAO,IAAI,CAAC;CAElF,QAAQ,MAAS,EAAS,IAAI,CAAI;AACpC"}
package/dist/errors.cjs CHANGED
@@ -1,2 +1,2 @@
1
- var e=class e extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}static is(t){return t instanceof e}},t=class extends e{},n=class extends e{};exports.ScoutDisposedError=t,exports.ScoutError=e,exports.ScoutIndexError=n;
1
+ var e=class e extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}static is(t){return t instanceof e}},t=class extends e{},n=class extends e{};exports.ScoutConfigurationError=n,exports.ScoutDisposedError=t,exports.ScoutError=e;
2
2
  //# sourceMappingURL=errors.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.cjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/** Base class for all scout errors. Use `instanceof ScoutError` to catch any scout-originated error. */\nexport class ScoutError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n static is(err: unknown): err is ScoutError {\n return err instanceof ScoutError;\n }\n}\n\n/** Thrown when a method is called on a disposed search state instance. */\nexport class ScoutDisposedError extends ScoutError {}\n\n/** Thrown when an index is built or queried with an invalid configuration (e.g. zero fields defined). */\nexport class ScoutIndexError extends ScoutError {}\n"],"mappings":"AACA,IAAa,EAAb,MAAa,UAAmB,KAAM,CACpC,YAAY,EAAiB,EAAqB,CAChD,MAAM,EAAS,CAAI,EACnB,KAAK,KAAO,WAAW,KACvB,OAAO,eAAe,KAAM,WAAW,SAAS,CAClD,CAEA,OAAO,GAAG,EAAiC,CACzC,OAAO,aAAe,CACxB,CACF,EAGa,EAAb,cAAwC,CAAW,CAAC,EAGvC,EAAb,cAAqC,CAAW,CAAC"}
1
+ {"version":3,"file":"errors.cjs","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/** Base class for all scout errors. Use `instanceof ScoutError` to catch any scout-originated error. */\nexport class ScoutError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n static is(err: unknown): err is ScoutError {\n return err instanceof ScoutError;\n }\n}\n\n/** Thrown when a method is called on a disposed search state instance. */\nexport class ScoutDisposedError extends ScoutError {}\n\n/** Thrown when an index, search, or reactive search receives an invalid configuration. */\nexport class ScoutConfigurationError extends ScoutError {}\n"],"mappings":"AACA,IAAa,EAAb,MAAa,UAAmB,KAAM,CACpC,YAAY,EAAiB,EAAqB,CAChD,MAAM,EAAS,CAAI,EACnB,KAAK,KAAO,WAAW,KACvB,OAAO,eAAe,KAAM,WAAW,SAAS,CAClD,CAEA,OAAO,GAAG,EAAiC,CACzC,OAAO,aAAe,CACxB,CACF,EAGa,EAAb,cAAwC,CAAW,CAAC,EAGvC,EAAb,cAA6C,CAAW,CAAC"}
package/dist/errors.d.ts CHANGED
@@ -6,7 +6,7 @@ export declare class ScoutError extends Error {
6
6
  /** Thrown when a method is called on a disposed search state instance. */
7
7
  export declare class ScoutDisposedError extends ScoutError {
8
8
  }
9
- /** Thrown when an index is built or queried with an invalid configuration (e.g. zero fields defined). */
10
- export declare class ScoutIndexError extends ScoutError {
9
+ /** Thrown when an index, search, or reactive search receives an invalid configuration. */
10
+ export declare class ScoutConfigurationError extends ScoutError {
11
11
  }
12
12
  //# sourceMappingURL=errors.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,wGAAwG;AACxG,qBAAa,UAAW,SAAQ,KAAK;gBACvB,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,YAAY;IAMhD,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,UAAU;CAG3C;AAED,0EAA0E;AAC1E,qBAAa,kBAAmB,SAAQ,UAAU;CAAG;AAErD,yGAAyG;AACzG,qBAAa,eAAgB,SAAQ,UAAU;CAAG"}
1
+ {"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,wGAAwG;AACxG,qBAAa,UAAW,SAAQ,KAAK;gBACvB,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,YAAY;IAMhD,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,UAAU;CAG3C;AAED,0EAA0E;AAC1E,qBAAa,kBAAmB,SAAQ,UAAU;CAAG;AAErD,0FAA0F;AAC1F,qBAAa,uBAAwB,SAAQ,UAAU;CAAG"}
package/dist/errors.js CHANGED
@@ -8,6 +8,6 @@ var e = class e extends Error {
8
8
  }
9
9
  }, t = class extends e {}, n = class extends e {};
10
10
  //#endregion
11
- export { t as ScoutDisposedError, e as ScoutError, n as ScoutIndexError };
11
+ export { n as ScoutConfigurationError, t as ScoutDisposedError, e as ScoutError };
12
12
 
13
13
  //# sourceMappingURL=errors.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/** Base class for all scout errors. Use `instanceof ScoutError` to catch any scout-originated error. */\nexport class ScoutError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n static is(err: unknown): err is ScoutError {\n return err instanceof ScoutError;\n }\n}\n\n/** Thrown when a method is called on a disposed search state instance. */\nexport class ScoutDisposedError extends ScoutError {}\n\n/** Thrown when an index is built or queried with an invalid configuration (e.g. zero fields defined). */\nexport class ScoutIndexError extends ScoutError {}\n"],"mappings":";AACA,IAAa,IAAb,MAAa,UAAmB,MAAM;CACpC,YAAY,GAAiB,GAAqB;EAGhD,AAFA,MAAM,GAAS,CAAI,GACnB,KAAK,OAAO,WAAW,MACvB,OAAO,eAAe,MAAM,WAAW,SAAS;CAClD;CAEA,OAAO,GAAG,GAAiC;EACzC,OAAO,aAAe;CACxB;AACF,GAGa,IAAb,cAAwC,EAAW,CAAC,GAGvC,IAAb,cAAqC,EAAW,CAAC"}
1
+ {"version":3,"file":"errors.js","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/** Base class for all scout errors. Use `instanceof ScoutError` to catch any scout-originated error. */\nexport class ScoutError extends Error {\n constructor(message: string, opts?: ErrorOptions) {\n super(message, opts);\n this.name = new.target.name;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n static is(err: unknown): err is ScoutError {\n return err instanceof ScoutError;\n }\n}\n\n/** Thrown when a method is called on a disposed search state instance. */\nexport class ScoutDisposedError extends ScoutError {}\n\n/** Thrown when an index, search, or reactive search receives an invalid configuration. */\nexport class ScoutConfigurationError extends ScoutError {}\n"],"mappings":";AACA,IAAa,IAAb,MAAa,UAAmB,MAAM;CACpC,YAAY,GAAiB,GAAqB;EAGhD,AAFA,MAAM,GAAS,CAAI,GACnB,KAAK,OAAO,WAAW,MACvB,OAAO,eAAe,MAAM,WAAW,SAAS;CAClD;CAEA,OAAO,GAAG,GAAiC;EACzC,OAAO,aAAe;CACxB;AACF,GAGa,IAAb,cAAwC,EAAW,CAAC,GAGvC,IAAb,cAA6C,EAAW,CAAC"}
@@ -1,2 +1,2 @@
1
- function e(e,t){let n=e.toLowerCase(),r=t.split(` `).filter(Boolean),i=[];for(let e of r){let t=0;for(;t<n.length;){let r=n.indexOf(e,t);if(r===-1)break;i.push([r,r+e.length]),t=r+1}}i.sort((e,t)=>e[0]-t[0]);let a=[];for(let e of i){let t=a[a.length-1];t&&e[0]<=t[1]?t[1]=Math.max(t[1],e[1]):a.push([e[0],e[1]])}return a}function t(e,t){if(!e)return[];if(!t.length)return[{highlighted:!1,text:e}];let n=[],r=0;for(let[i,a]of t)i>r&&n.push({highlighted:!1,text:e.slice(r,i)}),a>i&&n.push({highlighted:!0,text:e.slice(i,a)}),r=a;return r<e.length&&n.push({highlighted:!1,text:e.slice(r)}),n}function n(e,n,r){return t(r,e.matches.find(e=>e.field===n)?.ranges??[])}exports.findMatchRanges=e,exports.highlight=t,exports.highlightField=n;
1
+ const e=require("./tokenize.cjs");function t(t,n){let r=t.toLowerCase(),i=e.tokenize(n).split(` `).filter(Boolean),a=[];for(let e of i){let t=0;for(;t<r.length;){let n=r.indexOf(e,t);if(n===-1)break;a.push([n,n+e.length]),t=n+1}}a.sort((e,t)=>e[0]-t[0]);let o=[];for(let e of a){let t=o[o.length-1];t&&e[0]<=t[1]?t[1]=Math.max(t[1],e[1]):o.push([e[0],e[1]])}return o}function n(e,t){if(!e)return[];if(!t.length)return[{highlighted:!1,text:e}];let n=[],r=0;for(let[i,a]of t)i>r&&n.push({highlighted:!1,text:e.slice(r,i)}),a>i&&n.push({highlighted:!0,text:e.slice(i,a)}),r=a;return r<e.length&&n.push({highlighted:!1,text:e.slice(r)}),n}function r(e,t,r){return n(r,e.matches.find(e=>e.field===t)?.ranges??[])}exports.findMatchRanges=t,exports.highlight=n,exports.highlightField=r;
2
2
  //# sourceMappingURL=highlight.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"highlight.cjs","names":[],"sources":["../src/highlight.ts"],"sourcesContent":["import type { HighlightPart, SearchResult } from './types';\n\n/**\n * Finds character ranges within `text` where `query` words appear.\n * Ranges are sorted and overlapping ranges are merged.\n *\n * Useful when you need to apply match ranges to a different string than the\n * indexed field value (e.g. a truncated preview or a differently formatted display string).\n *\n * @param text - The string to search within.\n * @param query - The normalized (tokenized) query string. Words are split on spaces.\n * @returns Sorted, non-overlapping `[start, end]` character ranges.\n */\nexport function findMatchRanges(text: string, query: string): [number, number][] {\n const lower = text.toLowerCase();\n const words = query.split(' ').filter(Boolean);\n const ranges: [number, number][] = [];\n\n for (const word of words) {\n let pos = 0;\n\n while (pos < lower.length) {\n const idx = lower.indexOf(word, pos);\n\n if (idx === -1) break;\n\n ranges.push([idx, idx + word.length]);\n pos = idx + 1;\n }\n }\n\n ranges.sort((a, b) => a[0] - b[0]);\n\n const merged: [number, number][] = [];\n\n for (const range of ranges) {\n const last = merged[merged.length - 1];\n\n if (last && range[0] <= last[1]) {\n last[1] = Math.max(last[1], range[1]);\n } else {\n merged.push([range[0], range[1]]);\n }\n }\n\n return merged;\n}\n\n/**\n * Splits `text` into highlighted and unhighlighted fragments using match `ranges`.\n *\n * Ranges must be sorted and non-overlapping (as produced by `SearchResult.matches[n].ranges`).\n * Use the returned parts to render highlighted text in a UI component.\n *\n * **`part.text` is the original, unescaped field value** (e.g. a user's name, bio, or\n * product title) — this function does no HTML escaping. Render each part via safe DOM APIs\n * (`textContent`, a framework's text binding) and wrap `highlighted` parts in your own\n * element (e.g. `<mark>`); never concatenate `part.text` into an HTML string for\n * `innerHTML` — that reintroduces the XSS risk this structured return shape avoids.\n *\n * @example\n * ```ts\n * highlight('Hello World', [[0, 5]]);\n * // [{ text: 'Hello', highlighted: true }, { text: ' World', highlighted: false }]\n *\n * highlight('Hello World', [[0, 5], [6, 11]]);\n * // [\n * // { text: 'Hello', highlighted: true },\n * // { text: ' ', highlighted: false },\n * // { text: 'World', highlighted: true },\n * // ]\n * ```\n *\n * @param text - The original field value to split.\n * @param ranges - Sorted, non-overlapping `[start, end]` ranges from `FieldMatch.ranges`.\n * @returns An array of `HighlightPart` objects. Returns an empty array for an empty `text`.\n */\nexport function highlight(text: string, ranges: [number, number][]): HighlightPart[] {\n if (!text) return [];\n\n if (!ranges.length) return [{ highlighted: false, text }];\n\n const parts: HighlightPart[] = [];\n let cursor = 0;\n\n for (const [start, end] of ranges) {\n if (start > cursor) {\n parts.push({ highlighted: false, text: text.slice(cursor, start) });\n }\n\n if (end > start) {\n parts.push({ highlighted: true, text: text.slice(start, end) });\n }\n\n cursor = end;\n }\n\n if (cursor < text.length) {\n parts.push({ highlighted: false, text: text.slice(cursor) });\n }\n\n return parts;\n}\n\n/**\n * Finds the match ranges for `field` in `result` and splits `text` into\n * highlighted and unhighlighted fragments in one step.\n *\n * This is the ergonomic shorthand for the common pattern:\n * ```ts\n * const match = result.matches.find(m => m.field === 'name');\n * const parts = highlight(item.name, match?.ranges ?? []);\n * ```\n *\n * @example\n * ```ts\n * for (const result of index.search('alice')) {\n * const parts = highlightField(result, 'name', result.item.name);\n * console.log(parts.map(p => p.highlighted ? `[${p.text}]` : p.text).join(''));\n * }\n * ```\n *\n * @param result - A `SearchResult` from `ScoutIndex.search()`.\n * @param field - The field name to look up in `result.matches`.\n * @param text - The original field value string to split.\n * @returns An array of `HighlightPart` objects.\n */\nexport function highlightField<T>(result: SearchResult<T>, field: keyof T & string, text: string): HighlightPart[] {\n const match = result.matches.find((m) => m.field === field);\n\n return highlight(text, match?.ranges ?? []);\n}\n"],"mappings":"AAaA,SAAgB,EAAgB,EAAc,EAAmC,CAC/E,IAAM,EAAQ,EAAK,YAAY,EACzB,EAAQ,EAAM,MAAM,GAAG,CAAC,CAAC,OAAO,OAAO,EACvC,EAA6B,CAAC,EAEpC,IAAK,IAAM,KAAQ,EAAO,CACxB,IAAI,EAAM,EAEV,KAAO,EAAM,EAAM,QAAQ,CACzB,IAAM,EAAM,EAAM,QAAQ,EAAM,CAAG,EAEnC,GAAI,IAAQ,GAAI,MAEhB,EAAO,KAAK,CAAC,EAAK,EAAM,EAAK,MAAM,CAAC,EACpC,EAAM,EAAM,CACd,CACF,CAEA,EAAO,MAAM,EAAG,IAAM,EAAE,GAAK,EAAE,EAAE,EAEjC,IAAM,EAA6B,CAAC,EAEpC,IAAK,IAAM,KAAS,EAAQ,CAC1B,IAAM,EAAO,EAAO,EAAO,OAAS,GAEhC,GAAQ,EAAM,IAAM,EAAK,GAC3B,EAAK,GAAK,KAAK,IAAI,EAAK,GAAI,EAAM,EAAE,EAEpC,EAAO,KAAK,CAAC,EAAM,GAAI,EAAM,EAAE,CAAC,CAEpC,CAEA,OAAO,CACT,CA+BA,SAAgB,EAAU,EAAc,EAA6C,CACnF,GAAI,CAAC,EAAM,MAAO,CAAC,EAEnB,GAAI,CAAC,EAAO,OAAQ,MAAO,CAAC,CAAE,YAAa,GAAO,MAAK,CAAC,EAExD,IAAM,EAAyB,CAAC,EAC5B,EAAS,EAEb,IAAK,GAAM,CAAC,EAAO,KAAQ,EACrB,EAAQ,GACV,EAAM,KAAK,CAAE,YAAa,GAAO,KAAM,EAAK,MAAM,EAAQ,CAAK,CAAE,CAAC,EAGhE,EAAM,GACR,EAAM,KAAK,CAAE,YAAa,GAAM,KAAM,EAAK,MAAM,EAAO,CAAG,CAAE,CAAC,EAGhE,EAAS,EAOX,OAJI,EAAS,EAAK,QAChB,EAAM,KAAK,CAAE,YAAa,GAAO,KAAM,EAAK,MAAM,CAAM,CAAE,CAAC,EAGtD,CACT,CAyBA,SAAgB,EAAkB,EAAyB,EAAyB,EAA+B,CAGjH,OAAO,EAAU,EAFH,EAAO,QAAQ,KAAM,GAAM,EAAE,QAAU,CAE9B,CAAA,EAAO,QAAU,CAAC,CAAC,CAC5C"}
1
+ {"version":3,"file":"highlight.cjs","names":[],"sources":["../src/highlight.ts"],"sourcesContent":["import type { HighlightPart, SearchResult } from './types';\n\nimport { tokenize } from './tokenize';\n\n/**\n * Finds character ranges within `text` where `query` words appear.\n * Ranges are sorted and overlapping ranges are merged.\n *\n * Useful when you need to apply match ranges to a different string than the\n * indexed field value (e.g. a truncated preview or a differently formatted display string).\n *\n * @param text - The string to search within.\n * @param query - Raw query text. It is normalized with Scout's tokenizer before literal lookup.\n * @returns Sorted, non-overlapping `[start, end]` character ranges.\n */\nexport function findMatchRanges(text: string, query: string): [number, number][] {\n const lower = text.toLowerCase();\n const words = tokenize(query).split(' ').filter(Boolean);\n const ranges: [number, number][] = [];\n\n for (const word of words) {\n let pos = 0;\n\n while (pos < lower.length) {\n const idx = lower.indexOf(word, pos);\n\n if (idx === -1) break;\n\n ranges.push([idx, idx + word.length]);\n pos = idx + 1;\n }\n }\n\n ranges.sort((a, b) => a[0] - b[0]);\n\n const merged: [number, number][] = [];\n\n for (const range of ranges) {\n const last = merged[merged.length - 1];\n\n if (last && range[0] <= last[1]) {\n last[1] = Math.max(last[1], range[1]);\n } else {\n merged.push([range[0], range[1]]);\n }\n }\n\n return merged;\n}\n\n/**\n * Splits `text` into highlighted and unhighlighted fragments using match `ranges`.\n *\n * Ranges must be sorted and non-overlapping (as produced by `SearchResult.matches[n].ranges`).\n * Use the returned parts to render highlighted text in a UI component.\n *\n * **`part.text` is the original, unescaped field value** (e.g. a user's name, bio, or\n * product title) — this function does no HTML escaping. Render each part via safe DOM APIs\n * (`textContent`, a framework's text binding) and wrap `highlighted` parts in your own\n * element (e.g. `<mark>`); never concatenate `part.text` into an HTML string for\n * `innerHTML` — that reintroduces the XSS risk this structured return shape avoids.\n *\n * @example\n * ```ts\n * highlight('Hello World', [[0, 5]]);\n * // [{ text: 'Hello', highlighted: true }, { text: ' World', highlighted: false }]\n *\n * highlight('Hello World', [[0, 5], [6, 11]]);\n * // [\n * // { text: 'Hello', highlighted: true },\n * // { text: ' ', highlighted: false },\n * // { text: 'World', highlighted: true },\n * // ]\n * ```\n *\n * @param text - The original field value to split.\n * @param ranges - Sorted, non-overlapping `[start, end]` ranges from `FieldMatch.ranges`.\n * @returns An array of `HighlightPart` objects. Returns an empty array for an empty `text`.\n */\nexport function highlight(text: string, ranges: [number, number][]): HighlightPart[] {\n if (!text) return [];\n\n if (!ranges.length) return [{ highlighted: false, text }];\n\n const parts: HighlightPart[] = [];\n let cursor = 0;\n\n for (const [start, end] of ranges) {\n if (start > cursor) {\n parts.push({ highlighted: false, text: text.slice(cursor, start) });\n }\n\n if (end > start) {\n parts.push({ highlighted: true, text: text.slice(start, end) });\n }\n\n cursor = end;\n }\n\n if (cursor < text.length) {\n parts.push({ highlighted: false, text: text.slice(cursor) });\n }\n\n return parts;\n}\n\n/**\n * Finds the match ranges for `field` in `result` and splits `text` into\n * highlighted and unhighlighted fragments in one step.\n *\n * This is the ergonomic shorthand for the common pattern:\n * ```ts\n * const match = result.matches.find(m => m.field === 'name');\n * const parts = highlight(item.name, match?.ranges ?? []);\n * ```\n *\n * @example\n * ```ts\n * for (const result of index.search('alice')) {\n * const parts = highlightField(result, 'name', result.item.name);\n * console.log(parts.map(p => p.highlighted ? `[${p.text}]` : p.text).join(''));\n * }\n * ```\n *\n * @param result - A `SearchResult` from `ScoutIndex.search()`.\n * @param field - The field name to look up in `result.matches`.\n * @param text - The original field value string to split.\n * @returns An array of `HighlightPart` objects.\n */\nexport function highlightField<T>(result: SearchResult<T>, field: keyof T & string, text: string): HighlightPart[] {\n const match = result.matches.find((m) => m.field === field);\n\n return highlight(text, match?.ranges ?? []);\n}\n"],"mappings":"kCAeA,SAAgB,EAAgB,EAAc,EAAmC,CAC/E,IAAM,EAAQ,EAAK,YAAY,EACzB,EAAQ,EAAA,SAAS,CAAK,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,OAAO,OAAO,EACjD,EAA6B,CAAC,EAEpC,IAAK,IAAM,KAAQ,EAAO,CACxB,IAAI,EAAM,EAEV,KAAO,EAAM,EAAM,QAAQ,CACzB,IAAM,EAAM,EAAM,QAAQ,EAAM,CAAG,EAEnC,GAAI,IAAQ,GAAI,MAEhB,EAAO,KAAK,CAAC,EAAK,EAAM,EAAK,MAAM,CAAC,EACpC,EAAM,EAAM,CACd,CACF,CAEA,EAAO,MAAM,EAAG,IAAM,EAAE,GAAK,EAAE,EAAE,EAEjC,IAAM,EAA6B,CAAC,EAEpC,IAAK,IAAM,KAAS,EAAQ,CAC1B,IAAM,EAAO,EAAO,EAAO,OAAS,GAEhC,GAAQ,EAAM,IAAM,EAAK,GAC3B,EAAK,GAAK,KAAK,IAAI,EAAK,GAAI,EAAM,EAAE,EAEpC,EAAO,KAAK,CAAC,EAAM,GAAI,EAAM,EAAE,CAAC,CAEpC,CAEA,OAAO,CACT,CA+BA,SAAgB,EAAU,EAAc,EAA6C,CACnF,GAAI,CAAC,EAAM,MAAO,CAAC,EAEnB,GAAI,CAAC,EAAO,OAAQ,MAAO,CAAC,CAAE,YAAa,GAAO,MAAK,CAAC,EAExD,IAAM,EAAyB,CAAC,EAC5B,EAAS,EAEb,IAAK,GAAM,CAAC,EAAO,KAAQ,EACrB,EAAQ,GACV,EAAM,KAAK,CAAE,YAAa,GAAO,KAAM,EAAK,MAAM,EAAQ,CAAK,CAAE,CAAC,EAGhE,EAAM,GACR,EAAM,KAAK,CAAE,YAAa,GAAM,KAAM,EAAK,MAAM,EAAO,CAAG,CAAE,CAAC,EAGhE,EAAS,EAOX,OAJI,EAAS,EAAK,QAChB,EAAM,KAAK,CAAE,YAAa,GAAO,KAAM,EAAK,MAAM,CAAM,CAAE,CAAC,EAGtD,CACT,CAyBA,SAAgB,EAAkB,EAAyB,EAAyB,EAA+B,CAGjH,OAAO,EAAU,EAFH,EAAO,QAAQ,KAAM,GAAM,EAAE,QAAU,CAE9B,CAAA,EAAO,QAAU,CAAC,CAAC,CAC5C"}
@@ -7,7 +7,7 @@ import type { HighlightPart, SearchResult } from './types';
7
7
  * indexed field value (e.g. a truncated preview or a differently formatted display string).
8
8
  *
9
9
  * @param text - The string to search within.
10
- * @param query - The normalized (tokenized) query string. Words are split on spaces.
10
+ * @param query - Raw query text. It is normalized with Scout's tokenizer before literal lookup.
11
11
  * @returns Sorted, non-overlapping `[start, end]` character ranges.
12
12
  */
13
13
  export declare function findMatchRanges(text: string, query: string): [number, number][];
@@ -1 +1 @@
1
- {"version":3,"file":"highlight.d.ts","sourceRoot":"","sources":["../src/highlight.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE3D;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAiC/E;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,aAAa,EAAE,CAyBnF;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,aAAa,EAAE,CAIjH"}
1
+ {"version":3,"file":"highlight.d.ts","sourceRoot":"","sources":["../src/highlight.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAI3D;;;;;;;;;;GAUG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAiC/E;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,aAAa,EAAE,CAyBnF;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,aAAa,EAAE,CAIjH"}
package/dist/highlight.js CHANGED
@@ -1,23 +1,24 @@
1
+ import { tokenize as e } from "./tokenize.js";
1
2
  //#region src/highlight.ts
2
- function e(e, t) {
3
- let n = e.toLowerCase(), r = t.split(" ").filter(Boolean), i = [];
4
- for (let e of r) {
3
+ function t(t, n) {
4
+ let r = t.toLowerCase(), i = e(n).split(" ").filter(Boolean), a = [];
5
+ for (let e of i) {
5
6
  let t = 0;
6
- for (; t < n.length;) {
7
- let r = n.indexOf(e, t);
8
- if (r === -1) break;
9
- i.push([r, r + e.length]), t = r + 1;
7
+ for (; t < r.length;) {
8
+ let n = r.indexOf(e, t);
9
+ if (n === -1) break;
10
+ a.push([n, n + e.length]), t = n + 1;
10
11
  }
11
12
  }
12
- i.sort((e, t) => e[0] - t[0]);
13
- let a = [];
14
- for (let e of i) {
15
- let t = a[a.length - 1];
16
- t && e[0] <= t[1] ? t[1] = Math.max(t[1], e[1]) : a.push([e[0], e[1]]);
13
+ a.sort((e, t) => e[0] - t[0]);
14
+ let o = [];
15
+ for (let e of a) {
16
+ let t = o[o.length - 1];
17
+ t && e[0] <= t[1] ? t[1] = Math.max(t[1], e[1]) : o.push([e[0], e[1]]);
17
18
  }
18
- return a;
19
+ return o;
19
20
  }
20
- function t(e, t) {
21
+ function n(e, t) {
21
22
  if (!e) return [];
22
23
  if (!t.length) return [{
23
24
  highlighted: !1,
@@ -36,10 +37,10 @@ function t(e, t) {
36
37
  text: e.slice(r)
37
38
  }), n;
38
39
  }
39
- function n(e, n, r) {
40
- return t(r, e.matches.find((e) => e.field === n)?.ranges ?? []);
40
+ function r(e, t, r) {
41
+ return n(r, e.matches.find((e) => e.field === t)?.ranges ?? []);
41
42
  }
42
43
  //#endregion
43
- export { e as findMatchRanges, t as highlight, n as highlightField };
44
+ export { t as findMatchRanges, n as highlight, r as highlightField };
44
45
 
45
46
  //# sourceMappingURL=highlight.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"highlight.js","names":[],"sources":["../src/highlight.ts"],"sourcesContent":["import type { HighlightPart, SearchResult } from './types';\n\n/**\n * Finds character ranges within `text` where `query` words appear.\n * Ranges are sorted and overlapping ranges are merged.\n *\n * Useful when you need to apply match ranges to a different string than the\n * indexed field value (e.g. a truncated preview or a differently formatted display string).\n *\n * @param text - The string to search within.\n * @param query - The normalized (tokenized) query string. Words are split on spaces.\n * @returns Sorted, non-overlapping `[start, end]` character ranges.\n */\nexport function findMatchRanges(text: string, query: string): [number, number][] {\n const lower = text.toLowerCase();\n const words = query.split(' ').filter(Boolean);\n const ranges: [number, number][] = [];\n\n for (const word of words) {\n let pos = 0;\n\n while (pos < lower.length) {\n const idx = lower.indexOf(word, pos);\n\n if (idx === -1) break;\n\n ranges.push([idx, idx + word.length]);\n pos = idx + 1;\n }\n }\n\n ranges.sort((a, b) => a[0] - b[0]);\n\n const merged: [number, number][] = [];\n\n for (const range of ranges) {\n const last = merged[merged.length - 1];\n\n if (last && range[0] <= last[1]) {\n last[1] = Math.max(last[1], range[1]);\n } else {\n merged.push([range[0], range[1]]);\n }\n }\n\n return merged;\n}\n\n/**\n * Splits `text` into highlighted and unhighlighted fragments using match `ranges`.\n *\n * Ranges must be sorted and non-overlapping (as produced by `SearchResult.matches[n].ranges`).\n * Use the returned parts to render highlighted text in a UI component.\n *\n * **`part.text` is the original, unescaped field value** (e.g. a user's name, bio, or\n * product title) — this function does no HTML escaping. Render each part via safe DOM APIs\n * (`textContent`, a framework's text binding) and wrap `highlighted` parts in your own\n * element (e.g. `<mark>`); never concatenate `part.text` into an HTML string for\n * `innerHTML` — that reintroduces the XSS risk this structured return shape avoids.\n *\n * @example\n * ```ts\n * highlight('Hello World', [[0, 5]]);\n * // [{ text: 'Hello', highlighted: true }, { text: ' World', highlighted: false }]\n *\n * highlight('Hello World', [[0, 5], [6, 11]]);\n * // [\n * // { text: 'Hello', highlighted: true },\n * // { text: ' ', highlighted: false },\n * // { text: 'World', highlighted: true },\n * // ]\n * ```\n *\n * @param text - The original field value to split.\n * @param ranges - Sorted, non-overlapping `[start, end]` ranges from `FieldMatch.ranges`.\n * @returns An array of `HighlightPart` objects. Returns an empty array for an empty `text`.\n */\nexport function highlight(text: string, ranges: [number, number][]): HighlightPart[] {\n if (!text) return [];\n\n if (!ranges.length) return [{ highlighted: false, text }];\n\n const parts: HighlightPart[] = [];\n let cursor = 0;\n\n for (const [start, end] of ranges) {\n if (start > cursor) {\n parts.push({ highlighted: false, text: text.slice(cursor, start) });\n }\n\n if (end > start) {\n parts.push({ highlighted: true, text: text.slice(start, end) });\n }\n\n cursor = end;\n }\n\n if (cursor < text.length) {\n parts.push({ highlighted: false, text: text.slice(cursor) });\n }\n\n return parts;\n}\n\n/**\n * Finds the match ranges for `field` in `result` and splits `text` into\n * highlighted and unhighlighted fragments in one step.\n *\n * This is the ergonomic shorthand for the common pattern:\n * ```ts\n * const match = result.matches.find(m => m.field === 'name');\n * const parts = highlight(item.name, match?.ranges ?? []);\n * ```\n *\n * @example\n * ```ts\n * for (const result of index.search('alice')) {\n * const parts = highlightField(result, 'name', result.item.name);\n * console.log(parts.map(p => p.highlighted ? `[${p.text}]` : p.text).join(''));\n * }\n * ```\n *\n * @param result - A `SearchResult` from `ScoutIndex.search()`.\n * @param field - The field name to look up in `result.matches`.\n * @param text - The original field value string to split.\n * @returns An array of `HighlightPart` objects.\n */\nexport function highlightField<T>(result: SearchResult<T>, field: keyof T & string, text: string): HighlightPart[] {\n const match = result.matches.find((m) => m.field === field);\n\n return highlight(text, match?.ranges ?? []);\n}\n"],"mappings":";AAaA,SAAgB,EAAgB,GAAc,GAAmC;CAC/E,IAAM,IAAQ,EAAK,YAAY,GACzB,IAAQ,EAAM,MAAM,GAAG,CAAC,CAAC,OAAO,OAAO,GACvC,IAA6B,CAAC;CAEpC,KAAK,IAAM,KAAQ,GAAO;EACxB,IAAI,IAAM;EAEV,OAAO,IAAM,EAAM,SAAQ;GACzB,IAAM,IAAM,EAAM,QAAQ,GAAM,CAAG;GAEnC,IAAI,MAAQ,IAAI;GAGhB,AADA,EAAO,KAAK,CAAC,GAAK,IAAM,EAAK,MAAM,CAAC,GACpC,IAAM,IAAM;EACd;CACF;CAEA,EAAO,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,EAAE;CAEjC,IAAM,IAA6B,CAAC;CAEpC,KAAK,IAAM,KAAS,GAAQ;EAC1B,IAAM,IAAO,EAAO,EAAO,SAAS;EAEpC,AAAI,KAAQ,EAAM,MAAM,EAAK,KAC3B,EAAK,KAAK,KAAK,IAAI,EAAK,IAAI,EAAM,EAAE,IAEpC,EAAO,KAAK,CAAC,EAAM,IAAI,EAAM,EAAE,CAAC;CAEpC;CAEA,OAAO;AACT;AA+BA,SAAgB,EAAU,GAAc,GAA6C;CACnF,IAAI,CAAC,GAAM,OAAO,CAAC;CAEnB,IAAI,CAAC,EAAO,QAAQ,OAAO,CAAC;EAAE,aAAa;EAAO;CAAK,CAAC;CAExD,IAAM,IAAyB,CAAC,GAC5B,IAAS;CAEb,KAAK,IAAM,CAAC,GAAO,MAAQ,GASzB,AARI,IAAQ,KACV,EAAM,KAAK;EAAE,aAAa;EAAO,MAAM,EAAK,MAAM,GAAQ,CAAK;CAAE,CAAC,GAGhE,IAAM,KACR,EAAM,KAAK;EAAE,aAAa;EAAM,MAAM,EAAK,MAAM,GAAO,CAAG;CAAE,CAAC,GAGhE,IAAS;CAOX,OAJI,IAAS,EAAK,UAChB,EAAM,KAAK;EAAE,aAAa;EAAO,MAAM,EAAK,MAAM,CAAM;CAAE,CAAC,GAGtD;AACT;AAyBA,SAAgB,EAAkB,GAAyB,GAAyB,GAA+B;CAGjH,OAAO,EAAU,GAFH,EAAO,QAAQ,MAAM,MAAM,EAAE,UAAU,CAE9B,CAAA,EAAO,UAAU,CAAC,CAAC;AAC5C"}
1
+ {"version":3,"file":"highlight.js","names":[],"sources":["../src/highlight.ts"],"sourcesContent":["import type { HighlightPart, SearchResult } from './types';\n\nimport { tokenize } from './tokenize';\n\n/**\n * Finds character ranges within `text` where `query` words appear.\n * Ranges are sorted and overlapping ranges are merged.\n *\n * Useful when you need to apply match ranges to a different string than the\n * indexed field value (e.g. a truncated preview or a differently formatted display string).\n *\n * @param text - The string to search within.\n * @param query - Raw query text. It is normalized with Scout's tokenizer before literal lookup.\n * @returns Sorted, non-overlapping `[start, end]` character ranges.\n */\nexport function findMatchRanges(text: string, query: string): [number, number][] {\n const lower = text.toLowerCase();\n const words = tokenize(query).split(' ').filter(Boolean);\n const ranges: [number, number][] = [];\n\n for (const word of words) {\n let pos = 0;\n\n while (pos < lower.length) {\n const idx = lower.indexOf(word, pos);\n\n if (idx === -1) break;\n\n ranges.push([idx, idx + word.length]);\n pos = idx + 1;\n }\n }\n\n ranges.sort((a, b) => a[0] - b[0]);\n\n const merged: [number, number][] = [];\n\n for (const range of ranges) {\n const last = merged[merged.length - 1];\n\n if (last && range[0] <= last[1]) {\n last[1] = Math.max(last[1], range[1]);\n } else {\n merged.push([range[0], range[1]]);\n }\n }\n\n return merged;\n}\n\n/**\n * Splits `text` into highlighted and unhighlighted fragments using match `ranges`.\n *\n * Ranges must be sorted and non-overlapping (as produced by `SearchResult.matches[n].ranges`).\n * Use the returned parts to render highlighted text in a UI component.\n *\n * **`part.text` is the original, unescaped field value** (e.g. a user's name, bio, or\n * product title) — this function does no HTML escaping. Render each part via safe DOM APIs\n * (`textContent`, a framework's text binding) and wrap `highlighted` parts in your own\n * element (e.g. `<mark>`); never concatenate `part.text` into an HTML string for\n * `innerHTML` — that reintroduces the XSS risk this structured return shape avoids.\n *\n * @example\n * ```ts\n * highlight('Hello World', [[0, 5]]);\n * // [{ text: 'Hello', highlighted: true }, { text: ' World', highlighted: false }]\n *\n * highlight('Hello World', [[0, 5], [6, 11]]);\n * // [\n * // { text: 'Hello', highlighted: true },\n * // { text: ' ', highlighted: false },\n * // { text: 'World', highlighted: true },\n * // ]\n * ```\n *\n * @param text - The original field value to split.\n * @param ranges - Sorted, non-overlapping `[start, end]` ranges from `FieldMatch.ranges`.\n * @returns An array of `HighlightPart` objects. Returns an empty array for an empty `text`.\n */\nexport function highlight(text: string, ranges: [number, number][]): HighlightPart[] {\n if (!text) return [];\n\n if (!ranges.length) return [{ highlighted: false, text }];\n\n const parts: HighlightPart[] = [];\n let cursor = 0;\n\n for (const [start, end] of ranges) {\n if (start > cursor) {\n parts.push({ highlighted: false, text: text.slice(cursor, start) });\n }\n\n if (end > start) {\n parts.push({ highlighted: true, text: text.slice(start, end) });\n }\n\n cursor = end;\n }\n\n if (cursor < text.length) {\n parts.push({ highlighted: false, text: text.slice(cursor) });\n }\n\n return parts;\n}\n\n/**\n * Finds the match ranges for `field` in `result` and splits `text` into\n * highlighted and unhighlighted fragments in one step.\n *\n * This is the ergonomic shorthand for the common pattern:\n * ```ts\n * const match = result.matches.find(m => m.field === 'name');\n * const parts = highlight(item.name, match?.ranges ?? []);\n * ```\n *\n * @example\n * ```ts\n * for (const result of index.search('alice')) {\n * const parts = highlightField(result, 'name', result.item.name);\n * console.log(parts.map(p => p.highlighted ? `[${p.text}]` : p.text).join(''));\n * }\n * ```\n *\n * @param result - A `SearchResult` from `ScoutIndex.search()`.\n * @param field - The field name to look up in `result.matches`.\n * @param text - The original field value string to split.\n * @returns An array of `HighlightPart` objects.\n */\nexport function highlightField<T>(result: SearchResult<T>, field: keyof T & string, text: string): HighlightPart[] {\n const match = result.matches.find((m) => m.field === field);\n\n return highlight(text, match?.ranges ?? []);\n}\n"],"mappings":";;AAeA,SAAgB,EAAgB,GAAc,GAAmC;CAC/E,IAAM,IAAQ,EAAK,YAAY,GACzB,IAAQ,EAAS,CAAK,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,OAAO,OAAO,GACjD,IAA6B,CAAC;CAEpC,KAAK,IAAM,KAAQ,GAAO;EACxB,IAAI,IAAM;EAEV,OAAO,IAAM,EAAM,SAAQ;GACzB,IAAM,IAAM,EAAM,QAAQ,GAAM,CAAG;GAEnC,IAAI,MAAQ,IAAI;GAGhB,AADA,EAAO,KAAK,CAAC,GAAK,IAAM,EAAK,MAAM,CAAC,GACpC,IAAM,IAAM;EACd;CACF;CAEA,EAAO,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,EAAE;CAEjC,IAAM,IAA6B,CAAC;CAEpC,KAAK,IAAM,KAAS,GAAQ;EAC1B,IAAM,IAAO,EAAO,EAAO,SAAS;EAEpC,AAAI,KAAQ,EAAM,MAAM,EAAK,KAC3B,EAAK,KAAK,KAAK,IAAI,EAAK,IAAI,EAAM,EAAE,IAEpC,EAAO,KAAK,CAAC,EAAM,IAAI,EAAM,EAAE,CAAC;CAEpC;CAEA,OAAO;AACT;AA+BA,SAAgB,EAAU,GAAc,GAA6C;CACnF,IAAI,CAAC,GAAM,OAAO,CAAC;CAEnB,IAAI,CAAC,EAAO,QAAQ,OAAO,CAAC;EAAE,aAAa;EAAO;CAAK,CAAC;CAExD,IAAM,IAAyB,CAAC,GAC5B,IAAS;CAEb,KAAK,IAAM,CAAC,GAAO,MAAQ,GASzB,AARI,IAAQ,KACV,EAAM,KAAK;EAAE,aAAa;EAAO,MAAM,EAAK,MAAM,GAAQ,CAAK;CAAE,CAAC,GAGhE,IAAM,KACR,EAAM,KAAK;EAAE,aAAa;EAAM,MAAM,EAAK,MAAM,GAAO,CAAG;CAAE,CAAC,GAGhE,IAAS;CAOX,OAJI,IAAS,EAAK,UAChB,EAAM,KAAK;EAAE,aAAa;EAAO,MAAM,EAAK,MAAM,CAAM;CAAE,CAAC,GAGtD;AACT;AAyBA,SAAgB,EAAkB,GAAyB,GAAyB,GAA+B;CAGjH,OAAO,EAAU,GAFH,EAAO,QAAQ,MAAM,MAAM,EAAE,UAAU,CAE9B,CAAA,EAAO,UAAU,CAAC,CAAC;AAC5C"}
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./adapters.cjs"),t=require("./errors.cjs"),n=require("./highlight.cjs"),r=require("./scout-index.cjs"),i=require("./reactive.cjs"),a=require("./segment.cjs");exports.ScoutDisposedError=t.ScoutDisposedError,exports.ScoutError=t.ScoutError,exports.ScoutIndexError=t.ScoutIndexError,exports.createIndex=r.createIndex,exports.createReactiveSearch=i.createReactiveSearch,exports.createSearch=i.createSearch,exports.findMatchRanges=n.findMatchRanges,exports.highlight=n.highlight,exports.highlightField=n.highlightField,exports.segmentWords=a.segmentWords,exports.toFilterPredicate=e.toFilterPredicate,exports.toSearchMatcher=e.toSearchMatcher;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./adapters.cjs"),t=require("./errors.cjs"),n=require("./highlight.cjs"),r=require("./scout-index.cjs"),i=require("./reactive.cjs"),a=require("./segment.cjs");exports.ScoutConfigurationError=t.ScoutConfigurationError,exports.ScoutDisposedError=t.ScoutDisposedError,exports.ScoutError=t.ScoutError,exports.createIndex=r.createIndex,exports.createReactiveSearch=i.createReactiveSearch,exports.createSearch=i.createSearch,exports.findMatchRanges=n.findMatchRanges,exports.highlight=n.highlight,exports.highlightField=n.highlightField,exports.segmentWords=a.segmentWords,exports.toFilterPredicate=e.toFilterPredicate,exports.toSearchMatcher=e.toSearchMatcher;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { toFilterPredicate, toSearchMatcher } from './adapters';
2
- export { ScoutDisposedError, ScoutError, ScoutIndexError } from './errors';
2
+ export { ScoutConfigurationError, ScoutDisposedError, ScoutError } from './errors';
3
3
  export { findMatchRanges, highlight, highlightField } from './highlight';
4
4
  export { createReactiveSearch, createSearch } from './reactive';
5
5
  export type { ReactiveSearch } from './reactive';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAChE,OAAO,EAAE,kBAAkB,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAC3E,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AACzE,OAAO,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAChE,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,YAAY,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,YAAY,EACV,mBAAmB,EACnB,QAAQ,EACR,UAAU,EACV,aAAa,EACb,iBAAiB,EACjB,iBAAiB,EACjB,YAAY,EACZ,WAAW,GACZ,MAAM,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAChE,OAAO,EAAE,uBAAuB,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACnF,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AACzE,OAAO,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAChE,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,YAAY,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,YAAY,EACV,mBAAmB,EACnB,QAAQ,EACR,UAAU,EACV,aAAa,EACb,iBAAiB,EACjB,iBAAiB,EACjB,YAAY,EACZ,WAAW,GACZ,MAAM,SAAS,CAAC"}
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { toFilterPredicate as e, toSearchMatcher as t } from "./adapters.js";
2
- import { ScoutDisposedError as n, ScoutError as r, ScoutIndexError as i } from "./errors.js";
2
+ import { ScoutConfigurationError as n, ScoutDisposedError as r, ScoutError as i } from "./errors.js";
3
3
  import { findMatchRanges as a, highlight as o, highlightField as s } from "./highlight.js";
4
4
  import { createIndex as c } from "./scout-index.js";
5
5
  import { createReactiveSearch as l, createSearch as u } from "./reactive.js";
6
6
  import { segmentWords as d } from "./segment.js";
7
- export { n as ScoutDisposedError, r as ScoutError, i as ScoutIndexError, c as createIndex, l as createReactiveSearch, u as createSearch, a as findMatchRanges, o as highlight, s as highlightField, d as segmentWords, e as toFilterPredicate, t as toSearchMatcher };
7
+ export { n as ScoutConfigurationError, r as ScoutDisposedError, i as ScoutError, c as createIndex, l as createReactiveSearch, u as createSearch, a as findMatchRanges, o as highlight, s as highlightField, d as segmentWords, e as toFilterPredicate, t as toSearchMatcher };
package/dist/reactive.cjs CHANGED
@@ -1,2 +1,2 @@
1
- const e=require("./errors.cjs"),t=require("./scout-index.cjs");let n=require("@vielzeug/ripple");var r=200;function i(t,i={}){let{debounce:a=r,limit:o,minQueryLength:s,threshold:c}=i,l=(0,n.signal)(``,{name:`scout:query`}),u=(0,n.signal)(``,{name:`scout:committedQuery`}),d=(0,n.signal)(0,{name:`scout:indexVersion`}),f=t.onMutate(()=>{d.value++}),p=(0,n.computed)(()=>l.value!==u.value,{name:`scout:isSearching`}),m=(0,n.computed)(()=>(d.value,t.search(u.value,{limit:o,minQueryLength:s,threshold:c})),{name:`scout:results`}),h=null;function g(){h!==null&&(clearTimeout(h),h=null)}let _=l.subscribe(()=>{let e=l.peek();if(g(),e!==u.peek()){if(a===0){u.value=e;return}h=setTimeout(()=>{u.value=e,h=null},a)}});function v(){if(y)throw new e.ScoutDisposedError(`SearchState.clear() called after dispose()`);g(),(0,n.batch)(()=>{l.value=``,u.value=``})}let y=!1,b=new AbortController;function x(){y=!0,b.abort(),g(),_(),f()}return{clear:v,get disposalSignal(){return b.signal},dispose:x,get disposed(){return y},isSearching:p,query:l,results:m,[Symbol.dispose](){x()}}}function a(e,n){let r=t.createIndex(e,{fields:n.fields,limit:n.limit,minQueryLength:n.minQueryLength,threshold:n.threshold}),a=i(r,{debounce:n.debounce});return Object.assign(Object.create(a),{index:r})}exports.createReactiveSearch=a,exports.createSearch=i;
1
+ const e=require("./errors.cjs"),t=require("./scout-index.cjs");let n=require("@vielzeug/ripple");var r=200;function i(t,i={}){let{debounce:a=r,limit:o,minQueryLength:s,threshold:c}=i;if(!Number.isFinite(a)||!Number.isInteger(a)||a<0)throw new e.ScoutConfigurationError(`debounce must be a finite non-negative integer.`);let l=(0,n.signal)(``,{name:`scout:query`}),u=(0,n.signal)(``,{name:`scout:committedQuery`}),d=(0,n.signal)(0,{name:`scout:indexVersion`}),f=t.onMutate(()=>{d.value++}),p=(0,n.computed)(()=>l.value!==u.value,{name:`scout:isSearching`}),m=(0,n.computed)(()=>(d.value,t.search(u.value,{limit:o,minQueryLength:s,threshold:c})),{name:`scout:results`}),h=null;function g(){h!==null&&(clearTimeout(h),h=null)}let _=l.subscribe(()=>{let e=l.peek();if(g(),e!==u.peek()){if(a===0){u.value=e;return}h=setTimeout(()=>{u.value=e,h=null},a)}});function v(){if(y)throw new e.ScoutDisposedError(`SearchState.clear() called after dispose()`);g(),(0,n.batch)(()=>{l.value=``,u.value=``})}let y=!1,b=new AbortController;function x(){y=!0,b.abort(),g(),_(),f()}return{clear:v,get disposalSignal(){return b.signal},dispose:x,get disposed(){return y},isSearching:p,query:l,results:m,[Symbol.dispose](){x()}}}function a(e,n){let r=t.createIndex(e,{fields:n.fields,limit:n.limit,minQueryLength:n.minQueryLength,threshold:n.threshold}),a=i(r,{debounce:n.debounce});return Object.assign(Object.create(a),{index:r})}exports.createReactiveSearch=a,exports.createSearch=i;
2
2
  //# sourceMappingURL=reactive.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"reactive.cjs","names":[],"sources":["../src/reactive.ts"],"sourcesContent":["import { batch, computed, signal } from '@vielzeug/ripple';\n\nimport type { CreateSearchOptions, ScoutIndexOptions, SearchResult, SearchState } from './types';\n\nimport { ScoutDisposedError } from './errors';\nimport { createIndex, type ScoutIndex } from './scout-index';\n\n/**\n * Combined index + reactive search state returned by `createReactiveSearch()`.\n * Exposes the underlying `ScoutIndex` for incremental mutations (`add`, `remove`, `reindex`).\n */\nexport type ReactiveSearch<T> = SearchState<T> & {\n readonly index: ScoutIndex<T>;\n};\n\nconst DEFAULT_DEBOUNCE = 200;\n\n/**\n * Creates a reactive search state backed by a `ScoutIndex`.\n *\n * - Set `state.query.value` to trigger a (debounced) search.\n * - Read `state.results.value` inside an `effect` or `computed` to consume results reactively.\n * - `state.isSearching.value` is `true` while debouncing, `false` otherwise.\n * - Call `state.dispose()` (or `using state = createSearch(...)`) to release subscriptions.\n *\n * @example\n * ```ts\n * const index = createIndex(users, { fields: ['name', 'email'] });\n * const search = createSearch(index, { debounce: 150 });\n *\n * effect(() => {\n * const results = search.results.value;\n * renderList(results.map(r => r.item));\n * });\n *\n * // Wire to an input\n * input.addEventListener('input', e => {\n * search.query.value = e.currentTarget.value;\n * });\n *\n * // Clean up\n * search.dispose();\n * ```\n *\n * `results` also updates when the index is mutated directly via `index.add()` / `.remove()`\n * / `.reindex()` (not just when `query` changes), by subscribing to `index.onMutate()`.\n *\n * @param index - A `ScoutIndex` built with `createIndex()`.\n * @param options.debounce - Milliseconds to wait before committing query changes. Default: `200`.\n * @param options.limit - Override the index-level result limit.\n * @param options.minQueryLength - Override the index-level minimum query length.\n * @param options.threshold - Override the index-level score threshold.\n */\nexport function createSearch<T>(index: ScoutIndex<T>, options: CreateSearchOptions = {}): SearchState<T> {\n const { debounce: debounceMs = DEFAULT_DEBOUNCE, limit, minQueryLength, threshold } = options;\n\n const query = signal<string>('', { name: 'scout:query' });\n const committedQuery = signal<string>('', { name: 'scout:committedQuery' });\n const indexVersion = signal(0, { name: 'scout:indexVersion' });\n\n const unsubscribeMutations = index.onMutate(() => {\n indexVersion.value++;\n });\n\n const isSearching = computed(() => query.value !== committedQuery.value, { name: 'scout:isSearching' });\n\n const results = computed<SearchResult<T>[]>(\n () => {\n // Reading .value establishes a dependency so index mutations trigger a recompute.\n void indexVersion.value;\n\n return index.search(committedQuery.value, { limit, minQueryLength, threshold });\n },\n { name: 'scout:results' },\n );\n\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n function cancelTimer(): void {\n if (timer !== null) {\n clearTimeout(timer);\n timer = null;\n }\n }\n\n const subscription = query.subscribe(() => {\n const q = query.peek();\n\n cancelTimer();\n\n if (q === committedQuery.peek()) return;\n\n if (debounceMs === 0) {\n committedQuery.value = q;\n\n return;\n }\n\n timer = setTimeout(() => {\n committedQuery.value = q;\n timer = null;\n }, debounceMs);\n });\n\n function clear(): void {\n if (isDisposed) throw new ScoutDisposedError('SearchState.clear() called after dispose()');\n\n cancelTimer();\n\n batch(() => {\n query.value = '';\n committedQuery.value = '';\n });\n }\n\n let isDisposed = false;\n const ac = new AbortController();\n\n function dispose(): void {\n isDisposed = true;\n ac.abort();\n cancelTimer();\n subscription();\n unsubscribeMutations();\n }\n\n return {\n clear,\n get disposalSignal(): AbortSignal {\n return ac.signal;\n },\n dispose,\n get disposed(): boolean {\n return isDisposed;\n },\n isSearching,\n query,\n results,\n [Symbol.dispose](): void {\n dispose();\n },\n };\n}\n\n/**\n * Creates a `ScoutIndex` and a reactive search state in one call — the shorthand\n * for the common pattern of `createIndex` + `createSearch`.\n *\n * The returned `ReactiveSearch` exposes the underlying index via `.index` for\n * incremental mutations (`add`, `remove`, `reindex`) after construction.\n *\n * @example\n * ```ts\n * const search = createReactiveSearch(users, {\n * fields: [{ field: 'name', weight: 2 }, 'email'],\n * debounce: 150,\n * });\n *\n * effect(() => renderList(search.results.value.map(r => r.item)));\n *\n * // Wire to an input\n * input.addEventListener('input', e => { search.query.value = e.currentTarget.value; });\n *\n * // Add a new item at runtime\n * search.index.add(newUser);\n * ```\n *\n * @param items - Initial corpus to index.\n * @param options - Index options (`fields`, `limit`, `minQueryLength`, `threshold`) plus optional `debounce`.\n */\nexport function createReactiveSearch<T>(\n items: T[],\n options: ScoutIndexOptions<T> & Pick<CreateSearchOptions, 'debounce'>,\n): ReactiveSearch<T> {\n const index = createIndex(items, {\n fields: options.fields,\n limit: options.limit,\n minQueryLength: options.minQueryLength,\n threshold: options.threshold,\n });\n const state = createSearch(index, { debounce: options.debounce });\n\n return Object.assign(Object.create(state), { index }) as ReactiveSearch<T>;\n}\n"],"mappings":"iGAeA,IAAM,EAAmB,IAsCzB,SAAgB,EAAgB,EAAsB,EAA+B,CAAC,EAAmB,CACvG,GAAM,CAAE,SAAU,EAAa,EAAkB,QAAO,iBAAgB,aAAc,EAEhF,GAAA,EAAQ,EAAA,OAAA,CAAe,GAAI,CAAE,KAAM,aAAc,CAAC,EAClD,GAAA,EAAiB,EAAA,OAAA,CAAe,GAAI,CAAE,KAAM,sBAAuB,CAAC,EACpE,GAAA,EAAe,EAAA,OAAA,CAAO,EAAG,CAAE,KAAM,oBAAqB,CAAC,EAEvD,EAAuB,EAAM,aAAe,CAChD,EAAa,OACf,CAAC,EAEK,GAAA,EAAc,EAAA,SAAA,KAAe,EAAM,QAAU,EAAe,MAAO,CAAE,KAAM,mBAAoB,CAAC,EAEhG,GAAA,EAAU,EAAA,SAAA,MAGZ,EAAkB,MAEX,EAAM,OAAO,EAAe,MAAO,CAAE,QAAO,iBAAgB,WAAU,CAAC,GAEhF,CAAE,KAAM,eAAgB,CAC1B,EAEI,EAA8C,KAElD,SAAS,GAAoB,CACvB,IAAU,OACZ,aAAa,CAAK,EAClB,EAAQ,KAEZ,CAEA,IAAM,EAAe,EAAM,cAAgB,CACzC,IAAM,EAAI,EAAM,KAAK,EAErB,KAAY,EAER,IAAM,EAAe,KAAK,EAE9B,IAAI,IAAe,EAAG,CACpB,EAAe,MAAQ,EAEvB,MACF,CAEA,EAAQ,eAAiB,CACvB,EAAe,MAAQ,EACvB,EAAQ,IACV,EAAG,CAAU,CALb,CAMF,CAAC,EAED,SAAS,GAAc,CACrB,GAAI,EAAY,MAAM,IAAI,EAAA,mBAAmB,4CAA4C,EAEzF,EAAY,GAEZ,EAAA,EAAA,MAAA,KAAY,CACV,EAAM,MAAQ,GACd,EAAe,MAAQ,EACzB,CAAC,CACH,CAEA,IAAI,EAAa,GACX,EAAK,IAAI,gBAEf,SAAS,GAAgB,CACvB,EAAa,GACb,EAAG,MAAM,EACT,EAAY,EACZ,EAAa,EACb,EAAqB,CACvB,CAEA,MAAO,CACL,QACA,IAAI,gBAA8B,CAChC,OAAO,EAAG,MACZ,EACA,UACA,IAAI,UAAoB,CACtB,OAAO,CACT,EACA,cACA,QACA,UACA,CAAC,OAAO,UAAiB,CACvB,EAAQ,CACV,CACF,CACF,CA4BA,SAAgB,EACd,EACA,EACmB,CACnB,IAAM,EAAQ,EAAA,YAAY,EAAO,CAC/B,OAAQ,EAAQ,OAChB,MAAO,EAAQ,MACf,eAAgB,EAAQ,eACxB,UAAW,EAAQ,SACrB,CAAC,EACK,EAAQ,EAAa,EAAO,CAAE,SAAU,EAAQ,QAAS,CAAC,EAEhE,OAAO,OAAO,OAAO,OAAO,OAAO,CAAK,EAAG,CAAE,OAAM,CAAC,CACtD"}
1
+ {"version":3,"file":"reactive.cjs","names":[],"sources":["../src/reactive.ts"],"sourcesContent":["import { batch, computed, signal } from '@vielzeug/ripple';\n\nimport type { CreateSearchOptions, ScoutIndexOptions, SearchResult, SearchState } from './types';\n\nimport { ScoutConfigurationError, ScoutDisposedError } from './errors';\nimport { createIndex, type ScoutIndex } from './scout-index';\n\n/**\n * Combined index + reactive search state returned by `createReactiveSearch()`.\n * Exposes the underlying `ScoutIndex` for incremental mutations (`add`, `remove`, `reindex`).\n */\nexport type ReactiveSearch<T> = SearchState<T> & {\n readonly index: ScoutIndex<T>;\n};\n\nconst DEFAULT_DEBOUNCE = 200;\n\n/**\n * Creates a reactive search state backed by a `ScoutIndex`.\n *\n * - Set `state.query.value` to trigger a (debounced) search.\n * - Read `state.results.value` inside an `effect` or `computed` to consume results reactively.\n * - `state.isSearching.value` is `true` while debouncing, `false` otherwise.\n * - Call `state.dispose()` (or `using state = createSearch(...)`) to release subscriptions.\n *\n * @example\n * ```ts\n * const index = createIndex(users, { fields: ['name', 'email'] });\n * const search = createSearch(index, { debounce: 150 });\n *\n * effect(() => {\n * const results = search.results.value;\n * renderList(results.map(r => r.item));\n * });\n *\n * // Wire to an input\n * input.addEventListener('input', e => {\n * search.query.value = e.currentTarget.value;\n * });\n *\n * // Clean up\n * search.dispose();\n * ```\n *\n * `results` also updates when the index is mutated directly via `index.add()` / `.remove()`\n * / `.reindex()` / `.setItems()` (not just when `query` changes), by subscribing to `index.onMutate()`.\n *\n * @param index - A `ScoutIndex` built with `createIndex()`.\n * @param options.debounce - Milliseconds to wait before committing query changes. Default: `200`.\n * @param options.limit - Override the index-level result limit.\n * @param options.minQueryLength - Override the index-level minimum query length.\n * @param options.threshold - Override the index-level score threshold.\n */\nexport function createSearch<T>(index: ScoutIndex<T>, options: CreateSearchOptions = {}): SearchState<T> {\n const { debounce: debounceMs = DEFAULT_DEBOUNCE, limit, minQueryLength, threshold } = options;\n\n if (!Number.isFinite(debounceMs) || !Number.isInteger(debounceMs) || debounceMs < 0) {\n throw new ScoutConfigurationError('debounce must be a finite non-negative integer.');\n }\n\n const query = signal<string>('', { name: 'scout:query' });\n const committedQuery = signal<string>('', { name: 'scout:committedQuery' });\n const indexVersion = signal(0, { name: 'scout:indexVersion' });\n\n const unsubscribeMutations = index.onMutate(() => {\n indexVersion.value++;\n });\n\n const isSearching = computed(() => query.value !== committedQuery.value, { name: 'scout:isSearching' });\n\n const results = computed<SearchResult<T>[]>(\n () => {\n // Reading .value establishes a dependency so index mutations trigger a recompute.\n void indexVersion.value;\n\n return index.search(committedQuery.value, { limit, minQueryLength, threshold });\n },\n { name: 'scout:results' },\n );\n\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n function cancelTimer(): void {\n if (timer !== null) {\n clearTimeout(timer);\n timer = null;\n }\n }\n\n const subscription = query.subscribe(() => {\n const q = query.peek();\n\n cancelTimer();\n\n if (q === committedQuery.peek()) return;\n\n if (debounceMs === 0) {\n committedQuery.value = q;\n\n return;\n }\n\n timer = setTimeout(() => {\n committedQuery.value = q;\n timer = null;\n }, debounceMs);\n });\n\n function clear(): void {\n if (isDisposed) throw new ScoutDisposedError('SearchState.clear() called after dispose()');\n\n cancelTimer();\n\n batch(() => {\n query.value = '';\n committedQuery.value = '';\n });\n }\n\n let isDisposed = false;\n const ac = new AbortController();\n\n function dispose(): void {\n isDisposed = true;\n ac.abort();\n cancelTimer();\n subscription();\n unsubscribeMutations();\n }\n\n return {\n clear,\n get disposalSignal(): AbortSignal {\n return ac.signal;\n },\n dispose,\n get disposed(): boolean {\n return isDisposed;\n },\n isSearching,\n query,\n results,\n [Symbol.dispose](): void {\n dispose();\n },\n };\n}\n\n/**\n * Creates a `ScoutIndex` and a reactive search state in one call — the shorthand\n * for the common pattern of `createIndex` + `createSearch`.\n *\n * The returned `ReactiveSearch` exposes the underlying index via `.index` for\n * incremental mutations (`add`, `remove`, `reindex`, `setItems`) after construction.\n *\n * @example\n * ```ts\n * const search = createReactiveSearch(users, {\n * fields: [{ field: 'name', weight: 2 }, 'email'],\n * debounce: 150,\n * });\n *\n * effect(() => renderList(search.results.value.map(r => r.item)));\n *\n * // Wire to an input\n * input.addEventListener('input', e => { search.query.value = e.currentTarget.value; });\n *\n * // Add a new item at runtime\n * search.index.add(newUser);\n * ```\n *\n * @param items - Initial corpus to index.\n * @param options - Index options (`fields`, `limit`, `minQueryLength`, `threshold`) plus optional `debounce`.\n */\nexport function createReactiveSearch<T>(\n items: T[],\n options: ScoutIndexOptions<T> & Pick<CreateSearchOptions, 'debounce'>,\n): ReactiveSearch<T> {\n const index = createIndex(items, {\n fields: options.fields,\n limit: options.limit,\n minQueryLength: options.minQueryLength,\n threshold: options.threshold,\n });\n const state = createSearch(index, { debounce: options.debounce });\n\n return Object.assign(Object.create(state), { index }) as ReactiveSearch<T>;\n}\n"],"mappings":"iGAeA,IAAM,EAAmB,IAsCzB,SAAgB,EAAgB,EAAsB,EAA+B,CAAC,EAAmB,CACvG,GAAM,CAAE,SAAU,EAAa,EAAkB,QAAO,iBAAgB,aAAc,EAEtF,GAAI,CAAC,OAAO,SAAS,CAAU,GAAK,CAAC,OAAO,UAAU,CAAU,GAAK,EAAa,EAChF,MAAM,IAAI,EAAA,wBAAwB,iDAAiD,EAGrF,IAAM,GAAA,EAAQ,EAAA,OAAA,CAAe,GAAI,CAAE,KAAM,aAAc,CAAC,EAClD,GAAA,EAAiB,EAAA,OAAA,CAAe,GAAI,CAAE,KAAM,sBAAuB,CAAC,EACpE,GAAA,EAAe,EAAA,OAAA,CAAO,EAAG,CAAE,KAAM,oBAAqB,CAAC,EAEvD,EAAuB,EAAM,aAAe,CAChD,EAAa,OACf,CAAC,EAEK,GAAA,EAAc,EAAA,SAAA,KAAe,EAAM,QAAU,EAAe,MAAO,CAAE,KAAM,mBAAoB,CAAC,EAEhG,GAAA,EAAU,EAAA,SAAA,MAGZ,EAAkB,MAEX,EAAM,OAAO,EAAe,MAAO,CAAE,QAAO,iBAAgB,WAAU,CAAC,GAEhF,CAAE,KAAM,eAAgB,CAC1B,EAEI,EAA8C,KAElD,SAAS,GAAoB,CACvB,IAAU,OACZ,aAAa,CAAK,EAClB,EAAQ,KAEZ,CAEA,IAAM,EAAe,EAAM,cAAgB,CACzC,IAAM,EAAI,EAAM,KAAK,EAErB,KAAY,EAER,IAAM,EAAe,KAAK,EAE9B,IAAI,IAAe,EAAG,CACpB,EAAe,MAAQ,EAEvB,MACF,CAEA,EAAQ,eAAiB,CACvB,EAAe,MAAQ,EACvB,EAAQ,IACV,EAAG,CAAU,CALb,CAMF,CAAC,EAED,SAAS,GAAc,CACrB,GAAI,EAAY,MAAM,IAAI,EAAA,mBAAmB,4CAA4C,EAEzF,EAAY,GAEZ,EAAA,EAAA,MAAA,KAAY,CACV,EAAM,MAAQ,GACd,EAAe,MAAQ,EACzB,CAAC,CACH,CAEA,IAAI,EAAa,GACX,EAAK,IAAI,gBAEf,SAAS,GAAgB,CACvB,EAAa,GACb,EAAG,MAAM,EACT,EAAY,EACZ,EAAa,EACb,EAAqB,CACvB,CAEA,MAAO,CACL,QACA,IAAI,gBAA8B,CAChC,OAAO,EAAG,MACZ,EACA,UACA,IAAI,UAAoB,CACtB,OAAO,CACT,EACA,cACA,QACA,UACA,CAAC,OAAO,UAAiB,CACvB,EAAQ,CACV,CACF,CACF,CA4BA,SAAgB,EACd,EACA,EACmB,CACnB,IAAM,EAAQ,EAAA,YAAY,EAAO,CAC/B,OAAQ,EAAQ,OAChB,MAAO,EAAQ,MACf,eAAgB,EAAQ,eACxB,UAAW,EAAQ,SACrB,CAAC,EACK,EAAQ,EAAa,EAAO,CAAE,SAAU,EAAQ,QAAS,CAAC,EAEhE,OAAO,OAAO,OAAO,OAAO,OAAO,CAAK,EAAG,CAAE,OAAM,CAAC,CACtD"}
@@ -35,7 +35,7 @@ export type ReactiveSearch<T> = SearchState<T> & {
35
35
  * ```
36
36
  *
37
37
  * `results` also updates when the index is mutated directly via `index.add()` / `.remove()`
38
- * / `.reindex()` (not just when `query` changes), by subscribing to `index.onMutate()`.
38
+ * / `.reindex()` / `.setItems()` (not just when `query` changes), by subscribing to `index.onMutate()`.
39
39
  *
40
40
  * @param index - A `ScoutIndex` built with `createIndex()`.
41
41
  * @param options.debounce - Milliseconds to wait before committing query changes. Default: `200`.
@@ -49,7 +49,7 @@ export declare function createSearch<T>(index: ScoutIndex<T>, options?: CreateSe
49
49
  * for the common pattern of `createIndex` + `createSearch`.
50
50
  *
51
51
  * The returned `ReactiveSearch` exposes the underlying index via `.index` for
52
- * incremental mutations (`add`, `remove`, `reindex`) after construction.
52
+ * incremental mutations (`add`, `remove`, `reindex`, `setItems`) after construction.
53
53
  *
54
54
  * @example
55
55
  * ```ts
@@ -1 +1 @@
1
- {"version":3,"file":"reactive.d.ts","sourceRoot":"","sources":["../src/reactive.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAAE,iBAAiB,EAAgB,WAAW,EAAE,MAAM,SAAS,CAAC;AAGjG,OAAO,EAAe,KAAK,UAAU,EAAE,MAAM,eAAe,CAAC;AAE7D;;;GAGG;AACH,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI,WAAW,CAAC,CAAC,CAAC,GAAG;IAC/C,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC;CAC/B,CAAC;AAIF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,OAAO,GAAE,mBAAwB,GAAG,WAAW,CAAC,CAAC,CAAC,CAyFvG;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,EACpC,KAAK,EAAE,CAAC,EAAE,EACV,OAAO,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,mBAAmB,EAAE,UAAU,CAAC,GACpE,cAAc,CAAC,CAAC,CAAC,CAUnB"}
1
+ {"version":3,"file":"reactive.d.ts","sourceRoot":"","sources":["../src/reactive.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,mBAAmB,EAAE,iBAAiB,EAAgB,WAAW,EAAE,MAAM,SAAS,CAAC;AAGjG,OAAO,EAAe,KAAK,UAAU,EAAE,MAAM,eAAe,CAAC;AAE7D;;;GAGG;AACH,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI,WAAW,CAAC,CAAC,CAAC,GAAG;IAC/C,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC;CAC/B,CAAC;AAIF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAgB,YAAY,CAAC,CAAC,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,OAAO,GAAE,mBAAwB,GAAG,WAAW,CAAC,CAAC,CAAC,CA6FvG;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,oBAAoB,CAAC,CAAC,EACpC,KAAK,EAAE,CAAC,EAAE,EACV,OAAO,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,mBAAmB,EAAE,UAAU,CAAC,GACpE,cAAc,CAAC,CAAC,CAAC,CAUnB"}
package/dist/reactive.js CHANGED
@@ -1,68 +1,70 @@
1
- import { ScoutDisposedError as e } from "./errors.js";
2
- import { createIndex as t } from "./scout-index.js";
3
- import { batch as n, computed as r, signal as i } from "@vielzeug/ripple";
1
+ import { ScoutConfigurationError as e, ScoutDisposedError as t } from "./errors.js";
2
+ import { createIndex as n } from "./scout-index.js";
3
+ import { batch as r, computed as i, signal as a } from "@vielzeug/ripple";
4
4
  //#region src/reactive.ts
5
- var a = 200;
6
- function o(t, o = {}) {
7
- let { debounce: s = a, limit: c, minQueryLength: l, threshold: u } = o, d = i("", { name: "scout:query" }), f = i("", { name: "scout:committedQuery" }), p = i(0, { name: "scout:indexVersion" }), m = t.onMutate(() => {
8
- p.value++;
9
- }), h = r(() => d.value !== f.value, { name: "scout:isSearching" }), g = r(() => (p.value, t.search(f.value, {
10
- limit: c,
11
- minQueryLength: l,
12
- threshold: u
13
- })), { name: "scout:results" }), _ = null;
14
- function v() {
15
- _ !== null && (clearTimeout(_), _ = null);
5
+ var o = 200;
6
+ function s(n, s = {}) {
7
+ let { debounce: c = o, limit: l, minQueryLength: u, threshold: d } = s;
8
+ if (!Number.isFinite(c) || !Number.isInteger(c) || c < 0) throw new e("debounce must be a finite non-negative integer.");
9
+ let f = a("", { name: "scout:query" }), p = a("", { name: "scout:committedQuery" }), m = a(0, { name: "scout:indexVersion" }), h = n.onMutate(() => {
10
+ m.value++;
11
+ }), g = i(() => f.value !== p.value, { name: "scout:isSearching" }), _ = i(() => (m.value, n.search(p.value, {
12
+ limit: l,
13
+ minQueryLength: u,
14
+ threshold: d
15
+ })), { name: "scout:results" }), v = null;
16
+ function y() {
17
+ v !== null && (clearTimeout(v), v = null);
16
18
  }
17
- let y = d.subscribe(() => {
18
- let e = d.peek();
19
- if (v(), e !== f.peek()) {
20
- if (s === 0) {
21
- f.value = e;
19
+ let b = f.subscribe(() => {
20
+ let e = f.peek();
21
+ if (y(), e !== p.peek()) {
22
+ if (c === 0) {
23
+ p.value = e;
22
24
  return;
23
25
  }
24
- _ = setTimeout(() => {
25
- f.value = e, _ = null;
26
- }, s);
26
+ v = setTimeout(() => {
27
+ p.value = e, v = null;
28
+ }, c);
27
29
  }
28
30
  });
29
- function b() {
30
- if (x) throw new e("SearchState.clear() called after dispose()");
31
- v(), n(() => {
32
- d.value = "", f.value = "";
31
+ function x() {
32
+ if (S) throw new t("SearchState.clear() called after dispose()");
33
+ y(), r(() => {
34
+ f.value = "", p.value = "";
33
35
  });
34
36
  }
35
- let x = !1, S = new AbortController();
36
- function C() {
37
- x = !0, S.abort(), v(), y(), m();
37
+ let S = !1, C = new AbortController();
38
+ function w() {
39
+ S = !0, C.abort(), y(), b(), h();
38
40
  }
39
41
  return {
40
- clear: b,
42
+ clear: x,
41
43
  get disposalSignal() {
42
- return S.signal;
44
+ return C.signal;
43
45
  },
44
- dispose: C,
46
+ dispose: w,
45
47
  get disposed() {
46
- return x;
48
+ return S;
47
49
  },
48
- isSearching: h,
49
- query: d,
50
- results: g,
50
+ isSearching: g,
51
+ query: f,
52
+ results: _,
51
53
  [Symbol.dispose]() {
52
- C();
54
+ w();
53
55
  }
54
56
  };
55
57
  }
56
- function s(e, n) {
57
- let r = t(e, {
58
- fields: n.fields,
59
- limit: n.limit,
60
- minQueryLength: n.minQueryLength,
61
- threshold: n.threshold
62
- }), i = o(r, { debounce: n.debounce });
58
+ function c(e, t) {
59
+ let r = n(e, {
60
+ fields: t.fields,
61
+ limit: t.limit,
62
+ minQueryLength: t.minQueryLength,
63
+ threshold: t.threshold
64
+ }), i = s(r, { debounce: t.debounce });
63
65
  return Object.assign(Object.create(i), { index: r });
64
66
  }
65
67
  //#endregion
66
- export { s as createReactiveSearch, o as createSearch };
68
+ export { c as createReactiveSearch, s as createSearch };
67
69
 
68
70
  //# sourceMappingURL=reactive.js.map