@sylphx/lens-react 2.3.3 → 2.4.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.
- package/dist/index.d.ts +24 -2
- package/dist/index.js +12 -5
- package/package.json +1 -1
- package/src/create.tsx +43 -8
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,35 @@
|
|
|
1
1
|
import { LensClientConfig, QueryResult, SelectionObject, TypedClientConfig } from "@sylphx/lens-client";
|
|
2
2
|
import { MutationDef, QueryDef, RouterDef, RouterRoutes } from "@sylphx/lens-core";
|
|
3
|
+
/**
|
|
4
|
+
* Debug callbacks for query hooks.
|
|
5
|
+
* @internal For debugging purposes only - not recommended for production use.
|
|
6
|
+
*/
|
|
7
|
+
interface QueryDebugOptions<T> {
|
|
8
|
+
/** Called when data is received */
|
|
9
|
+
onData?: (data: T) => void;
|
|
10
|
+
/** Called when an error occurs */
|
|
11
|
+
onError?: (error: Error) => void;
|
|
12
|
+
/** Called when subscription starts */
|
|
13
|
+
onSubscribe?: () => void;
|
|
14
|
+
/** Called when subscription ends */
|
|
15
|
+
onUnsubscribe?: () => void;
|
|
16
|
+
}
|
|
3
17
|
/** Query hook options */
|
|
4
|
-
interface QueryHookOptions<
|
|
18
|
+
interface QueryHookOptions<
|
|
19
|
+
TInput,
|
|
20
|
+
TOutput = unknown
|
|
21
|
+
> {
|
|
5
22
|
/** Query input parameters */
|
|
6
23
|
input?: TInput;
|
|
7
24
|
/** Field selection */
|
|
8
25
|
select?: SelectionObject;
|
|
9
26
|
/** Skip query execution */
|
|
10
27
|
skip?: boolean;
|
|
28
|
+
/**
|
|
29
|
+
* Debug callbacks for development.
|
|
30
|
+
* @internal For debugging purposes only - not recommended for production use.
|
|
31
|
+
*/
|
|
32
|
+
debug?: QueryDebugOptions<TOutput>;
|
|
11
33
|
}
|
|
12
34
|
/** Query hook result */
|
|
13
35
|
interface QueryHookResult<T> {
|
|
@@ -59,7 +81,7 @@ interface QueryEndpoint<
|
|
|
59
81
|
select?: SelectionObject;
|
|
60
82
|
}): QueryResult<TOutput>;
|
|
61
83
|
/** React hook for reactive queries */
|
|
62
|
-
useQuery: (options?: TInput extends void ? QueryHookOptions<void> | void : QueryHookOptions<TInput>) => QueryHookResult<TOutput>;
|
|
84
|
+
useQuery: (options?: TInput extends void ? QueryHookOptions<void, TOutput> | void : QueryHookOptions<TInput, TOutput>) => QueryHookResult<TOutput>;
|
|
63
85
|
}
|
|
64
86
|
/** Mutation endpoint with React hooks */
|
|
65
87
|
interface MutationEndpoint<
|
package/dist/index.js
CHANGED
|
@@ -21,12 +21,16 @@ function queryReducer(state, action) {
|
|
|
21
21
|
}
|
|
22
22
|
function createUseQueryHook(getEndpoint) {
|
|
23
23
|
return function useQuery(options) {
|
|
24
|
+
const inputKey = JSON.stringify(options?.input);
|
|
25
|
+
const selectKey = JSON.stringify(options?.select);
|
|
26
|
+
const debugRef = useRef(options?.debug);
|
|
27
|
+
debugRef.current = options?.debug;
|
|
24
28
|
const query = useMemo(() => {
|
|
25
29
|
if (options?.skip)
|
|
26
30
|
return null;
|
|
27
31
|
const endpoint = getEndpoint();
|
|
28
32
|
return endpoint({ input: options?.input, select: options?.select });
|
|
29
|
-
}, [
|
|
33
|
+
}, [inputKey, selectKey, options?.skip]);
|
|
30
34
|
const initialState = {
|
|
31
35
|
data: null,
|
|
32
36
|
loading: query != null && !options?.skip,
|
|
@@ -43,32 +47,35 @@ function createUseQueryHook(getEndpoint) {
|
|
|
43
47
|
return;
|
|
44
48
|
}
|
|
45
49
|
dispatch({ type: "START" });
|
|
50
|
+
debugRef.current?.onSubscribe?.();
|
|
46
51
|
let hasReceivedData = false;
|
|
47
52
|
const unsubscribe = query.subscribe((value) => {
|
|
48
53
|
if (mountedRef.current) {
|
|
49
54
|
hasReceivedData = true;
|
|
50
55
|
dispatch({ type: "SUCCESS", data: value });
|
|
56
|
+
debugRef.current?.onData?.(value);
|
|
51
57
|
}
|
|
52
58
|
});
|
|
53
59
|
query.then((value) => {
|
|
54
60
|
if (mountedRef.current) {
|
|
55
61
|
if (!hasReceivedData) {
|
|
56
62
|
dispatch({ type: "SUCCESS", data: value });
|
|
63
|
+
debugRef.current?.onData?.(value);
|
|
57
64
|
} else {
|
|
58
65
|
dispatch({ type: "LOADING_DONE" });
|
|
59
66
|
}
|
|
60
67
|
}
|
|
61
68
|
}, (err) => {
|
|
62
69
|
if (mountedRef.current) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
});
|
|
70
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
71
|
+
dispatch({ type: "ERROR", error });
|
|
72
|
+
debugRef.current?.onError?.(error);
|
|
67
73
|
}
|
|
68
74
|
});
|
|
69
75
|
return () => {
|
|
70
76
|
mountedRef.current = false;
|
|
71
77
|
unsubscribe();
|
|
78
|
+
debugRef.current?.onUnsubscribe?.();
|
|
72
79
|
};
|
|
73
80
|
}, [query]);
|
|
74
81
|
const refetch = useCallback(() => {
|
package/package.json
CHANGED
package/src/create.tsx
CHANGED
|
@@ -39,14 +39,34 @@ import { useCallback, useEffect, useMemo, useReducer, useRef, useState } from "r
|
|
|
39
39
|
// Types
|
|
40
40
|
// =============================================================================
|
|
41
41
|
|
|
42
|
+
/**
|
|
43
|
+
* Debug callbacks for query hooks.
|
|
44
|
+
* @internal For debugging purposes only - not recommended for production use.
|
|
45
|
+
*/
|
|
46
|
+
export interface QueryDebugOptions<T> {
|
|
47
|
+
/** Called when data is received */
|
|
48
|
+
onData?: (data: T) => void;
|
|
49
|
+
/** Called when an error occurs */
|
|
50
|
+
onError?: (error: Error) => void;
|
|
51
|
+
/** Called when subscription starts */
|
|
52
|
+
onSubscribe?: () => void;
|
|
53
|
+
/** Called when subscription ends */
|
|
54
|
+
onUnsubscribe?: () => void;
|
|
55
|
+
}
|
|
56
|
+
|
|
42
57
|
/** Query hook options */
|
|
43
|
-
export interface QueryHookOptions<TInput> {
|
|
58
|
+
export interface QueryHookOptions<TInput, TOutput = unknown> {
|
|
44
59
|
/** Query input parameters */
|
|
45
60
|
input?: TInput;
|
|
46
61
|
/** Field selection */
|
|
47
62
|
select?: SelectionObject;
|
|
48
63
|
/** Skip query execution */
|
|
49
64
|
skip?: boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Debug callbacks for development.
|
|
67
|
+
* @internal For debugging purposes only - not recommended for production use.
|
|
68
|
+
*/
|
|
69
|
+
debug?: QueryDebugOptions<TOutput>;
|
|
50
70
|
}
|
|
51
71
|
|
|
52
72
|
/** Query hook result */
|
|
@@ -92,7 +112,9 @@ export interface QueryEndpoint<TInput, TOutput> {
|
|
|
92
112
|
|
|
93
113
|
/** React hook for reactive queries */
|
|
94
114
|
useQuery: (
|
|
95
|
-
options?: TInput extends void
|
|
115
|
+
options?: TInput extends void
|
|
116
|
+
? QueryHookOptions<void, TOutput> | void
|
|
117
|
+
: QueryHookOptions<TInput, TOutput>,
|
|
96
118
|
) => QueryHookResult<TOutput>;
|
|
97
119
|
}
|
|
98
120
|
|
|
@@ -164,13 +186,23 @@ function queryReducer<T>(state: QueryState<T>, action: QueryAction<T>): QuerySta
|
|
|
164
186
|
function createUseQueryHook<TInput, TOutput>(
|
|
165
187
|
getEndpoint: () => (options: unknown) => QueryResult<TOutput>,
|
|
166
188
|
) {
|
|
167
|
-
return function useQuery(options?: QueryHookOptions<TInput>): QueryHookResult<TOutput> {
|
|
189
|
+
return function useQuery(options?: QueryHookOptions<TInput, TOutput>): QueryHookResult<TOutput> {
|
|
190
|
+
// Use JSON.stringify for stable dependency comparison
|
|
191
|
+
// This prevents re-fetching when input object reference changes but content is the same
|
|
192
|
+
const inputKey = JSON.stringify(options?.input);
|
|
193
|
+
const selectKey = JSON.stringify(options?.select);
|
|
194
|
+
|
|
195
|
+
// Store debug callbacks in ref to avoid dependency issues
|
|
196
|
+
const debugRef = useRef(options?.debug);
|
|
197
|
+
debugRef.current = options?.debug;
|
|
198
|
+
|
|
168
199
|
// Get query result from base client
|
|
200
|
+
// biome-ignore lint/correctness/useExhaustiveDependencies: Using JSON.stringify keys (inputKey, selectKey) for stable comparison instead of object references
|
|
169
201
|
const query = useMemo(() => {
|
|
170
202
|
if (options?.skip) return null;
|
|
171
203
|
const endpoint = getEndpoint();
|
|
172
204
|
return endpoint({ input: options?.input, select: options?.select });
|
|
173
|
-
}, [
|
|
205
|
+
}, [inputKey, selectKey, options?.skip]);
|
|
174
206
|
|
|
175
207
|
// State management
|
|
176
208
|
const initialState: QueryState<TOutput> = {
|
|
@@ -195,6 +227,7 @@ function createUseQueryHook<TInput, TOutput>(
|
|
|
195
227
|
}
|
|
196
228
|
|
|
197
229
|
dispatch({ type: "START" });
|
|
230
|
+
debugRef.current?.onSubscribe?.();
|
|
198
231
|
|
|
199
232
|
let hasReceivedData = false;
|
|
200
233
|
|
|
@@ -202,6 +235,7 @@ function createUseQueryHook<TInput, TOutput>(
|
|
|
202
235
|
if (mountedRef.current) {
|
|
203
236
|
hasReceivedData = true;
|
|
204
237
|
dispatch({ type: "SUCCESS", data: value });
|
|
238
|
+
debugRef.current?.onData?.(value);
|
|
205
239
|
}
|
|
206
240
|
});
|
|
207
241
|
|
|
@@ -210,6 +244,7 @@ function createUseQueryHook<TInput, TOutput>(
|
|
|
210
244
|
if (mountedRef.current) {
|
|
211
245
|
if (!hasReceivedData) {
|
|
212
246
|
dispatch({ type: "SUCCESS", data: value });
|
|
247
|
+
debugRef.current?.onData?.(value);
|
|
213
248
|
} else {
|
|
214
249
|
dispatch({ type: "LOADING_DONE" });
|
|
215
250
|
}
|
|
@@ -217,10 +252,9 @@ function createUseQueryHook<TInput, TOutput>(
|
|
|
217
252
|
},
|
|
218
253
|
(err) => {
|
|
219
254
|
if (mountedRef.current) {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
});
|
|
255
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
256
|
+
dispatch({ type: "ERROR", error });
|
|
257
|
+
debugRef.current?.onError?.(error);
|
|
224
258
|
}
|
|
225
259
|
},
|
|
226
260
|
);
|
|
@@ -228,6 +262,7 @@ function createUseQueryHook<TInput, TOutput>(
|
|
|
228
262
|
return () => {
|
|
229
263
|
mountedRef.current = false;
|
|
230
264
|
unsubscribe();
|
|
265
|
+
debugRef.current?.onUnsubscribe?.();
|
|
231
266
|
};
|
|
232
267
|
}, [query]);
|
|
233
268
|
|