@@ -19,87 +19,142 @@ npm install eden-tanstack-query @elysiajs/eden @tanstack/query-core elysia
1919## Usage
2020
2121``` ts
22- import { createEdenTQ } from ' eden-tanstack-query'
23- import type { App } from ' ./server' // Your Elysia app type
22+ import { createEdenTQ } from " eden-tanstack-query" ;
23+ import type { App } from " ./server" ; // Your Elysia app type
2424
25- const eden = createEdenTQ <App >(' http://localhost:3000' )
25+ const eden = createEdenTQ <App >(" http://localhost:3000" );
2626```
2727
2828### Route Schema Mode (No Direct Elysia Type Import)
2929
3030For large codebases, you can avoid pulling full app types into every client file:
3131
3232``` ts
33- import { createEdenTQFromSchema } from ' eden-tanstack-query'
34- import type { App } from ' ./server'
33+ import { createEdenTQFromSchema } from " eden-tanstack-query" ;
34+ import type { App } from " ./server" ;
3535
36- type Routes = App [' ~Routes' ]
37- const eden = createEdenTQFromSchema <Routes >(' http://localhost:3000' )
36+ type Routes = App [" ~Routes" ];
37+ const eden = createEdenTQFromSchema <Routes >(" http://localhost:3000" );
3838```
3939
4040This keeps the client typed while reducing type-checker pressure compared with importing a full ` Elysia ` app type everywhere.
4141
4242### Queries
4343
4444``` ts
45- import { createQuery } from ' @tanstack/svelte-query' // or react-query, vue-query, etc.
45+ import { createQuery } from " @tanstack/svelte-query" ; // or react-query, vue-query, etc.
4646
4747// Fully type-safe, auto-generated query key
48- const query = createQuery (() =>
49- eden .users ({ id: ' 123' }).get .queryOptions ({
50- params: { id: ' 123' }
51- })
52- )
48+ const query = createQuery (() =>
49+ eden .users ({ id: " 123" }).get .queryOptions ({
50+ params: { id: " 123" },
51+ }),
52+ );
5353
5454// query.data is typed as your Elysia response type!
5555```
5656
5757React example:
5858
5959``` ts
60- import { useQuery } from ' @tanstack/react-query'
60+ import { useQuery } from " @tanstack/react-query" ;
6161
6262const query = useQuery (
63- eden .users ({ id: ' 123' }).get .queryOptions ({
64- params: { id: ' 123' }
65- })
66- )
63+ eden .users ({ id: " 123" }).get .queryOptions ({
64+ params: { id: " 123" },
65+ }),
66+ );
6767```
6868
6969### Infinite Queries
7070
7171``` ts
72- import { createInfiniteQuery } from ' @tanstack/svelte-query'
72+ import { createInfiniteQuery } from " @tanstack/svelte-query" ;
7373
7474const infiniteQuery = createInfiniteQuery (() =>
7575 eden .posts .get .infiniteQueryOptions (
76- { query: { limit: ' 10 ' } },
76+ { query: { limit: " 10 " } },
7777 {
7878 initialPageParam: 0 ,
7979 getNextPageParam : (lastPage ) => lastPage .nextCursor ,
8080 // cursorKey: 'cursor' // optional, defaults to 'cursor'
81- }
82- )
83- )
81+ },
82+ ),
83+ );
8484```
8585
8686### Mutations
8787
8888``` ts
89- import { createMutation } from ' @tanstack/svelte-query'
89+ import { createMutation } from " @tanstack/svelte-query" ;
9090
9191const mutation = createMutation (
9292 eden .users .post .mutation ({
9393 onSuccess : (data ) => {
94- console .log (' Created user:' , data .id )
95- }
96- })
97- )
94+ console .log (" Created user:" , data .id );
95+ },
96+ }),
97+ );
9898
9999// Type-safe variables
100100mutation .mutate ({
101- body: { name: ' Alice' , email: ' alice@example.com' }
102- })
101+ body: { name: " Alice" , email: " alice@example.com" },
102+ });
103+ ```
104+
105+ ### Svelte / Solid Inference Note
106+
107+ When using ` @tanstack/svelte-query ` or ` @tanstack/solid-query ` , TypeScript can
108+ sometimes widen mutation ` TData ` to ` undefined ` if ` mutationOptions(...) ` is
109+ fully inlined inside ` createMutation(() => ...) ` / ` useMutation(() => ...) ` .
110+
111+ Use one of these stable patterns:
112+
113+ ``` ts
114+ import { createQuery , createMutation } from " @tanstack/svelte-query" ;
115+
116+ // Query: hoist options first
117+ const userQueryOptions = eden .users ({ id: " 123" }).get .queryOptions ({
118+ params: { id: " 123" },
119+ });
120+ const userQuery = createQuery (() => userQueryOptions );
121+
122+ // Mutation: prefer the built-in accessor helper
123+ const createUserMutation = createMutation (
124+ eden .users .post .mutation ({
125+ onSuccess : (data ) => {
126+ console .log (data .id );
127+ },
128+ }),
129+ );
130+ ```
131+
132+ Solid example:
133+
134+ ``` ts
135+ import { useMutation } from " @tanstack/solid-query" ;
136+
137+ const createUserMutation = useMutation (
138+ eden .users .post .mutation ({
139+ onSuccess : (data ) => {
140+ console .log (data .id );
141+ },
142+ }),
143+ );
144+ ```
145+
146+ If you need fully inline calls, you can also pin the generic:
147+
148+ ``` ts
149+ type CreateUserResponse = App [" ~Routes" ][" users" ][" post" ][" response" ][200 ];
150+
151+ const mutation = createMutation (() =>
152+ eden .users .post .mutationOptions <CreateUserResponse >({
153+ onSuccess : (data ) => {
154+ console .log (data .id );
155+ },
156+ }),
157+ );
103158```
104159
105160### Path Params: Inline vs Deferred
@@ -109,29 +164,29 @@ For routes like `/cases/:id/workflow`, you can now choose either pattern:
109164Inline param (known when building options):
110165
111166``` ts
112- const query = eden .cases ({ id: ' case-123' }).workflow .get .queryOptions ({
113- params: { id: ' case-123' }
114- })
167+ const query = eden .cases ({ id: " case-123" }).workflow .get .queryOptions ({
168+ params: { id: " case-123" },
169+ });
115170
116- const mutation = eden .cases ({ id: ' case-123' }).workflow .patch .mutationOptions ()
171+ const mutation = eden .cases ({ id: " case-123" }).workflow .patch .mutationOptions ();
117172await mutation .mutationFn ({
118- params: { id: ' case-123' },
119- body: { status: ' active' }
120- })
173+ params: { id: " case-123" },
174+ body: { status: " active" },
175+ });
121176```
122177
123178Deferred param (ID known later at call time):
124179
125180``` ts
126- const query = eden .cases ({ id: ' ' }).workflow .get .queryOptions ({
127- params: { id: caseId }
128- })
181+ const query = eden .cases ({ id: " " }).workflow .get .queryOptions ({
182+ params: { id: caseId },
183+ });
129184
130- const mutation = eden .cases ({ id: ' ' }).workflow .patch .mutationOptions ()
185+ const mutation = eden .cases ({ id: " " }).workflow .patch .mutationOptions ();
131186await mutation .mutationFn ({
132187 params: { id: caseId },
133- body: { status: ' active' }
134- })
188+ body: { status: " active" },
189+ });
135190```
136191
137192Recommendation:
@@ -142,38 +197,38 @@ Recommendation:
142197### Invalidation
143198
144199``` ts
145- import { useQueryClient } from ' @tanstack/svelte-query'
200+ import { useQueryClient } from " @tanstack/svelte-query" ;
146201
147- const queryClient = useQueryClient ()
202+ const queryClient = useQueryClient ();
148203
149204// Invalidate specific query
150- await eden .users ({ id: ' 123' }).get .invalidate (queryClient , {
151- params: { id: ' 123' }
152- })
205+ await eden .users ({ id: " 123" }).get .invalidate (queryClient , {
206+ params: { id: " 123" },
207+ });
153208
154209// Invalidate all queries for a route
155- await eden .users ({ id: ' 123' }).get .invalidate (queryClient )
210+ await eden .users ({ id: " 123" }).get .invalidate (queryClient );
156211```
157212
158213### Utils (Bound QueryClient)
159214
160215For tRPC-like ergonomics, use ` createEdenTQUtils ` to bind a QueryClient once:
161216
162217``` ts
163- import { createEdenTQ , createEdenTQUtils } from ' eden-tanstack-query'
218+ import { createEdenTQ , createEdenTQUtils } from " eden-tanstack-query" ;
164219
165- const eden = createEdenTQ <App >(' http://localhost:3000' )
166- const utils = createEdenTQUtils (eden , queryClient )
220+ const eden = createEdenTQ <App >(" http://localhost:3000" );
221+ const utils = createEdenTQUtils (eden , queryClient );
167222
168223// No need to pass queryClient every time!
169- await utils .users ({ id: ' 123' }).get .invalidate ({ params: { id: ' 123' } })
170- await utils .posts .get .prefetch ({ query: { limit: ' 10 ' } })
171- await utils .posts .get .cancel ()
172- await utils .posts .get .refetch ()
224+ await utils .users ({ id: " 123" }).get .invalidate ({ params: { id: " 123" } });
225+ await utils .posts .get .prefetch ({ query: { limit: " 10 " } });
226+ await utils .posts .get .cancel ();
227+ await utils .posts .get .refetch ();
173228
174229// Cache manipulation
175- utils .users ({ id: ' 123' }).get .setData ({ params: { id: ' 123' } }, { id: ' 123' , name: ' Updated' })
176- const cached = utils .users ({ id: ' 123' }).get .getData ({ params: { id: ' 123' } })
230+ utils .users ({ id: " 123" }).get .setData ({ params: { id: " 123" } }, { id: " 123" , name: " Updated" });
231+ const cached = utils .users ({ id: " 123" }).get .getData ({ params: { id: " 123" } });
177232```
178233
179234### Error Handling
@@ -182,12 +237,12 @@ const cached = utils.users({ id: '123' }).get.getData({ params: { id: '123' } })
182237Query error states are populated automatically:
183238
184239``` ts
185- const options = eden .users ({ id: ' 123' }).get .queryOptions ({
186- params: { id: ' 123' }
187- })
240+ const options = eden .users ({ id: " 123" }).get .queryOptions ({
241+ params: { id: " 123" },
242+ });
188243
189244try {
190- const data = await options .queryFn ()
245+ const data = await options .queryFn ();
191246} catch (error ) {
192247 // error is typed from your Elysia response map
193248}
@@ -219,19 +274,19 @@ Creates a utils object with a bound QueryClient for tRPC-like ergonomics.
219274
220275Each HTTP method (` get ` , ` post ` , ` put ` , ` delete ` , ` patch ` ) has:
221276
222- | Method | Description |
223- | --------| -------------|
224- | ` .queryOptions(input, overrides?) ` | Returns ` { queryKey, queryFn, ...options } ` for ` createQuery ` |
225- | ` .infiniteQueryOptions(input, opts, overrides?) ` | Returns options for ` createInfiniteQuery ` |
226- | ` .mutationOptions(overrides?) ` | Returns ` { mutationKey, mutationFn, ...options } ` for ` createMutation ` |
227- | ` .mutation(overrides?) ` | Returns a stable ` () => mutationOptions ` accessor for adapters expecting an options factory |
228- | ` .queryKey(input?) ` | Returns the query key |
229- | ` .mutationKey(input?) ` | Returns the mutation key |
230- | ` .invalidate(queryClient, input?, exact?) ` | Invalidates matching queries |
231- | ` .prefetch(queryClient, input) ` | Prefetch a query |
232- | ` .ensureData(queryClient, input) ` | Ensure data exists or fetch it |
233- | ` .setData(queryClient, input, updater) ` | Manually set cache data |
234- | ` .getData(queryClient, input) ` | Read from cache |
277+ | Method | Description |
278+ | ------------------------------------------------ | ------------------------------------------------------------------------------------------- |
279+ | ` .queryOptions(input, overrides?) ` | Returns ` { queryKey, queryFn, ...options } ` for ` createQuery ` |
280+ | ` .infiniteQueryOptions(input, opts, overrides?) ` | Returns options for ` createInfiniteQuery ` |
281+ | ` .mutationOptions(overrides?) ` | Returns ` { mutationKey, mutationFn, ...options } ` for ` createMutation ` |
282+ | ` .mutation(overrides?) ` | Returns a stable ` () => mutationOptions ` accessor for adapters expecting an options factory |
283+ | ` .queryKey(input?) ` | Returns the query key |
284+ | ` .mutationKey(input?) ` | Returns the mutation key |
285+ | ` .invalidate(queryClient, input?, exact?) ` | Invalidates matching queries |
286+ | ` .prefetch(queryClient, input) ` | Prefetch a query |
287+ | ` .ensureData(queryClient, input) ` | Ensure data exists or fetch it |
288+ | ` .setData(queryClient, input, updater) ` | Manually set cache data |
289+ | ` .getData(queryClient, input) ` | Read from cache |
235290
236291### Query Key Shape
237292
@@ -253,15 +308,15 @@ You can pass standard TanStack Query options as overrides:
253308
254309``` ts
255310eden .posts .get .queryOptions (
256- { query: { limit: ' 10 ' } },
311+ { query: { limit: " 10 " } },
257312 {
258313 staleTime: 5000 ,
259314 gcTime: 10000 ,
260315 enabled: isReady ,
261316 refetchOnMount: false ,
262- retry: 3
263- }
264- )
317+ retry: 3 ,
318+ },
319+ );
265320```
266321
267322### Mutation Options Overrides
@@ -276,8 +331,8 @@ eden.users.post.mutationOptions({
276331 },
277332 onError : (error , variables , context ) => {
278333 // Rollback
279- }
280- })
334+ },
335+ });
281336```
282337
283338` mutationOptions() ` and ` mutation() ` are equivalent in typing.
@@ -299,25 +354,25 @@ If your API has many routes:
299354``` ts
300355export function createUserQuery(userId : string ) {
301356 return createQuery <User >(() => ({
302- queryKey: [' users' , userId ],
357+ queryKey: [" users" , userId ],
303358 queryFn : async () => {
304- const { data, error } = await api .users ({ id: userId }).get ()
305- if (error ) throw error
306- return data as User // Manual cast!
359+ const { data, error } = await api .users ({ id: userId }).get ();
360+ if (error ) throw error ;
361+ return data as User ; // Manual cast!
307362 },
308- }))
363+ }));
309364}
310365```
311366
312367### After (with eden-tanstack-query)
313368
314369``` ts
315370export function createUserQuery(userId : string ) {
316- return createQuery (() =>
371+ return createQuery (() =>
317372 eden .users ({ id: userId }).get .queryOptions ({
318- params: { id: userId }
319- })
320- )
373+ params: { id: userId },
374+ }),
375+ );
321376}
322377// Types are inferred from your Elysia server!
323378```
0 commit comments