@vielzeug/scout 1.1.1

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 (74) hide show
  1. package/README.md +70 -0
  2. package/dist/_dev.cjs +2 -0
  3. package/dist/_dev.cjs.map +1 -0
  4. package/dist/_dev.d.ts +2 -0
  5. package/dist/_dev.d.ts.map +1 -0
  6. package/dist/_dev.js +9 -0
  7. package/dist/_dev.js.map +1 -0
  8. package/dist/adapters.cjs +2 -0
  9. package/dist/adapters.cjs.map +1 -0
  10. package/dist/adapters.d.ts +40 -0
  11. package/dist/adapters.d.ts.map +1 -0
  12. package/dist/adapters.js +12 -0
  13. package/dist/adapters.js.map +1 -0
  14. package/dist/devtools.cjs +2 -0
  15. package/dist/devtools.cjs.map +1 -0
  16. package/dist/devtools.d.ts +38 -0
  17. package/dist/devtools.d.ts.map +1 -0
  18. package/dist/devtools.js +21 -0
  19. package/dist/devtools.js.map +1 -0
  20. package/dist/errors.cjs +2 -0
  21. package/dist/errors.cjs.map +1 -0
  22. package/dist/errors.d.ts +12 -0
  23. package/dist/errors.d.ts.map +1 -0
  24. package/dist/errors.js +13 -0
  25. package/dist/errors.js.map +1 -0
  26. package/dist/highlight.cjs +2 -0
  27. package/dist/highlight.cjs.map +1 -0
  28. package/dist/highlight.d.ts +68 -0
  29. package/dist/highlight.d.ts.map +1 -0
  30. package/dist/highlight.js +45 -0
  31. package/dist/highlight.js.map +1 -0
  32. package/dist/index.cjs +1 -0
  33. package/dist/index.d.ts +10 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +7 -0
  36. package/dist/reactive.cjs +2 -0
  37. package/dist/reactive.cjs.map +1 -0
  38. package/dist/reactive.d.ts +74 -0
  39. package/dist/reactive.d.ts.map +1 -0
  40. package/dist/reactive.js +71 -0
  41. package/dist/reactive.js.map +1 -0
  42. package/dist/scout-index.cjs +2 -0
  43. package/dist/scout-index.cjs.map +1 -0
  44. package/dist/scout-index.d.ts +64 -0
  45. package/dist/scout-index.d.ts.map +1 -0
  46. package/dist/scout-index.js +162 -0
  47. package/dist/scout-index.js.map +1 -0
  48. package/dist/scout.cjs +2 -0
  49. package/dist/scout.cjs.map +1 -0
  50. package/dist/scout.iife.js +2 -0
  51. package/dist/scout.iife.js.map +1 -0
  52. package/dist/scout.js +2 -0
  53. package/dist/scout.js.map +1 -0
  54. package/dist/segment.cjs +2 -0
  55. package/dist/segment.cjs.map +1 -0
  56. package/dist/segment.d.ts +26 -0
  57. package/dist/segment.d.ts.map +1 -0
  58. package/dist/segment.js +13 -0
  59. package/dist/segment.js.map +1 -0
  60. package/dist/tokenize.cjs +2 -0
  61. package/dist/tokenize.cjs.map +1 -0
  62. package/dist/tokenize.d.ts +2 -0
  63. package/dist/tokenize.d.ts.map +1 -0
  64. package/dist/tokenize.js +12 -0
  65. package/dist/tokenize.js.map +1 -0
  66. package/dist/trigram.cjs +2 -0
  67. package/dist/trigram.cjs.map +1 -0
  68. package/dist/trigram.d.ts +2 -0
  69. package/dist/trigram.d.ts.map +1 -0
  70. package/dist/trigram.js +16 -0
  71. package/dist/trigram.js.map +1 -0
  72. package/dist/types.d.ts +130 -0
  73. package/dist/types.d.ts.map +1 -0
  74. package/package.json +48 -0
package/README.md ADDED
@@ -0,0 +1,70 @@
1
+ # @vielzeug/scout
2
+
3
+ Fast fuzzy-search. Builds a trigram inverted index at construction — O(candidates) per query instead of O(corpus × field_length).
4
+
5
+ ## Features
6
+
7
+ - **Trigram index** — fast candidate lookup; Dice coefficient scoring
8
+ - **Multi-field weighted ranking** — per-field weights, custom stringifiers
9
+ - **Match highlighting** — character-range offsets for UI rendering
10
+ - **Reactive layer** — `createSearch()` wraps any index in `ripple` signals with debounce
11
+ - **Framework adapters** — `toSearchFn()` for sourcerer, `toFilterPredicate()` for filter pipelines
12
+ - **Incremental updates** — `add()`, `remove()`, `reindex()` patch the index in O(field_length)
13
+ - **Unsegmented-script helper** — `segmentWords()` pre-splits CJK/Thai text into words via `Intl.Segmenter`
14
+ - **Devtools** — `@vielzeug/scout/devtools`'s `debugSearch()` logs query/results transitions
15
+
16
+ ## Install
17
+
18
+ ```sh
19
+ pnpm add @vielzeug/scout
20
+ ```
21
+
22
+ ## Quick start
23
+
24
+ ```ts
25
+ import { createIndex } from '@vielzeug/scout';
26
+
27
+ const index = createIndex(users, {
28
+ fields: [
29
+ { field: 'name', weight: 2 },
30
+ { field: 'email' },
31
+ ],
32
+ });
33
+
34
+ const results = index.search('alice');
35
+ // [{ item: { name: 'Alice', email: '...' }, score: 0.85, matches: [...] }]
36
+ ```
37
+
38
+ ## Reactive search
39
+
40
+ ```ts
41
+ import { createIndex, createSearch } from '@vielzeug/scout';
42
+ import { effect } from '@vielzeug/ripple';
43
+
44
+ const index = createIndex(users, { fields: ['name'] });
45
+ const search = createSearch(index, { debounce: 150 });
46
+
47
+ effect(() => {
48
+ console.log(search.results.value);
49
+ });
50
+
51
+ search.query.value = 'alice';
52
+ ```
53
+
54
+ ## Sourcerer integration
55
+
56
+ ```ts
57
+ import { createIndex, toSearchFn } from '@vielzeug/scout';
58
+
59
+ const index = createIndex(users, { fields: ['name', 'email'] });
60
+ const source = createLocalSource(users, { searchFn: toSearchFn(index) });
61
+ ```
62
+
63
+ ## Highlighting
64
+
65
+ ```ts
66
+ import { highlight } from '@vielzeug/scout';
67
+
68
+ const parts = highlight('Hello World', [[0, 5]]);
69
+ // [{ text: 'Hello', highlighted: true }, { text: ' World', highlighted: false }]
70
+ ```
package/dist/_dev.cjs ADDED
@@ -0,0 +1,2 @@
1
+ var e=!globalThis.__SCOUT_PROD__;function t(t){e&&console.warn(`[@vielzeug/scout] ${t}`)}exports.warn=t;
2
+ //# sourceMappingURL=_dev.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_dev.cjs","names":[],"sources":["../src/_dev.ts"],"sourcesContent":["const isDev = !(globalThis as { __SCOUT_PROD__?: boolean }).__SCOUT_PROD__;\n\n/** @internal */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/scout] ${msg}`);\n}\n"],"mappings":"AAAA,IAAM,EAAQ,CAAE,WAA4C,eAG5D,SAAgB,EAAK,EAAmB,CAClC,GAAO,QAAQ,KAAK,qBAAqB,GAAK,CACpD"}
package/dist/_dev.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=_dev.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_dev.d.ts","sourceRoot":"","sources":["../src/_dev.ts"],"names":[],"mappings":""}
package/dist/_dev.js ADDED
@@ -0,0 +1,9 @@
1
+ //#region src/_dev.ts
2
+ var e = !globalThis.__SCOUT_PROD__;
3
+ function t(t) {
4
+ e && console.warn(`[@vielzeug/scout] ${t}`);
5
+ }
6
+ //#endregion
7
+ export { t as warn };
8
+
9
+ //# sourceMappingURL=_dev.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"_dev.js","names":[],"sources":["../src/_dev.ts"],"sourcesContent":["const isDev = !(globalThis as { __SCOUT_PROD__?: boolean }).__SCOUT_PROD__;\n\n/** @internal */\nexport function warn(msg: string): void {\n if (isDev) console.warn(`[@vielzeug/scout] ${msg}`);\n}\n"],"mappings":";AAAA,IAAM,IAAQ,CAAE,WAA4C;AAG5D,SAAgB,EAAK,GAAmB;CACtC,AAAI,KAAO,QAAQ,KAAK,qBAAqB,GAAK;AACpD"}
@@ -0,0 +1,2 @@
1
+ function e(e,t){return(n,r)=>e.search(r,t).map(e=>e.item)}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.toSearchFn=e;
2
+ //# sourceMappingURL=adapters.cjs.map
@@ -0,0 +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 * Wraps a `ScoutIndex` as a `searchFn` compatible with `sourcerer`'s `LocalSourceConfig`.\n *\n * The returned function ignores the `items` argument (the index is the source of truth)\n * and delegates entirely to `index.search()`, returning plain items in score order.\n *\n * @example\n * ```ts\n * const index = createIndex(users, { fields: ['name', 'email'] });\n *\n * // Pass directly to sourcerer's searchFn option\n * const source = createLocalSource(users, { searchFn: toSearchFn(index) });\n * ```\n */\nexport function toSearchFn<T>(\n index: ScoutIndex<T>,\n options?: SearchConstraints,\n): (items: readonly T[], query: string) => readonly T[] {\n return (_items, query) => index.search(query, options).map((r) => r.item);\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 *\n * @example\n * ```ts\n * const index = createIndex(products, { fields: ['title', 'sku'] });\n *\n * // Array filter\n * const results = products.filter(toFilterPredicate(index, 'widget'));\n *\n * // vault query builder\n * const rows = await db.query('products')\n * .filter(toFilterPredicate(index, searchTerm))\n * .toArray();\n * ```\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((r) => r.item));\n\n return (item) => matchSet.has(item);\n}\n"],"mappings":"AAiBA,SAAgB,EACd,EACA,EACsD,CACtD,OAAQ,EAAQ,IAAU,EAAM,OAAO,EAAO,CAAO,CAAC,CAAC,IAAK,GAAM,EAAE,IAAI,CAC1E,CAuBA,SAAgB,EACd,EACA,EACA,EACsB,CACtB,IAAM,EAAW,IAAI,IAAI,EAAM,OAAO,EAAO,CAAO,CAAC,CAAC,IAAK,GAAM,EAAE,IAAI,CAAC,EAExE,MAAQ,IAAS,EAAS,IAAI,CAAI,CACpC"}
@@ -0,0 +1,40 @@
1
+ import type { ScoutIndex } from './scout-index';
2
+ import type { SearchConstraints } from './types';
3
+ /**
4
+ * Wraps a `ScoutIndex` as a `searchFn` compatible with `sourcerer`'s `LocalSourceConfig`.
5
+ *
6
+ * The returned function ignores the `items` argument (the index is the source of truth)
7
+ * and delegates entirely to `index.search()`, returning plain items in score order.
8
+ *
9
+ * @example
10
+ * ```ts
11
+ * const index = createIndex(users, { fields: ['name', 'email'] });
12
+ *
13
+ * // Pass directly to sourcerer's searchFn option
14
+ * const source = createLocalSource(users, { searchFn: toSearchFn(index) });
15
+ * ```
16
+ */
17
+ export declare function toSearchFn<T>(index: ScoutIndex<T>, options?: SearchConstraints): (items: readonly T[], query: string) => readonly T[];
18
+ /**
19
+ * Returns a predicate that returns `true` for items matching `query` in the given index.
20
+ *
21
+ * The predicate is computed once at call time — call `toFilterPredicate` again if the
22
+ * query or corpus changes.
23
+ *
24
+ * Compatible with `Array.filter`, `vault`'s `query.filter()`, or any predicate pipeline.
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * const index = createIndex(products, { fields: ['title', 'sku'] });
29
+ *
30
+ * // Array filter
31
+ * const results = products.filter(toFilterPredicate(index, 'widget'));
32
+ *
33
+ * // vault query builder
34
+ * const rows = await db.query('products')
35
+ * .filter(toFilterPredicate(index, searchTerm))
36
+ * .toArray();
37
+ * ```
38
+ */
39
+ export declare function toFilterPredicate<T>(index: ScoutIndex<T>, query: string, options?: SearchConstraints): (item: T) => boolean;
40
+ //# sourceMappingURL=adapters.d.ts.map
@@ -0,0 +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;;;;;;;;;;;;;GAaG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAC1B,KAAK,EAAE,UAAU,CAAC,CAAC,CAAC,EACpB,OAAO,CAAC,EAAE,iBAAiB,GAC1B,CAAC,KAAK,EAAE,SAAS,CAAC,EAAE,EAAE,KAAK,EAAE,MAAM,KAAK,SAAS,CAAC,EAAE,CAEtD;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;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"}
@@ -0,0 +1,12 @@
1
+ //#region src/adapters.ts
2
+ function e(e, t) {
3
+ return (n, r) => e.search(r, t).map((e) => e.item);
4
+ }
5
+ function t(e, t, n) {
6
+ let r = new Set(e.search(t, n).map((e) => e.item));
7
+ return (e) => r.has(e);
8
+ }
9
+ //#endregion
10
+ export { t as toFilterPredicate, e as toSearchFn };
11
+
12
+ //# sourceMappingURL=adapters.js.map
@@ -0,0 +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 * Wraps a `ScoutIndex` as a `searchFn` compatible with `sourcerer`'s `LocalSourceConfig`.\n *\n * The returned function ignores the `items` argument (the index is the source of truth)\n * and delegates entirely to `index.search()`, returning plain items in score order.\n *\n * @example\n * ```ts\n * const index = createIndex(users, { fields: ['name', 'email'] });\n *\n * // Pass directly to sourcerer's searchFn option\n * const source = createLocalSource(users, { searchFn: toSearchFn(index) });\n * ```\n */\nexport function toSearchFn<T>(\n index: ScoutIndex<T>,\n options?: SearchConstraints,\n): (items: readonly T[], query: string) => readonly T[] {\n return (_items, query) => index.search(query, options).map((r) => r.item);\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 *\n * @example\n * ```ts\n * const index = createIndex(products, { fields: ['title', 'sku'] });\n *\n * // Array filter\n * const results = products.filter(toFilterPredicate(index, 'widget'));\n *\n * // vault query builder\n * const rows = await db.query('products')\n * .filter(toFilterPredicate(index, searchTerm))\n * .toArray();\n * ```\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((r) => r.item));\n\n return (item) => matchSet.has(item);\n}\n"],"mappings":";AAiBA,SAAgB,EACd,GACA,GACsD;CACtD,QAAQ,GAAQ,MAAU,EAAM,OAAO,GAAO,CAAO,CAAC,CAAC,KAAK,MAAM,EAAE,IAAI;AAC1E;AAuBA,SAAgB,EACd,GACA,GACA,GACsB;CACtB,IAAM,IAAW,IAAI,IAAI,EAAM,OAAO,GAAO,CAAO,CAAC,CAAC,KAAK,MAAM,EAAE,IAAI,CAAC;CAExE,QAAQ,MAAS,EAAS,IAAI,CAAI;AACpC"}
@@ -0,0 +1,2 @@
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});function e(e){let t=`scout:search`,n=[e.query.subscribe(()=>{console.debug(`[${t}] query -> ${JSON.stringify(e.query.peek())}`)}),e.isSearching.subscribe(()=>{console.debug(`[${t}] isSearching -> ${e.isSearching.peek()}`)}),e.results.subscribe(()=>{console.debug(`[${t}] results -> ${e.results.peek().length} item(s)`)})];return()=>{for(let e of n)e.dispose()}}exports.debugSearch=e;
2
+ //# sourceMappingURL=devtools.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"devtools.cjs","names":[],"sources":["../src/devtools.ts"],"sourcesContent":["/**\n * @vielzeug/scout — debug utilities for the reactive search layer.\n *\n * Import from the dedicated sub-path so it is tree-shaken from production bundles:\n * ```ts\n * import { debugSearch } from '@vielzeug/scout/devtools';\n * ```\n */\n\nimport type { SearchState } from './types';\n\n/**\n * Logs `query` → `isSearching` → `results` transitions of a `SearchState` to\n * `console.debug`. Development only — helps visualize debounce timing and result\n * churn without stepping through `createSearch()` internals.\n *\n * **Logs the full, literal search query string.** If your queries may carry PII (names,\n * emails, medical/financial terms typed by end users), don't enable this in production —\n * it's opt-in dev tooling imported from a dedicated sub-path for exactly that reason.\n *\n * Returns a function that unsubscribes all listeners installed by this call.\n *\n * @example\n * ```ts\n * import { debugSearch } from '@vielzeug/scout/devtools';\n *\n * const search = createSearch(index);\n * const stopDebugging = debugSearch(search);\n *\n * search.query.value = 'alice';\n * // [scout:search] query -> \"alice\"\n * // [scout:search] isSearching -> true\n * // [scout:search] isSearching -> false\n * // [scout:search] results -> 1 item(s)\n *\n * stopDebugging();\n * ```\n */\nexport function debugSearch<T>(search: SearchState<T>): () => void {\n const label = 'scout:search';\n\n const subscriptions = [\n search.query.subscribe(() => {\n console.debug(`[${label}] query -> ${JSON.stringify(search.query.peek())}`);\n }),\n search.isSearching.subscribe(() => {\n console.debug(`[${label}] isSearching -> ${search.isSearching.peek()}`);\n }),\n search.results.subscribe(() => {\n console.debug(`[${label}] results -> ${search.results.peek().length} item(s)`);\n }),\n ];\n\n return () => {\n for (const subscription of subscriptions) subscription.dispose();\n };\n}\n"],"mappings":"mEAsCA,SAAgB,EAAe,EAAoC,CACjE,IAAM,EAAQ,eAER,EAAgB,CACpB,EAAO,MAAM,cAAgB,CAC3B,QAAQ,MAAM,IAAI,EAAM,aAAa,KAAK,UAAU,EAAO,MAAM,KAAK,CAAC,GAAG,CAC5E,CAAC,EACD,EAAO,YAAY,cAAgB,CACjC,QAAQ,MAAM,IAAI,EAAM,mBAAmB,EAAO,YAAY,KAAK,GAAG,CACxE,CAAC,EACD,EAAO,QAAQ,cAAgB,CAC7B,QAAQ,MAAM,IAAI,EAAM,eAAe,EAAO,QAAQ,KAAK,CAAC,CAAC,OAAO,SAAS,CAC/E,CAAC,CACH,EAEA,UAAa,CACX,IAAK,IAAM,KAAgB,EAAe,EAAa,QAAQ,CACjE,CACF"}
@@ -0,0 +1,38 @@
1
+ /**
2
+ * @vielzeug/scout — debug utilities for the reactive search layer.
3
+ *
4
+ * Import from the dedicated sub-path so it is tree-shaken from production bundles:
5
+ * ```ts
6
+ * import { debugSearch } from '@vielzeug/scout/devtools';
7
+ * ```
8
+ */
9
+ import type { SearchState } from './types';
10
+ /**
11
+ * Logs `query` → `isSearching` → `results` transitions of a `SearchState` to
12
+ * `console.debug`. Development only — helps visualize debounce timing and result
13
+ * churn without stepping through `createSearch()` internals.
14
+ *
15
+ * **Logs the full, literal search query string.** If your queries may carry PII (names,
16
+ * emails, medical/financial terms typed by end users), don't enable this in production —
17
+ * it's opt-in dev tooling imported from a dedicated sub-path for exactly that reason.
18
+ *
19
+ * Returns a function that unsubscribes all listeners installed by this call.
20
+ *
21
+ * @example
22
+ * ```ts
23
+ * import { debugSearch } from '@vielzeug/scout/devtools';
24
+ *
25
+ * const search = createSearch(index);
26
+ * const stopDebugging = debugSearch(search);
27
+ *
28
+ * search.query.value = 'alice';
29
+ * // [scout:search] query -> "alice"
30
+ * // [scout:search] isSearching -> true
31
+ * // [scout:search] isSearching -> false
32
+ * // [scout:search] results -> 1 item(s)
33
+ *
34
+ * stopDebugging();
35
+ * ```
36
+ */
37
+ export declare function debugSearch<T>(search: SearchState<T>): () => void;
38
+ //# sourceMappingURL=devtools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"devtools.d.ts","sourceRoot":"","sources":["../src/devtools.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,CAkBjE"}
@@ -0,0 +1,21 @@
1
+ //#region src/devtools.ts
2
+ function e(e) {
3
+ let t = "scout:search", n = [
4
+ e.query.subscribe(() => {
5
+ console.debug(`[${t}] query -> ${JSON.stringify(e.query.peek())}`);
6
+ }),
7
+ e.isSearching.subscribe(() => {
8
+ console.debug(`[${t}] isSearching -> ${e.isSearching.peek()}`);
9
+ }),
10
+ e.results.subscribe(() => {
11
+ console.debug(`[${t}] results -> ${e.results.peek().length} item(s)`);
12
+ })
13
+ ];
14
+ return () => {
15
+ for (let e of n) e.dispose();
16
+ };
17
+ }
18
+ //#endregion
19
+ export { e as debugSearch };
20
+
21
+ //# sourceMappingURL=devtools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"devtools.js","names":[],"sources":["../src/devtools.ts"],"sourcesContent":["/**\n * @vielzeug/scout — debug utilities for the reactive search layer.\n *\n * Import from the dedicated sub-path so it is tree-shaken from production bundles:\n * ```ts\n * import { debugSearch } from '@vielzeug/scout/devtools';\n * ```\n */\n\nimport type { SearchState } from './types';\n\n/**\n * Logs `query` → `isSearching` → `results` transitions of a `SearchState` to\n * `console.debug`. Development only — helps visualize debounce timing and result\n * churn without stepping through `createSearch()` internals.\n *\n * **Logs the full, literal search query string.** If your queries may carry PII (names,\n * emails, medical/financial terms typed by end users), don't enable this in production —\n * it's opt-in dev tooling imported from a dedicated sub-path for exactly that reason.\n *\n * Returns a function that unsubscribes all listeners installed by this call.\n *\n * @example\n * ```ts\n * import { debugSearch } from '@vielzeug/scout/devtools';\n *\n * const search = createSearch(index);\n * const stopDebugging = debugSearch(search);\n *\n * search.query.value = 'alice';\n * // [scout:search] query -> \"alice\"\n * // [scout:search] isSearching -> true\n * // [scout:search] isSearching -> false\n * // [scout:search] results -> 1 item(s)\n *\n * stopDebugging();\n * ```\n */\nexport function debugSearch<T>(search: SearchState<T>): () => void {\n const label = 'scout:search';\n\n const subscriptions = [\n search.query.subscribe(() => {\n console.debug(`[${label}] query -> ${JSON.stringify(search.query.peek())}`);\n }),\n search.isSearching.subscribe(() => {\n console.debug(`[${label}] isSearching -> ${search.isSearching.peek()}`);\n }),\n search.results.subscribe(() => {\n console.debug(`[${label}] results -> ${search.results.peek().length} item(s)`);\n }),\n ];\n\n return () => {\n for (const subscription of subscriptions) subscription.dispose();\n };\n}\n"],"mappings":";AAsCA,SAAgB,EAAe,GAAoC;CACjE,IAAM,IAAQ,gBAER,IAAgB;EACpB,EAAO,MAAM,gBAAgB;GAC3B,QAAQ,MAAM,IAAI,EAAM,aAAa,KAAK,UAAU,EAAO,MAAM,KAAK,CAAC,GAAG;EAC5E,CAAC;EACD,EAAO,YAAY,gBAAgB;GACjC,QAAQ,MAAM,IAAI,EAAM,mBAAmB,EAAO,YAAY,KAAK,GAAG;EACxE,CAAC;EACD,EAAO,QAAQ,gBAAgB;GAC7B,QAAQ,MAAM,IAAI,EAAM,eAAe,EAAO,QAAQ,KAAK,CAAC,CAAC,OAAO,SAAS;EAC/E,CAAC;CACH;CAEA,aAAa;EACX,KAAK,IAAM,KAAgB,GAAe,EAAa,QAAQ;CACjE;AACF"}
@@ -0,0 +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;
2
+ //# sourceMappingURL=errors.cjs.map
@@ -0,0 +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,IAAI,OAAO,KACvB,OAAO,eAAe,KAAM,IAAI,OAAO,SAAS,CAClD,CAEA,OAAO,GAAG,EAAiC,CACzC,OAAO,aAAe,CACxB,CACF,EAGa,EAAb,cAAwC,CAAW,CAAC,EAGvC,EAAb,cAAqC,CAAW,CAAC"}
@@ -0,0 +1,12 @@
1
+ /** Base class for all scout errors. Use `instanceof ScoutError` to catch any scout-originated error. */
2
+ export declare class ScoutError extends Error {
3
+ constructor(message: string, opts?: ErrorOptions);
4
+ static is(err: unknown): err is ScoutError;
5
+ }
6
+ /** Thrown when a method is called on a disposed search state instance. */
7
+ export declare class ScoutDisposedError extends ScoutError {
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 {
11
+ }
12
+ //# sourceMappingURL=errors.d.ts.map
@@ -0,0 +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"}
package/dist/errors.js ADDED
@@ -0,0 +1,13 @@
1
+ //#region src/errors.ts
2
+ var e = class e extends Error {
3
+ constructor(e, t) {
4
+ super(e, t), this.name = new.target.name, Object.setPrototypeOf(this, new.target.prototype);
5
+ }
6
+ static is(t) {
7
+ return t instanceof e;
8
+ }
9
+ }, t = class extends e {}, n = class extends e {};
10
+ //#endregion
11
+ export { t as ScoutDisposedError, e as ScoutError, n as ScoutIndexError };
12
+
13
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +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,IAAI,OAAO,MACvB,OAAO,eAAe,MAAM,IAAI,OAAO,SAAS;CAClD;CAEA,OAAO,GAAG,GAAiC;EACzC,OAAO,aAAe;CACxB;AACF,GAGa,IAAb,cAAwC,EAAW,CAAC,GAGvC,IAAb,cAAqC,EAAW,CAAC"}
@@ -0,0 +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;
2
+ //# sourceMappingURL=highlight.cjs.map
@@ -0,0 +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"}
@@ -0,0 +1,68 @@
1
+ import type { HighlightPart, SearchResult } from './types';
2
+ /**
3
+ * Finds character ranges within `text` where `query` words appear.
4
+ * Ranges are sorted and overlapping ranges are merged.
5
+ *
6
+ * Useful when you need to apply match ranges to a different string than the
7
+ * indexed field value (e.g. a truncated preview or a differently formatted display string).
8
+ *
9
+ * @param text - The string to search within.
10
+ * @param query - The normalized (tokenized) query string. Words are split on spaces.
11
+ * @returns Sorted, non-overlapping `[start, end]` character ranges.
12
+ */
13
+ export declare function findMatchRanges(text: string, query: string): [number, number][];
14
+ /**
15
+ * Splits `text` into highlighted and unhighlighted fragments using match `ranges`.
16
+ *
17
+ * Ranges must be sorted and non-overlapping (as produced by `SearchResult.matches[n].ranges`).
18
+ * Use the returned parts to render highlighted text in a UI component.
19
+ *
20
+ * **`part.text` is the original, unescaped field value** (e.g. a user's name, bio, or
21
+ * product title) — this function does no HTML escaping. Render each part via safe DOM APIs
22
+ * (`textContent`, a framework's text binding) and wrap `highlighted` parts in your own
23
+ * element (e.g. `<mark>`); never concatenate `part.text` into an HTML string for
24
+ * `innerHTML` — that reintroduces the XSS risk this structured return shape avoids.
25
+ *
26
+ * @example
27
+ * ```ts
28
+ * highlight('Hello World', [[0, 5]]);
29
+ * // [{ text: 'Hello', highlighted: true }, { text: ' World', highlighted: false }]
30
+ *
31
+ * highlight('Hello World', [[0, 5], [6, 11]]);
32
+ * // [
33
+ * // { text: 'Hello', highlighted: true },
34
+ * // { text: ' ', highlighted: false },
35
+ * // { text: 'World', highlighted: true },
36
+ * // ]
37
+ * ```
38
+ *
39
+ * @param text - The original field value to split.
40
+ * @param ranges - Sorted, non-overlapping `[start, end]` ranges from `FieldMatch.ranges`.
41
+ * @returns An array of `HighlightPart` objects. Returns an empty array for an empty `text`.
42
+ */
43
+ export declare function highlight(text: string, ranges: [number, number][]): HighlightPart[];
44
+ /**
45
+ * Finds the match ranges for `field` in `result` and splits `text` into
46
+ * highlighted and unhighlighted fragments in one step.
47
+ *
48
+ * This is the ergonomic shorthand for the common pattern:
49
+ * ```ts
50
+ * const match = result.matches.find(m => m.field === 'name');
51
+ * const parts = highlight(item.name, match?.ranges ?? []);
52
+ * ```
53
+ *
54
+ * @example
55
+ * ```ts
56
+ * for (const result of index.search('alice')) {
57
+ * const parts = highlightField(result, 'name', result.item.name);
58
+ * console.log(parts.map(p => p.highlighted ? `[${p.text}]` : p.text).join(''));
59
+ * }
60
+ * ```
61
+ *
62
+ * @param result - A `SearchResult` from `ScoutIndex.search()`.
63
+ * @param field - The field name to look up in `result.matches`.
64
+ * @param text - The original field value string to split.
65
+ * @returns An array of `HighlightPart` objects.
66
+ */
67
+ export declare function highlightField<T>(result: SearchResult<T>, field: keyof T & string, text: string): HighlightPart[];
68
+ //# sourceMappingURL=highlight.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,45 @@
1
+ //#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) {
5
+ 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;
10
+ }
11
+ }
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]]);
17
+ }
18
+ return a;
19
+ }
20
+ function t(e, t) {
21
+ if (!e) return [];
22
+ if (!t.length) return [{
23
+ highlighted: !1,
24
+ text: e
25
+ }];
26
+ let n = [], r = 0;
27
+ for (let [i, a] of t) i > r && n.push({
28
+ highlighted: !1,
29
+ text: e.slice(r, i)
30
+ }), a > i && n.push({
31
+ highlighted: !0,
32
+ text: e.slice(i, a)
33
+ }), r = a;
34
+ return r < e.length && n.push({
35
+ highlighted: !1,
36
+ text: e.slice(r)
37
+ }), n;
38
+ }
39
+ function n(e, n, r) {
40
+ return t(r, e.matches.find((e) => e.field === n)?.ranges ?? []);
41
+ }
42
+ //#endregion
43
+ export { e as findMatchRanges, t as highlight, n as highlightField };
44
+
45
+ //# sourceMappingURL=highlight.js.map
@@ -0,0 +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"}
package/dist/index.cjs ADDED
@@ -0,0 +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.toSearchFn=e.toSearchFn;
@@ -0,0 +1,10 @@
1
+ export { toFilterPredicate, toSearchFn } from './adapters';
2
+ export { ScoutDisposedError, ScoutError, ScoutIndexError } from './errors';
3
+ export { findMatchRanges, highlight, highlightField } from './highlight';
4
+ export { createReactiveSearch, createSearch } from './reactive';
5
+ export type { ReactiveSearch } from './reactive';
6
+ export type { ScoutIndex } from './scout-index';
7
+ export { createIndex } from './scout-index';
8
+ export { segmentWords } from './segment';
9
+ export type { CreateSearchOptions, FieldDef, FieldMatch, HighlightPart, ScoutIndexOptions, SearchConstraints, SearchResult, SearchState, } from './types';
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAC3D,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"}
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ import { toFilterPredicate as e, toSearchFn as t } from "./adapters.js";
2
+ import { ScoutDisposedError as n, ScoutError as r, ScoutIndexError 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 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 toSearchFn };
@@ -0,0 +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(),_.dispose(),f(),l.dispose(),u.dispose(),p.dispose(),m.dispose(),d.dispose()}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});return{...i(r,{debounce:n.debounce}),index:r}}exports.createReactiveSearch=a,exports.createSearch=i;
2
+ //# sourceMappingURL=reactive.cjs.map
@@ -0,0 +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.dispose();\n unsubscribeMutations();\n query.dispose();\n committedQuery.dispose();\n isSearching.dispose();\n results.dispose();\n indexVersion.dispose();\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 { ...state, index };\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,EAAA,EAAA,OAAA,CAAuB,GAAI,CAAE,KAAM,aAAc,CAAC,EAClD,GAAA,EAAA,EAAA,OAAA,CAAgC,GAAI,CAAE,KAAM,sBAAuB,CAAC,EACpE,GAAA,EAAA,EAAA,OAAA,CAAsB,EAAG,CAAE,KAAM,oBAAqB,CAAC,EAEvD,EAAuB,EAAM,aAAe,CAChD,EAAa,OACf,CAAC,EAEK,GAAA,EAAA,EAAA,SAAA,KAA6B,EAAM,QAAU,EAAe,MAAO,CAAE,KAAM,mBAAoB,CAAC,EAEhG,GAAA,EAAA,EAAA,SAAA,MAGF,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,QAAQ,EACrB,EAAqB,EACrB,EAAM,QAAQ,EACd,EAAe,QAAQ,EACvB,EAAY,QAAQ,EACpB,EAAQ,QAAQ,EAChB,EAAa,QAAQ,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,EAGD,MAAO,CAAE,GAFK,EAAa,EAAO,CAAE,SAAU,EAAQ,QAAS,CAEnD,EAAO,OAAM,CAC3B"}
@@ -0,0 +1,74 @@
1
+ import type { CreateSearchOptions, ScoutIndexOptions, SearchState } from './types';
2
+ import { type ScoutIndex } from './scout-index';
3
+ /**
4
+ * Combined index + reactive search state returned by `createReactiveSearch()`.
5
+ * Exposes the underlying `ScoutIndex` for incremental mutations (`add`, `remove`, `reindex`).
6
+ */
7
+ export type ReactiveSearch<T> = SearchState<T> & {
8
+ readonly index: ScoutIndex<T>;
9
+ };
10
+ /**
11
+ * Creates a reactive search state backed by a `ScoutIndex`.
12
+ *
13
+ * - Set `state.query.value` to trigger a (debounced) search.
14
+ * - Read `state.results.value` inside an `effect` or `computed` to consume results reactively.
15
+ * - `state.isSearching.value` is `true` while debouncing, `false` otherwise.
16
+ * - Call `state.dispose()` (or `using state = createSearch(...)`) to release subscriptions.
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * const index = createIndex(users, { fields: ['name', 'email'] });
21
+ * const search = createSearch(index, { debounce: 150 });
22
+ *
23
+ * effect(() => {
24
+ * const results = search.results.value;
25
+ * renderList(results.map(r => r.item));
26
+ * });
27
+ *
28
+ * // Wire to an input
29
+ * input.addEventListener('input', e => {
30
+ * search.query.value = e.currentTarget.value;
31
+ * });
32
+ *
33
+ * // Clean up
34
+ * search.dispose();
35
+ * ```
36
+ *
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()`.
39
+ *
40
+ * @param index - A `ScoutIndex` built with `createIndex()`.
41
+ * @param options.debounce - Milliseconds to wait before committing query changes. Default: `200`.
42
+ * @param options.limit - Override the index-level result limit.
43
+ * @param options.minQueryLength - Override the index-level minimum query length.
44
+ * @param options.threshold - Override the index-level score threshold.
45
+ */
46
+ export declare function createSearch<T>(index: ScoutIndex<T>, options?: CreateSearchOptions): SearchState<T>;
47
+ /**
48
+ * Creates a `ScoutIndex` and a reactive search state in one call — the shorthand
49
+ * for the common pattern of `createIndex` + `createSearch`.
50
+ *
51
+ * The returned `ReactiveSearch` exposes the underlying index via `.index` for
52
+ * incremental mutations (`add`, `remove`, `reindex`) after construction.
53
+ *
54
+ * @example
55
+ * ```ts
56
+ * const search = createReactiveSearch(users, {
57
+ * fields: [{ field: 'name', weight: 2 }, 'email'],
58
+ * debounce: 150,
59
+ * });
60
+ *
61
+ * effect(() => renderList(search.results.value.map(r => r.item)));
62
+ *
63
+ * // Wire to an input
64
+ * input.addEventListener('input', e => { search.query.value = e.currentTarget.value; });
65
+ *
66
+ * // Add a new item at runtime
67
+ * search.index.add(newUser);
68
+ * ```
69
+ *
70
+ * @param items - Initial corpus to index.
71
+ * @param options - Index options (`fields`, `limit`, `minQueryLength`, `threshold`) plus optional `debounce`.
72
+ */
73
+ export declare function createReactiveSearch<T>(items: T[], options: ScoutIndexOptions<T> & Pick<CreateSearchOptions, 'debounce'>): ReactiveSearch<T>;
74
+ //# sourceMappingURL=reactive.d.ts.map
@@ -0,0 +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,CA8FvG;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"}