@geekmidas/client 0.4.0 → 0.5.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 (71) hide show
  1. package/dist/auth-fetcher.cjs +0 -1
  2. package/dist/auth-fetcher.cjs.map +1 -1
  3. package/dist/auth-fetcher.d.cts +1 -1
  4. package/dist/auth-fetcher.d.cts.map +1 -1
  5. package/dist/auth-fetcher.d.mts +1 -1
  6. package/dist/auth-fetcher.d.mts.map +1 -1
  7. package/dist/auth-fetcher.mjs +0 -1
  8. package/dist/auth-fetcher.mjs.map +1 -1
  9. package/dist/endpoint-hooks.cjs +10 -5
  10. package/dist/endpoint-hooks.cjs.map +1 -1
  11. package/dist/endpoint-hooks.d.cts +2 -2
  12. package/dist/endpoint-hooks.d.cts.map +1 -1
  13. package/dist/endpoint-hooks.d.mts +2 -2
  14. package/dist/endpoint-hooks.d.mts.map +1 -1
  15. package/dist/endpoint-hooks.mjs +10 -5
  16. package/dist/endpoint-hooks.mjs.map +1 -1
  17. package/dist/fetcher-5fnBE7Dk.mjs.map +1 -1
  18. package/dist/fetcher-CgLEaIAC.cjs.map +1 -1
  19. package/dist/fetcher.d.cts +1 -1
  20. package/dist/fetcher.d.cts.map +1 -1
  21. package/dist/fetcher.d.mts +1 -1
  22. package/dist/fetcher.d.mts.map +1 -1
  23. package/dist/infer.d.cts.map +1 -1
  24. package/dist/infer.d.mts.map +1 -1
  25. package/dist/openapi-hooks.cjs.map +1 -1
  26. package/dist/openapi-hooks.d.cts +2 -2
  27. package/dist/openapi-hooks.d.cts.map +1 -1
  28. package/dist/openapi-hooks.d.mts +2 -2
  29. package/dist/openapi-hooks.d.mts.map +1 -1
  30. package/dist/openapi-hooks.mjs.map +1 -1
  31. package/dist/openapi-types.d.cts.map +1 -1
  32. package/dist/openapi-types.d.mts.map +1 -1
  33. package/dist/openapi.cjs.map +1 -1
  34. package/dist/openapi.mjs.map +1 -1
  35. package/dist/react-query.cjs.map +1 -1
  36. package/dist/react-query.d.cts +1 -1
  37. package/dist/react-query.d.cts.map +1 -1
  38. package/dist/react-query.d.mts +1 -1
  39. package/dist/react-query.d.mts.map +1 -1
  40. package/dist/react-query.mjs.map +1 -1
  41. package/dist/{types-FxummKaL.d.mts → types-4K4N-Fl1.d.cts} +2 -2
  42. package/dist/types-4K4N-Fl1.d.cts.map +1 -0
  43. package/dist/{types-vutATyWv.d.cts → types-hhkZWjQn.d.mts} +2 -2
  44. package/dist/types-hhkZWjQn.d.mts.map +1 -0
  45. package/dist/types.d.cts +1 -1
  46. package/dist/types.d.mts +1 -1
  47. package/package.json +3 -3
  48. package/src/__tests__/auth-fetcher.spec.ts +476 -476
  49. package/src/__tests__/endpoint-hooks.spec.tsx +348 -348
  50. package/src/__tests__/fetcher.spec.ts +403 -403
  51. package/src/__tests__/infer.integration.spec.ts +171 -171
  52. package/src/__tests__/infer.spec.ts +182 -182
  53. package/src/__tests__/method-restrictions.spec.tsx +165 -165
  54. package/src/__tests__/openapi-hooks.spec.tsx +536 -536
  55. package/src/__tests__/react-query-infinite.spec.tsx +989 -989
  56. package/src/__tests__/react-query-invalidation.spec.tsx +223 -222
  57. package/src/__tests__/react-query.spec.tsx +567 -567
  58. package/src/__tests__/setup.ts +260 -260
  59. package/src/__tests__/types.spec.ts +127 -127
  60. package/src/__tests__/url-parsing.spec.ts +191 -191
  61. package/src/auth-fetcher.ts +160 -162
  62. package/src/endpoint-hooks.ts +151 -156
  63. package/src/fetcher.ts +163 -163
  64. package/src/infer.ts +64 -63
  65. package/src/openapi-hooks.ts +146 -146
  66. package/src/openapi-types.d.ts +428 -428
  67. package/src/openapi.json +593 -593
  68. package/src/react-query.ts +281 -281
  69. package/src/types.ts +121 -124
  70. package/dist/types-FxummKaL.d.mts.map +0 -1
  71. 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
  });