@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
package/src/fetcher.ts CHANGED
@@ -1,175 +1,175 @@
1
1
  import type {
2
- EndpointString,
3
- ExtractEndpointResponse,
4
- FetcherOptions,
5
- FilteredRequestConfig,
6
- ParseEndpoint,
7
- TypedEndpoint,
2
+ EndpointString,
3
+ ExtractEndpointResponse,
4
+ FetcherOptions,
5
+ FilteredRequestConfig,
6
+ ParseEndpoint,
7
+ TypedEndpoint,
8
8
  } from './types';
9
9
 
10
10
  export type { FetcherOptions } from './types';
11
11
 
12
12
  export class TypedFetcher<Paths> {
13
- private baseURL: string;
14
- private defaultHeaders: Record<string, string>;
15
- private options: FetcherOptions;
16
- private fetchFn: FetchFn;
17
-
18
- static getFetchFn(fn?: FetchFn): FetchFn {
19
- if (fn) {
20
- return fn;
21
- }
22
-
23
- if (typeof window !== 'undefined' && typeof window.fetch === 'function') {
24
- return window.fetch.bind(window);
25
- }
26
-
27
- if (
28
- typeof globalThis !== 'undefined' &&
29
- typeof globalThis.fetch === 'function'
30
- ) {
31
- return globalThis.fetch.bind(globalThis);
32
- }
33
-
34
- throw new Error('No fetch implementation found');
35
- }
36
-
37
- constructor(options: FetcherOptions = {}) {
38
- this.baseURL = options.baseURL || '';
39
- this.defaultHeaders = options.headers || {};
40
- this.options = options;
41
- this.fetchFn = TypedFetcher.getFetchFn(options.fetch);
42
- }
43
-
44
- async request<T extends TypedEndpoint<Paths>>(
45
- endpoint: T,
46
- config?: FilteredRequestConfig<Paths, T>,
47
- ): Promise<ExtractEndpointResponse<Paths, T>> {
48
- const { method, route } = this.parseEndpoint(endpoint);
49
-
50
- // Replace path parameters
51
- let url = route;
52
- if (config && 'params' in config && config.params) {
53
- Object.entries(config.params as Record<string, unknown>).forEach(
54
- ([key, value]) => {
55
- url = url.replace(`{${key}}`, encodeURIComponent(String(value)));
56
- },
57
- );
58
- }
59
-
60
- // Add query parameters
61
- if (config && 'query' in config && config.query) {
62
- const queryParams = new URLSearchParams();
63
-
64
- // Recursive function to handle nested objects and arrays
65
- const appendQueryParam = (prefix: string, value: unknown) => {
66
- if (value === undefined || value === null) {
67
- return;
68
- }
69
-
70
- if (Array.isArray(value)) {
71
- // Handle arrays by appending multiple values with the same key
72
- value.forEach((item) => {
73
- queryParams.append(prefix, String(item));
74
- });
75
- } else if (typeof value === 'object') {
76
- // For objects, recursively flatten into dot notation
77
- Object.entries(value as Record<string, unknown>).forEach(
78
- ([subKey, subValue]) => {
79
- appendQueryParam(`${prefix}.${subKey}`, subValue);
80
- },
81
- );
82
- } else {
83
- queryParams.append(prefix, String(value));
84
- }
85
- };
86
-
87
- // Process all query parameters
88
- Object.entries(config.query as Record<string, unknown>).forEach(
89
- ([key, value]) => {
90
- appendQueryParam(key, value);
91
- },
92
- );
93
-
94
- const queryString = queryParams.toString();
95
- if (queryString) {
96
- url += `?${queryString}`;
97
- }
98
- }
99
-
100
- // Build request configuration
101
- let requestConfig: RequestInit = {
102
- method: method.toUpperCase(),
103
- headers: {
104
- ...this.defaultHeaders,
105
- ...((config && 'headers' in config && config.headers) || {}),
106
- },
107
- };
108
-
109
- // Add body if present
110
- if (config && 'body' in config && config.body) {
111
- requestConfig.body = JSON.stringify(config.body);
112
- requestConfig.headers = {
113
- ...requestConfig.headers,
114
- 'Content-Type': 'application/json',
115
- };
116
- }
117
-
118
- // Apply request interceptor
119
- if (this.options.onRequest) {
120
- requestConfig = await this.options.onRequest(requestConfig);
121
- }
122
-
123
- try {
124
- // Make the request
125
- let response = await this.fetchFn(`${this.baseURL}${url}`, requestConfig);
126
-
127
- // Apply response interceptor
128
- if (this.options.onResponse) {
129
- response = await this.options.onResponse(response);
130
- }
131
-
132
- // Handle errors
133
- if (!response.ok) {
134
- throw response;
135
- }
136
-
137
- // Handle empty responses (204 No Content, etc.)
138
- if (
139
- response.status === 204 ||
140
- response.headers.get('content-length') === '0'
141
- ) {
142
- return undefined as ExtractEndpointResponse<Paths, T>;
143
- }
144
-
145
- // Parse JSON response
146
- const data = await response.json();
147
- return data as ExtractEndpointResponse<Paths, T>;
148
- } catch (error) {
149
- // Apply error handler
150
- if (this.options.onError) {
151
- // @ts-ignore
152
- await this.options.onError(error);
153
- }
154
- throw error;
155
- }
156
- }
157
-
158
- private parseEndpoint<T extends EndpointString>(
159
- endpoint: T,
160
- ): ParseEndpoint<T> {
161
- const [method, ...routeParts] = endpoint.split(' ');
162
- const route = routeParts.join(' ');
163
- return { method: method?.toLowerCase() ?? '', route } as ParseEndpoint<T>;
164
- }
13
+ private baseURL: string;
14
+ private defaultHeaders: Record<string, string>;
15
+ private options: FetcherOptions;
16
+ private fetchFn: FetchFn;
17
+
18
+ static getFetchFn(fn?: FetchFn): FetchFn {
19
+ if (fn) {
20
+ return fn;
21
+ }
22
+
23
+ if (typeof window !== 'undefined' && typeof window.fetch === 'function') {
24
+ return window.fetch.bind(window);
25
+ }
26
+
27
+ if (
28
+ typeof globalThis !== 'undefined' &&
29
+ typeof globalThis.fetch === 'function'
30
+ ) {
31
+ return globalThis.fetch.bind(globalThis);
32
+ }
33
+
34
+ throw new Error('No fetch implementation found');
35
+ }
36
+
37
+ constructor(options: FetcherOptions = {}) {
38
+ this.baseURL = options.baseURL || '';
39
+ this.defaultHeaders = options.headers || {};
40
+ this.options = options;
41
+ this.fetchFn = TypedFetcher.getFetchFn(options.fetch);
42
+ }
43
+
44
+ async request<T extends TypedEndpoint<Paths>>(
45
+ endpoint: T,
46
+ config?: FilteredRequestConfig<Paths, T>,
47
+ ): Promise<ExtractEndpointResponse<Paths, T>> {
48
+ const { method, route } = this.parseEndpoint(endpoint);
49
+
50
+ // Replace path parameters
51
+ let url = route;
52
+ if (config && 'params' in config && config.params) {
53
+ Object.entries(config.params as Record<string, unknown>).forEach(
54
+ ([key, value]) => {
55
+ url = url.replace(`{${key}}`, encodeURIComponent(String(value)));
56
+ },
57
+ );
58
+ }
59
+
60
+ // Add query parameters
61
+ if (config && 'query' in config && config.query) {
62
+ const queryParams = new URLSearchParams();
63
+
64
+ // Recursive function to handle nested objects and arrays
65
+ const appendQueryParam = (prefix: string, value: unknown) => {
66
+ if (value === undefined || value === null) {
67
+ return;
68
+ }
69
+
70
+ if (Array.isArray(value)) {
71
+ // Handle arrays by appending multiple values with the same key
72
+ value.forEach((item) => {
73
+ queryParams.append(prefix, String(item));
74
+ });
75
+ } else if (typeof value === 'object') {
76
+ // For objects, recursively flatten into dot notation
77
+ Object.entries(value as Record<string, unknown>).forEach(
78
+ ([subKey, subValue]) => {
79
+ appendQueryParam(`${prefix}.${subKey}`, subValue);
80
+ },
81
+ );
82
+ } else {
83
+ queryParams.append(prefix, String(value));
84
+ }
85
+ };
86
+
87
+ // Process all query parameters
88
+ Object.entries(config.query as Record<string, unknown>).forEach(
89
+ ([key, value]) => {
90
+ appendQueryParam(key, value);
91
+ },
92
+ );
93
+
94
+ const queryString = queryParams.toString();
95
+ if (queryString) {
96
+ url += `?${queryString}`;
97
+ }
98
+ }
99
+
100
+ // Build request configuration
101
+ let requestConfig: RequestInit = {
102
+ method: method.toUpperCase(),
103
+ headers: {
104
+ ...this.defaultHeaders,
105
+ ...((config && 'headers' in config && config.headers) || {}),
106
+ },
107
+ };
108
+
109
+ // Add body if present
110
+ if (config && 'body' in config && config.body) {
111
+ requestConfig.body = JSON.stringify(config.body);
112
+ requestConfig.headers = {
113
+ ...requestConfig.headers,
114
+ 'Content-Type': 'application/json',
115
+ };
116
+ }
117
+
118
+ // Apply request interceptor
119
+ if (this.options.onRequest) {
120
+ requestConfig = await this.options.onRequest(requestConfig);
121
+ }
122
+
123
+ try {
124
+ // Make the request
125
+ let response = await this.fetchFn(`${this.baseURL}${url}`, requestConfig);
126
+
127
+ // Apply response interceptor
128
+ if (this.options.onResponse) {
129
+ response = await this.options.onResponse(response);
130
+ }
131
+
132
+ // Handle errors
133
+ if (!response.ok) {
134
+ throw response;
135
+ }
136
+
137
+ // Handle empty responses (204 No Content, etc.)
138
+ if (
139
+ response.status === 204 ||
140
+ response.headers.get('content-length') === '0'
141
+ ) {
142
+ return undefined as ExtractEndpointResponse<Paths, T>;
143
+ }
144
+
145
+ // Parse JSON response
146
+ const data = await response.json();
147
+ return data as ExtractEndpointResponse<Paths, T>;
148
+ } catch (error) {
149
+ // Apply error handler
150
+ if (this.options.onError) {
151
+ // @ts-expect-error
152
+ await this.options.onError(error);
153
+ }
154
+ throw error;
155
+ }
156
+ }
157
+
158
+ private parseEndpoint<T extends EndpointString>(
159
+ endpoint: T,
160
+ ): ParseEndpoint<T> {
161
+ const [method, ...routeParts] = endpoint.split(' ');
162
+ const route = routeParts.join(' ');
163
+ return { method: method?.toLowerCase() ?? '', route } as ParseEndpoint<T>;
164
+ }
165
165
  }
166
166
 
167
167
  export function createTypedFetcher<Paths>(options?: FetcherOptions) {
168
- const fetcher = new TypedFetcher<Paths>(options);
169
- return <T extends TypedEndpoint<Paths>>(
170
- endpoint: T,
171
- config?: FilteredRequestConfig<Paths, T>,
172
- ) => fetcher.request(endpoint, config);
168
+ const fetcher = new TypedFetcher<Paths>(options);
169
+ return <T extends TypedEndpoint<Paths>>(
170
+ endpoint: T,
171
+ config?: FilteredRequestConfig<Paths, T>,
172
+ ) => fetcher.request(endpoint, config);
173
173
  }
174
174
 
175
175
  export type FetchFn = typeof fetch;
package/src/infer.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type {
2
- Endpoint,
3
- EndpointSchemas,
2
+ Endpoint,
3
+ EndpointSchemas,
4
4
  } from '@geekmidas/constructs/endpoints';
5
5
  import type { HttpMethod } from '@geekmidas/constructs/types';
6
6
  import type { InferStandardSchema } from '@geekmidas/schema';
@@ -11,9 +11,9 @@ import type { StandardSchemaV1 } from '@standard-schema/spec';
11
11
  * @example '/users/{id}/posts/{postId}' -> { id: string, postId: string }
12
12
  */
13
13
  type InferPathParams<TRoute extends string> =
14
- TRoute extends `${string}{${infer Param}}${infer Rest}`
15
- ? { [K in Param]: string } & InferPathParams<Rest>
16
- : {};
14
+ TRoute extends `${string}{${infer Param}}${infer Rest}`
15
+ ? { [K in Param]: string } & InferPathParams<Rest>
16
+ : {};
17
17
 
18
18
  /**
19
19
  * Converts an HTTP method to lowercase for TypedFetcher compatibility
@@ -24,50 +24,50 @@ type LowercaseMethod<T extends HttpMethod> = Lowercase<T>;
24
24
  * Infers route-level parameters (path params)
25
25
  */
26
26
  type InferRouteParameters<TRoute extends string> =
27
- InferPathParams<TRoute> extends Record<string, never>
28
- ? {}
29
- : {
30
- parameters: {
31
- path: InferPathParams<TRoute>;
32
- };
33
- };
27
+ InferPathParams<TRoute> extends Record<string, never>
28
+ ? {}
29
+ : {
30
+ parameters: {
31
+ path: InferPathParams<TRoute>;
32
+ };
33
+ };
34
34
 
35
35
  /**
36
36
  * Infers operation-level parameters (query params)
37
37
  */
38
38
  type InferOperationParameters<TInput extends EndpointSchemas> = TInput extends {
39
- query: infer Q;
39
+ query: infer Q;
40
40
  }
41
- ? {
42
- parameters: {
43
- query: InferStandardSchema<Q>;
44
- };
45
- }
46
- : {};
41
+ ? {
42
+ parameters: {
43
+ query: InferStandardSchema<Q>;
44
+ };
45
+ }
46
+ : {};
47
47
 
48
48
  /**
49
49
  * Infers the operation object compatible with TypedFetcher
50
50
  */
51
51
  type InferOperation<
52
- TInput extends EndpointSchemas,
53
- TOutput extends StandardSchemaV1 | undefined,
52
+ TInput extends EndpointSchemas,
53
+ TOutput extends StandardSchemaV1 | undefined,
54
54
  > = InferOperationParameters<TInput> & {
55
- requestBody?: TInput extends { body: infer B }
56
- ? {
57
- content: {
58
- 'application/json': InferStandardSchema<B>;
59
- };
60
- }
61
- : never;
62
- responses: {
63
- 200: {
64
- content: TOutput extends StandardSchemaV1
65
- ? {
66
- 'application/json': InferStandardSchema<TOutput>;
67
- }
68
- : never;
69
- };
70
- };
55
+ requestBody?: TInput extends { body: infer B }
56
+ ? {
57
+ content: {
58
+ 'application/json': InferStandardSchema<B>;
59
+ };
60
+ }
61
+ : never;
62
+ responses: {
63
+ 200: {
64
+ content: TOutput extends StandardSchemaV1
65
+ ? {
66
+ 'application/json': InferStandardSchema<TOutput>;
67
+ }
68
+ : never;
69
+ };
70
+ };
71
71
  };
72
72
 
73
73
  /**
@@ -94,23 +94,24 @@ type InferOperation<
94
94
  * const user = await client('GET /users/{id}', { params: { id: '123' } });
95
95
  * ```
96
96
  */
97
- export type InferOpenApiFromEndpoint<T> = T extends Endpoint<
98
- infer TRoute,
99
- infer TMethod,
100
- infer TInput,
101
- infer TOutput,
102
- any,
103
- any,
104
- any
105
- >
106
- ? {
107
- paths: {
108
- [K in TRoute]: InferRouteParameters<TRoute> & {
109
- [M in LowercaseMethod<TMethod>]: InferOperation<TInput, TOutput>;
110
- };
111
- };
112
- }
113
- : never;
97
+ export type InferOpenApiFromEndpoint<T> =
98
+ T extends Endpoint<
99
+ infer TRoute,
100
+ infer TMethod,
101
+ infer TInput,
102
+ infer TOutput,
103
+ any,
104
+ any,
105
+ any
106
+ >
107
+ ? {
108
+ paths: {
109
+ [K in TRoute]: InferRouteParameters<TRoute> & {
110
+ [M in LowercaseMethod<TMethod>]: InferOperation<TInput, TOutput>;
111
+ };
112
+ };
113
+ }
114
+ : never;
114
115
 
115
116
  /**
116
117
  * Infers TypedFetcher-compatible paths structure from multiple endpoints
@@ -150,12 +151,12 @@ export type InferOpenApiFromEndpoint<T> = T extends Endpoint<
150
151
  * ```
151
152
  */
152
153
  export type InferOpenApi<TEndpoints extends readonly any[]> =
153
- TEndpoints extends readonly [infer First, ...infer Rest]
154
- ? InferOpenApiFromEndpoint<First> extends { paths: infer P1 }
155
- ? Rest extends []
156
- ? { paths: P1 }
157
- : InferOpenApi<Rest> extends { paths: infer P2 }
158
- ? { paths: P1 & P2 }
159
- : { paths: P1 }
160
- : InferOpenApi<Rest>
161
- : { paths: {} };
154
+ TEndpoints extends readonly [infer First, ...infer Rest]
155
+ ? InferOpenApiFromEndpoint<First> extends { paths: infer P1 }
156
+ ? Rest extends []
157
+ ? { paths: P1 }
158
+ : InferOpenApi<Rest> extends { paths: infer P2 }
159
+ ? { paths: P1 & P2 }
160
+ : { paths: P1 }
161
+ : InferOpenApi<Rest>
162
+ : { paths: {} };