Skip to content

Commit b7b6764

Browse files
committed
add middlewares for queries
1 parent 0a5437c commit b7b6764

File tree

5 files changed

+74
-6
lines changed

5 files changed

+74
-6
lines changed

examples/vite/src/App.tsx

Lines changed: 12 additions & 5 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, useOperateOnTags } from 'react-query-builder';
5+
import { HttpQueryBuilder, MiddlewareContext, useOperateOnTags } from 'react-query-builder';
66
import { queryClient } from './client';
77

88
const baseQuery = new HttpQueryBuilder({ queryClient }).withBaseUrl(baseUrl);
@@ -22,6 +22,14 @@ const postQuery = baseQuery
2222
.withData<PostData>()
2323
.withConfig({
2424
tags: (ctx) => [{ type: 'posts' as any, id: ctx.vars.params.id }],
25+
})
26+
.withMiddleware(async (ctx, next) => {
27+
const res = await next(ctx);
28+
return { ...res, titleUppercase: res.title.toUpperCase() };
29+
})
30+
.withMiddleware((ctx: MiddlewareContext<{ id: number }>, next) => {
31+
const id = ctx.vars.id;
32+
return next({ ...ctx, vars: { ...ctx.vars, params: { id } } });
2533
});
2634

2735
const commentsQuery = baseQuery
@@ -96,7 +104,7 @@ function App() {
96104
<h2
97105
onClick={() => setPostId(post.id)}
98106
onMouseOver={() => {
99-
postQuery.client.prefetch({ params: { id: post.id } });
107+
postQuery.client.prefetch({ id: post.id });
100108
commentsQuery.client.prefetch({ search: { postId: post.id } });
101109
}}
102110
>
@@ -121,9 +129,8 @@ function App() {
121129
}
122130

123131
function PostPage({ postId, onBack }: { postId: number; onBack: () => void }) {
124-
const post = postQuery.useQuery({ params: { id: postId } });
132+
const post = postQuery.useQuery({ id: postId });
125133
const comments = commentsQuery.useQuery({ search: { postId: postId } });
126-
127134
const [showEdit, setShowEdit] = useState(false);
128135
const editPost = editPostMutation.useMutation();
129136

@@ -140,7 +147,7 @@ function PostPage({ postId, onBack }: { postId: number; onBack: () => void }) {
140147
post.error.message
141148
) : (
142149
<div>
143-
<h2>{post.data?.title}</h2>
150+
<h2>{post.data?.titleUppercase}</h2>
144151
<p>{post.data?.body}</p>
145152
<button onClick={onBack}>Back</button>
146153
<button onClick={() => setShowEdit(true)} disabled={editPost.isPending}>

src/builder/HttpQueryBuilder.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@ import { WithOptional } from '../types/utils';
33
import { HttpMutationBuilder } from './HttpMutationBuilder';
44
import { MutationBuilder } from './MutationBuilder';
55
import { QueryBuilder, QueryBuilderConfig } from './QueryBuilder';
6+
import { MiddlewareFn } from './middlewares';
67
import {
78
HttpBaseHeaders,
89
HttpBaseParams,
910
HttpBaseSearch,
1011
HttpBuilderTypeTemplate,
12+
SetAllTypes,
1113
SetDataType,
1214
SetErrorType,
1315
} from './types';
@@ -66,5 +68,8 @@ export class HttpQueryBuilder<T extends HttpBuilderTypeTemplate = HttpBuilderTyp
6668
reset?: TReset,
6769
) => HttpQueryBuilder<AppendVarsType<T, Partial<TVars>, TReset>>;
6870
declare asMutationBuilder: () => HttpMutationBuilder<T>;
71+
declare withMiddleware: <TVars = T['vars'], TData = T['data'], TError = T['error']>(
72+
middleware: MiddlewareFn<TVars, TData, TError, T>,
73+
) => HttpQueryBuilder<SetAllTypes<T, TData, TError, TVars, true>>;
6974
protected override MutationBuilderConstructor: typeof MutationBuilder = HttpMutationBuilder as typeof MutationBuilder;
7075
}

src/builder/QueryBuilder.ts

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,15 @@ import { mergeTagOptions } from '../tags/mergeTagOptions';
2020
import { QueryTagOption } from '../tags/types';
2121
import { FunctionType } from '../types/utils';
2222
import { MutationBuilder } from './MutationBuilder';
23-
import { BuilderMergeVarsFn, BuilderQueriesResult, BuilderQueryFn, SetDataType, SetErrorType } from './types';
23+
import { MiddlewareFn, applyMiddleware } from './middlewares';
24+
import {
25+
BuilderMergeVarsFn,
26+
BuilderQueriesResult,
27+
BuilderQueryFn,
28+
SetAllTypes,
29+
SetDataType,
30+
SetErrorType,
31+
} from './types';
2432
import { AppendVarsType, BuilderTypeTemplate } from './types';
2533
import { mergeQueryOptions, mergeVars } from './utils';
2634

@@ -207,6 +215,14 @@ export class QueryBuilder<T extends BuilderTypeTemplate = BuilderTypeTemplate> e
207215
return new ctor<T>(newConfig) as this;
208216
}
209217

218+
withMiddleware<TVars = T['vars'], TData = T['data'], TError = T['error']>(
219+
middleware: MiddlewareFn<TVars, TData, TError, T>,
220+
): QueryBuilder<SetAllTypes<T, TData, TError, TVars, true>> {
221+
const newBuilder = this as unknown as QueryBuilder<SetAllTypes<T, TData, TError, TVars, true>>;
222+
223+
return newBuilder.withConfig({ queryFn: applyMiddleware(this.config.queryFn, middleware) });
224+
}
225+
210226
freeze(): QueryBuilderFrozen<T> {
211227
return this;
212228
}

src/builder/middlewares.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { QueryFunction, QueryFunctionContext } from '@tanstack/react-query';
2+
import { BuilderQueryFn, BuilderTypeTemplate } from './types';
3+
4+
export type MiddlewareFn<TVars, TData, TError, TOriginalTemplate extends BuilderTypeTemplate> = (
5+
context: MiddlewareContext<TVars>,
6+
next: MiddlewareNextFn<TOriginalTemplate>,
7+
throwError: (error: TError) => never,
8+
) => TData | Promise<TData>;
9+
10+
export type MiddlewareContext<TVars> = QueryFunctionContext<[TVars]> & {
11+
vars: TVars;
12+
};
13+
14+
export type MiddlewareNextFn<T extends BuilderTypeTemplate> = (
15+
context: Omit<MiddlewareContext<T['vars']>, 'queryKey'>,
16+
) => Promise<T['data']>;
17+
18+
export const createMiddlewareContext = <TVars>(context: QueryFunctionContext<[TVars]>): MiddlewareContext<TVars> => {
19+
return {
20+
...context,
21+
vars: context.queryKey[0],
22+
};
23+
};
24+
25+
const throwError = (error: any): never => {
26+
throw error;
27+
};
28+
29+
export const applyMiddleware = <TVars, TData, TError, TOriginalTemplate extends BuilderTypeTemplate>(
30+
originalFn: BuilderQueryFn<TOriginalTemplate>,
31+
middleware: MiddlewareFn<TVars, TData, TError, TOriginalTemplate>,
32+
): QueryFunction<TData, [TVars]> => {
33+
return async (context) =>
34+
middleware(
35+
createMiddlewareContext<TVars>(context),
36+
async (ctx) => originalFn({ ...context, queryKey: [ctx.vars] }),
37+
throwError,
38+
);
39+
};

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ export * from './builder/QueryBuilder';
22
export * from './builder/HttpQueryBuilder';
33
export * from './builder/MutationBuilder';
44
export * from './builder/HttpMutationBuilder';
5+
export type { MiddlewareFn, MiddlewareContext, MiddlewareNextFn } from './builder/middlewares';
56
export * from './http/request';
67
export * from './tags/cache';
78
export * from './tags/useOperateOnTags';

0 commit comments

Comments
 (0)