@srpc.org/react-query 0.20.3
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/README.md +386 -0
- package/dist/context.d.ts +376 -0
- package/dist/context.js +344 -0
- package/dist/index.d.ts +205 -0
- package/dist/index.js +108 -0
- package/package.json +53 -0
package/dist/context.js
ADDED
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx } from 'react/jsx-runtime';
|
|
3
|
+
import { createContext, useContext, useMemo } from 'react';
|
|
4
|
+
import { createRecursiveProxy } from '@srpc/core/shared';
|
|
5
|
+
import { queryOptions, mutationOptions } from '@tanstack/react-query';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Transforms an SRPC client into React Query-compatible query and mutation options.
|
|
9
|
+
*
|
|
10
|
+
* This function takes an SRPC client and returns a proxy object where each procedure
|
|
11
|
+
* has `.queryOptions()` and `.mutationOptions()` methods that generate options for
|
|
12
|
+
* React Query's `useQuery` and `useMutation` hooks.
|
|
13
|
+
*
|
|
14
|
+
* The proxy automatically:
|
|
15
|
+
* - Generates query keys based on the procedure path and arguments
|
|
16
|
+
* - Wraps procedure calls in query/mutation functions
|
|
17
|
+
* - Preserves type safety throughout the chain
|
|
18
|
+
*
|
|
19
|
+
* @template TRouter - The SRPC router type
|
|
20
|
+
* @template TRoutes - The routes type (inferred automatically)
|
|
21
|
+
*
|
|
22
|
+
* @param options - Configuration object
|
|
23
|
+
* @param options.client - The SRPC client created with `createSRPCClient`
|
|
24
|
+
*
|
|
25
|
+
* @returns Decorated procedures with `.queryOptions()` and `.mutationOptions()` methods
|
|
26
|
+
*
|
|
27
|
+
* @example Basic usage
|
|
28
|
+
* ```typescript
|
|
29
|
+
* import { createSRPCClient } from "@srpc/core/client";
|
|
30
|
+
* import { createSRPCQueryOptions } from "@srpc.org/react-query";
|
|
31
|
+
* import { useQuery, useMutation } from "@tanstack/react-query";
|
|
32
|
+
* import type { AppRouter } from "./server";
|
|
33
|
+
*
|
|
34
|
+
* const client = createSRPCClient<AppRouter>({ endpoint: "/api" });
|
|
35
|
+
* const srpc = createSRPCQueryOptions({ client });
|
|
36
|
+
*
|
|
37
|
+
* function UserProfile({ userId }: { userId: number }) {
|
|
38
|
+
* // Use with useQuery
|
|
39
|
+
* const { data, isLoading } = useQuery(
|
|
40
|
+
* srpc.users.getUser.queryOptions(userId)
|
|
41
|
+
* );
|
|
42
|
+
*
|
|
43
|
+
* return <div>{data?.name}</div>;
|
|
44
|
+
* }
|
|
45
|
+
* ```
|
|
46
|
+
*
|
|
47
|
+
* @example With mutations
|
|
48
|
+
* ```typescript
|
|
49
|
+
* function CreateUserForm() {
|
|
50
|
+
* const createUser = useMutation(srpc.users.createUser.mutationOptions());
|
|
51
|
+
*
|
|
52
|
+
* const handleSubmit = (data: UserData) => {
|
|
53
|
+
* createUser.mutate(data);
|
|
54
|
+
* };
|
|
55
|
+
*
|
|
56
|
+
* return <form onSubmit={handleSubmit}>...</form>;
|
|
57
|
+
* }
|
|
58
|
+
* ```
|
|
59
|
+
*
|
|
60
|
+
* @example With query options
|
|
61
|
+
* ```typescript
|
|
62
|
+
* function UserList() {
|
|
63
|
+
* const { data } = useQuery({
|
|
64
|
+
* ...srpc.users.list.queryOptions(),
|
|
65
|
+
* staleTime: 5000,
|
|
66
|
+
* refetchInterval: 10000,
|
|
67
|
+
* });
|
|
68
|
+
*
|
|
69
|
+
* return <ul>{data?.map(user => <li key={user.id}>{user.name}</li>)}</ul>;
|
|
70
|
+
* }
|
|
71
|
+
* ```
|
|
72
|
+
*
|
|
73
|
+
* @example Nested routers
|
|
74
|
+
* ```typescript
|
|
75
|
+
* // Works with deeply nested routers
|
|
76
|
+
* const userQuery = useQuery(srpc.users.getUser.queryOptions(1));
|
|
77
|
+
* const adminQuery = useQuery(srpc.users.admin.getStats.queryOptions());
|
|
78
|
+
* const draftQuery = useQuery(srpc.posts.drafts.list.queryOptions());
|
|
79
|
+
* ```
|
|
80
|
+
*/ function createSRPCQueryOptions({ client }) {
|
|
81
|
+
return createRecursiveProxy(({ path, args })=>{
|
|
82
|
+
const lastPath = path[path.length - 1];
|
|
83
|
+
const pathWithoutTarget = path.slice(0, -1);
|
|
84
|
+
const procedure = pathWithoutTarget.reduce((acc, key)=>acc[key], client);
|
|
85
|
+
if (typeof procedure !== "function") {
|
|
86
|
+
throw new Error(`Procedure at path ${pathWithoutTarget.join(".")} is not a function`);
|
|
87
|
+
}
|
|
88
|
+
if (lastPath === "queryOptions") {
|
|
89
|
+
return queryOptions({
|
|
90
|
+
queryKey: [
|
|
91
|
+
...pathWithoutTarget,
|
|
92
|
+
args
|
|
93
|
+
],
|
|
94
|
+
queryFn: ()=>procedure(...args)
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
if (lastPath === "mutationOptions") {
|
|
98
|
+
return mutationOptions({
|
|
99
|
+
mutationKey: [
|
|
100
|
+
...pathWithoutTarget,
|
|
101
|
+
args
|
|
102
|
+
],
|
|
103
|
+
mutationFn: (variables)=>procedure(...variables)
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
throw new Error(`Unknown target path at: ${path.join(".")}`);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* @module
|
|
112
|
+
*
|
|
113
|
+
* React Context integration for SRPC with React Query.
|
|
114
|
+
*
|
|
115
|
+
* This module provides React Context-based access to SRPC procedures with React Query
|
|
116
|
+
* integration. It creates a Provider component and hooks for accessing RPC functionality
|
|
117
|
+
* throughout your React application.
|
|
118
|
+
*
|
|
119
|
+
* @example Complete setup
|
|
120
|
+
* ```typescript
|
|
121
|
+
* import { createSRPCClient } from "@srpc/core/client";
|
|
122
|
+
* import { createSRPCContext } from "@srpc/react-query/context";
|
|
123
|
+
* import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
124
|
+
* import type { AppRouter } from "./server";
|
|
125
|
+
*
|
|
126
|
+
* // Create client
|
|
127
|
+
* const client = createSRPCClient<AppRouter>({ endpoint: "/api" });
|
|
128
|
+
*
|
|
129
|
+
* // Create context and hooks
|
|
130
|
+
* export const { SRPCProvider, useSRPC, useSRPCClient } =
|
|
131
|
+
* createSRPCContext<AppRouter>();
|
|
132
|
+
*
|
|
133
|
+
* // Setup in app
|
|
134
|
+
* function App() {
|
|
135
|
+
* const [queryClient] = useState(() => new QueryClient());
|
|
136
|
+
*
|
|
137
|
+
* return (
|
|
138
|
+
* <QueryClientProvider client={queryClient}>
|
|
139
|
+
* <SRPCProvider client={client}>
|
|
140
|
+
* <YourApp />
|
|
141
|
+
* </SRPCProvider>
|
|
142
|
+
* </QueryClientProvider>
|
|
143
|
+
* );
|
|
144
|
+
* }
|
|
145
|
+
*
|
|
146
|
+
* // Use in components
|
|
147
|
+
* function UserProfile() {
|
|
148
|
+
* const srpc = useSRPC();
|
|
149
|
+
* const { data } = useQuery(srpc.users.getUser.queryOptions(1));
|
|
150
|
+
* return <div>{data?.name}</div>;
|
|
151
|
+
* }
|
|
152
|
+
* ```
|
|
153
|
+
*/
|
|
154
|
+
/**
|
|
155
|
+
* Factory function to create SRPC React Context, Provider, and hooks.
|
|
156
|
+
*
|
|
157
|
+
* Creates a type-safe React Context system for accessing SRPC procedures throughout
|
|
158
|
+
* your React application. The returned hooks provide access to both React Query
|
|
159
|
+
* integrated procedures and the raw RPC client.
|
|
160
|
+
*
|
|
161
|
+
* @template TRouter - The SRPC router type from your server
|
|
162
|
+
*
|
|
163
|
+
* @returns Object containing:
|
|
164
|
+
* - `SRPCContext` - React Context (rarely needed directly)
|
|
165
|
+
* - `SRPCProvider` - Provider component that accepts `client` prop
|
|
166
|
+
* - `useSRPC()` - Hook returning decorated procedures with `.queryOptions()` and `.mutationOptions()`
|
|
167
|
+
* - `useSRPCClient()` - Hook returning the raw SRPC client for direct calls
|
|
168
|
+
*
|
|
169
|
+
* @example Basic setup
|
|
170
|
+
* ```typescript
|
|
171
|
+
* import { createSRPCClient } from "@srpc/core/client";
|
|
172
|
+
* import { createSRPCContext } from "@srpc.org/react-query";
|
|
173
|
+
* import type { AppRouter } from "./server";
|
|
174
|
+
*
|
|
175
|
+
* // Create client
|
|
176
|
+
* const rpcClient = createSRPCClient<AppRouter>({ endpoint: "/api" });
|
|
177
|
+
*
|
|
178
|
+
* // Create context utilities
|
|
179
|
+
* export const { SRPCProvider, useSRPC, useSRPCClient } =
|
|
180
|
+
* createSRPCContext<AppRouter>();
|
|
181
|
+
*
|
|
182
|
+
* // In root component
|
|
183
|
+
* function App() {
|
|
184
|
+
* return (
|
|
185
|
+
* <QueryClientProvider client={queryClient}>
|
|
186
|
+
* <SRPCProvider client={rpcClient}>
|
|
187
|
+
* <YourApp />
|
|
188
|
+
* </SRPCProvider>
|
|
189
|
+
* </QueryClientProvider>
|
|
190
|
+
* );
|
|
191
|
+
* }
|
|
192
|
+
* ```
|
|
193
|
+
*
|
|
194
|
+
* @example Using in components with queries
|
|
195
|
+
* ```typescript
|
|
196
|
+
* import { useSRPC } from "./rpc";
|
|
197
|
+
* import { useQuery } from "@tanstack/react-query";
|
|
198
|
+
*
|
|
199
|
+
* function UserProfile({ userId }: { userId: number }) {
|
|
200
|
+
* const srpc = useSRPC();
|
|
201
|
+
*
|
|
202
|
+
* const { data, isLoading, error } = useQuery(
|
|
203
|
+
* srpc.users.getUser.queryOptions(userId)
|
|
204
|
+
* );
|
|
205
|
+
*
|
|
206
|
+
* if (isLoading) return <div>Loading...</div>;
|
|
207
|
+
* if (error) return <div>Error: {error.message}</div>;
|
|
208
|
+
*
|
|
209
|
+
* return (
|
|
210
|
+
* <div>
|
|
211
|
+
* <h2>{data.name}</h2>
|
|
212
|
+
* <p>{data.email}</p>
|
|
213
|
+
* </div>
|
|
214
|
+
* );
|
|
215
|
+
* }
|
|
216
|
+
* ```
|
|
217
|
+
*
|
|
218
|
+
* @example Using in components with mutations
|
|
219
|
+
* ```typescript
|
|
220
|
+
* import { useSRPC } from "./rpc";
|
|
221
|
+
* import { useMutation, useQueryClient } from "@tanstack/react-query";
|
|
222
|
+
*
|
|
223
|
+
* function CreateUserForm() {
|
|
224
|
+
* const srpc = useSRPC();
|
|
225
|
+
* const queryClient = useQueryClient();
|
|
226
|
+
*
|
|
227
|
+
* const createUser = useMutation({
|
|
228
|
+
* ...srpc.users.createUser.mutationOptions(),
|
|
229
|
+
* onSuccess: () => {
|
|
230
|
+
* queryClient.invalidateQueries({ queryKey: ["users"] });
|
|
231
|
+
* },
|
|
232
|
+
* });
|
|
233
|
+
*
|
|
234
|
+
* return (
|
|
235
|
+
* <form onSubmit={(e) => {
|
|
236
|
+
* e.preventDefault();
|
|
237
|
+
* const formData = new FormData(e.currentTarget);
|
|
238
|
+
* createUser.mutate({
|
|
239
|
+
* name: formData.get("name") as string,
|
|
240
|
+
* email: formData.get("email") as string,
|
|
241
|
+
* });
|
|
242
|
+
* }}>
|
|
243
|
+
* <input name="name" required />
|
|
244
|
+
* <input name="email" type="email" required />
|
|
245
|
+
* <button disabled={createUser.isPending}>Create User</button>
|
|
246
|
+
* </form>
|
|
247
|
+
* );
|
|
248
|
+
* }
|
|
249
|
+
* ```
|
|
250
|
+
*
|
|
251
|
+
* @example Using raw client for direct calls
|
|
252
|
+
* ```typescript
|
|
253
|
+
* import { useSRPCClient } from "./rpc";
|
|
254
|
+
*
|
|
255
|
+
* function DirectCallExample() {
|
|
256
|
+
* const client = useSRPCClient();
|
|
257
|
+
*
|
|
258
|
+
* const handleClick = async () => {
|
|
259
|
+
* // Direct RPC call without React Query
|
|
260
|
+
* const result = await client.sayHello("World");
|
|
261
|
+
* console.log(result); // "Hello World!"
|
|
262
|
+
* };
|
|
263
|
+
*
|
|
264
|
+
* return <button onClick={handleClick}>Call RPC</button>;
|
|
265
|
+
* }
|
|
266
|
+
* ```
|
|
267
|
+
*
|
|
268
|
+
* @example Nested routers
|
|
269
|
+
* ```typescript
|
|
270
|
+
* function NestedRouterExample() {
|
|
271
|
+
* const srpc = useSRPC();
|
|
272
|
+
*
|
|
273
|
+
* // All nested paths are fully typed
|
|
274
|
+
* const userQuery = useQuery(srpc.users.getUser.queryOptions(1));
|
|
275
|
+
* const adminQuery = useQuery(srpc.users.admin.getStats.queryOptions());
|
|
276
|
+
* const postQuery = useQuery(srpc.posts.drafts.list.queryOptions());
|
|
277
|
+
*
|
|
278
|
+
* return <div>...</div>;
|
|
279
|
+
* }
|
|
280
|
+
* ```
|
|
281
|
+
*
|
|
282
|
+
* @example With Next.js App Router
|
|
283
|
+
* ```typescript
|
|
284
|
+
* // app/providers.tsx
|
|
285
|
+
* "use client";
|
|
286
|
+
*
|
|
287
|
+
* import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
|
288
|
+
* import { rpcClient, SRPCProvider } from "@/lib/rpc";
|
|
289
|
+
* import { useState } from "react";
|
|
290
|
+
*
|
|
291
|
+
* export function Providers({ children }: { children: React.ReactNode }) {
|
|
292
|
+
* const [queryClient] = useState(() => new QueryClient());
|
|
293
|
+
*
|
|
294
|
+
* return (
|
|
295
|
+
* <QueryClientProvider client={queryClient}>
|
|
296
|
+
* <SRPCProvider client={rpcClient}>
|
|
297
|
+
* {children}
|
|
298
|
+
* </SRPCProvider>
|
|
299
|
+
* </QueryClientProvider>
|
|
300
|
+
* );
|
|
301
|
+
* }
|
|
302
|
+
* ```
|
|
303
|
+
*/ function createSRPCContext() {
|
|
304
|
+
// biome-ignore lint/style/noNonNullAssertion: <Asserting non null for context>
|
|
305
|
+
const SRPCContext = /*#__PURE__*/ createContext(null);
|
|
306
|
+
function SRPCProvider({ children, client }) {
|
|
307
|
+
const srpc = useMemo(()=>{
|
|
308
|
+
return createSRPCQueryOptions({
|
|
309
|
+
client: client
|
|
310
|
+
});
|
|
311
|
+
}, [
|
|
312
|
+
client
|
|
313
|
+
]);
|
|
314
|
+
return /*#__PURE__*/ jsx(SRPCContext.Provider, {
|
|
315
|
+
value: {
|
|
316
|
+
client: client,
|
|
317
|
+
srpc
|
|
318
|
+
},
|
|
319
|
+
children: children
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
function useSRPC() {
|
|
323
|
+
const ctx = useContext(SRPCContext);
|
|
324
|
+
if (!ctx) {
|
|
325
|
+
throw new Error("useSRPC must be used within a SRPCProvider");
|
|
326
|
+
}
|
|
327
|
+
return ctx.srpc;
|
|
328
|
+
}
|
|
329
|
+
function useSRPCClient() {
|
|
330
|
+
const ctx = useContext(SRPCContext);
|
|
331
|
+
if (!ctx) {
|
|
332
|
+
throw new Error("useSRPCClient must be used within a SRPCProvider");
|
|
333
|
+
}
|
|
334
|
+
return ctx.client;
|
|
335
|
+
}
|
|
336
|
+
return {
|
|
337
|
+
SRPCContext,
|
|
338
|
+
SRPCProvider,
|
|
339
|
+
useSRPC,
|
|
340
|
+
useSRPCClient
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
export { createSRPCContext };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { SRPC, AnySRPC } from '@srpc/core/server';
|
|
2
|
+
import { ClientProcedure, AnyRoutes, AnyProcedure, DecoratedProcedureRecord } from '@srpc/core/shared';
|
|
3
|
+
import { UseQueryOptions, UseMutationOptions } from '@tanstack/react-query';
|
|
4
|
+
export { SRPCContextFactory, SRPCContextValue, createSRPCContext } from './context.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @module
|
|
8
|
+
*
|
|
9
|
+
* React Query integration for SRPC - Type-safe RPC with automatic query and mutation options.
|
|
10
|
+
*
|
|
11
|
+
* This module provides the core functionality for integrating SRPC with TanStack React Query.
|
|
12
|
+
* It transforms SRPC client procedures into React Query-compatible options objects that can
|
|
13
|
+
* be used with `useQuery` and `useMutation` hooks.
|
|
14
|
+
*
|
|
15
|
+
* @example Basic usage
|
|
16
|
+
* ```typescript
|
|
17
|
+
* import { createSRPCClient } from "@srpc/core/client";
|
|
18
|
+
* import { createSRPCQueryOptions } from "@srpc.org/react-query";
|
|
19
|
+
* import { useQuery } from "@tanstack/react-query";
|
|
20
|
+
* import type { AppRouter } from "./server";
|
|
21
|
+
*
|
|
22
|
+
* // Create client
|
|
23
|
+
* const client = createSRPCClient<AppRouter>({ endpoint: "/api" });
|
|
24
|
+
*
|
|
25
|
+
* // Transform to React Query options
|
|
26
|
+
* const srpc = createSRPCQueryOptions({ client });
|
|
27
|
+
*
|
|
28
|
+
* // Use in components
|
|
29
|
+
* function MyComponent() {
|
|
30
|
+
* const { data } = useQuery(srpc.users.getUser.queryOptions(1));
|
|
31
|
+
* return <div>{data?.name}</div>;
|
|
32
|
+
* }
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* @example With React Context
|
|
36
|
+
* ```typescript
|
|
37
|
+
* import { createSRPCContext } from "@srpc.org/react-query";
|
|
38
|
+
*
|
|
39
|
+
* // Create context and hooks
|
|
40
|
+
* const { SRPCProvider, useSRPC } = createSRPCContext<AppRouter>();
|
|
41
|
+
*
|
|
42
|
+
* // Setup provider
|
|
43
|
+
* function App() {
|
|
44
|
+
* return (
|
|
45
|
+
* <QueryClientProvider client={queryClient}>
|
|
46
|
+
* <SRPCProvider client={rpcClient}>
|
|
47
|
+
* <MyComponent />
|
|
48
|
+
* </SRPCProvider>
|
|
49
|
+
* </QueryClientProvider>
|
|
50
|
+
* );
|
|
51
|
+
* }
|
|
52
|
+
*
|
|
53
|
+
* // Use in components
|
|
54
|
+
* function MyComponent() {
|
|
55
|
+
* const srpc = useSRPC();
|
|
56
|
+
* const { data } = useQuery(srpc.users.getUser.queryOptions(1));
|
|
57
|
+
* }
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
/** biome-ignore-all lint/suspicious/noExplicitAny: <Allow any type cast> */
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Type representing the query and mutation options factory for a single procedure.
|
|
64
|
+
*
|
|
65
|
+
* Each procedure in the SRPC router is transformed to have two methods:
|
|
66
|
+
* - `queryOptions(...args)` - Returns React Query options for `useQuery`
|
|
67
|
+
* - `mutationOptions()` - Returns React Query options for `useMutation`
|
|
68
|
+
*
|
|
69
|
+
* @template Procedure - The client procedure type
|
|
70
|
+
* @template TInput - Input parameters type (inferred from procedure)
|
|
71
|
+
* @template TOutput - Output/return type (inferred from procedure)
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```typescript
|
|
75
|
+
* // For a procedure: getUser(id: number) => Promise<User>
|
|
76
|
+
* type GetUserOptions = OptionsFactory<typeof getUser>;
|
|
77
|
+
*
|
|
78
|
+
* // Has methods:
|
|
79
|
+
* const options: GetUserOptions = {
|
|
80
|
+
* queryOptions: (id: number) => UseQueryOptions<Promise<User>>,
|
|
81
|
+
* mutationOptions: () => UseMutationOptions<Promise<User>, Error, [number]>
|
|
82
|
+
* };
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
type OptionsFactory<Procedure extends ClientProcedure<any>, TInput = Parameters<Procedure>, TOutput = ReturnType<Procedure>> = {
|
|
86
|
+
queryOptions: (...args: Parameters<Procedure>) => UseQueryOptions<TOutput>;
|
|
87
|
+
mutationOptions: () => UseMutationOptions<TOutput, Error, TInput>;
|
|
88
|
+
};
|
|
89
|
+
/**
|
|
90
|
+
* Type that recursively transforms an SRPC router into a structure with React Query options.
|
|
91
|
+
*
|
|
92
|
+
* For each procedure in the router, adds `.queryOptions()` and `.mutationOptions()` methods.
|
|
93
|
+
* Handles nested routers recursively, preserving the router structure.
|
|
94
|
+
*
|
|
95
|
+
* @template TRouter - The SRPC router routes type
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* ```typescript
|
|
99
|
+
* // Server router
|
|
100
|
+
* const appRouter = s.router({
|
|
101
|
+
* getUser: async (_, id: number) => ({ id, name: "John" }),
|
|
102
|
+
* users: s.router({
|
|
103
|
+
* createUser: async (_, data: UserData) => ({ id: 1, ...data })
|
|
104
|
+
* })
|
|
105
|
+
* });
|
|
106
|
+
*
|
|
107
|
+
* // Client with query options
|
|
108
|
+
* type QueryRouter = DecoratedQueryProcedureRecord<typeof appRouter["ipc"]>;
|
|
109
|
+
* // {
|
|
110
|
+
* // getUser: {
|
|
111
|
+
* // queryOptions: (id: number) => UseQueryOptions<User>,
|
|
112
|
+
* // mutationOptions: () => UseMutationOptions<User, Error, [number]>
|
|
113
|
+
* // },
|
|
114
|
+
* // users: {
|
|
115
|
+
* // createUser: {
|
|
116
|
+
* // queryOptions: (data: UserData) => UseQueryOptions<UserResult>,
|
|
117
|
+
* // mutationOptions: () => UseMutationOptions<UserResult, Error, [UserData]>
|
|
118
|
+
* // }
|
|
119
|
+
* // }
|
|
120
|
+
* // }
|
|
121
|
+
* ```
|
|
122
|
+
*/
|
|
123
|
+
type DecoratedQueryProcedureRecord<TRouter extends AnyRoutes> = {
|
|
124
|
+
[TKey in keyof TRouter]: TRouter[TKey] extends AnyProcedure ? OptionsFactory<ClientProcedure<TRouter[TKey]>> : TRouter[TKey] extends SRPC<any> ? DecoratedQueryProcedureRecord<TRouter[TKey]["ipc"]> : never;
|
|
125
|
+
};
|
|
126
|
+
/**
|
|
127
|
+
* Transforms an SRPC client into React Query-compatible query and mutation options.
|
|
128
|
+
*
|
|
129
|
+
* This function takes an SRPC client and returns a proxy object where each procedure
|
|
130
|
+
* has `.queryOptions()` and `.mutationOptions()` methods that generate options for
|
|
131
|
+
* React Query's `useQuery` and `useMutation` hooks.
|
|
132
|
+
*
|
|
133
|
+
* The proxy automatically:
|
|
134
|
+
* - Generates query keys based on the procedure path and arguments
|
|
135
|
+
* - Wraps procedure calls in query/mutation functions
|
|
136
|
+
* - Preserves type safety throughout the chain
|
|
137
|
+
*
|
|
138
|
+
* @template TRouter - The SRPC router type
|
|
139
|
+
* @template TRoutes - The routes type (inferred automatically)
|
|
140
|
+
*
|
|
141
|
+
* @param options - Configuration object
|
|
142
|
+
* @param options.client - The SRPC client created with `createSRPCClient`
|
|
143
|
+
*
|
|
144
|
+
* @returns Decorated procedures with `.queryOptions()` and `.mutationOptions()` methods
|
|
145
|
+
*
|
|
146
|
+
* @example Basic usage
|
|
147
|
+
* ```typescript
|
|
148
|
+
* import { createSRPCClient } from "@srpc/core/client";
|
|
149
|
+
* import { createSRPCQueryOptions } from "@srpc.org/react-query";
|
|
150
|
+
* import { useQuery, useMutation } from "@tanstack/react-query";
|
|
151
|
+
* import type { AppRouter } from "./server";
|
|
152
|
+
*
|
|
153
|
+
* const client = createSRPCClient<AppRouter>({ endpoint: "/api" });
|
|
154
|
+
* const srpc = createSRPCQueryOptions({ client });
|
|
155
|
+
*
|
|
156
|
+
* function UserProfile({ userId }: { userId: number }) {
|
|
157
|
+
* // Use with useQuery
|
|
158
|
+
* const { data, isLoading } = useQuery(
|
|
159
|
+
* srpc.users.getUser.queryOptions(userId)
|
|
160
|
+
* );
|
|
161
|
+
*
|
|
162
|
+
* return <div>{data?.name}</div>;
|
|
163
|
+
* }
|
|
164
|
+
* ```
|
|
165
|
+
*
|
|
166
|
+
* @example With mutations
|
|
167
|
+
* ```typescript
|
|
168
|
+
* function CreateUserForm() {
|
|
169
|
+
* const createUser = useMutation(srpc.users.createUser.mutationOptions());
|
|
170
|
+
*
|
|
171
|
+
* const handleSubmit = (data: UserData) => {
|
|
172
|
+
* createUser.mutate(data);
|
|
173
|
+
* };
|
|
174
|
+
*
|
|
175
|
+
* return <form onSubmit={handleSubmit}>...</form>;
|
|
176
|
+
* }
|
|
177
|
+
* ```
|
|
178
|
+
*
|
|
179
|
+
* @example With query options
|
|
180
|
+
* ```typescript
|
|
181
|
+
* function UserList() {
|
|
182
|
+
* const { data } = useQuery({
|
|
183
|
+
* ...srpc.users.list.queryOptions(),
|
|
184
|
+
* staleTime: 5000,
|
|
185
|
+
* refetchInterval: 10000,
|
|
186
|
+
* });
|
|
187
|
+
*
|
|
188
|
+
* return <ul>{data?.map(user => <li key={user.id}>{user.name}</li>)}</ul>;
|
|
189
|
+
* }
|
|
190
|
+
* ```
|
|
191
|
+
*
|
|
192
|
+
* @example Nested routers
|
|
193
|
+
* ```typescript
|
|
194
|
+
* // Works with deeply nested routers
|
|
195
|
+
* const userQuery = useQuery(srpc.users.getUser.queryOptions(1));
|
|
196
|
+
* const adminQuery = useQuery(srpc.users.admin.getStats.queryOptions());
|
|
197
|
+
* const draftQuery = useQuery(srpc.posts.drafts.list.queryOptions());
|
|
198
|
+
* ```
|
|
199
|
+
*/
|
|
200
|
+
declare function createSRPCQueryOptions<TRouter extends AnySRPC, TRoutes extends AnyRoutes = TRouter["ipc"]>({ client, }: {
|
|
201
|
+
client: DecoratedProcedureRecord<TRoutes>;
|
|
202
|
+
}): DecoratedQueryProcedureRecord<TRoutes>;
|
|
203
|
+
|
|
204
|
+
export { createSRPCQueryOptions };
|
|
205
|
+
export type { DecoratedQueryProcedureRecord, OptionsFactory };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { createRecursiveProxy } from '@srpc/core/shared';
|
|
2
|
+
import { queryOptions, mutationOptions } from '@tanstack/react-query';
|
|
3
|
+
export { createSRPCContext } from './context.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Transforms an SRPC client into React Query-compatible query and mutation options.
|
|
7
|
+
*
|
|
8
|
+
* This function takes an SRPC client and returns a proxy object where each procedure
|
|
9
|
+
* has `.queryOptions()` and `.mutationOptions()` methods that generate options for
|
|
10
|
+
* React Query's `useQuery` and `useMutation` hooks.
|
|
11
|
+
*
|
|
12
|
+
* The proxy automatically:
|
|
13
|
+
* - Generates query keys based on the procedure path and arguments
|
|
14
|
+
* - Wraps procedure calls in query/mutation functions
|
|
15
|
+
* - Preserves type safety throughout the chain
|
|
16
|
+
*
|
|
17
|
+
* @template TRouter - The SRPC router type
|
|
18
|
+
* @template TRoutes - The routes type (inferred automatically)
|
|
19
|
+
*
|
|
20
|
+
* @param options - Configuration object
|
|
21
|
+
* @param options.client - The SRPC client created with `createSRPCClient`
|
|
22
|
+
*
|
|
23
|
+
* @returns Decorated procedures with `.queryOptions()` and `.mutationOptions()` methods
|
|
24
|
+
*
|
|
25
|
+
* @example Basic usage
|
|
26
|
+
* ```typescript
|
|
27
|
+
* import { createSRPCClient } from "@srpc/core/client";
|
|
28
|
+
* import { createSRPCQueryOptions } from "@srpc.org/react-query";
|
|
29
|
+
* import { useQuery, useMutation } from "@tanstack/react-query";
|
|
30
|
+
* import type { AppRouter } from "./server";
|
|
31
|
+
*
|
|
32
|
+
* const client = createSRPCClient<AppRouter>({ endpoint: "/api" });
|
|
33
|
+
* const srpc = createSRPCQueryOptions({ client });
|
|
34
|
+
*
|
|
35
|
+
* function UserProfile({ userId }: { userId: number }) {
|
|
36
|
+
* // Use with useQuery
|
|
37
|
+
* const { data, isLoading } = useQuery(
|
|
38
|
+
* srpc.users.getUser.queryOptions(userId)
|
|
39
|
+
* );
|
|
40
|
+
*
|
|
41
|
+
* return <div>{data?.name}</div>;
|
|
42
|
+
* }
|
|
43
|
+
* ```
|
|
44
|
+
*
|
|
45
|
+
* @example With mutations
|
|
46
|
+
* ```typescript
|
|
47
|
+
* function CreateUserForm() {
|
|
48
|
+
* const createUser = useMutation(srpc.users.createUser.mutationOptions());
|
|
49
|
+
*
|
|
50
|
+
* const handleSubmit = (data: UserData) => {
|
|
51
|
+
* createUser.mutate(data);
|
|
52
|
+
* };
|
|
53
|
+
*
|
|
54
|
+
* return <form onSubmit={handleSubmit}>...</form>;
|
|
55
|
+
* }
|
|
56
|
+
* ```
|
|
57
|
+
*
|
|
58
|
+
* @example With query options
|
|
59
|
+
* ```typescript
|
|
60
|
+
* function UserList() {
|
|
61
|
+
* const { data } = useQuery({
|
|
62
|
+
* ...srpc.users.list.queryOptions(),
|
|
63
|
+
* staleTime: 5000,
|
|
64
|
+
* refetchInterval: 10000,
|
|
65
|
+
* });
|
|
66
|
+
*
|
|
67
|
+
* return <ul>{data?.map(user => <li key={user.id}>{user.name}</li>)}</ul>;
|
|
68
|
+
* }
|
|
69
|
+
* ```
|
|
70
|
+
*
|
|
71
|
+
* @example Nested routers
|
|
72
|
+
* ```typescript
|
|
73
|
+
* // Works with deeply nested routers
|
|
74
|
+
* const userQuery = useQuery(srpc.users.getUser.queryOptions(1));
|
|
75
|
+
* const adminQuery = useQuery(srpc.users.admin.getStats.queryOptions());
|
|
76
|
+
* const draftQuery = useQuery(srpc.posts.drafts.list.queryOptions());
|
|
77
|
+
* ```
|
|
78
|
+
*/ function createSRPCQueryOptions({ client }) {
|
|
79
|
+
return createRecursiveProxy(({ path, args })=>{
|
|
80
|
+
const lastPath = path[path.length - 1];
|
|
81
|
+
const pathWithoutTarget = path.slice(0, -1);
|
|
82
|
+
const procedure = pathWithoutTarget.reduce((acc, key)=>acc[key], client);
|
|
83
|
+
if (typeof procedure !== "function") {
|
|
84
|
+
throw new Error(`Procedure at path ${pathWithoutTarget.join(".")} is not a function`);
|
|
85
|
+
}
|
|
86
|
+
if (lastPath === "queryOptions") {
|
|
87
|
+
return queryOptions({
|
|
88
|
+
queryKey: [
|
|
89
|
+
...pathWithoutTarget,
|
|
90
|
+
args
|
|
91
|
+
],
|
|
92
|
+
queryFn: ()=>procedure(...args)
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
if (lastPath === "mutationOptions") {
|
|
96
|
+
return mutationOptions({
|
|
97
|
+
mutationKey: [
|
|
98
|
+
...pathWithoutTarget,
|
|
99
|
+
args
|
|
100
|
+
],
|
|
101
|
+
mutationFn: (variables)=>procedure(...variables)
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
throw new Error(`Unknown target path at: ${path.join(".")}`);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export { createSRPCQueryOptions };
|