Skip to content

Commit a06004e

Browse files
committed
feat(clients): add ExtResult support to TanStack Query hooks
Add InferSchema, InferOptions, and InferExtResult type utilities to client-helpers. Update React, Vue, and Svelte adapters to accept ClientContract type parameter, enabling computed fields from plugins to be reflected in result types. Simplify useClientQueries API by inferring schema and options from the client type. Update sliced-client tests to use new API surface.
1 parent 12aeb7b commit a06004e

7 files changed

Lines changed: 380 additions & 281 deletions

File tree

packages/clients/client-helpers/src/types.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,28 @@
1+
import type { ClientContract, QueryOptions } from '@zenstackhq/orm';
2+
import type { SchemaDef } from '@zenstackhq/schema';
3+
14
/**
25
* A type that represents either a value of type T or a Promise that resolves to type T.
36
*/
47
export type MaybePromise<T> = T | Promise<T> | PromiseLike<T>;
58

9+
/**
10+
* Infers the schema definition from a client contract type, or passes through a raw SchemaDef.
11+
*/
12+
export type InferSchema<T> = T extends { $schema: infer S extends SchemaDef } ? S : T extends SchemaDef ? T : never;
13+
14+
/**
15+
* Extracts the ExtResult type from a client contract, or defaults to `{}`.
16+
*/
17+
export type InferExtResult<T> = T extends ClientContract<any, any, any, any, infer E> ? E : {};
18+
19+
/**
20+
* Infers query options from a client contract type, or defaults to `QueryOptions<Schema>`.
21+
*/
22+
export type InferOptions<T, Schema extends SchemaDef> = T extends { $options: infer O extends QueryOptions<Schema> }
23+
? O
24+
: QueryOptions<Schema>;
25+
626
/**
727
* List of ORM write actions.
828
*/

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

Lines changed: 94 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,14 @@ import {
1919
type UseSuspenseQueryOptions,
2020
type UseSuspenseQueryResult,
2121
} from '@tanstack/react-query';
22-
import { createInvalidator, createOptimisticUpdater, DEFAULT_QUERY_ENDPOINT } from '@zenstackhq/client-helpers';
22+
import { createInvalidator, createOptimisticUpdater, DEFAULT_QUERY_ENDPOINT, type InferExtResult, type InferOptions, type InferSchema } from '@zenstackhq/client-helpers';
2323
import { fetcher, makeUrl, marshal } from '@zenstackhq/client-helpers/fetch';
2424
import { lowerCaseFirst } from '@zenstackhq/common-helpers';
2525
import type {
2626
AggregateArgs,
2727
AggregateResult,
2828
BatchResult,
29+
ClientContract,
2930
CountArgs,
3031
CountResult,
3132
CreateArgs,
@@ -34,6 +35,7 @@ import type {
3435
DeleteArgs,
3536
DeleteManyArgs,
3637
ExistsArgs,
38+
ExtResultBase,
3739
FindFirstArgs,
3840
FindManyArgs,
3941
FindUniqueArgs,
@@ -68,6 +70,8 @@ import type {
6870
WithOptimistic,
6971
} from './common/types.js';
7072
export type { FetchFn } from '@zenstackhq/client-helpers/fetch';
73+
export type { InferExtResult, InferOptions, InferSchema } from '@zenstackhq/client-helpers';
74+
export type { SchemaDef } from '@zenstackhq/schema';
7175

7276
type ProcedureHookFn<
7377
Schema extends SchemaDef,
@@ -142,15 +146,20 @@ export type ModelMutationModelResult<
142146
TArgs,
143147
Array extends boolean = false,
144148
Options extends QueryOptions<Schema> = QueryOptions<Schema>,
145-
> = Omit<ModelMutationResult<SimplifiedResult<Schema, Model, TArgs, Options, false, Array>, TArgs>, 'mutateAsync'> & {
149+
ExtResult extends ExtResultBase<Schema> = {},
150+
> = Omit<ModelMutationResult<SimplifiedResult<Schema, Model, TArgs, Options, false, Array, ExtResult>, TArgs>, 'mutateAsync'> & {
146151
mutateAsync<T extends TArgs>(
147152
args: T,
148-
options?: ModelMutationOptions<SimplifiedResult<Schema, Model, T, Options, false, Array>, T>,
149-
): Promise<SimplifiedResult<Schema, Model, T, Options, false, Array>>;
153+
options?: ModelMutationOptions<SimplifiedResult<Schema, Model, T, Options, false, Array, ExtResult>, T>,
154+
): Promise<SimplifiedResult<Schema, Model, T, Options, false, Array, ExtResult>>;
150155
};
151156

152-
export type ClientHooks<Schema extends SchemaDef, Options extends QueryOptions<Schema> = QueryOptions<Schema>> = {
153-
[Model in GetSlicedModels<Schema, Options> as `${Uncapitalize<Model>}`]: ModelQueryHooks<Schema, Model, Options>;
157+
export type ClientHooks<
158+
Schema extends SchemaDef,
159+
Options extends QueryOptions<Schema> = QueryOptions<Schema>,
160+
ExtResult extends ExtResultBase<Schema> = {},
161+
> = {
162+
[Model in GetSlicedModels<Schema, Options> as `${Uncapitalize<Model>}`]: ModelQueryHooks<Schema, Model, Options, ExtResult>;
154163
} & ProcedureHooks<Schema, Options>;
155164

156165
type ProcedureHookGroup<Schema extends SchemaDef, Options extends QueryOptions<Schema>> = {
@@ -213,87 +222,88 @@ export type ModelQueryHooks<
213222
Schema extends SchemaDef,
214223
Model extends GetModels<Schema>,
215224
Options extends QueryOptions<Schema> = QueryOptions<Schema>,
225+
ExtResult extends ExtResultBase<Schema> = {},
216226
> = TrimSlicedOperations<
217227
Schema,
218228
Model,
219229
Options,
220230
{
221-
useFindUnique<T extends FindUniqueArgs<Schema, Model, Options>>(
222-
args: SelectSubset<T, FindUniqueArgs<Schema, Model, Options>>,
223-
options?: ModelQueryOptions<SimplifiedPlainResult<Schema, Model, T, Options> | null>,
224-
): ModelQueryResult<SimplifiedPlainResult<Schema, Model, T, Options> | null>;
225-
226-
useSuspenseFindUnique<T extends FindUniqueArgs<Schema, Model, Options>>(
227-
args: SelectSubset<T, FindUniqueArgs<Schema, Model, Options>>,
228-
options?: ModelSuspenseQueryOptions<SimplifiedPlainResult<Schema, Model, T, Options> | null>,
229-
): ModelSuspenseQueryResult<SimplifiedPlainResult<Schema, Model, T, Options> | null>;
230-
231-
useFindFirst<T extends FindFirstArgs<Schema, Model, Options>>(
232-
args?: SelectSubset<T, FindFirstArgs<Schema, Model, Options>>,
233-
options?: ModelQueryOptions<SimplifiedPlainResult<Schema, Model, T, Options> | null>,
234-
): ModelQueryResult<SimplifiedPlainResult<Schema, Model, T, Options> | null>;
235-
236-
useSuspenseFindFirst<T extends FindFirstArgs<Schema, Model, Options>>(
237-
args?: SelectSubset<T, FindFirstArgs<Schema, Model, Options>>,
238-
options?: ModelSuspenseQueryOptions<SimplifiedPlainResult<Schema, Model, T, Options> | null>,
239-
): ModelSuspenseQueryResult<SimplifiedPlainResult<Schema, Model, T, Options> | null>;
231+
useFindUnique<T extends FindUniqueArgs<Schema, Model, Options, {}, ExtResult>>(
232+
args: SelectSubset<T, FindUniqueArgs<Schema, Model, Options, {}, ExtResult>>,
233+
options?: ModelQueryOptions<SimplifiedPlainResult<Schema, Model, T, Options, ExtResult> | null>,
234+
): ModelQueryResult<SimplifiedPlainResult<Schema, Model, T, Options, ExtResult> | null>;
235+
236+
useSuspenseFindUnique<T extends FindUniqueArgs<Schema, Model, Options, {}, ExtResult>>(
237+
args: SelectSubset<T, FindUniqueArgs<Schema, Model, Options, {}, ExtResult>>,
238+
options?: ModelSuspenseQueryOptions<SimplifiedPlainResult<Schema, Model, T, Options, ExtResult> | null>,
239+
): ModelSuspenseQueryResult<SimplifiedPlainResult<Schema, Model, T, Options, ExtResult> | null>;
240+
241+
useFindFirst<T extends FindFirstArgs<Schema, Model, Options, {}, ExtResult>>(
242+
args?: SelectSubset<T, FindFirstArgs<Schema, Model, Options, {}, ExtResult>>,
243+
options?: ModelQueryOptions<SimplifiedPlainResult<Schema, Model, T, Options, ExtResult> | null>,
244+
): ModelQueryResult<SimplifiedPlainResult<Schema, Model, T, Options, ExtResult> | null>;
245+
246+
useSuspenseFindFirst<T extends FindFirstArgs<Schema, Model, Options, {}, ExtResult>>(
247+
args?: SelectSubset<T, FindFirstArgs<Schema, Model, Options, {}, ExtResult>>,
248+
options?: ModelSuspenseQueryOptions<SimplifiedPlainResult<Schema, Model, T, Options, ExtResult> | null>,
249+
): ModelSuspenseQueryResult<SimplifiedPlainResult<Schema, Model, T, Options, ExtResult> | null>;
240250

241251
useExists<T extends ExistsArgs<Schema, Model, Options>>(
242252
args?: Subset<T, ExistsArgs<Schema, Model, Options>>,
243253
options?: ModelQueryOptions<boolean>,
244254
): ModelQueryResult<boolean>;
245255

246-
useFindMany<T extends FindManyArgs<Schema, Model, Options>>(
247-
args?: SelectSubset<T, FindManyArgs<Schema, Model, Options>>,
248-
options?: ModelQueryOptions<SimplifiedPlainResult<Schema, Model, T, Options>[]>,
249-
): ModelQueryResult<SimplifiedPlainResult<Schema, Model, T, Options>[]>;
256+
useFindMany<T extends FindManyArgs<Schema, Model, Options, {}, ExtResult>>(
257+
args?: SelectSubset<T, FindManyArgs<Schema, Model, Options, {}, ExtResult>>,
258+
options?: ModelQueryOptions<SimplifiedPlainResult<Schema, Model, T, Options, ExtResult>[]>,
259+
): ModelQueryResult<SimplifiedPlainResult<Schema, Model, T, Options, ExtResult>[]>;
250260

251-
useSuspenseFindMany<T extends FindManyArgs<Schema, Model, Options>>(
252-
args?: SelectSubset<T, FindManyArgs<Schema, Model, Options>>,
253-
options?: ModelSuspenseQueryOptions<SimplifiedPlainResult<Schema, Model, T, Options>[]>,
254-
): ModelSuspenseQueryResult<SimplifiedPlainResult<Schema, Model, T, Options>[]>;
261+
useSuspenseFindMany<T extends FindManyArgs<Schema, Model, Options, {}, ExtResult>>(
262+
args?: SelectSubset<T, FindManyArgs<Schema, Model, Options, {}, ExtResult>>,
263+
options?: ModelSuspenseQueryOptions<SimplifiedPlainResult<Schema, Model, T, Options, ExtResult>[]>,
264+
): ModelSuspenseQueryResult<SimplifiedPlainResult<Schema, Model, T, Options, ExtResult>[]>;
255265

256-
useInfiniteFindMany<T extends FindManyArgs<Schema, Model, Options>>(
257-
args?: SelectSubset<T, FindManyArgs<Schema, Model, Options>>,
258-
options?: ModelInfiniteQueryOptions<SimplifiedPlainResult<Schema, Model, T, Options>[]>,
259-
): ModelInfiniteQueryResult<InfiniteData<SimplifiedPlainResult<Schema, Model, T, Options>[]>>;
266+
useInfiniteFindMany<T extends FindManyArgs<Schema, Model, Options, {}, ExtResult>>(
267+
args?: SelectSubset<T, FindManyArgs<Schema, Model, Options, {}, ExtResult>>,
268+
options?: ModelInfiniteQueryOptions<SimplifiedPlainResult<Schema, Model, T, Options, ExtResult>[]>,
269+
): ModelInfiniteQueryResult<InfiniteData<SimplifiedPlainResult<Schema, Model, T, Options, ExtResult>[]>>;
260270

261-
useSuspenseInfiniteFindMany<T extends FindManyArgs<Schema, Model, Options>>(
262-
args?: SelectSubset<T, FindManyArgs<Schema, Model, Options>>,
263-
options?: ModelSuspenseInfiniteQueryOptions<SimplifiedPlainResult<Schema, Model, T, Options>[]>,
264-
): ModelSuspenseInfiniteQueryResult<InfiniteData<SimplifiedPlainResult<Schema, Model, T, Options>[]>>;
271+
useSuspenseInfiniteFindMany<T extends FindManyArgs<Schema, Model, Options, {}, ExtResult>>(
272+
args?: SelectSubset<T, FindManyArgs<Schema, Model, Options, {}, ExtResult>>,
273+
options?: ModelSuspenseInfiniteQueryOptions<SimplifiedPlainResult<Schema, Model, T, Options, ExtResult>[]>,
274+
): ModelSuspenseInfiniteQueryResult<InfiniteData<SimplifiedPlainResult<Schema, Model, T, Options, ExtResult>[]>>;
265275

266-
useCreate<T extends CreateArgs<Schema, Model, Options>>(
267-
options?: ModelMutationOptions<SimplifiedPlainResult<Schema, Model, T, Options>, T>,
268-
): ModelMutationModelResult<Schema, Model, T, false, Options>;
276+
useCreate<T extends CreateArgs<Schema, Model, Options, {}, ExtResult>>(
277+
options?: ModelMutationOptions<SimplifiedPlainResult<Schema, Model, T, Options, ExtResult>, T>,
278+
): ModelMutationModelResult<Schema, Model, T, false, Options, ExtResult>;
269279

270280
useCreateMany<T extends CreateManyArgs<Schema, Model>>(
271281
options?: ModelMutationOptions<BatchResult, T>,
272282
): ModelMutationResult<BatchResult, T>;
273283

274-
useCreateManyAndReturn<T extends CreateManyAndReturnArgs<Schema, Model, Options>>(
275-
options?: ModelMutationOptions<SimplifiedPlainResult<Schema, Model, T, Options>[], T>,
276-
): ModelMutationModelResult<Schema, Model, T, true, Options>;
284+
useCreateManyAndReturn<T extends CreateManyAndReturnArgs<Schema, Model, Options, {}, ExtResult>>(
285+
options?: ModelMutationOptions<SimplifiedPlainResult<Schema, Model, T, Options, ExtResult>[], T>,
286+
): ModelMutationModelResult<Schema, Model, T, true, Options, ExtResult>;
277287

278-
useUpdate<T extends UpdateArgs<Schema, Model, Options>>(
279-
options?: ModelMutationOptions<SimplifiedPlainResult<Schema, Model, T, Options>, T>,
280-
): ModelMutationModelResult<Schema, Model, T, false, Options>;
288+
useUpdate<T extends UpdateArgs<Schema, Model, Options, {}, ExtResult>>(
289+
options?: ModelMutationOptions<SimplifiedPlainResult<Schema, Model, T, Options, ExtResult>, T>,
290+
): ModelMutationModelResult<Schema, Model, T, false, Options, ExtResult>;
281291

282292
useUpdateMany<T extends UpdateManyArgs<Schema, Model, Options>>(
283293
options?: ModelMutationOptions<BatchResult, T>,
284294
): ModelMutationResult<BatchResult, T>;
285295

286-
useUpdateManyAndReturn<T extends UpdateManyAndReturnArgs<Schema, Model, Options>>(
287-
options?: ModelMutationOptions<SimplifiedPlainResult<Schema, Model, T, Options>[], T>,
288-
): ModelMutationModelResult<Schema, Model, T, true, Options>;
296+
useUpdateManyAndReturn<T extends UpdateManyAndReturnArgs<Schema, Model, Options, {}, ExtResult>>(
297+
options?: ModelMutationOptions<SimplifiedPlainResult<Schema, Model, T, Options, ExtResult>[], T>,
298+
): ModelMutationModelResult<Schema, Model, T, true, Options, ExtResult>;
289299

290-
useUpsert<T extends UpsertArgs<Schema, Model, Options>>(
291-
options?: ModelMutationOptions<SimplifiedPlainResult<Schema, Model, T, Options>, T>,
292-
): ModelMutationModelResult<Schema, Model, T, false, Options>;
300+
useUpsert<T extends UpsertArgs<Schema, Model, Options, {}, ExtResult>>(
301+
options?: ModelMutationOptions<SimplifiedPlainResult<Schema, Model, T, Options, ExtResult>, T>,
302+
): ModelMutationModelResult<Schema, Model, T, false, Options, ExtResult>;
293303

294-
useDelete<T extends DeleteArgs<Schema, Model, Options>>(
295-
options?: ModelMutationOptions<SimplifiedPlainResult<Schema, Model, T, Options>, T>,
296-
): ModelMutationModelResult<Schema, Model, T, false, Options>;
304+
useDelete<T extends DeleteArgs<Schema, Model, Options, {}, ExtResult>>(
305+
options?: ModelMutationOptions<SimplifiedPlainResult<Schema, Model, T, Options, ExtResult>, T>,
306+
): ModelMutationModelResult<Schema, Model, T, false, Options, ExtResult>;
297307

298308
useDeleteMany<T extends DeleteManyArgs<Schema, Model, Options>>(
299309
options?: ModelMutationOptions<BatchResult, T>,
@@ -334,23 +344,38 @@ export type ModelQueryHooks<
334344
/**
335345
* Gets data query hooks for all models in the schema.
336346
*
347+
* Accepts either a raw `SchemaDef` or a `ClientContract` type (e.g. `typeof db`) as the generic parameter.
348+
* When a `ClientContract` type is provided, computed fields from plugins are reflected in the result types.
349+
*
350+
* @example
351+
* ```typescript
352+
* // Basic usage with schema
353+
* const client = useClientQueries(schema)
354+
*
355+
* // With server client type for computed field support
356+
* import type { DbType } from '~/server/db'
357+
* const client = useClientQueries<DbType>(schema)
358+
* ```
359+
*
337360
* @param schema The schema.
338361
* @param options Options for all queries originated from this hook.
339362
*/
340-
export function useClientQueries<Schema extends SchemaDef, Options extends QueryOptions<Schema> = QueryOptions<Schema>>(
341-
schema: Schema,
363+
export function useClientQueries<
364+
Client extends SchemaDef | ClientContract<any, any, any, any, any>,
365+
>(
366+
schema: InferSchema<Client>,
342367
options?: QueryContext,
343-
): ClientHooks<Schema, Options> {
368+
): ClientHooks<InferSchema<Client>, InferOptions<Client, InferSchema<Client>>, InferExtResult<Client> extends ExtResultBase<InferSchema<Client>> ? InferExtResult<Client> : {}> {
344369
const result = Object.keys(schema.models).reduce(
345370
(acc, model) => {
346-
(acc as any)[lowerCaseFirst(model)] = useModelQueries<Schema, GetModels<Schema>, Options>(
347-
schema,
348-
model as GetModels<Schema>,
371+
(acc as any)[lowerCaseFirst(model)] = useModelQueries(
372+
schema as any,
373+
model as any,
349374
options,
350375
);
351376
return acc;
352377
},
353-
{} as ClientHooks<Schema, Options>,
378+
{} as any,
354379
);
355380

356381
const procedures = (schema as any).procedures as Record<string, { mutation?: boolean }> | undefined;
@@ -407,7 +432,8 @@ export function useModelQueries<
407432
Schema extends SchemaDef,
408433
Model extends GetModels<Schema>,
409434
Options extends QueryOptions<Schema>,
410-
>(schema: Schema, model: Model, rootOptions?: QueryContext): ModelQueryHooks<Schema, Model, Options> {
435+
ExtResult extends ExtResultBase<Schema> = {},
436+
>(schema: Schema, model: Model, rootOptions?: QueryContext): ModelQueryHooks<Schema, Model, Options, ExtResult> {
411437
const modelDef = Object.values(schema.models).find((m) => m.name.toLowerCase() === model.toLowerCase());
412438
if (!modelDef) {
413439
throw new Error(`Model "${model}" not found in schema`);
@@ -517,7 +543,7 @@ export function useModelQueries<
517543
useSuspenseGroupBy: (args: any, options?: any) => {
518544
return useInternalSuspenseQuery(schema, modelName, 'groupBy', args, { ...rootOptions, ...options });
519545
},
520-
} as ModelQueryHooks<Schema, Model, Options>;
546+
} as ModelQueryHooks<Schema, Model, Options, ExtResult>;
521547
}
522548

523549
export function useInternalQuery<TQueryFnData, TData>(

0 commit comments

Comments
 (0)