@rubixstudios/payload-typesense 1.2.4 → 1.3.1

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 (58) hide show
  1. package/README.md +55 -20
  2. package/dist/components/HeadlessSearchInput.d.ts.map +1 -1
  3. package/dist/components/HeadlessSearchInput.js +4 -3
  4. package/dist/components/index.d.ts +1 -0
  5. package/dist/components/index.d.ts.map +1 -1
  6. package/dist/components/index.js +1 -0
  7. package/dist/components/render/Header.d.ts +2 -1
  8. package/dist/components/render/Header.d.ts.map +1 -1
  9. package/dist/components/render/Header.js +1 -0
  10. package/dist/components/render/NoResults.d.ts +2 -1
  11. package/dist/components/render/NoResults.d.ts.map +1 -1
  12. package/dist/components/render/NoResults.js +1 -0
  13. package/dist/components/render/ResultError.d.ts +2 -1
  14. package/dist/components/render/ResultError.d.ts.map +1 -1
  15. package/dist/components/render/ResultError.js +1 -0
  16. package/dist/components/render/Results.d.ts +2 -1
  17. package/dist/components/render/Results.d.ts.map +1 -1
  18. package/dist/components/render/Results.js +3 -2
  19. package/dist/components/themes/utils.js +1 -1
  20. package/dist/endpoints/handler/createSearch.d.ts.map +1 -1
  21. package/dist/endpoints/handler/createSearch.js +32 -10
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +2 -6
  24. package/dist/lib/cache.d.ts +8 -2
  25. package/dist/lib/cache.d.ts.map +1 -1
  26. package/dist/lib/cache.js +60 -28
  27. package/dist/lib/client.d.ts.map +1 -1
  28. package/dist/lib/client.js +9 -16
  29. package/dist/lib/headlessSearch.d.ts +8 -3
  30. package/dist/lib/headlessSearch.d.ts.map +1 -1
  31. package/dist/lib/hooks.d.ts +2 -2
  32. package/dist/lib/hooks.d.ts.map +1 -1
  33. package/dist/lib/hooks.js +4 -3
  34. package/dist/lib/initialization.d.ts.map +1 -1
  35. package/dist/lib/initialization.js +9 -15
  36. package/dist/lib/schema-mapper.d.ts +3 -19
  37. package/dist/lib/schema-mapper.d.ts.map +1 -1
  38. package/dist/lib/schema-mapper.js +41 -13
  39. package/dist/types.d.ts +49 -2
  40. package/dist/types.d.ts.map +1 -1
  41. package/dist/utils/ensureCollection.d.ts +2 -1
  42. package/dist/utils/ensureCollection.d.ts.map +1 -1
  43. package/dist/utils/extractText.d.ts +1 -1
  44. package/dist/utils/extractText.d.ts.map +1 -1
  45. package/dist/utils/extractText.js +4 -6
  46. package/dist/utils/getAllCollections.d.ts +2 -0
  47. package/dist/utils/getAllCollections.d.ts.map +1 -1
  48. package/dist/utils/getAllCollections.js +33 -14
  49. package/dist/utils/keyboard.d.ts +1 -1
  50. package/dist/utils/keyboard.d.ts.map +1 -1
  51. package/dist/utils/keyboard.js +2 -6
  52. package/dist/utils/useSearch.d.ts +2 -1
  53. package/dist/utils/useSearch.d.ts.map +1 -1
  54. package/dist/utils/useSearch.js +25 -37
  55. package/dist/utils/vectorSearch.d.ts +7 -0
  56. package/dist/utils/vectorSearch.d.ts.map +1 -0
  57. package/dist/utils/vectorSearch.js +12 -0
  58. package/package.json +1 -1
package/README.md CHANGED
@@ -1,10 +1,12 @@
1
1
  # PayloadCMS + Typesense Plugin
2
2
 
3
- This plugin is a fork of FrontTribe’s Typesense Search Plugin for Payload CMS.
3
+ Forked from FrontTribe’s Typesense Search Plugin, Rubix Studios implementation has been substantially re-engineered to meet stricter production, deployment, and TypeScript standards. The codebase has been streamlined for improved maintainability, enhanced type safety, and predictable behaviour under load, while preserving full compatibility with Payload CMS and Typesense.
4
4
 
5
- It provides a production-ready integration between Payload CMS and Typesense, delivering fast, typo-tolerant search with real-time synchronization.
5
+ The fork introduces meaningful architectural improvements, including more efficient caching strategies with race-condition mitigation, improved request handling, and deployment-safe defaults. The result is a lighter, more resilient integration.
6
6
 
7
- The Rubix Studios fork introduces targeted enhancements that improve stability, TypeScript precision, and deployment reliability (including full Vercel compatibility) while reducing overall code size by 67% for a lighter, more maintainable build.
7
+ With a modular, tree-shakable structure and optional support for advanced search capabilities such as vector-based querying, the plugin is designed to scale from simple content search implementations to more complex, search-driven applications without unnecessary bloat.
8
+
9
+ This project is actively maintained by Rubix Studios and is intended for production environments where performance, stability, and code quality are critical.
8
10
 
9
11
  [![install size](https://packagephobia.com/badge?p=@rubixstudios/payload-typesense)](https://packagephobia.com/result?p=@rubixstudios/payload-typesense)
10
12
  **PayloadCMS + Typesense Plugin**
@@ -31,7 +33,13 @@ export default buildConfig({
31
33
  typesenseSearch({
32
34
  typesense: {
33
35
  apiKey: 'xyz',
34
- nodes: [{ host: 'localhost', port: 8108, protocol: 'http' }],
36
+ nodes: [
37
+ {
38
+ host: 'localhost',
39
+ port: 8108,
40
+ protocol: 'http',
41
+ },
42
+ ],
35
43
  },
36
44
  collections: {
37
45
  posts: {
@@ -40,9 +48,15 @@ export default buildConfig({
40
48
  facetFields: ['category', 'status'],
41
49
  displayName: 'Blog Posts',
42
50
  icon: '📝',
43
- syncLimit: 500, // overrides default 1000 per page (optional)
51
+ syncLimit: 500, // Overrides the default per-page sync limit of 1000
44
52
  },
45
53
  },
54
+ // This feature is experimental
55
+ vectorSearch: {
56
+ enabled: true, // Enables vector-based semantic search
57
+ embedFrom: ['title', 'content'], // Omit to fall back to collection searchFields
58
+ embeddingModel: 'ts/all-MiniLM-L12-v2',
59
+ },
46
60
  }),
47
61
  ],
48
62
  })
@@ -51,27 +65,41 @@ export default buildConfig({
51
65
  ```tsx
52
66
  import { HeadlessSearchInput } from '@rubixstudios/payload-typesense'
53
67
 
54
- function SearchPage() {
68
+ export function GlobalSearchPage() {
55
69
  return (
56
70
  <HeadlessSearchInput
57
71
  baseUrl="http://localhost:3000"
58
- theme="modern" // choose from either modern or dark
72
+ theme="modern" // Available themes: "modern" | "dark"
59
73
  placeholder="Search everything..."
60
74
  onResultClick={(result) => {
61
- console.log('Selected:', result.document)
75
+ console.log('Selected document:', result.document)
76
+ }}
77
+ />
78
+ )
79
+ }
80
+
81
+ export function CollectionSearch() {
82
+ return (
83
+ <HeadlessSearchInput
84
+ baseUrl="http://localhost:3000"
85
+ collections={['posts', 'products']}
86
+ placeholder="Search posts and products..."
87
+ onResultClick={(result) => {
88
+ console.log('Selected document:', result.document)
62
89
  }}
63
90
  />
64
91
  )
65
92
  }
66
93
 
67
- function CollectionSearch() {
94
+ export function VectorSearch() {
68
95
  return (
69
96
  <HeadlessSearchInput
70
97
  baseUrl="http://localhost:3000"
98
+ vector={true} // Vector search enabled
71
99
  collections={['posts', 'products']}
72
- placeholder="Search posts & products..."
100
+ placeholder="Search posts and products..."
73
101
  onResultClick={(result) => {
74
- console.log('Selected:', result.document)
102
+ console.log('Selected document:', result.document)
75
103
  }}
76
104
  />
77
105
  )
@@ -80,15 +108,22 @@ function CollectionSearch() {
80
108
 
81
109
  ## Features
82
110
 
83
- - **Performance**: Sub-millisecond response times for search queries
84
- - **Flexible**: Single, multiple, or universal collection search with one component
85
- - **Modern**: Responsive design implemented with Tailwind CSS
86
- - **Optimized API**: Automatically routes requests to the most efficient endpoint
87
- - **Real-Time**: Continuous data sync with Payload CMS
88
- - **Caching**: In-memory cache with configurable time-to-live settings
89
- - **Production Ready**: Robust error handling and performance optimization
90
- - **Responsive**: Mobile-first architecture ensuring compatibility across devices
91
- - **Tree Shakable**: Modular structure for lightweight builds
111
+ - **Performance**
112
+ Sub-millisecond search responses with optimized request handling.
113
+ - **Flexible Search**
114
+ Single-collection, multi-collection, or universal search using one component.
115
+ - **Vector Search**
116
+ Optional semantic search using embeddings, with graceful fallback to keyword search.
117
+ - **Modern UI**
118
+ Headless, responsive implementation compatible with Tailwind CSS.
119
+ - **Real-Time Synchronisation**
120
+ Continuous indexing and sync with Payload CMS.
121
+ - **Efficient Caching**
122
+ In-memory caching with configurable TTL and race-condition safeguards.
123
+ - **Production Ready**
124
+ Robust error handling, deployment-safe defaults, and platform compatibility.
125
+ - **Tree-Shakable Architecture**
126
+ Modular design enabling smaller bundles and selective feature usage.
92
127
 
93
128
  ## Endpoints
94
129
 
@@ -1 +1 @@
1
- {"version":3,"file":"HeadlessSearchInput.d.ts","sourceRoot":"","sources":["../../src/components/HeadlessSearchInput.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAsC,MAAM,OAAO,CAAA;AAE1D,OAAO,EAAE,KAAK,wBAAwB,EAAE,MAAM,0BAA0B,CAAA;AAaxE,wBAAgB,mBAAmB,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7D,KAAK,EAAE,wBAAwB,CAAC,CAAC,CAAC,GACjC,KAAK,CAAC,YAAY,CA8SpB;AAED,eAAe,mBAAmB,CAAA"}
1
+ {"version":3,"file":"HeadlessSearchInput.d.ts","sourceRoot":"","sources":["../../src/components/HeadlessSearchInput.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAsC,MAAM,OAAO,CAAA;AAE1D,OAAO,EAAE,KAAK,wBAAwB,EAAE,MAAM,0BAA0B,CAAA;AAaxE,wBAAgB,mBAAmB,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7D,KAAK,EAAE,wBAAwB,CAAC,CAAC,CAAC,GACjC,KAAK,CAAC,YAAY,CAgTpB;AAED,eAAe,mBAAmB,CAAA"}
@@ -6,7 +6,7 @@ import { useSearch } from '../utils/useSearch.js';
6
6
  import { RenderedHeader, RenderedNoResults, RenderedResult, RenderedResultError } from './render/index.js';
7
7
  import { useThemeConfig } from './themes/hooks.js';
8
8
  export function HeadlessSearchInput(props) {
9
- const { baseUrl, className = '', collections, debounceMs = 300, enableSuggestions: _enableSuggestions = true, errorClassName = '', inputClassName = '', inputWrapperClassName = '', minQueryLength = 2, noResultsClassName = '', onResultClick, onResults, onSearch, perPage = 10, placeholder = 'Search...', renderDate = true, renderError, renderInput, renderNoResults, renderResult, renderResultsHeader, resultItemClassName = '', resultsClassName = '', resultsContainerClassName = '', resultsHeaderClassName = '', resultsListClassName = '', showLoading = true, showResultCount = true, theme = 'modern' } = props;
9
+ const { baseUrl, className = '', collections, debounceMs = 300, enableSuggestions: _enableSuggestions = true, errorClassName = '', inputClassName = '', inputWrapperClassName = '', minQueryLength = 2, noResultsClassName = '', onResultClick, onResults, onSearch, perPage = 10, placeholder = 'Search...', renderDate = true, renderError, renderInput, renderNoResults, renderResult, renderResultsHeader, resultItemClassName = '', resultsClassName = '', resultsContainerClassName = '', resultsHeaderClassName = '', resultsListClassName = '', showLoading = true, showResultCount = true, theme = 'modern', vector = false } = props;
10
10
  const [query, setQuery] = useState('');
11
11
  const [isOpen, setIsOpen] = useState(false);
12
12
  const inputRef = useRef(null);
@@ -22,7 +22,8 @@ export function HeadlessSearchInput(props) {
22
22
  minQueryLength,
23
23
  onResults,
24
24
  onSearch,
25
- perPage
25
+ perPage,
26
+ vector
26
27
  });
27
28
  useEffect(()=>{
28
29
  if (debouncedQuery.length >= minQueryLength) {
@@ -178,7 +179,7 @@ export function HeadlessSearchInput(props) {
178
179
  errorClassName: errorClassName,
179
180
  themeConfig: themeConfig
180
181
  })), !error && results && /*#__PURE__*/ React.createElement(React.Fragment, null, showResultCount && (renderResultsHeader ? renderResultsHeader(results.found) : /*#__PURE__*/ React.createElement(RenderedHeader, {
181
- found: results.hits.length,
182
+ found: results.found,
182
183
  resultsHeaderClassName: resultsHeaderClassName,
183
184
  themeConfig: themeConfig
184
185
  })), results.hits.length > 0 ? /*#__PURE__*/ React.createElement("div", {
@@ -1,6 +1,7 @@
1
1
  export { type HeadlessSearchInputProps } from '../lib/headlessSearch.js';
2
2
  export { type BaseSearchInputProps, type HealthCheckResponse, type SearchResponse, type SearchResult, type SearchResultProps, type TypesenseSearchConfig, } from '../types.js';
3
3
  export { default as HeadlessSearchInput } from './HeadlessSearchInput.js';
4
+ export { RenderedHeader, RenderedNoResults, RenderedResult, RenderedResultError, } from './render/index.js';
4
5
  export { ThemeProvider } from './ThemeProvider.js';
5
6
  export * from './themes/index.js';
6
7
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,wBAAwB,EAAE,MAAM,0BAA0B,CAAA;AACxE,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,qBAAqB,GAC3B,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AACzE,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAElD,cAAc,mBAAmB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,wBAAwB,EAAE,MAAM,0BAA0B,CAAA;AACxE,OAAO,EACL,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACnB,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,qBAAqB,GAC3B,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,OAAO,IAAI,mBAAmB,EAAE,MAAM,0BAA0B,CAAA;AACzE,OAAO,EACL,cAAc,EACd,iBAAiB,EACjB,cAAc,EACd,mBAAmB,GACpB,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAClD,cAAc,mBAAmB,CAAA"}
@@ -1,3 +1,4 @@
1
1
  export { default as HeadlessSearchInput } from './HeadlessSearchInput.js';
2
+ export { RenderedHeader, RenderedNoResults, RenderedResult, RenderedResultError } from './render/index.js';
2
3
  export { ThemeProvider } from './ThemeProvider.js';
3
4
  export * from './themes/index.js';
@@ -1,9 +1,10 @@
1
+ import React from 'react';
1
2
  import { type ThemeContextValue } from '../themes/types.js';
2
3
  interface RenderedHeaderProps {
3
4
  found: number;
4
5
  resultsHeaderClassName?: string;
5
6
  themeConfig: ThemeContextValue;
6
7
  }
7
- export declare const RenderedHeader: ({ found, resultsHeaderClassName, themeConfig, }: RenderedHeaderProps) => import("react").JSX.Element;
8
+ export declare const RenderedHeader: ({ found, resultsHeaderClassName, themeConfig, }: RenderedHeaderProps) => React.JSX.Element;
8
9
  export {};
9
10
  //# sourceMappingURL=Header.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Header.d.ts","sourceRoot":"","sources":["../../../src/components/render/Header.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAE3D,UAAU,mBAAmB;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,sBAAsB,CAAC,EAAE,MAAM,CAAA;IAC/B,WAAW,EAAE,iBAAiB,CAAA;CAC/B;AAED,eAAO,MAAM,cAAc,GAAI,iDAI5B,mBAAmB,gCAuCrB,CAAA"}
1
+ {"version":3,"file":"Header.d.ts","sourceRoot":"","sources":["../../../src/components/render/Header.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAE3D,UAAU,mBAAmB;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,sBAAsB,CAAC,EAAE,MAAM,CAAA;IAC/B,WAAW,EAAE,iBAAiB,CAAA;CAC/B;AAED,eAAO,MAAM,cAAc,GAAI,iDAI5B,mBAAmB,sBAuCrB,CAAA"}
@@ -1,3 +1,4 @@
1
+ import React from 'react';
1
2
  export const RenderedHeader = ({ found, resultsHeaderClassName = '', themeConfig })=>/*#__PURE__*/ React.createElement("div", {
2
3
  className: resultsHeaderClassName,
3
4
  style: {
@@ -1,9 +1,10 @@
1
+ import React from 'react';
1
2
  import { type ThemeContextValue } from '../themes/types.js';
2
3
  interface RenderedNoResultsProps {
3
4
  noResultsClassName?: string;
4
5
  query: string;
5
6
  themeConfig: ThemeContextValue;
6
7
  }
7
- export declare const RenderedNoResults: ({ noResultsClassName, query: _query, themeConfig, }: RenderedNoResultsProps) => import("react").JSX.Element;
8
+ export declare const RenderedNoResults: ({ noResultsClassName, query: _query, themeConfig, }: RenderedNoResultsProps) => React.JSX.Element;
8
9
  export {};
9
10
  //# sourceMappingURL=NoResults.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"NoResults.d.ts","sourceRoot":"","sources":["../../../src/components/render/NoResults.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAE3D,UAAU,sBAAsB;IAC9B,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,iBAAiB,CAAA;CAC/B;AAED,eAAO,MAAM,iBAAiB,GAAI,qDAI/B,sBAAsB,gCAoDxB,CAAA"}
1
+ {"version":3,"file":"NoResults.d.ts","sourceRoot":"","sources":["../../../src/components/render/NoResults.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAE3D,UAAU,sBAAsB;IAC9B,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,iBAAiB,CAAA;CAC/B;AAED,eAAO,MAAM,iBAAiB,GAAI,qDAI/B,sBAAsB,sBAoDxB,CAAA"}
@@ -1,3 +1,4 @@
1
+ import React from 'react';
1
2
  export const RenderedNoResults = ({ noResultsClassName = '', query: _query, themeConfig })=>/*#__PURE__*/ React.createElement("div", {
2
3
  className: noResultsClassName,
3
4
  style: {
@@ -1,9 +1,10 @@
1
+ import React from 'react';
1
2
  import { type ThemeContextValue } from '../themes/types.js';
2
3
  interface RenderedResultErrorProps {
3
4
  error: string;
4
5
  errorClassName?: string;
5
6
  themeConfig: ThemeContextValue;
6
7
  }
7
- export declare const RenderedResultError: ({ error, errorClassName, themeConfig, }: RenderedResultErrorProps) => import("react").JSX.Element;
8
+ export declare const RenderedResultError: ({ error, errorClassName, themeConfig, }: RenderedResultErrorProps) => React.JSX.Element;
8
9
  export {};
9
10
  //# sourceMappingURL=ResultError.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"ResultError.d.ts","sourceRoot":"","sources":["../../../src/components/render/ResultError.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAE3D,UAAU,wBAAwB;IAChC,KAAK,EAAE,MAAM,CAAA;IACb,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,WAAW,EAAE,iBAAiB,CAAA;CAC/B;AAED,eAAO,MAAM,mBAAmB,GAAI,yCAIjC,wBAAwB,gCA+C1B,CAAA"}
1
+ {"version":3,"file":"ResultError.d.ts","sourceRoot":"","sources":["../../../src/components/render/ResultError.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAE3D,UAAU,wBAAwB;IAChC,KAAK,EAAE,MAAM,CAAA;IACb,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB,WAAW,EAAE,iBAAiB,CAAA;CAC/B;AAED,eAAO,MAAM,mBAAmB,GAAI,yCAIjC,wBAAwB,sBA+C1B,CAAA"}
@@ -1,3 +1,4 @@
1
+ import React from 'react';
1
2
  export const RenderedResultError = ({ error, errorClassName = '', themeConfig })=>/*#__PURE__*/ React.createElement("div", {
2
3
  className: errorClassName,
3
4
  style: {
@@ -1,3 +1,4 @@
1
+ import React from 'react';
1
2
  import { type SearchResult } from '../../types.js';
2
3
  import { type ThemeContextValue } from '../themes/types.js';
3
4
  interface RenderedResultProps {
@@ -9,6 +10,6 @@ interface RenderedResultProps {
9
10
  resultsContainerClassName?: string;
10
11
  themeConfig: ThemeContextValue;
11
12
  }
12
- export declare function RenderedResult({ index, onResultClick, renderDate, result, resultItemClassName, resultsContainerClassName, themeConfig, }: RenderedResultProps): import("react").JSX.Element;
13
+ export declare function RenderedResult({ index, onResultClick, renderDate, result, resultItemClassName, resultsContainerClassName, themeConfig, }: RenderedResultProps): React.JSX.Element;
13
14
  export {};
14
15
  //# sourceMappingURL=Results.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Results.d.ts","sourceRoot":"","sources":["../../../src/components/render/Results.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAClD,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAE3D,UAAU,mBAAmB;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,aAAa,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,IAAI,CAAA;IAC7C,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,MAAM,EAAE,YAAY,CAAA;IACpB,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,yBAAyB,CAAC,EAAE,MAAM,CAAA;IAClC,WAAW,EAAE,iBAAiB,CAAA;CAC/B;AAED,wBAAgB,cAAc,CAAC,EAC7B,KAAK,EACL,aAAa,EACb,UAAiB,EACjB,MAAM,EACN,mBAAwB,EACxB,yBAA8B,EAC9B,WAAW,GACZ,EAAE,mBAAmB,+BA4JrB"}
1
+ {"version":3,"file":"Results.d.ts","sourceRoot":"","sources":["../../../src/components/render/Results.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,MAAM,OAAO,CAAA;AAEzB,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,gBAAgB,CAAA;AAClD,OAAO,EAAE,KAAK,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAE3D,UAAU,mBAAmB;IAC3B,KAAK,EAAE,MAAM,CAAA;IACb,aAAa,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,IAAI,CAAA;IAC7C,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,MAAM,EAAE,YAAY,CAAA;IACpB,mBAAmB,CAAC,EAAE,MAAM,CAAA;IAC5B,yBAAyB,CAAC,EAAE,MAAM,CAAA;IAClC,WAAW,EAAE,iBAAiB,CAAA;CAC/B;AAED,wBAAgB,cAAc,CAAC,EAC7B,KAAK,EACL,aAAa,EACb,UAAiB,EACjB,MAAM,EACN,mBAAwB,EACxB,yBAA8B,EAC9B,WAAW,GACZ,EAAE,mBAAmB,qBAmKrB"}
@@ -1,3 +1,4 @@
1
+ import React from 'react';
1
2
  export function RenderedResult({ index, onResultClick, renderDate = true, result, resultItemClassName = '', resultsContainerClassName = '', themeConfig }) {
2
3
  return /*#__PURE__*/ React.createElement("div", {
3
4
  className: resultsContainerClassName,
@@ -5,7 +6,6 @@ export function RenderedResult({ index, onResultClick, renderDate = true, result
5
6
  backgroundColor: themeConfig.theme.colors.resultBackground,
6
7
  borderBottom: `1px solid ${themeConfig.theme.colors.resultBorder}`,
7
8
  cursor: 'pointer',
8
- padding: themeConfig.theme.spacing.itemPadding,
9
9
  transition: themeConfig.config.enableAnimations !== false ? `all ${themeConfig.theme.animations.transitionFast} ${themeConfig.theme.animations.easeInOut}` : 'none'
10
10
  }
11
11
  }, /*#__PURE__*/ React.createElement("div", {
@@ -38,6 +38,7 @@ export function RenderedResult({ index, onResultClick, renderDate = true, result
38
38
  alignItems: 'flex-start',
39
39
  display: 'flex',
40
40
  gap: '12px',
41
+ overflow: 'hidden',
41
42
  padding: '6px'
42
43
  }
43
44
  }, /*#__PURE__*/ React.createElement("div", {
@@ -51,7 +52,7 @@ export function RenderedResult({ index, onResultClick, renderDate = true, result
51
52
  borderRadius: themeConfig.theme.spacing.inputBorderRadius,
52
53
  color: themeConfig.theme.colors.collectionBadgeText,
53
54
  display: 'flex',
54
- fontSize: '14px',
55
+ fontSize: themeConfig.theme.typography.fontSizeBase,
55
56
  fontWeight: themeConfig.theme.typography.fontWeightMedium,
56
57
  height: '32px',
57
58
  justifyContent: 'center',
@@ -123,7 +123,7 @@ export function generateThemeClasses(theme, config = {}) {
123
123
  padding: theme.spacing.headerPadding
124
124
  });
125
125
  const resultsListStyles = css({
126
- padding: '4px'
126
+ padding: '2px'
127
127
  });
128
128
  const resultItemStyles = css({
129
129
  ':focus': {
@@ -1 +1 @@
1
- {"version":3,"file":"createSearch.d.ts","sourceRoot":"","sources":["../../../src/endpoints/handler/createSearch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,SAAS,MAAM,WAAW,CAAA;AAEtC,OAAO,EAAE,KAAK,cAAc,EAAuB,MAAM,SAAS,CAAA;AAIlE,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAGrD,eAAO,MAAM,YAAY,GACvB,iBAAiB,SAAS,CAAC,MAAM,EACjC,eAAe,eAAe,KAC7B,cAoGF,CAAA"}
1
+ {"version":3,"file":"createSearch.d.ts","sourceRoot":"","sources":["../../../src/endpoints/handler/createSearch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,SAAS,MAAM,WAAW,CAAA;AAEtC,OAAO,EAAE,KAAK,cAAc,EAAuB,MAAM,SAAS,CAAA;AAIlE,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,gBAAgB,CAAA;AAIrD,eAAO,MAAM,YAAY,GACvB,iBAAiB,SAAS,CAAC,MAAM,EACjC,eAAe,eAAe,KAC7B,cAmIF,CAAA"}
@@ -1,6 +1,7 @@
1
1
  import { searchCache } from '../../lib/cache.js';
2
2
  import { getValidationErrors, validateSearchParams } from '../../lib/validation.js';
3
3
  import { getAllCollections } from '../../utils/getAllCollections.js';
4
+ import { performVectorSearch } from '../../utils/vectorSearch.js';
4
5
  export const createSearch = (typesenseClient, pluginOptions)=>{
5
6
  return async (request)=>{
6
7
  try {
@@ -19,7 +20,10 @@ export const createSearch = (typesenseClient, pluginOptions)=>{
19
20
  const q = url.searchParams.get('q') || '';
20
21
  const page = Number(url.searchParams.get('page') || 1);
21
22
  const per_page = Number(url.searchParams.get('per_page') || 10);
22
- const sort_by = url.searchParams.get('sort_by') || undefined;
23
+ const collectionsParam = url.searchParams.get('collections');
24
+ const collections = collectionsParam ? collectionsParam.split(',').filter(Boolean) : undefined;
25
+ const vector = url.searchParams.get('vector') === 'true';
26
+ const sort_by = url.searchParams.has('sort_by') ? url.searchParams.get('sort_by') || '' : undefined;
23
27
  if (isNaN(page) || page < 1) {
24
28
  return Response.json({
25
29
  error: 'Invalid page parameter'
@@ -58,10 +62,12 @@ export const createSearch = (typesenseClient, pluginOptions)=>{
58
62
  });
59
63
  }
60
64
  return getAllCollections(typesenseClient, pluginOptions, q, {
65
+ collections,
61
66
  filters: {},
62
67
  page,
63
68
  per_page,
64
- sort_by
69
+ sort_by,
70
+ vector
65
71
  });
66
72
  }
67
73
  if (!pluginOptions.collections?.[collection]?.enabled) {
@@ -78,6 +84,23 @@ export const createSearch = (typesenseClient, pluginOptions)=>{
78
84
  status: 400
79
85
  });
80
86
  }
87
+ if (vector) {
88
+ try {
89
+ const results = await performVectorSearch(typesenseClient, q, {
90
+ collection,
91
+ page,
92
+ per_page
93
+ });
94
+ return Response.json(results);
95
+ } catch (vectorError) {
96
+ return Response.json({
97
+ details: vectorError instanceof Error ? vectorError.message : 'Unknown vector search error',
98
+ error: 'Vector search failed'
99
+ }, {
100
+ status: 500
101
+ });
102
+ }
103
+ }
81
104
  const fields = pluginOptions.collections?.[collection]?.searchFields?.join(',') || 'title,content';
82
105
  const searchParams = {
83
106
  highlight_full_fields: fields,
@@ -92,20 +115,19 @@ export const createSearch = (typesenseClient, pluginOptions)=>{
92
115
  sort_by
93
116
  } : {}
94
117
  };
95
- const cached = searchCache.get(q, collection, {
118
+ const cacheKey = {
119
+ fields,
96
120
  page,
97
121
  per_page,
98
- sort_by
99
- });
122
+ sort_by,
123
+ vector
124
+ };
125
+ const cached = searchCache.get(q, collection, cacheKey);
100
126
  if (cached) {
101
127
  return Response.json(cached);
102
128
  }
103
129
  const results = await typesenseClient.collections(collection).documents().search(searchParams);
104
- searchCache.set(q, results, collection, {
105
- page,
106
- per_page,
107
- sort_by
108
- });
130
+ searchCache.set(q, results, collection, cacheKey);
109
131
  return Response.json(results);
110
132
  } catch (error) {
111
133
  return Response.json({
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,SAAS,CAAA;AAMrC,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAA;AAEjD,cAAc,uBAAuB,CAAA;AACrC,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAEjD,eAAO,MAAM,eAAe,GACzB,eAAe,eAAe,MAC9B,QAAQ,MAAM,KAAG,MAiDjB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,MAAM,EAAE,MAAM,SAAS,CAAA;AAMrC,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,YAAY,CAAA;AAEjD,cAAc,uBAAuB,CAAA;AACrC,YAAY,EAAE,eAAe,EAAE,MAAM,YAAY,CAAA;AAEjD,eAAO,MAAM,eAAe,GACzB,eAAe,eAAe,MAC9B,QAAQ,MAAM,KAAG,MA6CjB,CAAA"}
package/dist/index.js CHANGED
@@ -4,9 +4,7 @@ import { deleteDocumentFromTypesense, syncDocumentToTypesense } from './lib/hook
4
4
  import { initializeTypesense } from './lib/initialization.js';
5
5
  export * from './components/index.js';
6
6
  export const typesenseSearch = (pluginOptions)=>(config)=>{
7
- if (pluginOptions.disabled) {
8
- return config;
9
- }
7
+ if (pluginOptions.disabled) return config;
10
8
  const client = createClient(pluginOptions.typesense);
11
9
  config.endpoints = [
12
10
  ...config.endpoints || [],
@@ -17,9 +15,7 @@ export const typesenseSearch = (pluginOptions)=>(config)=>{
17
15
  if (shouldAutoSync && hasCollections) {
18
16
  config.collections = (config.collections || []).map((collection)=>{
19
17
  const colConfig = pluginOptions.collections?.[collection.slug];
20
- if (!colConfig?.enabled) {
21
- return collection;
22
- }
18
+ if (!colConfig?.enabled) return collection;
23
19
  return {
24
20
  ...collection,
25
21
  hooks: {
@@ -3,13 +3,19 @@ export declare class SearchCache<T = unknown> {
3
3
  private cache;
4
4
  private readonly defaultTTL;
5
5
  private readonly maxSize;
6
+ private stats;
6
7
  constructor(options?: CacheOptions);
8
+ private evict;
7
9
  private generateKey;
10
+ private isExpired;
11
+ private lookup;
12
+ private normalize;
13
+ private serialize;
8
14
  cleanup(): void;
9
- clear(pattern?: string): void;
15
+ clear(pattern?: RegExp | string): void;
10
16
  get(query: string, collection?: string, params?: Record<string, unknown>): null | T;
11
17
  getStats(): {
12
- hitRate?: number;
18
+ hitRate: number;
13
19
  maxSize: number;
14
20
  size: number;
15
21
  };
@@ -1 +1 @@
1
- {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/lib/cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,YAAY,EAAE,MAAM,aAAa,CAAA;AAEhE,qBAAa,WAAW,CAAC,CAAC,GAAG,OAAO;IAClC,OAAO,CAAC,KAAK,CAAmC;IAChD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAQ;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAQ;gBAEpB,OAAO,GAAE,YAAiB;IAKtC,OAAO,CAAC,WAAW;IAmBnB,OAAO,IAAI,IAAI;IASf,KAAK,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI;IAY7B,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC;IAgBnF,QAAQ,IAAI;QAAE,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE;IAO/D,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO;IAIlF,GAAG,CACD,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,CAAC,EACP,UAAU,CAAC,EAAE,MAAM,EACnB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,GAAG,CAAC,EAAE,MAAM,GACX,IAAI;CAgBR;AAED,eAAO,MAAM,WAAW,sBAGtB,CAAA"}
1
+ {"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/lib/cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,YAAY,EAAmB,MAAM,aAAa,CAAA;AAEjF,qBAAa,WAAW,CAAC,CAAC,GAAG,OAAO;IAClC,OAAO,CAAC,KAAK,CAAmC;IAChD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAQ;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAQ;IAChC,OAAO,CAAC,KAAK,CAAqC;gBAEtC,OAAO,GAAE,YAAiB;IAKtC,OAAO,CAAC,KAAK;IAWb,OAAO,CAAC,WAAW;IAUnB,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,MAAM;IAsBd,OAAO,CAAC,SAAS;IAIjB,OAAO,CAAC,SAAS;IAWjB,OAAO,IAAI,IAAI;IAQf,KAAK,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAkBtC,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC;IAYnF,QAAQ,IAAI;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE;IAS9D,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO;IAelF,GAAG,CACD,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,CAAC,EACP,UAAU,CAAC,EAAE,MAAM,EACnB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAChC,GAAG,CAAC,EAAE,MAAM,GACX,IAAI;CAWR;AAED,eAAO,MAAM,WAAW,sBAGtB,CAAA"}
package/dist/lib/cache.js CHANGED
@@ -2,22 +2,54 @@ export class SearchCache {
2
2
  cache = new Map();
3
3
  defaultTTL;
4
4
  maxSize;
5
+ stats = {
6
+ hits: 0,
7
+ misses: 0
8
+ };
5
9
  constructor(options = {}){
6
- this.defaultTTL = options.ttl || 5 * 60 * 1000;
7
- this.maxSize = options.maxSize || 1000;
10
+ this.defaultTTL = options.ttl ?? 5 * 60 * 1000;
11
+ this.maxSize = options.maxSize ?? 1000;
12
+ }
13
+ evict() {
14
+ this.cleanup();
15
+ if (this.cache.size < this.maxSize) return;
16
+ const oldestKey = this.cache.keys().next().value;
17
+ if (oldestKey) {
18
+ this.cache.delete(oldestKey);
19
+ }
8
20
  }
9
21
  generateKey(query, collection, params) {
10
- const baseKey = `${collection || 'universal'}:${query}`;
11
- if (params) {
12
- const sortedParams = Object.keys(params).sort().map((key)=>`${key}=${String(params[key])}`).join('&');
13
- return `${baseKey}:${sortedParams}`;
22
+ const base = `${this.normalize(collection)}:${query}`;
23
+ const serializedParams = this.serialize(params);
24
+ return serializedParams ? `${base}:${serializedParams}` : base;
25
+ }
26
+ isExpired(entry) {
27
+ return Date.now() - entry.timestamp > entry.ttl;
28
+ }
29
+ lookup(query, collection, params) {
30
+ const key = this.generateKey(query, collection, params);
31
+ const entry = this.cache.get(key);
32
+ if (!entry || this.isExpired(entry)) {
33
+ if (entry) {
34
+ this.cache.delete(key);
35
+ }
36
+ return null;
14
37
  }
15
- return baseKey;
38
+ this.cache.delete(key);
39
+ this.cache.set(key, entry);
40
+ return entry;
41
+ }
42
+ normalize(collection) {
43
+ return collection ?? 'universal';
44
+ }
45
+ serialize(params) {
46
+ if (!params || Object.keys(params).length === 0) return '';
47
+ const sorted = Object.keys(params).sort().map((key)=>`${key}=${JSON.stringify(params[key])}`).join('&');
48
+ return sorted;
16
49
  }
17
50
  cleanup() {
18
- const now = Date.now();
19
51
  for (const [key, entry] of this.cache.entries()){
20
- if (now - entry.timestamp > entry.ttl) {
52
+ if (this.isExpired(entry)) {
21
53
  this.cache.delete(key);
22
54
  }
23
55
  }
@@ -27,45 +59,48 @@ export class SearchCache {
27
59
  this.cache.clear();
28
60
  return;
29
61
  }
62
+ const matcher = typeof pattern === 'string' ? (key)=>key.includes(pattern) : (key)=>pattern.test(key);
30
63
  for (const key of this.cache.keys()){
31
- if (key.includes(pattern)) {
64
+ if (matcher(key)) {
32
65
  this.cache.delete(key);
33
66
  }
34
67
  }
35
68
  }
36
69
  get(query, collection, params) {
37
- const key = this.generateKey(query, collection, params);
38
- const entry = this.cache.get(key);
70
+ const entry = this.lookup(query, collection, params);
39
71
  if (!entry) {
72
+ this.stats.misses++;
40
73
  return null;
41
74
  }
42
- if (Date.now() - entry.timestamp > entry.ttl) {
43
- this.cache.delete(key);
44
- return null;
45
- }
75
+ this.stats.hits++;
46
76
  return entry.data;
47
77
  }
48
78
  getStats() {
79
+ const total = this.stats.hits + this.stats.misses;
49
80
  return {
81
+ hitRate: total === 0 ? 0 : this.stats.hits / total,
50
82
  maxSize: this.maxSize,
51
83
  size: this.cache.size
52
84
  };
53
85
  }
54
86
  has(query, collection, params) {
55
- return this.get(query, collection, params) !== null;
56
- }
57
- set(query, data, collection, params, ttl) {
58
- const key = this.generateKey(query, collection || '', params);
59
- if (this.cache.size >= this.maxSize) {
60
- const oldestKey = this.cache.keys().next().value;
61
- if (oldestKey) {
62
- this.cache.delete(oldestKey);
87
+ const key = this.generateKey(query, collection, params);
88
+ const entry = this.cache.get(key);
89
+ if (!entry || this.isExpired(entry)) {
90
+ if (entry) {
91
+ this.cache.delete(key);
63
92
  }
93
+ return false;
64
94
  }
95
+ return true;
96
+ }
97
+ set(query, data, collection, params, ttl) {
98
+ const key = this.generateKey(query, collection, params);
99
+ this.evict();
65
100
  this.cache.set(key, {
66
101
  data,
67
102
  timestamp: Date.now(),
68
- ttl: ttl || this.defaultTTL
103
+ ttl: ttl ?? this.defaultTTL
69
104
  });
70
105
  }
71
106
  }
@@ -73,6 +108,3 @@ export const searchCache = new SearchCache({
73
108
  maxSize: 1000,
74
109
  ttl: 5 * 60 * 1000
75
110
  });
76
- setInterval(()=>{
77
- searchCache.cleanup();
78
- }, 10 * 60 * 1000);
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/lib/client.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,WAAW,CAAA;AAEjC,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,aAAa,CAAA;AAiDlD,eAAO,MAAM,YAAY,GAAI,iBAAiB,eAAe,CAAC,WAAW,CAAC,KAAG,SAAS,CAAC,MAoBtF,CAAA"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/lib/client.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,WAAW,CAAA;AAEjC,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,aAAa,CAAA;AA4ClD,eAAO,MAAM,YAAY,GAAI,iBAAiB,eAAe,CAAC,WAAW,CAAC,KAAG,SAAS,CAAC,MAkBtF,CAAA"}