@etoile-dev/react 1.0.0 → 1.0.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.
- package/README.md +4 -1
- package/dist/hooks/useEtoileSearch.d.ts +16 -2
- package/dist/hooks/useEtoileSearch.js +81 -32
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -481,10 +481,13 @@ const { results, isLoading } = useEtoileSearch({
|
|
|
481
481
|
apiKey: process.env.ETOILE_API_KEY!,
|
|
482
482
|
collections: ["paintings"],
|
|
483
483
|
query,
|
|
484
|
+
shouldRetryOnError: true,
|
|
485
|
+
errorRetryCount: 2,
|
|
486
|
+
errorRetryInterval: 1000,
|
|
484
487
|
});
|
|
485
488
|
```
|
|
486
489
|
|
|
487
|
-
Returns `results`, `isLoading`, `error`, `appliedFilters`, and `refinedQuery`.
|
|
490
|
+
Returns `results`, `isLoading`, `error`, `isError`, `appliedFilters`, and `refinedQuery`.
|
|
488
491
|
|
|
489
492
|
---
|
|
490
493
|
|
|
@@ -12,6 +12,15 @@ export type UseEtoileSearchOptions = {
|
|
|
12
12
|
offset?: number;
|
|
13
13
|
/** Debounce delay in ms before firing the request (default: 100) */
|
|
14
14
|
debounceMs?: number;
|
|
15
|
+
/**
|
|
16
|
+
* Whether failed requests should retry automatically (default: true).
|
|
17
|
+
* Uses `errorRetryCount` and `errorRetryInterval`.
|
|
18
|
+
*/
|
|
19
|
+
shouldRetryOnError?: boolean;
|
|
20
|
+
/** Maximum number of retries after a failed request (default: 2). */
|
|
21
|
+
errorRetryCount?: number;
|
|
22
|
+
/** Delay between retries in ms (default: 1000). */
|
|
23
|
+
errorRetryInterval?: number;
|
|
15
24
|
/**
|
|
16
25
|
* Explicit metadata filters applied to results.
|
|
17
26
|
* Mutually exclusive with `autoFilters`.
|
|
@@ -48,6 +57,8 @@ export type UseEtoileSearchReturn = {
|
|
|
48
57
|
isLoading: boolean;
|
|
49
58
|
/** Set when the last request failed; null otherwise. */
|
|
50
59
|
error: unknown;
|
|
60
|
+
/** True when `error` is set. */
|
|
61
|
+
isError: boolean;
|
|
51
62
|
/** Filters that were applied. Populated when `filters` or `autoFilters` is used. */
|
|
52
63
|
appliedFilters?: SearchFilter[];
|
|
53
64
|
/**
|
|
@@ -69,9 +80,12 @@ export type UseEtoileSearchReturn = {
|
|
|
69
80
|
* @param options.limit - Maximum results to return (default: `10`).
|
|
70
81
|
* @param options.offset - Number of results to skip for pagination (default: `0`).
|
|
71
82
|
* @param options.debounceMs - Debounce delay in ms before firing the request (default: `100`).
|
|
83
|
+
* @param options.shouldRetryOnError - Retry failed requests automatically (default: `true`).
|
|
84
|
+
* @param options.errorRetryCount - Maximum retries after a failed request (default: `2`).
|
|
85
|
+
* @param options.errorRetryInterval - Delay between retries in ms (default: `1000`).
|
|
72
86
|
* @param options.filters - Explicit metadata filters. Mutually exclusive with `autoFilters`.
|
|
73
87
|
* @param options.autoFilters - When `true`, the AI extracts filters from the query automatically.
|
|
74
|
-
* @returns Current search state: `results`, `isLoading`, `error`, `appliedFilters`, `refinedQuery`.
|
|
88
|
+
* @returns Current search state: `results`, `isLoading`, `error`, `isError`, `appliedFilters`, `refinedQuery`.
|
|
75
89
|
*
|
|
76
90
|
* @example Basic usage
|
|
77
91
|
* ```tsx
|
|
@@ -106,7 +120,7 @@ export type UseEtoileSearchReturn = {
|
|
|
106
120
|
* });
|
|
107
121
|
* ```
|
|
108
122
|
*/
|
|
109
|
-
export declare function useEtoileSearch({ apiKey, collections, query, limit, offset, debounceMs, filters, autoFilters, baseUrl, }: UseEtoileSearchOptions): UseEtoileSearchReturn;
|
|
123
|
+
export declare function useEtoileSearch({ apiKey, collections, query, limit, offset, debounceMs, shouldRetryOnError, errorRetryCount, errorRetryInterval, filters, autoFilters, baseUrl, }: UseEtoileSearchOptions): UseEtoileSearchReturn;
|
|
110
124
|
/**
|
|
111
125
|
* Alias of `useEtoileSearch` for ergonomic imports in examples.
|
|
112
126
|
*
|
|
@@ -13,9 +13,12 @@ import { Etoile } from "@etoile-dev/client";
|
|
|
13
13
|
* @param options.limit - Maximum results to return (default: `10`).
|
|
14
14
|
* @param options.offset - Number of results to skip for pagination (default: `0`).
|
|
15
15
|
* @param options.debounceMs - Debounce delay in ms before firing the request (default: `100`).
|
|
16
|
+
* @param options.shouldRetryOnError - Retry failed requests automatically (default: `true`).
|
|
17
|
+
* @param options.errorRetryCount - Maximum retries after a failed request (default: `2`).
|
|
18
|
+
* @param options.errorRetryInterval - Delay between retries in ms (default: `1000`).
|
|
16
19
|
* @param options.filters - Explicit metadata filters. Mutually exclusive with `autoFilters`.
|
|
17
20
|
* @param options.autoFilters - When `true`, the AI extracts filters from the query automatically.
|
|
18
|
-
* @returns Current search state: `results`, `isLoading`, `error`, `appliedFilters`, `refinedQuery`.
|
|
21
|
+
* @returns Current search state: `results`, `isLoading`, `error`, `isError`, `appliedFilters`, `refinedQuery`.
|
|
19
22
|
*
|
|
20
23
|
* @example Basic usage
|
|
21
24
|
* ```tsx
|
|
@@ -50,7 +53,7 @@ import { Etoile } from "@etoile-dev/client";
|
|
|
50
53
|
* });
|
|
51
54
|
* ```
|
|
52
55
|
*/
|
|
53
|
-
export function useEtoileSearch({ apiKey, collections, query, limit = 10, offset, debounceMs = 100, filters, autoFilters, baseUrl, }) {
|
|
56
|
+
export function useEtoileSearch({ apiKey, collections, query, limit = 10, offset, debounceMs = 100, shouldRetryOnError = true, errorRetryCount = 2, errorRetryInterval = 1000, filters, autoFilters, baseUrl, }) {
|
|
54
57
|
const [results, setResults] = React.useState([]);
|
|
55
58
|
const [isLoading, setIsLoading] = React.useState(false);
|
|
56
59
|
const [error, setError] = React.useState(null);
|
|
@@ -62,6 +65,18 @@ export function useEtoileSearch({ apiKey, collections, query, limit = 10, offset
|
|
|
62
65
|
filtersRef.current = filters;
|
|
63
66
|
const autoFiltersRef = React.useRef(autoFilters);
|
|
64
67
|
autoFiltersRef.current = autoFilters;
|
|
68
|
+
const collectionsRef = React.useRef(collections);
|
|
69
|
+
collectionsRef.current = collections;
|
|
70
|
+
// Compare value-based inputs by value so inline objects/arrays don't retrigger endlessly.
|
|
71
|
+
const collectionsKey = React.useMemo(() => collections.join("\u001f"), [collections]);
|
|
72
|
+
const filtersKey = React.useMemo(() => JSON.stringify(filters ?? null), [filters]);
|
|
73
|
+
const autoFiltersKey = autoFilters === undefined ? "unset" : autoFilters ? "true" : "false";
|
|
74
|
+
const retryCount = Number.isFinite(errorRetryCount)
|
|
75
|
+
? Math.max(0, Math.floor(errorRetryCount))
|
|
76
|
+
: 0;
|
|
77
|
+
const retryInterval = Number.isFinite(errorRetryInterval)
|
|
78
|
+
? Math.max(0, errorRetryInterval)
|
|
79
|
+
: 0;
|
|
65
80
|
// Debounce the raw query
|
|
66
81
|
const [debouncedQuery, setDebouncedQuery] = React.useState(query);
|
|
67
82
|
React.useEffect(() => {
|
|
@@ -77,10 +92,10 @@ export function useEtoileSearch({ apiKey, collections, query, limit = 10, offset
|
|
|
77
92
|
setIsLoading(false);
|
|
78
93
|
}
|
|
79
94
|
}, [query]);
|
|
80
|
-
// Fetch on debounced query / filter change
|
|
95
|
+
// Fetch on debounced query / collection / filter / retry option change
|
|
81
96
|
React.useEffect(() => {
|
|
82
97
|
if (debouncedQuery.trim() === "") {
|
|
83
|
-
setResults([]);
|
|
98
|
+
setResults((prev) => (prev.length === 0 ? prev : []));
|
|
84
99
|
setAppliedFilters(undefined);
|
|
85
100
|
setRefinedQuery(undefined);
|
|
86
101
|
setIsLoading(false);
|
|
@@ -88,40 +103,74 @@ export function useEtoileSearch({ apiKey, collections, query, limit = 10, offset
|
|
|
88
103
|
return;
|
|
89
104
|
}
|
|
90
105
|
let active = true;
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
collections,
|
|
95
|
-
query: debouncedQuery,
|
|
96
|
-
limit,
|
|
97
|
-
...(offset !== undefined && { offset }),
|
|
98
|
-
...(filtersRef.current !== undefined && { filters: filtersRef.current }),
|
|
99
|
-
...(autoFiltersRef.current !== undefined && { autoFilters: autoFiltersRef.current }),
|
|
100
|
-
})
|
|
101
|
-
.then((res) => {
|
|
102
|
-
if (!active)
|
|
103
|
-
return;
|
|
104
|
-
setResults(Array.isArray(res.results) ? res.results : []);
|
|
105
|
-
setAppliedFilters(res.appliedFilters);
|
|
106
|
-
setRefinedQuery(res.refinedQuery);
|
|
107
|
-
})
|
|
108
|
-
.catch((err) => {
|
|
106
|
+
let retryTimer;
|
|
107
|
+
let retryAttempt = 0;
|
|
108
|
+
const runSearch = () => {
|
|
109
109
|
if (!active)
|
|
110
110
|
return;
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
111
|
+
setIsLoading(true);
|
|
112
|
+
client
|
|
113
|
+
.search({
|
|
114
|
+
collections: collectionsRef.current,
|
|
115
|
+
query: debouncedQuery,
|
|
116
|
+
limit,
|
|
117
|
+
...(offset !== undefined && { offset }),
|
|
118
|
+
...(filtersRef.current !== undefined && { filters: filtersRef.current }),
|
|
119
|
+
...(autoFiltersRef.current !== undefined && { autoFilters: autoFiltersRef.current }),
|
|
120
|
+
})
|
|
121
|
+
.then((res) => {
|
|
122
|
+
if (!active)
|
|
123
|
+
return;
|
|
124
|
+
setResults(Array.isArray(res.results) ? res.results : []);
|
|
125
|
+
setAppliedFilters(res.appliedFilters);
|
|
126
|
+
setRefinedQuery(res.refinedQuery);
|
|
127
|
+
setError(null);
|
|
128
|
+
setIsLoading(false);
|
|
129
|
+
})
|
|
130
|
+
.catch((err) => {
|
|
131
|
+
if (!active)
|
|
132
|
+
return;
|
|
133
|
+
const canRetry = shouldRetryOnError && retryAttempt < retryCount;
|
|
134
|
+
if (canRetry) {
|
|
135
|
+
retryAttempt += 1;
|
|
136
|
+
retryTimer = setTimeout(runSearch, retryInterval);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
setResults([]);
|
|
140
|
+
setAppliedFilters(undefined);
|
|
141
|
+
setRefinedQuery(undefined);
|
|
142
|
+
setError(err);
|
|
118
143
|
setIsLoading(false);
|
|
119
|
-
|
|
144
|
+
});
|
|
145
|
+
};
|
|
146
|
+
setError(null);
|
|
147
|
+
runSearch();
|
|
120
148
|
return () => {
|
|
121
149
|
active = false;
|
|
150
|
+
if (retryTimer) {
|
|
151
|
+
clearTimeout(retryTimer);
|
|
152
|
+
}
|
|
122
153
|
};
|
|
123
|
-
}, [
|
|
124
|
-
|
|
154
|
+
}, [
|
|
155
|
+
client,
|
|
156
|
+
collectionsKey,
|
|
157
|
+
filtersKey,
|
|
158
|
+
autoFiltersKey,
|
|
159
|
+
debouncedQuery,
|
|
160
|
+
limit,
|
|
161
|
+
offset,
|
|
162
|
+
shouldRetryOnError,
|
|
163
|
+
retryCount,
|
|
164
|
+
retryInterval,
|
|
165
|
+
]);
|
|
166
|
+
return {
|
|
167
|
+
results,
|
|
168
|
+
isLoading,
|
|
169
|
+
error,
|
|
170
|
+
isError: error != null,
|
|
171
|
+
appliedFilters,
|
|
172
|
+
refinedQuery,
|
|
173
|
+
};
|
|
125
174
|
}
|
|
126
175
|
/**
|
|
127
176
|
* Alias of `useEtoileSearch` for ergonomic imports in examples.
|