@sylphx/lens-react 1.0.5 → 1.2.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/dist/index.d.ts CHANGED
@@ -45,6 +45,8 @@ declare function LensProvider({ client, children }: LensProviderProps): ReactEle
45
45
  */
46
46
  declare function useLensClient<TRouter = any>(): LensClient<any, any> & TRouter;
47
47
  import { MutationResult, QueryResult } from "@sylphx/lens-client";
48
+ /** Query input - can be a query, null/undefined, or an accessor function */
49
+ type QueryInput<T> = QueryResult<T> | null | undefined | (() => QueryResult<T> | null | undefined);
48
50
  /** Result of useQuery hook */
49
51
  interface UseQueryResult<T> {
50
52
  /** Query data (null if loading or error) */
@@ -80,7 +82,7 @@ interface UseQueryOptions {
80
82
  /**
81
83
  * Subscribe to a query with reactive updates
82
84
  *
83
- * @param query - QueryResult from client API call
85
+ * @param queryInput - QueryResult, null/undefined, or accessor function returning QueryResult
84
86
  * @param options - Query options
85
87
  *
86
88
  * @example
@@ -97,14 +99,23 @@ interface UseQueryOptions {
97
99
  * return <h1>{user.name}</h1>;
98
100
  * }
99
101
  *
100
- * // With select (type-safe field selection)
101
- * function UserName({ userId }: { userId: string }) {
102
+ * // Conditional query (null when condition not met)
103
+ * function SessionInfo({ sessionId }: { sessionId: string | null }) {
102
104
  * const client = useLensClient();
103
105
  * const { data } = useQuery(
104
- * client.user.get({ id: userId }).select({ name: true })
106
+ * sessionId ? client.session.get({ id: sessionId }) : null
105
107
  * );
106
- * // data is { name: string } | null
107
- * return <span>{data?.name}</span>;
108
+ * // data is null when sessionId is null
109
+ * return <span>{data?.totalTokens}</span>;
110
+ * }
111
+ *
112
+ * // Accessor function (reactive inputs)
113
+ * function ReactiveQuery({ sessionId }: { sessionId: Signal<string | null> }) {
114
+ * const client = useLensClient();
115
+ * const { data } = useQuery(() =>
116
+ * sessionId.value ? client.session.get({ id: sessionId.value }) : null
117
+ * );
118
+ * return <span>{data?.totalTokens}</span>;
108
119
  * }
109
120
  *
110
121
  * // Skip query conditionally
@@ -114,7 +125,7 @@ interface UseQueryOptions {
114
125
  * }
115
126
  * ```
116
127
  */
117
- declare function useQuery<T>(query: QueryResult<T>, options?: UseQueryOptions): UseQueryResult<T>;
128
+ declare function useQuery<T>(queryInput: QueryInput<T>, options?: UseQueryOptions): UseQueryResult<T>;
118
129
  /** Mutation function type */
119
130
  type MutationFn<
120
131
  TInput,
@@ -185,7 +196,7 @@ interface UseLazyQueryResult<T> {
185
196
  /**
186
197
  * Execute a query on demand (not on mount)
187
198
  *
188
- * @param query - QueryResult from client API call
199
+ * @param queryInput - QueryResult, null/undefined, or accessor function returning QueryResult
189
200
  *
190
201
  * @example
191
202
  * ```tsx
@@ -214,7 +225,16 @@ interface UseLazyQueryResult<T> {
214
225
  * </div>
215
226
  * );
216
227
  * }
228
+ *
229
+ * // With accessor function
230
+ * function LazyReactiveQuery({ sessionId }: { sessionId: Signal<string | null> }) {
231
+ * const client = useLensClient();
232
+ * const { execute, data } = useLazyQuery(() =>
233
+ * sessionId.value ? client.session.get({ id: sessionId.value }) : null
234
+ * );
235
+ * return <button onClick={execute}>Load</button>;
236
+ * }
217
237
  * ```
218
238
  */
219
- declare function useLazyQuery<T>(query: QueryResult<T>): UseLazyQueryResult<T>;
220
- export { useQuery, useMutation, useLensClient, useLazyQuery, UseQueryResult, UseQueryOptions, UseMutationResult, UseLazyQueryResult, MutationFn, LensProviderProps, LensProvider };
239
+ declare function useLazyQuery<T>(queryInput: QueryInput<T>): UseLazyQueryResult<T>;
240
+ export { useQuery, useMutation, useLensClient, useLazyQuery, UseQueryResult, UseQueryOptions, UseMutationResult, UseLazyQueryResult, QueryInput, MutationFn, LensProviderProps, LensProvider };
package/dist/index.js CHANGED
@@ -16,16 +16,24 @@ function useLensClient() {
16
16
  return client;
17
17
  }
18
18
  // src/hooks.ts
19
- import { useCallback, useEffect, useRef, useState } from "react";
20
- function useQuery(query, options) {
19
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
20
+ function resolveQuery(input) {
21
+ return typeof input === "function" ? input() : input;
22
+ }
23
+ function useQuery(queryInput, options) {
24
+ const query = useMemo(() => resolveQuery(queryInput), [queryInput]);
21
25
  const [data, setData] = useState(null);
22
- const [loading, setLoading] = useState(!options?.skip);
26
+ const [loading, setLoading] = useState(!options?.skip && query != null);
23
27
  const [error, setError] = useState(null);
24
28
  const mountedRef = useRef(true);
29
+ const queryRef = useRef(query);
30
+ queryRef.current = query;
25
31
  useEffect(() => {
26
32
  mountedRef.current = true;
27
- if (options?.skip) {
33
+ if (query == null || options?.skip) {
34
+ setData(null);
28
35
  setLoading(false);
36
+ setError(null);
29
37
  return;
30
38
  }
31
39
  setLoading(true);
@@ -53,11 +61,12 @@ function useQuery(query, options) {
53
61
  };
54
62
  }, [query, options?.skip]);
55
63
  const refetch = useCallback(() => {
56
- if (options?.skip)
64
+ const currentQuery = queryRef.current;
65
+ if (currentQuery == null || options?.skip)
57
66
  return;
58
67
  setLoading(true);
59
68
  setError(null);
60
- query.then((value) => {
69
+ currentQuery.then((value) => {
61
70
  if (mountedRef.current) {
62
71
  setData(value);
63
72
  setLoading(false);
@@ -68,7 +77,7 @@ function useQuery(query, options) {
68
77
  setLoading(false);
69
78
  }
70
79
  });
71
- }, [query, options?.skip]);
80
+ }, [options?.skip]);
72
81
  return { data, loading, error, refetch };
73
82
  }
74
83
  function useMutation(mutationFn) {
@@ -110,11 +119,13 @@ function useMutation(mutationFn) {
110
119
  }, []);
111
120
  return { mutate, loading, error, data, reset };
112
121
  }
113
- function useLazyQuery(query) {
122
+ function useLazyQuery(queryInput) {
114
123
  const [data, setData] = useState(null);
115
124
  const [loading, setLoading] = useState(false);
116
125
  const [error, setError] = useState(null);
117
126
  const mountedRef = useRef(true);
127
+ const queryInputRef = useRef(queryInput);
128
+ queryInputRef.current = queryInput;
118
129
  useEffect(() => {
119
130
  mountedRef.current = true;
120
131
  return () => {
@@ -122,6 +133,12 @@ function useLazyQuery(query) {
122
133
  };
123
134
  }, []);
124
135
  const execute = useCallback(async () => {
136
+ const query = resolveQuery(queryInputRef.current);
137
+ if (query == null) {
138
+ setData(null);
139
+ setLoading(false);
140
+ return null;
141
+ }
125
142
  setLoading(true);
126
143
  setError(null);
127
144
  try {
@@ -141,7 +158,7 @@ function useLazyQuery(query) {
141
158
  setLoading(false);
142
159
  }
143
160
  }
144
- }, [query]);
161
+ }, []);
145
162
  const reset = useCallback(() => {
146
163
  setLoading(false);
147
164
  setError(null);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sylphx/lens-react",
3
- "version": "1.0.5",
3
+ "version": "1.2.1",
4
4
  "description": "React bindings for Lens API framework",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -29,7 +29,7 @@
29
29
  "author": "SylphxAI",
30
30
  "license": "MIT",
31
31
  "dependencies": {
32
- "@sylphx/lens-client": "^1.0.4"
32
+ "@sylphx/lens-client": "^1.0.5"
33
33
  },
34
34
  "peerDependencies": {
35
35
  "react": ">=18.0.0"
package/src/hooks.ts CHANGED
@@ -25,7 +25,18 @@
25
25
  */
26
26
 
27
27
  import type { MutationResult, QueryResult } from "@sylphx/lens-client";
28
- import { useCallback, useEffect, useRef, useState } from "react";
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
40
 
30
41
  // =============================================================================
31
42
  // Types
@@ -67,10 +78,15 @@ export interface UseQueryOptions {
67
78
  // useQuery Hook
68
79
  // =============================================================================
69
80
 
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
+
70
86
  /**
71
87
  * Subscribe to a query with reactive updates
72
88
  *
73
- * @param query - QueryResult from client API call
89
+ * @param queryInput - QueryResult, null/undefined, or accessor function returning QueryResult
74
90
  * @param options - Query options
75
91
  *
76
92
  * @example
@@ -87,14 +103,23 @@ export interface UseQueryOptions {
87
103
  * return <h1>{user.name}</h1>;
88
104
  * }
89
105
  *
90
- * // With select (type-safe field selection)
91
- * function UserName({ userId }: { userId: string }) {
106
+ * // Conditional query (null when condition not met)
107
+ * function SessionInfo({ sessionId }: { sessionId: string | null }) {
92
108
  * const client = useLensClient();
93
109
  * const { data } = useQuery(
94
- * client.user.get({ id: userId }).select({ name: true })
110
+ * sessionId ? client.session.get({ id: sessionId }) : null
95
111
  * );
96
- * // data is { name: string } | null
97
- * return <span>{data?.name}</span>;
112
+ * // data is null when sessionId is null
113
+ * return <span>{data?.totalTokens}</span>;
114
+ * }
115
+ *
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
121
+ * );
122
+ * return <span>{data?.totalTokens}</span>;
98
123
  * }
99
124
  *
100
125
  * // Skip query conditionally
@@ -104,20 +129,30 @@ export interface UseQueryOptions {
104
129
  * }
105
130
  * ```
106
131
  */
107
- export function useQuery<T>(query: QueryResult<T>, options?: UseQueryOptions): UseQueryResult<T> {
132
+ export function useQuery<T>(queryInput: QueryInput<T>, options?: UseQueryOptions): UseQueryResult<T> {
133
+ // Resolve query (handles accessor functions)
134
+ const query = useMemo(() => resolveQuery(queryInput), [queryInput]);
135
+
108
136
  const [data, setData] = useState<T | null>(null);
109
- const [loading, setLoading] = useState(!options?.skip);
137
+ const [loading, setLoading] = useState(!options?.skip && query != null);
110
138
  const [error, setError] = useState<Error | null>(null);
111
139
 
112
140
  // Track mounted state
113
141
  const mountedRef = useRef(true);
114
142
 
143
+ // Store query ref for refetch
144
+ const queryRef = useRef(query);
145
+ queryRef.current = query;
146
+
115
147
  // Subscribe to query
116
148
  useEffect(() => {
117
149
  mountedRef.current = true;
118
150
 
119
- if (options?.skip) {
151
+ // Handle null/undefined query
152
+ if (query == null || options?.skip) {
153
+ setData(null);
120
154
  setLoading(false);
155
+ setError(null);
121
156
  return;
122
157
  }
123
158
 
@@ -156,12 +191,13 @@ export function useQuery<T>(query: QueryResult<T>, options?: UseQueryOptions): U
156
191
 
157
192
  // Refetch function
158
193
  const refetch = useCallback(() => {
159
- if (options?.skip) return;
194
+ const currentQuery = queryRef.current;
195
+ if (currentQuery == null || options?.skip) return;
160
196
 
161
197
  setLoading(true);
162
198
  setError(null);
163
199
 
164
- query.then(
200
+ currentQuery.then(
165
201
  (value) => {
166
202
  if (mountedRef.current) {
167
203
  setData(value);
@@ -175,7 +211,7 @@ export function useQuery<T>(query: QueryResult<T>, options?: UseQueryOptions): U
175
211
  }
176
212
  },
177
213
  );
178
- }, [query, options?.skip]);
214
+ }, [options?.skip]);
179
215
 
180
216
  return { data, loading, error, refetch };
181
217
  }
@@ -309,7 +345,7 @@ export interface UseLazyQueryResult<T> {
309
345
  /**
310
346
  * Execute a query on demand (not on mount)
311
347
  *
312
- * @param query - QueryResult from client API call
348
+ * @param queryInput - QueryResult, null/undefined, or accessor function returning QueryResult
313
349
  *
314
350
  * @example
315
351
  * ```tsx
@@ -338,9 +374,18 @@ export interface UseLazyQueryResult<T> {
338
374
  * </div>
339
375
  * );
340
376
  * }
377
+ *
378
+ * // With accessor function
379
+ * function LazyReactiveQuery({ sessionId }: { sessionId: Signal<string | null> }) {
380
+ * const client = useLensClient();
381
+ * const { execute, data } = useLazyQuery(() =>
382
+ * sessionId.value ? client.session.get({ id: sessionId.value }) : null
383
+ * );
384
+ * return <button onClick={execute}>Load</button>;
385
+ * }
341
386
  * ```
342
387
  */
343
- export function useLazyQuery<T>(query: QueryResult<T>): UseLazyQueryResult<T> {
388
+ export function useLazyQuery<T>(queryInput: QueryInput<T>): UseLazyQueryResult<T> {
344
389
  const [data, setData] = useState<T | null>(null);
345
390
  const [loading, setLoading] = useState(false);
346
391
  const [error, setError] = useState<Error | null>(null);
@@ -348,6 +393,10 @@ export function useLazyQuery<T>(query: QueryResult<T>): UseLazyQueryResult<T> {
348
393
  // Track mounted state
349
394
  const mountedRef = useRef(true);
350
395
 
396
+ // Store queryInput ref for execute (so it uses latest value)
397
+ const queryInputRef = useRef(queryInput);
398
+ queryInputRef.current = queryInput;
399
+
351
400
  useEffect(() => {
352
401
  mountedRef.current = true;
353
402
  return () => {
@@ -357,6 +406,14 @@ export function useLazyQuery<T>(query: QueryResult<T>): UseLazyQueryResult<T> {
357
406
 
358
407
  // Execute function
359
408
  const execute = useCallback(async (): Promise<T> => {
409
+ const query = resolveQuery(queryInputRef.current);
410
+
411
+ if (query == null) {
412
+ setData(null);
413
+ setLoading(false);
414
+ return null as T;
415
+ }
416
+
360
417
  setLoading(true);
361
418
  setError(null);
362
419
 
@@ -379,7 +436,7 @@ export function useLazyQuery<T>(query: QueryResult<T>): UseLazyQueryResult<T> {
379
436
  setLoading(false);
380
437
  }
381
438
  }
382
- }, [query]);
439
+ }, []);
383
440
 
384
441
  // Reset function
385
442
  const reset = useCallback(() => {
package/src/index.ts CHANGED
@@ -22,6 +22,7 @@ export {
22
22
  // Mutation hook
23
23
  useMutation,
24
24
  // Types
25
+ type QueryInput,
25
26
  type UseQueryResult,
26
27
  type UseLazyQueryResult,
27
28
  type UseMutationResult,