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