@rubixstudios/payload-typesense 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -22
- package/README.md +177 -182
- package/dist/components/themes/hooks.js +24 -24
- package/dist/components/themes/utils.js +14 -14
- package/dist/endpoints/health.js +10 -10
- package/dist/lib/cache.js +14 -14
- package/dist/lib/config-validation.js +12 -12
- package/package.json +15 -22
package/LICENSE
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2024 Front Tribe (https://fronttribe.com)
|
|
4
|
-
Copyright (c) 2025 Rubix Studios Pty. Ltd. <hello@rubixstudios.com.au>
|
|
5
|
-
|
|
6
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
-
in the Software without restriction, including without limitation the rights
|
|
9
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
-
furnished to do so, subject to the following conditions:
|
|
12
|
-
|
|
13
|
-
The above copyright notice and this permission notice shall be included in all
|
|
14
|
-
copies or substantial portions of the Software.
|
|
15
|
-
|
|
16
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Front Tribe (https://fronttribe.com)
|
|
4
|
+
Copyright (c) 2025 Rubix Studios Pty. Ltd. <hello@rubixstudios.com.au>
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,182 +1,177 @@
|
|
|
1
|
-
# Typesense Plugin for Payload CMS
|
|
2
|
-
|
|
3
|
-
This plugin is a fork of FrontTribe's Typesense Search Plugin for Payload CMS…
|
|
4
|
-
|
|
5
|
-
A production-ready search plugin that integrates Typesense with Payload CMS, offering fast, typo-tolerant search with real-time synchronization. This fork by Rubix Studios reduces bloat and introduces targeted changes for improved performance, maintainability, and flexibility.
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
- **
|
|
90
|
-
- **
|
|
91
|
-
- **
|
|
92
|
-
- **
|
|
93
|
-
- **
|
|
94
|
-
- **
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
- `
|
|
101
|
-
- `GET /api/search/{collection}
|
|
102
|
-
- `
|
|
103
|
-
- `GET /api/search/
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
- **
|
|
110
|
-
- **
|
|
111
|
-
- **
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
## Acknowledgments
|
|
179
|
-
|
|
180
|
-
- [FrontTribe](https://github.com/FrontTribe/typesense-search)
|
|
181
|
-
- [Typesense](https://typesense.org/)
|
|
182
|
-
- [Payload CMS](https://payloadcms.com/)
|
|
1
|
+
# Typesense Plugin for Payload CMS
|
|
2
|
+
|
|
3
|
+
This plugin is a fork of FrontTribe's Typesense Search Plugin for Payload CMS…
|
|
4
|
+
|
|
5
|
+
A production-ready search plugin that integrates Typesense with Payload CMS, offering fast, typo-tolerant search with real-time synchronization. This fork by Rubix Studios reduces bloat and introduces targeted changes for improved performance, maintainability, and flexibility.
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@rubixstudios/payload-typesense)
|
|
8
|
+

|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
pnpm add @rubixstudios/payload-typesense
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
```typescript
|
|
17
|
+
// payload.config.ts
|
|
18
|
+
import { buildConfig } from 'payload/config'
|
|
19
|
+
import { typesenseSearch } from '@rubixstudios/payload-typesense'
|
|
20
|
+
|
|
21
|
+
export default buildConfig({
|
|
22
|
+
plugins: [
|
|
23
|
+
typesenseSearch({
|
|
24
|
+
typesense: {
|
|
25
|
+
apiKey: 'xyz',
|
|
26
|
+
nodes: [{ host: 'localhost', port: 8108, protocol: 'http' }],
|
|
27
|
+
},
|
|
28
|
+
collections: {
|
|
29
|
+
posts: {
|
|
30
|
+
enabled: true,
|
|
31
|
+
searchFields: ['title', 'content'],
|
|
32
|
+
facetFields: ['category', 'status'],
|
|
33
|
+
displayName: 'Blog Posts',
|
|
34
|
+
icon: '📝',
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
}),
|
|
38
|
+
],
|
|
39
|
+
})
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
```tsx
|
|
43
|
+
import { HeadlessSearchInput } from '@rubixstudios/payload-typesense'
|
|
44
|
+
|
|
45
|
+
function SearchPage() {
|
|
46
|
+
return (
|
|
47
|
+
<HeadlessSearchInput
|
|
48
|
+
baseUrl="http://localhost:3000"
|
|
49
|
+
theme="modern" // Choose from: modern, dark
|
|
50
|
+
placeholder="Search everything..."
|
|
51
|
+
onResultClick={(result) => {
|
|
52
|
+
console.log('Selected:', result.document)
|
|
53
|
+
}}
|
|
54
|
+
/>
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Multi-collection search
|
|
59
|
+
function MultiCollectionSearch() {
|
|
60
|
+
return (
|
|
61
|
+
<HeadlessSearchInput
|
|
62
|
+
baseUrl="http://localhost:3000"
|
|
63
|
+
collections={['posts', 'products']}
|
|
64
|
+
placeholder="Search posts & products..."
|
|
65
|
+
onResultClick={(result) => {
|
|
66
|
+
console.log('Selected:', result.document)
|
|
67
|
+
}}
|
|
68
|
+
/>
|
|
69
|
+
)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Single collection search
|
|
73
|
+
function PostSearch() {
|
|
74
|
+
return (
|
|
75
|
+
<HeadlessSearchInput
|
|
76
|
+
baseUrl="http://localhost:3000"
|
|
77
|
+
collection="posts"
|
|
78
|
+
placeholder="Search posts..."
|
|
79
|
+
onResultClick={(result) => {
|
|
80
|
+
console.log('Selected:', result.document)
|
|
81
|
+
}}
|
|
82
|
+
/>
|
|
83
|
+
)
|
|
84
|
+
}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Features
|
|
88
|
+
|
|
89
|
+
- **Performance**: Sub-millisecond response times for search queries
|
|
90
|
+
- **Flexible**: Single, multiple, or universal collection search with one component
|
|
91
|
+
- **Modern**: Responsive design implemented with Tailwind CSS
|
|
92
|
+
- **Optimized API**: Automatically routes requests to the most efficient endpoint
|
|
93
|
+
- **Real-Time**: Continuous data sync with Payload CMS
|
|
94
|
+
- **Caching**: In-memory cache with configurable time-to-live settings
|
|
95
|
+
- **Production Ready**: Robust error handling and performance optimization
|
|
96
|
+
- **Responsive**: Mobile-first architecture ensuring compatibility across devices
|
|
97
|
+
|
|
98
|
+
## API Endpoints
|
|
99
|
+
|
|
100
|
+
- `GET /api/search` - Universal search across all collections
|
|
101
|
+
- `GET /api/search/{collection}` - Search specific collection
|
|
102
|
+
- `POST /api/search/{collection}` - Advanced search with filters
|
|
103
|
+
- `GET /api/search/{collection}/suggest` - Search suggestions
|
|
104
|
+
- `GET /api/search/collections` - Collection metadata
|
|
105
|
+
- `GET /api/search/health` - Health check
|
|
106
|
+
|
|
107
|
+
## Components
|
|
108
|
+
|
|
109
|
+
- **HeadlessSearchInput**: Single component supporting all search patterns:
|
|
110
|
+
- **Single Collection**: `collection="posts"` - Direct API calls for optimal performance
|
|
111
|
+
- **Multiple Collections**: `collections={['posts', 'products']}` - Smart filtering with universal search
|
|
112
|
+
- **Universal Search**: No collection props - Search across all collections
|
|
113
|
+
- **Complete UI Control**: Customizable rendering with comprehensive theme system
|
|
114
|
+
|
|
115
|
+
## Theme System
|
|
116
|
+
|
|
117
|
+
The plugin includes a powerful theme system with 2 pre-built themes and unlimited customization:
|
|
118
|
+
|
|
119
|
+
### Pre-built Themes
|
|
120
|
+
|
|
121
|
+
```tsx
|
|
122
|
+
// Modern theme (default)
|
|
123
|
+
<HeadlessSearchInput theme="modern" />
|
|
124
|
+
|
|
125
|
+
// Dark theme
|
|
126
|
+
<HeadlessSearchInput theme="dark" />
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Custom Themes
|
|
130
|
+
|
|
131
|
+
```tsx
|
|
132
|
+
const customTheme = {
|
|
133
|
+
theme: 'modern',
|
|
134
|
+
colors: {
|
|
135
|
+
inputBorderFocus: '#10b981',
|
|
136
|
+
inputBackground: '#f0fdf4',
|
|
137
|
+
resultsBackground: '#f0fdf4',
|
|
138
|
+
},
|
|
139
|
+
spacing: {
|
|
140
|
+
inputPadding: '1rem 1.25rem',
|
|
141
|
+
inputBorderRadius: '1.5rem',
|
|
142
|
+
},
|
|
143
|
+
enableAnimations: true,
|
|
144
|
+
enableShadows: true,
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
<HeadlessSearchInput theme={customTheme} />
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Theme Features
|
|
151
|
+
|
|
152
|
+
- **2 Pre-built Themes**: Modern, Dark
|
|
153
|
+
- **Unlimited Customization**: Override any color, spacing, typography, or animation
|
|
154
|
+
- **Performance Options**: Disable animations/shadows for better performance
|
|
155
|
+
- **Responsive Design**: Automatic mobile optimization
|
|
156
|
+
- **CSS Variables**: Advanced styling with CSS custom properties
|
|
157
|
+
- **TypeScript Support**: Full type safety for all theme configurations
|
|
158
|
+
|
|
159
|
+
## License
|
|
160
|
+
|
|
161
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
162
|
+
|
|
163
|
+
## Support
|
|
164
|
+
|
|
165
|
+
For support or inquiries:
|
|
166
|
+
|
|
167
|
+
- LinkedIn: [rubixvi](https://www.linkedin.com/in/rubixvi/)
|
|
168
|
+
- Website: [Rubix Studios](https://rubixstudios.com.au)
|
|
169
|
+
|
|
170
|
+
## Author
|
|
171
|
+
|
|
172
|
+
Rubix Studios Pty. Ltd.
|
|
173
|
+
[https://rubixstudios.com.au](https://rubixstudios.com.au)
|
|
174
|
+
|
|
175
|
+
## Acknowledgments
|
|
176
|
+
|
|
177
|
+
- [FrontTribe](https://github.com/FrontTribe/typesense-search)
|
|
@@ -3,8 +3,8 @@ import { createContext, useCallback, useContext, useMemo } from 'react';
|
|
|
3
3
|
import { applyTheme, generateThemeClasses, getThemeVariables, isDarkTheme, isLightTheme, mergeThemeConfig } from './utils.js';
|
|
4
4
|
// Theme context
|
|
5
5
|
const ThemeContext = createContext(null);
|
|
6
|
-
/**
|
|
7
|
-
* Hook to access theme context
|
|
6
|
+
/**
|
|
7
|
+
* Hook to access theme context
|
|
8
8
|
*/ export function useTheme() {
|
|
9
9
|
const context = useContext(ThemeContext);
|
|
10
10
|
if (!context) {
|
|
@@ -12,8 +12,8 @@ const ThemeContext = createContext(null);
|
|
|
12
12
|
}
|
|
13
13
|
return context;
|
|
14
14
|
}
|
|
15
|
-
/**
|
|
16
|
-
* Hook to create theme configuration
|
|
15
|
+
/**
|
|
16
|
+
* Hook to create theme configuration
|
|
17
17
|
*/ export function useThemeConfig(config) {
|
|
18
18
|
const theme = useMemo(()=>mergeThemeConfig(config), [
|
|
19
19
|
config
|
|
@@ -39,8 +39,8 @@ const ThemeContext = createContext(null);
|
|
|
39
39
|
applyThemeToElement
|
|
40
40
|
]);
|
|
41
41
|
}
|
|
42
|
-
/**
|
|
43
|
-
* Hook to apply theme styles to CSS variables
|
|
42
|
+
/**
|
|
43
|
+
* Hook to apply theme styles to CSS variables
|
|
44
44
|
*/ export function useThemeStyles(config) {
|
|
45
45
|
return useMemo(()=>{
|
|
46
46
|
const theme = mergeThemeConfig(config);
|
|
@@ -49,8 +49,8 @@ const ThemeContext = createContext(null);
|
|
|
49
49
|
config
|
|
50
50
|
]);
|
|
51
51
|
}
|
|
52
|
-
/**
|
|
53
|
-
* Hook to get responsive theme configuration
|
|
52
|
+
/**
|
|
53
|
+
* Hook to get responsive theme configuration
|
|
54
54
|
*/ export function useResponsiveTheme(baseConfig, isMobile) {
|
|
55
55
|
return useMemo(()=>{
|
|
56
56
|
if (!baseConfig.responsive) {
|
|
@@ -81,8 +81,8 @@ const ThemeContext = createContext(null);
|
|
|
81
81
|
isMobile
|
|
82
82
|
]);
|
|
83
83
|
}
|
|
84
|
-
/**
|
|
85
|
-
* Hook to toggle between light and dark themes
|
|
84
|
+
/**
|
|
85
|
+
* Hook to toggle between light and dark themes
|
|
86
86
|
*/ export function useThemeToggle(lightConfig, darkConfig, isDark) {
|
|
87
87
|
return useMemo(()=>{
|
|
88
88
|
return isDark ? darkConfig : lightConfig;
|
|
@@ -92,8 +92,8 @@ const ThemeContext = createContext(null);
|
|
|
92
92
|
isDark
|
|
93
93
|
]);
|
|
94
94
|
}
|
|
95
|
-
/**
|
|
96
|
-
* Hook to create theme-aware CSS classes
|
|
95
|
+
/**
|
|
96
|
+
* Hook to create theme-aware CSS classes
|
|
97
97
|
*/ export function useThemeClasses(config) {
|
|
98
98
|
return useMemo(()=>{
|
|
99
99
|
const theme = mergeThemeConfig(config);
|
|
@@ -108,8 +108,8 @@ const ThemeContext = createContext(null);
|
|
|
108
108
|
config
|
|
109
109
|
]);
|
|
110
110
|
}
|
|
111
|
-
/**
|
|
112
|
-
* Hook to get theme-aware color values
|
|
111
|
+
/**
|
|
112
|
+
* Hook to get theme-aware color values
|
|
113
113
|
*/ export function useThemeColors(config) {
|
|
114
114
|
return useMemo(()=>{
|
|
115
115
|
const theme = mergeThemeConfig(config);
|
|
@@ -118,8 +118,8 @@ const ThemeContext = createContext(null);
|
|
|
118
118
|
config
|
|
119
119
|
]);
|
|
120
120
|
}
|
|
121
|
-
/**
|
|
122
|
-
* Hook to get theme-aware spacing values
|
|
121
|
+
/**
|
|
122
|
+
* Hook to get theme-aware spacing values
|
|
123
123
|
*/ export function useThemeSpacing(config) {
|
|
124
124
|
return useMemo(()=>{
|
|
125
125
|
const theme = mergeThemeConfig(config);
|
|
@@ -128,8 +128,8 @@ const ThemeContext = createContext(null);
|
|
|
128
128
|
config
|
|
129
129
|
]);
|
|
130
130
|
}
|
|
131
|
-
/**
|
|
132
|
-
* Hook to get theme-aware typography values
|
|
131
|
+
/**
|
|
132
|
+
* Hook to get theme-aware typography values
|
|
133
133
|
*/ export function useThemeTypography(config) {
|
|
134
134
|
return useMemo(()=>{
|
|
135
135
|
const theme = mergeThemeConfig(config);
|
|
@@ -138,8 +138,8 @@ const ThemeContext = createContext(null);
|
|
|
138
138
|
config
|
|
139
139
|
]);
|
|
140
140
|
}
|
|
141
|
-
/**
|
|
142
|
-
* Hook to detect system theme preference
|
|
141
|
+
/**
|
|
142
|
+
* Hook to detect system theme preference
|
|
143
143
|
*/ export function useSystemTheme() {
|
|
144
144
|
return useMemo(()=>{
|
|
145
145
|
if (typeof window === 'undefined') {
|
|
@@ -148,8 +148,8 @@ const ThemeContext = createContext(null);
|
|
|
148
148
|
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
|
|
149
149
|
}, []);
|
|
150
150
|
}
|
|
151
|
-
/**
|
|
152
|
-
* Hook to create auto theme configuration based on system preference
|
|
151
|
+
/**
|
|
152
|
+
* Hook to create auto theme configuration based on system preference
|
|
153
153
|
*/ export function useAutoTheme(lightTheme, darkTheme) {
|
|
154
154
|
const systemTheme = useSystemTheme();
|
|
155
155
|
return useMemo(()=>{
|
|
@@ -160,8 +160,8 @@ const ThemeContext = createContext(null);
|
|
|
160
160
|
darkTheme
|
|
161
161
|
]);
|
|
162
162
|
}
|
|
163
|
-
/**
|
|
164
|
-
* Hook to create theme with custom overrides
|
|
163
|
+
/**
|
|
164
|
+
* Hook to create theme with custom overrides
|
|
165
165
|
*/ export function useCustomTheme(baseThemeName, overrides) {
|
|
166
166
|
return useMemo(()=>{
|
|
167
167
|
return {
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { defaultTheme, themes } from './themes.js';
|
|
2
|
-
/**
|
|
3
|
-
* Get a theme by name or return the default theme
|
|
2
|
+
/**
|
|
3
|
+
* Get a theme by name or return the default theme
|
|
4
4
|
*/ export function getTheme(themeName) {
|
|
5
5
|
return themes[themeName] || defaultTheme;
|
|
6
6
|
}
|
|
7
|
-
/**
|
|
8
|
-
* Merge theme configurations with custom overrides
|
|
7
|
+
/**
|
|
8
|
+
* Merge theme configurations with custom overrides
|
|
9
9
|
*/ export function mergeThemeConfig(config) {
|
|
10
10
|
const baseTheme = typeof config.theme === 'string' ? getTheme(config.theme) : config.theme;
|
|
11
11
|
return {
|
|
@@ -32,8 +32,8 @@ import { defaultTheme, themes } from './themes.js';
|
|
|
32
32
|
}
|
|
33
33
|
};
|
|
34
34
|
}
|
|
35
|
-
/**
|
|
36
|
-
* Generate CSS classes from theme configuration
|
|
35
|
+
/**
|
|
36
|
+
* Generate CSS classes from theme configuration
|
|
37
37
|
*/ export function generateThemeClasses(theme, config = {}) {
|
|
38
38
|
const enableAnimations = config.enableAnimations !== false;
|
|
39
39
|
const enableShadows = config.enableShadows !== false;
|
|
@@ -340,8 +340,8 @@ import { defaultTheme, themes } from './themes.js';
|
|
|
340
340
|
visible: visibleStyles
|
|
341
341
|
};
|
|
342
342
|
}
|
|
343
|
-
/**
|
|
344
|
-
* Apply theme to a CSS class with optional variant
|
|
343
|
+
/**
|
|
344
|
+
* Apply theme to a CSS class with optional variant
|
|
345
345
|
*/ export function applyTheme(theme, element, variant) {
|
|
346
346
|
const classes = generateThemeClasses(theme);
|
|
347
347
|
if (variant) {
|
|
@@ -350,18 +350,18 @@ import { defaultTheme, themes } from './themes.js';
|
|
|
350
350
|
}
|
|
351
351
|
return classes[element] || '';
|
|
352
352
|
}
|
|
353
|
-
/**
|
|
354
|
-
* Check if theme is dark mode
|
|
353
|
+
/**
|
|
354
|
+
* Check if theme is dark mode
|
|
355
355
|
*/ export function isDarkTheme(theme) {
|
|
356
356
|
return theme.name === 'dark' || theme.colors.inputBackground.includes('#1f') || theme.colors.inputBackground.includes('#0f');
|
|
357
357
|
}
|
|
358
|
-
/**
|
|
359
|
-
* Check if theme is light mode
|
|
358
|
+
/**
|
|
359
|
+
* Check if theme is light mode
|
|
360
360
|
*/ export function isLightTheme(theme) {
|
|
361
361
|
return !isDarkTheme(theme);
|
|
362
362
|
}
|
|
363
|
-
/**
|
|
364
|
-
* Get theme-specific CSS variables
|
|
363
|
+
/**
|
|
364
|
+
* Get theme-specific CSS variables
|
|
365
365
|
*/ export function getThemeVariables(theme) {
|
|
366
366
|
return {
|
|
367
367
|
'--search-collection-badge': theme.colors.collectionBadge,
|
package/dist/endpoints/health.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import pkg from '../../package.json';
|
|
2
2
|
import { searchCache } from '../lib/cache.js';
|
|
3
|
-
/**
|
|
4
|
-
* Test Typesense connection
|
|
3
|
+
/**
|
|
4
|
+
* Test Typesense connection
|
|
5
5
|
*/ const testTypesenseConnection = async (typesenseClient)=>{
|
|
6
6
|
try {
|
|
7
7
|
const health = await typesenseClient.health.retrieve();
|
|
@@ -11,8 +11,8 @@ import { searchCache } from '../lib/cache.js';
|
|
|
11
11
|
return false;
|
|
12
12
|
}
|
|
13
13
|
};
|
|
14
|
-
/**
|
|
15
|
-
* Get collection information
|
|
14
|
+
/**
|
|
15
|
+
* Get collection information
|
|
16
16
|
*/ const getCollectionInfo = async (typesenseClient)=>{
|
|
17
17
|
try {
|
|
18
18
|
const collections = await typesenseClient.collections().retrieve();
|
|
@@ -22,8 +22,8 @@ import { searchCache } from '../lib/cache.js';
|
|
|
22
22
|
return [];
|
|
23
23
|
}
|
|
24
24
|
};
|
|
25
|
-
/**
|
|
26
|
-
* Get cache statistics
|
|
25
|
+
/**
|
|
26
|
+
* Get cache statistics
|
|
27
27
|
*/ const getCacheStats = ()=>{
|
|
28
28
|
const stats = searchCache.getStats();
|
|
29
29
|
return {
|
|
@@ -32,8 +32,8 @@ import { searchCache } from '../lib/cache.js';
|
|
|
32
32
|
size: stats.size
|
|
33
33
|
};
|
|
34
34
|
};
|
|
35
|
-
/**
|
|
36
|
-
* Create health check handler
|
|
35
|
+
/**
|
|
36
|
+
* Create health check handler
|
|
37
37
|
*/ export const createHealthCheckHandler = (typesenseClient, pluginOptions, lastSyncTime)=>{
|
|
38
38
|
return async ()=>{
|
|
39
39
|
try {
|
|
@@ -93,8 +93,8 @@ import { searchCache } from '../lib/cache.js';
|
|
|
93
93
|
}
|
|
94
94
|
};
|
|
95
95
|
};
|
|
96
|
-
/**
|
|
97
|
-
* Create detailed health check handler with more information
|
|
96
|
+
/**
|
|
97
|
+
* Create detailed health check handler with more information
|
|
98
98
|
*/ export const createDetailedHealthCheckHandler = (typesenseClient, pluginOptions, lastSyncTime)=>{
|
|
99
99
|
return async ()=>{
|
|
100
100
|
try {
|
package/dist/lib/cache.js
CHANGED
|
@@ -6,8 +6,8 @@ export class SearchCache {
|
|
|
6
6
|
this.defaultTTL = options.ttl || 5 * 60 * 1000; // 5 minutes default
|
|
7
7
|
this.maxSize = options.maxSize || 1000; // 1000 entries default
|
|
8
8
|
}
|
|
9
|
-
/**
|
|
10
|
-
* Generate cache key from search parameters
|
|
9
|
+
/**
|
|
10
|
+
* Generate cache key from search parameters
|
|
11
11
|
*/ generateKey(query, collection, params) {
|
|
12
12
|
const baseKey = `${collection || 'universal'}:${query}`;
|
|
13
13
|
if (params) {
|
|
@@ -16,8 +16,8 @@ export class SearchCache {
|
|
|
16
16
|
}
|
|
17
17
|
return baseKey;
|
|
18
18
|
}
|
|
19
|
-
/**
|
|
20
|
-
* Clear expired entries
|
|
19
|
+
/**
|
|
20
|
+
* Clear expired entries
|
|
21
21
|
*/ cleanup() {
|
|
22
22
|
const now = Date.now();
|
|
23
23
|
for (const [key, entry] of this.cache.entries()){
|
|
@@ -26,8 +26,8 @@ export class SearchCache {
|
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
}
|
|
29
|
-
/**
|
|
30
|
-
* Clear cache entries matching pattern
|
|
29
|
+
/**
|
|
30
|
+
* Clear cache entries matching pattern
|
|
31
31
|
*/ clear(pattern) {
|
|
32
32
|
if (!pattern) {
|
|
33
33
|
this.cache.clear();
|
|
@@ -39,8 +39,8 @@ export class SearchCache {
|
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
|
-
/**
|
|
43
|
-
* Get cached search result
|
|
42
|
+
/**
|
|
43
|
+
* Get cached search result
|
|
44
44
|
*/ get(query, collection, params) {
|
|
45
45
|
const key = this.generateKey(query, collection || '', params);
|
|
46
46
|
const entry = this.cache.get(key);
|
|
@@ -54,21 +54,21 @@ export class SearchCache {
|
|
|
54
54
|
}
|
|
55
55
|
return entry.data;
|
|
56
56
|
}
|
|
57
|
-
/**
|
|
58
|
-
* Get cache statistics
|
|
57
|
+
/**
|
|
58
|
+
* Get cache statistics
|
|
59
59
|
*/ getStats() {
|
|
60
60
|
return {
|
|
61
61
|
maxSize: this.maxSize,
|
|
62
62
|
size: this.cache.size
|
|
63
63
|
};
|
|
64
64
|
}
|
|
65
|
-
/**
|
|
66
|
-
* Check if cache has valid entry
|
|
65
|
+
/**
|
|
66
|
+
* Check if cache has valid entry
|
|
67
67
|
*/ has(query, collection, params) {
|
|
68
68
|
return this.get(query, collection, params) !== null;
|
|
69
69
|
}
|
|
70
|
-
/**
|
|
71
|
-
* Set cached search result
|
|
70
|
+
/**
|
|
71
|
+
* Set cached search result
|
|
72
72
|
*/ set(query, data, collection, params, ttl) {
|
|
73
73
|
const key = this.generateKey(query, collection || '', params);
|
|
74
74
|
// Enforce max size by removing oldest entries
|
|
@@ -39,8 +39,8 @@ export const TypesenseSearchConfigSchema = z.object({
|
|
|
39
39
|
retryIntervalSeconds: z.number().int().min(1).optional().default(1)
|
|
40
40
|
})
|
|
41
41
|
});
|
|
42
|
-
/**
|
|
43
|
-
* Validate plugin configuration
|
|
42
|
+
/**
|
|
43
|
+
* Validate plugin configuration
|
|
44
44
|
*/ export function validateConfig(config) {
|
|
45
45
|
try {
|
|
46
46
|
const validatedConfig = TypesenseSearchConfigSchema.parse(config);
|
|
@@ -67,8 +67,8 @@ export const TypesenseSearchConfigSchema = z.object({
|
|
|
67
67
|
};
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
|
-
/**
|
|
71
|
-
* Validate and transform configuration with defaults
|
|
70
|
+
/**
|
|
71
|
+
* Validate and transform configuration with defaults
|
|
72
72
|
*/ export function validateAndTransformConfig(config) {
|
|
73
73
|
try {
|
|
74
74
|
const validatedConfig = TypesenseSearchConfigSchema.parse(config);
|
|
@@ -95,8 +95,8 @@ export const TypesenseSearchConfigSchema = z.object({
|
|
|
95
95
|
};
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
|
-
/**
|
|
99
|
-
* Validate individual collection configuration
|
|
98
|
+
/**
|
|
99
|
+
* Validate individual collection configuration
|
|
100
100
|
*/ export function validateCollectionConfig(collectionSlug, config) {
|
|
101
101
|
try {
|
|
102
102
|
const validatedConfig = CollectionConfigSchema.parse(config);
|
|
@@ -125,13 +125,13 @@ export const TypesenseSearchConfigSchema = z.object({
|
|
|
125
125
|
};
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
|
-
/**
|
|
129
|
-
* Get configuration validation errors in a user-friendly format
|
|
128
|
+
/**
|
|
129
|
+
* Get configuration validation errors in a user-friendly format
|
|
130
130
|
*/ export function getValidationErrors(errors) {
|
|
131
131
|
return errors.map((error, index)=>`${index + 1}. ${error}`).join('\n');
|
|
132
132
|
}
|
|
133
|
-
/**
|
|
134
|
-
* Validate search parameters
|
|
133
|
+
/**
|
|
134
|
+
* Validate search parameters
|
|
135
135
|
*/ export const SearchParamsSchema = z.object({
|
|
136
136
|
facets: z.array(z.string()).optional(),
|
|
137
137
|
filters: z.record(z.string(), z.any()).optional(),
|
|
@@ -144,8 +144,8 @@ export const TypesenseSearchConfigSchema = z.object({
|
|
|
144
144
|
sort_by: z.string().optional(),
|
|
145
145
|
typo_tokens_threshold: z.number().int().min(1).optional().default(1)
|
|
146
146
|
});
|
|
147
|
-
/**
|
|
148
|
-
* Validate search parameters
|
|
147
|
+
/**
|
|
148
|
+
* Validate search parameters
|
|
149
149
|
*/ export function validateSearchParams(params) {
|
|
150
150
|
try {
|
|
151
151
|
const validatedParams = SearchParamsSchema.parse(params);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rubixstudios/payload-typesense",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "A production-ready search plugin that integrates Typesense with Payload CMS, offering fast, typo-tolerant search with real-time synchronization. This fork by Rubix Studios reduces bloat and introduces targeted changes for improved performance, maintainability, and flexibility.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Rubix Studios <hello@rubixstudios.com.au> (https://rubixstudios.com.au)",
|
|
@@ -46,15 +46,6 @@
|
|
|
46
46
|
"files": [
|
|
47
47
|
"dist"
|
|
48
48
|
],
|
|
49
|
-
"scripts": {
|
|
50
|
-
"build": "pnpm build:types && pnpm build:swc",
|
|
51
|
-
"build:swc": "swc ./src -d ./dist --config-file .swcrc --strip-leading-paths",
|
|
52
|
-
"build:types": "tsc -p tsconfig.json",
|
|
53
|
-
"clean": "rimraf -g {dist,*.tsbuildinfo}",
|
|
54
|
-
"lint": "eslint .",
|
|
55
|
-
"lint:fix": "eslint . --fix",
|
|
56
|
-
"prepublishOnly": "pnpm clean && pnpm build"
|
|
57
|
-
},
|
|
58
49
|
"devDependencies": {
|
|
59
50
|
"@payloadcms/eslint-config": "3.28.0",
|
|
60
51
|
"@swc/cli": "^0.7.8",
|
|
@@ -73,20 +64,22 @@
|
|
|
73
64
|
},
|
|
74
65
|
"publishConfig": {
|
|
75
66
|
"access": "public",
|
|
76
|
-
"registry": "https://registry.npmjs.org/"
|
|
77
|
-
"exports": {
|
|
78
|
-
".": {
|
|
79
|
-
"import": "./dist/index.js",
|
|
80
|
-
"types": "./dist/index.d.ts",
|
|
81
|
-
"default": "./dist/index.js"
|
|
82
|
-
}
|
|
83
|
-
},
|
|
84
|
-
"main": "./dist/index.js",
|
|
85
|
-
"types": "./dist/index.d.ts"
|
|
67
|
+
"registry": "https://registry.npmjs.org/"
|
|
86
68
|
},
|
|
87
69
|
"dependencies": {
|
|
88
70
|
"typesense": "^2.1.0",
|
|
89
71
|
"zod": "^4.1.11"
|
|
90
72
|
},
|
|
91
|
-
"
|
|
92
|
-
|
|
73
|
+
"scripts": {
|
|
74
|
+
"build": "pnpm build:types && pnpm build:swc",
|
|
75
|
+
"build:swc": "swc ./src -d ./dist --config-file .swcrc --strip-leading-paths",
|
|
76
|
+
"build:types": "tsc -p tsconfig.json",
|
|
77
|
+
"clean": "rimraf -g {dist,*.tsbuildinfo}",
|
|
78
|
+
"lint": "eslint .",
|
|
79
|
+
"lint:fix": "eslint . --fix",
|
|
80
|
+
"update": "pnpm update --latest && pnpm install && pnpm prune && pnpm dedupe",
|
|
81
|
+
"release:patch": "pnpm clean && pnpm build && pnpm version patch -m \"chore(release): %s\" && pnpm publish --access public",
|
|
82
|
+
"release:minor": "pnpm clean && pnpm build && pnpm version minor -m \"chore(release): %s\" && pnpm publish --access public",
|
|
83
|
+
"release:major": "pnpm clean && pnpm build && pnpm version major -m \"chore(release): %s\" && pnpm publish --access public"
|
|
84
|
+
}
|
|
85
|
+
}
|