@meeovi/search 1.0.0 → 1.0.2

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/README.md ADDED
@@ -0,0 +1,55 @@
1
+
2
+ ---
3
+
4
+ # 📦 `@meeovi/search` — README.md
5
+
6
+ ```md
7
+ # @meeovi/search
8
+
9
+ A backend‑agnostic search abstraction for Meeovi.
10
+ Supports Searchkit, Meilisearch, Typesense, Algolia, and custom search engines.
11
+
12
+ ## ✨ Features
13
+
14
+ - Unified `useSearch()` composable
15
+ - Pluggable search providers
16
+ - Runtime configuration
17
+ - Works with any search backend
18
+
19
+ ## 📦 Installation
20
+
21
+ ```sh
22
+ npm install @meeovi/search
23
+
24
+ ⚙️ Configuration
25
+
26
+ import { setSearchConfig } from '@meeovi/search'
27
+
28
+ setSearchConfig({
29
+ searchProvider: 'searchkit',
30
+ searchUrl: 'https://api.meeovi.com/search'
31
+ })
32
+ 🧩 Usage
33
+
34
+ import { useSearch } from '@meeovi/search'
35
+
36
+ const { query, fetch } = useSearch()
37
+
38
+ const results = await fetch(query('laptops'))
39
+ 🔌 Providers
40
+
41
+ export interface SearchProvider {
42
+ query(input: string): any
43
+ fetch(query: any): Promise<any>
44
+ }
45
+ Register:
46
+
47
+
48
+ registerSearchProvider('searchkit', { query, fetch })
49
+ 🧱 Folder Structure
50
+ Code
51
+ src/
52
+ providers/
53
+ config.
54
+ registry.
55
+ useSearch.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@meeovi/search",
3
- "version": "1.0.0",
4
- "description": "",
3
+ "version": "1.0.2",
4
+ "description": "Powerful search module for the M Framework.",
5
5
  "main": "index.js",
6
6
  "scripts": {
7
7
  "test": "echo \"Error: no test specified\" && exit 1"
@@ -0,0 +1,8 @@
1
+ export interface GlobalSearchResult {
2
+ id?: string;
3
+ title?: string;
4
+ type?: string;
5
+ description?: string;
6
+ image?: string;
7
+ url?: string;
8
+ }
@@ -0,0 +1,21 @@
1
+ import { getAdminClient } from './client'
2
+
3
+ const TELEMETRY_INDEX = 'search_telemetry'
4
+
5
+ export async function logSearchEvent(event: Record<string, any>) {
6
+ const client = getAdminClient()
7
+ const idx = client.index(TELEMETRY_INDEX)
8
+ const payload = Object.assign({}, event, { ts: new Date().toISOString() })
9
+ return idx.addDocuments([payload])
10
+ }
11
+
12
+ export async function getTelemetryStats() {
13
+ const client = getAdminClient()
14
+ try {
15
+ return client.index(TELEMETRY_INDEX).getStats()
16
+ } catch (e) {
17
+ return null
18
+ }
19
+ }
20
+
21
+ export default { logSearchEvent, getTelemetryStats }
@@ -0,0 +1,43 @@
1
+ // Instantiate a Searchkit SDK client. The SDK is expected to be installed.
2
+ import Client from '@searchkit/instantsearch-client'
3
+ import { useRuntimeConfig } from '#imports'
4
+
5
+ function getRuntimeConfig(): any {
6
+ try {
7
+ return useRuntimeConfig()
8
+ } catch (e) {
9
+ return undefined
10
+ }
11
+ }
12
+
13
+ export function getAdminClient(): any {
14
+ const cfg = getRuntimeConfig()
15
+ const host = cfg?.public?.searchkit?.host || process.env.SEARCHKIT_HOST || process.env.ELASTICSEARCH_HOST || cfg?.public?.meilisearch?.host || process.env.MEILISEARCH_HOST
16
+ const apiKey = cfg?.searchkit?.adminApiKey || process.env.SEARCHKIT_ADMIN_API_KEY || cfg?.meilisearch?.adminApiKey || process.env.MEILISEARCH_ADMIN_API_KEY
17
+ if (!host) throw new Error('Search host not configured')
18
+ try {
19
+ return new (Client as any)({ host, apiKey })
20
+ } catch (e) {
21
+ // Fallback to a minimal placeholder if SDK instantiation fails
22
+ return { host, apiKey, _client: 'searchkit-fallback' } as any
23
+ }
24
+ }
25
+
26
+ export function getSearchClient(): any {
27
+ const cfg = getRuntimeConfig()
28
+ const host = cfg?.public?.searchkit?.host || process.env.SEARCHKIT_HOST || process.env.ELASTICSEARCH_HOST || cfg?.public?.meilisearch?.host || process.env.MEILISEARCH_HOST
29
+ const apiKey = cfg?.public?.searchkit?.searchApiKey || process.env.SEARCHKIT_SEARCH_API_KEY || cfg?.public?.meilisearch?.searchApiKey || process.env.MEILISEARCH_SEARCH_API_KEY
30
+ if (!host) throw new Error('Search host not configured')
31
+ try {
32
+ return new (Client as any)({ host, apiKey })
33
+ } catch (e) {
34
+ return { host, apiKey, _client: 'searchkit-fallback' } as any
35
+ }
36
+ }
37
+
38
+ export function getIndexName(): string {
39
+ const cfg = getRuntimeConfig()
40
+ return cfg?.public?.searchkit?.indexName || process.env.SEARCHKIT_INDEX_NAME || cfg?.public?.meilisearch?.indexName || process.env.MEILISEARCH_INDEX_NAME || 'default'
41
+ }
42
+
43
+ export type TaskInfo = any
@@ -0,0 +1,16 @@
1
+ import { getAdminClient, getIndexName } from './client'
2
+
3
+ export async function updateSettings(indexName: string | undefined, settings: Record<string, any>) {
4
+ const client = getAdminClient()
5
+ const idx = client.index(indexName || getIndexName())
6
+ return idx.updateSettings(settings)
7
+ }
8
+
9
+ export async function getSettings(indexName?: string) {
10
+ const client = getAdminClient()
11
+ const idx = client.index(indexName || getIndexName())
12
+ return idx.getSettings()
13
+ }
14
+
15
+ export default { updateSettings, getSettings }
16
+
@@ -0,0 +1,25 @@
1
+ export type FilterValue = string | number | boolean
2
+
3
+ function fmt(v: FilterValue) {
4
+ if (typeof v === 'string') return `"${v.replace(/"/g, '\\"')}"`
5
+ if (typeof v === 'boolean') return v ? 'true' : 'false'
6
+ return String(v)
7
+ }
8
+
9
+ export function toFilterString(filters: Record<string, FilterValue | FilterValue[]>): string | undefined {
10
+ const parts: string[] = []
11
+ for (const key of Object.keys(filters)) {
12
+ const val = filters[key]
13
+ if (val === undefined) continue
14
+ if (Array.isArray(val)) {
15
+ const orParts = val.map(v => `${key} = ${fmt(v)}`)
16
+ parts.push(`(${orParts.join(' OR ')})`)
17
+ } else {
18
+ parts.push(`${key} = ${fmt(val)}`)
19
+ }
20
+ }
21
+ if (parts.length === 0) return undefined
22
+ return parts.join(' AND ')
23
+ }
24
+
25
+ export default toFilterString
@@ -0,0 +1,40 @@
1
+ import { getAdminClient, getIndexName } from './client'
2
+
3
+ export async function addDocuments(indexName: string | undefined, docs: any[], primaryKey?: string) {
4
+ const client = getAdminClient()
5
+ const idx = client.index(indexName || getIndexName())
6
+ return idx.addDocuments(docs, { primaryKey })
7
+ }
8
+
9
+ export async function updateDocuments(indexName: string | undefined, docs: any[]) {
10
+ const client = getAdminClient()
11
+ const idx = client.index(indexName || getIndexName())
12
+ return idx.updateDocuments(docs)
13
+ }
14
+
15
+ export async function deleteDocuments(indexName: string | undefined, ids: Array<string | number>) {
16
+ const client = getAdminClient()
17
+ const idx = client.index(indexName || getIndexName())
18
+ const stringIds = ids.map(id => String(id)) as string[]
19
+ return idx.deleteDocuments(stringIds)
20
+ }
21
+
22
+ export async function clearIndex(indexName?: string) {
23
+ const client = getAdminClient()
24
+ const idx = client.index(indexName || getIndexName())
25
+ return idx.deleteAllDocuments()
26
+ }
27
+
28
+ export async function getDocument(indexName: string | undefined, id: string | number) {
29
+ const client = getAdminClient()
30
+ const idx = client.index(indexName || getIndexName())
31
+ return idx.getDocument(id)
32
+ }
33
+
34
+ export default {
35
+ addDocuments,
36
+ updateDocuments,
37
+ deleteDocuments,
38
+ clearIndex,
39
+ getDocument,
40
+ }
@@ -0,0 +1,21 @@
1
+ import { getAdminClient, getIndexName } from './client'
2
+
3
+ export async function ensureIndex(indexName?: string, options?: { primaryKey?: string }) {
4
+ const client = getAdminClient()
5
+ const name = indexName || getIndexName()
6
+ try {
7
+ await client.getIndex(name)
8
+ return { existed: true }
9
+ } catch (err) {
10
+ const created = await client.createIndex(name, options)
11
+ return { existed: false, created }
12
+ }
13
+ }
14
+
15
+ export async function deleteIndex(indexName?: string) {
16
+ const client = getAdminClient()
17
+ const name = indexName || getIndexName()
18
+ return client.deleteIndex(name)
19
+ }
20
+
21
+ export default { ensureIndex, deleteIndex }
@@ -0,0 +1,17 @@
1
+ import { getSearchClient } from './client'
2
+
3
+ export type MultiQuery = {
4
+ indexName: string
5
+ query?: string
6
+ params?: Record<string, any>
7
+ }
8
+
9
+ export async function multiSearch(queries: MultiQuery[]) {
10
+ const client = getSearchClient()
11
+ const ms = queries.map(q => ({ indexUid: q.indexName, query: q.query || '', params: q.params || {} }))
12
+ // MeiliSearch SDK expects: { queries: [{ indexUid, query, params }] }
13
+ return client.multiSearch({ queries: ms })
14
+ }
15
+
16
+ export default multiSearch
17
+
@@ -0,0 +1,23 @@
1
+ import { getAdminClient } from './client'
2
+
3
+ export async function createKey(params: {
4
+ description?: string
5
+ actions: string[]
6
+ indexes?: string[]
7
+ expiresAt?: string
8
+ }) {
9
+ const client = getAdminClient()
10
+ return client.createKey(params)
11
+ }
12
+
13
+ export async function listKeys() {
14
+ const client = getAdminClient()
15
+ return client.getKeys()
16
+ }
17
+
18
+ export async function deleteKey(key: string) {
19
+ const client = getAdminClient()
20
+ return client.deleteKey(key)
21
+ }
22
+
23
+ export default { createKey, listKeys, deleteKey }
@@ -0,0 +1,16 @@
1
+ import { getAdminClient, getIndexName } from './client'
2
+
3
+ export async function updateRankingRules(indexName: string | undefined, rankingRules: string[]) {
4
+ const client = getAdminClient()
5
+ const idx = client.index(indexName || getIndexName())
6
+ return idx.updateSettings({ rankingRules })
7
+ }
8
+
9
+ export async function updateSearchableAttributes(indexName: string | undefined, attrs: string[]) {
10
+ const client = getAdminClient()
11
+ const idx = client.index(indexName || getIndexName())
12
+ return idx.updateSettings({ searchableAttributes: attrs })
13
+ }
14
+
15
+ export default { updateRankingRules, updateSearchableAttributes }
16
+
@@ -0,0 +1,17 @@
1
+ import toFilterString from './filters'
2
+
3
+ export function sanitizeQuery(q?: string) {
4
+ if (!q) return ''
5
+ // basic sanitize to avoid control sequences
6
+ return q.replace(/[\u0000-\u001F\u007F]/g, '').trim()
7
+ }
8
+
9
+ export function buildUserFilter(user?: { id?: string | number; roles?: string[] }, extra?: Record<string, any>) {
10
+ const filters: Record<string, any> = {}
11
+ if (user?.id) filters['owner_id'] = user.id
12
+ if (user?.roles && user.roles.length > 0) filters['role'] = user.roles
13
+ if (extra) Object.assign(filters, extra)
14
+ return toFilterString(filters)
15
+ }
16
+
17
+ export default { sanitizeQuery, buildUserFilter }
@@ -0,0 +1,9 @@
1
+ export type SortSpec = { field: string; direction?: 'asc' | 'desc' }
2
+
3
+ export function buildSortClauses(specs: SortSpec[] | undefined) {
4
+ if (!specs || specs.length === 0) return undefined
5
+ return specs.map(s => `${s.field}:${s.direction || 'asc'}`)
6
+ }
7
+
8
+ export default buildSortClauses
9
+
@@ -0,0 +1,14 @@
1
+ import { getAdminClient, getIndexName } from './client'
2
+
3
+ export async function batchIndexDocuments(docs: any[], indexName?: string, batchSize = 1000) {
4
+ const client = getAdminClient()
5
+ const idx = client.index(indexName || getIndexName())
6
+ const tasks = [] as Promise<any>[]
7
+ for (let i = 0; i < docs.length; i += batchSize) {
8
+ const chunk = docs.slice(i, i + batchSize)
9
+ tasks.push(idx.addDocuments(chunk))
10
+ }
11
+ return Promise.all(tasks)
12
+ }
13
+
14
+ export default { batchIndexDocuments }
@@ -0,0 +1,5 @@
1
+ import { logSearchEvent } from './analytics'
2
+
3
+ export { logSearchEvent, getTelemetryStats } from './analytics'
4
+
5
+ export default { logSearchEvent: (event: any) => logSearchEvent(event) }