@meeovi/layer-search 1.0.6 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/README.md +39 -5
  2. package/app/composables/bridges/searchkit-server.ts +51 -0
  3. package/app/composables/bridges/searchkit.ts +88 -0
  4. package/app/composables/index.ts +4 -1
  5. package/app/composables/module.ts +41 -0
  6. package/app/composables/utils/health.ts +13 -0
  7. package/app/plugins/search.js +1 -1
  8. package/app/utils/search/client.ts +40 -0
  9. package/dist/app/composables/adapter/meilisearch.d.ts +8 -0
  10. package/dist/app/composables/adapter/meilisearch.js +36 -0
  11. package/dist/app/composables/adapter/mock.d.ts +3 -0
  12. package/dist/app/composables/adapter/mock.js +19 -0
  13. package/dist/app/composables/adapter/opensearch.d.ts +8 -0
  14. package/dist/app/composables/adapter/opensearch.js +46 -0
  15. package/dist/app/composables/adapter/types.d.ts +12 -0
  16. package/dist/app/composables/adapter/types.js +1 -0
  17. package/dist/app/composables/bridges/instantsearch.d.ts +4 -0
  18. package/dist/app/composables/bridges/instantsearch.js +17 -0
  19. package/dist/app/composables/bridges/react.d.ts +12 -0
  20. package/dist/app/composables/bridges/react.js +34 -0
  21. package/dist/app/composables/bridges/vue.d.ts +9 -0
  22. package/dist/app/composables/bridges/vue.js +31 -0
  23. package/dist/app/composables/cli.d.ts +2 -0
  24. package/dist/app/composables/cli.js +69 -0
  25. package/dist/app/composables/config/schema.d.ts +5 -0
  26. package/dist/app/composables/config/schema.js +8 -0
  27. package/dist/app/composables/config.d.ts +7 -0
  28. package/dist/app/composables/config.js +11 -0
  29. package/dist/app/composables/core/Facets.d.ts +17 -0
  30. package/dist/app/composables/core/Facets.js +8 -0
  31. package/dist/app/composables/core/Filters.d.ts +18 -0
  32. package/dist/app/composables/core/Filters.js +11 -0
  33. package/dist/app/composables/core/Normalizers.d.ts +0 -0
  34. package/dist/app/composables/core/Normalizers.js +1 -0
  35. package/dist/app/composables/core/Pipeline.d.ts +8 -0
  36. package/dist/app/composables/core/Pipeline.js +16 -0
  37. package/dist/app/composables/core/QueryBuilder.d.ts +13 -0
  38. package/dist/app/composables/core/QueryBuilder.js +15 -0
  39. package/dist/app/composables/core/SearchContext.d.ts +18 -0
  40. package/dist/app/composables/core/SearchContext.js +38 -0
  41. package/dist/app/composables/core/SearchManager.d.ts +10 -0
  42. package/dist/app/composables/core/SearchManager.js +20 -0
  43. package/dist/app/composables/events.d.ts +11 -0
  44. package/dist/app/composables/events.js +1 -0
  45. package/dist/app/composables/index.d.ts +9 -0
  46. package/dist/app/composables/index.js +9 -0
  47. package/dist/app/composables/module.d.ts +17 -0
  48. package/dist/app/composables/module.js +39 -0
  49. package/dist/app/composables/types/api/global-search.d.ts +8 -0
  50. package/dist/app/composables/types/api/global-search.js +1 -0
  51. package/dist/app/composables/utils/normalizers.d.ts +1 -0
  52. package/dist/app/composables/utils/normalizers.js +6 -0
  53. package/dist/app/utils/search/client.d.ts +3 -0
  54. package/dist/app/utils/search/client.js +31 -0
  55. package/dist/layers/search/app/composables/adapter/meilisearch.d.ts +8 -0
  56. package/dist/layers/search/app/composables/adapter/meilisearch.js +36 -0
  57. package/dist/layers/search/app/composables/adapter/mock.d.ts +3 -0
  58. package/dist/layers/search/app/composables/adapter/mock.js +19 -0
  59. package/dist/layers/search/app/composables/adapter/opensearch.d.ts +8 -0
  60. package/dist/layers/search/app/composables/adapter/opensearch.js +46 -0
  61. package/dist/layers/search/app/composables/adapter/types.d.ts +12 -0
  62. package/dist/layers/search/app/composables/adapter/types.js +1 -0
  63. package/dist/layers/search/app/composables/bridges/instantsearch.d.ts +4 -0
  64. package/dist/layers/search/app/composables/bridges/instantsearch.js +17 -0
  65. package/dist/layers/search/app/composables/bridges/react.d.ts +12 -0
  66. package/dist/layers/search/app/composables/bridges/react.js +34 -0
  67. package/dist/layers/search/app/composables/bridges/searchkit-server.d.ts +3 -0
  68. package/dist/layers/search/app/composables/bridges/searchkit-server.js +44 -0
  69. package/dist/layers/search/app/composables/bridges/searchkit.d.ts +21 -0
  70. package/dist/layers/search/app/composables/bridges/searchkit.js +60 -0
  71. package/dist/layers/search/app/composables/bridges/vue.d.ts +9 -0
  72. package/dist/layers/search/app/composables/bridges/vue.js +31 -0
  73. package/dist/layers/search/app/composables/cli.d.ts +2 -0
  74. package/dist/layers/search/app/composables/cli.js +69 -0
  75. package/dist/layers/search/app/composables/config/schema.d.ts +5 -0
  76. package/dist/layers/search/app/composables/config/schema.js +8 -0
  77. package/dist/layers/search/app/composables/config.d.ts +7 -0
  78. package/dist/layers/search/app/composables/config.js +11 -0
  79. package/dist/layers/search/app/composables/core/Facets.d.ts +17 -0
  80. package/dist/layers/search/app/composables/core/Facets.js +8 -0
  81. package/dist/layers/search/app/composables/core/Filters.d.ts +18 -0
  82. package/dist/layers/search/app/composables/core/Filters.js +11 -0
  83. package/dist/layers/search/app/composables/core/Normalizers.d.ts +0 -0
  84. package/dist/layers/search/app/composables/core/Normalizers.js +1 -0
  85. package/dist/layers/search/app/composables/core/Pipeline.d.ts +8 -0
  86. package/dist/layers/search/app/composables/core/Pipeline.js +16 -0
  87. package/dist/layers/search/app/composables/core/QueryBuilder.d.ts +13 -0
  88. package/dist/layers/search/app/composables/core/QueryBuilder.js +15 -0
  89. package/dist/layers/search/app/composables/core/SearchContext.d.ts +18 -0
  90. package/dist/layers/search/app/composables/core/SearchContext.js +38 -0
  91. package/dist/layers/search/app/composables/core/SearchManager.d.ts +10 -0
  92. package/dist/layers/search/app/composables/core/SearchManager.js +20 -0
  93. package/dist/layers/search/app/composables/events.d.ts +11 -0
  94. package/dist/layers/search/app/composables/events.js +1 -0
  95. package/dist/layers/search/app/composables/index.d.ts +12 -0
  96. package/dist/layers/search/app/composables/index.js +12 -0
  97. package/dist/layers/search/app/composables/module.d.ts +17 -0
  98. package/dist/layers/search/app/composables/module.js +73 -0
  99. package/dist/layers/search/app/composables/types/api/global-search.d.ts +8 -0
  100. package/dist/layers/search/app/composables/types/api/global-search.js +1 -0
  101. package/dist/layers/search/app/composables/utils/health.d.ts +11 -0
  102. package/dist/layers/search/app/composables/utils/health.js +11 -0
  103. package/dist/layers/search/app/composables/utils/normalizers.d.ts +1 -0
  104. package/dist/layers/search/app/composables/utils/normalizers.js +6 -0
  105. package/dist/layers/search/app/utils/search/client.d.ts +3 -0
  106. package/dist/layers/search/app/utils/search/client.js +31 -0
  107. package/dist/layers/search/nuxt.config.d.ts +2 -0
  108. package/dist/layers/search/nuxt.config.js +7 -0
  109. package/dist/layers/search/test/runtime-adapter.spec.d.ts +1 -0
  110. package/dist/layers/search/test/runtime-adapter.spec.js +49 -0
  111. package/dist/nuxt.config.d.ts +2 -0
  112. package/dist/nuxt.config.js +7 -0
  113. package/dist/packages/core/src/adapters/auth.d.ts +9 -0
  114. package/dist/packages/core/src/adapters/auth.js +1 -0
  115. package/dist/packages/core/src/adapters/cart.d.ts +22 -0
  116. package/dist/packages/core/src/adapters/cart.js +1 -0
  117. package/dist/packages/core/src/adapters/catalog.d.ts +17 -0
  118. package/dist/packages/core/src/adapters/catalog.js +1 -0
  119. package/dist/packages/core/src/adapters/common.d.ts +9 -0
  120. package/dist/packages/core/src/adapters/common.js +1 -0
  121. package/dist/packages/core/src/adapters/lists.d.ts +17 -0
  122. package/dist/packages/core/src/adapters/lists.js +1 -0
  123. package/dist/packages/core/src/adapters/search.d.ts +21 -0
  124. package/dist/packages/core/src/adapters/search.js +1 -0
  125. package/dist/packages/core/src/plugins/defineAdapter.d.ts +2 -0
  126. package/dist/packages/core/src/plugins/defineAdapter.js +3 -0
  127. package/dist/packages/core/src/plugins/defineModule.d.ts +2 -0
  128. package/dist/packages/core/src/plugins/defineModule.js +3 -0
  129. package/dist/packages/core/src/plugins/registry.d.ts +14 -0
  130. package/dist/packages/core/src/plugins/registry.js +46 -0
  131. package/dist/packages/core/src/runtime/app.d.ts +2 -0
  132. package/dist/packages/core/src/runtime/app.js +20 -0
  133. package/dist/packages/core/src/runtime/context.d.ts +9 -0
  134. package/dist/packages/core/src/runtime/context.js +11 -0
  135. package/dist/packages/core/src/runtime/hooks.d.ts +5 -0
  136. package/dist/packages/core/src/runtime/hooks.js +18 -0
  137. package/dist/packages/core/src/runtime/lifecycle.d.ts +4 -0
  138. package/dist/packages/core/src/runtime/lifecycle.js +5 -0
  139. package/dist/packages/core/src/types/adapters.d.ts +14 -0
  140. package/dist/packages/core/src/types/adapters.js +1 -0
  141. package/dist/packages/core/src/types/app.d.ts +13 -0
  142. package/dist/packages/core/src/types/app.js +1 -0
  143. package/dist/packages/core/src/types/config.d.ts +8 -0
  144. package/dist/packages/core/src/types/config.js +1 -0
  145. package/dist/packages/core/src/types/events.d.ts +20 -0
  146. package/dist/packages/core/src/types/events.js +22 -0
  147. package/dist/packages/core/src/types/module.d.ts +15 -0
  148. package/dist/packages/core/src/types/module.js +1 -0
  149. package/package.json +9 -4
  150. package/test/runtime-adapter.spec.ts +61 -0
@@ -0,0 +1,44 @@
1
+ import { graphql, buildSchema } from 'graphql';
2
+ const schema = buildSchema(`
3
+ type Hit { id: ID, title: String, description: String, price: Float }
4
+ type SearchResult {
5
+ items: [Hit]
6
+ total: Int
7
+ page: Int
8
+ pageSize: Int
9
+ }
10
+ type Query {
11
+ search(term: String, page: Int, pageSize: Int, filters: String): SearchResult
12
+ }
13
+ `);
14
+ function parseFilters(filters) {
15
+ if (!filters)
16
+ return {};
17
+ const entries = String(filters).split(' AND ').map((s) => s.split(':'));
18
+ return Object.fromEntries(entries.map(([k, v]) => [k, v?.replace(/^"|"$/g, '')]));
19
+ }
20
+ export function createSearchkitGraphQLHandler(manager) {
21
+ const root = {
22
+ search: async ({ term, page, pageSize, filters }) => {
23
+ manager.context.setQuery(term || '');
24
+ manager.context.setPage(page || 1);
25
+ manager.context.setPageSize(pageSize || manager.context.state.pageSize);
26
+ if (filters) {
27
+ manager.context.state.filters = parseFilters(filters);
28
+ }
29
+ const res = await manager.search();
30
+ return {
31
+ items: res.items,
32
+ total: res.total,
33
+ page: res.page,
34
+ pageSize: res.pageSize
35
+ };
36
+ }
37
+ };
38
+ return async function handler(req, res) {
39
+ const { query, variables } = req.body || {};
40
+ const result = await graphql({ schema, source: query, rootValue: root, variableValues: variables });
41
+ res.json(result);
42
+ };
43
+ }
44
+ export default createSearchkitGraphQLHandler;
@@ -0,0 +1,21 @@
1
+ import type { SearchManager } from '../core/SearchManager';
2
+ type InstantSearchRequest = {
3
+ indexName: string;
4
+ params: Record<string, any>;
5
+ };
6
+ type InstantSearchResult = {
7
+ results: {
8
+ hits: any[];
9
+ nbHits: number;
10
+ page: number;
11
+ hitsPerPage: number;
12
+ };
13
+ };
14
+ export declare function createSearchkitBridge(manager: SearchManager): {
15
+ search(requests: InstantSearchRequest[]): Promise<InstantSearchResult[]>;
16
+ searchForFacetValues(indexName: string, params: any): Promise<{
17
+ facetHits: any;
18
+ exhaustiveFacetsCount: boolean;
19
+ }>;
20
+ };
21
+ export default createSearchkitBridge;
@@ -0,0 +1,60 @@
1
+ function mapRequestToQuery(params, existing) {
2
+ const q = existing ?? {};
3
+ if (typeof params.query === 'string')
4
+ q.term = params.query;
5
+ if (typeof params.page === 'number')
6
+ q.page = params.page + 1; // instantsearch pages are 0-based
7
+ if (typeof params.hitsPerPage === 'number')
8
+ q.pageSize = params.hitsPerPage;
9
+ // Map simple filters from InstantSearch `facets` or `filters` param
10
+ if (params.filters && typeof params.filters === 'string') {
11
+ // basic parsing: "field:value AND other:value"
12
+ const entries = params.filters.split(' AND ').map((s) => s.split(':'));
13
+ q.filters = Object.fromEntries(entries.map(([k, v]) => [k, v.replace(/^"|"$/g, '')]));
14
+ }
15
+ return q;
16
+ }
17
+ export function createSearchkitBridge(manager) {
18
+ return {
19
+ // InstantSearch client "search" method
20
+ async search(requests) {
21
+ // Support single-index or multi-index by mapping each request to a search call
22
+ const results = [];
23
+ for (const req of requests) {
24
+ const params = req.params || {};
25
+ const built = mapRequestToQuery(params);
26
+ // apply to manager context
27
+ manager.context.setQuery(built.term || '');
28
+ manager.context.setPage(built.page || 1);
29
+ manager.context.setPageSize(built.pageSize || manager.context.state.pageSize);
30
+ if (built.filters) {
31
+ // clear and set simple filters
32
+ manager.context.state.filters = built.filters;
33
+ }
34
+ const res = await manager.search();
35
+ results.push({
36
+ results: {
37
+ hits: res.items,
38
+ nbHits: res.total,
39
+ page: (res.page || 1) - 1,
40
+ hitsPerPage: res.pageSize || manager.context.state.pageSize
41
+ }
42
+ });
43
+ }
44
+ return results;
45
+ },
46
+ // Minimal support for Searchkit/InstantSearch facet search
47
+ async searchForFacetValues(indexName, params) {
48
+ // Map to a query with term for facet matching
49
+ const built = mapRequestToQuery({ query: params.facetQuery });
50
+ manager.context.setQuery(built.term || '');
51
+ manager.context.setPage(1);
52
+ const res = await manager.search();
53
+ return {
54
+ facetHits: (res.items || []).map((item) => ({ value: item[params.attribute], count: 0 })),
55
+ exhaustiveFacetsCount: true
56
+ };
57
+ }
58
+ };
59
+ }
60
+ export default createSearchkitBridge;
@@ -0,0 +1,9 @@
1
+ export declare function useSearch(): {
2
+ query: import("vue").Ref<string, string>;
3
+ page: import("vue").Ref<number, number>;
4
+ pageSize: import("vue").Ref<number, number>;
5
+ results: import("vue").ComputedRef<any[]>;
6
+ total: import("vue").ComputedRef<number>;
7
+ loading: import("vue").ComputedRef<boolean>;
8
+ search: () => Promise<void>;
9
+ };
@@ -0,0 +1,31 @@
1
+ import { ref, computed } from 'vue';
2
+ import { useAlternateContext } from '@meeovi/core';
3
+ export function useSearch() {
4
+ const ctx = useAlternateContext();
5
+ const manager = ctx.searchManager;
6
+ const query = ref(manager.context.state.query);
7
+ const page = ref(manager.context.state.page);
8
+ const pageSize = ref(manager.context.state.pageSize);
9
+ const results = ref([]);
10
+ const total = ref(0);
11
+ const loading = ref(false);
12
+ async function run() {
13
+ loading.value = true;
14
+ manager.context.setQuery(query.value);
15
+ manager.context.setPage(page.value);
16
+ manager.context.setPageSize(pageSize.value);
17
+ const res = await manager.search();
18
+ results.value = res.items;
19
+ total.value = res.total;
20
+ loading.value = false;
21
+ }
22
+ return {
23
+ query,
24
+ page,
25
+ pageSize,
26
+ results: computed(() => results.value),
27
+ total: computed(() => total.value),
28
+ loading: computed(() => loading.value),
29
+ search: run
30
+ };
31
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,69 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { createAlternateApp } from '@meeovi/core';
6
+ import searchModule from './module';
7
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
8
+ async function run() {
9
+ const [, , command, arg] = process.argv;
10
+ const defaultProvider = process.env.SEARCH_PROVIDER === 'meilisearch' ? 'meilisearch' : 'opensearch';
11
+ const app = createAlternateApp({
12
+ config: {
13
+ env: 'production',
14
+ search: {
15
+ defaultProvider,
16
+ providers: {
17
+ opensearch: {
18
+ endpoint: process.env.OPENSEARCH_ENDPOINT,
19
+ index: process.env.OPENSEARCH_INDEX
20
+ },
21
+ meilisearch: {
22
+ host: process.env.MEILI_HOST,
23
+ index: process.env.MEILI_INDEX,
24
+ apiKey: process.env.MEILI_KEY
25
+ }
26
+ }
27
+ }
28
+ },
29
+ modules: [searchModule]
30
+ });
31
+ const ctx = await app.start();
32
+ const search = ctx.getAdapter('search');
33
+ if (!search) {
34
+ console.error('No search adapter registered');
35
+ process.exit(1);
36
+ }
37
+ if (command === 'warmup') {
38
+ console.log('Warming up search provider...');
39
+ await search.search({ term: 'warmup' });
40
+ console.log('Warmup complete');
41
+ return;
42
+ }
43
+ if (command === 'index') {
44
+ if (!arg) {
45
+ console.error('Missing file path: meeovi-search index <file.json>');
46
+ process.exit(1);
47
+ }
48
+ const filePath = path.resolve(process.cwd(), arg);
49
+ const data = JSON.parse(fs.readFileSync(filePath, 'utf8'));
50
+ console.log(`Indexing ${data.length} items...`);
51
+ if (search.id === 'search:opensearch') {
52
+ // TODO: implement bulk indexing
53
+ }
54
+ if (search.id === 'search:meilisearch') {
55
+ await fetch(`${process.env.MEILI_HOST}/indexes/${process.env.MEILI_INDEX}/documents`, {
56
+ method: 'POST',
57
+ headers: {
58
+ 'Content-Type': 'application/json',
59
+ Authorization: `Bearer ${process.env.MEILI_KEY}`
60
+ },
61
+ body: JSON.stringify(data)
62
+ });
63
+ }
64
+ console.log('Indexing complete');
65
+ return;
66
+ }
67
+ console.log(`Unknown command: ${command}`);
68
+ }
69
+ run();
@@ -0,0 +1,5 @@
1
+ export interface SearchModuleConfig {
2
+ defaultProvider: 'opensearch' | 'meilisearch';
3
+ providers: Record<string, unknown>;
4
+ }
5
+ export declare function validateSearchConfig(config: SearchModuleConfig): void;
@@ -0,0 +1,8 @@
1
+ export function validateSearchConfig(config) {
2
+ if (!config.defaultProvider) {
3
+ throw new Error('[@meeovi/search] Missing defaultProvider');
4
+ }
5
+ if (!config.providers[config.defaultProvider]) {
6
+ throw new Error(`[@meeovi/search] Provider "${config.defaultProvider}" not found in config.providers`);
7
+ }
8
+ }
@@ -0,0 +1,7 @@
1
+ export interface SearchConfig {
2
+ searchProvider: string;
3
+ searchUrl?: string;
4
+ apiKey?: string;
5
+ }
6
+ export declare function setSearchConfig(newConfig: Partial<SearchConfig>): void;
7
+ export declare function getSearchConfig(): SearchConfig;
@@ -0,0 +1,11 @@
1
+ let config = {
2
+ searchProvider: 'searchkit',
3
+ searchUrl: '',
4
+ apiKey: ''
5
+ };
6
+ export function setSearchConfig(newConfig) {
7
+ config = { ...config, ...newConfig };
8
+ }
9
+ export function getSearchConfig() {
10
+ return config;
11
+ }
@@ -0,0 +1,17 @@
1
+ export declare const Facets: {
2
+ terms(field: string): {
3
+ type: string;
4
+ field: string;
5
+ };
6
+ range(field: string, ranges: {
7
+ from?: number;
8
+ to?: number;
9
+ }[]): {
10
+ type: string;
11
+ field: string;
12
+ ranges: {
13
+ from?: number;
14
+ to?: number;
15
+ }[];
16
+ };
17
+ };
@@ -0,0 +1,8 @@
1
+ export const Facets = {
2
+ terms(field) {
3
+ return { type: 'terms', field };
4
+ },
5
+ range(field, ranges) {
6
+ return { type: 'range', field, ranges };
7
+ }
8
+ };
@@ -0,0 +1,18 @@
1
+ export declare const Filters: {
2
+ term(field: string, value: string): {
3
+ type: string;
4
+ field: string;
5
+ value: string;
6
+ };
7
+ range(field: string, min?: number, max?: number): {
8
+ type: string;
9
+ field: string;
10
+ min: number | undefined;
11
+ max: number | undefined;
12
+ };
13
+ boolean(field: string, value: boolean): {
14
+ type: string;
15
+ field: string;
16
+ value: boolean;
17
+ };
18
+ };
@@ -0,0 +1,11 @@
1
+ export const Filters = {
2
+ term(field, value) {
3
+ return { type: 'term', field, value };
4
+ },
5
+ range(field, min, max) {
6
+ return { type: 'range', field, min, max };
7
+ },
8
+ boolean(field, value) {
9
+ return { type: 'boolean', field, value };
10
+ }
11
+ };
@@ -0,0 +1 @@
1
+ "use strict";
@@ -0,0 +1,8 @@
1
+ export declare class SearchPipeline {
2
+ private before;
3
+ private after;
4
+ useBefore(fn: (query: any) => any): void;
5
+ useAfter(fn: (result: any) => any): void;
6
+ runBefore(query: any): any;
7
+ runAfter(result: any): any;
8
+ }
@@ -0,0 +1,16 @@
1
+ export class SearchPipeline {
2
+ before = [];
3
+ after = [];
4
+ useBefore(fn) {
5
+ this.before.push(fn);
6
+ }
7
+ useAfter(fn) {
8
+ this.after.push(fn);
9
+ }
10
+ runBefore(query) {
11
+ return this.before.reduce((q, fn) => fn(q), query);
12
+ }
13
+ runAfter(result) {
14
+ return this.after.reduce((r, fn) => fn(r), result);
15
+ }
16
+ }
@@ -0,0 +1,13 @@
1
+ import { SearchContext } from './SearchContext';
2
+ export interface BuiltSearchQuery {
3
+ term: string;
4
+ page: number;
5
+ pageSize: number;
6
+ sort?: string;
7
+ filters: Record<string, any>;
8
+ }
9
+ export declare class QueryBuilder {
10
+ private context;
11
+ constructor(context: SearchContext);
12
+ build(): BuiltSearchQuery;
13
+ }
@@ -0,0 +1,15 @@
1
+ export class QueryBuilder {
2
+ context;
3
+ constructor(context) {
4
+ this.context = context;
5
+ }
6
+ build() {
7
+ return {
8
+ term: this.context.state.query,
9
+ page: this.context.state.page,
10
+ pageSize: this.context.state.pageSize,
11
+ sort: this.context.state.sort,
12
+ filters: this.context.state.filters
13
+ };
14
+ }
15
+ }
@@ -0,0 +1,18 @@
1
+ export interface SearchContextState {
2
+ query: string;
3
+ page: number;
4
+ pageSize: number;
5
+ sort?: string;
6
+ filters: Record<string, any>;
7
+ }
8
+ export declare class SearchContext {
9
+ state: SearchContextState;
10
+ constructor(initial?: Partial<SearchContextState>);
11
+ setQuery(query: string): void;
12
+ setPage(page: number): void;
13
+ setPageSize(size: number): void;
14
+ setSort(sort: string | undefined): void;
15
+ setFilter(key: string, value: any): void;
16
+ removeFilter(key: string): void;
17
+ reset(): void;
18
+ }
@@ -0,0 +1,38 @@
1
+ export class SearchContext {
2
+ state;
3
+ constructor(initial) {
4
+ this.state = {
5
+ query: '',
6
+ page: 1,
7
+ pageSize: 20,
8
+ filters: {},
9
+ ...initial
10
+ };
11
+ }
12
+ setQuery(query) {
13
+ this.state.query = query;
14
+ }
15
+ setPage(page) {
16
+ this.state.page = page;
17
+ }
18
+ setPageSize(size) {
19
+ this.state.pageSize = size;
20
+ }
21
+ setSort(sort) {
22
+ this.state.sort = sort;
23
+ }
24
+ setFilter(key, value) {
25
+ this.state.filters[key] = value;
26
+ }
27
+ removeFilter(key) {
28
+ delete this.state.filters[key];
29
+ }
30
+ reset() {
31
+ this.state = {
32
+ query: '',
33
+ page: 1,
34
+ pageSize: 20,
35
+ filters: {}
36
+ };
37
+ }
38
+ }
@@ -0,0 +1,10 @@
1
+ import type { SearchAdapter } from '@meeovi/core';
2
+ import { SearchContext, type SearchContextState } from './SearchContext';
3
+ import { SearchPipeline } from './Pipeline';
4
+ export declare class SearchManager<TItem = any> {
5
+ context: SearchContext;
6
+ pipeline: SearchPipeline;
7
+ adapter: SearchAdapter<TItem>;
8
+ constructor(adapter: SearchAdapter<TItem>, initial?: Partial<SearchContextState>);
9
+ search(): Promise<any>;
10
+ }
@@ -0,0 +1,20 @@
1
+ import { SearchContext } from './SearchContext';
2
+ import { QueryBuilder } from './QueryBuilder';
3
+ import { SearchPipeline } from './Pipeline';
4
+ export class SearchManager {
5
+ context;
6
+ pipeline;
7
+ adapter;
8
+ constructor(adapter, initial) {
9
+ this.context = new SearchContext(initial);
10
+ this.pipeline = new SearchPipeline();
11
+ this.adapter = adapter;
12
+ }
13
+ async search() {
14
+ const builder = new QueryBuilder(this.context);
15
+ let query = builder.build();
16
+ query = this.pipeline.runBefore(query);
17
+ const result = await this.adapter.search(query);
18
+ return this.pipeline.runAfter(result);
19
+ }
20
+ }
@@ -0,0 +1,11 @@
1
+ declare module '@meeovi/core/dist/types/events' {
2
+ interface AlternateEventMap {
3
+ 'search:query': {
4
+ term: string;
5
+ };
6
+ 'search:results': {
7
+ term: string;
8
+ total: number;
9
+ };
10
+ }
11
+ }
@@ -0,0 +1 @@
1
+ "use strict";
@@ -0,0 +1,12 @@
1
+ export * from './module';
2
+ export * from './adapter/types';
3
+ export * from './core/SearchManager';
4
+ export * from './core/SearchContext';
5
+ export * from './core/Filters';
6
+ export * from './core/Facets';
7
+ export * from './bridges/instantsearch';
8
+ export * from './bridges/vue';
9
+ export * from './bridges/react';
10
+ export * from './bridges/searchkit';
11
+ export * from './bridges/searchkit-server';
12
+ export * from './utils/health';
@@ -0,0 +1,12 @@
1
+ export * from './module';
2
+ export * from './adapter/types';
3
+ export * from './core/SearchManager';
4
+ export * from './core/SearchContext';
5
+ export * from './core/Filters';
6
+ export * from './core/Facets';
7
+ export * from './bridges/instantsearch';
8
+ export * from './bridges/vue';
9
+ export * from './bridges/react';
10
+ export * from './bridges/searchkit';
11
+ export * from './bridges/searchkit-server';
12
+ export * from './utils/health';
@@ -0,0 +1,17 @@
1
+ import { type AlternateContext } from '@meeovi/core';
2
+ import { type SearchModuleConfig } from './config/schema';
3
+ import type { MeeoviSearchItem } from './adapter/types';
4
+ declare module '@meeovi/core' {
5
+ interface AlternateConfig {
6
+ search?: SearchModuleConfig;
7
+ }
8
+ interface AlternateContext {
9
+ searchManager?: import('./core/SearchManager').SearchManager<MeeoviSearchItem>;
10
+ }
11
+ }
12
+ declare const _default: {
13
+ id: string;
14
+ adapters: {};
15
+ setup(ctx: AlternateContext): Promise<void>;
16
+ };
17
+ export default _default;
@@ -0,0 +1,73 @@
1
+ import { defineAlternateModule, useAlternateEventBus } from '@meeovi/core';
2
+ import { validateSearchConfig } from './config/schema';
3
+ import { createOpenSearchAdapter } from './adapter/opensearch';
4
+ import { createMeilisearchAdapter } from './adapter/meilisearch';
5
+ import { SearchManager } from './core/SearchManager';
6
+ export default defineAlternateModule({
7
+ id: 'search',
8
+ adapters: {},
9
+ async setup(ctx) {
10
+ const bus = useAlternateEventBus();
11
+ const config = ctx.config.search;
12
+ if (!config)
13
+ return;
14
+ validateSearchConfig(config);
15
+ if (config.defaultProvider === 'opensearch') {
16
+ const providerConfig = config.providers.opensearch;
17
+ if (providerConfig) {
18
+ this.adapters = {
19
+ search: createOpenSearchAdapter(providerConfig)
20
+ };
21
+ }
22
+ }
23
+ if (config.defaultProvider === 'meilisearch') {
24
+ const providerConfig = config.providers.meilisearch;
25
+ if (providerConfig) {
26
+ this.adapters = {
27
+ search: createMeilisearchAdapter(providerConfig)
28
+ };
29
+ }
30
+ }
31
+ const adapter = ctx.getAdapter('search');
32
+ if (adapter) {
33
+ ctx.searchManager = new SearchManager(adapter);
34
+ }
35
+ // Ensure search manager exists now if adapter already registered
36
+ if (!ctx.searchManager && ctx.getAdapter('search')) {
37
+ const runtimeAdapter = ctx.getAdapter('search');
38
+ if (runtimeAdapter) {
39
+ ctx.searchManager = new SearchManager(runtimeAdapter);
40
+ }
41
+ }
42
+ // Listen for app readiness and for runtime adapter registrations so
43
+ // new adapters (registered by other modules) will auto-create the
44
+ // search manager when they become available.
45
+ bus.on('app:ready', () => {
46
+ if (!ctx.searchManager) {
47
+ const runtimeAdapter = ctx.getAdapter('search');
48
+ if (runtimeAdapter) {
49
+ ctx.searchManager = new SearchManager(runtimeAdapter);
50
+ }
51
+ }
52
+ console.info('[@meeovi/search] Search module initialized');
53
+ });
54
+ // Optional runtime event: if other modules emit `adapter:registered`
55
+ // with a `{ key }` payload, respond and initialize when the `search`
56
+ // adapter becomes available.
57
+ // This is defensive — the core registry does not emit this by default,
58
+ // but some runtimes may choose to.
59
+ bus.on('adapter:registered', (payload) => {
60
+ try {
61
+ if (payload?.key === 'search' && !ctx.searchManager) {
62
+ const runtimeAdapter = ctx.getAdapter('search');
63
+ if (runtimeAdapter) {
64
+ ctx.searchManager = new SearchManager(runtimeAdapter);
65
+ }
66
+ }
67
+ }
68
+ catch (e) {
69
+ /* noop */
70
+ }
71
+ });
72
+ }
73
+ });
@@ -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,11 @@
1
+ import type { SearchAdapter } from '@meeovi/core';
2
+ export declare function checkAdapterHealth(adapter: SearchAdapter<any>): Promise<{
3
+ ok: boolean;
4
+ total: number;
5
+ error?: undefined;
6
+ } | {
7
+ ok: boolean;
8
+ error: any;
9
+ total?: undefined;
10
+ }>;
11
+ export default checkAdapterHealth;