@geekmidas/client 0.0.1

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 (51) hide show
  1. package/README.md +518 -0
  2. package/dist/chunk-CUT6urMc.cjs +30 -0
  3. package/dist/fetcher-DLDD_7Sa.mjs +86 -0
  4. package/dist/fetcher-DLDD_7Sa.mjs.map +1 -0
  5. package/dist/fetcher-KdwHgdAl.cjs +98 -0
  6. package/dist/fetcher-KdwHgdAl.cjs.map +1 -0
  7. package/dist/fetcher.cjs +4 -0
  8. package/dist/fetcher.d.cts +18 -0
  9. package/dist/fetcher.d.mts +18 -0
  10. package/dist/fetcher.mjs +3 -0
  11. package/dist/openapi-hooks.cjs +44 -0
  12. package/dist/openapi-hooks.cjs.map +1 -0
  13. package/dist/openapi-hooks.d.cts +99 -0
  14. package/dist/openapi-hooks.d.mts +99 -0
  15. package/dist/openapi-hooks.mjs +43 -0
  16. package/dist/openapi-hooks.mjs.map +1 -0
  17. package/dist/openapi-types.d.cjs +0 -0
  18. package/dist/openapi-types.d.cts +443 -0
  19. package/dist/openapi-types.d.mts +443 -0
  20. package/dist/openapi.cjs +526 -0
  21. package/dist/openapi.cjs.map +1 -0
  22. package/dist/openapi.mjs +501 -0
  23. package/dist/openapi.mjs.map +1 -0
  24. package/dist/react-query.cjs +147 -0
  25. package/dist/react-query.cjs.map +1 -0
  26. package/dist/react-query.d.cts +77 -0
  27. package/dist/react-query.d.mts +77 -0
  28. package/dist/react-query.mjs +141 -0
  29. package/dist/react-query.mjs.map +1 -0
  30. package/dist/types-csACmD6U.d.cts +68 -0
  31. package/dist/types-tMp85Lt_.d.mts +68 -0
  32. package/dist/types.cjs +0 -0
  33. package/dist/types.d.cts +2 -0
  34. package/dist/types.d.mts +2 -0
  35. package/dist/types.mjs +0 -0
  36. package/package.json +59 -0
  37. package/src/__tests__/fetcher.spec.ts +409 -0
  38. package/src/__tests__/method-restrictions.spec.tsx +227 -0
  39. package/src/__tests__/openapi-hooks.spec.tsx +558 -0
  40. package/src/__tests__/react-query-infinite.spec.tsx +1003 -0
  41. package/src/__tests__/react-query-invalidation.spec.tsx +238 -0
  42. package/src/__tests__/react-query.spec.tsx +582 -0
  43. package/src/__tests__/setup.ts +281 -0
  44. package/src/__tests__/types.spec.ts +134 -0
  45. package/src/__tests__/url-parsing.spec.ts +196 -0
  46. package/src/fetcher.ts +173 -0
  47. package/src/openapi-hooks.ts +193 -0
  48. package/src/openapi-types.d.ts +440 -0
  49. package/src/openapi.json +595 -0
  50. package/src/react-query.ts +341 -0
  51. package/src/types.ts +149 -0
package/src/fetcher.ts ADDED
@@ -0,0 +1,173 @@
1
+ import type {
2
+ EndpointString,
3
+ ExtractEndpointResponse,
4
+ FetcherOptions,
5
+ FilteredRequestConfig,
6
+ ParseEndpoint,
7
+ TypedEndpoint,
8
+ } from './types';
9
+
10
+ export class TypedFetcher<Paths> {
11
+ private baseURL: string;
12
+ private defaultHeaders: Record<string, string>;
13
+ private options: FetcherOptions;
14
+ private fetchFn: FetchFn;
15
+
16
+ static getFetchFn(fn?: FetchFn): FetchFn {
17
+ if (fn) {
18
+ return fn;
19
+ }
20
+
21
+ if (typeof window !== 'undefined' && typeof window.fetch === 'function') {
22
+ return window.fetch.bind(window);
23
+ }
24
+
25
+ if (
26
+ typeof globalThis !== 'undefined' &&
27
+ typeof globalThis.fetch === 'function'
28
+ ) {
29
+ return globalThis.fetch.bind(globalThis);
30
+ }
31
+
32
+ throw new Error('No fetch implementation found');
33
+ }
34
+
35
+ constructor(options: FetcherOptions = {}) {
36
+ this.baseURL = options.baseURL || '';
37
+ this.defaultHeaders = options.headers || {};
38
+ this.options = options;
39
+ this.fetchFn = TypedFetcher.getFetchFn(options.fetch);
40
+ }
41
+
42
+ async request<T extends TypedEndpoint<Paths>>(
43
+ endpoint: T,
44
+ config?: FilteredRequestConfig<Paths, T>,
45
+ ): Promise<ExtractEndpointResponse<Paths, T>> {
46
+ const { method, route } = this.parseEndpoint(endpoint);
47
+
48
+ // Replace path parameters
49
+ let url = route;
50
+ if (config && 'params' in config && config.params) {
51
+ Object.entries(config.params as Record<string, unknown>).forEach(
52
+ ([key, value]) => {
53
+ url = url.replace(`{${key}}`, encodeURIComponent(String(value)));
54
+ },
55
+ );
56
+ }
57
+
58
+ // Add query parameters
59
+ if (config && 'query' in config && config.query) {
60
+ const queryParams = new URLSearchParams();
61
+
62
+ // Recursive function to handle nested objects and arrays
63
+ const appendQueryParam = (prefix: string, value: unknown) => {
64
+ if (value === undefined || value === null) {
65
+ return;
66
+ }
67
+
68
+ if (Array.isArray(value)) {
69
+ // Handle arrays by appending multiple values with the same key
70
+ value.forEach((item) => {
71
+ queryParams.append(prefix, String(item));
72
+ });
73
+ } else if (typeof value === 'object') {
74
+ // For objects, recursively flatten into dot notation
75
+ Object.entries(value as Record<string, unknown>).forEach(
76
+ ([subKey, subValue]) => {
77
+ appendQueryParam(`${prefix}.${subKey}`, subValue);
78
+ },
79
+ );
80
+ } else {
81
+ queryParams.append(prefix, String(value));
82
+ }
83
+ };
84
+
85
+ // Process all query parameters
86
+ Object.entries(config.query as Record<string, unknown>).forEach(
87
+ ([key, value]) => {
88
+ appendQueryParam(key, value);
89
+ },
90
+ );
91
+
92
+ const queryString = queryParams.toString();
93
+ if (queryString) {
94
+ url += `?${queryString}`;
95
+ }
96
+ }
97
+
98
+ // Build request configuration
99
+ let requestConfig: RequestInit = {
100
+ method: method.toUpperCase(),
101
+ headers: {
102
+ ...this.defaultHeaders,
103
+ ...((config && 'headers' in config && config.headers) || {}),
104
+ },
105
+ };
106
+
107
+ // Add body if present
108
+ if (config && 'body' in config && config.body) {
109
+ requestConfig.body = JSON.stringify(config.body);
110
+ requestConfig.headers = {
111
+ ...requestConfig.headers,
112
+ 'Content-Type': 'application/json',
113
+ };
114
+ }
115
+
116
+ // Apply request interceptor
117
+ if (this.options.onRequest) {
118
+ requestConfig = await this.options.onRequest(requestConfig);
119
+ }
120
+
121
+ try {
122
+ // Make the request
123
+ let response = await this.fetchFn(`${this.baseURL}${url}`, requestConfig);
124
+
125
+ // Apply response interceptor
126
+ if (this.options.onResponse) {
127
+ response = await this.options.onResponse(response);
128
+ }
129
+
130
+ // Handle errors
131
+ if (!response.ok) {
132
+ throw response;
133
+ }
134
+
135
+ // Handle empty responses (204 No Content, etc.)
136
+ if (
137
+ response.status === 204 ||
138
+ response.headers.get('content-length') === '0'
139
+ ) {
140
+ return undefined as ExtractEndpointResponse<Paths, T>;
141
+ }
142
+
143
+ // Parse JSON response
144
+ const data = await response.json();
145
+ return data as ExtractEndpointResponse<Paths, T>;
146
+ } catch (error) {
147
+ // Apply error handler
148
+ if (this.options.onError) {
149
+ // @ts-ignore
150
+ await this.options.onError(error);
151
+ }
152
+ throw error;
153
+ }
154
+ }
155
+
156
+ private parseEndpoint<T extends EndpointString>(
157
+ endpoint: T,
158
+ ): ParseEndpoint<T> {
159
+ const [method, ...routeParts] = endpoint.split(' ');
160
+ const route = routeParts.join(' ');
161
+ return { method: method.toLowerCase(), route } as ParseEndpoint<T>;
162
+ }
163
+ }
164
+
165
+ export function createTypedFetcher<Paths>(options?: FetcherOptions) {
166
+ const fetcher = new TypedFetcher<Paths>(options);
167
+ return <T extends TypedEndpoint<Paths>>(
168
+ endpoint: T,
169
+ config?: FilteredRequestConfig<Paths, T>,
170
+ ) => fetcher.request(endpoint, config);
171
+ }
172
+
173
+ export type FetchFn = typeof fetch;
@@ -0,0 +1,193 @@
1
+ import type {
2
+ UseMutationOptions,
3
+ UseQueryOptions,
4
+ } from '@tanstack/react-query';
5
+ import { useMutation, useQuery } from '@tanstack/react-query';
6
+ import { createTypedFetcher } from './fetcher';
7
+ import type { FetcherOptions } from './types';
8
+
9
+ // Simplified type helpers for OpenAPI paths
10
+ type HttpMethods = 'get' | 'post' | 'put' | 'patch' | 'delete';
11
+
12
+ // Extract all operations from paths
13
+ type AllOperations<Paths> = {
14
+ [Path in keyof Paths]: {
15
+ [Method in keyof Paths[Path] & HttpMethods]: Paths[Path][Method] extends {
16
+ operationId: infer OpId;
17
+ }
18
+ ? OpId extends string
19
+ ? {
20
+ operationId: OpId;
21
+ path: Path;
22
+ method: Method;
23
+ spec: Paths[Path][Method] & {
24
+ _pathParams?: Paths[Path] extends {
25
+ parameters: { path: infer P };
26
+ }
27
+ ? P
28
+ : never;
29
+ };
30
+ }
31
+ : never
32
+ : never;
33
+ }[keyof Paths[Path] & HttpMethods];
34
+ }[keyof Paths];
35
+
36
+ // Create operation map
37
+ type OperationMap<Paths> = {
38
+ [Op in AllOperations<Paths> as Op extends {
39
+ operationId: infer Id extends string;
40
+ }
41
+ ? Id
42
+ : never]: Op;
43
+ };
44
+
45
+ // Get operation IDs
46
+ type OperationId<Paths> = keyof OperationMap<Paths>;
47
+
48
+ // Get operations by method
49
+ type OperationsByMethod<Paths, Method extends HttpMethods> = {
50
+ [K in OperationId<Paths>]: OperationMap<Paths>[K] extends { method: Method }
51
+ ? K
52
+ : never;
53
+ }[OperationId<Paths>];
54
+
55
+ // Extract parameter types
56
+ type OperationParams<
57
+ Paths,
58
+ OpId extends OperationId<Paths>,
59
+ > = OperationMap<Paths>[OpId] extends {
60
+ path: infer Path extends keyof Paths;
61
+ spec: infer Spec;
62
+ }
63
+ ? {
64
+ params?: Paths[Path] extends { parameters: { path: infer P } }
65
+ ? P
66
+ : Spec extends { parameters: { path: infer P } }
67
+ ? P
68
+ : never;
69
+ query?: Spec extends { parameters: { query?: infer Q } } ? Q : never;
70
+ body?: Spec extends {
71
+ requestBody: { content: { 'application/json': infer Body } };
72
+ }
73
+ ? Body
74
+ : Spec extends {
75
+ requestBody: {
76
+ required: true;
77
+ content: { 'application/json': infer Body };
78
+ };
79
+ }
80
+ ? Body
81
+ : never;
82
+ }
83
+ : never;
84
+
85
+ // Extract response type
86
+ type OperationResponse<
87
+ Paths,
88
+ OpId extends OperationId<Paths>,
89
+ > = OperationMap<Paths>[OpId] extends { spec: infer Spec }
90
+ ? Spec extends {
91
+ responses: { 200: { content: { 'application/json': infer R } } };
92
+ }
93
+ ? R
94
+ : Spec extends {
95
+ responses: { 201: { content: { 'application/json': infer R } } };
96
+ }
97
+ ? R
98
+ : Spec extends { responses: { 204: any } }
99
+ ? void
100
+ : unknown
101
+ : never;
102
+
103
+ // Remove never properties
104
+ type RemoveNever<T> = Pick<
105
+ T,
106
+ {
107
+ [K in keyof T]: T[K] extends never ? never : K;
108
+ }[keyof T]
109
+ >;
110
+
111
+ // Check if type is empty
112
+ type IsEmpty<T> = keyof T extends never ? true : false;
113
+
114
+ // Runtime operation registry (would be generated)
115
+ interface OperationRegistry {
116
+ [operationId: string]: {
117
+ path: string;
118
+ method: string;
119
+ };
120
+ }
121
+
122
+ export function createOpenAPIHooks<Paths>(
123
+ options: FetcherOptions & { operations?: OperationRegistry } = {},
124
+ ) {
125
+ const { operations, ...fetcherOptions } = options;
126
+ const fetcher = createTypedFetcher<Paths>(fetcherOptions);
127
+
128
+ function buildEndpoint<OpId extends OperationId<Paths>>(
129
+ operationId: OpId,
130
+ ): string {
131
+ // Runtime lookup from registry
132
+ if (operations && operations[operationId as string]) {
133
+ const op = operations[operationId as string];
134
+ return `${op.method.toUpperCase()} ${op.path}`;
135
+ }
136
+ // Fallback for compile-time only usage
137
+ return operationId as string;
138
+ }
139
+
140
+ return {
141
+ useQuery: <OpId extends OperationsByMethod<Paths, 'get'>>(
142
+ operationId: OpId,
143
+ config?: RemoveNever<OperationParams<Paths, OpId>>,
144
+ options?: Omit<
145
+ UseQueryOptions<OperationResponse<Paths, OpId>, Error>,
146
+ 'queryKey' | 'queryFn'
147
+ >,
148
+ ) => {
149
+ const endpoint = buildEndpoint(operationId);
150
+ const queryKey = [operationId, ...(config ? [config] : [])];
151
+
152
+ return useQuery<OperationResponse<Paths, OpId>, Error>({
153
+ queryKey,
154
+ queryFn: async () => {
155
+ const response = await fetcher(endpoint as any, config as any);
156
+ return response as OperationResponse<Paths, OpId>;
157
+ },
158
+ ...options,
159
+ });
160
+ },
161
+
162
+ useMutation: <
163
+ OpId extends Exclude<
164
+ OperationId<Paths>,
165
+ OperationsByMethod<Paths, 'get'>
166
+ >,
167
+ >(
168
+ operationId: OpId,
169
+ options?: Omit<
170
+ UseMutationOptions<
171
+ OperationResponse<Paths, OpId>,
172
+ Error,
173
+ RemoveNever<OperationParams<Paths, OpId>>
174
+ >,
175
+ 'mutationFn'
176
+ >,
177
+ ) => {
178
+ const endpoint = buildEndpoint(operationId);
179
+
180
+ return useMutation<
181
+ OperationResponse<Paths, OpId>,
182
+ Error,
183
+ RemoveNever<OperationParams<Paths, OpId>>
184
+ >({
185
+ mutationFn: async (variables) => {
186
+ const response = await fetcher(endpoint as any, variables as any);
187
+ return response as OperationResponse<Paths, OpId>;
188
+ },
189
+ ...options,
190
+ });
191
+ },
192
+ };
193
+ }