@vielzeug/scout 3.0.0 → 26.10.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 (57) hide show
  1. package/dist/adapters.cjs +1 -1
  2. package/dist/adapters.cjs.map +1 -1
  3. package/dist/adapters.d.ts +2 -1
  4. package/dist/adapters.d.ts.map +1 -1
  5. package/dist/adapters.js +1 -12
  6. package/dist/adapters.js.map +1 -1
  7. package/dist/entries.cjs +2 -0
  8. package/dist/entries.cjs.map +1 -0
  9. package/dist/entries.d.ts +45 -0
  10. package/dist/entries.d.ts.map +1 -0
  11. package/dist/entries.js +2 -0
  12. package/dist/entries.js.map +1 -0
  13. package/dist/errors.cjs +1 -1
  14. package/dist/errors.cjs.map +1 -1
  15. package/dist/errors.d.ts +1 -1
  16. package/dist/errors.d.ts.map +1 -1
  17. package/dist/errors.js +1 -9
  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.js +1 -45
  22. package/dist/highlight.js.map +1 -1
  23. package/dist/index.cjs +1 -1
  24. package/dist/index.d.ts +2 -1
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +1 -7
  27. package/dist/reactive.cjs +1 -1
  28. package/dist/reactive.cjs.map +1 -1
  29. package/dist/reactive.d.ts.map +1 -1
  30. package/dist/reactive.js +1 -114
  31. package/dist/reactive.js.map +1 -1
  32. package/dist/scout-index.cjs +1 -1
  33. package/dist/scout-index.cjs.map +1 -1
  34. package/dist/scout-index.d.ts +1 -1
  35. package/dist/scout-index.js +1 -191
  36. package/dist/scout-index.js.map +1 -1
  37. package/dist/scout.cjs +1 -1
  38. package/dist/scout.cjs.map +1 -1
  39. package/dist/scout.iife.js +1 -1
  40. package/dist/scout.iife.js.map +1 -1
  41. package/dist/scout.js +1 -1
  42. package/dist/scout.js.map +1 -1
  43. package/dist/segment.cjs +1 -1
  44. package/dist/segment.cjs.map +1 -1
  45. package/dist/segment.js +1 -12
  46. package/dist/segment.js.map +1 -1
  47. package/dist/tokenize.cjs +1 -1
  48. package/dist/tokenize.cjs.map +1 -1
  49. package/dist/tokenize.js +1 -10
  50. package/dist/tokenize.js.map +1 -1
  51. package/dist/trigram.cjs +1 -1
  52. package/dist/trigram.cjs.map +1 -1
  53. package/dist/trigram.js +1 -15
  54. package/dist/trigram.js.map +1 -1
  55. package/dist/types.d.ts +2 -4
  56. package/dist/types.d.ts.map +1 -1
  57. package/package.json +8 -5
package/dist/adapters.cjs CHANGED
@@ -1,2 +1,2 @@
1
- function e(e,t){let n,r=-1,i=new Set;return(a,o)=>((o!==n||e.revision!==r)&&(n=o,r=e.revision,i=new Set(e.search(o,t).map(e=>e.item))),i.has(a))}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
+ function toSearchMatcher(e,t){let n,r=-1,i=new Set;return(a,o)=>((o!==n||e.revision!==r)&&(n=o,r=e.revision,i=new Set(e.search(o,t).map(e=>e.item))),i.has(a))}function toFilterPredicate(e,t,n){let r=new Set(e.search(t,n).map(e=>e.item));return e=>r.has(e)}exports.toFilterPredicate=toFilterPredicate,exports.toSearchMatcher=toSearchMatcher;
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 lastRevision = -1;\n let matches = new Set<T>();\n\n return (item, query) => {\n if (query !== lastQuery || index.revision !== lastRevision) {\n lastQuery = query;\n lastRevision = index.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":"AAOA,SAAgB,EACd,EACA,EACqC,CACrC,IAAI,EACA,EAAe,GACf,EAAU,IAAI,IAElB,OAAQ,EAAM,MACR,IAAU,GAAa,EAAM,WAAa,KAC5C,EAAY,EACZ,EAAe,EAAM,SACrB,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\n/**\n * Adapts a `ScoutIndex` to sourcerer's local-source `filter` callback\n * (`filter?: (item, params) => boolean`, with the query passed as `params`).\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 if (query !== lastQuery || index.revision !== lastRevision) {\n lastQuery = query;\n lastRevision = index.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":"AAQA,SAAgB,gBACd,EACA,EACqC,CACrC,IAAI,EACA,EAAe,GACf,EAAU,IAAI,IAElB,OAAQ,EAAM,MACR,IAAU,GAAa,EAAM,WAAa,KAC5C,EAAY,EACZ,EAAe,EAAM,SACrB,EAAU,IAAI,IAAI,EAAM,OAAO,EAAO,CAAO,CAAC,CAAC,IAAK,GAAW,EAAO,IAAI,CAAC,GAGtE,EAAQ,IAAI,CAAI,EAE3B,CAUA,SAAgB,kBACd,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,7 +1,8 @@
1
1
  import type { ScoutIndex } from './scout-index';
2
2
  import type { SearchConstraints } from './types';
3
3
  /**
4
- * Adapts a `ScoutIndex` to sourcerer's explicit local `match` callback.
4
+ * Adapts a `ScoutIndex` to sourcerer's local-source `filter` callback
5
+ * (`filter?: (item, params) => boolean`, with the query passed as `params`).
5
6
  * Caches one match set per query so local filtering does not repeat index work per item.
6
7
  */
7
8
  export declare function toSearchMatcher<T>(index: ScoutIndex<T>, options?: SearchConstraints): (item: T, query: string) => boolean;
@@ -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,CAcrC;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;AAEjD;;;;GAIG;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,CAcrC;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,2 @@
1
- //#region src/adapters.ts
2
- function e(e, t) {
3
- let n, r = -1, i = /* @__PURE__ */ new Set();
4
- return (a, o) => ((o !== n || e.revision !== r) && (n = o, r = e.revision, i = new Set(e.search(o, t).map((e) => e.item))), i.has(a));
5
- }
6
- function t(e, t, n) {
7
- let r = new Set(e.search(t, n).map((e) => e.item));
8
- return (e) => r.has(e);
9
- }
10
- //#endregion
11
- export { t as toFilterPredicate, e as toSearchMatcher };
12
-
1
+ function toSearchMatcher(e,t){let n,r=-1,i=new Set;return(a,o)=>((o!==n||e.revision!==r)&&(n=o,r=e.revision,i=new Set(e.search(o,t).map(e=>e.item))),i.has(a))}function toFilterPredicate(e,t,n){let r=new Set(e.search(t,n).map(e=>e.item));return e=>r.has(e)}export{toFilterPredicate,toSearchMatcher};
13
2
  //# 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 lastRevision = -1;\n let matches = new Set<T>();\n\n return (item, query) => {\n if (query !== lastQuery || index.revision !== lastRevision) {\n lastQuery = query;\n lastRevision = index.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":";AAOA,SAAgB,EACd,GACA,GACqC;CACrC,IAAI,GACA,IAAe,IACf,oBAAU,IAAI,IAAO;CAEzB,QAAQ,GAAM,QACR,MAAU,KAAa,EAAM,aAAa,OAC5C,IAAY,GACZ,IAAe,EAAM,UACrB,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\n/**\n * Adapts a `ScoutIndex` to sourcerer's local-source `filter` callback\n * (`filter?: (item, params) => boolean`, with the query passed as `params`).\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 if (query !== lastQuery || index.revision !== lastRevision) {\n lastQuery = query;\n lastRevision = index.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":"AAQA,SAAgB,gBACd,EACA,EACqC,CACrC,IAAI,EACA,EAAe,GACf,EAAU,IAAI,IAElB,OAAQ,EAAM,MACR,IAAU,GAAa,EAAM,WAAa,KAC5C,EAAY,EACZ,EAAe,EAAM,SACrB,EAAU,IAAI,IAAI,EAAM,OAAO,EAAO,CAAO,CAAC,CAAC,IAAK,GAAW,EAAO,IAAI,CAAC,GAGtE,EAAQ,IAAI,CAAI,EAE3B,CAUA,SAAgB,kBACd,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"}
@@ -0,0 +1,2 @@
1
+ var e={Exact:0,FuzzyOnly:5,LabelContains:2,LabelStart:1,MetaContains:3,TextContains:4};function rankEntries(t,n,r={}){let i=n.trim().toLowerCase();if(!i)return[...t];let a=r.fuzzyIds??new Set,o=[];for(let n of t){let t=n.label.toLowerCase(),r=t===i?e.Exact:t.startsWith(i)?e.LabelStart:t.includes(i)?e.LabelContains:n.meta.toLowerCase().includes(i)?e.MetaContains:n.text.toLowerCase().includes(i)?e.TextContains:a.has(n.id)?e.FuzzyOnly:null;r!==null&&o.push([r,n])}return o.sort((e,t)=>e[0]-t[0]||e[1].label.localeCompare(t[1].label)||e[1].text.localeCompare(t[1].text)).map(([,e])=>e)}function dedupeEntries(e){let t=new Set;return e.filter(e=>{let n=`${e.label}\u0000${e.text}`.toLowerCase();return!t.has(n)&&(t.add(n),!0)})}function splitPattern(e,t){if(!t)return[{text:e}];let n=[],r=0;for(let i of e.matchAll(t)){if(i[0].length===0)continue;let t=i.index??0;t>r&&n.push({text:e.slice(r,t)}),n.push({matched:!0,text:i[0]}),r=t+i[0].length}return r<e.length&&n.push({text:e.slice(r)}),n.length?n:[{text:e}]}var t=/[.*+?^${}()|[\]\\]/g,escapeRegExp=e=>e.replace(t,`\\$&`);exports.dedupeEntries=dedupeEntries,exports.escapeRegExp=escapeRegExp,exports.rankEntries=rankEntries,exports.splitPattern=splitPattern;
2
+ //# sourceMappingURL=entries.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entries.cjs","names":[],"sources":["../src/entries.ts"],"sourcesContent":["/**\n * Entry ranking and segmentation for searchable reference lists — the presentation half of\n * search: a heterogeneous set of entries (glossary terms, rules, profiles) ordered by how\n * directly they match a typed query, plus segmentation helpers for rendering matched and\n * keyword-linked text.\n */\n\n/** An entry a reference list ranks and renders: identified, labeled, with summary and body text. */\nexport interface RankableEntry {\n id: string;\n /** The entry's title — the primary match surface. */\n label: string;\n /** One-line context shown beside the label. */\n meta: string;\n /** The entry's body text. */\n text: string;\n}\n\nconst Score = {\n Exact: 0,\n FuzzyOnly: 5,\n LabelContains: 2,\n LabelStart: 1,\n MetaContains: 3,\n TextContains: 4,\n} as const;\ntype Score = (typeof Score)[keyof typeof Score];\n\nexport interface RankEntriesOptions {\n /**\n * Entry ids the fuzzy index already matched — offered as the last resort when no field\n * contains the query, so approximate hits still surface.\n */\n fuzzyIds?: ReadonlySet<string>;\n}\n\n/**\n * Orders entries by how directly they match `query`: exact label, then label prefix, label,\n * meta, text, and finally fuzzy-index hits (`fuzzyIds`) as last resort. Entries matching none\n * of these are dropped. Ties break alphabetically by label, then by body text.\n */\nexport function rankEntries<T extends RankableEntry>(\n entries: readonly T[],\n query: string,\n options: RankEntriesOptions = {},\n): T[] {\n const lower = query.trim().toLowerCase();\n if (!lower) return [...entries];\n const fuzzyIds = options.fuzzyIds ?? new Set<string>();\n const scored: [Score, T][] = [];\n for (const entry of entries) {\n const label = entry.label.toLowerCase();\n const score =\n label === lower\n ? Score.Exact\n : label.startsWith(lower)\n ? Score.LabelStart\n : label.includes(lower)\n ? Score.LabelContains\n : entry.meta.toLowerCase().includes(lower)\n ? Score.MetaContains\n : entry.text.toLowerCase().includes(lower)\n ? Score.TextContains\n : fuzzyIds.has(entry.id)\n ? Score.FuzzyOnly\n : null;\n if (score !== null) scored.push([score, entry]);\n }\n return scored\n .sort((a, b) => a[0] - b[0] || a[1].label.localeCompare(b[1].label) || a[1].text.localeCompare(b[1].text))\n .map(([, entry]) => entry);\n}\n\n/** Drops entries whose label and body are identical to an earlier entry (alias rows). */\nexport function dedupeEntries<T extends { label: string; text: string }>(entries: readonly T[]): T[] {\n const seen = new Set<string>();\n return entries.filter((entry) => {\n const key = `${entry.label}\\u0000${entry.text}`.toLowerCase();\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n}\n\n/** One renderable piece of a text: matched a pattern, or plain. */\nexport interface SplitSegment {\n /** Piece matched the pattern and should render emphasized or linked. */\n matched?: boolean;\n text: string;\n}\n\n/** Splits `text` on `pattern` matches, marking the matched pieces; a null pattern keeps it whole. */\nexport function splitPattern(text: string, pattern: RegExp | null): SplitSegment[] {\n if (!pattern) return [{ text }];\n const segments: SplitSegment[] = [];\n let last = 0;\n for (const match of text.matchAll(pattern)) {\n // Zero-length matches (optional-quantifier patterns) carry no text to emphasize.\n if (match[0].length === 0) continue;\n const start = match.index ?? 0;\n if (start > last) segments.push({ text: text.slice(last, start) });\n segments.push({ matched: true, text: match[0] });\n last = start + match[0].length;\n }\n if (last < text.length) segments.push({ text: text.slice(last) });\n return segments.length ? segments : [{ text }];\n}\n\nconst REGEXP_SPECIAL = /[.*+?^${}()|[\\]\\\\]/g;\n\n/** Escapes a literal so it can be embedded in a regular expression. */\nexport const escapeRegExp = (value: string): string => value.replace(REGEXP_SPECIAL, '\\\\$&');\n"],"mappings":"AAkBA,IAAM,EAAQ,CACZ,MAAO,EACP,UAAW,EACX,cAAe,EACf,WAAY,EACZ,aAAc,EACd,aAAc,CAChB,EAgBA,SAAgB,YACd,EACA,EACA,EAA8B,CAAC,EAC1B,CACL,IAAM,EAAQ,EAAM,KAAK,CAAC,CAAC,YAAY,EACvC,GAAI,CAAC,EAAO,MAAO,CAAC,GAAG,CAAO,EAC9B,IAAM,EAAW,EAAQ,UAAY,IAAI,IACnC,EAAuB,CAAC,EAC9B,IAAK,IAAM,KAAS,EAAS,CAC3B,IAAM,EAAQ,EAAM,MAAM,YAAY,EAChC,EACJ,IAAU,EACN,EAAM,MACN,EAAM,WAAW,CAAK,EACpB,EAAM,WACN,EAAM,SAAS,CAAK,EAClB,EAAM,cACN,EAAM,KAAK,YAAY,CAAC,CAAC,SAAS,CAAK,EACrC,EAAM,aACN,EAAM,KAAK,YAAY,CAAC,CAAC,SAAS,CAAK,EACrC,EAAM,aACN,EAAS,IAAI,EAAM,EAAE,EACnB,EAAM,UACN,KACZ,IAAU,MAAM,EAAO,KAAK,CAAC,EAAO,CAAK,CAAC,CAChD,CACA,OAAO,EACJ,MAAM,EAAG,IAAM,EAAE,GAAK,EAAE,IAAM,EAAE,EAAE,CAAC,MAAM,cAAc,EAAE,EAAE,CAAC,KAAK,GAAK,EAAE,EAAE,CAAC,KAAK,cAAc,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CACzG,KAAK,EAAG,KAAW,CAAK,CAC7B,CAGA,SAAgB,cAAyD,EAA4B,CACnG,IAAM,EAAO,IAAI,IACjB,OAAO,EAAQ,OAAQ,GAAU,CAC/B,IAAM,EAAM,GAAG,EAAM,MAAM,QAAQ,EAAM,OAAO,YAAY,EAG5D,MAFA,CAAI,EAAK,IAAI,CAAG,IAChB,EAAK,IAAI,CAAG,EACL,GACT,CAAC,CACH,CAUA,SAAgB,aAAa,EAAc,EAAwC,CACjF,GAAI,CAAC,EAAS,MAAO,CAAC,CAAE,MAAK,CAAC,EAC9B,IAAM,EAA2B,CAAC,EAC9B,EAAO,EACX,IAAK,IAAM,KAAS,EAAK,SAAS,CAAO,EAAG,CAE1C,GAAI,EAAM,EAAE,CAAC,SAAW,EAAG,SAC3B,IAAM,EAAQ,EAAM,OAAS,EACzB,EAAQ,GAAM,EAAS,KAAK,CAAE,KAAM,EAAK,MAAM,EAAM,CAAK,CAAE,CAAC,EACjE,EAAS,KAAK,CAAE,QAAS,GAAM,KAAM,EAAM,EAAG,CAAC,EAC/C,EAAO,EAAQ,EAAM,EAAE,CAAC,MAC1B,CAEA,OADI,EAAO,EAAK,QAAQ,EAAS,KAAK,CAAE,KAAM,EAAK,MAAM,CAAI,CAAE,CAAC,EACzD,EAAS,OAAS,EAAW,CAAC,CAAE,MAAK,CAAC,CAC/C,CAEA,IAAM,EAAiB,sBAGV,aAAgB,GAA0B,EAAM,QAAQ,EAAgB,MAAM"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Entry ranking and segmentation for searchable reference lists — the presentation half of
3
+ * search: a heterogeneous set of entries (glossary terms, rules, profiles) ordered by how
4
+ * directly they match a typed query, plus segmentation helpers for rendering matched and
5
+ * keyword-linked text.
6
+ */
7
+ /** An entry a reference list ranks and renders: identified, labeled, with summary and body text. */
8
+ export interface RankableEntry {
9
+ id: string;
10
+ /** The entry's title — the primary match surface. */
11
+ label: string;
12
+ /** One-line context shown beside the label. */
13
+ meta: string;
14
+ /** The entry's body text. */
15
+ text: string;
16
+ }
17
+ export interface RankEntriesOptions {
18
+ /**
19
+ * Entry ids the fuzzy index already matched — offered as the last resort when no field
20
+ * contains the query, so approximate hits still surface.
21
+ */
22
+ fuzzyIds?: ReadonlySet<string>;
23
+ }
24
+ /**
25
+ * Orders entries by how directly they match `query`: exact label, then label prefix, label,
26
+ * meta, text, and finally fuzzy-index hits (`fuzzyIds`) as last resort. Entries matching none
27
+ * of these are dropped. Ties break alphabetically by label, then by body text.
28
+ */
29
+ export declare function rankEntries<T extends RankableEntry>(entries: readonly T[], query: string, options?: RankEntriesOptions): T[];
30
+ /** Drops entries whose label and body are identical to an earlier entry (alias rows). */
31
+ export declare function dedupeEntries<T extends {
32
+ label: string;
33
+ text: string;
34
+ }>(entries: readonly T[]): T[];
35
+ /** One renderable piece of a text: matched a pattern, or plain. */
36
+ export interface SplitSegment {
37
+ /** Piece matched the pattern and should render emphasized or linked. */
38
+ matched?: boolean;
39
+ text: string;
40
+ }
41
+ /** Splits `text` on `pattern` matches, marking the matched pieces; a null pattern keeps it whole. */
42
+ export declare function splitPattern(text: string, pattern: RegExp | null): SplitSegment[];
43
+ /** Escapes a literal so it can be embedded in a regular expression. */
44
+ export declare const escapeRegExp: (value: string) => string;
45
+ //# sourceMappingURL=entries.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entries.d.ts","sourceRoot":"","sources":["../src/entries.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,oGAAoG;AACpG,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,qDAAqD;IACrD,KAAK,EAAE,MAAM,CAAC;IACd,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAC;IACb,6BAA6B;IAC7B,IAAI,EAAE,MAAM,CAAC;CACd;AAYD,MAAM,WAAW,kBAAkB;IACjC;;;OAGG;IACH,QAAQ,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;CAChC;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,CAAC,SAAS,aAAa,EACjD,OAAO,EAAE,SAAS,CAAC,EAAE,EACrB,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,kBAAuB,GAC/B,CAAC,EAAE,CA0BL;AAED,yFAAyF;AACzF,wBAAgB,aAAa,CAAC,CAAC,SAAS;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,OAAO,EAAE,SAAS,CAAC,EAAE,GAAG,CAAC,EAAE,CAQnG;AAED,mEAAmE;AACnE,MAAM,WAAW,YAAY;IAC3B,wEAAwE;IACxE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,qGAAqG;AACrG,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,YAAY,EAAE,CAcjF;AAID,uEAAuE;AACvE,eAAO,MAAM,YAAY,GAAI,OAAO,MAAM,KAAG,MAA+C,CAAC"}
@@ -0,0 +1,2 @@
1
+ var e={Exact:0,FuzzyOnly:5,LabelContains:2,LabelStart:1,MetaContains:3,TextContains:4};function rankEntries(t,n,r={}){let i=n.trim().toLowerCase();if(!i)return[...t];let a=r.fuzzyIds??new Set,o=[];for(let n of t){let t=n.label.toLowerCase(),r=t===i?e.Exact:t.startsWith(i)?e.LabelStart:t.includes(i)?e.LabelContains:n.meta.toLowerCase().includes(i)?e.MetaContains:n.text.toLowerCase().includes(i)?e.TextContains:a.has(n.id)?e.FuzzyOnly:null;r!==null&&o.push([r,n])}return o.sort((e,t)=>e[0]-t[0]||e[1].label.localeCompare(t[1].label)||e[1].text.localeCompare(t[1].text)).map(([,e])=>e)}function dedupeEntries(e){let t=new Set;return e.filter(e=>{let n=`${e.label}\u0000${e.text}`.toLowerCase();return!t.has(n)&&(t.add(n),!0)})}function splitPattern(e,t){if(!t)return[{text:e}];let n=[],r=0;for(let i of e.matchAll(t)){if(i[0].length===0)continue;let t=i.index??0;t>r&&n.push({text:e.slice(r,t)}),n.push({matched:!0,text:i[0]}),r=t+i[0].length}return r<e.length&&n.push({text:e.slice(r)}),n.length?n:[{text:e}]}var t=/[.*+?^${}()|[\]\\]/g,escapeRegExp=e=>e.replace(t,`\\$&`);export{dedupeEntries,escapeRegExp,rankEntries,splitPattern};
2
+ //# sourceMappingURL=entries.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"entries.js","names":[],"sources":["../src/entries.ts"],"sourcesContent":["/**\n * Entry ranking and segmentation for searchable reference lists — the presentation half of\n * search: a heterogeneous set of entries (glossary terms, rules, profiles) ordered by how\n * directly they match a typed query, plus segmentation helpers for rendering matched and\n * keyword-linked text.\n */\n\n/** An entry a reference list ranks and renders: identified, labeled, with summary and body text. */\nexport interface RankableEntry {\n id: string;\n /** The entry's title — the primary match surface. */\n label: string;\n /** One-line context shown beside the label. */\n meta: string;\n /** The entry's body text. */\n text: string;\n}\n\nconst Score = {\n Exact: 0,\n FuzzyOnly: 5,\n LabelContains: 2,\n LabelStart: 1,\n MetaContains: 3,\n TextContains: 4,\n} as const;\ntype Score = (typeof Score)[keyof typeof Score];\n\nexport interface RankEntriesOptions {\n /**\n * Entry ids the fuzzy index already matched — offered as the last resort when no field\n * contains the query, so approximate hits still surface.\n */\n fuzzyIds?: ReadonlySet<string>;\n}\n\n/**\n * Orders entries by how directly they match `query`: exact label, then label prefix, label,\n * meta, text, and finally fuzzy-index hits (`fuzzyIds`) as last resort. Entries matching none\n * of these are dropped. Ties break alphabetically by label, then by body text.\n */\nexport function rankEntries<T extends RankableEntry>(\n entries: readonly T[],\n query: string,\n options: RankEntriesOptions = {},\n): T[] {\n const lower = query.trim().toLowerCase();\n if (!lower) return [...entries];\n const fuzzyIds = options.fuzzyIds ?? new Set<string>();\n const scored: [Score, T][] = [];\n for (const entry of entries) {\n const label = entry.label.toLowerCase();\n const score =\n label === lower\n ? Score.Exact\n : label.startsWith(lower)\n ? Score.LabelStart\n : label.includes(lower)\n ? Score.LabelContains\n : entry.meta.toLowerCase().includes(lower)\n ? Score.MetaContains\n : entry.text.toLowerCase().includes(lower)\n ? Score.TextContains\n : fuzzyIds.has(entry.id)\n ? Score.FuzzyOnly\n : null;\n if (score !== null) scored.push([score, entry]);\n }\n return scored\n .sort((a, b) => a[0] - b[0] || a[1].label.localeCompare(b[1].label) || a[1].text.localeCompare(b[1].text))\n .map(([, entry]) => entry);\n}\n\n/** Drops entries whose label and body are identical to an earlier entry (alias rows). */\nexport function dedupeEntries<T extends { label: string; text: string }>(entries: readonly T[]): T[] {\n const seen = new Set<string>();\n return entries.filter((entry) => {\n const key = `${entry.label}\\u0000${entry.text}`.toLowerCase();\n if (seen.has(key)) return false;\n seen.add(key);\n return true;\n });\n}\n\n/** One renderable piece of a text: matched a pattern, or plain. */\nexport interface SplitSegment {\n /** Piece matched the pattern and should render emphasized or linked. */\n matched?: boolean;\n text: string;\n}\n\n/** Splits `text` on `pattern` matches, marking the matched pieces; a null pattern keeps it whole. */\nexport function splitPattern(text: string, pattern: RegExp | null): SplitSegment[] {\n if (!pattern) return [{ text }];\n const segments: SplitSegment[] = [];\n let last = 0;\n for (const match of text.matchAll(pattern)) {\n // Zero-length matches (optional-quantifier patterns) carry no text to emphasize.\n if (match[0].length === 0) continue;\n const start = match.index ?? 0;\n if (start > last) segments.push({ text: text.slice(last, start) });\n segments.push({ matched: true, text: match[0] });\n last = start + match[0].length;\n }\n if (last < text.length) segments.push({ text: text.slice(last) });\n return segments.length ? segments : [{ text }];\n}\n\nconst REGEXP_SPECIAL = /[.*+?^${}()|[\\]\\\\]/g;\n\n/** Escapes a literal so it can be embedded in a regular expression. */\nexport const escapeRegExp = (value: string): string => value.replace(REGEXP_SPECIAL, '\\\\$&');\n"],"mappings":"AAkBA,IAAM,EAAQ,CACZ,MAAO,EACP,UAAW,EACX,cAAe,EACf,WAAY,EACZ,aAAc,EACd,aAAc,CAChB,EAgBA,SAAgB,YACd,EACA,EACA,EAA8B,CAAC,EAC1B,CACL,IAAM,EAAQ,EAAM,KAAK,CAAC,CAAC,YAAY,EACvC,GAAI,CAAC,EAAO,MAAO,CAAC,GAAG,CAAO,EAC9B,IAAM,EAAW,EAAQ,UAAY,IAAI,IACnC,EAAuB,CAAC,EAC9B,IAAK,IAAM,KAAS,EAAS,CAC3B,IAAM,EAAQ,EAAM,MAAM,YAAY,EAChC,EACJ,IAAU,EACN,EAAM,MACN,EAAM,WAAW,CAAK,EACpB,EAAM,WACN,EAAM,SAAS,CAAK,EAClB,EAAM,cACN,EAAM,KAAK,YAAY,CAAC,CAAC,SAAS,CAAK,EACrC,EAAM,aACN,EAAM,KAAK,YAAY,CAAC,CAAC,SAAS,CAAK,EACrC,EAAM,aACN,EAAS,IAAI,EAAM,EAAE,EACnB,EAAM,UACN,KACZ,IAAU,MAAM,EAAO,KAAK,CAAC,EAAO,CAAK,CAAC,CAChD,CACA,OAAO,EACJ,MAAM,EAAG,IAAM,EAAE,GAAK,EAAE,IAAM,EAAE,EAAE,CAAC,MAAM,cAAc,EAAE,EAAE,CAAC,KAAK,GAAK,EAAE,EAAE,CAAC,KAAK,cAAc,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,CACzG,KAAK,EAAG,KAAW,CAAK,CAC7B,CAGA,SAAgB,cAAyD,EAA4B,CACnG,IAAM,EAAO,IAAI,IACjB,OAAO,EAAQ,OAAQ,GAAU,CAC/B,IAAM,EAAM,GAAG,EAAM,MAAM,QAAQ,EAAM,OAAO,YAAY,EAG5D,MAFA,CAAI,EAAK,IAAI,CAAG,IAChB,EAAK,IAAI,CAAG,EACL,GACT,CAAC,CACH,CAUA,SAAgB,aAAa,EAAc,EAAwC,CACjF,GAAI,CAAC,EAAS,MAAO,CAAC,CAAE,MAAK,CAAC,EAC9B,IAAM,EAA2B,CAAC,EAC9B,EAAO,EACX,IAAK,IAAM,KAAS,EAAK,SAAS,CAAO,EAAG,CAE1C,GAAI,EAAM,EAAE,CAAC,SAAW,EAAG,SAC3B,IAAM,EAAQ,EAAM,OAAS,EACzB,EAAQ,GAAM,EAAS,KAAK,CAAE,KAAM,EAAK,MAAM,EAAM,CAAK,CAAE,CAAC,EACjE,EAAS,KAAK,CAAE,QAAS,GAAM,KAAM,EAAM,EAAG,CAAC,EAC/C,EAAO,EAAQ,EAAM,EAAE,CAAC,MAC1B,CAEA,OADI,EAAO,EAAK,QAAQ,EAAS,KAAK,CAAE,KAAM,EAAK,MAAM,CAAI,CAAE,CAAC,EACzD,EAAS,OAAS,EAAW,CAAC,CAAE,MAAK,CAAC,CAC/C,CAEA,IAAM,EAAiB,sBAGV,aAAgB,GAA0B,EAAM,QAAQ,EAAgB,MAAM"}
package/dist/errors.cjs CHANGED
@@ -1,2 +1,2 @@
1
- var e=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},t=class extends e{},n=class extends e{};exports.ScoutConfigurationError=n,exports.ScoutDisposedError=t,exports.ScoutError=e;
1
+ var ScoutError=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},ScoutDisposedError=class extends ScoutError{},ScoutConfigError=class extends ScoutError{};exports.ScoutConfigError=ScoutConfigError,exports.ScoutDisposedError=ScoutDisposedError,exports.ScoutError=ScoutError;
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\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,cAAgC,KAAM,CACpC,YAAY,EAAiB,EAAqB,CAChD,MAAM,EAAS,CAAI,EACnB,KAAK,KAAO,WAAW,KACvB,OAAO,eAAe,KAAM,WAAW,SAAS,CAClD,CACF,EAGa,EAAb,cAAwC,CAAW,CAAC,EAGvC,EAAb,cAA6C,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\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 ScoutConfigError extends ScoutError {}\n"],"mappings":"AACA,IAAa,WAAb,cAAgC,KAAM,CACpC,YAAY,EAAiB,EAAqB,CAChD,MAAM,EAAS,CAAI,EACnB,KAAK,KAAO,WAAW,KACvB,OAAO,eAAe,KAAM,WAAW,SAAS,CAClD,CACF,EAGa,mBAAb,cAAwC,UAAW,CAAC,EAGvC,iBAAb,cAAsC,UAAW,CAAC"}
package/dist/errors.d.ts CHANGED
@@ -6,6 +6,6 @@ export declare class ScoutError extends Error {
6
6
  export declare class ScoutDisposedError extends ScoutError {
7
7
  }
8
8
  /** Thrown when an index, search, or reactive search receives an invalid configuration. */
9
- export declare class ScoutConfigurationError extends ScoutError {
9
+ export declare class ScoutConfigError extends ScoutError {
10
10
  }
11
11
  //# 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;CAKjD;AAED,0EAA0E;AAC1E,qBAAa,kBAAmB,SAAQ,UAAU;CAAG;AAErD,0FAA0F;AAC1F,qBAAa,uBAAwB,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;CAKjD;AAED,0EAA0E;AAC1E,qBAAa,kBAAmB,SAAQ,UAAU;CAAG;AAErD,0FAA0F;AAC1F,qBAAa,gBAAiB,SAAQ,UAAU;CAAG"}
package/dist/errors.js CHANGED
@@ -1,10 +1,2 @@
1
- //#region src/errors.ts
2
- var e = class extends Error {
3
- constructor(e, t) {
4
- super(e, t), this.name = new.target.name, Object.setPrototypeOf(this, new.target.prototype);
5
- }
6
- }, t = class extends e {}, n = class extends e {};
7
- //#endregion
8
- export { n as ScoutConfigurationError, t as ScoutDisposedError, e as ScoutError };
9
-
1
+ var ScoutError=class extends Error{constructor(e,t){super(e,t),this.name=new.target.name,Object.setPrototypeOf(this,new.target.prototype)}},ScoutDisposedError=class extends ScoutError{},ScoutConfigError=class extends ScoutError{};export{ScoutConfigError,ScoutDisposedError,ScoutError};
10
2
  //# 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\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,cAAgC,MAAM;CACpC,YAAY,GAAiB,GAAqB;EAGhD,AAFA,MAAM,GAAS,CAAI,GACnB,KAAK,OAAO,WAAW,MACvB,OAAO,eAAe,MAAM,WAAW,SAAS;CAClD;AACF,GAGa,IAAb,cAAwC,EAAW,CAAC,GAGvC,IAAb,cAA6C,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\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 ScoutConfigError extends ScoutError {}\n"],"mappings":"AACA,IAAa,WAAb,cAAgC,KAAM,CACpC,YAAY,EAAiB,EAAqB,CAChD,MAAM,EAAS,CAAI,EACnB,KAAK,KAAO,WAAW,KACvB,OAAO,eAAe,KAAM,WAAW,SAAS,CAClD,CACF,EAGa,mBAAb,cAAwC,UAAW,CAAC,EAGvC,iBAAb,cAAsC,UAAW,CAAC"}
@@ -1,2 +1,2 @@
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;
1
+ const e=require("./tokenize.cjs");function findMatchRanges(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 highlight(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 highlightField(e,t,n){return highlight(n,e.matches.find(e=>e.field===t)?.ranges??[])}exports.findMatchRanges=findMatchRanges,exports.highlight=highlight,exports.highlightField=highlightField;
2
2
  //# sourceMappingURL=highlight.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"highlight.cjs","names":[],"sources":["../src/highlight.ts"],"sourcesContent":["import { tokenize } from './tokenize';\nimport 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 - 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":"kCAcA,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"}
1
+ {"version":3,"file":"highlight.cjs","names":[],"sources":["../src/highlight.ts"],"sourcesContent":["import { tokenize } from './tokenize';\nimport 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 - 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":"kCAcA,SAAgB,gBAAgB,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,UAAU,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,UAAU,EAFH,EAAO,QAAQ,KAAM,GAAM,EAAE,QAAU,CAE9B,CAAA,EAAO,QAAU,CAAC,CAAC,CAC5C"}
package/dist/highlight.js CHANGED
@@ -1,46 +1,2 @@
1
- import { tokenize as e } from "./tokenize.js";
2
- //#region src/highlight.ts
3
- function t(t, n) {
4
- let r = t.toLowerCase(), i = e(n).split(" ").filter(Boolean), a = [];
5
- for (let e of i) {
6
- let t = 0;
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;
11
- }
12
- }
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]]);
18
- }
19
- return o;
20
- }
21
- function n(e, t) {
22
- if (!e) return [];
23
- if (!t.length) return [{
24
- highlighted: !1,
25
- text: e
26
- }];
27
- let n = [], r = 0;
28
- for (let [i, a] of t) i > r && n.push({
29
- highlighted: !1,
30
- text: e.slice(r, i)
31
- }), a > i && n.push({
32
- highlighted: !0,
33
- text: e.slice(i, a)
34
- }), r = a;
35
- return r < e.length && n.push({
36
- highlighted: !1,
37
- text: e.slice(r)
38
- }), n;
39
- }
40
- function r(e, t, r) {
41
- return n(r, e.matches.find((e) => e.field === t)?.ranges ?? []);
42
- }
43
- //#endregion
44
- export { t as findMatchRanges, n as highlight, r as highlightField };
45
-
1
+ import{tokenize as e}from"./tokenize.js";function findMatchRanges(t,n){let r=t.toLowerCase(),i=e(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 highlight(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 highlightField(e,t,n){return highlight(n,e.matches.find(e=>e.field===t)?.ranges??[])}export{findMatchRanges,highlight,highlightField};
46
2
  //# sourceMappingURL=highlight.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"highlight.js","names":[],"sources":["../src/highlight.ts"],"sourcesContent":["import { tokenize } from './tokenize';\nimport 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 - 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":";;AAcA,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"}
1
+ {"version":3,"file":"highlight.js","names":[],"sources":["../src/highlight.ts"],"sourcesContent":["import { tokenize } from './tokenize';\nimport 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 - 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":"yCAcA,SAAgB,gBAAgB,EAAc,EAAmC,CAC/E,IAAM,EAAQ,EAAK,YAAY,EACzB,EAAQ,EAAS,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,UAAU,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,UAAU,EAFH,EAAO,QAAQ,KAAM,GAAM,EAAE,QAAU,CAE9B,CAAA,EAAO,QAAU,CAAC,CAAC,CAC5C"}
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.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;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./adapters.cjs"),t=require("./entries.cjs"),n=require("./errors.cjs"),r=require("./highlight.cjs"),i=require("./scout-index.cjs"),a=require("./reactive.cjs"),o=require("./segment.cjs");exports.ScoutConfigError=n.ScoutConfigError,exports.ScoutDisposedError=n.ScoutDisposedError,exports.ScoutError=n.ScoutError,exports.createIndex=i.createIndex,exports.createReactiveSearch=a.createReactiveSearch,exports.createSearch=a.createSearch,exports.dedupeEntries=t.dedupeEntries,exports.escapeRegExp=t.escapeRegExp,exports.findMatchRanges=r.findMatchRanges,exports.highlight=r.highlight,exports.highlightField=r.highlightField,exports.rankEntries=t.rankEntries,exports.segmentWords=o.segmentWords,exports.splitPattern=t.splitPattern,exports.toFilterPredicate=e.toFilterPredicate,exports.toSearchMatcher=e.toSearchMatcher;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export { toFilterPredicate, toSearchMatcher } from './adapters';
2
- export { ScoutConfigurationError, ScoutDisposedError, ScoutError } from './errors';
2
+ export { dedupeEntries, escapeRegExp, type RankableEntry, rankEntries, type SplitSegment, splitPattern, } from './entries';
3
+ export { ScoutConfigError, ScoutDisposedError, ScoutError } from './errors';
3
4
  export { findMatchRanges, highlight, highlightField } from './highlight';
4
5
  export type { ReactiveSearch } from './reactive';
5
6
  export { createReactiveSearch, createSearch } 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,uBAAuB,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACnF,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AACzE,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAChE,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,UAAU,EACV,iBAAiB,EACjB,iBAAiB,EACjB,YAAY,EACZ,cAAc,EACd,WAAW,EACX,sBAAsB,GACvB,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,EACL,aAAa,EACb,YAAY,EACZ,KAAK,aAAa,EAClB,WAAW,EACX,KAAK,YAAY,EACjB,YAAY,GACb,MAAM,WAAW,CAAC;AACnB,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAC5E,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AACzE,YAAY,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAChE,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,UAAU,EACV,iBAAiB,EACjB,iBAAiB,EACjB,YAAY,EACZ,cAAc,EACd,WAAW,EACX,sBAAsB,GACvB,MAAM,SAAS,CAAC"}
package/dist/index.js CHANGED
@@ -1,7 +1 @@
1
- import { toFilterPredicate as e, toSearchMatcher as t } from "./adapters.js";
2
- import { ScoutConfigurationError as n, ScoutDisposedError as r, ScoutError as i } from "./errors.js";
3
- import { findMatchRanges as a, highlight as o, highlightField as s } from "./highlight.js";
4
- import { createIndex as c } from "./scout-index.js";
5
- import { createReactiveSearch as l, createSearch as u } from "./reactive.js";
6
- import { segmentWords as d } from "./segment.js";
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 };
1
+ import{toFilterPredicate as e,toSearchMatcher as t}from"./adapters.js";import{dedupeEntries as n,escapeRegExp as r,rankEntries as i,splitPattern as a}from"./entries.js";import{ScoutConfigError as o,ScoutDisposedError as s,ScoutError as c}from"./errors.js";import{findMatchRanges as l,highlight as u,highlightField as d}from"./highlight.js";import{createIndex as f}from"./scout-index.js";import{createReactiveSearch as p,createSearch as m}from"./reactive.js";import{segmentWords as h}from"./segment.js";export{o as ScoutConfigError,s as ScoutDisposedError,c as ScoutError,f as createIndex,p as createReactiveSearch,m as createSearch,n as dedupeEntries,r as escapeRegExp,l as findMatchRanges,u as highlight,d as highlightField,i as rankEntries,h as segmentWords,a as splitPattern,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");var n=200;function r(e){for(let t of[...e])try{t()}catch(e){queueMicrotask(()=>{throw e})}}function i(e,t){if(e.size!==0)for(let n of[...e])try{n(t)}catch{}}function a(e,t,n,r,i){if(r||n?.signal?.aborted)return()=>{};e.add(t);let a=()=>{e.delete(t),n?.signal?.removeEventListener(`abort`,a)};return n?.signal?.addEventListener(`abort`,a,{once:!0,signal:i}),a}function o(t,o={}){let{debounce:s=n,limit:c,minQueryLength:l,threshold:u}=o;if(!Number.isSafeInteger(s)||s<0)throw new e.ScoutConfigurationError(`debounce must be a finite non-negative integer.`);let d=e=>t.search(e,{limit:c,minQueryLength:l,threshold:u}),f=(e,t,n)=>Object.freeze({isSearching:t,query:e,results:Object.freeze(n)}),p=new Set,m=new Set,h=new AbortController,g=f(``,!1,d(``)),_=``,v=!1,y,b=e=>{g=e,r(p),i(m,{snapshot:e,type:`state-change`})},x=e=>{_=e,b(f(e,!1,d(e)))},S=t.onMutate(()=>{b(f(g.query,g.isSearching,d(_)))}),C=t=>{if(v)throw new e.ScoutDisposedError(`SearchState.${t}() called after dispose()`)},w=()=>{v||(v=!0,y!==void 0&&clearTimeout(y),y=void 0,S(),i(m,{type:`dispose`}),p.clear(),m.clear(),h.abort())};return{clear(){C(`clear`),(g.query!==``||g.isSearching)&&(y!==void 0&&clearTimeout(y),y=void 0,x(``))},get disposalSignal(){return h.signal},dispose:w,get disposed(){return v},getSnapshot(){return g},setQuery(e){if(C(`setQuery`),e!==g.query){if(y!==void 0&&clearTimeout(y),y=void 0,e===_){b(f(e,!1,g.results));return}if(s===0){x(e);return}b(f(e,!0,g.results)),y=setTimeout(()=>{y=void 0,x(e)},s)}},subscribe(e,t){return a(p,e,t,v,h.signal)},tap(e,t){return a(m,e,t,v,h.signal)},[Symbol.dispose]:w}}function s(e,n){let r=t.createIndex(e,n),i=o(r,n);return{clear:i.clear,get disposalSignal(){return i.disposalSignal},dispose:i.dispose,get disposed(){return i.disposed},getSnapshot:i.getSnapshot,index:r,setQuery:i.setQuery,subscribe:i.subscribe,tap:i.tap,[Symbol.dispose]:i[Symbol.dispose]}}exports.createReactiveSearch=s,exports.createSearch=o;
1
+ const e=require("./errors.cjs"),t=require("./scout-index.cjs");let n=require("@vielzeug/arsenal");var r=200;function notify(e){for(let t of[...e])try{t()}catch(e){queueMicrotask(()=>{throw e})}}function subscribe(e,t,n,r,i){if(r||n?.signal?.aborted)return()=>{};e.add(t);let unsubscribe=()=>{e.delete(t),n?.signal?.removeEventListener(`abort`,unsubscribe)};return n?.signal?.addEventListener(`abort`,unsubscribe,{once:!0,signal:i}),unsubscribe}function createSearch(t,i={}){let{debounce:a=r,limit:o,minQueryLength:s,threshold:c}=i;if(!Number.isSafeInteger(a)||a<0)throw new e.ScoutConfigError(`debounce must be a finite non-negative integer.`);let search=e=>t.search(e,{limit:o,minQueryLength:s,threshold:c}),createSnapshot=(e,t,n)=>Object.freeze({isSearching:t,query:e,results:Object.freeze(n)}),l=new Set,u=(0,n.tapper)(),d=new AbortController,f=createSnapshot(``,!1,search(``)),p=``,m=!1,h,publish=e=>{f=e,notify(l),u.emit({snapshot:e,type:`state-change`})},commit=e=>{p=e,publish(createSnapshot(e,!1,search(e)))},g=t.onMutate(()=>{publish(createSnapshot(f.query,f.isSearching,search(p)))}),assertActive=t=>{if(m)throw new e.ScoutDisposedError(`SearchState.${t}() called after dispose()`)},dispose=()=>{m||(m=!0,h!==void 0&&clearTimeout(h),h=void 0,g(),l.clear(),u.clear(),d.abort())};return{clear(){assertActive(`clear`),(f.query!==``||f.isSearching)&&(h!==void 0&&clearTimeout(h),h=void 0,commit(``))},get disposalSignal(){return d.signal},dispose,get disposed(){return m},getSnapshot(){return f},setQuery(e){if(assertActive(`setQuery`),e!==f.query){if(h!==void 0&&clearTimeout(h),h=void 0,e===p){publish(createSnapshot(e,!1,f.results));return}if(a===0){commit(e);return}publish(createSnapshot(e,!0,f.results)),h=setTimeout(()=>{h=void 0,commit(e)},a)}},subscribe(e,t){return subscribe(l,e,t,m,d.signal)},tap(e,t){return m?()=>{}:u.tap(e,t)},[Symbol.dispose]:dispose}}function createReactiveSearch(e,n){let r=t.createIndex(e,n),i=createSearch(r,n);return{clear:i.clear,get disposalSignal(){return i.disposalSignal},dispose:i.dispose,get disposed(){return i.disposed},getSnapshot:i.getSnapshot,index:r,setQuery:i.setQuery,subscribe:i.subscribe,tap:i.tap,[Symbol.dispose]:i[Symbol.dispose]}}exports.createReactiveSearch=createReactiveSearch,exports.createSearch=createSearch;
2
2
  //# sourceMappingURL=reactive.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"reactive.cjs","names":[],"sources":["../src/reactive.ts"],"sourcesContent":["import { ScoutConfigurationError, ScoutDisposedError } from './errors';\nimport { createIndex, type ScoutIndex } from './scout-index';\nimport type {\n CreateSearchOptions,\n ScoutEvent,\n ScoutIndexOptions,\n SearchResult,\n SearchSnapshot,\n SearchState,\n SearchSubscribeOptions,\n} from './types';\n\nexport type ReactiveSearch<T> = SearchState<T> & { readonly index: ScoutIndex<T> };\n\nconst DEFAULT_DEBOUNCE = 200;\n\nfunction notify(listeners: Set<() => void>): void {\n for (const listener of [...listeners]) {\n try {\n listener();\n } catch (error) {\n queueMicrotask(() => {\n throw error;\n });\n }\n }\n}\n\nfunction emit<T>(tappers: Set<(event: ScoutEvent<T>) => void>, event: ScoutEvent<T>): void {\n if (tappers.size === 0) return;\n\n for (const tapper of [...tappers]) {\n try {\n tapper(event);\n } catch {}\n }\n}\n\nfunction subscribe<T>(\n listeners: Set<T>,\n listener: T,\n options: SearchSubscribeOptions | undefined,\n disposed: boolean,\n lifetimeSignal: AbortSignal,\n): () => void {\n if (disposed || options?.signal?.aborted) return () => {};\n\n listeners.add(listener);\n const unsubscribe = (): void => {\n listeners.delete(listener);\n options?.signal?.removeEventListener('abort', unsubscribe);\n };\n options?.signal?.addEventListener('abort', unsubscribe, { once: true, signal: lifetimeSignal });\n return unsubscribe;\n}\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.isSafeInteger(debounceMs) || debounceMs < 0) {\n throw new ScoutConfigurationError('debounce must be a finite non-negative integer.');\n }\n\n const search = (query: string) => index.search(query, { limit, minQueryLength, threshold });\n const createSnapshot = (\n query: string,\n isSearching: boolean,\n results: ReadonlyArray<SearchResult<T>>,\n ): SearchSnapshot<T> => Object.freeze({ isSearching, query, results: Object.freeze(results) });\n const listeners = new Set<() => void>();\n const tappers = new Set<(event: ScoutEvent<T>) => void>();\n const controller = new AbortController();\n let snapshot = createSnapshot('', false, search(''));\n let committedQuery = '';\n let disposed = false;\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const publish = (next: SearchSnapshot<T>): void => {\n snapshot = next;\n notify(listeners);\n emit(tappers, { snapshot: next, type: 'state-change' });\n };\n const commit = (query: string): void => {\n committedQuery = query;\n publish(createSnapshot(query, false, search(query)));\n };\n const unsubscribeIndex = index.onMutate(() => {\n publish(createSnapshot(snapshot.query, snapshot.isSearching, search(committedQuery)));\n });\n const assertActive = (method: string): void => {\n if (disposed) throw new ScoutDisposedError(`SearchState.${method}() called after dispose()`);\n };\n const dispose = (): void => {\n if (disposed) return;\n disposed = true;\n if (timer !== undefined) clearTimeout(timer);\n timer = undefined;\n unsubscribeIndex();\n emit(tappers, { type: 'dispose' });\n listeners.clear();\n tappers.clear();\n controller.abort();\n };\n\n return {\n clear() {\n assertActive('clear');\n if (snapshot.query === '' && !snapshot.isSearching) return;\n if (timer !== undefined) clearTimeout(timer);\n timer = undefined;\n commit('');\n },\n get disposalSignal() {\n return controller.signal;\n },\n dispose,\n get disposed() {\n return disposed;\n },\n getSnapshot() {\n return snapshot;\n },\n setQuery(query) {\n assertActive('setQuery');\n if (query === snapshot.query) return;\n if (timer !== undefined) clearTimeout(timer);\n timer = undefined;\n\n if (query === committedQuery) {\n publish(createSnapshot(query, false, snapshot.results));\n return;\n }\n\n if (debounceMs === 0) {\n commit(query);\n return;\n }\n\n publish(createSnapshot(query, true, snapshot.results));\n timer = setTimeout(() => {\n timer = undefined;\n commit(query);\n }, debounceMs);\n },\n subscribe(listener, subscribeOptions) {\n return subscribe(listeners, listener, subscribeOptions, disposed, controller.signal);\n },\n tap(handler, tapOptions) {\n return subscribe(tappers, handler, tapOptions, disposed, controller.signal);\n },\n [Symbol.dispose]: dispose,\n };\n}\n\nexport function createReactiveSearch<T>(\n items: T[],\n options: ScoutIndexOptions<T> & Pick<CreateSearchOptions, 'debounce'>,\n): ReactiveSearch<T> {\n const index = createIndex(items, options);\n const state = createSearch(index, options);\n\n return {\n clear: state.clear,\n get disposalSignal() {\n return state.disposalSignal;\n },\n dispose: state.dispose,\n get disposed() {\n return state.disposed;\n },\n getSnapshot: state.getSnapshot,\n index,\n setQuery: state.setQuery,\n subscribe: state.subscribe,\n tap: state.tap,\n [Symbol.dispose]: state[Symbol.dispose],\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"reactive.cjs","names":[],"sources":["../src/reactive.ts"],"sourcesContent":["import { tapper } from '@vielzeug/arsenal';\n\nimport { ScoutConfigError, ScoutDisposedError } from './errors';\nimport { createIndex, type ScoutIndex } from './scout-index';\nimport type {\n CreateSearchOptions,\n ScoutEvent,\n ScoutIndexOptions,\n SearchResult,\n SearchSnapshot,\n SearchState,\n SearchSubscribeOptions,\n} from './types';\n\nexport type ReactiveSearch<T> = SearchState<T> & { readonly index: ScoutIndex<T> };\n\nconst DEFAULT_DEBOUNCE = 200;\n\nfunction notify(listeners: Set<() => void>): void {\n for (const listener of [...listeners]) {\n try {\n listener();\n } catch (error) {\n queueMicrotask(() => {\n throw error;\n });\n }\n }\n}\n\nfunction subscribe(\n listeners: Set<() => void>,\n listener: () => void,\n options: SearchSubscribeOptions | undefined,\n disposed: boolean,\n lifetimeSignal: AbortSignal,\n): () => void {\n if (disposed || options?.signal?.aborted) return () => {};\n\n listeners.add(listener);\n const unsubscribe = (): void => {\n listeners.delete(listener);\n options?.signal?.removeEventListener('abort', unsubscribe);\n };\n options?.signal?.addEventListener('abort', unsubscribe, { once: true, signal: lifetimeSignal });\n return unsubscribe;\n}\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.isSafeInteger(debounceMs) || debounceMs < 0) {\n throw new ScoutConfigError('debounce must be a finite non-negative integer.');\n }\n\n const search = (query: string) => index.search(query, { limit, minQueryLength, threshold });\n const createSnapshot = (\n query: string,\n isSearching: boolean,\n results: ReadonlyArray<SearchResult<T>>,\n ): SearchSnapshot<T> => Object.freeze({ isSearching, query, results: Object.freeze(results) });\n const listeners = new Set<() => void>();\n const tappers = tapper<ScoutEvent<T>>();\n const controller = new AbortController();\n let snapshot = createSnapshot('', false, search(''));\n let committedQuery = '';\n let disposed = false;\n let timer: ReturnType<typeof setTimeout> | undefined;\n\n const publish = (next: SearchSnapshot<T>): void => {\n snapshot = next;\n notify(listeners);\n tappers.emit({ snapshot: next, type: 'state-change' });\n };\n const commit = (query: string): void => {\n committedQuery = query;\n publish(createSnapshot(query, false, search(query)));\n };\n const unsubscribeIndex = index.onMutate(() => {\n publish(createSnapshot(snapshot.query, snapshot.isSearching, search(committedQuery)));\n });\n const assertActive = (method: string): void => {\n if (disposed) throw new ScoutDisposedError(`SearchState.${method}() called after dispose()`);\n };\n const dispose = (): void => {\n if (disposed) return;\n disposed = true;\n if (timer !== undefined) clearTimeout(timer);\n timer = undefined;\n unsubscribeIndex();\n listeners.clear();\n tappers.clear();\n controller.abort();\n };\n\n return {\n clear() {\n assertActive('clear');\n if (snapshot.query === '' && !snapshot.isSearching) return;\n if (timer !== undefined) clearTimeout(timer);\n timer = undefined;\n commit('');\n },\n get disposalSignal() {\n return controller.signal;\n },\n dispose,\n get disposed() {\n return disposed;\n },\n getSnapshot() {\n return snapshot;\n },\n setQuery(query) {\n assertActive('setQuery');\n if (query === snapshot.query) return;\n if (timer !== undefined) clearTimeout(timer);\n timer = undefined;\n\n if (query === committedQuery) {\n publish(createSnapshot(query, false, snapshot.results));\n return;\n }\n\n if (debounceMs === 0) {\n commit(query);\n return;\n }\n\n publish(createSnapshot(query, true, snapshot.results));\n timer = setTimeout(() => {\n timer = undefined;\n commit(query);\n }, debounceMs);\n },\n subscribe(listener, subscribeOptions) {\n return subscribe(listeners, listener, subscribeOptions, disposed, controller.signal);\n },\n tap(handler, tapOptions) {\n if (disposed) return () => {};\n\n return tappers.tap(handler, tapOptions);\n },\n [Symbol.dispose]: dispose,\n };\n}\n\nexport function createReactiveSearch<T>(\n items: T[],\n options: ScoutIndexOptions<T> & Pick<CreateSearchOptions, 'debounce'>,\n): ReactiveSearch<T> {\n const index = createIndex(items, options);\n const state = createSearch(index, options);\n\n return {\n clear: state.clear,\n get disposalSignal() {\n return state.disposalSignal;\n },\n dispose: state.dispose,\n get disposed() {\n return state.disposed;\n },\n getSnapshot: state.getSnapshot,\n index,\n setQuery: state.setQuery,\n subscribe: state.subscribe,\n tap: state.tap,\n [Symbol.dispose]: state[Symbol.dispose],\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"reactive.d.ts","sourceRoot":"","sources":["../src/reactive.ts"],"names":[],"mappings":"AACA,OAAO,EAAe,KAAK,UAAU,EAAE,MAAM,eAAe,CAAC;AAC7D,OAAO,KAAK,EACV,mBAAmB,EAEnB,iBAAiB,EAGjB,WAAW,EAEZ,MAAM,SAAS,CAAC;AAEjB,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI,WAAW,CAAC,CAAC,CAAC,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,CAAA;CAAE,CAAC;AA4CnF,wBAAgB,YAAY,CAAC,CAAC,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,OAAO,GAAE,mBAAwB,GAAG,WAAW,CAAC,CAAC,CAAC,CAgGvG;AAED,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,CAoBnB"}
1
+ {"version":3,"file":"reactive.d.ts","sourceRoot":"","sources":["../src/reactive.ts"],"names":[],"mappings":"AAGA,OAAO,EAAe,KAAK,UAAU,EAAE,MAAM,eAAe,CAAC;AAC7D,OAAO,KAAK,EACV,mBAAmB,EAEnB,iBAAiB,EAGjB,WAAW,EAEZ,MAAM,SAAS,CAAC;AAEjB,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI,WAAW,CAAC,CAAC,CAAC,GAAG;IAAE,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,CAAA;CAAE,CAAC;AAkCnF,wBAAgB,YAAY,CAAC,CAAC,EAAE,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,EAAE,OAAO,GAAE,mBAAwB,GAAG,WAAW,CAAC,CAAC,CAAC,CAiGvG;AAED,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,CAoBnB"}
package/dist/reactive.js CHANGED
@@ -1,115 +1,2 @@
1
- import { ScoutConfigurationError as e, ScoutDisposedError as t } from "./errors.js";
2
- import { createIndex as n } from "./scout-index.js";
3
- //#region src/reactive.ts
4
- var r = 200;
5
- function i(e) {
6
- for (let t of [...e]) try {
7
- t();
8
- } catch (e) {
9
- queueMicrotask(() => {
10
- throw e;
11
- });
12
- }
13
- }
14
- function a(e, t) {
15
- if (e.size !== 0) for (let n of [...e]) try {
16
- n(t);
17
- } catch {}
18
- }
19
- function o(e, t, n, r, i) {
20
- if (r || n?.signal?.aborted) return () => {};
21
- e.add(t);
22
- let a = () => {
23
- e.delete(t), n?.signal?.removeEventListener("abort", a);
24
- };
25
- return n?.signal?.addEventListener("abort", a, {
26
- once: !0,
27
- signal: i
28
- }), a;
29
- }
30
- function s(n, s = {}) {
31
- let { debounce: c = r, limit: l, minQueryLength: u, threshold: d } = s;
32
- if (!Number.isSafeInteger(c) || c < 0) throw new e("debounce must be a finite non-negative integer.");
33
- let f = (e) => n.search(e, {
34
- limit: l,
35
- minQueryLength: u,
36
- threshold: d
37
- }), p = (e, t, n) => Object.freeze({
38
- isSearching: t,
39
- query: e,
40
- results: Object.freeze(n)
41
- }), m = /* @__PURE__ */ new Set(), h = /* @__PURE__ */ new Set(), g = new AbortController(), _ = p("", !1, f("")), v = "", y = !1, b, x = (e) => {
42
- _ = e, i(m), a(h, {
43
- snapshot: e,
44
- type: "state-change"
45
- });
46
- }, S = (e) => {
47
- v = e, x(p(e, !1, f(e)));
48
- }, C = n.onMutate(() => {
49
- x(p(_.query, _.isSearching, f(v)));
50
- }), w = (e) => {
51
- if (y) throw new t(`SearchState.${e}() called after dispose()`);
52
- }, T = () => {
53
- y || (y = !0, b !== void 0 && clearTimeout(b), b = void 0, C(), a(h, { type: "dispose" }), m.clear(), h.clear(), g.abort());
54
- };
55
- return {
56
- clear() {
57
- w("clear"), (_.query !== "" || _.isSearching) && (b !== void 0 && clearTimeout(b), b = void 0, S(""));
58
- },
59
- get disposalSignal() {
60
- return g.signal;
61
- },
62
- dispose: T,
63
- get disposed() {
64
- return y;
65
- },
66
- getSnapshot() {
67
- return _;
68
- },
69
- setQuery(e) {
70
- if (w("setQuery"), e !== _.query) {
71
- if (b !== void 0 && clearTimeout(b), b = void 0, e === v) {
72
- x(p(e, !1, _.results));
73
- return;
74
- }
75
- if (c === 0) {
76
- S(e);
77
- return;
78
- }
79
- x(p(e, !0, _.results)), b = setTimeout(() => {
80
- b = void 0, S(e);
81
- }, c);
82
- }
83
- },
84
- subscribe(e, t) {
85
- return o(m, e, t, y, g.signal);
86
- },
87
- tap(e, t) {
88
- return o(h, e, t, y, g.signal);
89
- },
90
- [Symbol.dispose]: T
91
- };
92
- }
93
- function c(e, t) {
94
- let r = n(e, t), i = s(r, t);
95
- return {
96
- clear: i.clear,
97
- get disposalSignal() {
98
- return i.disposalSignal;
99
- },
100
- dispose: i.dispose,
101
- get disposed() {
102
- return i.disposed;
103
- },
104
- getSnapshot: i.getSnapshot,
105
- index: r,
106
- setQuery: i.setQuery,
107
- subscribe: i.subscribe,
108
- tap: i.tap,
109
- [Symbol.dispose]: i[Symbol.dispose]
110
- };
111
- }
112
- //#endregion
113
- export { c as createReactiveSearch, s as createSearch };
114
-
1
+ import{ScoutConfigError as e,ScoutDisposedError as t}from"./errors.js";import{createIndex as n}from"./scout-index.js";import{tapper as r}from"@vielzeug/arsenal";var i=200;function notify(e){for(let t of[...e])try{t()}catch(e){queueMicrotask(()=>{throw e})}}function subscribe(e,t,n,r,i){if(r||n?.signal?.aborted)return()=>{};e.add(t);let unsubscribe=()=>{e.delete(t),n?.signal?.removeEventListener(`abort`,unsubscribe)};return n?.signal?.addEventListener(`abort`,unsubscribe,{once:!0,signal:i}),unsubscribe}function createSearch(n,a={}){let{debounce:o=i,limit:s,minQueryLength:c,threshold:l}=a;if(!Number.isSafeInteger(o)||o<0)throw new e(`debounce must be a finite non-negative integer.`);let search=e=>n.search(e,{limit:s,minQueryLength:c,threshold:l}),createSnapshot=(e,t,n)=>Object.freeze({isSearching:t,query:e,results:Object.freeze(n)}),u=new Set,d=r(),f=new AbortController,p=createSnapshot(``,!1,search(``)),m=``,h=!1,g,publish=e=>{p=e,notify(u),d.emit({snapshot:e,type:`state-change`})},commit=e=>{m=e,publish(createSnapshot(e,!1,search(e)))},_=n.onMutate(()=>{publish(createSnapshot(p.query,p.isSearching,search(m)))}),assertActive=e=>{if(h)throw new t(`SearchState.${e}() called after dispose()`)},dispose=()=>{h||(h=!0,g!==void 0&&clearTimeout(g),g=void 0,_(),u.clear(),d.clear(),f.abort())};return{clear(){assertActive(`clear`),(p.query!==``||p.isSearching)&&(g!==void 0&&clearTimeout(g),g=void 0,commit(``))},get disposalSignal(){return f.signal},dispose,get disposed(){return h},getSnapshot(){return p},setQuery(e){if(assertActive(`setQuery`),e!==p.query){if(g!==void 0&&clearTimeout(g),g=void 0,e===m){publish(createSnapshot(e,!1,p.results));return}if(o===0){commit(e);return}publish(createSnapshot(e,!0,p.results)),g=setTimeout(()=>{g=void 0,commit(e)},o)}},subscribe(e,t){return subscribe(u,e,t,h,f.signal)},tap(e,t){return h?()=>{}:d.tap(e,t)},[Symbol.dispose]:dispose}}function createReactiveSearch(e,t){let r=n(e,t),i=createSearch(r,t);return{clear:i.clear,get disposalSignal(){return i.disposalSignal},dispose:i.dispose,get disposed(){return i.disposed},getSnapshot:i.getSnapshot,index:r,setQuery:i.setQuery,subscribe:i.subscribe,tap:i.tap,[Symbol.dispose]:i[Symbol.dispose]}}export{createReactiveSearch,createSearch};
115
2
  //# sourceMappingURL=reactive.js.map