Skip to content

Commit 663144d

Browse files
committed
add query key hashing, add preprocessors
1 parent c0799d2 commit 663144d

File tree

13 files changed

+173
-122
lines changed

13 files changed

+173
-122
lines changed

examples/vite/src/App.tsx

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import './mocks';
22
import { useRef, useState } from 'react';
33
import { CommentData, PostData, baseUrl } from './mocks';
44
import './App.css';
5-
import { HttpQueryBuilder, MiddlewareContext, useOperateOnTags } from 'react-query-builder';
5+
import { HttpQueryBuilder, useOperateOnTags } from 'react-query-builder';
66
import { queryClient } from './client';
77

88
const baseQuery = new HttpQueryBuilder({
@@ -23,13 +23,10 @@ const postQuery = baseQuery
2323
.withPath('/posts/:id')
2424
.withData<PostData>()
2525
.withTags((ctx) => ({ type: 'posts' as any, id: ctx.data.id }))
26+
.withPreprocessor<{ id: number }>((vars) => ({ ...vars, params: { id: vars.id } }))
2627
.withMiddleware(async (ctx, next) => {
2728
const res = await next(ctx);
2829
return { ...res, titleUppercase: res.title.toUpperCase() };
29-
})
30-
.withMiddleware((ctx: MiddlewareContext<{ id: number }>, next) => {
31-
const { id, ...vars } = ctx.vars;
32-
return next({ ...ctx, vars: { ...vars, params: { id } } });
3330
});
3431

3532
const commentsQuery = baseQuery
@@ -114,7 +111,6 @@ function App() {
114111
onClick={() => setPostId(post.id)}
115112
onMouseOver={() => {
116113
postQuery.client.prefetch({ id: post.id });
117-
commentsQuery.client.prefetch({ search: { postId: post.id } });
118114
}}
119115
>
120116
{post.title}

src/builder/HttpMutationBuilder.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import { ExtractPathParams, HttpMethod } from '../http/types';
22
import { WithOptional } from '../types/utils';
3-
import { HttpQueryBuilder } from './HttpQueryBuilder';
43
import { MutationBuilder, MutationBuilderConfig } from './MutationBuilder';
5-
import { QueryBuilder } from './QueryBuilder';
64
import { MiddlewareFn } from './createMiddlewareFunction';
75
import {
86
HttpBaseHeaders,
@@ -90,7 +88,4 @@ export class HttpMutationBuilder<
9088
declare withMiddleware: <TVars = T['vars'], TData = T['data'], TError = T['error']>(
9189
middleware: MiddlewareFn<TVars, TData, TError, T>,
9290
) => HttpMutationBuilder<SetAllTypes<T, TData, TError, TVars, true>>;
93-
94-
declare asQueryBuilder: () => HttpQueryBuilder<T>;
95-
protected override QueryBuilderConstructor: typeof QueryBuilder = HttpQueryBuilder as typeof QueryBuilder;
9691
}

src/builder/HttpQueryBuilder.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { HttpMutationBuilder } from './HttpMutationBuilder';
44
import { MutationBuilder } from './MutationBuilder';
55
import { QueryBuilder, QueryBuilderConfig } from './QueryBuilder';
66
import { MiddlewareFn } from './createMiddlewareFunction';
7+
import { PreprocessorFn } from './createPreprocessorFunction';
78
import {
89
HttpBaseHeaders,
910
HttpBaseParams,
@@ -14,13 +15,14 @@ import {
1415
SetErrorType,
1516
} from './types';
1617
import { AppendVarsType } from './types';
17-
import { createHttpQueryFn, mergeHttpVars } from './utils';
18+
import { createHttpQueryFn, createHttpQueryHashFn, mergeHttpVars } from './utils';
1819

1920
export class HttpQueryBuilder<T extends HttpBuilderTypeTemplate = HttpBuilderTypeTemplate> extends QueryBuilder<T> {
2021
constructor(config?: WithOptional<QueryBuilderConfig<T>, 'queryFn'>) {
2122
const mergeVars = config?.mergeVars || mergeHttpVars;
2223
const queryFn = config?.queryFn || createHttpQueryFn<T>(mergeVars);
23-
super({ mergeVars, queryFn, ...config });
24+
const queryKeyHashFn = config?.queryKeyHashFn || createHttpQueryHashFn();
25+
super({ mergeVars, queryFn, queryKeyHashFn, ...config });
2426
}
2527

2628
withBody<TBody>(body?: TBody): HttpQueryBuilder<AppendVarsType<T, { body: TBody }>> {
@@ -77,6 +79,10 @@ export class HttpQueryBuilder<T extends HttpBuilderTypeTemplate = HttpBuilderTyp
7779
reset?: TReset,
7880
) => HttpQueryBuilder<AppendVarsType<T, Partial<TVars>, TReset>>;
7981

82+
declare withPreprocessor: <TVars = T['vars']>(
83+
preprocessor: PreprocessorFn<TVars, T['vars']>,
84+
) => HttpQueryBuilder<AppendVarsType<T, TVars, true, true>>;
85+
8086
declare withMiddleware: <TVars = T['vars'], TData = T['data'], TError = T['error']>(
8187
middleware: MiddlewareFn<TVars, TData, TError, T>,
8288
) => HttpQueryBuilder<SetAllTypes<T, TData, TError, TVars, true>>;

src/builder/MutationBuilder.ts

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,9 @@ import {
1313
useQueryClient,
1414
} from '@tanstack/react-query';
1515
import { QueryTagOption, QueryUpdateTagObject } from '../tags/types';
16-
import { QueryBuilder } from './QueryBuilder';
1716
import { MiddlewareFn, createMiddlewareFunction } from './createMiddlewareFunction';
1817
import { createUpdateMiddleware } from './createUpdateMiddleware';
19-
import { BuilderMergeVarsFn, BuilderQueryFn, SetAllTypes, SetDataType, SetErrorType } from './types';
18+
import { BuilderConfig, SetAllTypes, SetDataType, SetErrorType } from './types';
2019
import { AppendVarsType, BuilderTypeTemplate } from './types';
2120
import { areKeysEqual, mergeMutationOptions, mergeVars } from './utils';
2221

@@ -200,27 +199,8 @@ export class MutationBuilder<T extends BuilderTypeTemplate = BuilderTypeTemplate
200199
freeze(): MutationBuilderFrozen<T> {
201200
return this;
202201
}
203-
204-
protected QueryBuilderConstructor: typeof QueryBuilder = QueryBuilder;
205-
206-
asQueryBuilder(): QueryBuilder<T> {
207-
return new this.QueryBuilderConstructor({
208-
queryFn: this.config.queryFn,
209-
queryClient: this.config.queryClient,
210-
mergeVars: this.config.mergeVars,
211-
vars: this.config.vars,
212-
syncChannel: this.config.syncChannel,
213-
});
214-
}
215202
}
216203

217-
export type MutationBuilderConfig<T extends BuilderTypeTemplate> = {
218-
queryFn: BuilderQueryFn<T>;
219-
220-
vars?: Partial<T['vars']>;
221-
mergeVars?: BuilderMergeVarsFn<T['vars']>;
222-
204+
export type MutationBuilderConfig<T extends BuilderTypeTemplate> = BuilderConfig<T> & {
223205
options?: UseMutationOptions<T['data'], T['error'], T['vars']>;
224-
queryClient?: QueryClient;
225-
syncChannel?: BroadcastChannel;
226206
};

src/builder/QueryBuilder.ts

Lines changed: 35 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import {
22
CancelOptions,
33
DataTag,
44
InvalidateOptions,
5-
QueryClient,
65
QueryFilters,
76
QueryFunction,
87
RefetchOptions,
@@ -21,15 +20,9 @@ import { QueryTagOption } from '../tags/types';
2120
import { FunctionType } from '../types/utils';
2221
import { MutationBuilder } from './MutationBuilder';
2322
import { MiddlewareFn, createMiddlewareFunction } from './createMiddlewareFunction';
23+
import { PreprocessorFn, createPreprocessorFunction, identityPreprocessor } from './createPreprocessorFunction';
2424
import { createTagMiddleware } from './createTagMiddleware';
25-
import {
26-
BuilderMergeVarsFn,
27-
BuilderQueriesResult,
28-
BuilderQueryFn,
29-
SetAllTypes,
30-
SetDataType,
31-
SetErrorType,
32-
} from './types';
25+
import { BuilderConfig, BuilderQueriesResult, SetAllTypes, SetDataType, SetErrorType } from './types';
3326
import { AppendVarsType, BuilderTypeTemplate } from './types';
3427
import { mergeQueryOptions, mergeVars } from './utils';
3528

@@ -52,23 +45,33 @@ export class QueryBuilderFrozen<T extends BuilderTypeTemplate> {
5245
return mergeVars(list, this.config.mergeVars);
5346
};
5447

55-
getQueryFn: () => QueryFunction<T['data'], [T['vars']]> = () => {
48+
protected preprocessVars: (vars: T['vars']) => T['queryKey'][0] = (vars) => {
49+
if (!this.config.preprocessorFn) return vars as T['queryKey'][0];
50+
return this.config.preprocessorFn(vars);
51+
};
52+
53+
getQueryFn: () => QueryFunction<T['data'], T['queryKey']> = () => {
5654
return ({ client, meta, queryKey, signal, pageParam }) => {
5755
return this.config.queryFn({ client, meta, queryKey, signal, pageParam, originalQueryKey: queryKey });
5856
};
5957
};
6058

61-
getQueryKey: (vars: T['vars']) => DataTag<[T['vars']], T['data'], T['error']> = (vars) => {
62-
return [this.mergeVars([this.config.vars, vars])] as DataTag<[T['vars']], T['data'], T['error']>;
59+
getQueryKey: (vars: T['vars']) => DataTag<T['queryKey'], T['data'], T['error']> = (vars) => {
60+
return [this.preprocessVars(this.mergeVars([this.config.vars, vars]))] as DataTag<
61+
T['queryKey'],
62+
T['data'],
63+
T['error']
64+
>;
6365
};
6466

6567
getQueryOptions: (
6668
vars: T['vars'],
6769
opts?: QueryBuilderConfig<T>['options'],
68-
) => UseQueryOptions<T['data'], T['error'], T['data'], [T['vars']]> & { queryFn: FunctionType } = (vars, opts) => {
70+
) => UseQueryOptions<T['data'], T['error'], T['data'], T['queryKey']> & { queryFn: FunctionType } = (vars, opts) => {
6971
return mergeQueryOptions([
7072
{
7173
queryFn: this.getQueryFn(),
74+
queryKeyHashFn: this.config.queryKeyHashFn,
7275
queryKey: this.getQueryKey(vars),
7376
},
7477
this.config.options,
@@ -79,21 +82,21 @@ export class QueryBuilderFrozen<T extends BuilderTypeTemplate> {
7982
useQuery: (
8083
vars: T['vars'],
8184
opts?: QueryBuilderConfig<T>['options'],
82-
) => ReturnType<typeof useQuery<T['data'], T['error'], T['data'], [T['vars']]>> = (vars, opts) => {
85+
) => ReturnType<typeof useQuery<T['data'], T['error'], T['data'], T['queryKey']>> = (vars, opts) => {
8386
return useQuery(this.getQueryOptions(vars, opts), this.config.queryClient);
8487
};
8588

8689
useSuspenseQuery: (
8790
vars: T['vars'],
8891
opts?: QueryBuilderConfig<T>['options'],
89-
) => ReturnType<typeof useSuspenseQuery<T['data'], T['error'], T['data'], [T['vars']]>> = (vars, opts) => {
92+
) => ReturnType<typeof useSuspenseQuery<T['data'], T['error'], T['data'], T['queryKey']>> = (vars, opts) => {
9093
return useSuspenseQuery(this.getQueryOptions(vars, opts), this.config.queryClient);
9194
};
9295

9396
usePrefetchQuery: (
9497
vars: T['vars'],
9598
opts?: QueryBuilderConfig<T>['options'],
96-
) => ReturnType<typeof usePrefetchQuery<T['data'], T['error'], T['data'], [T['vars']]>> = (vars, opts) => {
99+
) => ReturnType<typeof usePrefetchQuery<T['data'], T['error'], T['data'], T['queryKey']>> = (vars, opts) => {
97100
return usePrefetchQuery(this.getQueryOptions(vars, opts), this.config.queryClient);
98101
};
99102

@@ -147,7 +150,7 @@ export class QueryBuilderFrozen<T extends BuilderTypeTemplate> {
147150

148151
class QueryBuilderClient<
149152
T extends BuilderTypeTemplate,
150-
TFilters = QueryFilters<T['data'], T['error'], T['data'], [T['vars']]>,
153+
TFilters = QueryFilters<T['data'], T['error'], T['data'], T['queryKey']>,
151154
> {
152155
constructor(private builder: QueryBuilderFrozen<T>) {}
153156

@@ -182,7 +185,7 @@ class QueryBuilderClient<
182185
this.builder.config.queryClient?.getQueryData<T['data']>(this.builder.getQueryKey(vars));
183186

184187
readonly setData = (vars: T['vars'], updater: Updater<T['data']>, opts?: SetDataOptions) =>
185-
this.builder.config.queryClient?.setQueryData(this.builder.getQueryKey(vars), updater, opts);
188+
this.builder.config.queryClient?.setQueryData<T['data']>(this.builder.getQueryKey(vars), updater, opts);
186189

187190
readonly getState = (vars: T['vars']) =>
188191
this.builder.config.queryClient?.getQueryState<T['data'], T['error']>(this.builder.getQueryKey(vars));
@@ -214,6 +217,16 @@ export class QueryBuilder<T extends BuilderTypeTemplate = BuilderTypeTemplate> e
214217
return new ctor<T>(newConfig) as this;
215218
}
216219

220+
withPreprocessor<TVars = T['vars']>(
221+
preprocessor: PreprocessorFn<TVars, T['vars']>,
222+
): QueryBuilder<AppendVarsType<T, TVars, true, true>> {
223+
const newBuilder = this as unknown as QueryBuilder<AppendVarsType<T, TVars, true, true>>;
224+
225+
return newBuilder.withConfig({
226+
preprocessorFn: createPreprocessorFunction(preprocessor, this.config.preprocessorFn || identityPreprocessor),
227+
});
228+
}
229+
217230
withMiddleware<TVars = T['vars'], TData = T['data'], TError = T['error']>(
218231
middleware: MiddlewareFn<TVars, TData, TError, T>,
219232
config?: Partial<QueryBuilderConfig<SetAllTypes<T, TData, TError, TVars, true>>>,
@@ -249,25 +262,13 @@ export class QueryBuilder<T extends BuilderTypeTemplate = BuilderTypeTemplate> e
249262
protected MutationBuilderConstructor: typeof MutationBuilder = MutationBuilder;
250263

251264
asMutationBuilder(): MutationBuilder<T> {
252-
return new this.MutationBuilderConstructor({
253-
queryFn: this.config.queryFn,
254-
queryClient: this.config.queryClient,
255-
mergeVars: this.config.mergeVars,
256-
vars: this.config.vars,
257-
syncChannel: this.config.syncChannel,
258-
});
265+
const { options, ...restConfig } = this.config;
266+
return new this.MutationBuilderConstructor(restConfig);
259267
}
260268
}
261269

262270
type Updater<T> = T | undefined | ((oldData: T | undefined) => T | undefined);
263271

264-
export type QueryBuilderConfig<T extends BuilderTypeTemplate> = {
265-
queryFn: BuilderQueryFn<T>;
266-
vars?: Partial<T['vars']>;
267-
mergeVars?: BuilderMergeVarsFn<T['vars']>;
268-
269-
options?: Partial<UseQueryOptions<T['data'], T['error'], T['data'], [T['vars']]> & { queryFn: FunctionType }>;
270-
271-
queryClient?: QueryClient;
272-
syncChannel?: BroadcastChannel;
272+
export type QueryBuilderConfig<T extends BuilderTypeTemplate> = BuilderConfig<T> & {
273+
options?: Partial<UseQueryOptions<T['data'], T['error'], T['data'], T['queryKey']> & { queryFn: FunctionType }>;
273274
};

src/builder/createMiddlewareFunction.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,21 +3,23 @@ import { QueryBuilderConfig } from './QueryBuilder';
33
import type { BuilderQueryContext, BuilderQueryFn, BuilderTypeTemplate, SetAllTypes } from './types';
44

55
export type MiddlewareFn<TVars, TData, TError, TOriginalTemplate extends BuilderTypeTemplate> = (
6-
context: MiddlewareContext<TVars>,
6+
context: MiddlewareContext<TOriginalTemplate['queryKey']>,
77
next: MiddlewareNextFn<TOriginalTemplate>,
88
throwError: (error: TError) => never,
99
config: QueryBuilderConfig<TOriginalTemplate> | MutationBuilderConfig<TOriginalTemplate>,
1010
) => TData | Promise<TData>;
1111

12-
export type MiddlewareContext<TVars> = BuilderQueryContext<TVars> & {
13-
vars: TVars;
12+
export type MiddlewareContext<TKey extends unknown[]> = BuilderQueryContext<TKey> & {
13+
vars: TKey[0];
1414
};
1515

1616
export type MiddlewareNextFn<T extends BuilderTypeTemplate> = (
17-
context: Omit<MiddlewareContext<T['vars']>, 'queryKey'>,
17+
context: Omit<MiddlewareContext<T['queryKey']>, 'queryKey'>,
1818
) => Promise<T['data']>;
1919

20-
const createMiddlewareContext = <TVars>(context: BuilderQueryContext<TVars>): MiddlewareContext<TVars> => {
20+
const createMiddlewareContext = <TKey extends unknown[]>(
21+
context: BuilderQueryContext<TKey>,
22+
): MiddlewareContext<TKey> => {
2123
return {
2224
...context,
2325
vars: context.queryKey[0],
@@ -35,7 +37,7 @@ export const createMiddlewareFunction = <TVars, TData, TError, TOriginalTemplate
3537
): BuilderQueryFn<SetAllTypes<TOriginalTemplate, TData, TError, TVars, true>> => {
3638
return async (context) =>
3739
middleware(
38-
createMiddlewareContext<TVars>(context),
40+
createMiddlewareContext<TOriginalTemplate['queryKey']>(context),
3941
async (ctx) => originalFn({ ...context, queryKey: [ctx.vars] }),
4042
throwError,
4143
config,
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
export type PreprocessorFn<TVars, TOriginalVars> = (vars: TVars) => TOriginalVars;
2+
3+
export const identityPreprocessor = <TVars = unknown, TOut = TVars>(vars: TVars): TOut => vars as unknown as TOut;
4+
5+
export const createPreprocessorFunction = <TVars, TOriginalVars>(
6+
preprocessor: PreprocessorFn<TVars, TOriginalVars>,
7+
originalFn: PreprocessorFn<TOriginalVars, any> = identityPreprocessor,
8+
): PreprocessorFn<TVars, any> => {
9+
return (context) => originalFn(preprocessor(context));
10+
};

src/builder/createTagMiddleware.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ type CreateTagMiddleware = <T extends BuilderTypeTemplate>(
1010
) => MiddlewareFn<T['vars'], T['data'], T['error'], T>;
1111

1212
export const createTagMiddleware: CreateTagMiddleware = (tags, cacheId) =>
13-
async function tagMiddlware(ctx, next) {
13+
async function tagMiddlware(ctx, next, _, config) {
1414
let resolvedTags: QueryTagObject[] = [];
1515

1616
try {
@@ -24,7 +24,8 @@ export const createTagMiddleware: CreateTagMiddleware = (tags, cacheId) =>
2424

2525
throw error;
2626
} finally {
27-
const hash = hashKey(ctx.originalQueryKey);
27+
const hashFn = config?.queryKeyHashFn ?? hashKey;
28+
const hash = hashFn(ctx.originalQueryKey as any);
2829
const tagCache = ((ctx.client as any).tagCache ??= {}) as QueryTagCache;
2930
const tags = (tagCache[hash] ??= {});
3031
tags[cacheId] = resolvedTags;

0 commit comments

Comments
 (0)