@geekmidas/client 0.4.0 → 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.
Files changed (72) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/auth-fetcher.cjs +0 -1
  3. package/dist/auth-fetcher.cjs.map +1 -1
  4. package/dist/auth-fetcher.d.cts +1 -1
  5. package/dist/auth-fetcher.d.cts.map +1 -1
  6. package/dist/auth-fetcher.d.mts +1 -1
  7. package/dist/auth-fetcher.d.mts.map +1 -1
  8. package/dist/auth-fetcher.mjs +0 -1
  9. package/dist/auth-fetcher.mjs.map +1 -1
  10. package/dist/endpoint-hooks.cjs +10 -5
  11. package/dist/endpoint-hooks.cjs.map +1 -1
  12. package/dist/endpoint-hooks.d.cts +2 -2
  13. package/dist/endpoint-hooks.d.cts.map +1 -1
  14. package/dist/endpoint-hooks.d.mts +2 -2
  15. package/dist/endpoint-hooks.d.mts.map +1 -1
  16. package/dist/endpoint-hooks.mjs +10 -5
  17. package/dist/endpoint-hooks.mjs.map +1 -1
  18. package/dist/fetcher-5fnBE7Dk.mjs.map +1 -1
  19. package/dist/fetcher-CgLEaIAC.cjs.map +1 -1
  20. package/dist/fetcher.d.cts +1 -1
  21. package/dist/fetcher.d.cts.map +1 -1
  22. package/dist/fetcher.d.mts +1 -1
  23. package/dist/fetcher.d.mts.map +1 -1
  24. package/dist/infer.d.cts.map +1 -1
  25. package/dist/infer.d.mts.map +1 -1
  26. package/dist/openapi-hooks.cjs.map +1 -1
  27. package/dist/openapi-hooks.d.cts +2 -2
  28. package/dist/openapi-hooks.d.cts.map +1 -1
  29. package/dist/openapi-hooks.d.mts +5 -5
  30. package/dist/openapi-hooks.d.mts.map +1 -1
  31. package/dist/openapi-hooks.mjs.map +1 -1
  32. package/dist/openapi-types.d.cts.map +1 -1
  33. package/dist/openapi-types.d.mts.map +1 -1
  34. package/dist/openapi.cjs.map +1 -1
  35. package/dist/openapi.mjs.map +1 -1
  36. package/dist/react-query.cjs.map +1 -1
  37. package/dist/react-query.d.cts +1 -1
  38. package/dist/react-query.d.cts.map +1 -1
  39. package/dist/react-query.d.mts +8 -8
  40. package/dist/react-query.d.mts.map +1 -1
  41. package/dist/react-query.mjs.map +1 -1
  42. package/dist/{types-FxummKaL.d.mts → types-4K4N-Fl1.d.cts} +2 -2
  43. package/dist/types-4K4N-Fl1.d.cts.map +1 -0
  44. package/dist/{types-vutATyWv.d.cts → types-hhkZWjQn.d.mts} +2 -2
  45. package/dist/types-hhkZWjQn.d.mts.map +1 -0
  46. package/dist/types.d.cts +1 -1
  47. package/dist/types.d.mts +1 -1
  48. package/package.json +5 -5
  49. package/src/__tests__/auth-fetcher.spec.ts +476 -476
  50. package/src/__tests__/endpoint-hooks.spec.tsx +348 -348
  51. package/src/__tests__/fetcher.spec.ts +403 -403
  52. package/src/__tests__/infer.integration.spec.ts +171 -171
  53. package/src/__tests__/infer.spec.ts +182 -182
  54. package/src/__tests__/method-restrictions.spec.tsx +165 -165
  55. package/src/__tests__/openapi-hooks.spec.tsx +536 -536
  56. package/src/__tests__/react-query-infinite.spec.tsx +989 -989
  57. package/src/__tests__/react-query-invalidation.spec.tsx +223 -222
  58. package/src/__tests__/react-query.spec.tsx +567 -567
  59. package/src/__tests__/setup.ts +260 -260
  60. package/src/__tests__/types.spec.ts +127 -127
  61. package/src/__tests__/url-parsing.spec.ts +191 -191
  62. package/src/auth-fetcher.ts +160 -162
  63. package/src/endpoint-hooks.ts +151 -156
  64. package/src/fetcher.ts +163 -163
  65. package/src/infer.ts +64 -63
  66. package/src/openapi-hooks.ts +146 -146
  67. package/src/openapi-types.d.ts +428 -428
  68. package/src/openapi.json +593 -593
  69. package/src/react-query.ts +281 -281
  70. package/src/types.ts +121 -124
  71. package/dist/types-FxummKaL.d.mts.map +0 -1
  72. package/dist/types-vutATyWv.d.cts.map +0 -1
@@ -1,134 +1,134 @@
1
1
  import { describe, expectTypeOf, it } from 'vitest';
2
2
  import type { paths } from '../openapi-types';
3
3
  import type {
4
- ExtractEndpointConfig,
5
- ExtractEndpointResponse,
6
- FilteredRequestConfig,
7
- TypedEndpoint,
8
- ValidEndpoint,
4
+ ExtractEndpointConfig,
5
+ ExtractEndpointResponse,
6
+ FilteredRequestConfig,
7
+ TypedEndpoint,
8
+ ValidEndpoint,
9
9
  } from '../types';
10
10
 
11
11
  describe('Type utilities', () => {
12
- it('should extract valid endpoints from paths', () => {
13
- type ValidEndpoints = TypedEndpoint<paths>;
14
-
15
- // Test that valid endpoints are correctly generated
16
- expectTypeOf<ValidEndpoints>().toExtend<
17
- | 'GET /users'
18
- | 'POST /users'
19
- | 'GET /users/{id}'
20
- | 'PUT /users/{id}'
21
- | 'DELETE /users/{id}'
22
- | 'GET /posts'
23
- | 'GET /protected'
24
- | 'GET /error'
25
- >();
26
- });
27
-
28
- it('should create typed endpoint strings', () => {
29
- type Endpoints = TypedEndpoint<paths>;
30
-
31
- // Test that TypedEndpoint resolves to the same as ValidEndpoint
32
- expectTypeOf<Endpoints>().toEqualTypeOf<ValidEndpoint<paths>>();
33
- });
34
-
35
- it('should extract response types correctly', () => {
36
- type UserResponse = ExtractEndpointResponse<paths, 'GET /users/{id}'>;
37
- type UsersResponse = ExtractEndpointResponse<paths, 'GET /users'>;
38
- type PostsResponse = ExtractEndpointResponse<paths, 'GET /posts'>;
39
-
40
- expectTypeOf<UserResponse>().toEqualTypeOf<{
41
- id: string;
42
- name: string;
43
- email: string;
44
- }>();
45
-
46
- expectTypeOf<UsersResponse>().toEqualTypeOf<{
47
- users: Array<{
48
- id: string;
49
- name: string;
50
- email: string;
51
- }>;
52
- }>();
53
-
54
- expectTypeOf<PostsResponse>().toEqualTypeOf<{
55
- posts: Array<{
56
- id: string;
57
- title: string;
58
- content: string;
59
- authorId: string;
60
- createdAt: string;
61
- }>;
62
- pagination: {
63
- page: number;
64
- limit: number;
65
- total: number;
66
- };
67
- sort: 'asc' | 'desc';
68
- }>();
69
- });
70
-
71
- it('should extract config types correctly', () => {
72
- type GetUserConfig = ExtractEndpointConfig<paths, 'GET /users/{id}'>;
73
- type PostUserConfig = ExtractEndpointConfig<paths, 'POST /users'>;
74
- type GetPostsConfig = ExtractEndpointConfig<paths, 'GET /posts'>;
75
-
76
- expectTypeOf<GetUserConfig>().toEqualTypeOf<{
77
- params?: { id: string };
78
- query?: never;
79
- body?: never;
80
- headers?: Record<string, string>;
81
- }>();
82
-
83
- expectTypeOf<PostUserConfig>().toEqualTypeOf<{
84
- params?: never;
85
- query?: never;
86
- body?: { name: string; email: string };
87
- headers?: Record<string, string>;
88
- }>();
89
-
90
- expectTypeOf<GetPostsConfig>().toEqualTypeOf<{
91
- params?: never;
92
- query?: {
93
- page?: number;
94
- limit?: number;
95
- sort?: 'asc' | 'desc';
96
- };
97
- body?: never;
98
- headers?: Record<string, string>;
99
- }>();
100
- });
101
-
102
- it('should filter config to only required properties', () => {
103
- type GetUserFiltered = FilteredRequestConfig<paths, 'GET /users/{id}'>;
104
- type PostUserFiltered = FilteredRequestConfig<paths, 'POST /users'>;
105
- type GetUsersFiltered = FilteredRequestConfig<paths, 'GET /users'>;
106
-
107
- // GET /users/{id} should require params
108
- expectTypeOf<GetUserFiltered>().toEqualTypeOf<{
109
- params?: { id: string };
110
- headers?: Record<string, string>;
111
- }>();
112
-
113
- // POST /users should allow body
114
- expectTypeOf<PostUserFiltered>().toEqualTypeOf<{
115
- body?: { name: string; email: string };
116
- headers?: Record<string, string>;
117
- }>();
118
-
119
- // GET /users should only allow headers (no params, query, or body)
120
- expectTypeOf<GetUsersFiltered>().toEqualTypeOf<{
121
- headers?: Record<string, string>;
122
- }>();
123
- });
124
-
125
- it('should handle endpoints with multiple parameter types', () => {
126
- type PutUserConfig = FilteredRequestConfig<paths, 'PUT /users/{id}'>;
127
-
128
- expectTypeOf<PutUserConfig>().toEqualTypeOf<{
129
- params?: { id: string };
130
- body?: { name?: string; email?: string };
131
- headers?: Record<string, string>;
132
- }>();
133
- });
12
+ it('should extract valid endpoints from paths', () => {
13
+ type ValidEndpoints = TypedEndpoint<paths>;
14
+
15
+ // Test that valid endpoints are correctly generated
16
+ expectTypeOf<ValidEndpoints>().toExtend<
17
+ | 'GET /users'
18
+ | 'POST /users'
19
+ | 'GET /users/{id}'
20
+ | 'PUT /users/{id}'
21
+ | 'DELETE /users/{id}'
22
+ | 'GET /posts'
23
+ | 'GET /protected'
24
+ | 'GET /error'
25
+ >();
26
+ });
27
+
28
+ it('should create typed endpoint strings', () => {
29
+ type Endpoints = TypedEndpoint<paths>;
30
+
31
+ // Test that TypedEndpoint resolves to the same as ValidEndpoint
32
+ expectTypeOf<Endpoints>().toEqualTypeOf<ValidEndpoint<paths>>();
33
+ });
34
+
35
+ it('should extract response types correctly', () => {
36
+ type UserResponse = ExtractEndpointResponse<paths, 'GET /users/{id}'>;
37
+ type UsersResponse = ExtractEndpointResponse<paths, 'GET /users'>;
38
+ type PostsResponse = ExtractEndpointResponse<paths, 'GET /posts'>;
39
+
40
+ expectTypeOf<UserResponse>().toEqualTypeOf<{
41
+ id: string;
42
+ name: string;
43
+ email: string;
44
+ }>();
45
+
46
+ expectTypeOf<UsersResponse>().toEqualTypeOf<{
47
+ users: Array<{
48
+ id: string;
49
+ name: string;
50
+ email: string;
51
+ }>;
52
+ }>();
53
+
54
+ expectTypeOf<PostsResponse>().toEqualTypeOf<{
55
+ posts: Array<{
56
+ id: string;
57
+ title: string;
58
+ content: string;
59
+ authorId: string;
60
+ createdAt: string;
61
+ }>;
62
+ pagination: {
63
+ page: number;
64
+ limit: number;
65
+ total: number;
66
+ };
67
+ sort: 'asc' | 'desc';
68
+ }>();
69
+ });
70
+
71
+ it('should extract config types correctly', () => {
72
+ type GetUserConfig = ExtractEndpointConfig<paths, 'GET /users/{id}'>;
73
+ type PostUserConfig = ExtractEndpointConfig<paths, 'POST /users'>;
74
+ type GetPostsConfig = ExtractEndpointConfig<paths, 'GET /posts'>;
75
+
76
+ expectTypeOf<GetUserConfig>().toEqualTypeOf<{
77
+ params?: { id: string };
78
+ query?: never;
79
+ body?: never;
80
+ headers?: Record<string, string>;
81
+ }>();
82
+
83
+ expectTypeOf<PostUserConfig>().toEqualTypeOf<{
84
+ params?: never;
85
+ query?: never;
86
+ body?: { name: string; email: string };
87
+ headers?: Record<string, string>;
88
+ }>();
89
+
90
+ expectTypeOf<GetPostsConfig>().toEqualTypeOf<{
91
+ params?: never;
92
+ query?: {
93
+ page?: number;
94
+ limit?: number;
95
+ sort?: 'asc' | 'desc';
96
+ };
97
+ body?: never;
98
+ headers?: Record<string, string>;
99
+ }>();
100
+ });
101
+
102
+ it('should filter config to only required properties', () => {
103
+ type GetUserFiltered = FilteredRequestConfig<paths, 'GET /users/{id}'>;
104
+ type PostUserFiltered = FilteredRequestConfig<paths, 'POST /users'>;
105
+ type GetUsersFiltered = FilteredRequestConfig<paths, 'GET /users'>;
106
+
107
+ // GET /users/{id} should require params
108
+ expectTypeOf<GetUserFiltered>().toEqualTypeOf<{
109
+ params?: { id: string };
110
+ headers?: Record<string, string>;
111
+ }>();
112
+
113
+ // POST /users should allow body
114
+ expectTypeOf<PostUserFiltered>().toEqualTypeOf<{
115
+ body?: { name: string; email: string };
116
+ headers?: Record<string, string>;
117
+ }>();
118
+
119
+ // GET /users should only allow headers (no params, query, or body)
120
+ expectTypeOf<GetUsersFiltered>().toEqualTypeOf<{
121
+ headers?: Record<string, string>;
122
+ }>();
123
+ });
124
+
125
+ it('should handle endpoints with multiple parameter types', () => {
126
+ type PutUserConfig = FilteredRequestConfig<paths, 'PUT /users/{id}'>;
127
+
128
+ expectTypeOf<PutUserConfig>().toEqualTypeOf<{
129
+ params?: { id: string };
130
+ body?: { name?: string; email?: string };
131
+ headers?: Record<string, string>;
132
+ }>();
133
+ });
134
134
  });
@@ -2,195 +2,195 @@ import { describe, expect, it, vi } from 'vitest';
2
2
  import { createTypedFetcher } from '../fetcher';
3
3
 
4
4
  describe('URL Path Parameter Parsing', () => {
5
- it('should correctly parse single path parameter', async () => {
6
- const mockFetch = vi.fn().mockResolvedValue(
7
- new Response(JSON.stringify({ success: true }), {
8
- status: 200,
9
- headers: { 'Content-Type': 'application/json' },
10
- }),
11
- );
12
-
13
- const client = createTypedFetcher<any>({
14
- baseURL: 'https://api.example.com',
15
- fetch: mockFetch,
16
- });
17
-
18
- await client('GET /users/{userId}' as any, {
19
- params: { userId: '12345' },
20
- });
21
-
22
- expect(mockFetch).toHaveBeenCalledWith(
23
- 'https://api.example.com/users/12345',
24
- expect.any(Object),
25
- );
26
- });
27
-
28
- it('should correctly parse multiple path parameters', async () => {
29
- const mockFetch = vi.fn().mockResolvedValue(
30
- new Response(JSON.stringify({ success: true }), {
31
- status: 200,
32
- headers: { 'Content-Type': 'application/json' },
33
- }),
34
- );
35
-
36
- const client = createTypedFetcher<any>({
37
- fetch: mockFetch,
38
- baseURL: 'https://api.example.com',
39
- });
40
-
41
- await client(
42
- 'GET /organizations/{orgId}/projects/{projectId}/tasks/{taskId}' as any,
43
- {
44
- params: {
45
- orgId: 'org-123',
46
- projectId: 'proj-456',
47
- taskId: 'task-789',
48
- },
49
- },
50
- );
51
-
52
- expect(mockFetch).toHaveBeenCalledWith(
53
- 'https://api.example.com/organizations/org-123/projects/proj-456/tasks/task-789',
54
- expect.any(Object),
55
- );
56
- });
57
-
58
- it('should URL encode special characters in path parameters', async () => {
59
- const mockFetch = vi.fn().mockResolvedValue(
60
- new Response(JSON.stringify({ success: true }), {
61
- status: 200,
62
- headers: { 'Content-Type': 'application/json' },
63
- }),
64
- );
65
-
66
- const client = createTypedFetcher<any>({
67
- baseURL: 'https://api.example.com',
68
- fetch: mockFetch,
69
- });
70
-
71
- await client('GET /files/{filename}' as any, {
72
- params: {
73
- filename: 'my file with spaces & special#characters.txt',
74
- },
75
- });
76
-
77
- expect(mockFetch).toHaveBeenCalledWith(
78
- 'https://api.example.com/files/my%20file%20with%20spaces%20%26%20special%23characters.txt',
79
- expect.any(Object),
80
- );
81
- });
82
-
83
- it('should handle path parameters with forward slashes', async () => {
84
- const mockFetch = vi.fn().mockResolvedValue(
85
- new Response(JSON.stringify({ success: true }), {
86
- status: 200,
87
- headers: { 'Content-Type': 'application/json' },
88
- }),
89
- );
90
-
91
- const client = createTypedFetcher<any>({
92
- baseURL: 'https://api.example.com',
93
- fetch: mockFetch,
94
- });
95
-
96
- await client('GET /repositories/{repoPath}' as any, {
97
- params: {
98
- repoPath: 'owner/repo/branch',
99
- },
100
- });
101
-
102
- expect(mockFetch).toHaveBeenCalledWith(
103
- 'https://api.example.com/repositories/owner%2Frepo%2Fbranch',
104
- expect.any(Object),
105
- );
106
- });
107
-
108
- it('should handle path parameters and query parameters together', async () => {
109
- const mockFetch = vi.fn().mockResolvedValue(
110
- new Response(JSON.stringify({ success: true }), {
111
- status: 200,
112
- headers: { 'Content-Type': 'application/json' },
113
- }),
114
- );
115
- const client = createTypedFetcher<any>({
116
- baseURL: 'https://api.example.com',
117
- fetch: mockFetch,
118
- });
119
-
120
- await client('GET /users/{userId}/posts' as any, {
121
- params: { userId: 'user-123' },
122
- query: { page: 2, limit: 10, sort: 'desc' },
123
- });
124
-
125
- expect(mockFetch).toHaveBeenCalledWith(
126
- 'https://api.example.com/users/user-123/posts?page=2&limit=10&sort=desc',
127
- expect.any(Object),
128
- );
129
- });
130
-
131
- it('should handle numeric path parameters', async () => {
132
- const mockFetch = vi.fn().mockResolvedValue(
133
- new Response(JSON.stringify({ success: true }), {
134
- status: 200,
135
- headers: { 'Content-Type': 'application/json' },
136
- }),
137
- );
138
- const client = createTypedFetcher<any>({
139
- baseURL: 'https://api.example.com',
140
- fetch: mockFetch,
141
- });
142
-
143
- await client('GET /api/v{version}/users/{id}' as any, {
144
- params: { version: 2, id: 12345 },
145
- });
146
-
147
- expect(mockFetch).toHaveBeenCalledWith(
148
- 'https://api.example.com/api/v2/users/12345',
149
- expect.any(Object),
150
- );
151
- });
152
-
153
- it('should handle empty baseURL', async () => {
154
- const mockFetch = vi.fn().mockResolvedValue(
155
- new Response(JSON.stringify({ success: true }), {
156
- status: 200,
157
- headers: { 'Content-Type': 'application/json' },
158
- }),
159
- );
160
- const client = createTypedFetcher<any>({
161
- baseURL: '',
162
- fetch: mockFetch,
163
- });
164
-
165
- await client('GET /users/{id}' as any, {
166
- params: { id: '123' },
167
- });
168
-
169
- expect(mockFetch).toHaveBeenCalledWith('/users/123', expect.any(Object));
170
- });
171
-
172
- it('should not replace parameters that are not provided', async () => {
173
- const mockFetch = vi.fn().mockResolvedValue(
174
- new Response(JSON.stringify({ success: true }), {
175
- status: 200,
176
- headers: { 'Content-Type': 'application/json' },
177
- }),
178
- );
179
-
180
- const client = createTypedFetcher<any>({
181
- baseURL: 'https://api.example.com',
182
- fetch: mockFetch,
183
- });
184
-
185
- // Missing the userId parameter
186
- await client('GET /users/{userId}/posts/{postId}' as any, {
187
- params: { postId: '456' },
188
- });
189
-
190
- // The {userId} should remain in the URL unchanged
191
- expect(mockFetch).toHaveBeenCalledWith(
192
- 'https://api.example.com/users/{userId}/posts/456',
193
- expect.any(Object),
194
- );
195
- });
5
+ it('should correctly parse single path parameter', async () => {
6
+ const mockFetch = vi.fn().mockResolvedValue(
7
+ new Response(JSON.stringify({ success: true }), {
8
+ status: 200,
9
+ headers: { 'Content-Type': 'application/json' },
10
+ }),
11
+ );
12
+
13
+ const client = createTypedFetcher<any>({
14
+ baseURL: 'https://api.example.com',
15
+ fetch: mockFetch,
16
+ });
17
+
18
+ await client('GET /users/{userId}' as any, {
19
+ params: { userId: '12345' },
20
+ });
21
+
22
+ expect(mockFetch).toHaveBeenCalledWith(
23
+ 'https://api.example.com/users/12345',
24
+ expect.any(Object),
25
+ );
26
+ });
27
+
28
+ it('should correctly parse multiple path parameters', async () => {
29
+ const mockFetch = vi.fn().mockResolvedValue(
30
+ new Response(JSON.stringify({ success: true }), {
31
+ status: 200,
32
+ headers: { 'Content-Type': 'application/json' },
33
+ }),
34
+ );
35
+
36
+ const client = createTypedFetcher<any>({
37
+ fetch: mockFetch,
38
+ baseURL: 'https://api.example.com',
39
+ });
40
+
41
+ await client(
42
+ 'GET /organizations/{orgId}/projects/{projectId}/tasks/{taskId}' as any,
43
+ {
44
+ params: {
45
+ orgId: 'org-123',
46
+ projectId: 'proj-456',
47
+ taskId: 'task-789',
48
+ },
49
+ },
50
+ );
51
+
52
+ expect(mockFetch).toHaveBeenCalledWith(
53
+ 'https://api.example.com/organizations/org-123/projects/proj-456/tasks/task-789',
54
+ expect.any(Object),
55
+ );
56
+ });
57
+
58
+ it('should URL encode special characters in path parameters', async () => {
59
+ const mockFetch = vi.fn().mockResolvedValue(
60
+ new Response(JSON.stringify({ success: true }), {
61
+ status: 200,
62
+ headers: { 'Content-Type': 'application/json' },
63
+ }),
64
+ );
65
+
66
+ const client = createTypedFetcher<any>({
67
+ baseURL: 'https://api.example.com',
68
+ fetch: mockFetch,
69
+ });
70
+
71
+ await client('GET /files/{filename}' as any, {
72
+ params: {
73
+ filename: 'my file with spaces & special#characters.txt',
74
+ },
75
+ });
76
+
77
+ expect(mockFetch).toHaveBeenCalledWith(
78
+ 'https://api.example.com/files/my%20file%20with%20spaces%20%26%20special%23characters.txt',
79
+ expect.any(Object),
80
+ );
81
+ });
82
+
83
+ it('should handle path parameters with forward slashes', async () => {
84
+ const mockFetch = vi.fn().mockResolvedValue(
85
+ new Response(JSON.stringify({ success: true }), {
86
+ status: 200,
87
+ headers: { 'Content-Type': 'application/json' },
88
+ }),
89
+ );
90
+
91
+ const client = createTypedFetcher<any>({
92
+ baseURL: 'https://api.example.com',
93
+ fetch: mockFetch,
94
+ });
95
+
96
+ await client('GET /repositories/{repoPath}' as any, {
97
+ params: {
98
+ repoPath: 'owner/repo/branch',
99
+ },
100
+ });
101
+
102
+ expect(mockFetch).toHaveBeenCalledWith(
103
+ 'https://api.example.com/repositories/owner%2Frepo%2Fbranch',
104
+ expect.any(Object),
105
+ );
106
+ });
107
+
108
+ it('should handle path parameters and query parameters together', async () => {
109
+ const mockFetch = vi.fn().mockResolvedValue(
110
+ new Response(JSON.stringify({ success: true }), {
111
+ status: 200,
112
+ headers: { 'Content-Type': 'application/json' },
113
+ }),
114
+ );
115
+ const client = createTypedFetcher<any>({
116
+ baseURL: 'https://api.example.com',
117
+ fetch: mockFetch,
118
+ });
119
+
120
+ await client('GET /users/{userId}/posts' as any, {
121
+ params: { userId: 'user-123' },
122
+ query: { page: 2, limit: 10, sort: 'desc' },
123
+ });
124
+
125
+ expect(mockFetch).toHaveBeenCalledWith(
126
+ 'https://api.example.com/users/user-123/posts?page=2&limit=10&sort=desc',
127
+ expect.any(Object),
128
+ );
129
+ });
130
+
131
+ it('should handle numeric path parameters', async () => {
132
+ const mockFetch = vi.fn().mockResolvedValue(
133
+ new Response(JSON.stringify({ success: true }), {
134
+ status: 200,
135
+ headers: { 'Content-Type': 'application/json' },
136
+ }),
137
+ );
138
+ const client = createTypedFetcher<any>({
139
+ baseURL: 'https://api.example.com',
140
+ fetch: mockFetch,
141
+ });
142
+
143
+ await client('GET /api/v{version}/users/{id}' as any, {
144
+ params: { version: 2, id: 12345 },
145
+ });
146
+
147
+ expect(mockFetch).toHaveBeenCalledWith(
148
+ 'https://api.example.com/api/v2/users/12345',
149
+ expect.any(Object),
150
+ );
151
+ });
152
+
153
+ it('should handle empty baseURL', async () => {
154
+ const mockFetch = vi.fn().mockResolvedValue(
155
+ new Response(JSON.stringify({ success: true }), {
156
+ status: 200,
157
+ headers: { 'Content-Type': 'application/json' },
158
+ }),
159
+ );
160
+ const client = createTypedFetcher<any>({
161
+ baseURL: '',
162
+ fetch: mockFetch,
163
+ });
164
+
165
+ await client('GET /users/{id}' as any, {
166
+ params: { id: '123' },
167
+ });
168
+
169
+ expect(mockFetch).toHaveBeenCalledWith('/users/123', expect.any(Object));
170
+ });
171
+
172
+ it('should not replace parameters that are not provided', async () => {
173
+ const mockFetch = vi.fn().mockResolvedValue(
174
+ new Response(JSON.stringify({ success: true }), {
175
+ status: 200,
176
+ headers: { 'Content-Type': 'application/json' },
177
+ }),
178
+ );
179
+
180
+ const client = createTypedFetcher<any>({
181
+ baseURL: 'https://api.example.com',
182
+ fetch: mockFetch,
183
+ });
184
+
185
+ // Missing the userId parameter
186
+ await client('GET /users/{userId}/posts/{postId}' as any, {
187
+ params: { postId: '456' },
188
+ });
189
+
190
+ // The {userId} should remain in the URL unchanged
191
+ expect(mockFetch).toHaveBeenCalledWith(
192
+ 'https://api.example.com/users/{userId}/posts/456',
193
+ expect.any(Object),
194
+ );
195
+ });
196
196
  });