@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
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: {} };