@86d-app/search 0.0.4 → 0.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -0
- package/AGENTS.md +72 -0
- package/README.md +171 -28
- package/dist/__tests__/controllers.test.d.ts +2 -0
- package/dist/__tests__/controllers.test.d.ts.map +1 -0
- package/dist/__tests__/embedding-provider.test.d.ts +2 -0
- package/dist/__tests__/embedding-provider.test.d.ts.map +1 -0
- package/dist/__tests__/endpoint-security.test.d.ts +2 -0
- package/dist/__tests__/endpoint-security.test.d.ts.map +1 -0
- package/dist/__tests__/meilisearch-provider.test.d.ts +2 -0
- package/dist/__tests__/meilisearch-provider.test.d.ts.map +1 -0
- package/dist/__tests__/service-impl.test.d.ts +2 -0
- package/dist/__tests__/service-impl.test.d.ts.map +1 -0
- package/dist/admin/components/index.d.ts +2 -0
- package/dist/admin/components/index.d.ts.map +1 -0
- package/dist/admin/components/search-analytics.d.ts +2 -0
- package/dist/admin/components/search-analytics.d.ts.map +1 -0
- package/dist/admin/endpoints/analytics.d.ts +15 -0
- package/dist/admin/endpoints/analytics.d.ts.map +1 -0
- package/dist/admin/endpoints/bulk-index.d.ts +20 -0
- package/dist/admin/endpoints/bulk-index.d.ts.map +1 -0
- package/dist/admin/endpoints/click-analytics.d.ts +7 -0
- package/dist/admin/endpoints/click-analytics.d.ts.map +1 -0
- package/dist/admin/endpoints/get-settings.d.ts +17 -0
- package/dist/admin/endpoints/get-settings.d.ts.map +1 -0
- package/dist/admin/endpoints/index-manage.d.ts +26 -0
- package/dist/admin/endpoints/index-manage.d.ts.map +1 -0
- package/dist/admin/endpoints/index.d.ts +125 -0
- package/dist/admin/endpoints/index.d.ts.map +1 -0
- package/dist/admin/endpoints/popular.d.ts +10 -0
- package/dist/admin/endpoints/popular.d.ts.map +1 -0
- package/dist/admin/endpoints/synonyms.d.ts +30 -0
- package/dist/admin/endpoints/synonyms.d.ts.map +1 -0
- package/dist/admin/endpoints/zero-results.d.ts +10 -0
- package/dist/admin/endpoints/zero-results.d.ts.map +1 -0
- package/dist/embedding-provider.d.ts +28 -0
- package/dist/embedding-provider.d.ts.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/meilisearch-provider.d.ts +104 -0
- package/dist/meilisearch-provider.d.ts.map +1 -0
- package/dist/schema.d.ts +133 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/service-impl.d.ts +6 -0
- package/dist/service-impl.d.ts.map +1 -0
- package/dist/service.d.ts +127 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/store/components/_hooks.d.ts +6 -0
- package/dist/store/components/_hooks.d.ts.map +1 -0
- package/dist/store/components/index.d.ts +10 -0
- package/dist/store/components/index.d.ts.map +1 -0
- package/dist/store/components/search-bar.d.ts +7 -0
- package/dist/store/components/search-bar.d.ts.map +1 -0
- package/dist/store/components/search-page.d.ts +4 -0
- package/dist/store/components/search-page.d.ts.map +1 -0
- package/dist/store/components/search-results.d.ts +9 -0
- package/dist/store/components/search-results.d.ts.map +1 -0
- package/dist/store/endpoints/click.d.ts +14 -0
- package/dist/store/endpoints/click.d.ts.map +1 -0
- package/dist/store/endpoints/index.d.ts +85 -0
- package/dist/store/endpoints/index.d.ts.map +1 -0
- package/dist/store/endpoints/recent.d.ts +15 -0
- package/dist/store/endpoints/recent.d.ts.map +1 -0
- package/dist/store/endpoints/search.d.ts +36 -0
- package/dist/store/endpoints/search.d.ts.map +1 -0
- package/dist/store/endpoints/store-search.d.ts +16 -0
- package/dist/store/endpoints/store-search.d.ts.map +1 -0
- package/dist/store/endpoints/suggest.d.ts +11 -0
- package/dist/store/endpoints/suggest.d.ts.map +1 -0
- package/package.json +3 -3
- package/src/__tests__/controllers.test.ts +1026 -0
- package/src/__tests__/embedding-provider.test.ts +195 -0
- package/src/__tests__/endpoint-security.test.ts +300 -0
- package/src/__tests__/meilisearch-provider.test.ts +400 -0
- package/src/__tests__/service-impl.test.ts +341 -8
- package/src/admin/components/search-analytics.tsx +120 -0
- package/src/admin/endpoints/bulk-index.ts +34 -0
- package/src/admin/endpoints/click-analytics.ts +16 -0
- package/src/admin/endpoints/get-settings.ts +56 -0
- package/src/admin/endpoints/index-manage.ts +4 -1
- package/src/admin/endpoints/index.ts +6 -0
- package/src/admin/endpoints/synonyms.ts +1 -1
- package/src/embedding-provider.ts +99 -0
- package/src/index.ts +60 -4
- package/src/meilisearch-provider.ts +239 -0
- package/src/schema.ts +15 -0
- package/src/service-impl.ts +605 -34
- package/src/service.ts +60 -1
- package/src/store/endpoints/click.ts +21 -0
- package/src/store/endpoints/index.ts +2 -0
- package/src/store/endpoints/recent.ts +1 -1
- package/src/store/endpoints/search.ts +38 -10
- package/src/store/endpoints/store-search.ts +1 -1
- package/src/store/endpoints/suggest.ts +2 -2
- package/vitest.config.ts +2 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
[0m[2m[35m$[0m [2m[1mtsc[0m
|
package/AGENTS.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Search Module
|
|
2
|
+
|
|
3
|
+
In-memory full-text search with fuzzy matching, faceted filtering, click tracking, and query analytics.
|
|
4
|
+
|
|
5
|
+
## Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
src/
|
|
9
|
+
index.ts Factory: search(options?) => Module
|
|
10
|
+
schema.ts Data models: searchIndex, searchQuery, searchSynonym, searchClick
|
|
11
|
+
service.ts SearchController interface + types (SearchResult, SearchFacets, SearchClick, etc.)
|
|
12
|
+
service-impl.ts SearchController implementation (fuzzy, Levenshtein, highlights, facets)
|
|
13
|
+
store/
|
|
14
|
+
components/ Store-facing TSX (search bar, page, results)
|
|
15
|
+
endpoints/
|
|
16
|
+
store-search.ts GET /search/store-search (search integration)
|
|
17
|
+
search.ts GET /search (full-text with sort, tags, fuzzy, facets, did-you-mean)
|
|
18
|
+
suggest.ts GET /search/suggest
|
|
19
|
+
recent.ts GET /search/recent
|
|
20
|
+
click.ts POST /search/click (click tracking)
|
|
21
|
+
admin/
|
|
22
|
+
components/ Admin TSX (analytics dashboard)
|
|
23
|
+
endpoints/
|
|
24
|
+
analytics.ts GET /admin/search/analytics (includes CTR + avg click position)
|
|
25
|
+
popular.ts GET /admin/search/popular
|
|
26
|
+
zero-results.ts GET /admin/search/zero-results
|
|
27
|
+
click-analytics.ts GET /admin/search/clicks
|
|
28
|
+
synonyms.ts GET /admin/search/synonyms
|
|
29
|
+
POST /admin/search/synonyms/add
|
|
30
|
+
POST /admin/search/synonyms/:id/delete
|
|
31
|
+
index-manage.ts POST /admin/search/index
|
|
32
|
+
POST /admin/search/index/remove
|
|
33
|
+
bulk-index.ts POST /admin/search/index/bulk (up to 500 items)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Options
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
SearchOptions {
|
|
40
|
+
maxResults?: number
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Data models
|
|
45
|
+
|
|
46
|
+
- **searchIndex**: id, entityType, entityId, title, body?, tags (json[]), url, image?, metadata (json), indexedAt
|
|
47
|
+
- **searchQuery**: id, term, normalizedTerm, resultCount, sessionId?, searchedAt
|
|
48
|
+
- **searchSynonym**: id, term, synonyms (json[]), createdAt
|
|
49
|
+
- **searchClick**: id, queryId, term, entityType, entityId, position, clickedAt
|
|
50
|
+
|
|
51
|
+
## Events
|
|
52
|
+
|
|
53
|
+
- Emits: `search.queried`, `search.indexed`, `search.removed`, `search.clicked`
|
|
54
|
+
|
|
55
|
+
## Patterns
|
|
56
|
+
|
|
57
|
+
- Registers `search: { store: "/search/store-search" }` for cross-module search integration
|
|
58
|
+
- Registers store page at `/search` (SearchPage component)
|
|
59
|
+
- `indexItem` uses composite key `${entityType}_${entityId}` for deduplication; re-indexing updates in-place
|
|
60
|
+
- Different entityTypes with same entityId are separate index entries
|
|
61
|
+
- **Fuzzy search**: Levenshtein distance matching on title/tag/body tokens. Edit tolerance: 0 for ≤3 chars, 1 for 4-5 chars, 2 for 6+ chars. Enabled by default, disable with `fuzzy: false`
|
|
62
|
+
- **Scoring**: exact title (100) > title prefix (50) > tag exact (30) > title substring (25) > fuzzy title (15) > tag substring (15) > body (10) > fuzzy tag (8) > fuzzy body (5)
|
|
63
|
+
- **Sorting**: relevance (default), newest, oldest, title_asc, title_desc
|
|
64
|
+
- **Facets**: search results include entityType counts and tag counts (top 20)
|
|
65
|
+
- **Tag filtering**: pass `tags` option to filter results to items matching any specified tag
|
|
66
|
+
- **Highlights**: results include `<mark>` wrapped text in title and body for matched terms
|
|
67
|
+
- **Did-you-mean**: when a search returns zero results, suggests corrections using Levenshtein distance against indexed titles and popular search terms
|
|
68
|
+
- **Click tracking**: `recordClick()` stores query-to-click data; analytics includes CTR and avg click position
|
|
69
|
+
- **Bulk indexing**: `bulkIndex()` accepts up to 500 items, returns `{indexed, errors}` counts
|
|
70
|
+
- `recordQuery()` logs each search for analytics (popular terms, zero-result tracking)
|
|
71
|
+
- `suggest(prefix)` returns autocomplete: popular terms with results first, then matching index titles, deduplicated
|
|
72
|
+
- Synonyms are bidirectional: if "tee" → ["t-shirt"], then searching "tee" or "t-shirt" finds both
|
package/README.md
CHANGED
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
> [!WARNING]
|
|
18
18
|
> This project is under active development and is not ready for production use. Please proceed with caution. Use at your own risk.
|
|
19
19
|
|
|
20
|
-
#
|
|
20
|
+
# Search Module
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
Full-text search with fuzzy matching, faceted filtering, autocomplete, click tracking, and search analytics for 86d commerce platform.
|
|
23
23
|
|
|
24
24
|
## Installation
|
|
25
25
|
|
|
@@ -47,55 +47,128 @@ const module = search({
|
|
|
47
47
|
|
|
48
48
|
| Method | Path | Description |
|
|
49
49
|
|---|---|---|
|
|
50
|
-
| `GET` | `/search?q=...&type=...&limit=...&skip=...` | Full-text search with
|
|
51
|
-
| `GET` | `/search/suggest?
|
|
50
|
+
| `GET` | `/search?q=...&type=...&tags=...&sort=...&fuzzy=...&limit=...&skip=...` | Full-text search with facets, sorting, fuzzy matching, and did-you-mean |
|
|
51
|
+
| `GET` | `/search/suggest?q=...&limit=...` | Autocomplete suggestions |
|
|
52
52
|
| `GET` | `/search/recent?sessionId=...&limit=...` | Recent search queries by session |
|
|
53
|
+
| `POST` | `/search/click` | Record a search result click (queryId, term, entityType, entityId, position) |
|
|
54
|
+
|
|
55
|
+
### Search query parameters
|
|
56
|
+
|
|
57
|
+
| Param | Type | Default | Description |
|
|
58
|
+
|---|---|---|---|
|
|
59
|
+
| `q` | `string` | required | Search query text |
|
|
60
|
+
| `type` | `string` | — | Filter by entity type |
|
|
61
|
+
| `tags` | `string` | — | Comma-separated tag filter |
|
|
62
|
+
| `sort` | `string` | `relevance` | Sort: `relevance`, `newest`, `oldest`, `title_asc`, `title_desc` |
|
|
63
|
+
| `fuzzy` | `boolean` | `true` | Enable fuzzy/typo-tolerant matching |
|
|
64
|
+
| `limit` | `number` | `20` | Results per page (max 100) |
|
|
65
|
+
| `skip` | `number` | `0` | Offset for pagination |
|
|
66
|
+
| `sessionId` | `string` | — | Session ID for analytics tracking |
|
|
67
|
+
|
|
68
|
+
### Search response
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
{
|
|
72
|
+
results: Array<{
|
|
73
|
+
id: string;
|
|
74
|
+
entityType: string;
|
|
75
|
+
entityId: string;
|
|
76
|
+
title: string;
|
|
77
|
+
url: string;
|
|
78
|
+
image?: string;
|
|
79
|
+
tags: string[];
|
|
80
|
+
score: number;
|
|
81
|
+
highlights?: { title?: string; body?: string };
|
|
82
|
+
}>;
|
|
83
|
+
total: number;
|
|
84
|
+
facets: {
|
|
85
|
+
entityTypes: Array<{ type: string; count: number }>;
|
|
86
|
+
tags: Array<{ tag: string; count: number }>;
|
|
87
|
+
};
|
|
88
|
+
didYouMean?: string;
|
|
89
|
+
}
|
|
90
|
+
```
|
|
53
91
|
|
|
54
92
|
## Admin Endpoints
|
|
55
93
|
|
|
56
94
|
| Method | Path | Description |
|
|
57
95
|
|---|---|---|
|
|
58
|
-
| `GET` | `/admin/search/analytics` | Search analytics summary |
|
|
96
|
+
| `GET` | `/admin/search/analytics` | Search analytics summary (includes CTR and avg click position) |
|
|
59
97
|
| `GET` | `/admin/search/popular` | Most popular search terms |
|
|
60
98
|
| `GET` | `/admin/search/zero-results` | Queries that returned zero results |
|
|
99
|
+
| `GET` | `/admin/search/clicks` | Click-through rate analytics |
|
|
61
100
|
| `GET` | `/admin/search/synonyms` | List all synonym groups |
|
|
62
101
|
| `POST` | `/admin/search/synonyms/add` | Add a synonym group |
|
|
63
102
|
| `POST` | `/admin/search/synonyms/:id/delete` | Delete a synonym group |
|
|
64
103
|
| `POST` | `/admin/search/index` | Manually index an item |
|
|
65
104
|
| `POST` | `/admin/search/index/remove` | Remove an item from the index |
|
|
105
|
+
| `POST` | `/admin/search/index/bulk` | Bulk index up to 500 items |
|
|
66
106
|
|
|
67
107
|
## Controller API
|
|
68
108
|
|
|
69
109
|
```ts
|
|
70
110
|
interface SearchController {
|
|
111
|
+
// Indexing
|
|
71
112
|
indexItem(params: {
|
|
72
113
|
entityType: string;
|
|
73
114
|
entityId: string;
|
|
74
115
|
title: string;
|
|
75
116
|
body?: string;
|
|
76
117
|
tags?: string[];
|
|
77
|
-
url
|
|
118
|
+
url: string;
|
|
78
119
|
image?: string;
|
|
79
120
|
metadata?: Record<string, unknown>;
|
|
80
121
|
}): Promise<SearchIndexItem>;
|
|
81
122
|
|
|
82
|
-
|
|
123
|
+
bulkIndex(items: Array<{
|
|
124
|
+
entityType: string;
|
|
125
|
+
entityId: string;
|
|
126
|
+
title: string;
|
|
127
|
+
body?: string;
|
|
128
|
+
tags?: string[];
|
|
129
|
+
url: string;
|
|
130
|
+
image?: string;
|
|
131
|
+
metadata?: Record<string, unknown>;
|
|
132
|
+
}>): Promise<{ indexed: number; errors: number }>;
|
|
133
|
+
|
|
134
|
+
removeFromIndex(entityType: string, entityId: string): Promise<boolean>;
|
|
83
135
|
|
|
136
|
+
// Search
|
|
84
137
|
search(query: string, options?: {
|
|
85
138
|
entityType?: string;
|
|
139
|
+
tags?: string[];
|
|
140
|
+
sort?: SearchSortField;
|
|
141
|
+
fuzzy?: boolean;
|
|
86
142
|
limit?: number;
|
|
87
143
|
skip?: number;
|
|
88
|
-
}): Promise<{
|
|
144
|
+
}): Promise<{
|
|
145
|
+
results: SearchResult[];
|
|
146
|
+
total: number;
|
|
147
|
+
facets: SearchFacets;
|
|
148
|
+
didYouMean?: string;
|
|
149
|
+
}>;
|
|
89
150
|
|
|
90
151
|
suggest(prefix: string, limit?: number): Promise<string[]>;
|
|
91
|
-
|
|
152
|
+
|
|
153
|
+
// Analytics
|
|
154
|
+
recordQuery(term: string, resultCount: number, sessionId?: string): Promise<SearchQuery>;
|
|
155
|
+
recordClick(params: {
|
|
156
|
+
queryId: string;
|
|
157
|
+
term: string;
|
|
158
|
+
entityType: string;
|
|
159
|
+
entityId: string;
|
|
160
|
+
position: number;
|
|
161
|
+
}): Promise<SearchClick>;
|
|
92
162
|
getRecentQueries(sessionId: string, limit?: number): Promise<SearchQuery[]>;
|
|
93
163
|
getPopularTerms(limit?: number): Promise<PopularTerm[]>;
|
|
94
|
-
getZeroResultQueries(limit?: number): Promise<
|
|
164
|
+
getZeroResultQueries(limit?: number): Promise<PopularTerm[]>;
|
|
95
165
|
getAnalytics(): Promise<SearchAnalyticsSummary>;
|
|
166
|
+
|
|
167
|
+
// Synonyms
|
|
96
168
|
addSynonym(term: string, synonyms: string[]): Promise<SearchSynonym>;
|
|
97
|
-
removeSynonym(id: string): Promise<
|
|
169
|
+
removeSynonym(id: string): Promise<boolean>;
|
|
98
170
|
listSynonyms(): Promise<SearchSynonym[]>;
|
|
171
|
+
|
|
99
172
|
getIndexCount(): Promise<number>;
|
|
100
173
|
}
|
|
101
174
|
```
|
|
@@ -103,22 +176,27 @@ interface SearchController {
|
|
|
103
176
|
## Types
|
|
104
177
|
|
|
105
178
|
```ts
|
|
106
|
-
|
|
107
|
-
id: string;
|
|
108
|
-
entityType: string;
|
|
109
|
-
entityId: string;
|
|
110
|
-
title: string;
|
|
111
|
-
body?: string;
|
|
112
|
-
tags: string[];
|
|
113
|
-
url?: string;
|
|
114
|
-
image?: string;
|
|
115
|
-
metadata?: Record<string, unknown>;
|
|
116
|
-
indexedAt: Date;
|
|
117
|
-
}
|
|
179
|
+
type SearchSortField = "relevance" | "newest" | "oldest" | "title_asc" | "title_desc";
|
|
118
180
|
|
|
119
181
|
interface SearchResult {
|
|
120
182
|
item: SearchIndexItem;
|
|
121
183
|
score: number;
|
|
184
|
+
highlights?: { title?: string; body?: string };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
interface SearchFacets {
|
|
188
|
+
entityTypes: Array<{ type: string; count: number }>;
|
|
189
|
+
tags: Array<{ tag: string; count: number }>;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
interface SearchClick {
|
|
193
|
+
id: string;
|
|
194
|
+
queryId: string;
|
|
195
|
+
term: string;
|
|
196
|
+
entityType: string;
|
|
197
|
+
entityId: string;
|
|
198
|
+
position: number;
|
|
199
|
+
clickedAt: Date;
|
|
122
200
|
}
|
|
123
201
|
|
|
124
202
|
interface SearchAnalyticsSummary {
|
|
@@ -127,11 +205,76 @@ interface SearchAnalyticsSummary {
|
|
|
127
205
|
avgResultCount: number;
|
|
128
206
|
zeroResultCount: number;
|
|
129
207
|
zeroResultRate: number;
|
|
208
|
+
clickThroughRate: number;
|
|
209
|
+
avgClickPosition: number;
|
|
130
210
|
}
|
|
211
|
+
```
|
|
131
212
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
213
|
+
## Store Components
|
|
214
|
+
|
|
215
|
+
### SearchBar
|
|
216
|
+
|
|
217
|
+
An autocomplete search input with keyboard navigation support. Fetches suggestions as the user types (minimum 2 characters) and triggers a search callback on submission. Includes a search icon and an accessible combobox dropdown.
|
|
218
|
+
|
|
219
|
+
#### Props
|
|
220
|
+
|
|
221
|
+
| Prop | Type | Required | Description |
|
|
222
|
+
|------|------|----------|-------------|
|
|
223
|
+
| `placeholder` | `string` | No | Placeholder text for the search input. Defaults to `"Search..."`. |
|
|
224
|
+
| `onSearch` | `(query: string) => void` | No | Callback fired when the user submits a search query (via Enter key or suggestion click). |
|
|
225
|
+
|
|
226
|
+
#### Usage in MDX
|
|
227
|
+
|
|
228
|
+
```mdx
|
|
229
|
+
<SearchBar placeholder="Search products..." onSearch={handleSearch} />
|
|
137
230
|
```
|
|
231
|
+
|
|
232
|
+
Best used in the site header or on a search page to provide instant search suggestions as customers type.
|
|
233
|
+
|
|
234
|
+
### SearchPage
|
|
235
|
+
|
|
236
|
+
A complete search experience combining SearchBar and SearchResults into a single page layout with a heading, search input, and results area. Manages the search query state internally.
|
|
237
|
+
|
|
238
|
+
#### Props
|
|
239
|
+
|
|
240
|
+
| Prop | Type | Required | Description |
|
|
241
|
+
|------|------|----------|-------------|
|
|
242
|
+
| `sessionId` | `string` | No | Optional session ID passed to SearchResults for analytics tracking. |
|
|
243
|
+
|
|
244
|
+
#### Usage in MDX
|
|
245
|
+
|
|
246
|
+
```mdx
|
|
247
|
+
<SearchPage />
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
Best used as the main content of a dedicated `/search` page in the storefront.
|
|
251
|
+
|
|
252
|
+
### SearchResults
|
|
253
|
+
|
|
254
|
+
Displays search results for a given query, with loading and empty states. Fetches results from the search module and renders them as linked cards with optional images.
|
|
255
|
+
|
|
256
|
+
#### Props
|
|
257
|
+
|
|
258
|
+
| Prop | Type | Required | Description |
|
|
259
|
+
|------|------|----------|-------------|
|
|
260
|
+
| `query` | `string` | Yes | The search query string to execute. |
|
|
261
|
+
| `entityType` | `string` | No | Filter results to a specific entity type (e.g., `"product"`). |
|
|
262
|
+
| `sessionId` | `string` | No | Optional session ID for search analytics tracking. |
|
|
263
|
+
| `limit` | `number` | No | Maximum number of results to return. Defaults to `20`. |
|
|
264
|
+
|
|
265
|
+
#### Usage in MDX
|
|
266
|
+
|
|
267
|
+
```mdx
|
|
268
|
+
<SearchResults query="shoes" entityType="product" limit={10} />
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
Best used below a search bar to display results, or on a category page for filtered search results.
|
|
272
|
+
|
|
273
|
+
## Notes
|
|
274
|
+
|
|
275
|
+
- **Fuzzy search** uses Levenshtein distance. Edit tolerance scales with word length: 0 for ≤3 chars, 1 for 4-5 chars, 2 for 6+ chars. Enabled by default.
|
|
276
|
+
- **Facets** are computed from all matching results before pagination, giving accurate counts regardless of page.
|
|
277
|
+
- **Did-you-mean** only activates when zero results are found, checking against indexed titles and historically successful search terms.
|
|
278
|
+
- **Click tracking** records which result was clicked and at what position, enabling CTR and rank quality analytics.
|
|
279
|
+
- **Synonyms** are bidirectional: adding "tee" → ["t-shirt"] means searching for either term finds both.
|
|
280
|
+
- **Highlights** wrap matched terms in `<mark>` tags for rendering in search result UIs.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"controllers.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/controllers.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"embedding-provider.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/embedding-provider.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"endpoint-security.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/endpoint-security.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meilisearch-provider.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/meilisearch-provider.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-impl.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/service-impl.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/admin/components/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search-analytics.d.ts","sourceRoot":"","sources":["../../../src/admin/components/search-analytics.tsx"],"names":[],"mappings":"AA4FA,wBAAgB,eAAe,gCA+T9B"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare const analyticsEndpoint: import("better-call").StrictEndpoint<"/admin/search/analytics", {
|
|
2
|
+
method: "GET";
|
|
3
|
+
}, {
|
|
4
|
+
analytics: {
|
|
5
|
+
indexedItems: number;
|
|
6
|
+
totalQueries: number;
|
|
7
|
+
uniqueTerms: number;
|
|
8
|
+
avgResultCount: number;
|
|
9
|
+
zeroResultCount: number;
|
|
10
|
+
zeroResultRate: number;
|
|
11
|
+
clickThroughRate: number;
|
|
12
|
+
avgClickPosition: number;
|
|
13
|
+
};
|
|
14
|
+
}>;
|
|
15
|
+
//# sourceMappingURL=analytics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../../../src/admin/endpoints/analytics.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;EAW7B,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { z } from "@86d-app/core";
|
|
2
|
+
export declare const bulkIndex: import("better-call").StrictEndpoint<"/admin/search/index/bulk", {
|
|
3
|
+
method: "POST";
|
|
4
|
+
body: z.ZodObject<{
|
|
5
|
+
items: z.ZodArray<z.ZodObject<{
|
|
6
|
+
entityType: z.ZodString;
|
|
7
|
+
entityId: z.ZodString;
|
|
8
|
+
title: z.ZodString;
|
|
9
|
+
body: z.ZodOptional<z.ZodString>;
|
|
10
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
11
|
+
url: z.ZodString;
|
|
12
|
+
image: z.ZodOptional<z.ZodString>;
|
|
13
|
+
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
14
|
+
}, z.core.$strip>>;
|
|
15
|
+
}, z.core.$strip>;
|
|
16
|
+
}, {
|
|
17
|
+
indexed: number;
|
|
18
|
+
errors: number;
|
|
19
|
+
}>;
|
|
20
|
+
//# sourceMappingURL=bulk-index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bulk-index.d.ts","sourceRoot":"","sources":["../../../src/admin/endpoints/bulk-index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,CAAC,EAAE,MAAM,eAAe,CAAC;AAGvD,eAAO,MAAM,SAAS;;;;;;;;;;;;;;;;;EA8BrB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"click-analytics.d.ts","sourceRoot":"","sources":["../../../src/admin/endpoints/click-analytics.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,sBAAsB;;;;;EAYlC,CAAC"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare const getSettings: import("better-call").StrictEndpoint<"/admin/search/settings", {
|
|
2
|
+
method: "GET";
|
|
3
|
+
}, {
|
|
4
|
+
meilisearch: {
|
|
5
|
+
configured: boolean;
|
|
6
|
+
host: string | null;
|
|
7
|
+
apiKey: string | null;
|
|
8
|
+
indexUid: string;
|
|
9
|
+
};
|
|
10
|
+
embeddings: {
|
|
11
|
+
configured: boolean;
|
|
12
|
+
provider: string | null;
|
|
13
|
+
model: string;
|
|
14
|
+
};
|
|
15
|
+
indexCount: number;
|
|
16
|
+
}>;
|
|
17
|
+
//# sourceMappingURL=get-settings.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-settings.d.ts","sourceRoot":"","sources":["../../../src/admin/endpoints/get-settings.ts"],"names":[],"mappings":"AAiBA,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;EAsCvB,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { z } from "@86d-app/core";
|
|
2
|
+
export declare const indexItem: import("better-call").StrictEndpoint<"/admin/search/index", {
|
|
3
|
+
method: "POST";
|
|
4
|
+
body: z.ZodObject<{
|
|
5
|
+
entityType: z.ZodString;
|
|
6
|
+
entityId: z.ZodString;
|
|
7
|
+
title: z.ZodString;
|
|
8
|
+
body: z.ZodOptional<z.ZodString>;
|
|
9
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
10
|
+
url: z.ZodString;
|
|
11
|
+
image: z.ZodOptional<z.ZodString>;
|
|
12
|
+
metadata: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
13
|
+
}, z.core.$strip>;
|
|
14
|
+
}, {
|
|
15
|
+
item: import("../..").SearchIndexItem;
|
|
16
|
+
}>;
|
|
17
|
+
export declare const removeFromIndex: import("better-call").StrictEndpoint<"/admin/search/index/remove", {
|
|
18
|
+
method: "POST";
|
|
19
|
+
body: z.ZodObject<{
|
|
20
|
+
entityType: z.ZodString;
|
|
21
|
+
entityId: z.ZodString;
|
|
22
|
+
}, z.core.$strip>;
|
|
23
|
+
}, {
|
|
24
|
+
removed: boolean;
|
|
25
|
+
}>;
|
|
26
|
+
//# sourceMappingURL=index-manage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index-manage.d.ts","sourceRoot":"","sources":["../../../src/admin/endpoints/index-manage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,CAAC,EAAE,MAAM,eAAe,CAAC;AAGvD,eAAO,MAAM,SAAS;;;;;;;;;;;;;;EAuBrB,CAAC;AAEF,eAAO,MAAM,eAAe;;;;;;;;EAiB3B,CAAC"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
export declare const adminEndpoints: {
|
|
2
|
+
"/admin/search/settings": import("better-call").StrictEndpoint<"/admin/search/settings", {
|
|
3
|
+
method: "GET";
|
|
4
|
+
}, {
|
|
5
|
+
meilisearch: {
|
|
6
|
+
configured: boolean;
|
|
7
|
+
host: string | null;
|
|
8
|
+
apiKey: string | null;
|
|
9
|
+
indexUid: string;
|
|
10
|
+
};
|
|
11
|
+
embeddings: {
|
|
12
|
+
configured: boolean;
|
|
13
|
+
provider: string | null;
|
|
14
|
+
model: string;
|
|
15
|
+
};
|
|
16
|
+
indexCount: number;
|
|
17
|
+
}>;
|
|
18
|
+
"/admin/search/analytics": import("better-call").StrictEndpoint<"/admin/search/analytics", {
|
|
19
|
+
method: "GET";
|
|
20
|
+
}, {
|
|
21
|
+
analytics: {
|
|
22
|
+
indexedItems: number;
|
|
23
|
+
totalQueries: number;
|
|
24
|
+
uniqueTerms: number;
|
|
25
|
+
avgResultCount: number;
|
|
26
|
+
zeroResultCount: number;
|
|
27
|
+
zeroResultRate: number;
|
|
28
|
+
clickThroughRate: number;
|
|
29
|
+
avgClickPosition: number;
|
|
30
|
+
};
|
|
31
|
+
}>;
|
|
32
|
+
"/admin/search/popular": import("better-call").StrictEndpoint<"/admin/search/popular", {
|
|
33
|
+
method: "GET";
|
|
34
|
+
query: import("zod").ZodObject<{
|
|
35
|
+
limit: import("zod").ZodOptional<import("zod").ZodCoercedNumber<unknown>>;
|
|
36
|
+
}, import("zod/v4/core").$strip>;
|
|
37
|
+
}, {
|
|
38
|
+
terms: import("../../service").PopularTerm[];
|
|
39
|
+
}>;
|
|
40
|
+
"/admin/search/zero-results": import("better-call").StrictEndpoint<"/admin/search/zero-results", {
|
|
41
|
+
method: "GET";
|
|
42
|
+
query: import("zod").ZodObject<{
|
|
43
|
+
limit: import("zod").ZodOptional<import("zod").ZodCoercedNumber<unknown>>;
|
|
44
|
+
}, import("zod/v4/core").$strip>;
|
|
45
|
+
}, {
|
|
46
|
+
terms: import("../../service").PopularTerm[];
|
|
47
|
+
}>;
|
|
48
|
+
"/admin/search/clicks": import("better-call").StrictEndpoint<"/admin/search/clicks", {
|
|
49
|
+
method: "GET";
|
|
50
|
+
}, {
|
|
51
|
+
clickThroughRate: number;
|
|
52
|
+
avgClickPosition: number;
|
|
53
|
+
}>;
|
|
54
|
+
"/admin/search/synonyms": import("better-call").StrictEndpoint<"/admin/search/synonyms", {
|
|
55
|
+
method: "GET";
|
|
56
|
+
}, {
|
|
57
|
+
synonyms: import("../..").SearchSynonym[];
|
|
58
|
+
}>;
|
|
59
|
+
"/admin/search/synonyms/add": import("better-call").StrictEndpoint<"/admin/search/synonyms/add", {
|
|
60
|
+
method: "POST";
|
|
61
|
+
body: import("zod").ZodObject<{
|
|
62
|
+
term: import("zod").ZodString;
|
|
63
|
+
synonyms: import("zod").ZodArray<import("zod").ZodString>;
|
|
64
|
+
}, import("zod/v4/core").$strip>;
|
|
65
|
+
}, {
|
|
66
|
+
synonym: import("../..").SearchSynonym;
|
|
67
|
+
}>;
|
|
68
|
+
"/admin/search/synonyms/:id/delete": import("better-call").StrictEndpoint<"/admin/search/synonyms/:id/delete", {
|
|
69
|
+
method: "POST";
|
|
70
|
+
params: import("zod").ZodObject<{
|
|
71
|
+
id: import("zod").ZodString;
|
|
72
|
+
}, import("zod/v4/core").$strip>;
|
|
73
|
+
}, {
|
|
74
|
+
error: string;
|
|
75
|
+
status: number;
|
|
76
|
+
success?: never;
|
|
77
|
+
} | {
|
|
78
|
+
success: boolean;
|
|
79
|
+
error?: never;
|
|
80
|
+
status?: never;
|
|
81
|
+
}>;
|
|
82
|
+
"/admin/search/index": import("better-call").StrictEndpoint<"/admin/search/index", {
|
|
83
|
+
method: "POST";
|
|
84
|
+
body: import("zod").ZodObject<{
|
|
85
|
+
entityType: import("zod").ZodString;
|
|
86
|
+
entityId: import("zod").ZodString;
|
|
87
|
+
title: import("zod").ZodString;
|
|
88
|
+
body: import("zod").ZodOptional<import("zod").ZodString>;
|
|
89
|
+
tags: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
|
|
90
|
+
url: import("zod").ZodString;
|
|
91
|
+
image: import("zod").ZodOptional<import("zod").ZodString>;
|
|
92
|
+
metadata: import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodUnknown>>;
|
|
93
|
+
}, import("zod/v4/core").$strip>;
|
|
94
|
+
}, {
|
|
95
|
+
item: import("../..").SearchIndexItem;
|
|
96
|
+
}>;
|
|
97
|
+
"/admin/search/index/remove": import("better-call").StrictEndpoint<"/admin/search/index/remove", {
|
|
98
|
+
method: "POST";
|
|
99
|
+
body: import("zod").ZodObject<{
|
|
100
|
+
entityType: import("zod").ZodString;
|
|
101
|
+
entityId: import("zod").ZodString;
|
|
102
|
+
}, import("zod/v4/core").$strip>;
|
|
103
|
+
}, {
|
|
104
|
+
removed: boolean;
|
|
105
|
+
}>;
|
|
106
|
+
"/admin/search/index/bulk": import("better-call").StrictEndpoint<"/admin/search/index/bulk", {
|
|
107
|
+
method: "POST";
|
|
108
|
+
body: import("zod").ZodObject<{
|
|
109
|
+
items: import("zod").ZodArray<import("zod").ZodObject<{
|
|
110
|
+
entityType: import("zod").ZodString;
|
|
111
|
+
entityId: import("zod").ZodString;
|
|
112
|
+
title: import("zod").ZodString;
|
|
113
|
+
body: import("zod").ZodOptional<import("zod").ZodString>;
|
|
114
|
+
tags: import("zod").ZodOptional<import("zod").ZodArray<import("zod").ZodString>>;
|
|
115
|
+
url: import("zod").ZodString;
|
|
116
|
+
image: import("zod").ZodOptional<import("zod").ZodString>;
|
|
117
|
+
metadata: import("zod").ZodOptional<import("zod").ZodRecord<import("zod").ZodString, import("zod").ZodUnknown>>;
|
|
118
|
+
}, import("zod/v4/core").$strip>>;
|
|
119
|
+
}, import("zod/v4/core").$strip>;
|
|
120
|
+
}, {
|
|
121
|
+
indexed: number;
|
|
122
|
+
errors: number;
|
|
123
|
+
}>;
|
|
124
|
+
};
|
|
125
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/admin/endpoints/index.ts"],"names":[],"mappings":"AASA,eAAO,MAAM,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAY1B,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { z } from "@86d-app/core";
|
|
2
|
+
export declare const popularEndpoint: import("better-call").StrictEndpoint<"/admin/search/popular", {
|
|
3
|
+
method: "GET";
|
|
4
|
+
query: z.ZodObject<{
|
|
5
|
+
limit: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
|
|
6
|
+
}, z.core.$strip>;
|
|
7
|
+
}, {
|
|
8
|
+
terms: import("../../service").PopularTerm[];
|
|
9
|
+
}>;
|
|
10
|
+
//# sourceMappingURL=popular.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"popular.d.ts","sourceRoot":"","sources":["../../../src/admin/endpoints/popular.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,CAAC,EAAE,MAAM,eAAe,CAAC;AAGvD,eAAO,MAAM,eAAe;;;;;;;EAa3B,CAAC"}
|