@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 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<TInput> {
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
- }, [options?.input, options?.select, options?.skip, getEndpoint]);
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
- dispatch({
64
- type: "ERROR",
65
- error: err instanceof Error ? err : new Error(String(err))
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sylphx/lens-react",
3
- "version": "2.3.3",
3
+ "version": "2.4.0",
4
4
  "description": "React bindings for Lens API framework",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
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 ? QueryHookOptions<void> | void : QueryHookOptions<TInput>,
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
- }, [options?.input, options?.select, options?.skip, getEndpoint]);
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
- dispatch({
221
- type: "ERROR",
222
- error: err instanceof Error ? err : new Error(String(err)),
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