@open-mercato/search 0.4.2-canary-c02407ff85
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/AGENTS.md +678 -0
- package/build.mjs +92 -0
- package/dist/di.js +157 -0
- package/dist/di.js.map +7 -0
- package/dist/fulltext/drivers/index.js +21 -0
- package/dist/fulltext/drivers/index.js.map +7 -0
- package/dist/fulltext/drivers/meilisearch/index.js +320 -0
- package/dist/fulltext/drivers/meilisearch/index.js.map +7 -0
- package/dist/fulltext/index.js +7 -0
- package/dist/fulltext/index.js.map +7 -0
- package/dist/fulltext/types.js +1 -0
- package/dist/fulltext/types.js.map +7 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +7 -0
- package/dist/indexer/index.js +8 -0
- package/dist/indexer/index.js.map +7 -0
- package/dist/indexer/search-indexer.js +848 -0
- package/dist/indexer/search-indexer.js.map +7 -0
- package/dist/indexer/subscribers/delete.js +41 -0
- package/dist/indexer/subscribers/delete.js.map +7 -0
- package/dist/lib/debug.js +34 -0
- package/dist/lib/debug.js.map +7 -0
- package/dist/lib/fallback-presenter.js +107 -0
- package/dist/lib/fallback-presenter.js.map +7 -0
- package/dist/lib/field-policy.js +75 -0
- package/dist/lib/field-policy.js.map +7 -0
- package/dist/lib/index.js +19 -0
- package/dist/lib/index.js.map +7 -0
- package/dist/lib/merger.js +93 -0
- package/dist/lib/merger.js.map +7 -0
- package/dist/lib/presenter-enricher.js +192 -0
- package/dist/lib/presenter-enricher.js.map +7 -0
- package/dist/modules/search/acl.js +14 -0
- package/dist/modules/search/acl.js.map +7 -0
- package/dist/modules/search/ai-tools.js +284 -0
- package/dist/modules/search/ai-tools.js.map +7 -0
- package/dist/modules/search/api/embeddings/reindex/cancel/route.js +65 -0
- package/dist/modules/search/api/embeddings/reindex/cancel/route.js.map +7 -0
- package/dist/modules/search/api/embeddings/reindex/route.js +165 -0
- package/dist/modules/search/api/embeddings/reindex/route.js.map +7 -0
- package/dist/modules/search/api/embeddings/route.js +246 -0
- package/dist/modules/search/api/embeddings/route.js.map +7 -0
- package/dist/modules/search/api/index/route.js +245 -0
- package/dist/modules/search/api/index/route.js.map +7 -0
- package/dist/modules/search/api/reindex/cancel/route.js +65 -0
- package/dist/modules/search/api/reindex/cancel/route.js.map +7 -0
- package/dist/modules/search/api/reindex/route.js +332 -0
- package/dist/modules/search/api/reindex/route.js.map +7 -0
- package/dist/modules/search/api/search/global/route.js +100 -0
- package/dist/modules/search/api/search/global/route.js.map +7 -0
- package/dist/modules/search/api/search/route.js +101 -0
- package/dist/modules/search/api/search/route.js.map +7 -0
- package/dist/modules/search/api/settings/fulltext/route.js +55 -0
- package/dist/modules/search/api/settings/fulltext/route.js.map +7 -0
- package/dist/modules/search/api/settings/global-search/route.js +80 -0
- package/dist/modules/search/api/settings/global-search/route.js.map +7 -0
- package/dist/modules/search/api/settings/route.js +118 -0
- package/dist/modules/search/api/settings/route.js.map +7 -0
- package/dist/modules/search/api/settings/vector-store/route.js +77 -0
- package/dist/modules/search/api/settings/vector-store/route.js.map +7 -0
- package/dist/modules/search/backend/config/search/page.js +10 -0
- package/dist/modules/search/backend/config/search/page.js.map +7 -0
- package/dist/modules/search/backend/config/search/page.meta.js +24 -0
- package/dist/modules/search/backend/config/search/page.meta.js.map +7 -0
- package/dist/modules/search/cli.js +698 -0
- package/dist/modules/search/cli.js.map +7 -0
- package/dist/modules/search/di.js +32 -0
- package/dist/modules/search/di.js.map +7 -0
- package/dist/modules/search/frontend/components/GlobalSearchDialog.js +357 -0
- package/dist/modules/search/frontend/components/GlobalSearchDialog.js.map +7 -0
- package/dist/modules/search/frontend/components/HybridSearchTable.js +343 -0
- package/dist/modules/search/frontend/components/HybridSearchTable.js.map +7 -0
- package/dist/modules/search/frontend/components/SearchSettingsPageClient.js +303 -0
- package/dist/modules/search/frontend/components/SearchSettingsPageClient.js.map +7 -0
- package/dist/modules/search/frontend/components/sections/FulltextSearchSection.js +360 -0
- package/dist/modules/search/frontend/components/sections/FulltextSearchSection.js.map +7 -0
- package/dist/modules/search/frontend/components/sections/GlobalSearchSection.js +101 -0
- package/dist/modules/search/frontend/components/sections/GlobalSearchSection.js.map +7 -0
- package/dist/modules/search/frontend/components/sections/VectorSearchSection.js +608 -0
- package/dist/modules/search/frontend/components/sections/VectorSearchSection.js.map +7 -0
- package/dist/modules/search/frontend/index.js +9 -0
- package/dist/modules/search/frontend/index.js.map +7 -0
- package/dist/modules/search/frontend/utils.js +41 -0
- package/dist/modules/search/frontend/utils.js.map +7 -0
- package/dist/modules/search/i18n/de.json +61 -0
- package/dist/modules/search/i18n/en.json +72 -0
- package/dist/modules/search/i18n/es.json +61 -0
- package/dist/modules/search/i18n/pl.json +61 -0
- package/dist/modules/search/index.js +11 -0
- package/dist/modules/search/index.js.map +7 -0
- package/dist/modules/search/lib/auto-indexing.js +29 -0
- package/dist/modules/search/lib/auto-indexing.js.map +7 -0
- package/dist/modules/search/lib/embedding-config.js +131 -0
- package/dist/modules/search/lib/embedding-config.js.map +7 -0
- package/dist/modules/search/lib/global-search-config.js +45 -0
- package/dist/modules/search/lib/global-search-config.js.map +7 -0
- package/dist/modules/search/lib/reindex-lock.js +99 -0
- package/dist/modules/search/lib/reindex-lock.js.map +7 -0
- package/dist/modules/search/subscribers/fulltext_upsert.js +64 -0
- package/dist/modules/search/subscribers/fulltext_upsert.js.map +7 -0
- package/dist/modules/search/subscribers/vector_delete.js +58 -0
- package/dist/modules/search/subscribers/vector_delete.js.map +7 -0
- package/dist/modules/search/subscribers/vector_purge.js +142 -0
- package/dist/modules/search/subscribers/vector_purge.js.map +7 -0
- package/dist/modules/search/subscribers/vector_upsert.js +58 -0
- package/dist/modules/search/subscribers/vector_upsert.js.map +7 -0
- package/dist/modules/search/workers/fulltext-index.worker.js +240 -0
- package/dist/modules/search/workers/fulltext-index.worker.js.map +7 -0
- package/dist/modules/search/workers/vector-index.worker.js +234 -0
- package/dist/modules/search/workers/vector-index.worker.js.map +7 -0
- package/dist/queue/fulltext-indexing.js +15 -0
- package/dist/queue/fulltext-indexing.js.map +7 -0
- package/dist/queue/index.js +3 -0
- package/dist/queue/index.js.map +7 -0
- package/dist/queue/vector-indexing.js +15 -0
- package/dist/queue/vector-indexing.js.map +7 -0
- package/dist/service.js +286 -0
- package/dist/service.js.map +7 -0
- package/dist/strategies/fulltext.strategy.js +116 -0
- package/dist/strategies/fulltext.strategy.js.map +7 -0
- package/dist/strategies/index.js +12 -0
- package/dist/strategies/index.js.map +7 -0
- package/dist/strategies/token.strategy.js +80 -0
- package/dist/strategies/token.strategy.js.map +7 -0
- package/dist/strategies/vector.strategy.js +137 -0
- package/dist/strategies/vector.strategy.js.map +7 -0
- package/dist/types.js +1 -0
- package/dist/types.js.map +7 -0
- package/dist/vector/drivers/chromadb/index.js +44 -0
- package/dist/vector/drivers/chromadb/index.js.map +7 -0
- package/dist/vector/drivers/index.js +9 -0
- package/dist/vector/drivers/index.js.map +7 -0
- package/dist/vector/drivers/pgvector/index.js +509 -0
- package/dist/vector/drivers/pgvector/index.js.map +7 -0
- package/dist/vector/drivers/qdrant/index.js +44 -0
- package/dist/vector/drivers/qdrant/index.js.map +7 -0
- package/dist/vector/index.js +4 -0
- package/dist/vector/index.js.map +7 -0
- package/dist/vector/lib/vector-logs.js +33 -0
- package/dist/vector/lib/vector-logs.js.map +7 -0
- package/dist/vector/services/checksum.js +20 -0
- package/dist/vector/services/checksum.js.map +7 -0
- package/dist/vector/services/embedding.js +222 -0
- package/dist/vector/services/embedding.js.map +7 -0
- package/dist/vector/services/index.js +4 -0
- package/dist/vector/services/index.js.map +7 -0
- package/dist/vector/services/vector-index.service.js +960 -0
- package/dist/vector/services/vector-index.service.js.map +7 -0
- package/dist/vector/types/pg.d.js +1 -0
- package/dist/vector/types/pg.d.js.map +7 -0
- package/dist/vector/types.js +75 -0
- package/dist/vector/types.js.map +7 -0
- package/jest.config.cjs +19 -0
- package/package.json +142 -0
- package/src/__tests__/queue.test.ts +148 -0
- package/src/__tests__/service.test.ts +345 -0
- package/src/__tests__/workers.test.ts +319 -0
- package/src/di.ts +291 -0
- package/src/fulltext/drivers/index.ts +41 -0
- package/src/fulltext/drivers/meilisearch/index.ts +410 -0
- package/src/fulltext/index.ts +13 -0
- package/src/fulltext/types.ts +115 -0
- package/src/index.ts +36 -0
- package/src/indexer/index.ts +13 -0
- package/src/indexer/search-indexer.ts +1141 -0
- package/src/indexer/subscribers/delete.ts +49 -0
- package/src/lib/debug.ts +46 -0
- package/src/lib/fallback-presenter.ts +106 -0
- package/src/lib/field-policy.ts +169 -0
- package/src/lib/index.ts +13 -0
- package/src/lib/merger.ts +159 -0
- package/src/lib/presenter-enricher.ts +323 -0
- package/src/modules/search/README.md +694 -0
- package/src/modules/search/acl.ts +10 -0
- package/src/modules/search/ai-tools.ts +467 -0
- package/src/modules/search/api/embeddings/reindex/cancel/route.ts +77 -0
- package/src/modules/search/api/embeddings/reindex/route.ts +197 -0
- package/src/modules/search/api/embeddings/route.ts +304 -0
- package/src/modules/search/api/index/route.ts +297 -0
- package/src/modules/search/api/reindex/cancel/route.ts +77 -0
- package/src/modules/search/api/reindex/route.ts +419 -0
- package/src/modules/search/api/search/global/route.ts +120 -0
- package/src/modules/search/api/search/route.ts +121 -0
- package/src/modules/search/api/settings/fulltext/route.ts +82 -0
- package/src/modules/search/api/settings/global-search/route.ts +91 -0
- package/src/modules/search/api/settings/route.ts +187 -0
- package/src/modules/search/api/settings/vector-store/route.ts +105 -0
- package/src/modules/search/backend/config/search/page.meta.ts +22 -0
- package/src/modules/search/backend/config/search/page.tsx +12 -0
- package/src/modules/search/cli.ts +818 -0
- package/src/modules/search/di.ts +50 -0
- package/src/modules/search/frontend/components/GlobalSearchDialog.tsx +436 -0
- package/src/modules/search/frontend/components/HybridSearchTable.tsx +418 -0
- package/src/modules/search/frontend/components/SearchSettingsPageClient.tsx +476 -0
- package/src/modules/search/frontend/components/sections/FulltextSearchSection.tsx +624 -0
- package/src/modules/search/frontend/components/sections/GlobalSearchSection.tsx +124 -0
- package/src/modules/search/frontend/components/sections/VectorSearchSection.tsx +943 -0
- package/src/modules/search/frontend/index.ts +3 -0
- package/src/modules/search/frontend/utils.ts +82 -0
- package/src/modules/search/i18n/de.json +61 -0
- package/src/modules/search/i18n/en.json +72 -0
- package/src/modules/search/i18n/es.json +61 -0
- package/src/modules/search/i18n/pl.json +61 -0
- package/src/modules/search/index.ts +9 -0
- package/src/modules/search/lib/auto-indexing.ts +35 -0
- package/src/modules/search/lib/embedding-config.ts +161 -0
- package/src/modules/search/lib/global-search-config.ts +69 -0
- package/src/modules/search/lib/reindex-lock.ts +201 -0
- package/src/modules/search/subscribers/fulltext_upsert.ts +83 -0
- package/src/modules/search/subscribers/vector_delete.ts +75 -0
- package/src/modules/search/subscribers/vector_purge.ts +161 -0
- package/src/modules/search/subscribers/vector_upsert.ts +75 -0
- package/src/modules/search/workers/fulltext-index.worker.ts +318 -0
- package/src/modules/search/workers/vector-index.worker.ts +292 -0
- package/src/queue/fulltext-indexing.ts +87 -0
- package/src/queue/index.ts +2 -0
- package/src/queue/vector-indexing.ts +66 -0
- package/src/service.ts +397 -0
- package/src/strategies/fulltext.strategy.ts +155 -0
- package/src/strategies/index.ts +17 -0
- package/src/strategies/token.strategy.ts +153 -0
- package/src/strategies/vector.strategy.ts +234 -0
- package/src/types.ts +38 -0
- package/src/vector/drivers/chromadb/index.ts +49 -0
- package/src/vector/drivers/index.ts +4 -0
- package/src/vector/drivers/pgvector/index.ts +627 -0
- package/src/vector/drivers/qdrant/index.ts +49 -0
- package/src/vector/index.ts +3 -0
- package/src/vector/lib/vector-logs.ts +46 -0
- package/src/vector/services/checksum.ts +18 -0
- package/src/vector/services/embedding.ts +275 -0
- package/src/vector/services/index.ts +3 -0
- package/src/vector/services/vector-index.service.ts +1234 -0
- package/src/vector/types/pg.d.ts +1 -0
- package/src/vector/types.ts +220 -0
- package/tsconfig.json +9 -0
- package/watch.mjs +6 -0
|
@@ -0,0 +1,694 @@
|
|
|
1
|
+
# Search Module
|
|
2
|
+
|
|
3
|
+
The search module provides unified search capabilities across all entities in Open Mercato, supporting multiple search strategies including Meilisearch (full-text) and vector embeddings (semantic search).
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Multi-strategy search**: Combines full-text search (Meilisearch), vector-based semantic search, and token matching
|
|
8
|
+
- **Automatic indexing**: Subscribes to entity events for real-time index updates
|
|
9
|
+
- **Queue-based processing**: Supports async batch processing via Redis/BullMQ for high-volume indexing
|
|
10
|
+
- **Configurable embeddings**: Supports OpenAI, Ollama, and other embedding providers
|
|
11
|
+
- **Tenant-scoped**: All indexes are scoped by tenant and optionally by organization
|
|
12
|
+
- **Admin-configurable**: Global search (Cmd+K) strategies can be configured per-tenant
|
|
13
|
+
|
|
14
|
+
## Global Search Settings
|
|
15
|
+
|
|
16
|
+
The global search dialog (Cmd+K) can be configured by administrators to control which search strategies are used. This configuration is stored per-tenant.
|
|
17
|
+
|
|
18
|
+
### Admin Configuration
|
|
19
|
+
|
|
20
|
+
Navigate to **Settings > Search** to configure which search methods are enabled for Cmd+K:
|
|
21
|
+
|
|
22
|
+
- **Full-Text Search**: Fast, typo-tolerant search powered by Meilisearch
|
|
23
|
+
- **Semantic Search (AI)**: AI-powered search that understands meaning (requires embedding provider)
|
|
24
|
+
- **Keyword Search**: Exact word matching in the database
|
|
25
|
+
|
|
26
|
+
### API Endpoint
|
|
27
|
+
|
|
28
|
+
**`GET /api/search/settings/global-search`**
|
|
29
|
+
|
|
30
|
+
Returns the currently enabled strategies for global search.
|
|
31
|
+
|
|
32
|
+
**Response:**
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"enabledStrategies": ["fulltext", "vector", "tokens"]
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
**`POST /api/search/settings/global-search`**
|
|
40
|
+
|
|
41
|
+
Updates the enabled strategies.
|
|
42
|
+
|
|
43
|
+
**Request:**
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"enabledStrategies": ["fulltext", "vector"]
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**Permissions:**
|
|
51
|
+
- GET requires `search.view`
|
|
52
|
+
- POST requires `search.manage`
|
|
53
|
+
|
|
54
|
+
## Programmatic Integration (DI)
|
|
55
|
+
|
|
56
|
+
Other modules can use the search functionality by resolving services from the DI container.
|
|
57
|
+
|
|
58
|
+
### SearchService
|
|
59
|
+
|
|
60
|
+
The primary service for executing searches and managing indexes:
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
import type { SearchService } from '@open-mercato/search'
|
|
64
|
+
|
|
65
|
+
// Resolve from DI container
|
|
66
|
+
const searchService = container.resolve('searchService') as SearchService
|
|
67
|
+
|
|
68
|
+
// Execute a search
|
|
69
|
+
const results = await searchService.search('john doe', {
|
|
70
|
+
tenantId: 'tenant-123',
|
|
71
|
+
organizationId: 'org-456', // optional
|
|
72
|
+
limit: 20,
|
|
73
|
+
strategies: ['fulltext', 'vector'], // optional - defaults to all available
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
// Index a record
|
|
77
|
+
await searchService.index({
|
|
78
|
+
entityId: 'customers:customer_person_profile',
|
|
79
|
+
recordId: 'rec-123',
|
|
80
|
+
tenantId: 'tenant-123',
|
|
81
|
+
organizationId: 'org-456',
|
|
82
|
+
fields: { name: 'John Doe', email: 'john@example.com' },
|
|
83
|
+
presenter: { title: 'John Doe', subtitle: 'Customer' },
|
|
84
|
+
url: '/backend/customers/people/rec-123',
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
// Bulk index multiple records
|
|
88
|
+
await searchService.bulkIndex([record1, record2, record3])
|
|
89
|
+
|
|
90
|
+
// Delete from all indexes
|
|
91
|
+
await searchService.delete('customers:customer_person_profile', 'rec-123', 'tenant-123')
|
|
92
|
+
|
|
93
|
+
// Purge all records for an entity type
|
|
94
|
+
await searchService.purge('customers:customer_person_profile', 'tenant-123')
|
|
95
|
+
|
|
96
|
+
// Check strategy availability
|
|
97
|
+
const isAvailable = await searchService.isStrategyAvailable('fulltext')
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### SearchIndexer (Higher-Level API)
|
|
101
|
+
|
|
102
|
+
For config-aware indexing with automatic presenter/URL resolution:
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
import type { SearchIndexer } from '@open-mercato/search'
|
|
106
|
+
|
|
107
|
+
const searchIndexer = container.resolve('searchIndexer') as SearchIndexer
|
|
108
|
+
|
|
109
|
+
// Index with automatic config-based formatting
|
|
110
|
+
await searchIndexer.indexRecord({
|
|
111
|
+
entityId: 'customers:customer_person_profile',
|
|
112
|
+
recordId: 'rec-123',
|
|
113
|
+
tenantId: 'tenant-123',
|
|
114
|
+
organizationId: 'org-456',
|
|
115
|
+
record: { id: 'rec-123', name: 'John Doe', email: 'john@example.com' },
|
|
116
|
+
customFields: { priority: 'high' },
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
// Check if entity is enabled for search
|
|
120
|
+
if (searchIndexer.isEntityEnabled('customers:customer_person_profile')) {
|
|
121
|
+
// Entity is configured for indexing
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// List all search-enabled entities
|
|
125
|
+
const entities = searchIndexer.listEnabledEntities()
|
|
126
|
+
|
|
127
|
+
// Reindex to Meilisearch with queue support
|
|
128
|
+
const result = await searchIndexer.reindexEntityToMeilisearch({
|
|
129
|
+
entityId: 'customers:customer_person_profile',
|
|
130
|
+
tenantId: 'tenant-123',
|
|
131
|
+
organizationId: 'org-456',
|
|
132
|
+
recreateIndex: true,
|
|
133
|
+
useQueue: true, // Use async queue if available
|
|
134
|
+
})
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### SearchService Methods
|
|
138
|
+
|
|
139
|
+
| Method | Description |
|
|
140
|
+
|--------|-------------|
|
|
141
|
+
| `search(query, options)` | Execute search across strategies |
|
|
142
|
+
| `index(record)` | Index a single record |
|
|
143
|
+
| `bulkIndex(records)` | Bulk index multiple records |
|
|
144
|
+
| `delete(entityId, recordId, tenantId)` | Delete from all strategies |
|
|
145
|
+
| `purge(entityId, tenantId)` | Purge all records for entity type |
|
|
146
|
+
| `registerStrategy(strategy)` | Add custom strategy at runtime |
|
|
147
|
+
| `unregisterStrategy(strategyId)` | Remove a strategy |
|
|
148
|
+
| `getRegisteredStrategies()` | List registered strategy IDs |
|
|
149
|
+
| `getStrategy(strategyId)` | Get specific strategy instance |
|
|
150
|
+
| `isStrategyAvailable(strategyId)` | Check strategy availability |
|
|
151
|
+
|
|
152
|
+
### SearchIndexer Methods
|
|
153
|
+
|
|
154
|
+
| Method | Description |
|
|
155
|
+
|--------|-------------|
|
|
156
|
+
| `indexRecord(params)` | Index with config-based formatting |
|
|
157
|
+
| `deleteRecord(params)` | Delete with config handling |
|
|
158
|
+
| `bulkIndexRecords(params[])` | Bulk index with formatting |
|
|
159
|
+
| `purgeEntity(params)` | Purge entity type from indexes |
|
|
160
|
+
| `reindexEntityToMeilisearch(params)` | Reindex single entity |
|
|
161
|
+
| `reindexAllToMeilisearch(params)` | Reindex all entities |
|
|
162
|
+
| `getEntityConfig(entityId)` | Get entity search configuration |
|
|
163
|
+
| `isEntityEnabled(entityId)` | Check if entity is search-enabled |
|
|
164
|
+
| `listEnabledEntities()` | List all enabled entities |
|
|
165
|
+
|
|
166
|
+
## REST API
|
|
167
|
+
|
|
168
|
+
### Search Endpoint
|
|
169
|
+
|
|
170
|
+
**`GET /api/search`**
|
|
171
|
+
|
|
172
|
+
Execute a search query via HTTP.
|
|
173
|
+
|
|
174
|
+
**Query Parameters:**
|
|
175
|
+
|
|
176
|
+
| Parameter | Type | Required | Description |
|
|
177
|
+
|-----------|------|----------|-------------|
|
|
178
|
+
| `q` | string | Yes | Search query |
|
|
179
|
+
| `limit` | number | No | Max results (default: 50, max: 100) |
|
|
180
|
+
| `strategies` | string | No | Comma-separated strategy IDs (e.g., `fulltext,vector`) |
|
|
181
|
+
|
|
182
|
+
**Headers:**
|
|
183
|
+
- Requires authentication (session cookie or bearer token)
|
|
184
|
+
- Requires `search.view` feature permission
|
|
185
|
+
|
|
186
|
+
**Example Request:**
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
curl -X GET "https://your-app.com/api/search?q=john%20doe&limit=20" \
|
|
190
|
+
-H "Authorization: Bearer <token>"
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**Response:**
|
|
194
|
+
|
|
195
|
+
```json
|
|
196
|
+
{
|
|
197
|
+
"results": [
|
|
198
|
+
{
|
|
199
|
+
"entityId": "customers:customer_person_profile",
|
|
200
|
+
"recordId": "rec-123",
|
|
201
|
+
"score": 0.95,
|
|
202
|
+
"source": "fulltext",
|
|
203
|
+
"presenter": {
|
|
204
|
+
"title": "John Doe",
|
|
205
|
+
"subtitle": "Customer",
|
|
206
|
+
"icon": "user"
|
|
207
|
+
},
|
|
208
|
+
"url": "/backend/customers/people/rec-123",
|
|
209
|
+
"links": [
|
|
210
|
+
{ "label": "View", "url": "/backend/customers/people/rec-123" }
|
|
211
|
+
]
|
|
212
|
+
}
|
|
213
|
+
],
|
|
214
|
+
"strategiesUsed": ["fulltext", "vector"],
|
|
215
|
+
"timing": 45,
|
|
216
|
+
"query": "john doe",
|
|
217
|
+
"limit": 20
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
**Error Responses:**
|
|
222
|
+
|
|
223
|
+
| Status | Description |
|
|
224
|
+
|--------|-------------|
|
|
225
|
+
| 400 | Missing query parameter |
|
|
226
|
+
| 401 | Unauthorized |
|
|
227
|
+
| 503 | Search service unavailable |
|
|
228
|
+
|
|
229
|
+
### Other API Endpoints
|
|
230
|
+
|
|
231
|
+
| Endpoint | Method | Description |
|
|
232
|
+
|----------|--------|-------------|
|
|
233
|
+
| `/api/search/settings/global-search` | GET | Get global search strategy configuration |
|
|
234
|
+
| `/api/search/settings/global-search` | POST | Update global search strategy configuration |
|
|
235
|
+
| `/api/search/reindex` | POST | Trigger full-text search reindex |
|
|
236
|
+
| `/api/search/embeddings/reindex` | POST | Trigger vector embeddings reindex |
|
|
237
|
+
| `/api/search/embeddings/status` | GET | Get vector indexing status |
|
|
238
|
+
| `/api/search/embeddings/config` | POST | Update embedding configuration |
|
|
239
|
+
| `/api/search/index` | GET | List indexed entries |
|
|
240
|
+
| `/api/search/index` | DELETE | Purge vector index |
|
|
241
|
+
|
|
242
|
+
## Types
|
|
243
|
+
|
|
244
|
+
### SearchOptions
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
interface SearchOptions {
|
|
248
|
+
tenantId: string
|
|
249
|
+
organizationId?: string | null
|
|
250
|
+
limit?: number
|
|
251
|
+
strategies?: SearchStrategyId[] // 'fulltext' | 'vector' | 'tokens'
|
|
252
|
+
entityTypes?: string[] // Filter by entity types
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### SearchResult
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
interface SearchResult {
|
|
260
|
+
entityId: string // e.g., 'customers:customer_person_profile'
|
|
261
|
+
recordId: string // Primary key of the record
|
|
262
|
+
score: number // Relevance score (0-1)
|
|
263
|
+
source: SearchStrategyId // Which strategy returned this result
|
|
264
|
+
presenter?: {
|
|
265
|
+
title: string // Display title
|
|
266
|
+
subtitle?: string // Secondary text
|
|
267
|
+
icon?: string // Icon identifier
|
|
268
|
+
}
|
|
269
|
+
url?: string // Link to the record
|
|
270
|
+
links?: Array<{
|
|
271
|
+
label: string
|
|
272
|
+
url: string
|
|
273
|
+
}>
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### IndexableRecord
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
interface IndexableRecord {
|
|
281
|
+
entityId: string
|
|
282
|
+
recordId: string
|
|
283
|
+
tenantId: string
|
|
284
|
+
organizationId?: string | null
|
|
285
|
+
fields: Record<string, unknown> // Searchable field values
|
|
286
|
+
presenter?: {
|
|
287
|
+
title: string
|
|
288
|
+
subtitle?: string
|
|
289
|
+
icon?: string
|
|
290
|
+
}
|
|
291
|
+
url?: string
|
|
292
|
+
links?: Array<{ label: string; url: string }>
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
## CLI Commands
|
|
297
|
+
|
|
298
|
+
The search module exposes CLI commands via `yarn mercato search <command>`.
|
|
299
|
+
|
|
300
|
+
### Status
|
|
301
|
+
|
|
302
|
+
Show search module status and available strategies:
|
|
303
|
+
|
|
304
|
+
```bash
|
|
305
|
+
yarn mercato search status
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### Query
|
|
309
|
+
|
|
310
|
+
Execute a search query:
|
|
311
|
+
|
|
312
|
+
```bash
|
|
313
|
+
yarn mercato search query --query "search terms" --tenant <id> [options]
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
Options:
|
|
317
|
+
- `--query, -q` - Search query (required)
|
|
318
|
+
- `--tenant` - Tenant ID (required)
|
|
319
|
+
- `--org` - Organization ID (optional)
|
|
320
|
+
- `--entity` - Entity types to search (comma-separated)
|
|
321
|
+
- `--strategy` - Strategies to use (comma-separated: fulltext, vector, tokens)
|
|
322
|
+
- `--limit` - Max results (default: 20)
|
|
323
|
+
|
|
324
|
+
### Index
|
|
325
|
+
|
|
326
|
+
Index a specific record:
|
|
327
|
+
|
|
328
|
+
```bash
|
|
329
|
+
yarn mercato search index --entity <entityId> --record <recordId> --tenant <tenantId>
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
Options:
|
|
333
|
+
- `--entity` - Entity ID (e.g., `customers:customer_person_profile`)
|
|
334
|
+
- `--record` - Record ID
|
|
335
|
+
- `--tenant` - Tenant ID
|
|
336
|
+
- `--org` - Organization ID (optional)
|
|
337
|
+
|
|
338
|
+
### Reindex
|
|
339
|
+
|
|
340
|
+
Reindex vector embeddings for entities:
|
|
341
|
+
|
|
342
|
+
```bash
|
|
343
|
+
yarn mercato search reindex --tenant <id> [options]
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
Options:
|
|
347
|
+
- `--tenant <id>` - Tenant scope (required for purge & coverage)
|
|
348
|
+
- `--org <id>` - Organization scope (requires tenant)
|
|
349
|
+
- `--entity <module:entity>` - Reindex a single entity (defaults to all enabled entities)
|
|
350
|
+
- `--partitions <n>` - Number of partitions to process in parallel
|
|
351
|
+
- `--partition <idx>` - Restrict to a specific partition index
|
|
352
|
+
- `--batch <n>` - Override batch size per chunk
|
|
353
|
+
- `--force` - Force reindex even if another job is running
|
|
354
|
+
- `--purgeFirst` - Purge vector rows before reindexing
|
|
355
|
+
- `--skipPurge` - Explicitly skip purging vector rows
|
|
356
|
+
- `--skipResetCoverage` - Keep existing coverage snapshots
|
|
357
|
+
|
|
358
|
+
Use `yarn mercato search reindex-help` for detailed options.
|
|
359
|
+
|
|
360
|
+
### Test Meilisearch
|
|
361
|
+
|
|
362
|
+
Test the Meilisearch connection:
|
|
363
|
+
|
|
364
|
+
```bash
|
|
365
|
+
yarn mercato search test-meilisearch
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### Worker
|
|
369
|
+
|
|
370
|
+
Start a queue worker for processing search indexing jobs:
|
|
371
|
+
|
|
372
|
+
```bash
|
|
373
|
+
yarn mercato search worker <queue-name> [options]
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
Available queues:
|
|
377
|
+
- `vector-indexing` - Process vector embedding indexing jobs
|
|
378
|
+
- `fulltext-indexing` - Process Meilisearch batch indexing jobs
|
|
379
|
+
|
|
380
|
+
Options:
|
|
381
|
+
- `--concurrency <n>` - Number of concurrent jobs to process (default: 1)
|
|
382
|
+
|
|
383
|
+
Examples:
|
|
384
|
+
```bash
|
|
385
|
+
# Start vector indexing worker with 10 concurrent jobs
|
|
386
|
+
yarn mercato search worker vector-indexing --concurrency=10
|
|
387
|
+
|
|
388
|
+
# Start Meilisearch indexing worker with 5 concurrent jobs
|
|
389
|
+
yarn mercato search worker fulltext-indexing --concurrency=5
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
**Requirements:**
|
|
393
|
+
- `QUEUE_STRATEGY=async` must be set in environment
|
|
394
|
+
- Redis must be configured via `REDIS_URL` or `QUEUE_REDIS_URL`
|
|
395
|
+
|
|
396
|
+
### Help
|
|
397
|
+
|
|
398
|
+
Show all available commands:
|
|
399
|
+
|
|
400
|
+
```bash
|
|
401
|
+
yarn mercato search help
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
## Queue Configuration
|
|
405
|
+
|
|
406
|
+
The search module supports two queue strategies:
|
|
407
|
+
|
|
408
|
+
### Local Queue (Development)
|
|
409
|
+
|
|
410
|
+
File-based queue stored in `.queue/` directory. No additional configuration required.
|
|
411
|
+
|
|
412
|
+
```env
|
|
413
|
+
QUEUE_STRATEGY=local
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
### Async Queue (Production)
|
|
417
|
+
|
|
418
|
+
Redis-based queue using BullMQ for distributed processing.
|
|
419
|
+
|
|
420
|
+
```env
|
|
421
|
+
QUEUE_STRATEGY=async
|
|
422
|
+
REDIS_URL=redis://localhost:6379
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
When using async queues, start workers in separate processes:
|
|
426
|
+
|
|
427
|
+
```bash
|
|
428
|
+
# Terminal 1: Start vector indexing worker
|
|
429
|
+
yarn mercato search worker vector-indexing --concurrency=10
|
|
430
|
+
|
|
431
|
+
# Terminal 2: Start Meilisearch indexing worker
|
|
432
|
+
yarn mercato search worker fulltext-indexing --concurrency=5
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
**Note:** If no workers are running when a reindex is triggered, the API will automatically fall back to synchronous processing and display a warning.
|
|
436
|
+
|
|
437
|
+
## Environment Variables
|
|
438
|
+
|
|
439
|
+
| Variable | Description | Default |
|
|
440
|
+
|----------|-------------|---------|
|
|
441
|
+
| `MEILISEARCH_HOST` | Meilisearch server URL | - |
|
|
442
|
+
| `MEILISEARCH_API_KEY` | Meilisearch API key | - |
|
|
443
|
+
| `OPENAI_API_KEY` | OpenAI API key for embeddings | - |
|
|
444
|
+
| `OM_SEARCH_ENABLED` | Enable/disable search module | `true` |
|
|
445
|
+
| `OM_SEARCH_DEBUG` | Enable debug logging for search module | `false` |
|
|
446
|
+
| `SEARCH_EXCLUDE_ENCRYPTED_FIELDS` | Exclude encrypted fields from Meilisearch indexing | `false` |
|
|
447
|
+
| `QUEUE_STRATEGY` | Queue strategy (`local` or `async`) | `local` |
|
|
448
|
+
| `REDIS_URL` | Redis connection URL for async queues | - |
|
|
449
|
+
| `QUEUE_REDIS_URL` | Alternative Redis URL for queues | - |
|
|
450
|
+
|
|
451
|
+
### Debug Logging
|
|
452
|
+
|
|
453
|
+
Set `OM_SEARCH_DEBUG=true` to enable verbose debug logging for the search module. This outputs detailed information about indexing operations, strategy selection, and error handling. Errors are always logged regardless of this flag.
|
|
454
|
+
|
|
455
|
+
```env
|
|
456
|
+
OM_SEARCH_DEBUG=true
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
### Encrypted Field Exclusion
|
|
460
|
+
|
|
461
|
+
By default, all fields (including decrypted values of encrypted fields) are indexed into Meilisearch for full-text search. For security-sensitive deployments, you can exclude encrypted fields from indexing by setting:
|
|
462
|
+
|
|
463
|
+
```env
|
|
464
|
+
SEARCH_EXCLUDE_ENCRYPTED_FIELDS=true
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
When enabled, fields defined in `encryption_maps` are automatically excluded from the Meilisearch index. This includes fields like:
|
|
468
|
+
- Customer PII: `display_name`, `primary_email`, `primary_phone`
|
|
469
|
+
- Person profiles: `first_name`, `last_name`, `job_title`
|
|
470
|
+
- Company profiles: `legal_name`, `brand_name`, `domain`
|
|
471
|
+
- Comments and activities: `body`, `subject`
|
|
472
|
+
- And other fields configured in the encryption maps
|
|
473
|
+
|
|
474
|
+
**Note:** This only affects Meilisearch indexing. Vector search uses its own field configuration via `buildSource`. Consider adjusting your `buildSource` implementation if you also need to exclude sensitive fields from vector embeddings.
|
|
475
|
+
|
|
476
|
+
## Configuring Entities for Search
|
|
477
|
+
|
|
478
|
+
Each module can define which entities are searchable by creating a `search.ts` file in the module root.
|
|
479
|
+
|
|
480
|
+
### Entity Configuration Structure
|
|
481
|
+
|
|
482
|
+
```typescript
|
|
483
|
+
// packages/your-package/src/modules/your-module/search.ts
|
|
484
|
+
import type {
|
|
485
|
+
SearchModuleConfig,
|
|
486
|
+
SearchBuildContext,
|
|
487
|
+
SearchIndexSource,
|
|
488
|
+
SearchResultPresenter,
|
|
489
|
+
SearchResultLink,
|
|
490
|
+
} from '@open-mercato/shared/modules/search'
|
|
491
|
+
|
|
492
|
+
export const searchConfig: SearchModuleConfig = {
|
|
493
|
+
entities: [
|
|
494
|
+
{
|
|
495
|
+
entityId: 'your_module:your_entity', // Must match entity registry
|
|
496
|
+
enabled: true,
|
|
497
|
+
priority: 10, // Higher = appears first in mixed results
|
|
498
|
+
|
|
499
|
+
// FOR VECTOR SEARCH: buildSource generates text for embeddings
|
|
500
|
+
buildSource: async (ctx: SearchBuildContext): Promise<SearchIndexSource | null> => {
|
|
501
|
+
const lines: string[] = []
|
|
502
|
+
|
|
503
|
+
// Add text that should be searchable semantically
|
|
504
|
+
lines.push(`Name: ${ctx.record.name}`)
|
|
505
|
+
lines.push(`Description: ${ctx.record.description}`)
|
|
506
|
+
|
|
507
|
+
// Include custom fields
|
|
508
|
+
if (ctx.customFields.notes) {
|
|
509
|
+
lines.push(`Notes: ${ctx.customFields.notes}`)
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
if (!lines.length) return null
|
|
513
|
+
|
|
514
|
+
return {
|
|
515
|
+
text: lines, // This text gets embedded for vector search
|
|
516
|
+
presenter: {
|
|
517
|
+
title: ctx.record.name,
|
|
518
|
+
subtitle: ctx.record.description,
|
|
519
|
+
icon: 'lucide:file',
|
|
520
|
+
},
|
|
521
|
+
links: [
|
|
522
|
+
{ href: `/your-entity/${ctx.record.id}`, label: 'View', kind: 'primary' }
|
|
523
|
+
],
|
|
524
|
+
checksumSource: { record: ctx.record, customFields: ctx.customFields },
|
|
525
|
+
}
|
|
526
|
+
},
|
|
527
|
+
|
|
528
|
+
// FOR MEILISEARCH: fieldPolicy controls full-text indexing
|
|
529
|
+
fieldPolicy: {
|
|
530
|
+
searchable: ['name', 'description', 'notes'], // Indexed for full-text
|
|
531
|
+
hashOnly: ['email', 'phone'], // Hashed, not searchable
|
|
532
|
+
excluded: ['password', 'secret'], // Never indexed
|
|
533
|
+
},
|
|
534
|
+
|
|
535
|
+
// Optional: Custom presenter formatting
|
|
536
|
+
formatResult: async (ctx: SearchBuildContext): Promise<SearchResultPresenter | null> => {
|
|
537
|
+
return {
|
|
538
|
+
title: ctx.record.name,
|
|
539
|
+
subtitle: ctx.record.status,
|
|
540
|
+
icon: 'lucide:user',
|
|
541
|
+
}
|
|
542
|
+
},
|
|
543
|
+
|
|
544
|
+
// Optional: Primary URL for the record
|
|
545
|
+
resolveUrl: async (ctx: SearchBuildContext): Promise<string | null> => {
|
|
546
|
+
return `/your-entity/${ctx.record.id}`
|
|
547
|
+
},
|
|
548
|
+
|
|
549
|
+
// Optional: Additional action links
|
|
550
|
+
resolveLinks: async (ctx: SearchBuildContext): Promise<SearchResultLink[] | null> => {
|
|
551
|
+
return [
|
|
552
|
+
{ href: `/your-entity/${ctx.record.id}/edit`, label: 'Edit', kind: 'secondary' }
|
|
553
|
+
]
|
|
554
|
+
},
|
|
555
|
+
},
|
|
556
|
+
],
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
export default searchConfig
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
### Search Strategies Comparison
|
|
563
|
+
|
|
564
|
+
| Aspect | Full-Text (fulltext) | Vector Search (vector) | Token Search (tokens) |
|
|
565
|
+
|--------|----------------------|------------------------|----------------------|
|
|
566
|
+
| **Configuration** | `fieldPolicy` | `buildSource` | Automatic |
|
|
567
|
+
| **Search Type** | Full-text with typo tolerance | Semantic similarity | Exact token matching |
|
|
568
|
+
| **Good For** | Exact matches, filters, facets | Natural language, "find similar" | Simple lookups |
|
|
569
|
+
| **Backend** | Meilisearch server | pgvector/Qdrant/ChromaDB + embeddings | PostgreSQL |
|
|
570
|
+
| **Requires** | `MEILISEARCH_HOST` | `OPENAI_API_KEY` (or other provider) | Database connection |
|
|
571
|
+
|
|
572
|
+
### Enabling/Disabling Strategies
|
|
573
|
+
|
|
574
|
+
You can control which strategies are used at multiple levels:
|
|
575
|
+
|
|
576
|
+
#### Per Entity
|
|
577
|
+
|
|
578
|
+
```typescript
|
|
579
|
+
// Full-text only (no vector search)
|
|
580
|
+
{
|
|
581
|
+
entityId: 'your_module:your_entity',
|
|
582
|
+
enabled: true,
|
|
583
|
+
// NO buildSource = no vector search
|
|
584
|
+
fieldPolicy: {
|
|
585
|
+
searchable: ['name', 'description'],
|
|
586
|
+
},
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// Vector only (no full-text)
|
|
590
|
+
{
|
|
591
|
+
entityId: 'your_module:your_entity',
|
|
592
|
+
enabled: true,
|
|
593
|
+
buildSource: async (ctx) => ({ text: [...], presenter: {...} }),
|
|
594
|
+
// NO fieldPolicy = no full-text search
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
// Both strategies
|
|
598
|
+
{
|
|
599
|
+
entityId: 'your_module:your_entity',
|
|
600
|
+
enabled: true,
|
|
601
|
+
buildSource: async (ctx) => ({ text: [...], presenter: {...} }),
|
|
602
|
+
fieldPolicy: { searchable: ['name'] },
|
|
603
|
+
}
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
#### Global Level (DI Registration)
|
|
607
|
+
|
|
608
|
+
In `packages/core/src/bootstrap.ts`:
|
|
609
|
+
|
|
610
|
+
```typescript
|
|
611
|
+
import { registerSearchModule } from '@open-mercato/search'
|
|
612
|
+
|
|
613
|
+
registerSearchModule(container, {
|
|
614
|
+
moduleConfigs: searchModuleConfigs,
|
|
615
|
+
skipVector: true, // Disable vector search globally
|
|
616
|
+
skipFulltext: true, // Disable full-text search globally
|
|
617
|
+
skipTokens: true, // Disable token search globally
|
|
618
|
+
})
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
#### Per Query
|
|
622
|
+
|
|
623
|
+
```typescript
|
|
624
|
+
// Only use full-text search for this query
|
|
625
|
+
const results = await searchService.search('query', {
|
|
626
|
+
tenantId: '...',
|
|
627
|
+
strategies: ['fulltext'],
|
|
628
|
+
})
|
|
629
|
+
|
|
630
|
+
// Only use vector search
|
|
631
|
+
const results = await searchService.search('query', {
|
|
632
|
+
tenantId: '...',
|
|
633
|
+
strategies: ['vector'],
|
|
634
|
+
})
|
|
635
|
+
|
|
636
|
+
// Use all available strategies (default)
|
|
637
|
+
const results = await searchService.search('query', {
|
|
638
|
+
tenantId: '...',
|
|
639
|
+
})
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
#### Environment-Based
|
|
643
|
+
|
|
644
|
+
Strategies automatically become unavailable if their backend is not configured:
|
|
645
|
+
|
|
646
|
+
| Strategy | Required Environment |
|
|
647
|
+
|----------|---------------------|
|
|
648
|
+
| fulltext | `MEILISEARCH_HOST` |
|
|
649
|
+
| vector | `OPENAI_API_KEY` (or other embedding provider) |
|
|
650
|
+
| tokens | Database connection (always available) |
|
|
651
|
+
|
|
652
|
+
### SearchBuildContext
|
|
653
|
+
|
|
654
|
+
The context object passed to `buildSource` and other config functions:
|
|
655
|
+
|
|
656
|
+
```typescript
|
|
657
|
+
interface SearchBuildContext {
|
|
658
|
+
record: Record<string, unknown> // The database record
|
|
659
|
+
customFields: Record<string, unknown> // Custom field values (cf:* fields)
|
|
660
|
+
tenantId?: string | null
|
|
661
|
+
organizationId?: string | null
|
|
662
|
+
queryEngine?: QueryEngine // For loading related entities
|
|
663
|
+
}
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
### SearchIndexSource
|
|
667
|
+
|
|
668
|
+
The return type from `buildSource`:
|
|
669
|
+
|
|
670
|
+
```typescript
|
|
671
|
+
interface SearchIndexSource {
|
|
672
|
+
text: string | string[] // Text to embed for vector search
|
|
673
|
+
presenter?: SearchResultPresenter // Display info for search results
|
|
674
|
+
links?: SearchResultLink[] // Action links
|
|
675
|
+
checksumSource?: unknown // Used for change detection
|
|
676
|
+
}
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
## Architecture
|
|
680
|
+
|
|
681
|
+
```
|
|
682
|
+
packages/search/
|
|
683
|
+
├── src/
|
|
684
|
+
│ ├── modules/search/
|
|
685
|
+
│ │ ├── api/ # API routes
|
|
686
|
+
│ │ ├── cli.ts # CLI commands
|
|
687
|
+
│ │ ├── di.ts # Dependency injection
|
|
688
|
+
│ │ ├── subscribers/ # Event subscribers for auto-indexing
|
|
689
|
+
│ │ └── workers/ # Queue job handlers
|
|
690
|
+
│ ├── indexer/ # Search indexer implementation
|
|
691
|
+
│ ├── queue/ # Queue definitions
|
|
692
|
+
│ ├── strategies/ # Search strategy implementations
|
|
693
|
+
│ └── vector/ # Vector index service
|
|
694
|
+
```
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const features = [
|
|
2
|
+
{ id: 'search.view', title: 'View search settings', module: 'search' },
|
|
3
|
+
{ id: 'search.manage', title: 'Manage search settings', module: 'search' },
|
|
4
|
+
{ id: 'search.reindex', title: 'Reindex search data', module: 'search' },
|
|
5
|
+
{ id: 'search.embeddings.view', title: 'View embedding settings', module: 'search' },
|
|
6
|
+
{ id: 'search.embeddings.manage', title: 'Manage embedding settings', module: 'search' },
|
|
7
|
+
{ id: 'search.global', title: 'Use global search (Cmd+K)', module: 'search' },
|
|
8
|
+
]
|
|
9
|
+
|
|
10
|
+
export default features
|