@richie-rpc/react-query 1.0.3 → 1.0.5
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 +24 -33
- package/dist/cjs/index.cjs +15 -1
- package/dist/cjs/index.cjs.map +3 -3
- package/dist/cjs/package.json +1 -1
- package/dist/mjs/index.mjs +15 -1
- package/dist/mjs/index.mjs.map +3 -3
- package/dist/mjs/package.json +1 -1
- package/dist/types/index.d.ts +29 -7
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -55,7 +55,7 @@ Query hooks automatically fetch data when the component mounts:
|
|
|
55
55
|
```tsx
|
|
56
56
|
function UserList() {
|
|
57
57
|
const { data, isLoading, error, refetch } = hooks.listUsers.useQuery({
|
|
58
|
-
query: { limit:
|
|
58
|
+
query: { limit: '10', offset: '0' },
|
|
59
59
|
});
|
|
60
60
|
|
|
61
61
|
if (isLoading) return <div>Loading...</div>;
|
|
@@ -63,7 +63,7 @@ function UserList() {
|
|
|
63
63
|
|
|
64
64
|
return (
|
|
65
65
|
<div>
|
|
66
|
-
{data.data.users.map(user => (
|
|
66
|
+
{data.data.users.map((user) => (
|
|
67
67
|
<div key={user.id}>{user.name}</div>
|
|
68
68
|
))}
|
|
69
69
|
<button onClick={() => refetch()}>Refresh</button>
|
|
@@ -80,12 +80,12 @@ For React Suspense integration:
|
|
|
80
80
|
function UserListSuspense() {
|
|
81
81
|
// This will suspend the component until data is loaded
|
|
82
82
|
const { data } = hooks.listUsers.useSuspenseQuery({
|
|
83
|
-
query: { limit:
|
|
83
|
+
query: { limit: '10' },
|
|
84
84
|
});
|
|
85
85
|
|
|
86
86
|
return (
|
|
87
87
|
<div>
|
|
88
|
-
{data.data.users.map(user => (
|
|
88
|
+
{data.data.users.map((user) => (
|
|
89
89
|
<div key={user.id}>{user.name}</div>
|
|
90
90
|
))}
|
|
91
91
|
</div>
|
|
@@ -123,19 +123,16 @@ function CreateUserForm() {
|
|
|
123
123
|
e.preventDefault();
|
|
124
124
|
mutation.mutate({
|
|
125
125
|
body: {
|
|
126
|
-
name:
|
|
127
|
-
email:
|
|
126
|
+
name: 'Alice',
|
|
127
|
+
email: 'alice@example.com',
|
|
128
128
|
age: 25,
|
|
129
|
-
}
|
|
129
|
+
},
|
|
130
130
|
});
|
|
131
131
|
};
|
|
132
132
|
|
|
133
133
|
return (
|
|
134
134
|
<form onSubmit={handleSubmit}>
|
|
135
|
-
<button
|
|
136
|
-
type="submit"
|
|
137
|
-
disabled={mutation.isPending}
|
|
138
|
-
>
|
|
135
|
+
<button type="submit" disabled={mutation.isPending}>
|
|
139
136
|
{mutation.isPending ? 'Creating...' : 'Create User'}
|
|
140
137
|
</button>
|
|
141
138
|
{mutation.error && <div>Error: {mutation.error.message}</div>}
|
|
@@ -151,6 +148,7 @@ function CreateUserForm() {
|
|
|
151
148
|
Creates a typed hooks object from a client and contract.
|
|
152
149
|
|
|
153
150
|
**Parameters:**
|
|
151
|
+
|
|
154
152
|
- `client`: Client created with `createClient()`
|
|
155
153
|
- `contract`: Your API contract definition
|
|
156
154
|
|
|
@@ -163,6 +161,7 @@ Creates a typed hooks object from a client and contract.
|
|
|
163
161
|
Standard query hook for read operations.
|
|
164
162
|
|
|
165
163
|
**Parameters:**
|
|
164
|
+
|
|
166
165
|
- `options`: Request options (params, query, headers, body)
|
|
167
166
|
- `queryOptions`: Optional TanStack Query options (staleTime, cacheTime, etc.)
|
|
168
167
|
|
|
@@ -173,6 +172,7 @@ Standard query hook for read operations.
|
|
|
173
172
|
Suspense-enabled query hook.
|
|
174
173
|
|
|
175
174
|
**Parameters:**
|
|
175
|
+
|
|
176
176
|
- `options`: Request options (params, query, headers, body)
|
|
177
177
|
- `queryOptions`: Optional TanStack Query options
|
|
178
178
|
|
|
@@ -185,6 +185,7 @@ Suspense-enabled query hook.
|
|
|
185
185
|
Mutation hook for write operations.
|
|
186
186
|
|
|
187
187
|
**Parameters:**
|
|
188
|
+
|
|
188
189
|
- `mutationOptions`: Optional TanStack Query mutation options (onSuccess, onError, etc.)
|
|
189
190
|
|
|
190
191
|
**Returns:** `UseMutationResult` with mutate, isPending, error, data, etc.
|
|
@@ -197,13 +198,13 @@ Pass TanStack Query options for fine-grained control:
|
|
|
197
198
|
|
|
198
199
|
```tsx
|
|
199
200
|
const { data } = hooks.listUsers.useQuery(
|
|
200
|
-
{ query: { limit:
|
|
201
|
+
{ query: { limit: '10' } },
|
|
201
202
|
{
|
|
202
203
|
staleTime: 5 * 60 * 1000, // 5 minutes
|
|
203
204
|
cacheTime: 10 * 60 * 1000, // 10 minutes
|
|
204
205
|
refetchInterval: 30000, // Refetch every 30 seconds
|
|
205
206
|
refetchOnWindowFocus: false,
|
|
206
|
-
}
|
|
207
|
+
},
|
|
207
208
|
);
|
|
208
209
|
```
|
|
209
210
|
|
|
@@ -216,24 +217,20 @@ import { useQueryClient } from '@tanstack/react-query';
|
|
|
216
217
|
|
|
217
218
|
function DeleteUserButton({ userId }: { userId: string }) {
|
|
218
219
|
const queryClient = useQueryClient();
|
|
219
|
-
|
|
220
|
+
|
|
220
221
|
const mutation = hooks.deleteUser.useMutation({
|
|
221
222
|
onSuccess: () => {
|
|
222
223
|
// Invalidate all queries that start with 'listUsers'
|
|
223
224
|
queryClient.invalidateQueries({ queryKey: ['listUsers'] });
|
|
224
|
-
|
|
225
|
+
|
|
225
226
|
// Or invalidate specific query
|
|
226
|
-
queryClient.invalidateQueries({
|
|
227
|
-
queryKey: ['getUser', { params: { id: userId } }]
|
|
227
|
+
queryClient.invalidateQueries({
|
|
228
|
+
queryKey: ['getUser', { params: { id: userId } }],
|
|
228
229
|
});
|
|
229
230
|
},
|
|
230
231
|
});
|
|
231
232
|
|
|
232
|
-
return (
|
|
233
|
-
<button onClick={() => mutation.mutate({ params: { id: userId } })}>
|
|
234
|
-
Delete User
|
|
235
|
-
</button>
|
|
236
|
-
);
|
|
233
|
+
return <button onClick={() => mutation.mutate({ params: { id: userId } })}>Delete User</button>;
|
|
237
234
|
}
|
|
238
235
|
```
|
|
239
236
|
|
|
@@ -253,7 +250,7 @@ const mutation = hooks.updateUser.useMutation({
|
|
|
253
250
|
// Optimistically update
|
|
254
251
|
queryClient.setQueryData(['getUser', { params: { id: userId } }], (old) => ({
|
|
255
252
|
...old,
|
|
256
|
-
data: { ...old.data, ...variables.body }
|
|
253
|
+
data: { ...old.data, ...variables.body },
|
|
257
254
|
}));
|
|
258
255
|
|
|
259
256
|
return { previousUser };
|
|
@@ -261,10 +258,7 @@ const mutation = hooks.updateUser.useMutation({
|
|
|
261
258
|
onError: (err, variables, context) => {
|
|
262
259
|
// Rollback on error
|
|
263
260
|
if (context?.previousUser) {
|
|
264
|
-
queryClient.setQueryData(
|
|
265
|
-
['getUser', { params: { id: userId } }],
|
|
266
|
-
context.previousUser
|
|
267
|
-
);
|
|
261
|
+
queryClient.setQueryData(['getUser', { params: { id: userId } }], context.previousUser);
|
|
268
262
|
}
|
|
269
263
|
},
|
|
270
264
|
onSettled: () => {
|
|
@@ -283,7 +277,7 @@ function UserPosts({ userId }: { userId: string | null }) {
|
|
|
283
277
|
{ params: { userId: userId! } },
|
|
284
278
|
{
|
|
285
279
|
enabled: !!userId, // Only fetch when userId is available
|
|
286
|
-
}
|
|
280
|
+
},
|
|
287
281
|
);
|
|
288
282
|
|
|
289
283
|
// ...
|
|
@@ -332,10 +326,8 @@ Create a helper for consistent query keys:
|
|
|
332
326
|
|
|
333
327
|
```tsx
|
|
334
328
|
const queryKeys = {
|
|
335
|
-
listUsers: (query: { limit?: string; offset?: string }) =>
|
|
336
|
-
|
|
337
|
-
getUser: (id: string) =>
|
|
338
|
-
['getUser', { params: { id } }] as const,
|
|
329
|
+
listUsers: (query: { limit?: string; offset?: string }) => ['listUsers', { query }] as const,
|
|
330
|
+
getUser: (id: string) => ['getUser', { params: { id } }] as const,
|
|
339
331
|
};
|
|
340
332
|
|
|
341
333
|
// Use in invalidation
|
|
@@ -357,4 +349,3 @@ See the `packages/demo` directory for complete working examples.
|
|
|
357
349
|
## License
|
|
358
350
|
|
|
359
351
|
MIT
|
|
360
|
-
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -37,6 +37,20 @@ var import_react_query = require("@tanstack/react-query");
|
|
|
37
37
|
function createHooks(client, contract) {
|
|
38
38
|
const hooks = {};
|
|
39
39
|
for (const [name, endpoint] of Object.entries(contract)) {
|
|
40
|
+
if (endpoint.type === "streaming") {
|
|
41
|
+
const streamMethod = client[name];
|
|
42
|
+
hooks[name] = {
|
|
43
|
+
stream: streamMethod
|
|
44
|
+
};
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (endpoint.type === "sse") {
|
|
48
|
+
const connectMethod = client[name];
|
|
49
|
+
hooks[name] = {
|
|
50
|
+
connect: connectMethod
|
|
51
|
+
};
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
40
54
|
const method = endpoint.method;
|
|
41
55
|
const clientMethod = client[name];
|
|
42
56
|
if (method === "GET" || method === "HEAD") {
|
|
@@ -83,4 +97,4 @@ function createHooks(client, contract) {
|
|
|
83
97
|
}
|
|
84
98
|
})
|
|
85
99
|
|
|
86
|
-
//# debugId=
|
|
100
|
+
//# debugId=47DF1EE9E6FD487A64756E2164756E21
|
package/dist/cjs/index.cjs.map
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../index.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import type {\n Client,\n ClientMethod,\n EndpointRequestOptions,\n EndpointResponse,\n} from '@richie-rpc/client';\nimport type {
|
|
5
|
+
"import type {\n Client,\n ClientMethod,\n EndpointRequestOptions,\n EndpointResponse,\n SSEClientMethod,\n StreamingClientMethod,\n} from '@richie-rpc/client';\nimport type {\n Contract,\n SSEEndpointDefinition,\n StandardEndpointDefinition,\n StreamingEndpointDefinition,\n} 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 StandardEndpointDefinition> = {\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 StandardEndpointDefinition> = {\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 StandardEndpointDefinition> = T['method'] extends QueryMethods\n ? QueryHook<T>\n : T['method'] extends MutationMethods\n ? MutationHook<T>\n : never;\n\n/**\n * Hook wrapper for streaming endpoints\n * Exposes the streaming client method directly since React Query\n * doesn't fit well with long-lived streaming connections\n */\nexport type StreamingHook<T extends StreamingEndpointDefinition> = {\n /**\n * Start a streaming request\n */\n stream: StreamingClientMethod<T>;\n};\n\n/**\n * Hook wrapper for SSE endpoints\n * Exposes the SSE client method directly since React Query\n * doesn't fit well with long-lived SSE connections\n */\nexport type SSEHook<T extends SSEEndpointDefinition> = {\n /**\n * Create an SSE connection\n */\n connect: SSEClientMethod<T>;\n};\n\n/**\n * Complete hooks object for a contract\n * Each endpoint gets appropriate hooks based on its type and HTTP method\n */\nexport type Hooks<T extends Contract> = {\n [K in keyof T]: T[K] extends StandardEndpointDefinition\n ? EndpointHook<T[K]>\n : T[K] extends StreamingEndpointDefinition\n ? StreamingHook<T[K]>\n : T[K] extends SSEEndpointDefinition\n ? SSEHook<T[K]>\n : never;\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 // Handle streaming endpoints\n if (endpoint.type === 'streaming') {\n const streamMethod = client[\n name as keyof T\n ] as unknown as StreamingClientMethod<StreamingEndpointDefinition>;\n hooks[name] = {\n stream: streamMethod,\n };\n continue;\n }\n\n // Handle SSE endpoints\n if (endpoint.type === 'sse') {\n const connectMethod = client[\n name as keyof T\n ] as unknown as SSEClientMethod<SSEEndpointDefinition>;\n hooks[name] = {\n connect: connectMethod,\n };\n continue;\n }\n\n // Handle standard endpoints\n const method = endpoint.method;\n const clientMethod = client[\n name as keyof T\n ] as unknown as ClientMethod<StandardEndpointDefinition>;\n\n if (method === 'GET' || method === 'HEAD') {\n // Create query hooks for read operations\n hooks[name] = {\n useQuery: (\n options: EndpointRequestOptions<StandardEndpointDefinition>,\n queryOptions?: Omit<\n UseQueryOptions<EndpointResponse<StandardEndpointDefinition>, Error>,\n 'queryKey' | 'queryFn'\n >,\n ) => {\n return useQuery({\n queryKey: [\n name,\n options.params ?? null,\n options.query ?? null,\n options.headers ?? null,\n options.body ?? null,\n ],\n queryFn: () => clientMethod(options),\n ...queryOptions,\n });\n },\n useSuspenseQuery: (\n options: EndpointRequestOptions<StandardEndpointDefinition>,\n queryOptions?: Omit<\n UseSuspenseQueryOptions<EndpointResponse<StandardEndpointDefinition>, Error>,\n 'queryKey' | 'queryFn'\n >,\n ) => {\n return useSuspenseQuery({\n queryKey: [\n name,\n options.params ?? null,\n options.query ?? null,\n options.headers ?? null,\n options.body ?? null,\n ],\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<StandardEndpointDefinition>,\n Error,\n EndpointRequestOptions<StandardEndpointDefinition>\n >,\n 'mutationFn'\n >,\n ) => {\n return useMutation({\n mutationFn: (options: EndpointRequestOptions<StandardEndpointDefinition>) =>\n clientMethod(options),\n ...mutationOptions,\n });\n },\n };\n }\n }\n\n return hooks as Hooks<T>;\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwBO,IAVP;AA8IO,SAAS,WAA+B,CAAC,QAAmB,UAAuB;AAAA,EACxF,MAAM,QAAiC,CAAC;AAAA,EAExC,YAAY,MAAM,aAAa,OAAO,QAAQ,QAAQ,GAAG;AAAA,IAEvD,IAAI,SAAS,SAAS,aAAa;AAAA,MACjC,MAAM,eAAe,OACnB;AAAA,MAEF,MAAM,QAAQ;AAAA,QACZ,QAAQ;AAAA,MACV;AAAA,MACA;AAAA,IACF;AAAA,IAGA,IAAI,SAAS,SAAS,OAAO;AAAA,MAC3B,MAAM,gBAAgB,OACpB;AAAA,MAEF,MAAM,QAAQ;AAAA,QACZ,SAAS;AAAA,MACX;AAAA,MACA;AAAA,IACF;AAAA,IAGA,MAAM,SAAS,SAAS;AAAA,IACxB,MAAM,eAAe,OACnB;AAAA,IAGF,IAAI,WAAW,SAAS,WAAW,QAAQ;AAAA,MAEzC,MAAM,QAAQ;AAAA,QACZ,UAAU,CACR,SACA,iBAIG;AAAA,UACH,OAAO,4BAAS;AAAA,YACd,UAAU;AAAA,cACR;AAAA,cACA,QAAQ,UAAU;AAAA,cAClB,QAAQ,SAAS;AAAA,cACjB,QAAQ,WAAW;AAAA,cACnB,QAAQ,QAAQ;AAAA,YAClB;AAAA,YACA,SAAS,MAAM,aAAa,OAAO;AAAA,eAChC;AAAA,UACL,CAAC;AAAA;AAAA,QAEH,kBAAkB,CAChB,SACA,iBAIG;AAAA,UACH,OAAO,oCAAiB;AAAA,YACtB,UAAU;AAAA,cACR;AAAA,cACA,QAAQ,UAAU;AAAA,cAClB,QAAQ,SAAS;AAAA,cACjB,QAAQ,WAAW;AAAA,cACnB,QAAQ,QAAQ;AAAA,YAClB;AAAA,YACA,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": "47DF1EE9E6FD487A64756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
package/dist/cjs/package.json
CHANGED
package/dist/mjs/index.mjs
CHANGED
|
@@ -8,6 +8,20 @@ import {
|
|
|
8
8
|
function createHooks(client, contract) {
|
|
9
9
|
const hooks = {};
|
|
10
10
|
for (const [name, endpoint] of Object.entries(contract)) {
|
|
11
|
+
if (endpoint.type === "streaming") {
|
|
12
|
+
const streamMethod = client[name];
|
|
13
|
+
hooks[name] = {
|
|
14
|
+
stream: streamMethod
|
|
15
|
+
};
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
if (endpoint.type === "sse") {
|
|
19
|
+
const connectMethod = client[name];
|
|
20
|
+
hooks[name] = {
|
|
21
|
+
connect: connectMethod
|
|
22
|
+
};
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
11
25
|
const method = endpoint.method;
|
|
12
26
|
const clientMethod = client[name];
|
|
13
27
|
if (method === "GET" || method === "HEAD") {
|
|
@@ -56,4 +70,4 @@ export {
|
|
|
56
70
|
createHooks
|
|
57
71
|
};
|
|
58
72
|
|
|
59
|
-
//# debugId=
|
|
73
|
+
//# debugId=47BC57BE472723C264756E2164756E21
|
package/dist/mjs/index.mjs.map
CHANGED
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../index.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"import type {\n Client,\n ClientMethod,\n EndpointRequestOptions,\n EndpointResponse,\n} from '@richie-rpc/client';\nimport type {
|
|
5
|
+
"import type {\n Client,\n ClientMethod,\n EndpointRequestOptions,\n EndpointResponse,\n SSEClientMethod,\n StreamingClientMethod,\n} from '@richie-rpc/client';\nimport type {\n Contract,\n SSEEndpointDefinition,\n StandardEndpointDefinition,\n StreamingEndpointDefinition,\n} 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 StandardEndpointDefinition> = {\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 StandardEndpointDefinition> = {\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 StandardEndpointDefinition> = T['method'] extends QueryMethods\n ? QueryHook<T>\n : T['method'] extends MutationMethods\n ? MutationHook<T>\n : never;\n\n/**\n * Hook wrapper for streaming endpoints\n * Exposes the streaming client method directly since React Query\n * doesn't fit well with long-lived streaming connections\n */\nexport type StreamingHook<T extends StreamingEndpointDefinition> = {\n /**\n * Start a streaming request\n */\n stream: StreamingClientMethod<T>;\n};\n\n/**\n * Hook wrapper for SSE endpoints\n * Exposes the SSE client method directly since React Query\n * doesn't fit well with long-lived SSE connections\n */\nexport type SSEHook<T extends SSEEndpointDefinition> = {\n /**\n * Create an SSE connection\n */\n connect: SSEClientMethod<T>;\n};\n\n/**\n * Complete hooks object for a contract\n * Each endpoint gets appropriate hooks based on its type and HTTP method\n */\nexport type Hooks<T extends Contract> = {\n [K in keyof T]: T[K] extends StandardEndpointDefinition\n ? EndpointHook<T[K]>\n : T[K] extends StreamingEndpointDefinition\n ? StreamingHook<T[K]>\n : T[K] extends SSEEndpointDefinition\n ? SSEHook<T[K]>\n : never;\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 // Handle streaming endpoints\n if (endpoint.type === 'streaming') {\n const streamMethod = client[\n name as keyof T\n ] as unknown as StreamingClientMethod<StreamingEndpointDefinition>;\n hooks[name] = {\n stream: streamMethod,\n };\n continue;\n }\n\n // Handle SSE endpoints\n if (endpoint.type === 'sse') {\n const connectMethod = client[\n name as keyof T\n ] as unknown as SSEClientMethod<SSEEndpointDefinition>;\n hooks[name] = {\n connect: connectMethod,\n };\n continue;\n }\n\n // Handle standard endpoints\n const method = endpoint.method;\n const clientMethod = client[\n name as keyof T\n ] as unknown as ClientMethod<StandardEndpointDefinition>;\n\n if (method === 'GET' || method === 'HEAD') {\n // Create query hooks for read operations\n hooks[name] = {\n useQuery: (\n options: EndpointRequestOptions<StandardEndpointDefinition>,\n queryOptions?: Omit<\n UseQueryOptions<EndpointResponse<StandardEndpointDefinition>, Error>,\n 'queryKey' | 'queryFn'\n >,\n ) => {\n return useQuery({\n queryKey: [\n name,\n options.params ?? null,\n options.query ?? null,\n options.headers ?? null,\n options.body ?? null,\n ],\n queryFn: () => clientMethod(options),\n ...queryOptions,\n });\n },\n useSuspenseQuery: (\n options: EndpointRequestOptions<StandardEndpointDefinition>,\n queryOptions?: Omit<\n UseSuspenseQueryOptions<EndpointResponse<StandardEndpointDefinition>, Error>,\n 'queryKey' | 'queryFn'\n >,\n ) => {\n return useSuspenseQuery({\n queryKey: [\n name,\n options.params ?? null,\n options.query ?? null,\n options.headers ?? null,\n options.body ?? null,\n ],\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<StandardEndpointDefinition>,\n Error,\n EndpointRequestOptions<StandardEndpointDefinition>\n >,\n 'mutationFn'\n >,\n ) => {\n return useMutation({\n mutationFn: (options: EndpointRequestOptions<StandardEndpointDefinition>) =>\n clientMethod(options),\n ...mutationOptions,\n });\n },\n };\n }\n }\n\n return hooks as Hooks<T>;\n}\n"
|
|
6
6
|
],
|
|
7
|
-
"mappings": ";;
|
|
8
|
-
"debugId": "
|
|
7
|
+
"mappings": ";;AAcA;AAAA;AAAA;AAAA;AAAA;AA8IO,SAAS,WAA+B,CAAC,QAAmB,UAAuB;AAAA,EACxF,MAAM,QAAiC,CAAC;AAAA,EAExC,YAAY,MAAM,aAAa,OAAO,QAAQ,QAAQ,GAAG;AAAA,IAEvD,IAAI,SAAS,SAAS,aAAa;AAAA,MACjC,MAAM,eAAe,OACnB;AAAA,MAEF,MAAM,QAAQ;AAAA,QACZ,QAAQ;AAAA,MACV;AAAA,MACA;AAAA,IACF;AAAA,IAGA,IAAI,SAAS,SAAS,OAAO;AAAA,MAC3B,MAAM,gBAAgB,OACpB;AAAA,MAEF,MAAM,QAAQ;AAAA,QACZ,SAAS;AAAA,MACX;AAAA,MACA;AAAA,IACF;AAAA,IAGA,MAAM,SAAS,SAAS;AAAA,IACxB,MAAM,eAAe,OACnB;AAAA,IAGF,IAAI,WAAW,SAAS,WAAW,QAAQ;AAAA,MAEzC,MAAM,QAAQ;AAAA,QACZ,UAAU,CACR,SACA,iBAIG;AAAA,UACH,OAAO,SAAS;AAAA,YACd,UAAU;AAAA,cACR;AAAA,cACA,QAAQ,UAAU;AAAA,cAClB,QAAQ,SAAS;AAAA,cACjB,QAAQ,WAAW;AAAA,cACnB,QAAQ,QAAQ;AAAA,YAClB;AAAA,YACA,SAAS,MAAM,aAAa,OAAO;AAAA,eAChC;AAAA,UACL,CAAC;AAAA;AAAA,QAEH,kBAAkB,CAChB,SACA,iBAIG;AAAA,UACH,OAAO,iBAAiB;AAAA,YACtB,UAAU;AAAA,cACR;AAAA,cACA,QAAQ,UAAU;AAAA,cAClB,QAAQ,SAAS;AAAA,cACjB,QAAQ,WAAW;AAAA,cACnB,QAAQ,QAAQ;AAAA,YAClB;AAAA,YACA,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": "47BC57BE472723C264756E2164756E21",
|
|
9
9
|
"names": []
|
|
10
10
|
}
|
package/dist/mjs/package.json
CHANGED
package/dist/types/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { Client, EndpointRequestOptions, EndpointResponse } from '@richie-rpc/client';
|
|
2
|
-
import type { Contract,
|
|
1
|
+
import type { Client, EndpointRequestOptions, EndpointResponse, SSEClientMethod, StreamingClientMethod } from '@richie-rpc/client';
|
|
2
|
+
import type { Contract, SSEEndpointDefinition, StandardEndpointDefinition, StreamingEndpointDefinition } from '@richie-rpc/core';
|
|
3
3
|
import { type UseMutationOptions, type UseMutationResult, type UseQueryOptions, type UseQueryResult, type UseSuspenseQueryOptions, type UseSuspenseQueryResult } from '@tanstack/react-query';
|
|
4
4
|
type QueryMethods = 'GET' | 'HEAD';
|
|
5
5
|
type MutationMethods = 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS';
|
|
@@ -7,7 +7,7 @@ type MutationMethods = 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'OPTIONS';
|
|
|
7
7
|
* Hook wrapper for query endpoints (GET, HEAD)
|
|
8
8
|
* Provides useQuery and useSuspenseQuery methods
|
|
9
9
|
*/
|
|
10
|
-
export type QueryHook<T extends
|
|
10
|
+
export type QueryHook<T extends StandardEndpointDefinition> = {
|
|
11
11
|
/**
|
|
12
12
|
* Standard query hook that returns loading states
|
|
13
13
|
*/
|
|
@@ -21,7 +21,7 @@ export type QueryHook<T extends EndpointDefinition> = {
|
|
|
21
21
|
* Hook wrapper for mutation endpoints (POST, PUT, PATCH, DELETE)
|
|
22
22
|
* Provides useMutation method
|
|
23
23
|
*/
|
|
24
|
-
export type MutationHook<T extends
|
|
24
|
+
export type MutationHook<T extends StandardEndpointDefinition> = {
|
|
25
25
|
/**
|
|
26
26
|
* Mutation hook for write operations
|
|
27
27
|
*/
|
|
@@ -30,13 +30,35 @@ export type MutationHook<T extends EndpointDefinition> = {
|
|
|
30
30
|
/**
|
|
31
31
|
* Conditionally apply hook type based on HTTP method
|
|
32
32
|
*/
|
|
33
|
-
export type EndpointHook<T extends
|
|
33
|
+
export type EndpointHook<T extends StandardEndpointDefinition> = T['method'] extends QueryMethods ? QueryHook<T> : T['method'] extends MutationMethods ? MutationHook<T> : never;
|
|
34
|
+
/**
|
|
35
|
+
* Hook wrapper for streaming endpoints
|
|
36
|
+
* Exposes the streaming client method directly since React Query
|
|
37
|
+
* doesn't fit well with long-lived streaming connections
|
|
38
|
+
*/
|
|
39
|
+
export type StreamingHook<T extends StreamingEndpointDefinition> = {
|
|
40
|
+
/**
|
|
41
|
+
* Start a streaming request
|
|
42
|
+
*/
|
|
43
|
+
stream: StreamingClientMethod<T>;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* Hook wrapper for SSE endpoints
|
|
47
|
+
* Exposes the SSE client method directly since React Query
|
|
48
|
+
* doesn't fit well with long-lived SSE connections
|
|
49
|
+
*/
|
|
50
|
+
export type SSEHook<T extends SSEEndpointDefinition> = {
|
|
51
|
+
/**
|
|
52
|
+
* Create an SSE connection
|
|
53
|
+
*/
|
|
54
|
+
connect: SSEClientMethod<T>;
|
|
55
|
+
};
|
|
34
56
|
/**
|
|
35
57
|
* Complete hooks object for a contract
|
|
36
|
-
* Each endpoint gets appropriate hooks based on its HTTP method
|
|
58
|
+
* Each endpoint gets appropriate hooks based on its type and HTTP method
|
|
37
59
|
*/
|
|
38
60
|
export type Hooks<T extends Contract> = {
|
|
39
|
-
[K in keyof T]: EndpointHook<T[K]
|
|
61
|
+
[K in keyof T]: T[K] extends StandardEndpointDefinition ? EndpointHook<T[K]> : T[K] extends StreamingEndpointDefinition ? StreamingHook<T[K]> : T[K] extends SSEEndpointDefinition ? SSEHook<T[K]> : never;
|
|
40
62
|
};
|
|
41
63
|
/**
|
|
42
64
|
* Create typed React hooks for all endpoints in a contract
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@richie-rpc/react-query",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"main": "./dist/cjs/index.cjs",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
}
|
|
11
11
|
},
|
|
12
12
|
"peerDependencies": {
|
|
13
|
-
"@richie-rpc/client": "^1.2.
|
|
14
|
-
"@richie-rpc/core": "^1.2.
|
|
13
|
+
"@richie-rpc/client": "^1.2.4",
|
|
14
|
+
"@richie-rpc/core": "^1.2.3",
|
|
15
15
|
"@tanstack/react-query": "^5.0.0",
|
|
16
16
|
"react": "^18.0.0 || ^19.0.0",
|
|
17
17
|
"typescript": "^5",
|