@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.
@@ -0,0 +1,376 @@
1
+ import { SRPC, AnySRPC } from '@srpc/core/server';
2
+ import { AnyRoutes, AnyProcedure, ClientProcedure, DecoratedProcedureRecord } from '@srpc/core/shared';
3
+ import React, { PropsWithChildren } from 'react';
4
+ import { UseQueryOptions, UseMutationOptions } from '@tanstack/react-query';
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
+ /**
128
+ * @module
129
+ *
130
+ * React Context integration for SRPC with React Query.
131
+ *
132
+ * This module provides React Context-based access to SRPC procedures with React Query
133
+ * integration. It creates a Provider component and hooks for accessing RPC functionality
134
+ * throughout your React application.
135
+ *
136
+ * @example Complete setup
137
+ * ```typescript
138
+ * import { createSRPCClient } from "@srpc/core/client";
139
+ * import { createSRPCContext } from "@srpc/react-query/context";
140
+ * import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
141
+ * import type { AppRouter } from "./server";
142
+ *
143
+ * // Create client
144
+ * const client = createSRPCClient<AppRouter>({ endpoint: "/api" });
145
+ *
146
+ * // Create context and hooks
147
+ * export const { SRPCProvider, useSRPC, useSRPCClient } =
148
+ * createSRPCContext<AppRouter>();
149
+ *
150
+ * // Setup in app
151
+ * function App() {
152
+ * const [queryClient] = useState(() => new QueryClient());
153
+ *
154
+ * return (
155
+ * <QueryClientProvider client={queryClient}>
156
+ * <SRPCProvider client={client}>
157
+ * <YourApp />
158
+ * </SRPCProvider>
159
+ * </QueryClientProvider>
160
+ * );
161
+ * }
162
+ *
163
+ * // Use in components
164
+ * function UserProfile() {
165
+ * const srpc = useSRPC();
166
+ * const { data } = useQuery(srpc.users.getUser.queryOptions(1));
167
+ * return <div>{data?.name}</div>;
168
+ * }
169
+ * ```
170
+ */
171
+
172
+ /**
173
+ * Type definition for the SRPC context value.
174
+ *
175
+ * Contains both the raw client for direct RPC calls and the decorated
176
+ * SRPC object with React Query options.
177
+ *
178
+ * @template TRouter - The SRPC router type
179
+ *
180
+ * @property client - Raw SRPC client for direct procedure calls
181
+ * @property srpc - Decorated procedures with `.queryOptions()` and `.mutationOptions()`
182
+ */
183
+ interface SRPCContextValue<TRouter extends AnySRPC> {
184
+ client: DecoratedProcedureRecord<TRouter["ipc"]>;
185
+ srpc: DecoratedQueryProcedureRecord<TRouter["ipc"]>;
186
+ }
187
+ /**
188
+ * Return type of `createSRPCContext` containing all React components and hooks.
189
+ *
190
+ * @template TRouter - The SRPC router type
191
+ *
192
+ * @property SRPCContext - React Context object (rarely used directly)
193
+ * @property SRPCProvider - Provider component to wrap your app
194
+ * @property useSRPC - Hook to access decorated procedures with React Query options
195
+ * @property useSRPCClient - Hook to access the raw SRPC client
196
+ *
197
+ * @example
198
+ * ```typescript
199
+ * const { SRPCProvider, useSRPC, useSRPCClient } = createSRPCContext<AppRouter>();
200
+ *
201
+ * // SRPCProvider: Wrap your app
202
+ * <SRPCProvider client={rpcClient}>
203
+ * <App />
204
+ * </SRPCProvider>
205
+ *
206
+ * // useSRPC: Get React Query options in components
207
+ * const srpc = useSRPC();
208
+ * const query = useQuery(srpc.users.getUser.queryOptions(1));
209
+ *
210
+ * // useSRPCClient: Get raw client for direct calls
211
+ * const client = useSRPCClient();
212
+ * const result = await client.users.getUser(1);
213
+ * ```
214
+ */
215
+ type SRPCContextFactory<TRouter extends AnySRPC> = {
216
+ SRPCContext: React.Context<SRPCContextValue<TRouter>>;
217
+ SRPCProvider: ({ children, client, }: PropsWithChildren<{
218
+ client: DecoratedProcedureRecord<TRouter["ipc"]>;
219
+ }>) => React.JSX.Element;
220
+ useSRPC: () => DecoratedQueryProcedureRecord<TRouter["ipc"]>;
221
+ useSRPCClient: () => DecoratedProcedureRecord<TRouter["ipc"]>;
222
+ };
223
+ /**
224
+ * Factory function to create SRPC React Context, Provider, and hooks.
225
+ *
226
+ * Creates a type-safe React Context system for accessing SRPC procedures throughout
227
+ * your React application. The returned hooks provide access to both React Query
228
+ * integrated procedures and the raw RPC client.
229
+ *
230
+ * @template TRouter - The SRPC router type from your server
231
+ *
232
+ * @returns Object containing:
233
+ * - `SRPCContext` - React Context (rarely needed directly)
234
+ * - `SRPCProvider` - Provider component that accepts `client` prop
235
+ * - `useSRPC()` - Hook returning decorated procedures with `.queryOptions()` and `.mutationOptions()`
236
+ * - `useSRPCClient()` - Hook returning the raw SRPC client for direct calls
237
+ *
238
+ * @example Basic setup
239
+ * ```typescript
240
+ * import { createSRPCClient } from "@srpc/core/client";
241
+ * import { createSRPCContext } from "@srpc.org/react-query";
242
+ * import type { AppRouter } from "./server";
243
+ *
244
+ * // Create client
245
+ * const rpcClient = createSRPCClient<AppRouter>({ endpoint: "/api" });
246
+ *
247
+ * // Create context utilities
248
+ * export const { SRPCProvider, useSRPC, useSRPCClient } =
249
+ * createSRPCContext<AppRouter>();
250
+ *
251
+ * // In root component
252
+ * function App() {
253
+ * return (
254
+ * <QueryClientProvider client={queryClient}>
255
+ * <SRPCProvider client={rpcClient}>
256
+ * <YourApp />
257
+ * </SRPCProvider>
258
+ * </QueryClientProvider>
259
+ * );
260
+ * }
261
+ * ```
262
+ *
263
+ * @example Using in components with queries
264
+ * ```typescript
265
+ * import { useSRPC } from "./rpc";
266
+ * import { useQuery } from "@tanstack/react-query";
267
+ *
268
+ * function UserProfile({ userId }: { userId: number }) {
269
+ * const srpc = useSRPC();
270
+ *
271
+ * const { data, isLoading, error } = useQuery(
272
+ * srpc.users.getUser.queryOptions(userId)
273
+ * );
274
+ *
275
+ * if (isLoading) return <div>Loading...</div>;
276
+ * if (error) return <div>Error: {error.message}</div>;
277
+ *
278
+ * return (
279
+ * <div>
280
+ * <h2>{data.name}</h2>
281
+ * <p>{data.email}</p>
282
+ * </div>
283
+ * );
284
+ * }
285
+ * ```
286
+ *
287
+ * @example Using in components with mutations
288
+ * ```typescript
289
+ * import { useSRPC } from "./rpc";
290
+ * import { useMutation, useQueryClient } from "@tanstack/react-query";
291
+ *
292
+ * function CreateUserForm() {
293
+ * const srpc = useSRPC();
294
+ * const queryClient = useQueryClient();
295
+ *
296
+ * const createUser = useMutation({
297
+ * ...srpc.users.createUser.mutationOptions(),
298
+ * onSuccess: () => {
299
+ * queryClient.invalidateQueries({ queryKey: ["users"] });
300
+ * },
301
+ * });
302
+ *
303
+ * return (
304
+ * <form onSubmit={(e) => {
305
+ * e.preventDefault();
306
+ * const formData = new FormData(e.currentTarget);
307
+ * createUser.mutate({
308
+ * name: formData.get("name") as string,
309
+ * email: formData.get("email") as string,
310
+ * });
311
+ * }}>
312
+ * <input name="name" required />
313
+ * <input name="email" type="email" required />
314
+ * <button disabled={createUser.isPending}>Create User</button>
315
+ * </form>
316
+ * );
317
+ * }
318
+ * ```
319
+ *
320
+ * @example Using raw client for direct calls
321
+ * ```typescript
322
+ * import { useSRPCClient } from "./rpc";
323
+ *
324
+ * function DirectCallExample() {
325
+ * const client = useSRPCClient();
326
+ *
327
+ * const handleClick = async () => {
328
+ * // Direct RPC call without React Query
329
+ * const result = await client.sayHello("World");
330
+ * console.log(result); // "Hello World!"
331
+ * };
332
+ *
333
+ * return <button onClick={handleClick}>Call RPC</button>;
334
+ * }
335
+ * ```
336
+ *
337
+ * @example Nested routers
338
+ * ```typescript
339
+ * function NestedRouterExample() {
340
+ * const srpc = useSRPC();
341
+ *
342
+ * // All nested paths are fully typed
343
+ * const userQuery = useQuery(srpc.users.getUser.queryOptions(1));
344
+ * const adminQuery = useQuery(srpc.users.admin.getStats.queryOptions());
345
+ * const postQuery = useQuery(srpc.posts.drafts.list.queryOptions());
346
+ *
347
+ * return <div>...</div>;
348
+ * }
349
+ * ```
350
+ *
351
+ * @example With Next.js App Router
352
+ * ```typescript
353
+ * // app/providers.tsx
354
+ * "use client";
355
+ *
356
+ * import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
357
+ * import { rpcClient, SRPCProvider } from "@/lib/rpc";
358
+ * import { useState } from "react";
359
+ *
360
+ * export function Providers({ children }: { children: React.ReactNode }) {
361
+ * const [queryClient] = useState(() => new QueryClient());
362
+ *
363
+ * return (
364
+ * <QueryClientProvider client={queryClient}>
365
+ * <SRPCProvider client={rpcClient}>
366
+ * {children}
367
+ * </SRPCProvider>
368
+ * </QueryClientProvider>
369
+ * );
370
+ * }
371
+ * ```
372
+ */
373
+ declare function createSRPCContext<TRouter extends AnySRPC>(): SRPCContextFactory<TRouter>;
374
+
375
+ export { createSRPCContext };
376
+ export type { SRPCContextFactory, SRPCContextValue };