@tuyau/react-query 0.0.1-next.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.
- package/LICENSE.md +9 -0
- package/build/index.d.ts +314 -0
- package/build/index.js +249 -0
- package/package.json +56 -0
package/LICENSE.md
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# The MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/build/index.d.ts
ADDED
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
import { IsNever } from '@tuyau/utils/types';
|
|
2
|
+
import { SkipToken, DataTag, QueryFilters, UseMutationOptions, DefinedInitialDataOptions, UnusedSkipTokenOptions, UndefinedInitialDataOptions, DefinedInitialDataInfiniteOptions, UnusedSkipTokenInfiniteOptions, UndefinedInitialDataInfiniteOptions, QueryClient } from '@tanstack/react-query';
|
|
3
|
+
import { GeneratedRoutes, TuyauClient, QueryParameters, ApiDefinition } from '@tuyau/client';
|
|
4
|
+
import * as React from 'react';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Type definition for query options with overloads for different scenarios
|
|
8
|
+
*/
|
|
9
|
+
interface TuyauReactQueryOptions<EDef extends EndpointDef, TParams = Record<string, string | number>> {
|
|
10
|
+
<TData = UnionFromSuccessStatuses<EDef['response']>>(input: {
|
|
11
|
+
payload?: EDef['request'];
|
|
12
|
+
params?: TParams;
|
|
13
|
+
} | SkipToken, opts: DefinedTuyauQueryOptionsIn<UnionFromSuccessStatuses<EDef['response']>, TData, any>): DefinedTuyauQueryOptionsOut<UnionFromSuccessStatuses<EDef['response']>, TData, any>;
|
|
14
|
+
<TData = UnionFromSuccessStatuses<EDef['response']>>(input: {
|
|
15
|
+
payload?: EDef['request'];
|
|
16
|
+
params?: TParams;
|
|
17
|
+
}, opts?: UnusedSkipTokenTuyauQueryOptionsIn<UnionFromSuccessStatuses<EDef['response']>, TData, any>): UnusedSkipTokenTuyauQueryOptionsOut<UnionFromSuccessStatuses<EDef['response']>, TData, any>;
|
|
18
|
+
<TData = UnionFromSuccessStatuses<EDef['response']>>(input?: {
|
|
19
|
+
payload?: EDef['request'];
|
|
20
|
+
params?: TParams;
|
|
21
|
+
} | SkipToken, opts?: UndefinedTuyauQueryOptionsIn<UnionFromSuccessStatuses<EDef['response']>, TData, any>): UndefinedTuyauQueryOptionsOut<UnionFromSuccessStatuses<EDef['response']>, TData, any>;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Interface for query function decorators (queryOptions, queryKey, queryFilter)
|
|
25
|
+
*/
|
|
26
|
+
interface DecorateQueryFn<EDef extends EndpointDef, TParams = Record<string, string | number>> extends TypeHelper<EDef> {
|
|
27
|
+
queryOptions: TuyauReactQueryOptions<EDef, TParams>;
|
|
28
|
+
queryKey: (input?: {
|
|
29
|
+
payload?: Partial<EDef['request']>;
|
|
30
|
+
params?: TParams;
|
|
31
|
+
}) => DataTag<TuyauQueryKey, UnionFromSuccessStatuses<EDef['response']>, any>;
|
|
32
|
+
queryFilter: (input?: {
|
|
33
|
+
payload?: Partial<EDef['request']>;
|
|
34
|
+
params?: TParams;
|
|
35
|
+
}, filters?: QueryFilters<DataTag<TuyauQueryKey, UnionFromSuccessStatuses<EDef['response']>, any>>) => WithRequired<QueryFilters<DataTag<TuyauQueryKey, UnionFromSuccessStatuses<EDef['response']>, any>>, 'queryKey'>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Interface for mutation function decorators
|
|
40
|
+
*/
|
|
41
|
+
interface DecorateMutationFn<EDef extends EndpointDef, TParams = Record<string, string | number>> extends TypeHelper<EDef> {
|
|
42
|
+
mutationOptions: TuyauReactMutationOptions<EDef, TParams>;
|
|
43
|
+
mutationKey: () => TuyauMutationKey;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Output type for mutation options
|
|
47
|
+
*/
|
|
48
|
+
interface TuyauMutationOptionsOut<TInput, TError, TOutput, TContext, TParams = Record<string, string | number>> extends UseMutationOptions<TOutput, TError, {
|
|
49
|
+
payload: TInput;
|
|
50
|
+
params?: TParams;
|
|
51
|
+
}, TContext> {
|
|
52
|
+
mutationKey: TuyauMutationKey;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Type definition for mutation options with params and payload support
|
|
56
|
+
*/
|
|
57
|
+
interface TuyauReactMutationOptions<TDef extends EndpointDef, TParams = Record<string, string | number>> {
|
|
58
|
+
<TContext = unknown>(opts?: TuyauMutationOptionsIn<TDef['request'], any, UnionFromSuccessStatuses<TDef['response']>, TContext, TParams>): TuyauMutationOptionsOut<TDef['request'], any, UnionFromSuccessStatuses<TDef['response']>, TContext, TParams>;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Extract union type from successful HTTP status codes (200-299)
|
|
63
|
+
*/
|
|
64
|
+
type UnionFromSuccessStatuses<Res extends Record<number, unknown>> = Res[Extract<keyof Res, 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 226>];
|
|
65
|
+
/**
|
|
66
|
+
* Base endpoint definition structure
|
|
67
|
+
*/
|
|
68
|
+
type EndpointDef = {
|
|
69
|
+
request: Record<string, unknown>;
|
|
70
|
+
response: Record<number, unknown>;
|
|
71
|
+
};
|
|
72
|
+
/**
|
|
73
|
+
* Tuyau-specific query key structure
|
|
74
|
+
*/
|
|
75
|
+
type TuyauQueryKey = [
|
|
76
|
+
readonly string[],
|
|
77
|
+
{
|
|
78
|
+
payload?: unknown;
|
|
79
|
+
params?: unknown;
|
|
80
|
+
type?: 'infinite' | 'query';
|
|
81
|
+
}?
|
|
82
|
+
];
|
|
83
|
+
/**
|
|
84
|
+
* Tuyau-specific mutation key structure
|
|
85
|
+
*/
|
|
86
|
+
type TuyauMutationKey = [readonly string[]];
|
|
87
|
+
/**
|
|
88
|
+
* Omits the key without removing a potential union
|
|
89
|
+
*/
|
|
90
|
+
type DistributiveOmit<TObj, TKey extends keyof any> = TObj extends any ? Omit<TObj, TKey> : never;
|
|
91
|
+
/**
|
|
92
|
+
* Make certain keys required in a type
|
|
93
|
+
*/
|
|
94
|
+
type WithRequired<T, K extends keyof T> = T & Required<Pick<T, K>>;
|
|
95
|
+
/**
|
|
96
|
+
* Query options with defined initial data
|
|
97
|
+
*/
|
|
98
|
+
interface DefinedTuyauQueryOptionsIn<TQueryFnData, TData, TError> extends DistributiveOmit<DefinedInitialDataOptions<TQueryFnData, TError, TData, TuyauQueryKey>, 'queryKey' | 'queryFn' | 'queryHashFn' | 'queryHash'> {
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Query options with undefined initial data
|
|
102
|
+
*/
|
|
103
|
+
interface UndefinedTuyauQueryOptionsIn<TQueryFnData, TData, TError> extends DistributiveOmit<UndefinedInitialDataOptions<TQueryFnData, TError, TData, TuyauQueryKey>, 'queryKey' | 'queryFn' | 'queryHashFn' | 'queryHash'> {
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Query options with unused skip token
|
|
107
|
+
*/
|
|
108
|
+
interface UnusedSkipTokenTuyauQueryOptionsIn<TQueryFnData, TData, TError> extends DistributiveOmit<UnusedSkipTokenOptions<TQueryFnData, TError, TData, TuyauQueryKey>, 'queryKey' | 'queryFn' | 'queryHashFn' | 'queryHash'> {
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Output type for query options with defined initial data
|
|
112
|
+
*/
|
|
113
|
+
interface DefinedTuyauQueryOptionsOut<TQueryFnData, TData, TError> extends DefinedInitialDataOptions<TQueryFnData, TError, TData, TuyauQueryKey> {
|
|
114
|
+
queryKey: DataTag<TuyauQueryKey, TData, TError>;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Output type for query options with undefined initial data
|
|
118
|
+
*/
|
|
119
|
+
interface UndefinedTuyauQueryOptionsOut<TQueryFnData, TData, TError> extends UndefinedInitialDataOptions<TQueryFnData, TError, TData, TuyauQueryKey> {
|
|
120
|
+
queryKey: DataTag<TuyauQueryKey, TData, TError>;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Output type for query options with unused skip token
|
|
124
|
+
*/
|
|
125
|
+
interface UnusedSkipTokenTuyauQueryOptionsOut<TQueryFnData, TData, TError> extends UnusedSkipTokenOptions<TQueryFnData, TError, TData, TuyauQueryKey> {
|
|
126
|
+
queryKey: DataTag<TuyauQueryKey, TData, TError>;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Input type for mutation options with params and payload support
|
|
130
|
+
*/
|
|
131
|
+
interface TuyauMutationOptionsIn<TInput, TError, TOutput, TContext, TParams = Record<string, string | number>> extends DistributiveOmit<UseMutationOptions<TOutput, TError, {
|
|
132
|
+
payload: TInput;
|
|
133
|
+
params?: TParams;
|
|
134
|
+
}, TContext>, 'mutationKey' | 'mutationFn'> {
|
|
135
|
+
/**
|
|
136
|
+
* Route parameters to be passed to the mutation
|
|
137
|
+
*/
|
|
138
|
+
params?: TParams;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Interface for router keyable decorators (pathKey, pathFilter)
|
|
142
|
+
*/
|
|
143
|
+
interface DecorateRouterKeyable {
|
|
144
|
+
pathKey: () => TuyauQueryKey;
|
|
145
|
+
pathFilter: (filters?: QueryFilters<TuyauQueryKey>) => WithRequired<QueryFilters<TuyauQueryKey>, 'queryKey'>;
|
|
146
|
+
}
|
|
147
|
+
interface TypeHelper<EDef extends EndpointDef> {
|
|
148
|
+
/**
|
|
149
|
+
* @internal
|
|
150
|
+
*/
|
|
151
|
+
'~types': {
|
|
152
|
+
request: EDef['request'];
|
|
153
|
+
response: EDef['response'];
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Infer request type from an endpoint
|
|
158
|
+
*/
|
|
159
|
+
type InferRequestType<Endpoint extends DecorateQueryFn<any> | DecorateMutationFn<any>> = Endpoint['~types']['request'];
|
|
160
|
+
/**
|
|
161
|
+
* Infer response type from an endpoint
|
|
162
|
+
*/
|
|
163
|
+
type InferResponseType<Endpoint extends DecorateQueryFn<any> | DecorateMutationFn<any>> = Endpoint['~types']['response'];
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Reserved infinite query options that should not be overridden
|
|
167
|
+
*/
|
|
168
|
+
type InfiniteQueryReservedOptions = 'queryKey' | 'queryFn' | 'queryHashFn' | 'queryHash';
|
|
169
|
+
/**
|
|
170
|
+
* Tuyau infinite data structure
|
|
171
|
+
*/
|
|
172
|
+
type TuyauInfiniteData<TData> = {
|
|
173
|
+
pages: TData[];
|
|
174
|
+
pageParams: unknown[];
|
|
175
|
+
};
|
|
176
|
+
/**
|
|
177
|
+
* Infinite query options with undefined initial data
|
|
178
|
+
*/
|
|
179
|
+
interface UndefinedTuyauInfiniteQueryOptionsIn<TQueryFnData, TData, TError> extends DistributiveOmit<UndefinedInitialDataInfiniteOptions<TQueryFnData, TError, TuyauInfiniteData<TData>, TuyauQueryKey, unknown>, InfiniteQueryReservedOptions> {
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Infinite query options with defined initial data
|
|
183
|
+
*/
|
|
184
|
+
interface DefinedTuyauInfiniteQueryOptionsIn<TQueryFnData, TData, TError> extends DistributiveOmit<DefinedInitialDataInfiniteOptions<TQueryFnData, TError, TuyauInfiniteData<TData>, TuyauQueryKey, unknown>, InfiniteQueryReservedOptions> {
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Infinite query options with unused skip token
|
|
188
|
+
*/
|
|
189
|
+
interface UnusedSkipTokenTuyauInfiniteQueryOptionsIn<TQueryFnData, TData, TError> extends DistributiveOmit<UnusedSkipTokenInfiniteOptions<TQueryFnData, TError, TuyauInfiniteData<TData>, TuyauQueryKey, unknown>, InfiniteQueryReservedOptions> {
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Output type for infinite query options with undefined initial data
|
|
193
|
+
*/
|
|
194
|
+
interface UndefinedTuyauInfiniteQueryOptionsOut<TQueryFnData, TData, TError> extends UndefinedInitialDataInfiniteOptions<TQueryFnData, TError, TuyauInfiniteData<TData>, TuyauQueryKey, unknown> {
|
|
195
|
+
queryKey: DataTag<TuyauQueryKey, TuyauInfiniteData<TData>, TError>;
|
|
196
|
+
tuyau: {
|
|
197
|
+
path: string[];
|
|
198
|
+
type: 'infinite';
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Output type for infinite query options with defined initial data
|
|
203
|
+
*/
|
|
204
|
+
interface DefinedTuyauInfiniteQueryOptionsOut<TQueryFnData, TData, TError> extends DefinedInitialDataInfiniteOptions<TQueryFnData, TError, TuyauInfiniteData<TData>, TuyauQueryKey, unknown> {
|
|
205
|
+
queryKey: DataTag<TuyauQueryKey, TuyauInfiniteData<TData>, TError>;
|
|
206
|
+
tuyau: {
|
|
207
|
+
path: string[];
|
|
208
|
+
type: 'infinite';
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Output type for infinite query options with unused skip token
|
|
213
|
+
*/
|
|
214
|
+
interface UnusedSkipTokenTuyauInfiniteQueryOptionsOut<TQueryFnData, TData, TError> extends UnusedSkipTokenInfiniteOptions<TQueryFnData, TError, TuyauInfiniteData<TData>, TuyauQueryKey, unknown> {
|
|
215
|
+
queryKey: DataTag<TuyauQueryKey, TuyauInfiniteData<TData>, TError>;
|
|
216
|
+
tuyau: {
|
|
217
|
+
path: string[];
|
|
218
|
+
type: 'infinite';
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Type definition for infinite query options with overloads for different scenarios
|
|
223
|
+
*/
|
|
224
|
+
interface TuyauReactInfiniteQueryOptions<EDef extends EndpointDef, TParams = Record<string, string | number>> {
|
|
225
|
+
<TData = UnionFromSuccessStatuses<EDef['response']>>(input: {
|
|
226
|
+
payload?: EDef['request'];
|
|
227
|
+
params?: TParams;
|
|
228
|
+
} | SkipToken, opts: DefinedTuyauInfiniteQueryOptionsIn<UnionFromSuccessStatuses<EDef['response']>, TData, any>): DefinedTuyauInfiniteQueryOptionsOut<UnionFromSuccessStatuses<EDef['response']>, TData, any>;
|
|
229
|
+
<TData = UnionFromSuccessStatuses<EDef['response']>>(input: {
|
|
230
|
+
payload?: EDef['request'];
|
|
231
|
+
params?: TParams;
|
|
232
|
+
}, opts: UnusedSkipTokenTuyauInfiniteQueryOptionsIn<UnionFromSuccessStatuses<EDef['response']>, TData, any>): UnusedSkipTokenTuyauInfiniteQueryOptionsOut<UnionFromSuccessStatuses<EDef['response']>, TData, any>;
|
|
233
|
+
<TData = UnionFromSuccessStatuses<EDef['response']>>(input?: {
|
|
234
|
+
payload?: EDef['request'];
|
|
235
|
+
params?: TParams;
|
|
236
|
+
} | SkipToken, opts?: UndefinedTuyauInfiniteQueryOptionsIn<UnionFromSuccessStatuses<EDef['response']>, TData, any>): UndefinedTuyauInfiniteQueryOptionsOut<UnionFromSuccessStatuses<EDef['response']>, TData, any>;
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Interface for infinite query function decorators
|
|
240
|
+
*/
|
|
241
|
+
interface DecorateInfiniteQueryFn<EDef extends EndpointDef, TParams = Record<string, string | number>> {
|
|
242
|
+
infiniteQueryOptions: TuyauReactInfiniteQueryOptions<EDef, TParams>;
|
|
243
|
+
infiniteQueryKey: (input?: {
|
|
244
|
+
payload?: Partial<EDef['request']>;
|
|
245
|
+
params?: TParams;
|
|
246
|
+
}) => DataTag<TuyauQueryKey, TuyauInfiniteData<UnionFromSuccessStatuses<EDef['response']>>, any>;
|
|
247
|
+
infiniteQueryFilter: (input?: {
|
|
248
|
+
payload?: Partial<EDef['request']>;
|
|
249
|
+
params?: TParams;
|
|
250
|
+
}, filters?: QueryFilters<DataTag<TuyauQueryKey, TuyauInfiniteData<UnionFromSuccessStatuses<EDef['response']>>, any>>) => WithRequired<QueryFilters<DataTag<TuyauQueryKey, TuyauInfiniteData<UnionFromSuccessStatuses<EDef['response']>>, any>>, 'queryKey'>;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Create the Tuyau React Query client
|
|
255
|
+
*/
|
|
256
|
+
declare function createTuyauReactQueryClient<D extends Record<string, any>, R extends GeneratedRoutes>(options: {
|
|
257
|
+
client: TuyauClient<D, R>;
|
|
258
|
+
queryClient: QueryClient | (() => QueryClient);
|
|
259
|
+
}): TuyauReactQuery<D> & DecorateRouterKeyable;
|
|
260
|
+
/**
|
|
261
|
+
* Main type for the Tuyau React Query client
|
|
262
|
+
* Maps route definitions to appropriate query or mutation decorators
|
|
263
|
+
*/
|
|
264
|
+
type TuyauReactQuery<in out Route extends Record<string, any>, NotProvidedParams = {}> = {
|
|
265
|
+
[K in keyof Route as K extends `:${string}` ? never : K]: Route[K] extends {
|
|
266
|
+
response: infer _Res extends Record<number, unknown>;
|
|
267
|
+
request: infer _Request;
|
|
268
|
+
} ? K extends '$get' | '$head' ? DecorateQueryFn<Route[K], NotProvidedParams> & DecorateInfiniteQueryFn<Route[K], NotProvidedParams> & DecorateRouterKeyable : // POST, PUT, PATCH, DELETE methods become mutations
|
|
269
|
+
DecorateMutationFn<Route[K], NotProvidedParams> & DecorateRouterKeyable : K extends '$url' ? (options?: {
|
|
270
|
+
query?: QueryParameters;
|
|
271
|
+
}) => string : CreateParams<Route[K], NotProvidedParams> & DecorateRouterKeyable;
|
|
272
|
+
};
|
|
273
|
+
/**
|
|
274
|
+
* Extract path parameters from route keys
|
|
275
|
+
*/
|
|
276
|
+
type ExtractPathParams<Route> = Extract<keyof Route, `:${string}`>;
|
|
277
|
+
/**
|
|
278
|
+
* Convert path parameter to object type
|
|
279
|
+
*/
|
|
280
|
+
type PathParamToObject<Path extends string> = Path extends `:${infer Param}` ? {
|
|
281
|
+
[K in Param]: string | number;
|
|
282
|
+
} : never;
|
|
283
|
+
/**
|
|
284
|
+
* Create the route parameter function signature
|
|
285
|
+
*/
|
|
286
|
+
type CreateParamFunction<Route extends Record<string, any>, Path extends string, NotProvidedParams> = (params: PathParamToObject<Path>) => TuyauReactQuery<Route[Path], NotProvidedParams> & CreateParams<Route[Path], NotProvidedParams & PathParamToObject<Path>>;
|
|
287
|
+
/**
|
|
288
|
+
* Create the parameter property mappings
|
|
289
|
+
*/
|
|
290
|
+
type CreateParamProperties<Route extends Record<string, any>, Path extends string, NotProvidedParams> = {
|
|
291
|
+
[K in keyof Route as K extends `:${string}` ? K : never]: TuyauReactQuery<Route[K], NotProvidedParams & PathParamToObject<Path>>;
|
|
292
|
+
};
|
|
293
|
+
/**
|
|
294
|
+
* Type for handling route parameters
|
|
295
|
+
*/
|
|
296
|
+
type CreateParams<Route extends Record<string, any>, NotProvidedParams = {}> = ExtractPathParams<Route> extends infer Path extends string ? IsNever<Path> extends true ? TuyauReactQuery<Route, NotProvidedParams> & DecorateRouterKeyable : CreateParamFunction<Route, Path, NotProvidedParams> & TuyauReactQuery<Route, NotProvidedParams> & CreateParamProperties<Route, Path, NotProvidedParams> & DecorateRouterKeyable : never;
|
|
297
|
+
|
|
298
|
+
type InferTuyauClient<API extends ApiDefinition> = TuyauClient<API['definition'], API['routes'] extends GeneratedRoutes ? API['routes'] : any>;
|
|
299
|
+
type InferTuyauReactQuery<API extends ApiDefinition> = TuyauReactQuery<API['definition']>;
|
|
300
|
+
interface CreateTuyauContextResult<API extends ApiDefinition> {
|
|
301
|
+
TuyauProvider: React.FC<{
|
|
302
|
+
children: React.ReactNode;
|
|
303
|
+
queryClient: QueryClient;
|
|
304
|
+
client: InferTuyauClient<API>;
|
|
305
|
+
}>;
|
|
306
|
+
useTuyau: () => InferTuyauReactQuery<API>;
|
|
307
|
+
useTuyauClient: () => InferTuyauClient<API>;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Create a set of type-safe provider-consumers for Tuyau with React Query
|
|
311
|
+
*/
|
|
312
|
+
declare function createTuyauContext<API extends ApiDefinition>(): CreateTuyauContextResult<API>;
|
|
313
|
+
|
|
314
|
+
export { type CreateTuyauContextResult, type InferRequestType, type InferResponseType, type TuyauReactInfiniteQueryOptions, type TuyauReactMutationOptions, type TuyauReactQuery, type TuyauReactQueryOptions, createTuyauContext, createTuyauReactQueryClient };
|
package/build/index.js
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
// src/main.ts
|
|
2
|
+
import {
|
|
3
|
+
createTuyauRecursiveProxy
|
|
4
|
+
} from "@tuyau/client";
|
|
5
|
+
|
|
6
|
+
// src/utils.ts
|
|
7
|
+
function unwrapLazyArg(arg) {
|
|
8
|
+
return typeof arg === "function" ? arg() : arg;
|
|
9
|
+
}
|
|
10
|
+
function isObject(value) {
|
|
11
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
12
|
+
}
|
|
13
|
+
function buildRequestPath(path, params) {
|
|
14
|
+
if (!params) return path.map(String);
|
|
15
|
+
const result = [];
|
|
16
|
+
for (const segment of path) {
|
|
17
|
+
const segmentStr = String(segment);
|
|
18
|
+
if (segmentStr.includes("/")) {
|
|
19
|
+
const parts = segmentStr.split("/");
|
|
20
|
+
for (const part of parts) {
|
|
21
|
+
if (part.startsWith(":")) {
|
|
22
|
+
const paramName = part.slice(1);
|
|
23
|
+
result.push(params[paramName]?.toString() || part);
|
|
24
|
+
} else {
|
|
25
|
+
result.push(part);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
} else if (segmentStr.startsWith(":")) {
|
|
29
|
+
const paramName = segmentStr.slice(1);
|
|
30
|
+
result.push(params[paramName]?.toString() || segmentStr);
|
|
31
|
+
} else {
|
|
32
|
+
result.push(segmentStr);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// src/query.ts
|
|
39
|
+
import {
|
|
40
|
+
queryOptions,
|
|
41
|
+
skipToken
|
|
42
|
+
} from "@tanstack/react-query";
|
|
43
|
+
function getQueryKeyInternal(path, input, type) {
|
|
44
|
+
let params;
|
|
45
|
+
let payload;
|
|
46
|
+
if (isObject(input)) {
|
|
47
|
+
if ("params" in input && input.params && typeof input.params === "object") {
|
|
48
|
+
params = input.params;
|
|
49
|
+
}
|
|
50
|
+
if ("payload" in input) {
|
|
51
|
+
payload = input.payload;
|
|
52
|
+
} else if (!("params" in input)) {
|
|
53
|
+
payload = input;
|
|
54
|
+
}
|
|
55
|
+
} else {
|
|
56
|
+
payload = input;
|
|
57
|
+
}
|
|
58
|
+
const splitPath = path.flatMap((part) => part.toString().split("/"));
|
|
59
|
+
if (!input && (!type || type === "any")) {
|
|
60
|
+
return splitPath.length ? [splitPath] : [];
|
|
61
|
+
}
|
|
62
|
+
return [
|
|
63
|
+
splitPath,
|
|
64
|
+
{
|
|
65
|
+
...typeof payload !== "undefined" && payload !== skipToken && { payload },
|
|
66
|
+
...typeof params !== "undefined" && { params },
|
|
67
|
+
...type && type !== "any" && { type }
|
|
68
|
+
}
|
|
69
|
+
];
|
|
70
|
+
}
|
|
71
|
+
function tuyauQueryOptions(options) {
|
|
72
|
+
const { input, opts, queryKey, path, client } = options;
|
|
73
|
+
const inputIsSkipToken = input === skipToken;
|
|
74
|
+
const queryFn = inputIsSkipToken ? skipToken : async () => {
|
|
75
|
+
const { payload, requestPath } = extractInputAndPath(input, path);
|
|
76
|
+
return await client.$fetch({ paths: requestPath, input: payload });
|
|
77
|
+
};
|
|
78
|
+
return Object.assign(queryOptions({ ...opts, queryKey, queryFn }), {
|
|
79
|
+
tuyau: { path, type: "query" }
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
function extractInputAndPath(input, path) {
|
|
83
|
+
if (typeof input !== "object" || input === null || !("payload" in input) && !("params" in input)) {
|
|
84
|
+
return { payload: input, requestPath: path };
|
|
85
|
+
}
|
|
86
|
+
const { payload, params } = input;
|
|
87
|
+
const requestPath = buildRequestPath(path, params);
|
|
88
|
+
return { payload, requestPath };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// src/infinite_query.ts
|
|
92
|
+
import {
|
|
93
|
+
infiniteQueryOptions,
|
|
94
|
+
skipToken as skipToken2
|
|
95
|
+
} from "@tanstack/react-query";
|
|
96
|
+
function extractInfiniteInputAndPath(input, path) {
|
|
97
|
+
if (typeof input !== "object" || input === null || !("payload" in input) && !("params" in input)) {
|
|
98
|
+
const payload2 = typeof input === "object" && input !== null ? input : {};
|
|
99
|
+
return { payload: payload2, requestPath: path };
|
|
100
|
+
}
|
|
101
|
+
const { payload, params } = input;
|
|
102
|
+
const requestPath = buildRequestPath(path, params);
|
|
103
|
+
return { payload, requestPath };
|
|
104
|
+
}
|
|
105
|
+
function tuyauInfiniteQueryOptions(options) {
|
|
106
|
+
const { input, opts, queryKey, path, client } = options;
|
|
107
|
+
const inputIsSkipToken = input === skipToken2;
|
|
108
|
+
const queryFn = inputIsSkipToken ? skipToken2 : async () => {
|
|
109
|
+
const { payload, requestPath } = extractInfiniteInputAndPath(input, path);
|
|
110
|
+
return await client.$fetch({ paths: requestPath, input: payload });
|
|
111
|
+
};
|
|
112
|
+
return Object.assign(
|
|
113
|
+
infiniteQueryOptions({
|
|
114
|
+
...opts,
|
|
115
|
+
queryKey,
|
|
116
|
+
queryFn,
|
|
117
|
+
initialPageParam: opts?.initialPageParam ?? null
|
|
118
|
+
}),
|
|
119
|
+
{ tuyau: { path, type: "infinite" } }
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// src/mutation.ts
|
|
124
|
+
function getMutationKeyInternal(path) {
|
|
125
|
+
const splitPath = path.flatMap((part) => part.toString().split("."));
|
|
126
|
+
return splitPath.length ? [splitPath] : [];
|
|
127
|
+
}
|
|
128
|
+
function createTuyauOptionsResult(opts) {
|
|
129
|
+
return {
|
|
130
|
+
path: opts.path,
|
|
131
|
+
type: "mutation"
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
function tuyauMutationOptions(args) {
|
|
135
|
+
const { opts, path, client, overrides } = args;
|
|
136
|
+
const queryClient = unwrapLazyArg(args.queryClient);
|
|
137
|
+
const mutationKey = getMutationKeyInternal(path);
|
|
138
|
+
const defaultOpts = queryClient.defaultMutationOptions(
|
|
139
|
+
queryClient.getMutationDefaults(mutationKey)
|
|
140
|
+
);
|
|
141
|
+
const mutationSuccessOverride = overrides?.onSuccess ?? ((options) => options.originalFn());
|
|
142
|
+
const mutationFn = async (input) => {
|
|
143
|
+
const requestPath = buildRequestPath(path, input.params);
|
|
144
|
+
return await client.$fetch({ paths: requestPath, input: input.payload });
|
|
145
|
+
};
|
|
146
|
+
return {
|
|
147
|
+
...opts,
|
|
148
|
+
mutationKey,
|
|
149
|
+
mutationFn,
|
|
150
|
+
onSuccess(data, variables, context) {
|
|
151
|
+
const originalFn = () => {
|
|
152
|
+
if (opts?.onSuccess) return opts.onSuccess(data, variables, context);
|
|
153
|
+
if (defaultOpts?.onSuccess) return defaultOpts.onSuccess(data, variables, context);
|
|
154
|
+
};
|
|
155
|
+
return mutationSuccessOverride({
|
|
156
|
+
originalFn,
|
|
157
|
+
queryClient,
|
|
158
|
+
meta: opts?.meta ?? defaultOpts?.meta ?? {}
|
|
159
|
+
});
|
|
160
|
+
},
|
|
161
|
+
tuyau: createTuyauOptionsResult({ path })
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// src/main.ts
|
|
166
|
+
function createTuyauReactQueryClient(options) {
|
|
167
|
+
return createTuyauRecursiveProxy(({ args, executeIfRouteParamCall, paths, patternPaths }) => {
|
|
168
|
+
const fnName = paths.at(-1);
|
|
169
|
+
const path = paths.slice(0, -1);
|
|
170
|
+
const patternPath = patternPaths ? patternPaths.slice(0, -1) : path;
|
|
171
|
+
const [arg1, arg2] = args;
|
|
172
|
+
if (fnName === "queryOptions") {
|
|
173
|
+
return tuyauQueryOptions({
|
|
174
|
+
input: arg1,
|
|
175
|
+
opts: arg2 || {},
|
|
176
|
+
queryKey: getQueryKeyInternal(patternPath, arg1, "query"),
|
|
177
|
+
queryClient: unwrapLazyArg(options.queryClient),
|
|
178
|
+
client: options.client,
|
|
179
|
+
path
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
if (fnName === "infiniteQueryOptions") {
|
|
183
|
+
return tuyauInfiniteQueryOptions({
|
|
184
|
+
input: arg1,
|
|
185
|
+
opts: arg2 || {},
|
|
186
|
+
queryKey: getQueryKeyInternal(patternPath, arg1, "infinite"),
|
|
187
|
+
queryClient: unwrapLazyArg(options.queryClient),
|
|
188
|
+
client: options.client,
|
|
189
|
+
path
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
if (fnName === "queryKey") return getQueryKeyInternal(patternPath, arg1, "query");
|
|
193
|
+
if (fnName === "infiniteQueryKey") return getQueryKeyInternal(patternPath, arg1, "infinite");
|
|
194
|
+
if (fnName === "pathKey") return getQueryKeyInternal(patternPath);
|
|
195
|
+
if (fnName === "queryFilter") {
|
|
196
|
+
return { ...arg2, queryKey: getQueryKeyInternal(patternPath, arg1, "query") };
|
|
197
|
+
}
|
|
198
|
+
if (fnName === "infiniteQueryFilter") {
|
|
199
|
+
return { ...arg2, queryKey: getQueryKeyInternal(patternPath, arg1, "infinite") };
|
|
200
|
+
}
|
|
201
|
+
if (fnName === "pathFilter") {
|
|
202
|
+
return { ...arg1, queryKey: getQueryKeyInternal(patternPath) };
|
|
203
|
+
}
|
|
204
|
+
if (fnName === "mutationOptions") {
|
|
205
|
+
return tuyauMutationOptions({
|
|
206
|
+
path,
|
|
207
|
+
opts: arg1,
|
|
208
|
+
queryClient: unwrapLazyArg(options.queryClient),
|
|
209
|
+
client: options.client
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
if (fnName === "mutationKey") return getMutationKeyInternal(path);
|
|
213
|
+
const newProxy = executeIfRouteParamCall({ fnName, body: args[0] });
|
|
214
|
+
if (newProxy) return newProxy;
|
|
215
|
+
throw new Error(`Method ${fnName} not found on Tuyau client`);
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// src/context.tsx
|
|
220
|
+
import * as React from "react";
|
|
221
|
+
function createTuyauContext() {
|
|
222
|
+
const TuyauClientContext = React.createContext(null);
|
|
223
|
+
const TuyauContext = React.createContext(null);
|
|
224
|
+
function TuyauProvider(props) {
|
|
225
|
+
const value = React.useMemo(
|
|
226
|
+
() => createTuyauReactQueryClient({
|
|
227
|
+
client: props.client,
|
|
228
|
+
queryClient: props.queryClient
|
|
229
|
+
}),
|
|
230
|
+
[props.client, props.queryClient]
|
|
231
|
+
);
|
|
232
|
+
return /* @__PURE__ */ React.createElement(TuyauClientContext.Provider, { value: props.client }, /* @__PURE__ */ React.createElement(TuyauContext.Provider, { value }, props.children));
|
|
233
|
+
}
|
|
234
|
+
function useTuyau() {
|
|
235
|
+
const utils = React.useContext(TuyauContext);
|
|
236
|
+
if (!utils) throw new Error("useTuyau() can only be used inside of a <TuyauProvider>");
|
|
237
|
+
return utils;
|
|
238
|
+
}
|
|
239
|
+
function useTuyauClient() {
|
|
240
|
+
const client = React.useContext(TuyauClientContext);
|
|
241
|
+
if (!client) throw new Error("useTuyauClient() can only be used inside of a <TuyauProvider>");
|
|
242
|
+
return client;
|
|
243
|
+
}
|
|
244
|
+
return { TuyauProvider, useTuyau, useTuyauClient };
|
|
245
|
+
}
|
|
246
|
+
export {
|
|
247
|
+
createTuyauContext,
|
|
248
|
+
createTuyauReactQueryClient
|
|
249
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tuyau/react-query",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"version": "0.0.1-next.0",
|
|
5
|
+
"description": "React Query plugin for Tuyau",
|
|
6
|
+
"author": "Julien Ripouteau <julien@ripouteau.com>",
|
|
7
|
+
"license": "ISC",
|
|
8
|
+
"keywords": [],
|
|
9
|
+
"exports": {
|
|
10
|
+
".": "./build/index.js"
|
|
11
|
+
},
|
|
12
|
+
"main": "build/index.js",
|
|
13
|
+
"files": [
|
|
14
|
+
"build"
|
|
15
|
+
],
|
|
16
|
+
"peerDependencies": {
|
|
17
|
+
"@tanstack/react-query": "^5.74.7",
|
|
18
|
+
"@tuyau/client": "^0.2.11-next.0"
|
|
19
|
+
},
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@tuyau/utils": "0.0.9"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@adonisjs/core": "^6.18.0",
|
|
25
|
+
"@happy-dom/global-registrator": "^17.4.7",
|
|
26
|
+
"@tanstack/react-query": "^5.76.1",
|
|
27
|
+
"@testing-library/react": "^16.3.0",
|
|
28
|
+
"@types/react": "^19.1.4",
|
|
29
|
+
"@vinejs/vine": "^3.0.1",
|
|
30
|
+
"nock": "^14.0.4",
|
|
31
|
+
"react": "^19.1.0",
|
|
32
|
+
"@tuyau/client": "0.2.11-next.0"
|
|
33
|
+
},
|
|
34
|
+
"tsup": {
|
|
35
|
+
"entry": [
|
|
36
|
+
"./src/index.ts"
|
|
37
|
+
],
|
|
38
|
+
"outDir": "./build",
|
|
39
|
+
"clean": true,
|
|
40
|
+
"format": "esm",
|
|
41
|
+
"dts": true,
|
|
42
|
+
"target": "esnext"
|
|
43
|
+
},
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public",
|
|
46
|
+
"tag": "latest"
|
|
47
|
+
},
|
|
48
|
+
"scripts": {
|
|
49
|
+
"lint": "eslint .",
|
|
50
|
+
"typecheck": "tsc --noEmit",
|
|
51
|
+
"build": "tsup-node",
|
|
52
|
+
"test": "c8 node --enable-source-maps --loader ts-node/esm bin/test.ts",
|
|
53
|
+
"quick:test": "node --enable-source-maps --loader ts-node/esm bin/test.ts",
|
|
54
|
+
"checks": "pnpm lint && pnpm typecheck"
|
|
55
|
+
}
|
|
56
|
+
}
|