@sylphx/lens-react 2.0.1 → 2.1.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/src/hooks.ts CHANGED
@@ -6,37 +6,29 @@
6
6
  *
7
7
  * @example
8
8
  * ```tsx
9
- * import { useLensClient, useQuery, useMutation } from '@sylphx/lens-react';
9
+ * import { useQuery, useMutation } from '@sylphx/lens-react';
10
10
  *
11
11
  * function UserProfile({ userId }: { userId: string }) {
12
- * const client = useLensClient();
13
- * const { data: user, loading } = useQuery(client.user.get({ id: userId }));
12
+ * // Client is automatically injected from context
13
+ * const { data: user, loading } = useQuery(
14
+ * (client) => client.user.get,
15
+ * { id: userId }
16
+ * );
14
17
  * if (loading) return <Spinner />;
15
18
  * return <h1>{user?.name}</h1>;
16
19
  * }
17
20
  *
18
21
  * function CreatePost() {
19
- * const client = useLensClient();
20
- * const { mutate, loading } = useMutation(client.post.create);
22
+ * const { mutate, loading } = useMutation((client) => client.post.create);
21
23
  * const handleCreate = () => mutate({ title: 'Hello' });
22
24
  * return <button onClick={handleCreate} disabled={loading}>Create</button>;
23
25
  * }
24
26
  * ```
25
27
  */
26
28
 
27
- import type { MutationResult, QueryResult } from "@sylphx/lens-client";
28
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
29
-
30
- // =============================================================================
31
- // Query Input Types
32
- // =============================================================================
33
-
34
- /** Query input - can be a query, null/undefined, or an accessor function */
35
- export type QueryInput<T> =
36
- | QueryResult<T>
37
- | null
38
- | undefined
39
- | (() => QueryResult<T> | null | undefined);
29
+ import type { LensClient, MutationResult, QueryResult } from "@sylphx/lens-client";
30
+ import { type DependencyList, useCallback, useEffect, useMemo, useRef, useState } from "react";
31
+ import { useLensClient } from "./context.js";
40
32
 
41
33
  // =============================================================================
42
34
  // Types
@@ -69,75 +61,159 @@ export interface UseMutationResult<TInput, TOutput> {
69
61
  }
70
62
 
71
63
  /** Options for useQuery */
72
- export interface UseQueryOptions {
64
+ export interface UseQueryOptions<TData = unknown, TSelected = TData> {
73
65
  /** Skip the query (don't execute) */
74
66
  skip?: boolean;
67
+ /** Transform the query result */
68
+ select?: (data: TData) => TSelected;
75
69
  }
76
70
 
71
+ /** Client type for callbacks */
72
+ type Client = LensClient<any, any>;
73
+
74
+ /** Route selector - callback that returns a route function */
75
+ export type RouteSelector<TParams, TResult> = (
76
+ client: Client,
77
+ ) => ((params: TParams) => QueryResult<TResult>) | null;
78
+
79
+ /** Query accessor selector - callback that returns QueryResult */
80
+ export type QuerySelector<TResult> = (client: Client) => QueryResult<TResult> | null | undefined;
81
+
82
+ /** Mutation selector - callback that returns mutation function */
83
+ export type MutationSelector<TInput, TOutput> = (
84
+ client: Client,
85
+ ) => (input: TInput) => Promise<MutationResult<TOutput>>;
86
+
77
87
  // =============================================================================
78
88
  // useQuery Hook
79
89
  // =============================================================================
80
90
 
81
- /** Helper to resolve query input (handles accessor functions) */
82
- function resolveQuery<T>(input: QueryInput<T>): QueryResult<T> | null | undefined {
83
- return typeof input === "function" ? input() : input;
84
- }
85
-
86
91
  /**
87
- * Subscribe to a query with reactive updates
92
+ * Subscribe to a query with reactive updates.
93
+ * Client is automatically injected from LensProvider context.
88
94
  *
89
- * @param queryInput - QueryResult, null/undefined, or accessor function returning QueryResult
90
- * @param options - Query options
95
+ * Two usage patterns:
96
+ *
97
+ * **1. Route + Params (recommended)** - Stable references, no infinite loops
98
+ * ```tsx
99
+ * const { data } = useQuery((client) => client.user.get, { id: userId });
100
+ * ```
101
+ *
102
+ * **2. Accessor + Deps (escape hatch)** - For complex/composed queries
103
+ * ```tsx
104
+ * const { data } = useQuery((client) => client.user.get({ id }), [id]);
105
+ * ```
91
106
  *
92
107
  * @example
93
108
  * ```tsx
94
- * // Basic usage
109
+ * // Basic usage - Route + Params
95
110
  * function UserProfile({ userId }: { userId: string }) {
96
- * const client = useLensClient();
97
- * const { data: user, loading, error } = useQuery(client.user.get({ id: userId }));
111
+ * const { data: user, loading, error } = useQuery(
112
+ * (client) => client.user.get,
113
+ * { id: userId }
114
+ * );
98
115
  *
99
116
  * if (loading) return <Spinner />;
100
117
  * if (error) return <Error message={error.message} />;
101
- * if (!user) return <NotFound />;
118
+ * return <h1>{user?.name}</h1>;
119
+ * }
102
120
  *
103
- * return <h1>{user.name}</h1>;
121
+ * // With select transform
122
+ * function UserName({ userId }: { userId: string }) {
123
+ * const { data: name } = useQuery(
124
+ * (client) => client.user.get,
125
+ * { id: userId },
126
+ * { select: (user) => user.name }
127
+ * );
128
+ * return <span>{name}</span>;
104
129
  * }
105
130
  *
106
- * // Conditional query (null when condition not met)
131
+ * // Conditional query (return null to skip)
107
132
  * function SessionInfo({ sessionId }: { sessionId: string | null }) {
108
- * const client = useLensClient();
109
133
  * const { data } = useQuery(
110
- * sessionId ? client.session.get({ id: sessionId }) : null
134
+ * (client) => sessionId ? client.session.get : null,
135
+ * { id: sessionId ?? '' }
111
136
  * );
112
- * // data is null when sessionId is null
113
137
  * return <span>{data?.totalTokens}</span>;
114
138
  * }
115
139
  *
116
- * // Accessor function (reactive inputs)
117
- * function ReactiveQuery({ sessionId }: { sessionId: Signal<string | null> }) {
118
- * const client = useLensClient();
119
- * const { data } = useQuery(() =>
120
- * sessionId.value ? client.session.get({ id: sessionId.value }) : null
140
+ * // Skip query with option
141
+ * function ConditionalQuery({ userId, shouldFetch }: { userId: string; shouldFetch: boolean }) {
142
+ * const { data } = useQuery(
143
+ * (client) => client.user.get,
144
+ * { id: userId },
145
+ * { skip: !shouldFetch }
121
146
  * );
122
- * return <span>{data?.totalTokens}</span>;
123
147
  * }
124
148
  *
125
- * // Skip query conditionally
126
- * function ConditionalQuery({ shouldFetch }: { shouldFetch: boolean }) {
127
- * const client = useLensClient();
128
- * const { data } = useQuery(client.user.list(), { skip: !shouldFetch });
149
+ * // Complex queries with accessor (escape hatch)
150
+ * function ComplexQuery({ userId }: { userId: string }) {
151
+ * const { data } = useQuery(
152
+ * (client) => client.user.get({ id: userId }),
153
+ * [userId]
154
+ * );
129
155
  * }
130
156
  * ```
131
157
  */
132
- export function useQuery<T>(
133
- queryInput: QueryInput<T>,
134
- options?: UseQueryOptions,
135
- ): UseQueryResult<T> {
136
- // Resolve query (handles accessor functions)
137
- const query = useMemo(() => resolveQuery(queryInput), [queryInput]);
138
-
139
- const [data, setData] = useState<T | null>(null);
140
- const [loading, setLoading] = useState(!options?.skip && query != null);
158
+
159
+ // Overload 1: Route + Params (recommended)
160
+ export function useQuery<TParams, TResult, TSelected = TResult>(
161
+ selector: RouteSelector<TParams, TResult>,
162
+ params: TParams,
163
+ options?: UseQueryOptions<TResult, TSelected>,
164
+ ): UseQueryResult<TSelected>;
165
+
166
+ // Overload 2: Accessor + Deps (escape hatch for complex queries)
167
+ export function useQuery<TResult, TSelected = TResult>(
168
+ selector: QuerySelector<TResult>,
169
+ deps: DependencyList,
170
+ options?: UseQueryOptions<TResult, TSelected>,
171
+ ): UseQueryResult<TSelected>;
172
+
173
+ // Implementation
174
+ export function useQuery<TParams, TResult, TSelected = TResult>(
175
+ selector: RouteSelector<TParams, TResult> | QuerySelector<TResult>,
176
+ paramsOrDeps: TParams | DependencyList,
177
+ options?: UseQueryOptions<TResult, TSelected>,
178
+ ): UseQueryResult<TSelected> {
179
+ const client = useLensClient();
180
+
181
+ // Detect which overload is being used
182
+ const isAccessorMode = Array.isArray(paramsOrDeps);
183
+
184
+ // Stable params key for Route + Params mode
185
+ const paramsKey = !isAccessorMode ? JSON.stringify(paramsOrDeps) : null;
186
+
187
+ // Create query - memoized based on route/params or deps
188
+ const query = useMemo(
189
+ () => {
190
+ if (options?.skip) return null;
191
+
192
+ if (isAccessorMode) {
193
+ // Accessor mode: selector returns QueryResult directly
194
+ const querySelector = selector as QuerySelector<TResult>;
195
+ return querySelector(client);
196
+ }
197
+ // Route + Params mode: selector returns route function
198
+ const routeSelector = selector as RouteSelector<TParams, TResult>;
199
+ const route = routeSelector(client);
200
+ if (!route) return null;
201
+ return route(paramsOrDeps as TParams);
202
+ },
203
+ // biome-ignore lint/correctness/useExhaustiveDependencies: Dynamic deps based on overload mode - intentional
204
+ isAccessorMode
205
+ ? // eslint-disable-next-line react-hooks/exhaustive-deps
206
+ [client, options?.skip, ...(paramsOrDeps as DependencyList)]
207
+ : // eslint-disable-next-line react-hooks/exhaustive-deps
208
+ [client, selector, paramsKey, options?.skip],
209
+ );
210
+
211
+ // Use ref for select to avoid it being a dependency
212
+ const selectRef = useRef(options?.select);
213
+ selectRef.current = options?.select;
214
+
215
+ const [data, setData] = useState<TSelected | null>(null);
216
+ const [loading, setLoading] = useState(query != null && !options?.skip);
141
217
  const [error, setError] = useState<Error | null>(null);
142
218
 
143
219
  // Track mounted state
@@ -147,12 +223,17 @@ export function useQuery<T>(
147
223
  const queryRef = useRef(query);
148
224
  queryRef.current = query;
149
225
 
226
+ // Transform helper
227
+ const transform = useCallback((value: TResult): TSelected => {
228
+ return selectRef.current ? selectRef.current(value) : (value as unknown as TSelected);
229
+ }, []);
230
+
150
231
  // Subscribe to query
151
232
  useEffect(() => {
152
233
  mountedRef.current = true;
153
234
 
154
235
  // Handle null/undefined query
155
- if (query == null || options?.skip) {
236
+ if (query == null) {
156
237
  setData(null);
157
238
  setLoading(false);
158
239
  setError(null);
@@ -162,19 +243,26 @@ export function useQuery<T>(
162
243
  setLoading(true);
163
244
  setError(null);
164
245
 
165
- // Subscribe to updates
246
+ // Track if subscribe has provided data (to avoid duplicate updates from then)
247
+ let hasReceivedData = false;
248
+
249
+ // Subscribe to updates - primary data source for streaming
166
250
  const unsubscribe = query.subscribe((value) => {
167
251
  if (mountedRef.current) {
168
- setData(value);
252
+ hasReceivedData = true;
253
+ setData(transform(value));
169
254
  setLoading(false);
170
255
  }
171
256
  });
172
257
 
173
- // Handle initial load via promise (for one-shot queries)
258
+ // Handle completion/error via promise
259
+ // Only setData if subscribe hasn't already provided data (one-shot queries)
174
260
  query.then(
175
261
  (value) => {
262
+ if (mountedRef.current && !hasReceivedData) {
263
+ setData(transform(value));
264
+ }
176
265
  if (mountedRef.current) {
177
- setData(value);
178
266
  setLoading(false);
179
267
  }
180
268
  },
@@ -190,12 +278,12 @@ export function useQuery<T>(
190
278
  mountedRef.current = false;
191
279
  unsubscribe();
192
280
  };
193
- }, [query, options?.skip]);
281
+ }, [query, transform]);
194
282
 
195
283
  // Refetch function
196
284
  const refetch = useCallback(() => {
197
285
  const currentQuery = queryRef.current;
198
- if (currentQuery == null || options?.skip) return;
286
+ if (currentQuery == null) return;
199
287
 
200
288
  setLoading(true);
201
289
  setError(null);
@@ -203,7 +291,7 @@ export function useQuery<T>(
203
291
  currentQuery.then(
204
292
  (value) => {
205
293
  if (mountedRef.current) {
206
- setData(value);
294
+ setData(transform(value));
207
295
  setLoading(false);
208
296
  }
209
297
  },
@@ -214,7 +302,7 @@ export function useQuery<T>(
214
302
  }
215
303
  },
216
304
  );
217
- }, [options?.skip]);
305
+ }, [transform]);
218
306
 
219
307
  return { data, loading, error, refetch };
220
308
  }
@@ -223,19 +311,18 @@ export function useQuery<T>(
223
311
  // useMutation Hook
224
312
  // =============================================================================
225
313
 
226
- /** Mutation function type */
227
- export type MutationFn<TInput, TOutput> = (input: TInput) => Promise<MutationResult<TOutput>>;
228
-
229
314
  /**
230
- * Execute mutations with loading/error state
315
+ * Execute mutations with loading/error state.
316
+ * Client is automatically injected from LensProvider context.
231
317
  *
232
- * @param mutationFn - Mutation function from client API
318
+ * @param selector - Callback that returns mutation function from client
233
319
  *
234
320
  * @example
235
321
  * ```tsx
236
322
  * function CreatePost() {
237
- * const client = useLensClient();
238
- * const { mutate, loading, error, data } = useMutation(client.post.create);
323
+ * const { mutate, loading, error, data } = useMutation(
324
+ * (client) => client.post.create
325
+ * );
239
326
  *
240
327
  * const handleSubmit = async (formData: FormData) => {
241
328
  * try {
@@ -261,8 +348,7 @@ export type MutationFn<TInput, TOutput> = (input: TInput) => Promise<MutationRes
261
348
  *
262
349
  * // With optimistic updates
263
350
  * function UpdatePost({ postId }: { postId: string }) {
264
- * const client = useLensClient();
265
- * const { mutate } = useMutation(client.post.update);
351
+ * const { mutate } = useMutation((client) => client.post.update);
266
352
  *
267
353
  * const handleUpdate = async (title: string) => {
268
354
  * const result = await mutate({ id: postId, title });
@@ -272,8 +358,11 @@ export type MutationFn<TInput, TOutput> = (input: TInput) => Promise<MutationRes
272
358
  * ```
273
359
  */
274
360
  export function useMutation<TInput, TOutput>(
275
- mutationFn: MutationFn<TInput, TOutput>,
361
+ selector: MutationSelector<TInput, TOutput>,
276
362
  ): UseMutationResult<TInput, TOutput> {
363
+ const client = useLensClient();
364
+ const mutationFn = selector(client);
365
+
277
366
  const [loading, setLoading] = useState(false);
278
367
  const [error, setError] = useState<Error | null>(null);
279
368
  const [data, setData] = useState<TOutput | null>(null);
@@ -281,6 +370,10 @@ export function useMutation<TInput, TOutput>(
281
370
  // Track mounted state
282
371
  const mountedRef = useRef(true);
283
372
 
373
+ // Store mutation ref for latest version
374
+ const mutationRef = useRef(mutationFn);
375
+ mutationRef.current = mutationFn;
376
+
284
377
  useEffect(() => {
285
378
  mountedRef.current = true;
286
379
  return () => {
@@ -289,33 +382,30 @@ export function useMutation<TInput, TOutput>(
289
382
  }, []);
290
383
 
291
384
  // Mutation wrapper
292
- const mutate = useCallback(
293
- async (input: TInput): Promise<MutationResult<TOutput>> => {
294
- setLoading(true);
295
- setError(null);
385
+ const mutate = useCallback(async (input: TInput): Promise<MutationResult<TOutput>> => {
386
+ setLoading(true);
387
+ setError(null);
296
388
 
297
- try {
298
- const result = await mutationFn(input);
389
+ try {
390
+ const result = await mutationRef.current(input);
299
391
 
300
- if (mountedRef.current) {
301
- setData(result.data);
302
- }
392
+ if (mountedRef.current) {
393
+ setData(result.data);
394
+ }
303
395
 
304
- return result;
305
- } catch (err) {
306
- const mutationError = err instanceof Error ? err : new Error(String(err));
307
- if (mountedRef.current) {
308
- setError(mutationError);
309
- }
310
- throw mutationError;
311
- } finally {
312
- if (mountedRef.current) {
313
- setLoading(false);
314
- }
396
+ return result;
397
+ } catch (err) {
398
+ const mutationError = err instanceof Error ? err : new Error(String(err));
399
+ if (mountedRef.current) {
400
+ setError(mutationError);
315
401
  }
316
- },
317
- [mutationFn],
318
- );
402
+ throw mutationError;
403
+ } finally {
404
+ if (mountedRef.current) {
405
+ setLoading(false);
406
+ }
407
+ }
408
+ }, []);
319
409
 
320
410
  // Reset function
321
411
  const reset = useCallback(() => {
@@ -346,59 +436,80 @@ export interface UseLazyQueryResult<T> {
346
436
  }
347
437
 
348
438
  /**
349
- * Execute a query on demand (not on mount)
350
- *
351
- * @param queryInput - QueryResult, null/undefined, or accessor function returning QueryResult
439
+ * Execute a query on demand (not on mount).
440
+ * Client is automatically injected from LensProvider context.
352
441
  *
353
442
  * @example
354
443
  * ```tsx
444
+ * // Route + Params pattern
355
445
  * function SearchUsers() {
356
- * const client = useLensClient();
357
446
  * const [searchTerm, setSearchTerm] = useState('');
358
447
  * const { execute, data, loading } = useLazyQuery(
359
- * client.user.search({ query: searchTerm })
448
+ * (client) => client.user.search,
449
+ * { query: searchTerm }
360
450
  * );
361
451
  *
362
- * const handleSearch = async () => {
363
- * const users = await execute();
364
- * console.log('Found:', users);
365
- * };
366
- *
367
452
  * return (
368
453
  * <div>
369
- * <input
370
- * value={searchTerm}
371
- * onChange={e => setSearchTerm(e.target.value)}
372
- * />
373
- * <button onClick={handleSearch} disabled={loading}>
374
- * Search
375
- * </button>
454
+ * <input value={searchTerm} onChange={e => setSearchTerm(e.target.value)} />
455
+ * <button onClick={execute} disabled={loading}>Search</button>
376
456
  * {data?.map(user => <UserCard key={user.id} user={user} />)}
377
457
  * </div>
378
458
  * );
379
459
  * }
380
460
  *
381
- * // With accessor function
382
- * function LazyReactiveQuery({ sessionId }: { sessionId: Signal<string | null> }) {
383
- * const client = useLensClient();
384
- * const { execute, data } = useLazyQuery(() =>
385
- * sessionId.value ? client.session.get({ id: sessionId.value }) : null
461
+ * // Accessor pattern
462
+ * function LazyComplexQuery({ userId }: { userId: string }) {
463
+ * const { execute, data } = useLazyQuery(
464
+ * (client) => client.user.get({ id: userId }),
465
+ * [userId]
386
466
  * );
387
467
  * return <button onClick={execute}>Load</button>;
388
468
  * }
389
469
  * ```
390
470
  */
391
- export function useLazyQuery<T>(queryInput: QueryInput<T>): UseLazyQueryResult<T> {
392
- const [data, setData] = useState<T | null>(null);
471
+
472
+ // Overload 1: Route + Params
473
+ export function useLazyQuery<TParams, TResult, TSelected = TResult>(
474
+ selector: RouteSelector<TParams, TResult>,
475
+ params: TParams,
476
+ options?: UseQueryOptions<TResult, TSelected>,
477
+ ): UseLazyQueryResult<TSelected>;
478
+
479
+ // Overload 2: Accessor + Deps
480
+ export function useLazyQuery<TResult, TSelected = TResult>(
481
+ selector: QuerySelector<TResult>,
482
+ deps: DependencyList,
483
+ options?: UseQueryOptions<TResult, TSelected>,
484
+ ): UseLazyQueryResult<TSelected>;
485
+
486
+ // Implementation
487
+ export function useLazyQuery<TParams, TResult, TSelected = TResult>(
488
+ selector: RouteSelector<TParams, TResult> | QuerySelector<TResult>,
489
+ paramsOrDeps: TParams | DependencyList,
490
+ options?: UseQueryOptions<TResult, TSelected>,
491
+ ): UseLazyQueryResult<TSelected> {
492
+ const client = useLensClient();
493
+
494
+ const [data, setData] = useState<TSelected | null>(null);
393
495
  const [loading, setLoading] = useState(false);
394
496
  const [error, setError] = useState<Error | null>(null);
395
497
 
396
498
  // Track mounted state
397
499
  const mountedRef = useRef(true);
398
500
 
399
- // Store queryInput ref for execute (so it uses latest value)
400
- const queryInputRef = useRef(queryInput);
401
- queryInputRef.current = queryInput;
501
+ // Detect which overload
502
+ const isAccessorMode = Array.isArray(paramsOrDeps);
503
+
504
+ // Store refs for execute (so it uses latest values)
505
+ const selectorRef = useRef(selector);
506
+ selectorRef.current = selector;
507
+
508
+ const paramsOrDepsRef = useRef(paramsOrDeps);
509
+ paramsOrDepsRef.current = paramsOrDeps;
510
+
511
+ const selectRef = useRef(options?.select);
512
+ selectRef.current = options?.select;
402
513
 
403
514
  useEffect(() => {
404
515
  mountedRef.current = true;
@@ -408,13 +519,24 @@ export function useLazyQuery<T>(queryInput: QueryInput<T>): UseLazyQueryResult<T
408
519
  }, []);
409
520
 
410
521
  // Execute function
411
- const execute = useCallback(async (): Promise<T> => {
412
- const query = resolveQuery(queryInputRef.current);
522
+ const execute = useCallback(async (): Promise<TSelected> => {
523
+ let query: QueryResult<TResult> | null | undefined;
524
+
525
+ if (isAccessorMode) {
526
+ const querySelector = selectorRef.current as QuerySelector<TResult>;
527
+ query = querySelector(client);
528
+ } else {
529
+ const routeSelector = selectorRef.current as RouteSelector<TParams, TResult>;
530
+ const route = routeSelector(client);
531
+ if (route) {
532
+ query = route(paramsOrDepsRef.current as TParams);
533
+ }
534
+ }
413
535
 
414
536
  if (query == null) {
415
537
  setData(null);
416
538
  setLoading(false);
417
- return null as T;
539
+ return null as TSelected;
418
540
  }
419
541
 
420
542
  setLoading(true);
@@ -422,12 +544,15 @@ export function useLazyQuery<T>(queryInput: QueryInput<T>): UseLazyQueryResult<T
422
544
 
423
545
  try {
424
546
  const result = await query;
547
+ const selected = selectRef.current
548
+ ? selectRef.current(result)
549
+ : (result as unknown as TSelected);
425
550
 
426
551
  if (mountedRef.current) {
427
- setData(result);
552
+ setData(selected);
428
553
  }
429
554
 
430
- return result;
555
+ return selected;
431
556
  } catch (err) {
432
557
  const queryError = err instanceof Error ? err : new Error(String(err));
433
558
  if (mountedRef.current) {
@@ -439,7 +564,7 @@ export function useLazyQuery<T>(queryInput: QueryInput<T>): UseLazyQueryResult<T
439
564
  setLoading(false);
440
565
  }
441
566
  }
442
- }, []);
567
+ }, [client, isAccessorMode]);
443
568
 
444
569
  // Reset function
445
570
  const reset = useCallback(() => {
package/src/index.ts CHANGED
@@ -16,16 +16,17 @@ export { LensProvider, type LensProviderProps, useLensClient } from "./context.j
16
16
  // =============================================================================
17
17
 
18
18
  export {
19
- type MutationFn,
20
19
  // Types
21
- type QueryInput,
20
+ type MutationSelector,
21
+ type QuerySelector,
22
+ type RouteSelector,
22
23
  type UseLazyQueryResult,
23
24
  type UseMutationResult,
24
25
  type UseQueryOptions,
25
26
  type UseQueryResult,
27
+ // Query hooks
26
28
  useLazyQuery,
27
29
  // Mutation hook
28
30
  useMutation,
29
- // Query hooks
30
31
  useQuery,
31
32
  } from "./hooks.js";