@rubixstudios/payload-typesense 1.0.0

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 (60) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +182 -0
  3. package/dist/components/HeadlessSearchInput.d.ts +86 -0
  4. package/dist/components/HeadlessSearchInput.d.ts.map +1 -0
  5. package/dist/components/HeadlessSearchInput.js +602 -0
  6. package/dist/components/ThemeProvider.d.ts +10 -0
  7. package/dist/components/ThemeProvider.d.ts.map +1 -0
  8. package/dist/components/ThemeProvider.js +17 -0
  9. package/dist/components/index.d.ts +6 -0
  10. package/dist/components/index.d.ts.map +1 -0
  11. package/dist/components/index.js +3 -0
  12. package/dist/components/themes/hooks.d.ts +52 -0
  13. package/dist/components/themes/hooks.d.ts.map +1 -0
  14. package/dist/components/themes/hooks.js +177 -0
  15. package/dist/components/themes/index.d.ts +5 -0
  16. package/dist/components/themes/index.d.ts.map +1 -0
  17. package/dist/components/themes/index.js +4 -0
  18. package/dist/components/themes/themes.d.ts +6 -0
  19. package/dist/components/themes/themes.d.ts.map +1 -0
  20. package/dist/components/themes/themes.js +156 -0
  21. package/dist/components/themes/types.d.ts +147 -0
  22. package/dist/components/themes/types.d.ts.map +1 -0
  23. package/dist/components/themes/types.js +1 -0
  24. package/dist/components/themes/utils.d.ts +30 -0
  25. package/dist/components/themes/utils.d.ts.map +1 -0
  26. package/dist/components/themes/utils.js +397 -0
  27. package/dist/endpoints/customEndpointHandler.d.ts +3 -0
  28. package/dist/endpoints/customEndpointHandler.d.ts.map +1 -0
  29. package/dist/endpoints/customEndpointHandler.js +5 -0
  30. package/dist/endpoints/health.d.ts +12 -0
  31. package/dist/endpoints/health.d.ts.map +1 -0
  32. package/dist/endpoints/health.js +174 -0
  33. package/dist/endpoints/search.d.ts +13 -0
  34. package/dist/endpoints/search.d.ts.map +1 -0
  35. package/dist/endpoints/search.js +375 -0
  36. package/dist/index.d.ts +39 -0
  37. package/dist/index.d.ts.map +1 -0
  38. package/dist/index.js +148 -0
  39. package/dist/lib/cache.d.ts +41 -0
  40. package/dist/lib/cache.d.ts.map +1 -0
  41. package/dist/lib/cache.js +96 -0
  42. package/dist/lib/config-validation.d.ts +75 -0
  43. package/dist/lib/config-validation.d.ts.map +1 -0
  44. package/dist/lib/config-validation.js +174 -0
  45. package/dist/lib/hooks.d.ts +4 -0
  46. package/dist/lib/hooks.d.ts.map +1 -0
  47. package/dist/lib/hooks.js +54 -0
  48. package/dist/lib/initialization.d.ts +5 -0
  49. package/dist/lib/initialization.d.ts.map +1 -0
  50. package/dist/lib/initialization.js +102 -0
  51. package/dist/lib/schema-mapper.d.ts +14 -0
  52. package/dist/lib/schema-mapper.d.ts.map +1 -0
  53. package/dist/lib/schema-mapper.js +137 -0
  54. package/dist/lib/types.d.ts +183 -0
  55. package/dist/lib/types.d.ts.map +1 -0
  56. package/dist/lib/types.js +2 -0
  57. package/dist/lib/typesense-client.d.ts +5 -0
  58. package/dist/lib/typesense-client.d.ts.map +1 -0
  59. package/dist/lib/typesense-client.js +20 -0
  60. package/package.json +92 -0
@@ -0,0 +1,375 @@
1
+ import { searchCache } from '../lib/cache.js';
2
+ import { getValidationErrors, validateSearchParams } from '../lib/config-validation.js';
3
+ import { createDetailedHealthCheckHandler, createHealthCheckHandler } from './health.js';
4
+ // Universal search across all collections
5
+ const searchAllCollections = async (typesenseClient, pluginOptions, query, options)=>{
6
+ try {
7
+ // Universal search logic
8
+ // Check cache first
9
+ const cachedResult = searchCache.get(query, 'universal', options);
10
+ if (cachedResult) {
11
+ // Return cached result
12
+ return Response.json(cachedResult);
13
+ }
14
+ const enabledCollections = Object.entries(pluginOptions.collections || {}).filter(([_, config])=>config?.enabled);
15
+ // Process enabled collections
16
+ if (enabledCollections.length === 0) {
17
+ return Response.json({
18
+ error: 'No collections enabled for search'
19
+ }, {
20
+ status: 400
21
+ });
22
+ }
23
+ // Search all collections in parallel
24
+ const searchPromises = enabledCollections.map(async ([collectionName, config])=>{
25
+ try {
26
+ const searchParameters = {
27
+ highlight_full_fields: config?.searchFields?.join(',') || 'title,content',
28
+ num_typos: 0,
29
+ page: options.page,
30
+ per_page: Math.ceil(options.per_page / enabledCollections.length),
31
+ q: query,
32
+ query_by: config?.searchFields?.join(',') || 'title,content',
33
+ snippet_threshold: 30,
34
+ typo_tokens_threshold: 1
35
+ };
36
+ // Search collection
37
+ const results = await typesenseClient.collections(collectionName).documents().search(searchParameters);
38
+ // Process results
39
+ // Add collection metadata to each hit
40
+ return {
41
+ collection: collectionName,
42
+ displayName: config?.displayName || collectionName,
43
+ icon: config?.icon || '📄',
44
+ ...results,
45
+ hits: results.hits?.map((hit)=>({
46
+ ...hit,
47
+ collection: collectionName,
48
+ displayName: config?.displayName || collectionName,
49
+ icon: config?.icon || '📄'
50
+ })) || []
51
+ };
52
+ } catch (_error) {
53
+ // Handle search error
54
+ return {
55
+ collection: collectionName,
56
+ displayName: config?.displayName || collectionName,
57
+ error: _error instanceof Error ? _error.message : 'Unknown error',
58
+ found: 0,
59
+ hits: [],
60
+ icon: config?.icon || '📄'
61
+ };
62
+ }
63
+ });
64
+ const results = await Promise.all(searchPromises);
65
+ // Combine results
66
+ const combinedHits = results.flatMap((result)=>result.hits || []);
67
+ const totalFound = results.reduce((sum, result)=>sum + (result.found || 0), 0);
68
+ // Sort combined results by relevance (text_match score)
69
+ combinedHits.sort((a, b)=>(b.text_match || 0) - (a.text_match || 0));
70
+ const searchResult = {
71
+ collections: results.map((r)=>({
72
+ collection: r.collection,
73
+ displayName: r.displayName,
74
+ error: r.error,
75
+ found: r.found || 0,
76
+ icon: r.icon
77
+ })),
78
+ found: totalFound,
79
+ hits: combinedHits.slice(0, options.per_page),
80
+ page: options.page,
81
+ request_params: {
82
+ per_page: options.per_page,
83
+ q: query
84
+ },
85
+ search_cutoff: false,
86
+ search_time_ms: 0
87
+ };
88
+ // Cache the result
89
+ searchCache.set(query, searchResult, 'universal', options);
90
+ return Response.json(searchResult);
91
+ } catch (error) {
92
+ // Handle universal search error
93
+ return Response.json({
94
+ details: error instanceof Error ? error.message : 'Unknown error',
95
+ error: 'Universal search failed'
96
+ }, {
97
+ status: 500
98
+ });
99
+ }
100
+ };
101
+ export const createSearchEndpoints = (typesenseClient, pluginOptions, lastSyncTime)=>{
102
+ return [
103
+ {
104
+ handler: createCollectionsHandler(pluginOptions),
105
+ method: 'get',
106
+ path: '/search/collections'
107
+ },
108
+ {
109
+ handler: createSuggestHandler(typesenseClient, pluginOptions),
110
+ method: 'get',
111
+ path: '/search/:collectionName/suggest'
112
+ },
113
+ {
114
+ handler: createSearchHandler(typesenseClient, pluginOptions),
115
+ method: 'get',
116
+ path: '/search/:collectionName'
117
+ },
118
+ {
119
+ handler: createAdvancedSearchHandler(typesenseClient, pluginOptions),
120
+ method: 'post',
121
+ path: '/search/:collectionName'
122
+ },
123
+ {
124
+ handler: createSearchHandler(typesenseClient, pluginOptions),
125
+ method: 'get',
126
+ path: '/search'
127
+ },
128
+ {
129
+ handler: createHealthCheckHandler(typesenseClient, pluginOptions, lastSyncTime),
130
+ method: 'get',
131
+ path: '/search/health'
132
+ },
133
+ {
134
+ handler: createDetailedHealthCheckHandler(typesenseClient, pluginOptions, lastSyncTime),
135
+ method: 'get',
136
+ path: '/search/health/detailed'
137
+ }
138
+ ];
139
+ };
140
+ const createSearchHandler = (typesenseClient, pluginOptions)=>{
141
+ return async (request)=>{
142
+ try {
143
+ // Extract query parameters from the request
144
+ const { params, query } = request;
145
+ // Extract collection name from URL path (fallback to params if available)
146
+ let collectionName;
147
+ let collectionNameStr;
148
+ if (request.url && typeof request.url === 'string') {
149
+ const url = new URL(request.url);
150
+ const pathParts = url.pathname.split('/');
151
+ const searchIndex = pathParts.indexOf('search');
152
+ if (searchIndex !== -1 && pathParts[searchIndex + 1]) {
153
+ collectionName = pathParts[searchIndex + 1] || '';
154
+ collectionNameStr = String(collectionName);
155
+ } else {
156
+ collectionName = '';
157
+ collectionNameStr = '';
158
+ }
159
+ } else {
160
+ // Fallback to params extraction
161
+ const { collectionName: paramCollectionName } = params || {};
162
+ collectionName = String(paramCollectionName || '');
163
+ collectionNameStr = collectionName;
164
+ }
165
+ // Extract search parameters
166
+ const q = typeof query?.q === 'string' ? query.q : '';
167
+ const pageParam = query?.page;
168
+ const perPageParam = query?.per_page;
169
+ const page = typeof pageParam === 'string' || typeof pageParam === 'number' ? parseInt(String(pageParam), 10) : 1;
170
+ const per_page = typeof perPageParam === 'string' || typeof perPageParam === 'number' ? parseInt(String(perPageParam), 10) : 10;
171
+ const sort_by = query?.sort_by;
172
+ // Validate parsed numbers
173
+ if (isNaN(page) || page < 1) {
174
+ return Response.json({
175
+ error: 'Invalid page parameter'
176
+ }, {
177
+ status: 400
178
+ });
179
+ }
180
+ if (isNaN(per_page) || per_page < 1 || per_page > 250) {
181
+ return Response.json({
182
+ error: 'Invalid per_page parameter'
183
+ }, {
184
+ status: 400
185
+ });
186
+ }
187
+ // Process search request
188
+ // Validate search parameters
189
+ const searchParams = {
190
+ page,
191
+ per_page,
192
+ q,
193
+ sort_by: sort_by
194
+ };
195
+ const validation = validateSearchParams(searchParams);
196
+ if (!validation.success) {
197
+ return Response.json({
198
+ details: getValidationErrors(validation.errors || []),
199
+ error: 'Invalid search parameters'
200
+ }, {
201
+ status: 400
202
+ });
203
+ }
204
+ // If no collection specified, search across all enabled collections
205
+ if (!collectionName) {
206
+ if (!q || q.trim() === '') {
207
+ return Response.json({
208
+ details: 'Please provide a search query using ?q=your_search_term',
209
+ error: 'Query parameter "q" is required',
210
+ example: '/api/search?q=example'
211
+ }, {
212
+ status: 400
213
+ });
214
+ }
215
+ const searchOptions = {
216
+ filters: {},
217
+ page,
218
+ per_page
219
+ };
220
+ if (sort_by && typeof sort_by === 'string') {
221
+ searchOptions.sort_by = sort_by;
222
+ }
223
+ return await searchAllCollections(typesenseClient, pluginOptions, q, searchOptions);
224
+ }
225
+ // Validate collection is enabled
226
+ if (!pluginOptions.collections?.[collectionNameStr]?.enabled) {
227
+ return Response.json({
228
+ error: 'Collection not enabled for search'
229
+ }, {
230
+ status: 400
231
+ });
232
+ }
233
+ if (!q) {
234
+ return Response.json({
235
+ error: 'Query parameter "q" is required'
236
+ }, {
237
+ status: 400
238
+ });
239
+ }
240
+ const searchParameters = {
241
+ highlight_full_fields: pluginOptions.collections?.[collectionNameStr]?.searchFields?.join(',') || 'title,content',
242
+ num_typos: 0,
243
+ page: Number(page),
244
+ per_page: Number(per_page),
245
+ q: String(q),
246
+ query_by: pluginOptions.collections?.[collectionNameStr]?.searchFields?.join(',') || 'title,content',
247
+ snippet_threshold: 30,
248
+ typo_tokens_threshold: 1
249
+ };
250
+ // Add sorting
251
+ if (sort_by && typeof sort_by === 'string') {
252
+ searchParameters.sort_by = sort_by;
253
+ }
254
+ // Execute Typesense search
255
+ // Check cache first
256
+ const cacheOptions = {
257
+ collection: collectionName,
258
+ page,
259
+ per_page,
260
+ sort_by
261
+ };
262
+ const cachedResult = searchCache.get(q, collectionNameStr, cacheOptions);
263
+ if (cachedResult) {
264
+ // Return cached result
265
+ return Response.json(cachedResult);
266
+ }
267
+ const searchResults = await typesenseClient.collections(collectionNameStr).documents().search(searchParameters);
268
+ // Process search results
269
+ // Cache the result
270
+ searchCache.set(q, searchResults, collectionNameStr, cacheOptions);
271
+ return Response.json(searchResults);
272
+ } catch (_error) {
273
+ // Handle search error
274
+ return Response.json({
275
+ details: _error instanceof Error ? _error.message : 'Unknown error',
276
+ error: 'Search handler failed'
277
+ }, {
278
+ status: 500
279
+ });
280
+ }
281
+ };
282
+ };
283
+ const createAdvancedSearchHandler = (typesenseClient, pluginOptions)=>{
284
+ return async (request)=>{
285
+ const { params, req } = request;
286
+ const { collectionName } = params || {};
287
+ const collectionNameStr = String(collectionName || '');
288
+ const body = await req?.json?.() || {};
289
+ if (!pluginOptions.collections?.[collectionNameStr]?.enabled) {
290
+ return Response.json({
291
+ error: 'Collection not enabled for search'
292
+ }, {
293
+ status: 400
294
+ });
295
+ }
296
+ try {
297
+ const searchResults = await typesenseClient.collections(collectionNameStr).documents().search(body);
298
+ return Response.json(searchResults);
299
+ } catch (_error) {
300
+ // Handle advanced search error
301
+ return Response.json({
302
+ error: 'Advanced search failed'
303
+ }, {
304
+ status: 500
305
+ });
306
+ }
307
+ };
308
+ };
309
+ const createSuggestHandler = (typesenseClient, pluginOptions)=>{
310
+ return async (request)=>{
311
+ // Extract collection name from URL path
312
+ const url = new URL(request.url);
313
+ const pathParts = url.pathname.split('/');
314
+ const collectionName = pathParts[pathParts.indexOf('search') + 1];
315
+ const collectionNameStr = String(collectionName || '');
316
+ // Extract query parameters
317
+ const q = url.searchParams.get('q');
318
+ const limit = url.searchParams.get('limit') || '5';
319
+ if (!collectionName || !pluginOptions.collections?.[collectionNameStr]?.enabled) {
320
+ return Response.json({
321
+ error: 'Collection not enabled for search'
322
+ }, {
323
+ status: 400
324
+ });
325
+ }
326
+ if (!q) {
327
+ return Response.json({
328
+ error: 'Query parameter "q" is required'
329
+ }, {
330
+ status: 400
331
+ });
332
+ }
333
+ try {
334
+ const suggestResults = await typesenseClient.collections(collectionNameStr).documents().search({
335
+ highlight_full_fields: pluginOptions.collections?.[collectionNameStr]?.searchFields?.join(',') || 'title,content',
336
+ per_page: Number(limit),
337
+ q,
338
+ query_by: pluginOptions.collections?.[collectionNameStr]?.searchFields?.join(',') || 'title,content',
339
+ snippet_threshold: 30
340
+ });
341
+ return Response.json(suggestResults);
342
+ } catch (_error) {
343
+ // Handle suggest error
344
+ return Response.json({
345
+ error: 'Suggest failed'
346
+ }, {
347
+ status: 500
348
+ });
349
+ }
350
+ };
351
+ };
352
+ const createCollectionsHandler = (pluginOptions)=>{
353
+ return ()=>{
354
+ try {
355
+ const collections = Object.entries(pluginOptions.collections || {}).filter(([_, config])=>config?.enabled).map(([slug, config])=>({
356
+ slug,
357
+ displayName: config?.displayName || slug.charAt(0).toUpperCase() + slug.slice(1),
358
+ facetFields: config?.facetFields || [],
359
+ icon: config?.icon || '📄',
360
+ searchFields: config?.searchFields || []
361
+ }));
362
+ return Response.json({
363
+ categorized: pluginOptions.settings?.categorized || false,
364
+ collections
365
+ });
366
+ } catch (_error) {
367
+ // Handle collections error
368
+ return Response.json({
369
+ error: 'Failed to get collections'
370
+ }, {
371
+ status: 500
372
+ });
373
+ }
374
+ };
375
+ };
@@ -0,0 +1,39 @@
1
+ import type { Config } from 'payload';
2
+ export * from './components/index.js';
3
+ export type TypesenseSearchConfig = {
4
+ /**
5
+ * Collections to index in Typesense
6
+ */
7
+ collections?: Partial<Record<string, {
8
+ displayName?: string;
9
+ enabled: boolean;
10
+ facetFields?: string[];
11
+ icon?: string;
12
+ searchFields?: string[];
13
+ sortFields?: string[];
14
+ }>>;
15
+ disabled?: boolean;
16
+ /**
17
+ * Global plugin settings
18
+ */
19
+ settings?: {
20
+ autoSync?: boolean;
21
+ batchSize?: number;
22
+ categorized?: boolean;
23
+ searchEndpoint?: string;
24
+ };
25
+ /**
26
+ * Typesense server configuration
27
+ */
28
+ typesense: {
29
+ apiKey: string;
30
+ connectionTimeoutSeconds?: number;
31
+ nodes: Array<{
32
+ host: string;
33
+ port: number | string;
34
+ protocol: 'http' | 'https';
35
+ }>;
36
+ };
37
+ };
38
+ export declare const typesenseSearch: (pluginOptions: TypesenseSearchConfig) => (config: Config) => Config;
39
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAQrC,cAAc,uBAAuB,CAAA;AAErC,MAAM,MAAM,qBAAqB,GAAG;IAClC;;OAEG;IACH,WAAW,CAAC,EAAE,OAAO,CACnB,MAAM,CACJ,MAAM,EACN;QACE,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,OAAO,EAAE,OAAO,CAAA;QAChB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAA;QACtB,IAAI,CAAC,EAAE,MAAM,CAAA;QACb,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;QACvB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;KACtB,CACF,CACF,CAAA;IAED,QAAQ,CAAC,EAAE,OAAO,CAAA;IAElB;;OAEG;IACH,QAAQ,CAAC,EAAE;QACT,QAAQ,CAAC,EAAE,OAAO,CAAA;QAClB,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,WAAW,CAAC,EAAE,OAAO,CAAA;QACrB,cAAc,CAAC,EAAE,MAAM,CAAA;KACxB,CAAA;IAED;;OAEG;IACH,SAAS,EAAE;QACT,MAAM,EAAE,MAAM,CAAA;QACd,wBAAwB,CAAC,EAAE,MAAM,CAAA;QACjC,KAAK,EAAE,KAAK,CAAC;YACX,IAAI,EAAE,MAAM,CAAA;YACZ,IAAI,EAAE,MAAM,GAAG,MAAM,CAAA;YACrB,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAA;SAC3B,CAAC,CAAA;KACH,CAAA;CACF,CAAA;AAED,eAAO,MAAM,eAAe,GACzB,eAAe,qBAAqB,MACpC,QAAQ,MAAM,KAAG,MA6DjB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,148 @@
1
+ import { createSearchEndpoints } from './endpoints/search.js';
2
+ import { initializeTypesenseCollections } from './lib/initialization.js';
3
+ import { mapPayloadDocumentToTypesense } from './lib/schema-mapper.js';
4
+ import { createTypesenseClient } from './lib/typesense-client.js';
5
+ export * from './components/index.js';
6
+ export const typesenseSearch = (pluginOptions)=>(config)=>{
7
+ if (pluginOptions.disabled) {
8
+ return config;
9
+ }
10
+ // Initialize Typesense client
11
+ const typesenseClient = createTypesenseClient(pluginOptions.typesense);
12
+ // Add search endpoints
13
+ config.endpoints = [
14
+ ...config.endpoints || [],
15
+ ...createSearchEndpoints(typesenseClient, pluginOptions, Date.now())
16
+ ];
17
+ // Apply hooks to individual collections
18
+ if (pluginOptions.settings?.autoSync !== false && pluginOptions.collections) {
19
+ config.collections = (config.collections || []).map((collection)=>{
20
+ const collectionConfig = pluginOptions.collections?.[collection.slug];
21
+ if (collectionConfig?.enabled) {
22
+ return {
23
+ ...collection,
24
+ hooks: {
25
+ ...collection.hooks,
26
+ afterChange: [
27
+ ...collection.hooks?.afterChange || [],
28
+ async ({ doc, operation, req: _req })=>{
29
+ await syncDocumentToTypesense(typesenseClient, collection.slug, doc, operation, collectionConfig);
30
+ }
31
+ ],
32
+ afterDelete: [
33
+ ...collection.hooks?.afterDelete || [],
34
+ async ({ doc, req: _req })=>{
35
+ await deleteDocumentFromTypesense(typesenseClient, collection.slug, doc.id);
36
+ }
37
+ ]
38
+ }
39
+ };
40
+ }
41
+ return collection;
42
+ });
43
+ }
44
+ // Initialize collections in Typesense
45
+ const incomingOnInit = config.onInit;
46
+ config.onInit = async (payload)=>{
47
+ if (incomingOnInit) {
48
+ await incomingOnInit(payload);
49
+ }
50
+ await initializeTypesenseCollections(payload, typesenseClient, pluginOptions);
51
+ };
52
+ return config;
53
+ };
54
+ // Helper function to create collection if it doesn't exist
55
+ const createCollectionIfNotExists = async (typesenseClient, collectionSlug, config)=>{
56
+ const searchableFields = config?.searchFields || [
57
+ 'title',
58
+ 'content',
59
+ 'description'
60
+ ];
61
+ const facetFields = config?.facetFields || [];
62
+ // Base fields that every collection should have
63
+ const baseFields = [
64
+ {
65
+ name: 'id',
66
+ type: 'string'
67
+ },
68
+ {
69
+ name: 'createdAt',
70
+ type: 'int64'
71
+ },
72
+ {
73
+ name: 'updatedAt',
74
+ type: 'int64'
75
+ }
76
+ ];
77
+ // Map searchable fields
78
+ const searchFields = searchableFields.map((field)=>({
79
+ name: field,
80
+ type: 'string',
81
+ facet: facetFields.includes(field)
82
+ }));
83
+ // Map facet-only fields (not in searchable fields)
84
+ const facetOnlyFields = facetFields.filter((field)=>!searchableFields.includes(field)).map((field)=>({
85
+ name: field,
86
+ type: 'string',
87
+ facet: true
88
+ }));
89
+ const schema = {
90
+ name: collectionSlug,
91
+ fields: [
92
+ ...baseFields,
93
+ ...searchFields,
94
+ ...facetOnlyFields
95
+ ]
96
+ };
97
+ await typesenseClient.collections().create(schema);
98
+ // Collection created successfully
99
+ };
100
+ // Sync functions for hooks
101
+ const syncDocumentToTypesense = async (typesenseClient, collectionSlug, doc, operation, config)=>{
102
+ try {
103
+ // First check if the collection exists, create it if it doesn't
104
+ try {
105
+ await typesenseClient.collections(collectionSlug).retrieve();
106
+ } catch (collectionError) {
107
+ if (collectionError.httpStatus === 404) {
108
+ // Collection not found, creating it
109
+ await createCollectionIfNotExists(typesenseClient, collectionSlug, config);
110
+ } else {
111
+ throw collectionError;
112
+ }
113
+ }
114
+ const typesenseDoc = mapPayloadDocumentToTypesense(doc, collectionSlug, config);
115
+ await typesenseClient.collections(collectionSlug).documents().upsert(typesenseDoc);
116
+ // Document synced successfully
117
+ } catch (error) {
118
+ // Handle document sync error
119
+ // Log the problematic document for debugging
120
+ if (error.message.includes('validation')) {
121
+ // Log problematic document details
122
+ }
123
+ }
124
+ };
125
+ const deleteDocumentFromTypesense = async (typesenseClient, collectionSlug, docId)=>{
126
+ try {
127
+ // First check if the collection exists
128
+ try {
129
+ await typesenseClient.collections(collectionSlug).retrieve();
130
+ } catch (collectionError) {
131
+ if (collectionError.httpStatus === 404) {
132
+ // Collection not found, skipping delete
133
+ return;
134
+ }
135
+ throw collectionError;
136
+ }
137
+ // Try to delete the document
138
+ await typesenseClient.collections(collectionSlug).documents(docId).delete();
139
+ // Document deleted successfully
140
+ } catch (error) {
141
+ // Handle specific error cases
142
+ if (error.httpStatus === 404) {
143
+ // Document not found, already deleted
144
+ } else {
145
+ // Handle document deletion error
146
+ }
147
+ }
148
+ };
@@ -0,0 +1,41 @@
1
+ import type { CacheOptions } from './types.js';
2
+ export declare class SearchCache<T = any> {
3
+ private cache;
4
+ private readonly defaultTTL;
5
+ private readonly maxSize;
6
+ constructor(options?: CacheOptions);
7
+ /**
8
+ * Generate cache key from search parameters
9
+ */
10
+ private generateKey;
11
+ /**
12
+ * Clear expired entries
13
+ */
14
+ cleanup(): void;
15
+ /**
16
+ * Clear cache entries matching pattern
17
+ */
18
+ clear(pattern?: string): void;
19
+ /**
20
+ * Get cached search result
21
+ */
22
+ get(query: string, collection?: string, params?: Record<string, any>): null | T;
23
+ /**
24
+ * Get cache statistics
25
+ */
26
+ getStats(): {
27
+ hitRate?: number;
28
+ maxSize: number;
29
+ size: number;
30
+ };
31
+ /**
32
+ * Check if cache has valid entry
33
+ */
34
+ has(query: string, collection?: string, params?: Record<string, any>): boolean;
35
+ /**
36
+ * Set cached search result
37
+ */
38
+ set(query: string, data: T, collection?: string, params?: Record<string, any>, ttl?: number): void;
39
+ }
40
+ export declare const searchCache: SearchCache<any>;
41
+ //# sourceMappingURL=cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/lib/cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAc,YAAY,EAAE,MAAM,YAAY,CAAA;AAE1D,qBAAa,WAAW,CAAC,CAAC,GAAG,GAAG;IAC9B,OAAO,CAAC,KAAK,CAAmC;IAChD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAQ;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAQ;gBAEpB,OAAO,GAAE,YAAiB;IAKtC;;OAEG;IACH,OAAO,CAAC,WAAW;IAYnB;;OAEG;IACH,OAAO,IAAI,IAAI;IASf;;OAEG;IACH,KAAK,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI;IAa7B;;OAEG;IACH,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC;IAiB/E;;OAEG;IACH,QAAQ,IAAI;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE;IAO/D;;OAEG;IACH,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,GAAG,OAAO;IAI9E;;OAEG;IACH,GAAG,CACD,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,CAAC,EACP,UAAU,CAAC,EAAE,MAAM,EACnB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EAC5B,GAAG,CAAC,EAAE,MAAM,GACX,IAAI;CAiBR;AAGD,eAAO,MAAM,WAAW,kBAGtB,CAAA"}