@86d-app/search 0.0.23 → 0.0.25

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 (104) hide show
  1. package/dist/modules/search/src/__tests__/admin-settings.test.js +262 -0
  2. package/dist/modules/search/src/__tests__/controllers.test.js +853 -0
  3. package/dist/modules/search/src/__tests__/embedding-provider.test.js +150 -0
  4. package/dist/modules/search/src/__tests__/endpoint-security.test.js +250 -0
  5. package/dist/modules/search/src/__tests__/meilisearch-provider.test.js +318 -0
  6. package/dist/modules/search/src/__tests__/service-impl.test.js +703 -0
  7. package/dist/modules/search/src/__tests__/store-endpoints.test.js +295 -0
  8. package/dist/{admin/components/index.d.ts → modules/search/src/admin/components/index.jsx} +0 -1
  9. package/dist/modules/search/src/admin/components/search-analytics.jsx +230 -0
  10. package/dist/modules/search/src/admin/endpoints/analytics.js +9 -0
  11. package/dist/modules/search/src/admin/endpoints/bulk-index.js +26 -0
  12. package/dist/modules/search/src/admin/endpoints/click-analytics.js +9 -0
  13. package/dist/modules/search/src/admin/endpoints/get-settings.js +97 -0
  14. package/dist/modules/search/src/admin/endpoints/index-manage.js +32 -0
  15. package/dist/modules/search/src/admin/endpoints/index.js +21 -0
  16. package/dist/modules/search/src/admin/endpoints/popular.js +11 -0
  17. package/dist/modules/search/src/admin/endpoints/synonyms.js +30 -0
  18. package/dist/modules/search/src/admin/endpoints/zero-results.js +11 -0
  19. package/dist/modules/search/src/embedding-provider.js +77 -0
  20. package/dist/modules/search/src/index.js +75 -0
  21. package/dist/modules/search/src/meilisearch-provider.js +138 -0
  22. package/dist/modules/search/src/schema.js +61 -0
  23. package/dist/modules/search/src/service-impl.js +770 -0
  24. package/dist/modules/search/src/service.js +1 -0
  25. package/dist/modules/search/src/store/components/_hooks.js +10 -0
  26. package/dist/modules/search/src/store/components/index.jsx +9 -0
  27. package/dist/modules/search/src/store/components/search-bar.jsx +91 -0
  28. package/dist/modules/search/src/store/components/search-page.jsx +17 -0
  29. package/dist/modules/search/src/store/components/search-results.jsx +51 -0
  30. package/dist/modules/search/src/store/endpoints/click.js +15 -0
  31. package/dist/modules/search/src/store/endpoints/index.js +12 -0
  32. package/dist/modules/search/src/store/endpoints/recent.js +18 -0
  33. package/dist/modules/search/src/store/endpoints/search.js +57 -0
  34. package/dist/modules/search/src/store/endpoints/store-search.js +33 -0
  35. package/dist/modules/search/src/store/endpoints/suggest.js +12 -0
  36. package/package.json +1 -1
  37. package/src/__tests__/admin-settings.test.ts +367 -0
  38. package/src/__tests__/store-endpoints.test.ts +392 -0
  39. package/src/admin/endpoints/get-settings.ts +77 -0
  40. package/dist/__tests__/controllers.test.d.ts +0 -2
  41. package/dist/__tests__/controllers.test.d.ts.map +0 -1
  42. package/dist/__tests__/embedding-provider.test.d.ts +0 -2
  43. package/dist/__tests__/embedding-provider.test.d.ts.map +0 -1
  44. package/dist/__tests__/endpoint-security.test.d.ts +0 -2
  45. package/dist/__tests__/endpoint-security.test.d.ts.map +0 -1
  46. package/dist/__tests__/meilisearch-provider.test.d.ts +0 -2
  47. package/dist/__tests__/meilisearch-provider.test.d.ts.map +0 -1
  48. package/dist/__tests__/service-impl.test.d.ts +0 -2
  49. package/dist/__tests__/service-impl.test.d.ts.map +0 -1
  50. package/dist/admin/components/index.d.ts.map +0 -1
  51. package/dist/admin/components/search-analytics.d.ts +0 -2
  52. package/dist/admin/components/search-analytics.d.ts.map +0 -1
  53. package/dist/admin/endpoints/analytics.d.ts +0 -15
  54. package/dist/admin/endpoints/analytics.d.ts.map +0 -1
  55. package/dist/admin/endpoints/bulk-index.d.ts +0 -20
  56. package/dist/admin/endpoints/bulk-index.d.ts.map +0 -1
  57. package/dist/admin/endpoints/click-analytics.d.ts +0 -7
  58. package/dist/admin/endpoints/click-analytics.d.ts.map +0 -1
  59. package/dist/admin/endpoints/get-settings.d.ts +0 -17
  60. package/dist/admin/endpoints/get-settings.d.ts.map +0 -1
  61. package/dist/admin/endpoints/index-manage.d.ts +0 -26
  62. package/dist/admin/endpoints/index-manage.d.ts.map +0 -1
  63. package/dist/admin/endpoints/index.d.ts +0 -125
  64. package/dist/admin/endpoints/index.d.ts.map +0 -1
  65. package/dist/admin/endpoints/popular.d.ts +0 -10
  66. package/dist/admin/endpoints/popular.d.ts.map +0 -1
  67. package/dist/admin/endpoints/synonyms.d.ts +0 -30
  68. package/dist/admin/endpoints/synonyms.d.ts.map +0 -1
  69. package/dist/admin/endpoints/zero-results.d.ts +0 -10
  70. package/dist/admin/endpoints/zero-results.d.ts.map +0 -1
  71. package/dist/embedding-provider.d.ts +0 -28
  72. package/dist/embedding-provider.d.ts.map +0 -1
  73. package/dist/index.d.ts +0 -23
  74. package/dist/index.d.ts.map +0 -1
  75. package/dist/meilisearch-provider.d.ts +0 -104
  76. package/dist/meilisearch-provider.d.ts.map +0 -1
  77. package/dist/schema.d.ts +0 -133
  78. package/dist/schema.d.ts.map +0 -1
  79. package/dist/service-impl.d.ts +0 -6
  80. package/dist/service-impl.d.ts.map +0 -1
  81. package/dist/service.d.ts +0 -127
  82. package/dist/service.d.ts.map +0 -1
  83. package/dist/store/components/_hooks.d.ts +0 -6
  84. package/dist/store/components/_hooks.d.ts.map +0 -1
  85. package/dist/store/components/index.d.ts +0 -10
  86. package/dist/store/components/index.d.ts.map +0 -1
  87. package/dist/store/components/search-bar.d.ts +0 -7
  88. package/dist/store/components/search-bar.d.ts.map +0 -1
  89. package/dist/store/components/search-page.d.ts +0 -4
  90. package/dist/store/components/search-page.d.ts.map +0 -1
  91. package/dist/store/components/search-results.d.ts +0 -9
  92. package/dist/store/components/search-results.d.ts.map +0 -1
  93. package/dist/store/endpoints/click.d.ts +0 -14
  94. package/dist/store/endpoints/click.d.ts.map +0 -1
  95. package/dist/store/endpoints/index.d.ts +0 -85
  96. package/dist/store/endpoints/index.d.ts.map +0 -1
  97. package/dist/store/endpoints/recent.d.ts +0 -15
  98. package/dist/store/endpoints/recent.d.ts.map +0 -1
  99. package/dist/store/endpoints/search.d.ts +0 -36
  100. package/dist/store/endpoints/search.d.ts.map +0 -1
  101. package/dist/store/endpoints/store-search.d.ts +0 -16
  102. package/dist/store/endpoints/store-search.d.ts.map +0 -1
  103. package/dist/store/endpoints/suggest.d.ts +0 -11
  104. package/dist/store/endpoints/suggest.d.ts.map +0 -1
@@ -0,0 +1,392 @@
1
+ import { createMockDataService } from "@86d-app/core/test-utils";
2
+ import { beforeEach, describe, expect, it } from "vitest";
3
+ import type { SearchResult } from "../service";
4
+ import { createSearchController } from "../service-impl";
5
+
6
+ /**
7
+ * Store endpoint integration tests for the search module.
8
+ *
9
+ * These tests verify the business logic in store-facing endpoints:
10
+ *
11
+ * 1. search: comma-separated tag parsing, response shaping (strips
12
+ * internal fields like body/metadata), fire-and-forget analytics,
13
+ * facets and didYouMean pass-through
14
+ * 2. suggest: prefix autocomplete, limit handling
15
+ * 3. click: analytics click recording
16
+ * 4. recent: session-scoped recent query history, response shaping
17
+ */
18
+
19
+ type DataService = ReturnType<typeof createMockDataService>;
20
+
21
+ // ── Simulate store endpoint logic ─────────────────────────────────────
22
+
23
+ /**
24
+ * Simulates the search endpoint: parses tags from comma-separated
25
+ * string, delegates to controller, shapes response by stripping
26
+ * internal fields.
27
+ */
28
+ async function simulateSearch(
29
+ data: DataService,
30
+ query: {
31
+ q: string;
32
+ type?: string;
33
+ tags?: string;
34
+ sort?: "relevance" | "newest" | "oldest" | "title_asc" | "title_desc";
35
+ fuzzy?: boolean;
36
+ limit?: number;
37
+ skip?: number;
38
+ sessionId?: string;
39
+ },
40
+ ) {
41
+ const controller = createSearchController(data);
42
+ const parsedTags = query.tags
43
+ ? query.tags
44
+ .split(",")
45
+ .map((t) => t.trim())
46
+ .filter(Boolean)
47
+ : undefined;
48
+
49
+ const { results, total, facets, didYouMean } = await controller.search(
50
+ query.q,
51
+ {
52
+ entityType: query.type,
53
+ tags: parsedTags,
54
+ sort: query.sort,
55
+ fuzzy: query.fuzzy,
56
+ limit: query.limit ?? 20,
57
+ skip: query.skip ?? 0,
58
+ },
59
+ );
60
+
61
+ // Fire-and-forget analytics recording
62
+ controller.recordQuery(query.q, total, query.sessionId).catch(() => {});
63
+
64
+ return {
65
+ results: results.map((r: SearchResult) => ({
66
+ id: r.item.id,
67
+ entityType: r.item.entityType,
68
+ entityId: r.item.entityId,
69
+ title: r.item.title,
70
+ url: r.item.url,
71
+ image: r.item.image,
72
+ tags: r.item.tags,
73
+ score: r.score,
74
+ highlights: r.highlights,
75
+ })),
76
+ total,
77
+ facets,
78
+ didYouMean,
79
+ };
80
+ }
81
+
82
+ /**
83
+ * Simulates the suggest endpoint.
84
+ */
85
+ async function simulateSuggest(data: DataService, q: string, limit?: number) {
86
+ const controller = createSearchController(data);
87
+ const suggestions = await controller.suggest(q, limit ?? 8);
88
+ return { suggestions };
89
+ }
90
+
91
+ /**
92
+ * Simulates the click endpoint.
93
+ */
94
+ async function simulateClick(
95
+ data: DataService,
96
+ body: {
97
+ queryId: string;
98
+ term: string;
99
+ entityType: string;
100
+ entityId: string;
101
+ position: number;
102
+ },
103
+ ) {
104
+ const controller = createSearchController(data);
105
+ const click = await controller.recordClick(body);
106
+ return { id: click.id };
107
+ }
108
+
109
+ /**
110
+ * Simulates the recent endpoint: returns shaped recent queries.
111
+ */
112
+ async function simulateRecent(
113
+ data: DataService,
114
+ sessionId: string,
115
+ limit?: number,
116
+ ) {
117
+ const controller = createSearchController(data);
118
+ const queries = await controller.getRecentQueries(sessionId, limit ?? 10);
119
+ return {
120
+ recent: queries.map((q) => ({
121
+ term: q.term,
122
+ resultCount: q.resultCount,
123
+ searchedAt: q.searchedAt,
124
+ })),
125
+ };
126
+ }
127
+
128
+ // ── Helpers ───────────────────────────────────────────────────────────
129
+
130
+ async function seedIndexItems(data: DataService) {
131
+ const controller = createSearchController(data);
132
+ await controller.indexItem({
133
+ entityType: "product",
134
+ entityId: "prod-1",
135
+ title: "Red Widget",
136
+ body: "A fantastic red widget for everyday use",
137
+ tags: ["electronics", "sale"],
138
+ url: "/products/red-widget",
139
+ image: "/img/red-widget.jpg",
140
+ });
141
+ await controller.indexItem({
142
+ entityType: "product",
143
+ entityId: "prod-2",
144
+ title: "Blue Gadget",
145
+ body: "The best blue gadget on the market",
146
+ tags: ["electronics", "new"],
147
+ url: "/products/blue-gadget",
148
+ });
149
+ await controller.indexItem({
150
+ entityType: "page",
151
+ entityId: "page-1",
152
+ title: "About Us",
153
+ body: "Learn about our company",
154
+ tags: ["info"],
155
+ url: "/about",
156
+ });
157
+ }
158
+
159
+ // ── Tests ─────────────────────────────────────────────────────────────
160
+
161
+ describe("search store endpoints", () => {
162
+ let data: DataService;
163
+
164
+ beforeEach(() => {
165
+ data = createMockDataService();
166
+ });
167
+
168
+ // ── search ───────────────────────────────────────────────────────
169
+
170
+ describe("search", () => {
171
+ it("returns results matching query", async () => {
172
+ await seedIndexItems(data);
173
+ const result = await simulateSearch(data, { q: "widget" });
174
+
175
+ expect(result.total).toBeGreaterThan(0);
176
+ expect(result.results[0].title).toContain("Widget");
177
+ });
178
+
179
+ it("shapes response by stripping internal fields", async () => {
180
+ await seedIndexItems(data);
181
+ const result = await simulateSearch(data, { q: "red" });
182
+
183
+ const firstResult = result.results[0];
184
+ // Should include these fields
185
+ expect(firstResult).toHaveProperty("id");
186
+ expect(firstResult).toHaveProperty("entityType");
187
+ expect(firstResult).toHaveProperty("entityId");
188
+ expect(firstResult).toHaveProperty("title");
189
+ expect(firstResult).toHaveProperty("url");
190
+ expect(firstResult).toHaveProperty("tags");
191
+ expect(firstResult).toHaveProperty("score");
192
+ // Should NOT include body or metadata (internal fields)
193
+ expect(firstResult).not.toHaveProperty("body");
194
+ expect(firstResult).not.toHaveProperty("metadata");
195
+ expect(firstResult).not.toHaveProperty("indexedAt");
196
+ });
197
+
198
+ it("filters by entity type", async () => {
199
+ await seedIndexItems(data);
200
+ const result = await simulateSearch(data, {
201
+ q: "about",
202
+ type: "page",
203
+ });
204
+
205
+ for (const r of result.results) {
206
+ expect(r.entityType).toBe("page");
207
+ }
208
+ });
209
+
210
+ it("parses comma-separated tags into array", async () => {
211
+ await seedIndexItems(data);
212
+ const result = await simulateSearch(data, {
213
+ q: "widget",
214
+ tags: "electronics, sale",
215
+ });
216
+
217
+ // Should find items tagged with electronics AND sale
218
+ expect(result.total).toBeGreaterThanOrEqual(0);
219
+ });
220
+
221
+ it("handles tags with extra whitespace and empty segments", async () => {
222
+ await seedIndexItems(data);
223
+
224
+ // "electronics,, new, " should parse to ["electronics", "new"]
225
+ const result = await simulateSearch(data, {
226
+ q: "gadget",
227
+ tags: "electronics,, new, ",
228
+ });
229
+
230
+ expect(result.total).toBeGreaterThanOrEqual(0);
231
+ });
232
+
233
+ it("returns empty results for no-match query", async () => {
234
+ await seedIndexItems(data);
235
+ const result = await simulateSearch(data, {
236
+ q: "xyznonexistent",
237
+ });
238
+
239
+ expect(result.results).toHaveLength(0);
240
+ expect(result.total).toBe(0);
241
+ });
242
+
243
+ it("respects limit and skip pagination", async () => {
244
+ await seedIndexItems(data);
245
+ const page1 = await simulateSearch(data, {
246
+ q: "widget gadget about",
247
+ limit: 1,
248
+ skip: 0,
249
+ });
250
+ const page2 = await simulateSearch(data, {
251
+ q: "widget gadget about",
252
+ limit: 1,
253
+ skip: 1,
254
+ });
255
+
256
+ // Pages should return at most 1 result each
257
+ expect(page1.results.length).toBeLessThanOrEqual(1);
258
+ expect(page2.results.length).toBeLessThanOrEqual(1);
259
+ });
260
+
261
+ it("returns facets with result counts", async () => {
262
+ await seedIndexItems(data);
263
+ const result = await simulateSearch(data, { q: "widget" });
264
+
265
+ expect(result.facets).toBeDefined();
266
+ expect(result.facets.entityTypes).toBeDefined();
267
+ expect(result.facets.tags).toBeDefined();
268
+ });
269
+
270
+ it("includes image field when present", async () => {
271
+ await seedIndexItems(data);
272
+ const result = await simulateSearch(data, { q: "red widget" });
273
+
274
+ const redWidget = result.results.find((r) => r.entityId === "prod-1");
275
+ if (redWidget) {
276
+ expect(redWidget.image).toBe("/img/red-widget.jpg");
277
+ }
278
+
279
+ const blueGadget = result.results.find((r) => r.entityId === "prod-2");
280
+ if (blueGadget) {
281
+ expect(blueGadget.image).toBeUndefined();
282
+ }
283
+ });
284
+ });
285
+
286
+ // ── suggest ──────────────────────────────────────────────────────
287
+
288
+ describe("suggest", () => {
289
+ it("returns suggestions for prefix", async () => {
290
+ await seedIndexItems(data);
291
+ const result = await simulateSuggest(data, "wid");
292
+
293
+ expect(result.suggestions).toBeDefined();
294
+ expect(Array.isArray(result.suggestions)).toBe(true);
295
+ });
296
+
297
+ it("returns empty for no-match prefix", async () => {
298
+ await seedIndexItems(data);
299
+ const result = await simulateSuggest(data, "zzz");
300
+ expect(result.suggestions).toHaveLength(0);
301
+ });
302
+
303
+ it("respects custom limit", async () => {
304
+ await seedIndexItems(data);
305
+ const result = await simulateSuggest(data, "a", 2);
306
+ expect(result.suggestions.length).toBeLessThanOrEqual(2);
307
+ });
308
+ });
309
+
310
+ // ── click ────────────────────────────────────────────────────────
311
+
312
+ describe("click", () => {
313
+ it("records a click and returns id", async () => {
314
+ const result = await simulateClick(data, {
315
+ queryId: "q-1",
316
+ term: "widget",
317
+ entityType: "product",
318
+ entityId: "prod-1",
319
+ position: 0,
320
+ });
321
+
322
+ expect(result.id).toBeDefined();
323
+ expect(typeof result.id).toBe("string");
324
+ });
325
+
326
+ it("records multiple clicks for same query", async () => {
327
+ const click1 = await simulateClick(data, {
328
+ queryId: "q-1",
329
+ term: "widget",
330
+ entityType: "product",
331
+ entityId: "prod-1",
332
+ position: 0,
333
+ });
334
+ const click2 = await simulateClick(data, {
335
+ queryId: "q-1",
336
+ term: "widget",
337
+ entityType: "product",
338
+ entityId: "prod-2",
339
+ position: 1,
340
+ });
341
+
342
+ expect(click1.id).not.toBe(click2.id);
343
+ });
344
+ });
345
+
346
+ // ── recent ───────────────────────────────────────────────────────
347
+
348
+ describe("recent", () => {
349
+ it("returns empty for new session", async () => {
350
+ const result = await simulateRecent(data, "session-new");
351
+ expect(result.recent).toHaveLength(0);
352
+ });
353
+
354
+ it("returns shaped recent queries (term, resultCount, searchedAt only)", async () => {
355
+ const controller = createSearchController(data);
356
+ await controller.recordQuery("widgets", 5, "session-1");
357
+ await controller.recordQuery("gadgets", 3, "session-1");
358
+
359
+ const result = await simulateRecent(data, "session-1");
360
+ expect(result.recent.length).toBeGreaterThan(0);
361
+
362
+ const entry = result.recent[0];
363
+ expect(entry).toHaveProperty("term");
364
+ expect(entry).toHaveProperty("resultCount");
365
+ expect(entry).toHaveProperty("searchedAt");
366
+ // Should NOT include internal fields
367
+ expect(entry).not.toHaveProperty("id");
368
+ expect(entry).not.toHaveProperty("normalizedTerm");
369
+ expect(entry).not.toHaveProperty("sessionId");
370
+ });
371
+
372
+ it("scopes queries to session", async () => {
373
+ const controller = createSearchController(data);
374
+ await controller.recordQuery("widgets", 5, "session-1");
375
+ await controller.recordQuery("gadgets", 3, "session-2");
376
+
377
+ const result = await simulateRecent(data, "session-1");
378
+ expect(result.recent).toHaveLength(1);
379
+ expect(result.recent[0].term).toBe("widgets");
380
+ });
381
+
382
+ it("respects limit parameter", async () => {
383
+ const controller = createSearchController(data);
384
+ for (let i = 0; i < 5; i++) {
385
+ await controller.recordQuery(`term-${i}`, i, "session-1");
386
+ }
387
+
388
+ const result = await simulateRecent(data, "session-1", 2);
389
+ expect(result.recent.length).toBeLessThanOrEqual(2);
390
+ });
391
+ });
392
+ });
@@ -1,4 +1,5 @@
1
1
  import { createAdminEndpoint } from "@86d-app/core";
2
+ import { MeiliSearchProvider } from "../../meilisearch-provider";
2
3
  import type { SearchController } from "../../service";
3
4
 
4
5
  function maskKey(key: string): string {
@@ -15,6 +16,31 @@ interface SearchModuleOptions {
15
16
  embeddingModel?: string;
16
17
  }
17
18
 
19
+ async function verifyEmbeddingConnection(
20
+ apiKey: string,
21
+ baseUrl: string,
22
+ ): Promise<{ ok: true } | { ok: false; error: string }> {
23
+ try {
24
+ const res = await fetch(`${baseUrl}/models`, {
25
+ method: "GET",
26
+ headers: { Authorization: `Bearer ${apiKey}` },
27
+ });
28
+ if (res.ok) {
29
+ return { ok: true };
30
+ }
31
+ const text = await res.text().catch(() => "");
32
+ return {
33
+ ok: false,
34
+ error: `HTTP ${res.status}${text ? `: ${text.slice(0, 200)}` : ""}`,
35
+ };
36
+ } catch (err) {
37
+ return {
38
+ ok: false,
39
+ error: err instanceof Error ? err.message : "Connection failed",
40
+ };
41
+ }
42
+ }
43
+
18
44
  export const getSettings = createAdminEndpoint(
19
45
  "/admin/search/settings",
20
46
  { method: "GET" },
@@ -30,18 +56,69 @@ export const getSettings = createAdminEndpoint(
30
56
  options?.openaiApiKey || options?.openrouterApiKey,
31
57
  );
32
58
 
59
+ let meilisearchStatus: "connected" | "not_configured" | "error" =
60
+ "not_configured";
61
+ let meilisearchError: string | undefined;
62
+ let documentCount: number | undefined;
63
+
64
+ if (
65
+ meilisearchConfigured &&
66
+ options?.meilisearchHost &&
67
+ options.meilisearchApiKey
68
+ ) {
69
+ const provider = new MeiliSearchProvider(
70
+ options.meilisearchHost,
71
+ options.meilisearchApiKey,
72
+ options.meilisearchIndexUid,
73
+ );
74
+ const healthy = await provider.isHealthy();
75
+ if (healthy) {
76
+ meilisearchStatus = "connected";
77
+ const stats = await provider.getStats();
78
+ if (stats) {
79
+ documentCount = stats.numberOfDocuments;
80
+ }
81
+ } else {
82
+ meilisearchStatus = "error";
83
+ meilisearchError = "MeiliSearch instance is not reachable";
84
+ }
85
+ }
86
+
87
+ let embeddingStatus: "connected" | "not_configured" | "error" =
88
+ "not_configured";
89
+ let embeddingError: string | undefined;
90
+
91
+ if (embeddingConfigured) {
92
+ const apiKey = options?.openaiApiKey ?? options?.openrouterApiKey ?? "";
93
+ const baseUrl = options?.openrouterApiKey
94
+ ? "https://openrouter.ai/api/v1"
95
+ : "https://api.openai.com/v1";
96
+ const result = await verifyEmbeddingConnection(apiKey, baseUrl);
97
+ if (result.ok) {
98
+ embeddingStatus = "connected";
99
+ } else {
100
+ embeddingStatus = "error";
101
+ embeddingError = result.error;
102
+ }
103
+ }
104
+
33
105
  const indexCount = await controller.getIndexCount();
34
106
 
35
107
  return {
36
108
  meilisearch: {
109
+ status: meilisearchStatus,
110
+ error: meilisearchError,
37
111
  configured: meilisearchConfigured,
38
112
  host: options?.meilisearchHost ?? null,
39
113
  apiKey: options?.meilisearchApiKey
40
114
  ? maskKey(options.meilisearchApiKey)
41
115
  : null,
42
116
  indexUid: options?.meilisearchIndexUid ?? "search",
117
+ documentCount,
43
118
  },
44
119
  embeddings: {
120
+ status: embeddingStatus,
121
+ error: embeddingError,
45
122
  configured: embeddingConfigured,
46
123
  provider: options?.openaiApiKey
47
124
  ? "openai"
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=controllers.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"controllers.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/controllers.test.ts"],"names":[],"mappings":""}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=embedding-provider.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"embedding-provider.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/embedding-provider.test.ts"],"names":[],"mappings":""}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=endpoint-security.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"endpoint-security.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/endpoint-security.test.ts"],"names":[],"mappings":""}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=meilisearch-provider.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"meilisearch-provider.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/meilisearch-provider.test.ts"],"names":[],"mappings":""}
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=service-impl.test.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"service-impl.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/service-impl.test.ts"],"names":[],"mappings":""}
@@ -1 +0,0 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/admin/components/index.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,oBAAoB,CAAC"}
@@ -1,2 +0,0 @@
1
- export declare function SearchAnalytics(): import("react").JSX.Element;
2
- //# sourceMappingURL=search-analytics.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"search-analytics.d.ts","sourceRoot":"","sources":["../../../src/admin/components/search-analytics.tsx"],"names":[],"mappings":"AA4FA,wBAAgB,eAAe,gCA+T9B"}
@@ -1,15 +0,0 @@
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
@@ -1 +0,0 @@
1
- {"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../../../src/admin/endpoints/analytics.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;EAW7B,CAAC"}
@@ -1,20 +0,0 @@
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
@@ -1 +0,0 @@
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"}
@@ -1,7 +0,0 @@
1
- export declare const clickAnalyticsEndpoint: import("better-call").StrictEndpoint<"/admin/search/clicks", {
2
- method: "GET";
3
- }, {
4
- clickThroughRate: number;
5
- avgClickPosition: number;
6
- }>;
7
- //# sourceMappingURL=click-analytics.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"click-analytics.d.ts","sourceRoot":"","sources":["../../../src/admin/endpoints/click-analytics.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,sBAAsB;;;;;EAYlC,CAAC"}
@@ -1,17 +0,0 @@
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
@@ -1 +0,0 @@
1
- {"version":3,"file":"get-settings.d.ts","sourceRoot":"","sources":["../../../src/admin/endpoints/get-settings.ts"],"names":[],"mappings":"AAiBA,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;EAsCvB,CAAC"}
@@ -1,26 +0,0 @@
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
@@ -1 +0,0 @@
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"}