@richie-rpc/react-query 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,360 @@
1
+ # @richie-rpc/react-query
2
+
3
+ React hooks integration for Richie RPC using TanStack Query (React Query). Provides type-safe hooks with automatic caching, background refetching, and React Suspense support.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ bun add @richie-rpc/react-query @richie-rpc/client @richie-rpc/core @tanstack/react-query zod@^4
9
+ ```
10
+
11
+ ## Features
12
+
13
+ - 🎯 **Fully Type-Safe**: Complete TypeScript inference from contract to hooks
14
+ - 🔄 **Automatic Method Detection**: GET/HEAD → queries, POST/PUT/PATCH/DELETE → mutations
15
+ - âš¡ **React Suspense**: Built-in support with `useSuspenseQuery`
16
+ - 💾 **Smart Caching**: Powered by TanStack Query
17
+ - 🔥 **Zero Config**: Works out of the box with sensible defaults
18
+ - 🎨 **Customizable**: Pass through all TanStack Query options
19
+
20
+ ## Quick Start
21
+
22
+ ### 1. Setup Provider
23
+
24
+ Wrap your app with `QueryClientProvider`:
25
+
26
+ ```tsx
27
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
28
+ import { createClient } from '@richie-rpc/client';
29
+ import { createHooks } from '@richie-rpc/react-query';
30
+ import { contract } from './contract';
31
+
32
+ // Create client and hooks
33
+ const client = createClient(contract, {
34
+ baseUrl: 'http://localhost:3000',
35
+ });
36
+
37
+ const hooks = createHooks(client, contract);
38
+
39
+ // Create query client
40
+ const queryClient = new QueryClient();
41
+
42
+ function App() {
43
+ return (
44
+ <QueryClientProvider client={queryClient}>
45
+ <YourApp />
46
+ </QueryClientProvider>
47
+ );
48
+ }
49
+ ```
50
+
51
+ ### 2. Use Query Hooks (GET requests)
52
+
53
+ Query hooks automatically fetch data when the component mounts:
54
+
55
+ ```tsx
56
+ function UserList() {
57
+ const { data, isLoading, error, refetch } = hooks.listUsers.useQuery({
58
+ query: { limit: "10", offset: "0" }
59
+ });
60
+
61
+ if (isLoading) return <div>Loading...</div>;
62
+ if (error) return <div>Error: {error.message}</div>;
63
+
64
+ return (
65
+ <div>
66
+ {data.data.users.map(user => (
67
+ <div key={user.id}>{user.name}</div>
68
+ ))}
69
+ <button onClick={() => refetch()}>Refresh</button>
70
+ </div>
71
+ );
72
+ }
73
+ ```
74
+
75
+ ### 3. Use Suspense Queries
76
+
77
+ For React Suspense integration:
78
+
79
+ ```tsx
80
+ function UserListSuspense() {
81
+ // This will suspend the component until data is loaded
82
+ const { data } = hooks.listUsers.useSuspenseQuery({
83
+ query: { limit: "10" }
84
+ });
85
+
86
+ return (
87
+ <div>
88
+ {data.data.users.map(user => (
89
+ <div key={user.id}>{user.name}</div>
90
+ ))}
91
+ </div>
92
+ );
93
+ }
94
+
95
+ // Wrap with Suspense boundary
96
+ function App() {
97
+ return (
98
+ <Suspense fallback={<div>Loading...</div>}>
99
+ <UserListSuspense />
100
+ </Suspense>
101
+ );
102
+ }
103
+ ```
104
+
105
+ ### 4. Use Mutation Hooks (POST/PUT/PATCH/DELETE)
106
+
107
+ Mutation hooks don't auto-fetch; they return a function to trigger the request:
108
+
109
+ ```tsx
110
+ function CreateUserForm() {
111
+ const mutation = hooks.createUser.useMutation({
112
+ onSuccess: (data) => {
113
+ console.log('User created:', data);
114
+ // Invalidate and refetch
115
+ queryClient.invalidateQueries({ queryKey: ['listUsers'] });
116
+ },
117
+ onError: (error) => {
118
+ console.error('Failed to create user:', error);
119
+ },
120
+ });
121
+
122
+ const handleSubmit = (e: FormEvent) => {
123
+ e.preventDefault();
124
+ mutation.mutate({
125
+ body: {
126
+ name: "Alice",
127
+ email: "alice@example.com",
128
+ age: 25,
129
+ }
130
+ });
131
+ };
132
+
133
+ return (
134
+ <form onSubmit={handleSubmit}>
135
+ <button
136
+ type="submit"
137
+ disabled={mutation.isPending}
138
+ >
139
+ {mutation.isPending ? 'Creating...' : 'Create User'}
140
+ </button>
141
+ {mutation.error && <div>Error: {mutation.error.message}</div>}
142
+ </form>
143
+ );
144
+ }
145
+ ```
146
+
147
+ ## API Reference
148
+
149
+ ### `createHooks(client, contract)`
150
+
151
+ Creates a typed hooks object from a client and contract.
152
+
153
+ **Parameters:**
154
+ - `client`: Client created with `createClient()`
155
+ - `contract`: Your API contract definition
156
+
157
+ **Returns:** Hooks object with methods for each endpoint
158
+
159
+ ### Query Hooks (GET/HEAD methods)
160
+
161
+ #### `hooks.endpointName.useQuery(options, queryOptions?)`
162
+
163
+ Standard query hook for read operations.
164
+
165
+ **Parameters:**
166
+ - `options`: Request options (params, query, headers, body)
167
+ - `queryOptions`: Optional TanStack Query options (staleTime, cacheTime, etc.)
168
+
169
+ **Returns:** `UseQueryResult` with data, isLoading, error, refetch, etc.
170
+
171
+ #### `hooks.endpointName.useSuspenseQuery(options, queryOptions?)`
172
+
173
+ Suspense-enabled query hook.
174
+
175
+ **Parameters:**
176
+ - `options`: Request options (params, query, headers, body)
177
+ - `queryOptions`: Optional TanStack Query options
178
+
179
+ **Returns:** `UseSuspenseQueryResult` with data (always defined when rendered)
180
+
181
+ ### Mutation Hooks (POST/PUT/PATCH/DELETE methods)
182
+
183
+ #### `hooks.endpointName.useMutation(mutationOptions?)`
184
+
185
+ Mutation hook for write operations.
186
+
187
+ **Parameters:**
188
+ - `mutationOptions`: Optional TanStack Query mutation options (onSuccess, onError, etc.)
189
+
190
+ **Returns:** `UseMutationResult` with mutate, isPending, error, data, etc.
191
+
192
+ ## Advanced Usage
193
+
194
+ ### Custom Query Options
195
+
196
+ Pass TanStack Query options for fine-grained control:
197
+
198
+ ```tsx
199
+ const { data } = hooks.listUsers.useQuery(
200
+ { query: { limit: "10" } },
201
+ {
202
+ staleTime: 5 * 60 * 1000, // 5 minutes
203
+ cacheTime: 10 * 60 * 1000, // 10 minutes
204
+ refetchInterval: 30000, // Refetch every 30 seconds
205
+ refetchOnWindowFocus: false,
206
+ }
207
+ );
208
+ ```
209
+
210
+ ### Invalidating Queries
211
+
212
+ After a mutation, invalidate related queries to trigger refetch:
213
+
214
+ ```tsx
215
+ import { useQueryClient } from '@tanstack/react-query';
216
+
217
+ function DeleteUserButton({ userId }: { userId: string }) {
218
+ const queryClient = useQueryClient();
219
+
220
+ const mutation = hooks.deleteUser.useMutation({
221
+ onSuccess: () => {
222
+ // Invalidate all queries that start with 'listUsers'
223
+ queryClient.invalidateQueries({ queryKey: ['listUsers'] });
224
+
225
+ // Or invalidate specific query
226
+ queryClient.invalidateQueries({
227
+ queryKey: ['getUser', { params: { id: userId } }]
228
+ });
229
+ },
230
+ });
231
+
232
+ return (
233
+ <button onClick={() => mutation.mutate({ params: { id: userId } })}>
234
+ Delete User
235
+ </button>
236
+ );
237
+ }
238
+ ```
239
+
240
+ ### Optimistic Updates
241
+
242
+ Update the UI immediately before the server responds:
243
+
244
+ ```tsx
245
+ const mutation = hooks.updateUser.useMutation({
246
+ onMutate: async (variables) => {
247
+ // Cancel outgoing refetches
248
+ await queryClient.cancelQueries({ queryKey: ['getUser'] });
249
+
250
+ // Snapshot previous value
251
+ const previousUser = queryClient.getQueryData(['getUser', { params: { id: userId } }]);
252
+
253
+ // Optimistically update
254
+ queryClient.setQueryData(['getUser', { params: { id: userId } }], (old) => ({
255
+ ...old,
256
+ data: { ...old.data, ...variables.body }
257
+ }));
258
+
259
+ return { previousUser };
260
+ },
261
+ onError: (err, variables, context) => {
262
+ // Rollback on error
263
+ if (context?.previousUser) {
264
+ queryClient.setQueryData(
265
+ ['getUser', { params: { id: userId } }],
266
+ context.previousUser
267
+ );
268
+ }
269
+ },
270
+ onSettled: () => {
271
+ queryClient.invalidateQueries({ queryKey: ['getUser'] });
272
+ },
273
+ });
274
+ ```
275
+
276
+ ### Dependent Queries
277
+
278
+ Enable queries only when conditions are met:
279
+
280
+ ```tsx
281
+ function UserPosts({ userId }: { userId: string | null }) {
282
+ const { data } = hooks.getUserPosts.useQuery(
283
+ { params: { userId: userId! } },
284
+ {
285
+ enabled: !!userId, // Only fetch when userId is available
286
+ }
287
+ );
288
+
289
+ // ...
290
+ }
291
+ ```
292
+
293
+ ### Parallel Queries
294
+
295
+ Fetch multiple queries at once:
296
+
297
+ ```tsx
298
+ function Dashboard() {
299
+ const users = hooks.listUsers.useQuery({ query: {} });
300
+ const stats = hooks.getStats.useQuery({});
301
+ const settings = hooks.getSettings.useQuery({});
302
+
303
+ if (users.isLoading || stats.isLoading || settings.isLoading) {
304
+ return <div>Loading...</div>;
305
+ }
306
+
307
+ // All queries are fetched in parallel
308
+ return <div>Dashboard with {users.data.data.total} users</div>;
309
+ }
310
+ ```
311
+
312
+ ## TypeScript Tips
313
+
314
+ ### Inferring Types
315
+
316
+ Extract types from your hooks:
317
+
318
+ ```tsx
319
+ import type { EndpointResponse } from '@richie-rpc/client';
320
+ import type { contract } from './contract';
321
+
322
+ // Get the response type for an endpoint
323
+ type UserListResponse = EndpointResponse<typeof contract.listUsers>;
324
+
325
+ // Or extract from hook result
326
+ type UserData = Awaited<ReturnType<typeof hooks.listUsers.useQuery>>['data'];
327
+ ```
328
+
329
+ ### Type-Safe Query Keys
330
+
331
+ Create a helper for consistent query keys:
332
+
333
+ ```tsx
334
+ const queryKeys = {
335
+ listUsers: (query: { limit?: string; offset?: string }) =>
336
+ ['listUsers', { query }] as const,
337
+ getUser: (id: string) =>
338
+ ['getUser', { params: { id } }] as const,
339
+ };
340
+
341
+ // Use in invalidation
342
+ queryClient.invalidateQueries({ queryKey: queryKeys.listUsers({}) });
343
+ ```
344
+
345
+ ## Best Practices
346
+
347
+ 1. **Create hooks once**: Create the hooks object at the module level, not inside components
348
+ 2. **Use Suspense for loading states**: Cleaner than manual loading state management
349
+ 3. **Invalidate related queries**: After mutations, invalidate queries that may be affected
350
+ 4. **Set appropriate staleTime**: Reduce unnecessary refetches by setting staleTime
351
+ 5. **Handle errors with Error Boundaries**: Use React Error Boundaries with Suspense queries
352
+
353
+ ## Examples
354
+
355
+ See the `packages/demo` directory for complete working examples.
356
+
357
+ ## License
358
+
359
+ MIT
360
+
@@ -0,0 +1,74 @@
1
+ // @bun @bun-cjs
2
+ (function(exports, require, module, __filename, __dirname) {var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __moduleCache = /* @__PURE__ */ new WeakMap;
7
+ var __toCommonJS = (from) => {
8
+ var entry = __moduleCache.get(from), desc;
9
+ if (entry)
10
+ return entry;
11
+ entry = __defProp({}, "__esModule", { value: true });
12
+ if (from && typeof from === "object" || typeof from === "function")
13
+ __getOwnPropNames(from).map((key) => !__hasOwnProp.call(entry, key) && __defProp(entry, key, {
14
+ get: () => from[key],
15
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
16
+ }));
17
+ __moduleCache.set(from, entry);
18
+ return entry;
19
+ };
20
+ var __export = (target, all) => {
21
+ for (var name in all)
22
+ __defProp(target, name, {
23
+ get: all[name],
24
+ enumerable: true,
25
+ configurable: true,
26
+ set: (newValue) => all[name] = () => newValue
27
+ });
28
+ };
29
+
30
+ // packages/react-query/index.ts
31
+ var exports_react_query = {};
32
+ __export(exports_react_query, {
33
+ createHooks: () => createHooks
34
+ });
35
+ module.exports = __toCommonJS(exports_react_query);
36
+ var import_react_query = require("@tanstack/react-query");
37
+ function createHooks(client, contract) {
38
+ const hooks = {};
39
+ for (const [name, endpoint] of Object.entries(contract)) {
40
+ const method = endpoint.method;
41
+ const clientMethod = client[name];
42
+ if (method === "GET" || method === "HEAD") {
43
+ hooks[name] = {
44
+ useQuery: (options, queryOptions) => {
45
+ return import_react_query.useQuery({
46
+ queryKey: [name, options],
47
+ queryFn: () => clientMethod(options),
48
+ ...queryOptions
49
+ });
50
+ },
51
+ useSuspenseQuery: (options, queryOptions) => {
52
+ return import_react_query.useSuspenseQuery({
53
+ queryKey: [name, options],
54
+ queryFn: () => clientMethod(options),
55
+ ...queryOptions
56
+ });
57
+ }
58
+ };
59
+ } else {
60
+ hooks[name] = {
61
+ useMutation: (mutationOptions) => {
62
+ return import_react_query.useMutation({
63
+ mutationFn: (options) => clientMethod(options),
64
+ ...mutationOptions
65
+ });
66
+ }
67
+ };
68
+ }
69
+ }
70
+ return hooks;
71
+ }
72
+ })
73
+
74
+ //# debugId=CC8C1467648847F064756E2164756E21
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../index.ts"],
4
+ "sourcesContent": [
5
+ "import type {\n Client,\n ClientMethod,\n EndpointRequestOptions,\n EndpointResponse,\n} from '@richie-rpc/client';\nimport type { Contract, EndpointDefinition } from '@richie-rpc/core';\nimport {\n type UseMutationOptions,\n type UseMutationResult,\n type UseQueryOptions,\n type UseQueryResult,\n type UseSuspenseQueryOptions,\n type UseSuspenseQueryResult,\n useMutation,\n useQuery,\n useSuspenseQuery,\n} from '@tanstack/react-query';\n\n// HTTP methods that should use query hooks (read operations)\ntype QueryMethods = 'GET' | 'HEAD';\n\n// HTTP methods that should use mutation hooks (write operations)\ntype MutationMethods = 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS';\n\n/**\n * Hook wrapper for query endpoints (GET, HEAD)\n * Provides useQuery and useSuspenseQuery methods\n */\nexport type QueryHook<T extends EndpointDefinition> = {\n /**\n * Standard query hook that returns loading states\n */\n useQuery: (\n options: EndpointRequestOptions<T>,\n queryOptions?: Omit<UseQueryOptions<EndpointResponse<T>, Error>, 'queryKey' | 'queryFn'>,\n ) => UseQueryResult<EndpointResponse<T>, Error>;\n\n /**\n * Suspense-enabled query hook that throws promises for React Suspense\n */\n useSuspenseQuery: (\n options: EndpointRequestOptions<T>,\n queryOptions?: Omit<\n UseSuspenseQueryOptions<EndpointResponse<T>, Error>,\n 'queryKey' | 'queryFn'\n >,\n ) => UseSuspenseQueryResult<EndpointResponse<T>, Error>;\n};\n\n/**\n * Hook wrapper for mutation endpoints (POST, PUT, PATCH, DELETE)\n * Provides useMutation method\n */\nexport type MutationHook<T extends EndpointDefinition> = {\n /**\n * Mutation hook for write operations\n */\n useMutation: (\n mutationOptions?: Omit<\n UseMutationOptions<EndpointResponse<T>, Error, EndpointRequestOptions<T>>,\n 'mutationFn'\n >,\n ) => UseMutationResult<EndpointResponse<T>, Error, EndpointRequestOptions<T>>;\n};\n\n/**\n * Conditionally apply hook type based on HTTP method\n */\nexport type EndpointHook<T extends EndpointDefinition> = T['method'] extends QueryMethods\n ? QueryHook<T>\n : T['method'] extends MutationMethods\n ? MutationHook<T>\n : never;\n\n/**\n * Complete hooks object for a contract\n * Each endpoint gets appropriate hooks based on its HTTP method\n */\nexport type Hooks<T extends Contract> = {\n [K in keyof T]: EndpointHook<T[K]>;\n};\n\n/**\n * Create typed React hooks for all endpoints in a contract\n *\n * Query endpoints (GET, HEAD) get useQuery and useSuspenseQuery methods\n * Mutation endpoints (POST, PUT, PATCH, DELETE) get useMutation method\n *\n * @param client - The typed client created with createClient()\n * @param contract - The contract definition\n * @returns Hooks object with methods for each endpoint\n *\n * @example\n * ```tsx\n * const client = createClient(contract, { baseUrl: 'http://localhost:3000' });\n * const hooks = createHooks(client, contract);\n *\n * // In a component - Query\n * function UserList() {\n * const { data, isLoading } = hooks.listUsers.useQuery({\n * query: { limit: \"10\" }\n * });\n * // ...\n * }\n *\n * // In a component - Mutation\n * function CreateUser() {\n * const mutation = hooks.createUser.useMutation();\n * return (\n * <button onClick={() => mutation.mutate({\n * body: { name: \"Alice\", email: \"alice@example.com\" }\n * })}>\n * Create User\n * </button>\n * );\n * }\n * ```\n */\nexport function createHooks<T extends Contract>(client: Client<T>, contract: T): Hooks<T> {\n const hooks: Record<string, unknown> = {};\n\n for (const [name, endpoint] of Object.entries(contract)) {\n const method = endpoint.method;\n const clientMethod = client[name as keyof T] as unknown as ClientMethod<EndpointDefinition>;\n\n if (method === 'GET' || method === 'HEAD') {\n // Create query hooks for read operations\n hooks[name] = {\n useQuery: (\n options: EndpointRequestOptions<EndpointDefinition>,\n queryOptions?: Omit<\n UseQueryOptions<EndpointResponse<EndpointDefinition>, Error>,\n 'queryKey' | 'queryFn'\n >,\n ) => {\n return useQuery({\n queryKey: [name, options],\n queryFn: () => clientMethod(options),\n ...queryOptions,\n });\n },\n useSuspenseQuery: (\n options: EndpointRequestOptions<EndpointDefinition>,\n queryOptions?: Omit<\n UseSuspenseQueryOptions<EndpointResponse<EndpointDefinition>, Error>,\n 'queryKey' | 'queryFn'\n >,\n ) => {\n return useSuspenseQuery({\n queryKey: [name, options],\n queryFn: () => clientMethod(options),\n ...queryOptions,\n });\n },\n };\n } else {\n // Create mutation hooks for write operations\n hooks[name] = {\n useMutation: (\n mutationOptions?: Omit<\n UseMutationOptions<\n EndpointResponse<EndpointDefinition>,\n Error,\n EndpointRequestOptions<EndpointDefinition>\n >,\n 'mutationFn'\n >,\n ) => {\n return useMutation({\n mutationFn: (options: EndpointRequestOptions<EndpointDefinition>) =>\n clientMethod(options),\n ...mutationOptions,\n });\n },\n };\n }\n }\n\n return hooks as Hooks<T>;\n}\n"
6
+ ],
7
+ "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiBO,IAVP;AAgHO,SAAS,WAA+B,CAAC,QAAmB,UAAuB;AAAA,EACxF,MAAM,QAAiC,CAAC;AAAA,EAExC,YAAY,MAAM,aAAa,OAAO,QAAQ,QAAQ,GAAG;AAAA,IACvD,MAAM,SAAS,SAAS;AAAA,IACxB,MAAM,eAAe,OAAO;AAAA,IAE5B,IAAI,WAAW,SAAS,WAAW,QAAQ;AAAA,MAEzC,MAAM,QAAQ;AAAA,QACZ,UAAU,CACR,SACA,iBAIG;AAAA,UACH,OAAO,4BAAS;AAAA,YACd,UAAU,CAAC,MAAM,OAAO;AAAA,YACxB,SAAS,MAAM,aAAa,OAAO;AAAA,eAChC;AAAA,UACL,CAAC;AAAA;AAAA,QAEH,kBAAkB,CAChB,SACA,iBAIG;AAAA,UACH,OAAO,oCAAiB;AAAA,YACtB,UAAU,CAAC,MAAM,OAAO;AAAA,YACxB,SAAS,MAAM,aAAa,OAAO;AAAA,eAChC;AAAA,UACL,CAAC;AAAA;AAAA,MAEL;AAAA,IACF,EAAO;AAAA,MAEL,MAAM,QAAQ;AAAA,QACZ,aAAa,CACX,oBAQG;AAAA,UACH,OAAO,+BAAY;AAAA,YACjB,YAAY,CAAC,YACX,aAAa,OAAO;AAAA,eACnB;AAAA,UACL,CAAC;AAAA;AAAA,MAEL;AAAA;AAAA,EAEJ;AAAA,EAEA,OAAO;AAAA;",
8
+ "debugId": "CC8C1467648847F064756E2164756E21",
9
+ "names": []
10
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "@richie-rpc/react-query",
3
+ "version": "1.0.0",
4
+ "type": "commonjs"
5
+ }
@@ -0,0 +1,47 @@
1
+ // @bun
2
+ // packages/react-query/index.ts
3
+ import {
4
+ useMutation,
5
+ useQuery,
6
+ useSuspenseQuery
7
+ } from "@tanstack/react-query";
8
+ function createHooks(client, contract) {
9
+ const hooks = {};
10
+ for (const [name, endpoint] of Object.entries(contract)) {
11
+ const method = endpoint.method;
12
+ const clientMethod = client[name];
13
+ if (method === "GET" || method === "HEAD") {
14
+ hooks[name] = {
15
+ useQuery: (options, queryOptions) => {
16
+ return useQuery({
17
+ queryKey: [name, options],
18
+ queryFn: () => clientMethod(options),
19
+ ...queryOptions
20
+ });
21
+ },
22
+ useSuspenseQuery: (options, queryOptions) => {
23
+ return useSuspenseQuery({
24
+ queryKey: [name, options],
25
+ queryFn: () => clientMethod(options),
26
+ ...queryOptions
27
+ });
28
+ }
29
+ };
30
+ } else {
31
+ hooks[name] = {
32
+ useMutation: (mutationOptions) => {
33
+ return useMutation({
34
+ mutationFn: (options) => clientMethod(options),
35
+ ...mutationOptions
36
+ });
37
+ }
38
+ };
39
+ }
40
+ }
41
+ return hooks;
42
+ }
43
+ export {
44
+ createHooks
45
+ };
46
+
47
+ //# debugId=579950B8ADA2DE1A64756E2164756E21
@@ -0,0 +1,10 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../index.ts"],
4
+ "sourcesContent": [
5
+ "import type {\n Client,\n ClientMethod,\n EndpointRequestOptions,\n EndpointResponse,\n} from '@richie-rpc/client';\nimport type { Contract, EndpointDefinition } from '@richie-rpc/core';\nimport {\n type UseMutationOptions,\n type UseMutationResult,\n type UseQueryOptions,\n type UseQueryResult,\n type UseSuspenseQueryOptions,\n type UseSuspenseQueryResult,\n useMutation,\n useQuery,\n useSuspenseQuery,\n} from '@tanstack/react-query';\n\n// HTTP methods that should use query hooks (read operations)\ntype QueryMethods = 'GET' | 'HEAD';\n\n// HTTP methods that should use mutation hooks (write operations)\ntype MutationMethods = 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS';\n\n/**\n * Hook wrapper for query endpoints (GET, HEAD)\n * Provides useQuery and useSuspenseQuery methods\n */\nexport type QueryHook<T extends EndpointDefinition> = {\n /**\n * Standard query hook that returns loading states\n */\n useQuery: (\n options: EndpointRequestOptions<T>,\n queryOptions?: Omit<UseQueryOptions<EndpointResponse<T>, Error>, 'queryKey' | 'queryFn'>,\n ) => UseQueryResult<EndpointResponse<T>, Error>;\n\n /**\n * Suspense-enabled query hook that throws promises for React Suspense\n */\n useSuspenseQuery: (\n options: EndpointRequestOptions<T>,\n queryOptions?: Omit<\n UseSuspenseQueryOptions<EndpointResponse<T>, Error>,\n 'queryKey' | 'queryFn'\n >,\n ) => UseSuspenseQueryResult<EndpointResponse<T>, Error>;\n};\n\n/**\n * Hook wrapper for mutation endpoints (POST, PUT, PATCH, DELETE)\n * Provides useMutation method\n */\nexport type MutationHook<T extends EndpointDefinition> = {\n /**\n * Mutation hook for write operations\n */\n useMutation: (\n mutationOptions?: Omit<\n UseMutationOptions<EndpointResponse<T>, Error, EndpointRequestOptions<T>>,\n 'mutationFn'\n >,\n ) => UseMutationResult<EndpointResponse<T>, Error, EndpointRequestOptions<T>>;\n};\n\n/**\n * Conditionally apply hook type based on HTTP method\n */\nexport type EndpointHook<T extends EndpointDefinition> = T['method'] extends QueryMethods\n ? QueryHook<T>\n : T['method'] extends MutationMethods\n ? MutationHook<T>\n : never;\n\n/**\n * Complete hooks object for a contract\n * Each endpoint gets appropriate hooks based on its HTTP method\n */\nexport type Hooks<T extends Contract> = {\n [K in keyof T]: EndpointHook<T[K]>;\n};\n\n/**\n * Create typed React hooks for all endpoints in a contract\n *\n * Query endpoints (GET, HEAD) get useQuery and useSuspenseQuery methods\n * Mutation endpoints (POST, PUT, PATCH, DELETE) get useMutation method\n *\n * @param client - The typed client created with createClient()\n * @param contract - The contract definition\n * @returns Hooks object with methods for each endpoint\n *\n * @example\n * ```tsx\n * const client = createClient(contract, { baseUrl: 'http://localhost:3000' });\n * const hooks = createHooks(client, contract);\n *\n * // In a component - Query\n * function UserList() {\n * const { data, isLoading } = hooks.listUsers.useQuery({\n * query: { limit: \"10\" }\n * });\n * // ...\n * }\n *\n * // In a component - Mutation\n * function CreateUser() {\n * const mutation = hooks.createUser.useMutation();\n * return (\n * <button onClick={() => mutation.mutate({\n * body: { name: \"Alice\", email: \"alice@example.com\" }\n * })}>\n * Create User\n * </button>\n * );\n * }\n * ```\n */\nexport function createHooks<T extends Contract>(client: Client<T>, contract: T): Hooks<T> {\n const hooks: Record<string, unknown> = {};\n\n for (const [name, endpoint] of Object.entries(contract)) {\n const method = endpoint.method;\n const clientMethod = client[name as keyof T] as unknown as ClientMethod<EndpointDefinition>;\n\n if (method === 'GET' || method === 'HEAD') {\n // Create query hooks for read operations\n hooks[name] = {\n useQuery: (\n options: EndpointRequestOptions<EndpointDefinition>,\n queryOptions?: Omit<\n UseQueryOptions<EndpointResponse<EndpointDefinition>, Error>,\n 'queryKey' | 'queryFn'\n >,\n ) => {\n return useQuery({\n queryKey: [name, options],\n queryFn: () => clientMethod(options),\n ...queryOptions,\n });\n },\n useSuspenseQuery: (\n options: EndpointRequestOptions<EndpointDefinition>,\n queryOptions?: Omit<\n UseSuspenseQueryOptions<EndpointResponse<EndpointDefinition>, Error>,\n 'queryKey' | 'queryFn'\n >,\n ) => {\n return useSuspenseQuery({\n queryKey: [name, options],\n queryFn: () => clientMethod(options),\n ...queryOptions,\n });\n },\n };\n } else {\n // Create mutation hooks for write operations\n hooks[name] = {\n useMutation: (\n mutationOptions?: Omit<\n UseMutationOptions<\n EndpointResponse<EndpointDefinition>,\n Error,\n EndpointRequestOptions<EndpointDefinition>\n >,\n 'mutationFn'\n >,\n ) => {\n return useMutation({\n mutationFn: (options: EndpointRequestOptions<EndpointDefinition>) =>\n clientMethod(options),\n ...mutationOptions,\n });\n },\n };\n }\n }\n\n return hooks as Hooks<T>;\n}\n"
6
+ ],
7
+ "mappings": ";;AAOA;AAAA;AAAA;AAAA;AAAA;AAgHO,SAAS,WAA+B,CAAC,QAAmB,UAAuB;AAAA,EACxF,MAAM,QAAiC,CAAC;AAAA,EAExC,YAAY,MAAM,aAAa,OAAO,QAAQ,QAAQ,GAAG;AAAA,IACvD,MAAM,SAAS,SAAS;AAAA,IACxB,MAAM,eAAe,OAAO;AAAA,IAE5B,IAAI,WAAW,SAAS,WAAW,QAAQ;AAAA,MAEzC,MAAM,QAAQ;AAAA,QACZ,UAAU,CACR,SACA,iBAIG;AAAA,UACH,OAAO,SAAS;AAAA,YACd,UAAU,CAAC,MAAM,OAAO;AAAA,YACxB,SAAS,MAAM,aAAa,OAAO;AAAA,eAChC;AAAA,UACL,CAAC;AAAA;AAAA,QAEH,kBAAkB,CAChB,SACA,iBAIG;AAAA,UACH,OAAO,iBAAiB;AAAA,YACtB,UAAU,CAAC,MAAM,OAAO;AAAA,YACxB,SAAS,MAAM,aAAa,OAAO;AAAA,eAChC;AAAA,UACL,CAAC;AAAA;AAAA,MAEL;AAAA,IACF,EAAO;AAAA,MAEL,MAAM,QAAQ;AAAA,QACZ,aAAa,CACX,oBAQG;AAAA,UACH,OAAO,YAAY;AAAA,YACjB,YAAY,CAAC,YACX,aAAa,OAAO;AAAA,eACnB;AAAA,UACL,CAAC;AAAA;AAAA,MAEL;AAAA;AAAA,EAEJ;AAAA,EAEA,OAAO;AAAA;",
8
+ "debugId": "579950B8ADA2DE1A64756E2164756E21",
9
+ "names": []
10
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "@richie-rpc/react-query",
3
+ "version": "1.0.0",
4
+ "type": "module"
5
+ }
@@ -0,0 +1,78 @@
1
+ import type { Client, EndpointRequestOptions, EndpointResponse } from '@richie-rpc/client';
2
+ import type { Contract, EndpointDefinition } from '@richie-rpc/core';
3
+ import { type UseMutationOptions, type UseMutationResult, type UseQueryOptions, type UseQueryResult, type UseSuspenseQueryOptions, type UseSuspenseQueryResult } from '@tanstack/react-query';
4
+ type QueryMethods = 'GET' | 'HEAD';
5
+ type MutationMethods = 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS';
6
+ /**
7
+ * Hook wrapper for query endpoints (GET, HEAD)
8
+ * Provides useQuery and useSuspenseQuery methods
9
+ */
10
+ export type QueryHook<T extends EndpointDefinition> = {
11
+ /**
12
+ * Standard query hook that returns loading states
13
+ */
14
+ useQuery: (options: EndpointRequestOptions<T>, queryOptions?: Omit<UseQueryOptions<EndpointResponse<T>, Error>, 'queryKey' | 'queryFn'>) => UseQueryResult<EndpointResponse<T>, Error>;
15
+ /**
16
+ * Suspense-enabled query hook that throws promises for React Suspense
17
+ */
18
+ useSuspenseQuery: (options: EndpointRequestOptions<T>, queryOptions?: Omit<UseSuspenseQueryOptions<EndpointResponse<T>, Error>, 'queryKey' | 'queryFn'>) => UseSuspenseQueryResult<EndpointResponse<T>, Error>;
19
+ };
20
+ /**
21
+ * Hook wrapper for mutation endpoints (POST, PUT, PATCH, DELETE)
22
+ * Provides useMutation method
23
+ */
24
+ export type MutationHook<T extends EndpointDefinition> = {
25
+ /**
26
+ * Mutation hook for write operations
27
+ */
28
+ useMutation: (mutationOptions?: Omit<UseMutationOptions<EndpointResponse<T>, Error, EndpointRequestOptions<T>>, 'mutationFn'>) => UseMutationResult<EndpointResponse<T>, Error, EndpointRequestOptions<T>>;
29
+ };
30
+ /**
31
+ * Conditionally apply hook type based on HTTP method
32
+ */
33
+ export type EndpointHook<T extends EndpointDefinition> = T['method'] extends QueryMethods ? QueryHook<T> : T['method'] extends MutationMethods ? MutationHook<T> : never;
34
+ /**
35
+ * Complete hooks object for a contract
36
+ * Each endpoint gets appropriate hooks based on its HTTP method
37
+ */
38
+ export type Hooks<T extends Contract> = {
39
+ [K in keyof T]: EndpointHook<T[K]>;
40
+ };
41
+ /**
42
+ * Create typed React hooks for all endpoints in a contract
43
+ *
44
+ * Query endpoints (GET, HEAD) get useQuery and useSuspenseQuery methods
45
+ * Mutation endpoints (POST, PUT, PATCH, DELETE) get useMutation method
46
+ *
47
+ * @param client - The typed client created with createClient()
48
+ * @param contract - The contract definition
49
+ * @returns Hooks object with methods for each endpoint
50
+ *
51
+ * @example
52
+ * ```tsx
53
+ * const client = createClient(contract, { baseUrl: 'http://localhost:3000' });
54
+ * const hooks = createHooks(client, contract);
55
+ *
56
+ * // In a component - Query
57
+ * function UserList() {
58
+ * const { data, isLoading } = hooks.listUsers.useQuery({
59
+ * query: { limit: "10" }
60
+ * });
61
+ * // ...
62
+ * }
63
+ *
64
+ * // In a component - Mutation
65
+ * function CreateUser() {
66
+ * const mutation = hooks.createUser.useMutation();
67
+ * return (
68
+ * <button onClick={() => mutation.mutate({
69
+ * body: { name: "Alice", email: "alice@example.com" }
70
+ * })}>
71
+ * Create User
72
+ * </button>
73
+ * );
74
+ * }
75
+ * ```
76
+ */
77
+ export declare function createHooks<T extends Contract>(client: Client<T>, contract: T): Hooks<T>;
78
+ export {};
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "@richie-rpc/react-query",
3
+ "version": "1.0.0",
4
+ "main": "./dist/cjs/index.cjs",
5
+ "exports": {
6
+ ".": {
7
+ "types": "./dist/types/index.d.ts",
8
+ "require": "./dist/cjs/index.cjs",
9
+ "import": "./dist/mjs/index.mjs"
10
+ }
11
+ },
12
+ "peerDependencies": {
13
+ "@richie-rpc/client": "^1.2.0",
14
+ "@richie-rpc/core": "^1.2.0",
15
+ "@tanstack/react-query": "^5.0.0",
16
+ "react": "^18.0.0 || ^19.0.0",
17
+ "typescript": "^5",
18
+ "zod": "^4.1.12"
19
+ },
20
+ "author": "Richie <oss@ricsam.dev>",
21
+ "license": "MIT",
22
+ "repository": {
23
+ "type": "git",
24
+ "url": "git+https://github.com/ricsam/richie-rpc.git"
25
+ },
26
+ "bugs": {
27
+ "url": "https://github.com/ricsam/richie-rpc/issues"
28
+ },
29
+ "homepage": "https://github.com/ricsam/richie-rpc#readme",
30
+ "keywords": [
31
+ "typescript",
32
+ "bun",
33
+ "zod",
34
+ "api",
35
+ "contract",
36
+ "rpc",
37
+ "rest",
38
+ "openapi",
39
+ "type-safe"
40
+ ],
41
+ "description": "A TypeScript-first, type-safe API contract library for Bun with Zod validation",
42
+ "module": "./dist/mjs/index.mjs",
43
+ "types": "./dist/types/index.d.ts",
44
+ "publishConfig": {
45
+ "access": "public"
46
+ },
47
+ "files": [
48
+ "dist",
49
+ "README.md"
50
+ ]
51
+ }