Skip to content

Commit 5ff17ce

Browse files
authored
fix(tanstack): improve mutation mutateAsync typing (#368)
* fix(tanstack): improve mutation mutateAsync typing * simplify typing
1 parent 9372688 commit 5ff17ce

File tree

10 files changed

+719
-373
lines changed

10 files changed

+719
-373
lines changed

packages/clients/tanstack-query/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@
6767
},
6868
"devDependencies": {
6969
"@tanstack/react-query": "catalog:",
70-
"@tanstack/svelte-query": "5.90.2",
7170
"@tanstack/vue-query": "5.90.6",
71+
"@tanstack/svelte-query": "5.90.2",
7272
"@testing-library/dom": "^10.4.1",
7373
"@testing-library/react": "^16.3.0",
7474
"@types/react": "catalog:",

packages/clients/tanstack-query/src/react.ts

Lines changed: 153 additions & 146 deletions
Large diffs are not rendered by default.

packages/clients/tanstack-query/src/svelte.ts

Lines changed: 121 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import type {
3232
GroupByArgs,
3333
GroupByResult,
3434
ModelResult,
35+
SelectIncludeOmit,
3536
SelectSubset,
3637
Subset,
3738
UpdateArgs,
@@ -53,6 +54,7 @@ import {
5354
type ExtraMutationOptions,
5455
type ExtraQueryOptions,
5556
} from './utils/common';
57+
import type { TrimDelegateModelOperations } from './utils/types';
5658

5759
export type { FetchFn } from './utils/common';
5860

@@ -107,91 +109,111 @@ export type ModelMutationOptions<T, TArgs> = Omit<CreateMutationOptions<T, Defau
107109

108110
export type ModelMutationResult<T, TArgs> = CreateMutationResult<T, DefaultError, TArgs>;
109111

110-
export type SchemaHooks<Schema extends SchemaDef> = {
112+
export type ModelMutationModelResult<
113+
Schema extends SchemaDef,
114+
Model extends GetModels<Schema>,
115+
TArgs extends SelectIncludeOmit<Schema, Model, boolean>,
116+
Array extends boolean = false,
117+
> = Readable<
118+
Omit<UnwrapStore<ModelMutationResult<ModelResult<Schema, Model, TArgs>, TArgs>>, 'mutateAsync'> & {
119+
mutateAsync<T extends TArgs>(
120+
args: T,
121+
options?: ModelMutationOptions<ModelResult<Schema, Model, T>, T>,
122+
): Promise<Array extends true ? ModelResult<Schema, Model, T>[] : ModelResult<Schema, Model, T>>;
123+
}
124+
>;
125+
126+
export type ClientHooks<Schema extends SchemaDef> = {
111127
[Model in GetModels<Schema> as `${Uncapitalize<Model>}`]: ModelQueryHooks<Schema, Model>;
112128
};
113129

114-
export type ModelQueryHooks<Schema extends SchemaDef, Model extends GetModels<Schema>> = {
115-
useFindUnique<T extends FindUniqueArgs<Schema, Model>>(
116-
args: SelectSubset<T, FindUniqueArgs<Schema, Model>>,
117-
options?: ModelQueryOptions<ModelResult<Schema, Model, T> | null>,
118-
): ModelQueryResult<ModelResult<Schema, Model, T> | null>;
119-
120-
useFindFirst<T extends FindArgs<Schema, Model, false>>(
121-
args?: SelectSubset<T, FindArgs<Schema, Model, false>>,
122-
options?: ModelQueryOptions<ModelResult<Schema, Model, T> | null>,
123-
): ModelQueryResult<ModelResult<Schema, Model, T> | null>;
124-
125-
useFindMany<T extends FindArgs<Schema, Model, true>>(
126-
args?: SelectSubset<T, FindArgs<Schema, Model, true>>,
127-
options?: ModelQueryOptions<ModelResult<Schema, Model, T>[]>,
128-
): ModelQueryResult<ModelResult<Schema, Model, T>[]>;
129-
130-
useInfiniteFindMany<T extends FindArgs<Schema, Model, true>>(
131-
args?: SelectSubset<T, FindArgs<Schema, Model, true>>,
132-
options?: ModelInfiniteQueryOptions<ModelResult<Schema, Model, T>[]>,
133-
): ModelInfiniteQueryResult<InfiniteData<ModelResult<Schema, Model, T>[]>>;
134-
135-
useCreate<T extends CreateArgs<Schema, Model>>(
136-
options?: ModelMutationOptions<ModelResult<Schema, Model, T>, T>,
137-
): ModelMutationResult<ModelResult<Schema, Model, T>, T>;
138-
139-
useCreateMany<T extends CreateManyArgs<Schema, Model>>(
140-
options?: ModelMutationOptions<BatchResult, T>,
141-
): ModelMutationResult<BatchResult, T>;
142-
143-
useCreateManyAndReturn<T extends CreateManyAndReturnArgs<Schema, Model>>(
144-
options?: ModelMutationOptions<ModelResult<Schema, Model, T>[], T>,
145-
): ModelMutationResult<ModelResult<Schema, Model, T>[], T>;
146-
147-
useUpdate<T extends UpdateArgs<Schema, Model>>(
148-
options?: ModelMutationOptions<ModelResult<Schema, Model, T>, T>,
149-
): ModelMutationResult<ModelResult<Schema, Model, T>, T>;
150-
151-
useUpdateMany<T extends UpdateManyArgs<Schema, Model>>(
152-
options?: ModelMutationOptions<BatchResult, T>,
153-
): ModelMutationResult<BatchResult, T>;
154-
155-
useUpdateManyAndReturn<T extends UpdateManyAndReturnArgs<Schema, Model>>(
156-
options?: ModelMutationOptions<ModelResult<Schema, Model, T>[], T>,
157-
): ModelMutationResult<ModelResult<Schema, Model, T>[], T>;
158-
159-
useUpsert<T extends UpsertArgs<Schema, Model>>(
160-
options?: ModelMutationOptions<ModelResult<Schema, Model, T>, T>,
161-
): ModelMutationResult<ModelResult<Schema, Model, T>, T>;
162-
163-
useDelete<T extends DeleteArgs<Schema, Model>>(
164-
options?: ModelMutationOptions<ModelResult<Schema, Model, T>, T>,
165-
): ModelMutationResult<ModelResult<Schema, Model, T>, T>;
166-
167-
useDeleteMany<T extends DeleteManyArgs<Schema, Model>>(
168-
options?: ModelMutationOptions<BatchResult, T>,
169-
): ModelMutationResult<BatchResult, T>;
170-
171-
useCount<T extends CountArgs<Schema, Model>>(
172-
args?: Subset<T, CountArgs<Schema, Model>>,
173-
options?: ModelQueryOptions<CountResult<Schema, Model, T>>,
174-
): ModelQueryResult<CountResult<Schema, Model, T>>;
175-
176-
useAggregate<T extends AggregateArgs<Schema, Model>>(
177-
args: Subset<T, AggregateArgs<Schema, Model>>,
178-
options?: ModelQueryOptions<AggregateResult<Schema, Model, T>>,
179-
): ModelQueryResult<AggregateResult<Schema, Model, T>>;
180-
181-
useGroupBy<T extends GroupByArgs<Schema, Model>>(
182-
args: Subset<T, GroupByArgs<Schema, Model>>,
183-
options?: ModelQueryOptions<GroupByResult<Schema, Model, T>>,
184-
): ModelQueryResult<GroupByResult<Schema, Model, T>>;
185-
};
130+
// Note that we can potentially use TypeScript's mapped type to directly map from ORM contract, but that seems
131+
// to significantly slow down tsc performance ...
132+
export type ModelQueryHooks<Schema extends SchemaDef, Model extends GetModels<Schema>> = TrimDelegateModelOperations<
133+
Schema,
134+
Model,
135+
{
136+
useFindUnique<T extends FindUniqueArgs<Schema, Model>>(
137+
args: SelectSubset<T, FindUniqueArgs<Schema, Model>>,
138+
options?: ModelQueryOptions<ModelResult<Schema, Model, T> | null>,
139+
): ModelQueryResult<ModelResult<Schema, Model, T> | null>;
140+
141+
useFindFirst<T extends FindArgs<Schema, Model, false>>(
142+
args?: SelectSubset<T, FindArgs<Schema, Model, false>>,
143+
options?: ModelQueryOptions<ModelResult<Schema, Model, T> | null>,
144+
): ModelQueryResult<ModelResult<Schema, Model, T> | null>;
145+
146+
useFindMany<T extends FindArgs<Schema, Model, true>>(
147+
args?: SelectSubset<T, FindArgs<Schema, Model, true>>,
148+
options?: ModelQueryOptions<ModelResult<Schema, Model, T>[]>,
149+
): ModelQueryResult<ModelResult<Schema, Model, T>[]>;
150+
151+
useInfiniteFindMany<T extends FindArgs<Schema, Model, true>>(
152+
args?: SelectSubset<T, FindArgs<Schema, Model, true>>,
153+
options?: ModelInfiniteQueryOptions<ModelResult<Schema, Model, T>[]>,
154+
): ModelInfiniteQueryResult<InfiniteData<ModelResult<Schema, Model, T>[]>>;
155+
156+
useCreate<T extends CreateArgs<Schema, Model>>(
157+
options?: ModelMutationOptions<ModelResult<Schema, Model, T>, T>,
158+
): ModelMutationModelResult<Schema, Model, T>;
159+
160+
useCreateMany<T extends CreateManyArgs<Schema, Model>>(
161+
options?: ModelMutationOptions<BatchResult, T>,
162+
): ModelMutationResult<BatchResult, T>;
163+
164+
useCreateManyAndReturn<T extends CreateManyAndReturnArgs<Schema, Model>>(
165+
options?: ModelMutationOptions<ModelResult<Schema, Model, T>[], T>,
166+
): ModelMutationModelResult<Schema, Model, T, true>;
167+
168+
useUpdate<T extends UpdateArgs<Schema, Model>>(
169+
options?: ModelMutationOptions<ModelResult<Schema, Model, T>, T>,
170+
): ModelMutationModelResult<Schema, Model, T>;
171+
172+
useUpdateMany<T extends UpdateManyArgs<Schema, Model>>(
173+
options?: ModelMutationOptions<BatchResult, T>,
174+
): ModelMutationResult<BatchResult, T>;
175+
176+
useUpdateManyAndReturn<T extends UpdateManyAndReturnArgs<Schema, Model>>(
177+
options?: ModelMutationOptions<ModelResult<Schema, Model, T>[], T>,
178+
): ModelMutationModelResult<Schema, Model, T, true>;
179+
180+
useUpsert<T extends UpsertArgs<Schema, Model>>(
181+
options?: ModelMutationOptions<ModelResult<Schema, Model, T>, T>,
182+
): ModelMutationModelResult<Schema, Model, T>;
183+
184+
useDelete<T extends DeleteArgs<Schema, Model>>(
185+
options?: ModelMutationOptions<ModelResult<Schema, Model, T>, T>,
186+
): ModelMutationModelResult<Schema, Model, T>;
187+
188+
useDeleteMany<T extends DeleteManyArgs<Schema, Model>>(
189+
options?: ModelMutationOptions<BatchResult, T>,
190+
): ModelMutationResult<BatchResult, T>;
191+
192+
useCount<T extends CountArgs<Schema, Model>>(
193+
args?: Subset<T, CountArgs<Schema, Model>>,
194+
options?: ModelQueryOptions<CountResult<Schema, Model, T>>,
195+
): ModelQueryResult<CountResult<Schema, Model, T>>;
196+
197+
useAggregate<T extends AggregateArgs<Schema, Model>>(
198+
args: Subset<T, AggregateArgs<Schema, Model>>,
199+
options?: ModelQueryOptions<AggregateResult<Schema, Model, T>>,
200+
): ModelQueryResult<AggregateResult<Schema, Model, T>>;
201+
202+
useGroupBy<T extends GroupByArgs<Schema, Model>>(
203+
args: Subset<T, GroupByArgs<Schema, Model>>,
204+
options?: ModelQueryOptions<GroupByResult<Schema, Model, T>>,
205+
): ModelQueryResult<GroupByResult<Schema, Model, T>>;
206+
}
207+
>;
186208

187209
/**
188210
* Gets data query hooks for all models in the schema.
189211
*/
190-
export function useClientQueries<Schema extends SchemaDef>(schema: Schema): SchemaHooks<Schema> {
212+
export function useClientQueries<Schema extends SchemaDef>(schema: Schema): ClientHooks<Schema> {
191213
return Object.keys(schema.models).reduce((acc, model) => {
192214
(acc as any)[lowerCaseFirst(model)] = useModelQueries(schema, model as GetModels<Schema>);
193215
return acc;
194-
}, {} as SchemaHooks<Schema>);
216+
}, {} as ClientHooks<Schema>);
195217
}
196218

197219
/**
@@ -226,39 +248,39 @@ export function useModelQueries<Schema extends SchemaDef, Model extends GetModel
226248
},
227249

228250
useCreate: (options?: any) => {
229-
return useInternalMutation(schema, modelName, 'POST', 'create', options, true);
251+
return useInternalMutation(schema, modelName, 'POST', 'create', options);
230252
},
231253

232254
useCreateMany: (options?: any) => {
233-
return useInternalMutation(schema, modelName, 'POST', 'createMany', options, false);
255+
return useInternalMutation(schema, modelName, 'POST', 'createMany', options);
234256
},
235257

236258
useCreateManyAndReturn: (options?: any) => {
237-
return useInternalMutation(schema, modelName, 'POST', 'createManyAndReturn', options, true);
259+
return useInternalMutation(schema, modelName, 'POST', 'createManyAndReturn', options);
238260
},
239261

240262
useUpdate: (options?: any) => {
241-
return useInternalMutation(schema, modelName, 'PUT', 'update', options, true);
263+
return useInternalMutation(schema, modelName, 'PUT', 'update', options);
242264
},
243265

244266
useUpdateMany: (options?: any) => {
245-
return useInternalMutation(schema, modelName, 'PUT', 'updateMany', options, false);
267+
return useInternalMutation(schema, modelName, 'PUT', 'updateMany', options);
246268
},
247269

248270
useUpdateManyAndReturn: (options?: any) => {
249-
return useInternalMutation(schema, modelName, 'PUT', 'updateManyAndReturn', options, true);
271+
return useInternalMutation(schema, modelName, 'PUT', 'updateManyAndReturn', options);
250272
},
251273

252274
useUpsert: (options?: any) => {
253-
return useInternalMutation(schema, modelName, 'POST', 'upsert', options, true);
275+
return useInternalMutation(schema, modelName, 'POST', 'upsert', options);
254276
},
255277

256278
useDelete: (options?: any) => {
257-
return useInternalMutation(schema, modelName, 'DELETE', 'delete', options, true);
279+
return useInternalMutation(schema, modelName, 'DELETE', 'delete', options);
258280
},
259281

260282
useDeleteMany: (options?: any) => {
261-
return useInternalMutation(schema, modelName, 'DELETE', 'deleteMany', options, false);
283+
return useInternalMutation(schema, modelName, 'DELETE', 'deleteMany', options);
262284
},
263285

264286
useCount: (options?: any) => {
@@ -291,7 +313,7 @@ export function useInternalQuery<TQueryFnData, TData>(
291313
optimisticUpdate: optionsValue?.optimisticUpdate !== false,
292314
});
293315
const queryFn: QueryFunction<TQueryFnData, QueryKey, unknown> = ({ signal }) =>
294-
fetcher<TQueryFnData, false>(reqUrl, { signal }, fetch, false);
316+
fetcher<TQueryFnData>(reqUrl, { signal }, fetch);
295317

296318
let mergedOpt: any;
297319
if (isStore(options)) {
@@ -324,23 +346,21 @@ export function useInternalInfiniteQuery<TQueryFnData, TData>(
324346
model: string,
325347
operation: string,
326348
args: StoreOrVal<unknown>,
327-
options: StoreOrVal<
328-
Omit<
329-
CreateInfiniteQueryOptions<TQueryFnData, DefaultError, InfiniteData<TData>>,
330-
'queryKey' | 'initialPageParam'
331-
>
332-
>,
349+
options:
350+
| StoreOrVal<
351+
Omit<
352+
CreateInfiniteQueryOptions<TQueryFnData, DefaultError, InfiniteData<TData>>,
353+
'queryKey' | 'initialPageParam'
354+
>
355+
>
356+
| undefined,
333357
) {
358+
options = options ?? { getNextPageParam: () => undefined };
334359
const { endpoint, fetch } = getQuerySettings();
335360
const argsValue = unwrapStore(args);
336361
const queryKey = getQueryKey(model, operation, argsValue, { infinite: true, optimisticUpdate: false });
337362
const queryFn: QueryFunction<TQueryFnData, QueryKey, unknown> = ({ pageParam, signal }) =>
338-
fetcher<TQueryFnData, false>(
339-
makeUrl(endpoint, model, operation, pageParam ?? argsValue),
340-
{ signal },
341-
fetch,
342-
false,
343-
);
363+
fetcher<TQueryFnData>(makeUrl(endpoint, model, operation, pageParam ?? argsValue), { signal }, fetch);
344364

345365
let mergedOpt: StoreOrVal<CreateInfiniteQueryOptions<TQueryFnData, DefaultError, InfiniteData<TData>>>;
346366
if (isStore(options)) {
@@ -381,18 +401,12 @@ export function useInternalInfiniteQuery<TQueryFnData, TData>(
381401
* @param options The svelte-query options.
382402
* @param checkReadBack Whether to check for read back errors and return undefined if found.
383403
*/
384-
export function useInternalMutation<
385-
TArgs,
386-
R = any,
387-
C extends boolean = boolean,
388-
Result = C extends true ? R | undefined : R,
389-
>(
404+
export function useInternalMutation<TArgs, R = any>(
390405
schema: SchemaDef,
391406
model: string,
392407
method: 'POST' | 'PUT' | 'DELETE',
393408
operation: string,
394-
options?: StoreOrVal<Omit<CreateMutationOptions<Result, DefaultError, TArgs>, 'mutationFn'> & ExtraMutationOptions>,
395-
checkReadBack?: C,
409+
options?: StoreOrVal<Omit<CreateMutationOptions<R, DefaultError, TArgs>, 'mutationFn'> & ExtraMutationOptions>,
396410
) {
397411
const { endpoint, fetch, logging } = getQuerySettings();
398412
const queryClient = useQueryClient();
@@ -409,10 +423,10 @@ export function useInternalMutation<
409423
body: marshal(data),
410424
}),
411425
};
412-
return fetcher<R, C>(reqUrl, fetchInit, fetch, checkReadBack) as Promise<Result>;
426+
return fetcher<R>(reqUrl, fetchInit, fetch) as Promise<R>;
413427
};
414428

415-
let mergedOpt: StoreOrVal<CreateMutationOptions<Result, DefaultError, TArgs>>;
429+
let mergedOpt: StoreOrVal<CreateMutationOptions<R, DefaultError, TArgs>>;
416430

417431
if (isStore(options)) {
418432
mergedOpt = derived([options], ([$opt]) => ({

packages/clients/tanstack-query/src/utils/common.ts

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -125,21 +125,12 @@ export type APIContext = {
125125
logging?: boolean;
126126
};
127127

128-
export async function fetcher<R, C extends boolean>(
129-
url: string,
130-
options?: RequestInit,
131-
customFetch?: FetchFn,
132-
checkReadBack?: C,
133-
): Promise<C extends true ? R | undefined : R> {
128+
export async function fetcher<R>(url: string, options?: RequestInit, customFetch?: FetchFn): Promise<R> {
134129
const _fetch = customFetch ?? fetch;
135130
const res = await _fetch(url, options);
136131
if (!res.ok) {
137132
const errData = unmarshal(await res.text());
138-
if (
139-
checkReadBack !== false &&
140-
errData.error?.rejectedByPolicy &&
141-
errData.error?.rejectReason === 'cannot-read-back'
142-
) {
133+
if (errData.error?.rejectedByPolicy && errData.error?.rejectReason === 'cannot-read-back') {
143134
// policy doesn't allow mutation result to be read back, just return undefined
144135
return undefined as any;
145136
}

packages/clients/tanstack-query/src/utils/types.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
import type { OperationsIneligibleForDelegateModels } from '@zenstackhq/orm';
2+
import type { GetModels, IsDelegateModel, SchemaDef } from '@zenstackhq/schema';
3+
14
export type MaybePromise<T> = T | Promise<T> | PromiseLike<T>;
25

36
export const ORMWriteActions = [
@@ -17,3 +20,9 @@ export const ORMWriteActions = [
1720
] as const;
1821

1922
export type ORMWriteActionType = (typeof ORMWriteActions)[number];
23+
24+
export type TrimDelegateModelOperations<
25+
Schema extends SchemaDef,
26+
Model extends GetModels<Schema>,
27+
T extends Record<string, unknown>,
28+
> = IsDelegateModel<Schema, Model> extends true ? Omit<T, OperationsIneligibleForDelegateModels> : T;

0 commit comments

Comments
 (0)