@navios/react-query 0.5.0 → 0.5.1

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,702 @@
1
+ import type {
2
+ DataTag,
3
+ InfiniteData,
4
+ UseMutationResult,
5
+ UseSuspenseInfiniteQueryOptions,
6
+ UseSuspenseQueryOptions,
7
+ } from '@tanstack/react-query'
8
+ import type { z } from 'zod/v4'
9
+
10
+ import { assertType, describe, test } from 'vitest'
11
+ import { z as zod } from 'zod/v4'
12
+
13
+ import type { ClientInstance } from '../types.mjs'
14
+ import type { QueryHelpers } from '../../query/types.mjs'
15
+ import type { MutationHelpers } from '../../mutation/types.mjs'
16
+ import type { EndpointHelper } from '../types.mjs'
17
+ import type { Split } from '../../common/types.mjs'
18
+
19
+ declare const client: ClientInstance
20
+
21
+ const responseSchema = zod.object({
22
+ id: zod.string(),
23
+ name: zod.string(),
24
+ })
25
+
26
+ const querySchema = zod.object({
27
+ page: zod.number(),
28
+ limit: zod.number(),
29
+ })
30
+
31
+ const requestSchema = zod.object({
32
+ name: zod.string(),
33
+ email: zod.string(),
34
+ })
35
+
36
+ type ResponseType = z.output<typeof responseSchema>
37
+ type QueryType = z.input<typeof querySchema>
38
+ type RequestType = z.input<typeof requestSchema>
39
+
40
+ describe('ClientInstance', () => {
41
+ describe('query() method', () => {
42
+ describe('GET endpoints', () => {
43
+ test('simple GET query without params', () => {
44
+ const query = client.query({
45
+ method: 'GET',
46
+ url: '/users',
47
+ responseSchema,
48
+ })
49
+
50
+ // Return type should be callable returning UseSuspenseQueryOptions
51
+ assertType<
52
+ (params: {}) => UseSuspenseQueryOptions<
53
+ ResponseType,
54
+ Error,
55
+ ResponseType,
56
+ DataTag<Split<'/users', '/'>, ResponseType, Error>
57
+ >
58
+ >(query)
59
+
60
+ // Should have QueryHelpers
61
+ assertType<QueryHelpers<'/users', undefined, ResponseType>['queryKey']>(
62
+ query.queryKey,
63
+ )
64
+ assertType<QueryHelpers<'/users', undefined, ResponseType>['use']>(
65
+ query.use,
66
+ )
67
+ assertType<
68
+ QueryHelpers<'/users', undefined, ResponseType>['useSuspense']
69
+ >(query.useSuspense)
70
+ assertType<
71
+ QueryHelpers<'/users', undefined, ResponseType>['invalidate']
72
+ >(query.invalidate)
73
+ assertType<
74
+ QueryHelpers<'/users', undefined, ResponseType>['invalidateAll']
75
+ >(query.invalidateAll)
76
+
77
+ // Should have EndpointHelper
78
+ assertType<
79
+ EndpointHelper<
80
+ 'GET',
81
+ '/users',
82
+ undefined,
83
+ typeof responseSchema
84
+ >['endpoint']
85
+ >(query.endpoint)
86
+ })
87
+
88
+ test('GET query with URL params', () => {
89
+ const query = client.query({
90
+ method: 'GET',
91
+ url: '/users/$userId',
92
+ responseSchema,
93
+ })
94
+
95
+ // Should require urlParams
96
+ assertType<
97
+ (params: {
98
+ urlParams: { userId: string | number }
99
+ }) => UseSuspenseQueryOptions<
100
+ ResponseType,
101
+ Error,
102
+ ResponseType,
103
+ DataTag<Split<'/users/$userId', '/'>, ResponseType, Error>
104
+ >
105
+ >(query)
106
+ })
107
+
108
+ test('GET query with query schema', () => {
109
+ const query = client.query({
110
+ method: 'GET',
111
+ url: '/users',
112
+ querySchema,
113
+ responseSchema,
114
+ })
115
+
116
+ // Should require params
117
+ assertType<
118
+ (params: { params: QueryType }) => UseSuspenseQueryOptions<
119
+ ResponseType,
120
+ Error,
121
+ ResponseType,
122
+ DataTag<Split<'/users', '/'>, ResponseType, Error>
123
+ >
124
+ >(query)
125
+
126
+ // Should have QueryHelpers with query schema
127
+ assertType<
128
+ QueryHelpers<'/users', typeof querySchema, ResponseType>['queryKey']
129
+ >(query.queryKey)
130
+ })
131
+
132
+ test('GET query with URL params and query schema', () => {
133
+ const query = client.query({
134
+ method: 'GET',
135
+ url: '/users/$userId/posts',
136
+ querySchema,
137
+ responseSchema,
138
+ })
139
+
140
+ // Should require both urlParams and params
141
+ assertType<
142
+ (params: {
143
+ urlParams: { userId: string | number }
144
+ params: QueryType
145
+ }) => UseSuspenseQueryOptions<
146
+ ResponseType,
147
+ Error,
148
+ ResponseType,
149
+ DataTag<Split<'/users/$userId/posts', '/'>, ResponseType, Error>
150
+ >
151
+ >(query)
152
+ })
153
+
154
+ test('GET query with processResponse transformation', () => {
155
+ const query = client.query({
156
+ method: 'GET',
157
+ url: '/users',
158
+ responseSchema,
159
+ processResponse: (data) => data.name.toUpperCase(),
160
+ })
161
+
162
+ // Result type should be string (transformed)
163
+ assertType<
164
+ (params: {}) => UseSuspenseQueryOptions<
165
+ string,
166
+ Error,
167
+ string,
168
+ DataTag<Split<'/users', '/'>, string, Error>
169
+ >
170
+ >(query)
171
+ })
172
+ })
173
+
174
+ describe('HEAD and OPTIONS endpoints', () => {
175
+ test('HEAD query', () => {
176
+ const query = client.query({
177
+ method: 'HEAD',
178
+ url: '/ping',
179
+ responseSchema,
180
+ })
181
+
182
+ assertType<
183
+ (params: {}) => UseSuspenseQueryOptions<
184
+ ResponseType,
185
+ Error,
186
+ ResponseType,
187
+ DataTag<Split<'/ping', '/'>, ResponseType, Error>
188
+ >
189
+ >(query)
190
+ })
191
+
192
+ test('OPTIONS query', () => {
193
+ const query = client.query({
194
+ method: 'OPTIONS',
195
+ url: '/cors',
196
+ responseSchema,
197
+ })
198
+
199
+ assertType<
200
+ (params: {}) => UseSuspenseQueryOptions<
201
+ ResponseType,
202
+ Error,
203
+ ResponseType,
204
+ DataTag<Split<'/cors', '/'>, ResponseType, Error>
205
+ >
206
+ >(query)
207
+ })
208
+ })
209
+
210
+ describe('POST query endpoints (for search)', () => {
211
+ test('POST query with request schema', () => {
212
+ const query = client.query({
213
+ method: 'POST',
214
+ url: '/search',
215
+ requestSchema,
216
+ responseSchema,
217
+ })
218
+
219
+ // Should require data
220
+ assertType<
221
+ (params: { data: RequestType }) => UseSuspenseQueryOptions<
222
+ ResponseType,
223
+ Error,
224
+ ResponseType,
225
+ DataTag<Split<'/search', '/'>, ResponseType, Error>
226
+ >
227
+ >(query)
228
+
229
+ // Should have EndpointHelper with request schema
230
+ assertType<
231
+ EndpointHelper<
232
+ 'POST',
233
+ '/search',
234
+ typeof requestSchema,
235
+ typeof responseSchema
236
+ >['endpoint']
237
+ >(query.endpoint)
238
+ })
239
+
240
+ test('POST query with URL params and request schema', () => {
241
+ const query = client.query({
242
+ method: 'POST',
243
+ url: '/users/$userId/search',
244
+ requestSchema,
245
+ responseSchema,
246
+ })
247
+
248
+ // Should require urlParams and data
249
+ assertType<
250
+ (params: {
251
+ urlParams: { userId: string | number }
252
+ data: RequestType
253
+ }) => UseSuspenseQueryOptions<
254
+ ResponseType,
255
+ Error,
256
+ ResponseType,
257
+ DataTag<Split<'/users/$userId/search', '/'>, ResponseType, Error>
258
+ >
259
+ >(query)
260
+ })
261
+
262
+ test('POST query with all schemas', () => {
263
+ const query = client.query({
264
+ method: 'POST',
265
+ url: '/search',
266
+ querySchema,
267
+ requestSchema,
268
+ responseSchema,
269
+ })
270
+
271
+ // Should require params and data
272
+ assertType<
273
+ (params: {
274
+ params: QueryType
275
+ data: RequestType
276
+ }) => UseSuspenseQueryOptions<
277
+ ResponseType,
278
+ Error,
279
+ ResponseType,
280
+ DataTag<Split<'/search', '/'>, ResponseType, Error>
281
+ >
282
+ >(query)
283
+ })
284
+ })
285
+ })
286
+
287
+ describe('infiniteQuery() method', () => {
288
+ test('GET infinite query', () => {
289
+ const query = client.infiniteQuery({
290
+ method: 'GET',
291
+ url: '/users',
292
+ querySchema,
293
+ responseSchema,
294
+ getNextPageParam: (lastPage, allPages, lastPageParam, allPageParams) =>
295
+ undefined,
296
+ })
297
+
298
+ // Should return UseSuspenseInfiniteQueryOptions
299
+ assertType<
300
+ (params: { params: QueryType }) => UseSuspenseInfiniteQueryOptions<
301
+ ResponseType,
302
+ Error,
303
+ InfiniteData<ResponseType>,
304
+ DataTag<Split<'/users', '/'>, ResponseType, Error>,
305
+ z.output<typeof querySchema>
306
+ >
307
+ >(query)
308
+
309
+ // Should have QueryHelpers with isInfinite=true
310
+ assertType<
311
+ QueryHelpers<
312
+ '/users',
313
+ typeof querySchema,
314
+ ResponseType,
315
+ true
316
+ >['queryKey']
317
+ >(query.queryKey)
318
+ })
319
+
320
+ test('GET infinite query with URL params', () => {
321
+ const query = client.infiniteQuery({
322
+ method: 'GET',
323
+ url: '/users/$userId/posts',
324
+ querySchema,
325
+ responseSchema,
326
+ getNextPageParam: () => undefined,
327
+ })
328
+
329
+ // Should require urlParams and params
330
+ assertType<
331
+ (params: {
332
+ urlParams: { userId: string | number }
333
+ params: QueryType
334
+ }) => UseSuspenseInfiniteQueryOptions<
335
+ ResponseType,
336
+ Error,
337
+ InfiniteData<ResponseType>,
338
+ DataTag<Split<'/users/$userId/posts', '/'>, ResponseType, Error>,
339
+ z.output<typeof querySchema>
340
+ >
341
+ >(query)
342
+ })
343
+
344
+ test('POST infinite query', () => {
345
+ const query = client.infiniteQuery({
346
+ method: 'POST',
347
+ url: '/search',
348
+ querySchema,
349
+ requestSchema,
350
+ responseSchema,
351
+ getNextPageParam: () => undefined,
352
+ })
353
+
354
+ // Should require params and data
355
+ assertType<
356
+ (params: {
357
+ params: QueryType
358
+ data: RequestType
359
+ }) => UseSuspenseInfiniteQueryOptions<
360
+ ResponseType,
361
+ Error,
362
+ InfiniteData<ResponseType>,
363
+ DataTag<Split<'/search', '/'>, ResponseType, Error>,
364
+ z.output<typeof querySchema>
365
+ >
366
+ >(query)
367
+ })
368
+ })
369
+
370
+ describe('mutation() method', () => {
371
+ describe('POST/PUT/PATCH mutations', () => {
372
+ test('POST mutation with request schema only', () => {
373
+ const mutation = client.mutation({
374
+ method: 'POST',
375
+ url: '/users',
376
+ requestSchema,
377
+ responseSchema,
378
+ processResponse: (data) => data,
379
+ })
380
+
381
+ // Should return a function that returns UseMutationResult
382
+ assertType<
383
+ () => UseMutationResult<
384
+ ResponseType,
385
+ Error,
386
+ { data: RequestType }
387
+ >
388
+ >(mutation)
389
+
390
+ // Should have MutationHelpers
391
+ assertType<MutationHelpers<'/users', ResponseType>['mutationKey']>(
392
+ mutation.mutationKey,
393
+ )
394
+ assertType<MutationHelpers<'/users', ResponseType>['useIsMutating']>(
395
+ mutation.useIsMutating,
396
+ )
397
+
398
+ // Should have EndpointHelper
399
+ assertType<
400
+ EndpointHelper<
401
+ 'POST',
402
+ '/users',
403
+ typeof requestSchema,
404
+ typeof responseSchema
405
+ >['endpoint']
406
+ >(mutation.endpoint)
407
+ })
408
+
409
+ test('POST mutation with useKey', () => {
410
+ const mutation = client.mutation({
411
+ method: 'POST',
412
+ url: '/users/$userId',
413
+ useKey: true,
414
+ requestSchema,
415
+ responseSchema,
416
+ processResponse: (data) => data,
417
+ })
418
+
419
+ // With useKey, should require urlParams in the call
420
+ assertType<
421
+ (params: {
422
+ urlParams: { userId: string | number }
423
+ }) => UseMutationResult<
424
+ ResponseType,
425
+ Error,
426
+ { urlParams: { userId: string | number }; data: RequestType }
427
+ >
428
+ >(mutation)
429
+ })
430
+
431
+ test('PUT mutation with URL params', () => {
432
+ const mutation = client.mutation({
433
+ method: 'PUT',
434
+ url: '/users/$userId',
435
+ requestSchema,
436
+ responseSchema,
437
+ processResponse: (data) => data,
438
+ })
439
+
440
+ // Without useKey, should return hook directly
441
+ assertType<
442
+ () => UseMutationResult<
443
+ ResponseType,
444
+ Error,
445
+ { urlParams: { userId: string | number }; data: RequestType }
446
+ >
447
+ >(mutation)
448
+ })
449
+
450
+ test('PATCH mutation with query schema', () => {
451
+ const mutation = client.mutation({
452
+ method: 'PATCH',
453
+ url: '/users/$userId',
454
+ requestSchema,
455
+ querySchema,
456
+ responseSchema,
457
+ processResponse: (data) => data,
458
+ })
459
+
460
+ // Should include params in variables
461
+ assertType<
462
+ () => UseMutationResult<
463
+ ResponseType,
464
+ Error,
465
+ {
466
+ urlParams: { userId: string | number }
467
+ data: RequestType
468
+ params: QueryType
469
+ }
470
+ >
471
+ >(mutation)
472
+ })
473
+
474
+ test('mutation with custom result type', () => {
475
+ const mutation = client.mutation({
476
+ method: 'POST',
477
+ url: '/users',
478
+ requestSchema,
479
+ responseSchema,
480
+ processResponse: (data) => ({ processed: true, name: data.name }),
481
+ })
482
+
483
+ // Result type should be the transformed type
484
+ assertType<
485
+ () => UseMutationResult<
486
+ { processed: boolean; name: string },
487
+ Error,
488
+ { data: RequestType }
489
+ >
490
+ >(mutation)
491
+ })
492
+ })
493
+
494
+ describe('DELETE mutations', () => {
495
+ test('DELETE mutation without schemas', () => {
496
+ const mutation = client.mutation({
497
+ method: 'DELETE',
498
+ url: '/users/$userId',
499
+ responseSchema,
500
+ processResponse: (data) => data,
501
+ })
502
+
503
+ assertType<
504
+ () => UseMutationResult<
505
+ ResponseType,
506
+ Error,
507
+ { urlParams: { userId: string | number } }
508
+ >
509
+ >(mutation)
510
+ })
511
+
512
+ test('DELETE mutation with useKey and URL params and querySchema', () => {
513
+ const mutation = client.mutation({
514
+ method: 'DELETE',
515
+ url: '/users/$userId',
516
+ useKey: true,
517
+ querySchema,
518
+ responseSchema,
519
+ processResponse: (data) => data,
520
+ })
521
+
522
+ // With useKey and URL params, should require urlParams in the call
523
+ assertType<
524
+ (params: {
525
+ urlParams: { userId: string | number }
526
+ }) => UseMutationResult<
527
+ ResponseType,
528
+ Error,
529
+ { urlParams: { userId: string | number }; params: QueryType }
530
+ >
531
+ >(mutation)
532
+ })
533
+
534
+ test('DELETE mutation with useKey and URL params (no querySchema)', () => {
535
+ const mutation = client.mutation({
536
+ method: 'DELETE',
537
+ url: '/users/$userId',
538
+ useKey: true,
539
+ responseSchema,
540
+ processResponse: (data) => data,
541
+ })
542
+
543
+ // With useKey and URL params, should require urlParams in the call
544
+ assertType<
545
+ (params: {
546
+ urlParams: { userId: string | number }
547
+ }) => UseMutationResult<
548
+ ResponseType,
549
+ Error,
550
+ { urlParams: { userId: string | number } }
551
+ >
552
+ >(mutation)
553
+ })
554
+
555
+ test('DELETE mutation with useKey without URL params', () => {
556
+ const mutation = client.mutation({
557
+ method: 'DELETE',
558
+ url: '/cache',
559
+ useKey: true,
560
+ responseSchema,
561
+ processResponse: (data) => data,
562
+ })
563
+
564
+ // With useKey but no URL params, should require empty params
565
+ assertType<
566
+ (params: {}) => UseMutationResult<ResponseType, Error, {}>
567
+ >(mutation)
568
+ })
569
+
570
+ test('DELETE mutation with query schema', () => {
571
+ const mutation = client.mutation({
572
+ method: 'DELETE',
573
+ url: '/cache',
574
+ querySchema,
575
+ responseSchema,
576
+ processResponse: (data) => data,
577
+ })
578
+
579
+ assertType<
580
+ () => UseMutationResult<
581
+ ResponseType,
582
+ Error,
583
+ { params: QueryType }
584
+ >
585
+ >(mutation)
586
+ })
587
+ })
588
+ })
589
+
590
+ describe('multipartMutation() method', () => {
591
+ test('multipart mutation with request schema', () => {
592
+ const mutation = client.multipartMutation({
593
+ method: 'POST',
594
+ url: '/upload',
595
+ requestSchema,
596
+ responseSchema,
597
+ processResponse: (data) => data,
598
+ })
599
+
600
+ assertType<
601
+ () => UseMutationResult<ResponseType, Error, { data: RequestType }>
602
+ >(mutation)
603
+ })
604
+
605
+ test('multipart mutation with useKey', () => {
606
+ const mutation = client.multipartMutation({
607
+ method: 'POST',
608
+ url: '/users/$userId/avatar',
609
+ useKey: true,
610
+ requestSchema,
611
+ responseSchema,
612
+ processResponse: (data) => data,
613
+ })
614
+
615
+ assertType<
616
+ (params: {
617
+ urlParams: { userId: string | number }
618
+ }) => UseMutationResult<
619
+ ResponseType,
620
+ Error,
621
+ { urlParams: { userId: string | number }; data: RequestType }
622
+ >
623
+ >(mutation)
624
+ })
625
+
626
+ test('multipart mutation with query schema', () => {
627
+ const mutation = client.multipartMutation({
628
+ method: 'POST',
629
+ url: '/upload',
630
+ requestSchema,
631
+ querySchema,
632
+ responseSchema,
633
+ processResponse: (data) => data,
634
+ })
635
+
636
+ assertType<
637
+ () => UseMutationResult<
638
+ ResponseType,
639
+ Error,
640
+ { data: RequestType; params: QueryType }
641
+ >
642
+ >(mutation)
643
+ })
644
+ })
645
+ })
646
+
647
+ describe('Error cases - should fail type checking', () => {
648
+ test('GET query without urlParams when URL has params', () => {
649
+ const query = client.query({
650
+ method: 'GET',
651
+ url: '/users/$userId',
652
+ responseSchema,
653
+ })
654
+
655
+ // @ts-expect-error - missing urlParams
656
+ query({})
657
+ })
658
+
659
+ test('GET query without params when querySchema is defined', () => {
660
+ const query = client.query({
661
+ method: 'GET',
662
+ url: '/users',
663
+ querySchema,
664
+ responseSchema,
665
+ })
666
+
667
+ // @ts-expect-error - missing params
668
+ query({})
669
+ })
670
+
671
+ test('POST query without data when requestSchema is defined', () => {
672
+ const query = client.query({
673
+ method: 'POST',
674
+ url: '/search',
675
+ requestSchema,
676
+ responseSchema,
677
+ })
678
+
679
+ // @ts-expect-error - missing data
680
+ query({})
681
+ })
682
+
683
+ test('mutation without processResponse', () => {
684
+ client.mutation({
685
+ // @ts-expect-error - missing processResponse causes method to not match any overload
686
+ method: 'POST',
687
+ url: '/users',
688
+ requestSchema,
689
+ responseSchema,
690
+ })
691
+ })
692
+
693
+ test('infiniteQuery without getNextPageParam', () => {
694
+ // @ts-expect-error - missing getNextPageParam
695
+ client.infiniteQuery({
696
+ method: 'GET',
697
+ url: '/users',
698
+ querySchema,
699
+ responseSchema,
700
+ })
701
+ })
702
+ })