@sylphx/lens-react 2.3.0 → 2.3.2
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 +33 -47
- package/dist/index.js +52 -100
- package/package.json +3 -3
- package/src/create.tsx +90 -219
- package/src/index.ts +6 -7
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { LensClientConfig, SelectionObject, TypedClientConfig } from "@sylphx/lens-client";
|
|
1
|
+
import { LensClientConfig, QueryResult, SelectionObject, TypedClientConfig } from "@sylphx/lens-client";
|
|
2
2
|
import { MutationDef, QueryDef, RouterDef, RouterRoutes } from "@sylphx/lens-core";
|
|
3
3
|
/** Query hook options */
|
|
4
4
|
interface QueryHookOptions<TInput> {
|
|
@@ -48,34 +48,33 @@ interface MutationHookResult<
|
|
|
48
48
|
/** Reset mutation state */
|
|
49
49
|
reset: () => void;
|
|
50
50
|
}
|
|
51
|
-
/** Query endpoint
|
|
51
|
+
/** Query endpoint with React hooks */
|
|
52
52
|
interface QueryEndpoint<
|
|
53
53
|
TInput,
|
|
54
54
|
TOutput
|
|
55
55
|
> {
|
|
56
|
-
/**
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
input: TInput;
|
|
64
|
-
select?: TSelect;
|
|
65
|
-
}) => Promise<TOutput>;
|
|
56
|
+
/** Vanilla JS call - returns QueryResult (Promise + Observable) */
|
|
57
|
+
(options?: {
|
|
58
|
+
input?: TInput;
|
|
59
|
+
select?: SelectionObject;
|
|
60
|
+
}): QueryResult<TOutput>;
|
|
61
|
+
/** React hook for reactive queries */
|
|
62
|
+
useQuery: (options?: TInput extends void ? QueryHookOptions<void> | void : QueryHookOptions<TInput>) => QueryHookResult<TOutput>;
|
|
66
63
|
}
|
|
67
|
-
/** Mutation endpoint
|
|
64
|
+
/** Mutation endpoint with React hooks */
|
|
68
65
|
interface MutationEndpoint<
|
|
69
66
|
TInput,
|
|
70
67
|
TOutput
|
|
71
68
|
> {
|
|
72
|
-
/**
|
|
73
|
-
(options
|
|
74
|
-
/** Promise call (SSR) */
|
|
75
|
-
fetch: <TSelect extends SelectionObject = Record<string, never>>(options: {
|
|
69
|
+
/** Vanilla JS call - returns Promise */
|
|
70
|
+
(options: {
|
|
76
71
|
input: TInput;
|
|
77
|
-
select?:
|
|
78
|
-
})
|
|
72
|
+
select?: SelectionObject;
|
|
73
|
+
}): Promise<{
|
|
74
|
+
data: TOutput;
|
|
75
|
+
}>;
|
|
76
|
+
/** React hook for mutations */
|
|
77
|
+
useMutation: (options?: MutationHookOptions<TOutput>) => MutationHookResult<TInput, TOutput>;
|
|
79
78
|
}
|
|
80
79
|
/** Infer client type from router routes */
|
|
81
80
|
type InferTypedClient<TRoutes extends RouterRoutes> = { [K in keyof TRoutes] : TRoutes[K] extends RouterDef<infer TNestedRoutes> ? InferTypedClient<TNestedRoutes> : TRoutes[K] extends QueryDef<infer TInput, infer TOutput> ? QueryEndpoint<TInput, TOutput> : TRoutes[K] extends MutationDef<infer TInput, infer TOutput> ? MutationEndpoint<TInput, TOutput> : never };
|
|
@@ -84,9 +83,8 @@ type TypedClient<TRouter extends RouterDef> = TRouter extends RouterDef<infer TR
|
|
|
84
83
|
/**
|
|
85
84
|
* Create a Lens client with React hooks.
|
|
86
85
|
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
* - Via .fetch() for promises: `await client.user.get.fetch({ input: { id } })`
|
|
86
|
+
* Base client methods work in vanilla JS (SSR, utilities, event handlers).
|
|
87
|
+
* React hooks are available as `.useQuery()` and `.useMutation()`.
|
|
90
88
|
*
|
|
91
89
|
* @example
|
|
92
90
|
* ```tsx
|
|
@@ -99,20 +97,14 @@ type TypedClient<TRouter extends RouterDef> = TRouter extends RouterDef<infer TR
|
|
|
99
97
|
* transport: httpTransport({ url: '/api/lens' }),
|
|
100
98
|
* });
|
|
101
99
|
*
|
|
102
|
-
* //
|
|
103
|
-
*
|
|
104
|
-
* // Query hook - auto-subscribes
|
|
105
|
-
* const { data, loading, error } = client.user.get({
|
|
106
|
-
* input: { id },
|
|
107
|
-
* select: { name: true },
|
|
108
|
-
* });
|
|
100
|
+
* // Vanilla JS (anywhere)
|
|
101
|
+
* const user = await client.user.get({ input: { id } });
|
|
109
102
|
*
|
|
110
|
-
*
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
* });
|
|
103
|
+
* // React component
|
|
104
|
+
* function UserProfile({ id }: { id: string }) {
|
|
105
|
+
* const { data, loading } = client.user.get.useQuery({ input: { id } });
|
|
106
|
+
* const { mutate } = client.user.update.useMutation();
|
|
114
107
|
*
|
|
115
|
-
* if (loading) return <Spinner />;
|
|
116
108
|
* return (
|
|
117
109
|
* <div>
|
|
118
110
|
* <h1>{data?.name}</h1>
|
|
@@ -122,12 +114,6 @@ type TypedClient<TRouter extends RouterDef> = TRouter extends RouterDef<infer TR
|
|
|
122
114
|
* </div>
|
|
123
115
|
* );
|
|
124
116
|
* }
|
|
125
|
-
*
|
|
126
|
-
* // SSR usage
|
|
127
|
-
* async function UserPage({ id }: { id: string }) {
|
|
128
|
-
* const user = await client.user.get.fetch({ input: { id } });
|
|
129
|
-
* return <div>{user.name}</div>;
|
|
130
|
-
* }
|
|
131
117
|
* ```
|
|
132
118
|
*/
|
|
133
119
|
declare function createClient<TRouter extends RouterDef>(config: LensClientConfig | TypedClientConfig<{
|
|
@@ -179,7 +165,7 @@ declare function LensProvider({ client, children }: LensProviderProps): ReactEle
|
|
|
179
165
|
* ```
|
|
180
166
|
*/
|
|
181
167
|
declare function useLensClient<TRouter = any>(): LensClient<any, any> & TRouter;
|
|
182
|
-
import { LensClient as LensClient2, MutationResult, QueryResult } from "@sylphx/lens-client";
|
|
168
|
+
import { LensClient as LensClient2, MutationResult, QueryResult as QueryResult2 } from "@sylphx/lens-client";
|
|
183
169
|
import { DependencyList } from "react";
|
|
184
170
|
/** Result of useQuery hook */
|
|
185
171
|
interface UseQueryResult<T> {
|
|
@@ -224,9 +210,9 @@ type Client = LensClient2<any, any>;
|
|
|
224
210
|
type RouteSelector<
|
|
225
211
|
TParams,
|
|
226
212
|
TResult
|
|
227
|
-
> = (client: Client) => ((params: TParams) =>
|
|
213
|
+
> = (client: Client) => ((params: TParams) => QueryResult2<TResult>) | null;
|
|
228
214
|
/** Query accessor selector - callback that returns QueryResult */
|
|
229
|
-
type QuerySelector<TResult> = (client: Client) =>
|
|
215
|
+
type QuerySelector<TResult> = (client: Client) => QueryResult2<TResult> | null | undefined;
|
|
230
216
|
/** Mutation selector - callback that returns mutation function */
|
|
231
217
|
type MutationSelector<
|
|
232
218
|
TInput,
|
|
@@ -299,12 +285,12 @@ type MutationSelector<
|
|
|
299
285
|
* }
|
|
300
286
|
* ```
|
|
301
287
|
*/
|
|
302
|
-
declare function
|
|
288
|
+
declare function useQuery2<
|
|
303
289
|
TParams,
|
|
304
290
|
TResult,
|
|
305
291
|
TSelected = TResult
|
|
306
292
|
>(selector: RouteSelector<TParams, TResult>, params: TParams, options?: UseQueryOptions<TResult, TSelected>): UseQueryResult<TSelected>;
|
|
307
|
-
declare function
|
|
293
|
+
declare function useQuery2<
|
|
308
294
|
TResult,
|
|
309
295
|
TSelected = TResult
|
|
310
296
|
>(selector: QuerySelector<TResult>, deps: DependencyList, options?: UseQueryOptions<TResult, TSelected>): UseQueryResult<TSelected>;
|
|
@@ -354,7 +340,7 @@ declare function useQuery<
|
|
|
354
340
|
* }
|
|
355
341
|
* \`\`\`
|
|
356
342
|
*/
|
|
357
|
-
declare function
|
|
343
|
+
declare function useMutation2<
|
|
358
344
|
TInput,
|
|
359
345
|
TOutput
|
|
360
346
|
>(selector: MutationSelector<TInput, TOutput>): UseMutationResult<TInput, TOutput>;
|
|
@@ -413,4 +399,4 @@ declare function useLazyQuery<
|
|
|
413
399
|
TResult,
|
|
414
400
|
TSelected = TResult
|
|
415
401
|
>(selector: QuerySelector<TResult>, deps: DependencyList, options?: UseQueryOptions<TResult, TSelected>): UseLazyQueryResult<TSelected>;
|
|
416
|
-
export { useQuery, useMutation, useLensClient, useLazyQuery, createClient, UseQueryResult, UseQueryOptions, UseMutationResult, UseLazyQueryResult, TypedClient, RouteSelector, QuerySelector, QueryHookResult, QueryHookOptions, QueryEndpoint, MutationSelector, MutationHookResult, MutationHookOptions, MutationEndpoint, LensProviderProps, LensProvider };
|
|
402
|
+
export { useQuery2 as useQuery, useMutation2 as useMutation, useLensClient, useLazyQuery, createClient, UseQueryResult, UseQueryOptions, UseMutationResult, UseLazyQueryResult, TypedClient, RouteSelector, QuerySelector, QueryHookResult, QueryHookOptions, QueryEndpoint, MutationSelector, MutationHookResult, MutationHookOptions, MutationEndpoint, LensProviderProps, LensProvider };
|
package/dist/index.js
CHANGED
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
import {
|
|
3
3
|
createClient as createBaseClient
|
|
4
4
|
} from "@sylphx/lens-client";
|
|
5
|
-
import { useCallback, useEffect, useMemo, useReducer, useRef } from "react";
|
|
6
|
-
import * as React from "react";
|
|
5
|
+
import { useCallback, useEffect, useMemo, useReducer, useRef, useState } from "react";
|
|
7
6
|
function queryReducer(state, action) {
|
|
8
7
|
switch (action.type) {
|
|
9
8
|
case "RESET":
|
|
@@ -20,24 +19,14 @@ function queryReducer(state, action) {
|
|
|
20
19
|
return state;
|
|
21
20
|
}
|
|
22
21
|
}
|
|
23
|
-
function
|
|
24
|
-
|
|
25
|
-
const getEndpoint = (p) => {
|
|
26
|
-
const parts = p.split(".");
|
|
27
|
-
let current = baseClient;
|
|
28
|
-
for (const part of parts) {
|
|
29
|
-
current = current[part];
|
|
30
|
-
}
|
|
31
|
-
return current;
|
|
32
|
-
};
|
|
33
|
-
const useQueryHook = (options) => {
|
|
34
|
-
const _optionsKey = JSON.stringify(options ?? {});
|
|
22
|
+
function createUseQueryHook(getEndpoint) {
|
|
23
|
+
return function useQuery(options) {
|
|
35
24
|
const query = useMemo(() => {
|
|
36
25
|
if (options?.skip)
|
|
37
26
|
return null;
|
|
38
|
-
const
|
|
39
|
-
return
|
|
40
|
-
}, [options?.input, options?.select, options?.skip,
|
|
27
|
+
const endpoint = getEndpoint();
|
|
28
|
+
return endpoint({ input: options?.input, select: options?.select });
|
|
29
|
+
}, [options?.input, options?.select, options?.skip, getEndpoint]);
|
|
41
30
|
const initialState = {
|
|
42
31
|
data: null,
|
|
43
32
|
loading: query != null && !options?.skip,
|
|
@@ -102,30 +91,12 @@ function createQueryHook(baseClient, path) {
|
|
|
102
91
|
}, []);
|
|
103
92
|
return { data: state.data, loading: state.loading, error: state.error, refetch };
|
|
104
93
|
};
|
|
105
|
-
const fetch = async (options) => {
|
|
106
|
-
const endpoint2 = getEndpoint(path);
|
|
107
|
-
const queryResult = endpoint2({ input: options?.input, select: options?.select });
|
|
108
|
-
return queryResult.then((data) => data);
|
|
109
|
-
};
|
|
110
|
-
const endpoint = useQueryHook;
|
|
111
|
-
endpoint.fetch = fetch;
|
|
112
|
-
cachedHook = endpoint;
|
|
113
|
-
return cachedHook;
|
|
114
94
|
}
|
|
115
|
-
function
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const
|
|
119
|
-
|
|
120
|
-
for (const part of parts) {
|
|
121
|
-
current = current[part];
|
|
122
|
-
}
|
|
123
|
-
return current;
|
|
124
|
-
};
|
|
125
|
-
const useMutationHook = (hookOptions) => {
|
|
126
|
-
const [loading, setLoading] = React.useState(false);
|
|
127
|
-
const [error, setError] = React.useState(null);
|
|
128
|
-
const [data, setData] = React.useState(null);
|
|
95
|
+
function createUseMutationHook(getEndpoint) {
|
|
96
|
+
return function useMutation(hookOptions) {
|
|
97
|
+
const [loading, setLoading] = useState(false);
|
|
98
|
+
const [error, setError] = useState(null);
|
|
99
|
+
const [data, setData] = useState(null);
|
|
129
100
|
const mountedRef = useRef(true);
|
|
130
101
|
const hookOptionsRef = useRef(hookOptions);
|
|
131
102
|
hookOptionsRef.current = hookOptions;
|
|
@@ -139,16 +110,15 @@ function createMutationHook(baseClient, path) {
|
|
|
139
110
|
setLoading(true);
|
|
140
111
|
setError(null);
|
|
141
112
|
try {
|
|
142
|
-
const
|
|
143
|
-
const result = await
|
|
144
|
-
const mutationResult = result;
|
|
113
|
+
const endpoint = getEndpoint();
|
|
114
|
+
const result = await endpoint({ input: options.input, select: options.select });
|
|
145
115
|
if (mountedRef.current) {
|
|
146
|
-
setData(
|
|
116
|
+
setData(result.data);
|
|
147
117
|
setLoading(false);
|
|
148
118
|
}
|
|
149
|
-
hookOptionsRef.current?.onSuccess?.(
|
|
119
|
+
hookOptionsRef.current?.onSuccess?.(result.data);
|
|
150
120
|
hookOptionsRef.current?.onSettled?.();
|
|
151
|
-
return
|
|
121
|
+
return result.data;
|
|
152
122
|
} catch (err) {
|
|
153
123
|
const mutationError = err instanceof Error ? err : new Error(String(err));
|
|
154
124
|
if (mountedRef.current) {
|
|
@@ -159,7 +129,7 @@ function createMutationHook(baseClient, path) {
|
|
|
159
129
|
hookOptionsRef.current?.onSettled?.();
|
|
160
130
|
throw mutationError;
|
|
161
131
|
}
|
|
162
|
-
}, [
|
|
132
|
+
}, [getEndpoint]);
|
|
163
133
|
const reset = useCallback(() => {
|
|
164
134
|
setLoading(false);
|
|
165
135
|
setError(null);
|
|
@@ -167,46 +137,45 @@ function createMutationHook(baseClient, path) {
|
|
|
167
137
|
}, []);
|
|
168
138
|
return { mutate, loading, error, data, reset };
|
|
169
139
|
};
|
|
170
|
-
const fetch = async (options) => {
|
|
171
|
-
const endpoint2 = getEndpoint(path);
|
|
172
|
-
const result = await endpoint2({ input: options.input, select: options.select });
|
|
173
|
-
const mutationResult = result;
|
|
174
|
-
return mutationResult.data;
|
|
175
|
-
};
|
|
176
|
-
const endpoint = useMutationHook;
|
|
177
|
-
endpoint.fetch = fetch;
|
|
178
|
-
cachedHook = endpoint;
|
|
179
|
-
return cachedHook;
|
|
180
140
|
}
|
|
181
141
|
var hookCache = new Map;
|
|
182
142
|
function createClient(config) {
|
|
183
143
|
const baseClient = createBaseClient(config);
|
|
184
|
-
const _endpointTypes = new Map;
|
|
185
144
|
function createProxy(path) {
|
|
186
|
-
const cacheKey = path;
|
|
187
|
-
if (hookCache.has(cacheKey)) {
|
|
188
|
-
return hookCache.get(cacheKey);
|
|
189
|
-
}
|
|
190
145
|
const handler = {
|
|
191
146
|
get(_target, prop) {
|
|
192
147
|
if (typeof prop === "symbol")
|
|
193
148
|
return;
|
|
194
149
|
const key = prop;
|
|
195
|
-
if (key === "
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
current =
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
150
|
+
if (key === "useQuery") {
|
|
151
|
+
const cacheKey = `${path}:useQuery`;
|
|
152
|
+
if (!hookCache.has(cacheKey)) {
|
|
153
|
+
const getEndpoint = () => {
|
|
154
|
+
const parts = path.split(".");
|
|
155
|
+
let current = baseClient;
|
|
156
|
+
for (const part of parts) {
|
|
157
|
+
current = current[part];
|
|
158
|
+
}
|
|
159
|
+
return current;
|
|
160
|
+
};
|
|
161
|
+
hookCache.set(cacheKey, createUseQueryHook(getEndpoint));
|
|
162
|
+
}
|
|
163
|
+
return hookCache.get(cacheKey);
|
|
164
|
+
}
|
|
165
|
+
if (key === "useMutation") {
|
|
166
|
+
const cacheKey = `${path}:useMutation`;
|
|
167
|
+
if (!hookCache.has(cacheKey)) {
|
|
168
|
+
const getEndpoint = () => {
|
|
169
|
+
const parts = path.split(".");
|
|
170
|
+
let current = baseClient;
|
|
171
|
+
for (const part of parts) {
|
|
172
|
+
current = current[part];
|
|
173
|
+
}
|
|
174
|
+
return current;
|
|
175
|
+
};
|
|
176
|
+
hookCache.set(cacheKey, createUseMutationHook(getEndpoint));
|
|
177
|
+
}
|
|
178
|
+
return hookCache.get(cacheKey);
|
|
210
179
|
}
|
|
211
180
|
if (key === "then")
|
|
212
181
|
return;
|
|
@@ -216,30 +185,13 @@ function createClient(config) {
|
|
|
216
185
|
return createProxy(newPath);
|
|
217
186
|
},
|
|
218
187
|
apply(_target, _thisArg, args) {
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
const cacheKeyMutation = `${path}:mutation`;
|
|
224
|
-
if (isQueryOptions) {
|
|
225
|
-
if (!hookCache.has(cacheKeyQuery)) {
|
|
226
|
-
hookCache.set(cacheKeyQuery, createQueryHook(baseClient, path));
|
|
227
|
-
}
|
|
228
|
-
const hook2 = hookCache.get(cacheKeyQuery);
|
|
229
|
-
return hook2(options);
|
|
230
|
-
}
|
|
231
|
-
if (isMutationOptions) {
|
|
232
|
-
if (!hookCache.has(cacheKeyMutation)) {
|
|
233
|
-
hookCache.set(cacheKeyMutation, createMutationHook(baseClient, path));
|
|
234
|
-
}
|
|
235
|
-
const hook2 = hookCache.get(cacheKeyMutation);
|
|
236
|
-
return hook2(options);
|
|
237
|
-
}
|
|
238
|
-
if (!hookCache.has(cacheKeyQuery)) {
|
|
239
|
-
hookCache.set(cacheKeyQuery, createQueryHook(baseClient, path));
|
|
188
|
+
const parts = path.split(".");
|
|
189
|
+
let current = baseClient;
|
|
190
|
+
for (const part of parts) {
|
|
191
|
+
current = current[part];
|
|
240
192
|
}
|
|
241
|
-
const
|
|
242
|
-
return
|
|
193
|
+
const endpoint = current;
|
|
194
|
+
return endpoint(args[0]);
|
|
243
195
|
}
|
|
244
196
|
};
|
|
245
197
|
const proxy = new Proxy(() => {}, handler);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sylphx/lens-react",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.2",
|
|
4
4
|
"description": "React bindings for Lens API framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -30,8 +30,8 @@
|
|
|
30
30
|
"author": "SylphxAI",
|
|
31
31
|
"license": "MIT",
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@sylphx/lens-client": "^2.
|
|
34
|
-
"@sylphx/lens-core": "^2.
|
|
33
|
+
"@sylphx/lens-client": "^2.3.0",
|
|
34
|
+
"@sylphx/lens-core": "^2.2.0"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
37
|
"react": ">=18.0.0"
|
package/src/create.tsx
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* @sylphx/lens-react - Create Client
|
|
3
3
|
*
|
|
4
4
|
* Creates a typed Lens client with React hooks.
|
|
5
|
-
*
|
|
5
|
+
* Base client methods work in vanilla JS, hooks are extensions.
|
|
6
6
|
*
|
|
7
7
|
* @example
|
|
8
8
|
* ```tsx
|
|
@@ -15,14 +15,13 @@
|
|
|
15
15
|
* transport: httpTransport({ url: '/api/lens' }),
|
|
16
16
|
* });
|
|
17
17
|
*
|
|
18
|
-
* //
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* return <div>{data?.name}</div>;
|
|
22
|
-
* }
|
|
18
|
+
* // Vanilla JS (anywhere - SSR, utilities, event handlers)
|
|
19
|
+
* const user = await client.user.get({ input: { id } });
|
|
20
|
+
* client.user.get({ input: { id } }).subscribe(data => console.log(data));
|
|
23
21
|
*
|
|
24
|
-
* //
|
|
25
|
-
* const
|
|
22
|
+
* // React hooks (in components)
|
|
23
|
+
* const { data, loading } = client.user.get.useQuery({ input: { id } });
|
|
24
|
+
* const { mutate, loading } = client.user.create.useMutation();
|
|
26
25
|
* ```
|
|
27
26
|
*/
|
|
28
27
|
|
|
@@ -34,7 +33,7 @@ import {
|
|
|
34
33
|
type TypedClientConfig,
|
|
35
34
|
} from "@sylphx/lens-client";
|
|
36
35
|
import type { MutationDef, QueryDef, RouterDef, RouterRoutes } from "@sylphx/lens-core";
|
|
37
|
-
import { useCallback, useEffect, useMemo, useReducer, useRef } from "react";
|
|
36
|
+
import { useCallback, useEffect, useMemo, useReducer, useRef, useState } from "react";
|
|
38
37
|
|
|
39
38
|
// =============================================================================
|
|
40
39
|
// Types
|
|
@@ -86,31 +85,24 @@ export interface MutationHookResult<TInput, TOutput> {
|
|
|
86
85
|
reset: () => void;
|
|
87
86
|
}
|
|
88
87
|
|
|
89
|
-
/** Query endpoint
|
|
88
|
+
/** Query endpoint with React hooks */
|
|
90
89
|
export interface QueryEndpoint<TInput, TOutput> {
|
|
91
|
-
/**
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
options: TInput extends void
|
|
99
|
-
? { input?: void; select?: TSelect } | void
|
|
100
|
-
: { input: TInput; select?: TSelect },
|
|
101
|
-
) => Promise<TOutput>;
|
|
90
|
+
/** Vanilla JS call - returns QueryResult (Promise + Observable) */
|
|
91
|
+
(options?: { input?: TInput; select?: SelectionObject }): QueryResult<TOutput>;
|
|
92
|
+
|
|
93
|
+
/** React hook for reactive queries */
|
|
94
|
+
useQuery: (
|
|
95
|
+
options?: TInput extends void ? QueryHookOptions<void> | void : QueryHookOptions<TInput>,
|
|
96
|
+
) => QueryHookResult<TOutput>;
|
|
102
97
|
}
|
|
103
98
|
|
|
104
|
-
/** Mutation endpoint
|
|
99
|
+
/** Mutation endpoint with React hooks */
|
|
105
100
|
export interface MutationEndpoint<TInput, TOutput> {
|
|
106
|
-
/**
|
|
107
|
-
(options?:
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
|
|
111
|
-
input: TInput;
|
|
112
|
-
select?: TSelect;
|
|
113
|
-
}) => Promise<TOutput>;
|
|
101
|
+
/** Vanilla JS call - returns Promise */
|
|
102
|
+
(options: { input: TInput; select?: SelectionObject }): Promise<{ data: TOutput }>;
|
|
103
|
+
|
|
104
|
+
/** React hook for mutations */
|
|
105
|
+
useMutation: (options?: MutationHookOptions<TOutput>) => MutationHookResult<TInput, TOutput>;
|
|
114
106
|
}
|
|
115
107
|
|
|
116
108
|
/** Infer client type from router routes */
|
|
@@ -167,33 +159,18 @@ function queryReducer<T>(state: QueryState<T>, action: QueryAction<T>): QuerySta
|
|
|
167
159
|
// =============================================================================
|
|
168
160
|
|
|
169
161
|
/**
|
|
170
|
-
* Create
|
|
162
|
+
* Create useQuery hook for a specific endpoint
|
|
171
163
|
*/
|
|
172
|
-
function
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
):
|
|
176
|
-
// Cache for stable hook reference
|
|
177
|
-
let cachedHook: QueryEndpoint<TInput, TOutput> | null = null;
|
|
178
|
-
|
|
179
|
-
const getEndpoint = (p: string) => {
|
|
180
|
-
const parts = p.split(".");
|
|
181
|
-
let current: unknown = baseClient;
|
|
182
|
-
for (const part of parts) {
|
|
183
|
-
current = (current as Record<string, unknown>)[part];
|
|
184
|
-
}
|
|
185
|
-
return current as (options: unknown) => QueryResult<TOutput>;
|
|
186
|
-
};
|
|
187
|
-
|
|
188
|
-
const useQueryHook = (options?: QueryHookOptions<TInput>): QueryHookResult<TOutput> => {
|
|
189
|
-
const _optionsKey = JSON.stringify(options ?? {});
|
|
190
|
-
|
|
164
|
+
function createUseQueryHook<TInput, TOutput>(
|
|
165
|
+
getEndpoint: () => (options: unknown) => QueryResult<TOutput>,
|
|
166
|
+
) {
|
|
167
|
+
return function useQuery(options?: QueryHookOptions<TInput>): QueryHookResult<TOutput> {
|
|
191
168
|
// Get query result from base client
|
|
192
169
|
const query = useMemo(() => {
|
|
193
170
|
if (options?.skip) return null;
|
|
194
|
-
const endpoint = getEndpoint(
|
|
171
|
+
const endpoint = getEndpoint();
|
|
195
172
|
return endpoint({ input: options?.input, select: options?.select });
|
|
196
|
-
}, [options?.input, options?.select, options?.skip,
|
|
173
|
+
}, [options?.input, options?.select, options?.skip, getEndpoint]);
|
|
197
174
|
|
|
198
175
|
// State management
|
|
199
176
|
const initialState: QueryState<TOutput> = {
|
|
@@ -280,49 +257,20 @@ function createQueryHook<TInput, TOutput>(
|
|
|
280
257
|
|
|
281
258
|
return { data: state.data, loading: state.loading, error: state.error, refetch };
|
|
282
259
|
};
|
|
283
|
-
|
|
284
|
-
// Fetch method for promises (SSR)
|
|
285
|
-
const fetch = async (options?: {
|
|
286
|
-
input?: TInput;
|
|
287
|
-
select?: SelectionObject;
|
|
288
|
-
}): Promise<TOutput> => {
|
|
289
|
-
const endpoint = getEndpoint(path);
|
|
290
|
-
const queryResult = endpoint({ input: options?.input, select: options?.select });
|
|
291
|
-
return queryResult.then((data) => data);
|
|
292
|
-
};
|
|
293
|
-
|
|
294
|
-
// Create the endpoint object with hook + fetch
|
|
295
|
-
const endpoint = useQueryHook as unknown as QueryEndpoint<TInput, TOutput>;
|
|
296
|
-
endpoint.fetch = fetch as QueryEndpoint<TInput, TOutput>["fetch"];
|
|
297
|
-
|
|
298
|
-
cachedHook = endpoint;
|
|
299
|
-
return cachedHook;
|
|
300
260
|
}
|
|
301
261
|
|
|
302
262
|
/**
|
|
303
|
-
* Create
|
|
263
|
+
* Create useMutation hook for a specific endpoint
|
|
304
264
|
*/
|
|
305
|
-
function
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
let cachedHook: MutationEndpoint<TInput, TOutput> | null = null;
|
|
310
|
-
|
|
311
|
-
const getEndpoint = (p: string) => {
|
|
312
|
-
const parts = p.split(".");
|
|
313
|
-
let current: unknown = baseClient;
|
|
314
|
-
for (const part of parts) {
|
|
315
|
-
current = (current as Record<string, unknown>)[part];
|
|
316
|
-
}
|
|
317
|
-
return current as (options: unknown) => QueryResult<{ data: TOutput }>;
|
|
318
|
-
};
|
|
319
|
-
|
|
320
|
-
const useMutationHook = (
|
|
265
|
+
function createUseMutationHook<TInput, TOutput>(
|
|
266
|
+
getEndpoint: () => (options: unknown) => Promise<{ data: TOutput }>,
|
|
267
|
+
) {
|
|
268
|
+
return function useMutation(
|
|
321
269
|
hookOptions?: MutationHookOptions<TOutput>,
|
|
322
|
-
): MutationHookResult<TInput, TOutput>
|
|
323
|
-
const [loading, setLoading] =
|
|
324
|
-
const [error, setError] =
|
|
325
|
-
const [data, setData] =
|
|
270
|
+
): MutationHookResult<TInput, TOutput> {
|
|
271
|
+
const [loading, setLoading] = useState(false);
|
|
272
|
+
const [error, setError] = useState<Error | null>(null);
|
|
273
|
+
const [data, setData] = useState<TOutput | null>(null);
|
|
326
274
|
|
|
327
275
|
const mountedRef = useRef(true);
|
|
328
276
|
const hookOptionsRef = useRef(hookOptions);
|
|
@@ -341,19 +289,18 @@ function createMutationHook<TInput, TOutput>(
|
|
|
341
289
|
setError(null);
|
|
342
290
|
|
|
343
291
|
try {
|
|
344
|
-
const endpoint = getEndpoint(
|
|
292
|
+
const endpoint = getEndpoint();
|
|
345
293
|
const result = await endpoint({ input: options.input, select: options.select });
|
|
346
|
-
const mutationResult = result as unknown as { data: TOutput };
|
|
347
294
|
|
|
348
295
|
if (mountedRef.current) {
|
|
349
|
-
setData(
|
|
296
|
+
setData(result.data);
|
|
350
297
|
setLoading(false);
|
|
351
298
|
}
|
|
352
299
|
|
|
353
|
-
hookOptionsRef.current?.onSuccess?.(
|
|
300
|
+
hookOptionsRef.current?.onSuccess?.(result.data);
|
|
354
301
|
hookOptionsRef.current?.onSettled?.();
|
|
355
302
|
|
|
356
|
-
return
|
|
303
|
+
return result.data;
|
|
357
304
|
} catch (err) {
|
|
358
305
|
const mutationError = err instanceof Error ? err : new Error(String(err));
|
|
359
306
|
|
|
@@ -368,7 +315,7 @@ function createMutationHook<TInput, TOutput>(
|
|
|
368
315
|
throw mutationError;
|
|
369
316
|
}
|
|
370
317
|
},
|
|
371
|
-
[
|
|
318
|
+
[getEndpoint],
|
|
372
319
|
);
|
|
373
320
|
|
|
374
321
|
const reset = useCallback(() => {
|
|
@@ -379,28 +326,8 @@ function createMutationHook<TInput, TOutput>(
|
|
|
379
326
|
|
|
380
327
|
return { mutate, loading, error, data, reset };
|
|
381
328
|
};
|
|
382
|
-
|
|
383
|
-
// Fetch method for promises (SSR)
|
|
384
|
-
const fetch = async (options: { input: TInput; select?: SelectionObject }): Promise<TOutput> => {
|
|
385
|
-
const endpoint = getEndpoint(path);
|
|
386
|
-
const result = await endpoint({ input: options.input, select: options.select });
|
|
387
|
-
const mutationResult = result as unknown as { data: TOutput };
|
|
388
|
-
return mutationResult.data;
|
|
389
|
-
};
|
|
390
|
-
|
|
391
|
-
const endpoint = useMutationHook as MutationEndpoint<TInput, TOutput>;
|
|
392
|
-
endpoint.fetch = fetch;
|
|
393
|
-
|
|
394
|
-
cachedHook = endpoint;
|
|
395
|
-
return cachedHook;
|
|
396
329
|
}
|
|
397
330
|
|
|
398
|
-
// =============================================================================
|
|
399
|
-
// React import for useState (needed in mutation hook)
|
|
400
|
-
// =============================================================================
|
|
401
|
-
|
|
402
|
-
import * as React from "react";
|
|
403
|
-
|
|
404
331
|
// =============================================================================
|
|
405
332
|
// Create Client
|
|
406
333
|
// =============================================================================
|
|
@@ -411,9 +338,8 @@ const hookCache = new Map<string, unknown>();
|
|
|
411
338
|
/**
|
|
412
339
|
* Create a Lens client with React hooks.
|
|
413
340
|
*
|
|
414
|
-
*
|
|
415
|
-
*
|
|
416
|
-
* - Via .fetch() for promises: `await client.user.get.fetch({ input: { id } })`
|
|
341
|
+
* Base client methods work in vanilla JS (SSR, utilities, event handlers).
|
|
342
|
+
* React hooks are available as `.useQuery()` and `.useMutation()`.
|
|
417
343
|
*
|
|
418
344
|
* @example
|
|
419
345
|
* ```tsx
|
|
@@ -426,20 +352,14 @@ const hookCache = new Map<string, unknown>();
|
|
|
426
352
|
* transport: httpTransport({ url: '/api/lens' }),
|
|
427
353
|
* });
|
|
428
354
|
*
|
|
429
|
-
* //
|
|
430
|
-
*
|
|
431
|
-
* // Query hook - auto-subscribes
|
|
432
|
-
* const { data, loading, error } = client.user.get({
|
|
433
|
-
* input: { id },
|
|
434
|
-
* select: { name: true },
|
|
435
|
-
* });
|
|
355
|
+
* // Vanilla JS (anywhere)
|
|
356
|
+
* const user = await client.user.get({ input: { id } });
|
|
436
357
|
*
|
|
437
|
-
*
|
|
438
|
-
*
|
|
439
|
-
*
|
|
440
|
-
* });
|
|
358
|
+
* // React component
|
|
359
|
+
* function UserProfile({ id }: { id: string }) {
|
|
360
|
+
* const { data, loading } = client.user.get.useQuery({ input: { id } });
|
|
361
|
+
* const { mutate } = client.user.update.useMutation();
|
|
441
362
|
*
|
|
442
|
-
* if (loading) return <Spinner />;
|
|
443
363
|
* return (
|
|
444
364
|
* <div>
|
|
445
365
|
* <h1>{data?.name}</h1>
|
|
@@ -449,12 +369,6 @@ const hookCache = new Map<string, unknown>();
|
|
|
449
369
|
* </div>
|
|
450
370
|
* );
|
|
451
371
|
* }
|
|
452
|
-
*
|
|
453
|
-
* // SSR usage
|
|
454
|
-
* async function UserPage({ id }: { id: string }) {
|
|
455
|
-
* const user = await client.user.get.fetch({ input: { id } });
|
|
456
|
-
* return <div>{user.name}</div>;
|
|
457
|
-
* }
|
|
458
372
|
* ```
|
|
459
373
|
*/
|
|
460
374
|
export function createClient<TRouter extends RouterDef>(
|
|
@@ -463,50 +377,44 @@ export function createClient<TRouter extends RouterDef>(
|
|
|
463
377
|
// Create base client for transport
|
|
464
378
|
const baseClient = createBaseClient(config as LensClientConfig);
|
|
465
379
|
|
|
466
|
-
// Track endpoint types (query vs mutation) - determined at runtime via metadata
|
|
467
|
-
// For now, we'll detect based on the operation result
|
|
468
|
-
const _endpointTypes = new Map<string, "query" | "mutation">();
|
|
469
|
-
|
|
470
380
|
function createProxy(path: string): unknown {
|
|
471
|
-
const cacheKey = path;
|
|
472
|
-
|
|
473
|
-
// Return cached hook if available
|
|
474
|
-
if (hookCache.has(cacheKey)) {
|
|
475
|
-
return hookCache.get(cacheKey);
|
|
476
|
-
}
|
|
477
|
-
|
|
478
381
|
const handler: ProxyHandler<(...args: unknown[]) => unknown> = {
|
|
479
382
|
get(_target, prop) {
|
|
480
383
|
if (typeof prop === "symbol") return undefined;
|
|
481
384
|
const key = prop as string;
|
|
482
385
|
|
|
483
|
-
// Handle .
|
|
484
|
-
if (key === "
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
const
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
386
|
+
// Handle .useQuery() - React hook for queries
|
|
387
|
+
if (key === "useQuery") {
|
|
388
|
+
const cacheKey = `${path}:useQuery`;
|
|
389
|
+
if (!hookCache.has(cacheKey)) {
|
|
390
|
+
const getEndpoint = () => {
|
|
391
|
+
const parts = path.split(".");
|
|
392
|
+
let current: unknown = baseClient;
|
|
393
|
+
for (const part of parts) {
|
|
394
|
+
current = (current as Record<string, unknown>)[part];
|
|
395
|
+
}
|
|
396
|
+
return current as (options: unknown) => QueryResult<unknown>;
|
|
397
|
+
};
|
|
398
|
+
hookCache.set(cacheKey, createUseQueryHook(getEndpoint));
|
|
399
|
+
}
|
|
400
|
+
return hookCache.get(cacheKey);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Handle .useMutation() - React hook for mutations
|
|
404
|
+
if (key === "useMutation") {
|
|
405
|
+
const cacheKey = `${path}:useMutation`;
|
|
406
|
+
if (!hookCache.has(cacheKey)) {
|
|
407
|
+
const getEndpoint = () => {
|
|
408
|
+
const parts = path.split(".");
|
|
409
|
+
let current: unknown = baseClient;
|
|
410
|
+
for (const part of parts) {
|
|
411
|
+
current = (current as Record<string, unknown>)[part];
|
|
412
|
+
}
|
|
413
|
+
return current as (options: unknown) => Promise<{ data: unknown }>;
|
|
414
|
+
};
|
|
415
|
+
hookCache.set(cacheKey, createUseMutationHook(getEndpoint));
|
|
416
|
+
}
|
|
417
|
+
return hookCache.get(cacheKey);
|
|
510
418
|
}
|
|
511
419
|
|
|
512
420
|
if (key === "then") return undefined;
|
|
@@ -517,51 +425,14 @@ export function createClient<TRouter extends RouterDef>(
|
|
|
517
425
|
},
|
|
518
426
|
|
|
519
427
|
apply(_target, _thisArg, args) {
|
|
520
|
-
//
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
const options = args[0] as Record<string, unknown> | undefined;
|
|
526
|
-
|
|
527
|
-
// Detect based on option keys
|
|
528
|
-
const isQueryOptions =
|
|
529
|
-
options && ("input" in options || "select" in options || "skip" in options);
|
|
530
|
-
|
|
531
|
-
const isMutationOptions =
|
|
532
|
-
!options ||
|
|
533
|
-
(!isQueryOptions &&
|
|
534
|
-
(Object.keys(options).length === 0 ||
|
|
535
|
-
"onSuccess" in options ||
|
|
536
|
-
"onError" in options ||
|
|
537
|
-
"onSettled" in options));
|
|
538
|
-
|
|
539
|
-
// Check cache - but we need to know the type first
|
|
540
|
-
const cacheKeyQuery = `${path}:query`;
|
|
541
|
-
const cacheKeyMutation = `${path}:mutation`;
|
|
542
|
-
|
|
543
|
-
if (isQueryOptions) {
|
|
544
|
-
if (!hookCache.has(cacheKeyQuery)) {
|
|
545
|
-
hookCache.set(cacheKeyQuery, createQueryHook(baseClient, path));
|
|
546
|
-
}
|
|
547
|
-
const hook = hookCache.get(cacheKeyQuery) as (opts: unknown) => unknown;
|
|
548
|
-
return hook(options);
|
|
549
|
-
}
|
|
550
|
-
|
|
551
|
-
if (isMutationOptions) {
|
|
552
|
-
if (!hookCache.has(cacheKeyMutation)) {
|
|
553
|
-
hookCache.set(cacheKeyMutation, createMutationHook(baseClient, path));
|
|
554
|
-
}
|
|
555
|
-
const hook = hookCache.get(cacheKeyMutation) as (opts: unknown) => unknown;
|
|
556
|
-
return hook(options);
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
// Fallback to query
|
|
560
|
-
if (!hookCache.has(cacheKeyQuery)) {
|
|
561
|
-
hookCache.set(cacheKeyQuery, createQueryHook(baseClient, path));
|
|
428
|
+
// Direct call - delegate to base client (returns QueryResult or Promise)
|
|
429
|
+
const parts = path.split(".");
|
|
430
|
+
let current: unknown = baseClient;
|
|
431
|
+
for (const part of parts) {
|
|
432
|
+
current = (current as Record<string, unknown>)[part];
|
|
562
433
|
}
|
|
563
|
-
const
|
|
564
|
-
return
|
|
434
|
+
const endpoint = current as (options: unknown) => unknown;
|
|
435
|
+
return endpoint(args[0]);
|
|
565
436
|
},
|
|
566
437
|
};
|
|
567
438
|
|
package/src/index.ts
CHANGED
|
@@ -15,14 +15,13 @@
|
|
|
15
15
|
* transport: httpTransport({ url: '/api/lens' }),
|
|
16
16
|
* });
|
|
17
17
|
*
|
|
18
|
-
* //
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* return <div>{data?.name}</div>;
|
|
22
|
-
* }
|
|
18
|
+
* // Vanilla JS (anywhere - SSR, utilities, event handlers)
|
|
19
|
+
* const user = await client.user.get({ input: { id } });
|
|
20
|
+
* client.user.get({ input: { id } }).subscribe(data => console.log(data));
|
|
23
21
|
*
|
|
24
|
-
* //
|
|
25
|
-
* const
|
|
22
|
+
* // React hooks (in components)
|
|
23
|
+
* const { data, loading } = client.user.get.useQuery({ input: { id } });
|
|
24
|
+
* const { mutate, loading } = client.user.create.useMutation();
|
|
26
25
|
* ```
|
|
27
26
|
*/
|
|
28
27
|
|