@open-mercato/search 0.4.7-develop-74069040de → 0.4.7-develop-bdeaa0fc10

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.
@@ -0,0 +1,115 @@
1
+ # Search Package — Standalone Developer Guide
2
+
3
+ `@open-mercato/search` provides fulltext, vector, and token-based search. Configure search for your entities via `search.ts` in your module.
4
+
5
+ ## Strategy Overview
6
+
7
+ | Strategy | Backend | Use when |
8
+ |----------|---------|----------|
9
+ | **Fulltext** | Meilisearch | Fast, typo-tolerant search (names, titles, descriptions) |
10
+ | **Vector** | OpenAI / Ollama | Semantic, meaning-based search ("find customers interested in X") |
11
+ | **Tokens** | PostgreSQL | Baseline keyword search, always available, no external services |
12
+
13
+ Strategies auto-degrade when their backend is not configured.
14
+
15
+ ## Adding Search to a Module
16
+
17
+ Create `src/modules/<module>/search.ts`:
18
+
19
+ ```typescript
20
+ import type { SearchModuleConfig, SearchBuildContext } from '@open-mercato/shared/modules/search'
21
+
22
+ export const searchConfig: SearchModuleConfig = {
23
+ entities: [{
24
+ entityId: 'my_module:my_entity', // MUST match entity registry
25
+ priority: 10,
26
+
27
+ // Fulltext: control field indexing
28
+ fieldPolicy: {
29
+ searchable: ['name', 'description'],
30
+ hashOnly: ['email', 'phone'], // exact match only
31
+ excluded: ['password', 'api_key'], // never indexed
32
+ },
33
+
34
+ // Vector: generate text for embeddings
35
+ buildSource: async (ctx: SearchBuildContext) => ({
36
+ text: [`Name: ${ctx.record.name}`, `Description: ${ctx.record.description}`],
37
+ presenter: { title: ctx.record.name, subtitle: ctx.record.status, icon: 'lucide:file', badge: 'Item' },
38
+ links: [{ href: `/backend/my-module/${ctx.record.id}`, label: 'View', kind: 'primary' }],
39
+ checksumSource: { record: ctx.record, customFields: ctx.customFields },
40
+ }),
41
+
42
+ // Tokens: format at search time
43
+ formatResult: async (ctx: SearchBuildContext) => ({
44
+ title: ctx.record.name ?? 'Unknown',
45
+ subtitle: ctx.record.status,
46
+ icon: 'lucide:file',
47
+ badge: 'Item',
48
+ }),
49
+
50
+ resolveUrl: async (ctx) => `/backend/my-module/${ctx.record.id}`,
51
+ }],
52
+ }
53
+ export default searchConfig
54
+ ```
55
+
56
+ Run `yarn generate` after creating the file.
57
+
58
+ ## MUST Rules
59
+
60
+ 1. **MUST create `search.ts`** for every module with searchable entities
61
+ 2. **MUST define `fieldPolicy.excluded`** for sensitive fields (passwords, tokens, SSNs)
62
+ 3. **MUST define `formatResult`** for every entity using the tokens strategy
63
+ 4. **MUST include `checksumSource`** in every `buildSource` return value
64
+ 5. **MUST NOT** include encrypted/sensitive fields in `buildSource` text
65
+ 6. **MUST NOT** use raw `fetch` against search API — use `apiCall`/`apiCallOrThrow`
66
+
67
+ ## Auto-Indexing
68
+
69
+ When CRUD routes have `indexer: { entityType }`, the search module automatically subscribes to entity CRUD events and indexes/removes records. No manual indexing code needed.
70
+
71
+ ## CLI Commands
72
+
73
+ ```bash
74
+ yarn mercato search status # Check strategies and connectivity
75
+ yarn mercato search query -q "term" --tenant <id> # Run a search
76
+ yarn mercato search reindex --tenant <id> # Reindex all entities
77
+ yarn mercato search reindex --entity my_module:my_entity --tenant <id> # Reindex specific entity
78
+ yarn mercato search test-meilisearch # Test Meilisearch connection
79
+ ```
80
+
81
+ ## Environment Variables
82
+
83
+ | Variable | Purpose |
84
+ |----------|---------|
85
+ | `MEILISEARCH_HOST` | Meilisearch URL (enables fulltext) |
86
+ | `MEILISEARCH_API_KEY` | Meilisearch auth key |
87
+ | `OPENAI_API_KEY` | OpenAI API key (enables vector search) |
88
+ | `OM_SEARCH_DEBUG` | Enable verbose debug logging |
89
+
90
+ ## Programmatic Search via DI
91
+
92
+ ```typescript
93
+ const searchService = container.resolve('searchService')
94
+ const results = await searchService.search('query', {
95
+ tenantId: 'tenant-123',
96
+ limit: 20,
97
+ strategies: ['fulltext', 'vector'],
98
+ })
99
+ ```
100
+
101
+ ## SearchBuildContext
102
+
103
+ Both `buildSource` and `formatResult` receive this context:
104
+
105
+ ```typescript
106
+ interface SearchBuildContext {
107
+ record: Record<string, unknown> // The database record
108
+ customFields: Record<string, unknown> // Custom fields (without cf: prefix)
109
+ tenantId?: string | null
110
+ organizationId?: string | null
111
+ queryEngine?: QueryEngine // For loading related entities
112
+ }
113
+ ```
114
+
115
+ Use `queryEngine` to load parent/related entities for richer presenter data.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/search",
3
- "version": "0.4.7-develop-74069040de",
3
+ "version": "0.4.7-develop-bdeaa0fc10",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "exports": {
@@ -126,9 +126,9 @@
126
126
  "zod": "^4.0.0"
127
127
  },
128
128
  "peerDependencies": {
129
- "@open-mercato/core": "0.4.7-develop-74069040de",
130
- "@open-mercato/queue": "0.4.7-develop-74069040de",
131
- "@open-mercato/shared": "0.4.7-develop-74069040de"
129
+ "@open-mercato/core": "0.4.7-develop-bdeaa0fc10",
130
+ "@open-mercato/queue": "0.4.7-develop-bdeaa0fc10",
131
+ "@open-mercato/shared": "0.4.7-develop-bdeaa0fc10"
132
132
  },
133
133
  "devDependencies": {
134
134
  "@types/jest": "^30.0.0",