@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 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.
@@ -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
+ }