Skip to content

Commit 25e007f

Browse files
committed
add strong typed tags
1 parent ce35e8b commit 25e007f

16 files changed

+205
-127
lines changed

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ It uses the builder pattern, the best pattern that works with complex Typescript
3232

3333
## TODO
3434

35-
- Strong typed customizable tags
3635
- Infinite queries
3736
- Single interface for mutations and queries
3837

examples/vite/src/App.tsx

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

88
const baseQuery = new HttpQueryBuilder({
99
queryClient,
1010
syncChannel: new BroadcastChannel('react-query-builder'),
11-
}).withBaseUrl(baseUrl);
12-
const baseMutation = new HttpMutationBuilder({
13-
queryClient,
14-
syncChannel: new BroadcastChannel('react-query-builder'),
15-
}).withBaseUrl(baseUrl);
11+
})
12+
.withBaseUrl(baseUrl)
13+
.withTagTypes<{
14+
post: PostData;
15+
posts: PostData[];
16+
comments: CommentData;
17+
refreshable: unknown;
18+
}>();
19+
20+
const baseMutation = baseQuery.asMutationBuilder();
1621

1722
const resetMutation = baseMutation.withPath('/reset').withUpdates('*');
1823

19-
const postsQuery = baseQuery
20-
.withTags('refreshable', { type: 'posts' as any, id: 'LIST' })
21-
.withPath('/posts')
22-
.withData<PostData[]>();
24+
const postsQuery = baseQuery.withTags('refreshable', 'posts').withPath('/posts').withData<PostData[]>();
2325

2426
const postQuery = baseQuery
2527
.withTags('refreshable')
2628
.withPath('/posts/:id')
2729
.withData<PostData>()
28-
.withTags((ctx) => ({ type: 'posts' as any, id: ctx.data.id }))
30+
.withTags((ctx) => ({ type: 'post', id: ctx.data.id }))
2931
.withPreprocessor<{ id: number }>((vars) => ({ ...vars, params: { id: vars.id } }))
3032
.withMiddleware(async (ctx, next) => {
3133
const res = await next(ctx);
@@ -42,15 +44,14 @@ const editPostMutation = baseMutation
4244
.withPath('/posts/:id')
4345
.withMethod('put')
4446
.withBody<Partial<PostData>>()
45-
.withUpdates<PostData | PostData[]>(
47+
.withUpdates(
4648
{
47-
type: 'posts' as any,
48-
id: 'LIST',
49+
type: 'posts',
4950
optimistic: true,
5051
updater: 'update-body-by-id',
5152
},
5253
(ctx) => ({
53-
type: 'posts' as any,
54+
type: 'post',
5455
id: ctx.vars.params.id,
5556
optimistic: true,
5657
updater: 'merge-body',
@@ -67,15 +68,11 @@ const editPostMutation = baseMutation
6768
return res;
6869
});
6970

70-
const deletePostMutation = baseMutation
71-
.withMethod('delete')
72-
.withPath('/posts/:id')
73-
.withUpdates<PostData[]>({
74-
type: 'posts' as any,
75-
id: 'LIST',
76-
optimistic: true,
77-
updater: 'delete-params-by-id',
78-
});
71+
const deletePostMutation = baseMutation.withMethod('delete').withPath('/posts/:id').withUpdates({
72+
type: 'posts',
73+
optimistic: true,
74+
updater: 'delete-params-by-id',
75+
});
7976

8077
function App() {
8178
const [postId, setPostId] = useState<number | null>(null);

src/builder/HttpMutationBuilder.ts

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,9 @@ export class HttpMutationBuilder<
1515
TMeta = unknown,
1616
TData = unknown,
1717
TError = RequestError,
18+
TTags extends Record<string, unknown> = Record<string, unknown>,
1819
TKey extends [HttpBuilderVars] = [HttpBuilderVars<TParam, TSearch, TBody, THeader, TMeta>],
19-
> extends MutationBuilder<HttpBuilderVars<TParam, TSearch, TBody, THeader, TMeta>, TData, TError, TKey> {
20-
protected declare _vars: HttpBuilderVars<TParam, TSearch, TBody, THeader, TMeta>;
21-
20+
> extends MutationBuilder<HttpBuilderVars<TParam, TSearch, TBody, THeader, TMeta>, TData, TError, TKey, TTags> {
2221
constructor(
2322
config?: WithOptional<MutationBuilderConfig<HttpBuilderVars<TParam, TSearch, TBody, THeader, TMeta>, TData, TError, TKey>, 'queryFn'>,
2423
) {
@@ -38,33 +37,33 @@ export class HttpMutationBuilder<
3837
});
3938
}
4039

41-
withBody<TBody$>(body?: TBody$): HttpMutationBuilder<TParam, TSearch, TBody$, THeader, TMeta, TData, TError> {
40+
withBody<TBody$>(body?: TBody$): HttpMutationBuilder<TParam, TSearch, TBody$, THeader, TMeta, TData, TError, TTags> {
4241
if (!body) return this as any;
4342
return this.withVars({ body }) as any;
4443
}
4544

4645
withHeaders<THeaders$ extends HttpBaseHeaders>(
4746
headers?: THeaders$,
48-
): HttpMutationBuilder<TParam, TSearch, TBody, THeaders$, TMeta, TData, TError> {
47+
): HttpMutationBuilder<TParam, TSearch, TBody, THeaders$, TMeta, TData, TError, TTags> {
4948
if (!headers) return this as any;
5049
return this.withVars({ headers }) as any;
5150
}
5251

5352
withParams<TParams$ extends HttpBaseParams>(
5453
params?: TParams$,
55-
): HttpMutationBuilder<TParams$, TSearch, TBody, THeader, TMeta, TData, TError> {
54+
): HttpMutationBuilder<TParams$, TSearch, TBody, THeader, TMeta, TData, TError, TTags> {
5655
if (!params) return this as any;
5756
return this.withVars({ params }) as any;
5857
}
5958

6059
withSearch<TSearch$ extends HttpBaseSearch>(
6160
search?: TSearch$,
62-
): HttpMutationBuilder<TParam, TSearch$, TBody, THeader, TMeta, TData, TError> {
61+
): HttpMutationBuilder<TParam, TSearch$, TBody, THeader, TMeta, TData, TError, TTags> {
6362
if (!search) return this as any;
6463
return this.withVars({ search }) as any;
6564
}
6665

67-
withMeta<TMeta$>(meta?: TMeta$): HttpMutationBuilder<TParam, TSearch, TBody, THeader, TMeta$, TData, TError> {
66+
withMeta<TMeta$>(meta?: TMeta$): HttpMutationBuilder<TParam, TSearch, TBody, THeader, TMeta$, TData, TError, TTags> {
6867
if (!meta) return this as any;
6968
return this.withVars({ meta }) as any;
7069
}
@@ -73,7 +72,7 @@ export class HttpMutationBuilder<
7372
path: TPath$,
7473
): ExtractPathParams<TPath$> extends void
7574
? this
76-
: HttpMutationBuilder<ExtractPathParams<TPath$>, TSearch, TBody, THeader, TMeta, TData, TError> {
75+
: HttpMutationBuilder<ExtractPathParams<TPath$>, TSearch, TBody, THeader, TMeta, TData, TError, TTags> {
7776
return this.withVars({ path }) as any;
7877
}
7978

@@ -85,6 +84,32 @@ export class HttpMutationBuilder<
8584
return this.withVars({ method }) as any;
8685
}
8786

88-
declare withData: <TData$>() => HttpMutationBuilder<TParam, TSearch, TBody, THeader, TMeta, TData$, TError, TKey>;
89-
declare withError: <TError$>() => HttpMutationBuilder<TParam, TSearch, TBody, THeader, TMeta, TData, TError$, TKey>;
87+
declare withData: <TData$>() => HttpMutationBuilder<TParam, TSearch, TBody, THeader, TMeta, TData$, TError, TTags, TKey>;
88+
declare withError: <TError$>() => HttpMutationBuilder<TParam, TSearch, TBody, THeader, TMeta, TData, TError$, TTags, TKey>;
89+
90+
withTagTypes<TTag extends string, T = unknown>(): HttpMutationBuilder<
91+
TParam,
92+
TSearch,
93+
TBody,
94+
THeader,
95+
TMeta,
96+
TData,
97+
TError,
98+
TTags & Record<TTag, T>,
99+
TKey
100+
>;
101+
withTagTypes<TTags$ extends Record<string, unknown>>(): HttpMutationBuilder<
102+
TParam,
103+
TSearch,
104+
TBody,
105+
THeader,
106+
TMeta,
107+
TData,
108+
TError,
109+
TTags$,
110+
TKey
111+
>;
112+
withTagTypes(): this {
113+
return this as any;
114+
}
90115
}

src/builder/HttpQueryBuilder.ts

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { RequestError } from '../http/errors';
22
import { ExtractPathParams, HttpMethod } from '../http/types';
33
import { WithOptional } from '../types/utils';
4+
import { HttpMutationBuilder } from './HttpMutationBuilder';
5+
import { MutationBuilder } from './MutationBuilder';
46
import { QueryBuilder } from './QueryBuilder';
57
import { QueryBuilderConfig } from './QueryBuilderFrozen';
68
import { HttpBaseHeaders, HttpBaseParams, HttpBaseSearch, HttpBuilderVars } from './types';
@@ -14,10 +16,9 @@ export class HttpQueryBuilder<
1416
TMeta = unknown,
1517
TData = unknown,
1618
TError = RequestError,
19+
TTags extends Record<string, unknown> = Record<string, unknown>,
1720
TKey extends [HttpBuilderVars] = [HttpBuilderVars<TParam, TSearch, TBody, THeader, TMeta>],
18-
> extends QueryBuilder<HttpBuilderVars<TParam, TSearch, TBody, THeader, TMeta>, TData, TError, TKey> {
19-
protected declare _vars: HttpBuilderVars<TParam, TSearch, TBody, THeader, TMeta>;
20-
21+
> extends QueryBuilder<HttpBuilderVars<TParam, TSearch, TBody, THeader, TMeta>, TData, TError, TKey, TTags> {
2122
constructor(
2223
config?: WithOptional<QueryBuilderConfig<HttpBuilderVars<TParam, TSearch, TBody, THeader, TMeta>, TData, TError, TKey>, 'queryFn'>,
2324
) {
@@ -27,33 +28,33 @@ export class HttpQueryBuilder<
2728
super({ mergeVars, queryFn, queryKeyHashFn, ...config });
2829
}
2930

30-
withBody<TBody$>(body?: TBody$): HttpQueryBuilder<TParam, TSearch, TBody$, THeader, TMeta, TData, TError, TKey> {
31+
withBody<TBody$>(body?: TBody$): HttpQueryBuilder<TParam, TSearch, TBody$, THeader, TMeta, TData, TError, TTags> {
3132
if (!body) return this as any;
3233
return this.withVars({ body }) as any;
3334
}
3435

3536
withHeaders<THeaders$ extends HttpBaseHeaders>(
3637
headers?: THeaders$,
37-
): HttpQueryBuilder<TParam, TSearch, TBody, THeaders$, TMeta, TData, TError, TKey> {
38+
): HttpQueryBuilder<TParam, TSearch, TBody, THeaders$, TMeta, TData, TError, TTags> {
3839
if (!headers) return this as any;
3940
return this.withVars({ headers }) as any;
4041
}
4142

4243
withParams<TParams$ extends HttpBaseParams>(
4344
params?: TParams$,
44-
): HttpQueryBuilder<TParams$, TSearch, TBody, THeader, TMeta, TData, TError, TKey> {
45+
): HttpQueryBuilder<TParams$, TSearch, TBody, THeader, TMeta, TData, TError, TTags> {
4546
if (!params) return this as any;
4647
return this.withVars({ params }) as any;
4748
}
4849

4950
withSearch<TSearch$ extends HttpBaseSearch>(
5051
search?: TSearch$,
51-
): HttpQueryBuilder<TParam, TSearch$, TBody, THeader, TMeta, TData, TError, TKey> {
52+
): HttpQueryBuilder<TParam, TSearch$, TBody, THeader, TMeta, TData, TError, TTags> {
5253
if (!search) return this as any;
5354
return this.withVars({ search }) as any;
5455
}
5556

56-
withMeta<TMeta$>(meta?: TMeta$): HttpQueryBuilder<TParam, TSearch, TBody, THeader, TMeta$, TData, TError, TKey> {
57+
withMeta<TMeta$>(meta?: TMeta$): HttpQueryBuilder<TParam, TSearch, TBody, THeader, TMeta$, TData, TError, TTags> {
5758
if (!meta) return this as any;
5859
return this.withVars({ meta }) as any;
5960
}
@@ -62,7 +63,7 @@ export class HttpQueryBuilder<
6263
path: TPath$,
6364
): ExtractPathParams<TPath$> extends void
6465
? this
65-
: HttpQueryBuilder<ExtractPathParams<TPath$>, TSearch, TBody, THeader, TMeta, TData, TError, TKey> {
66+
: HttpQueryBuilder<ExtractPathParams<TPath$>, TSearch, TBody, THeader, TMeta, TData, TError, TTags> {
6667
return this.withVars({ path }) as any;
6768
}
6869

@@ -74,6 +75,39 @@ export class HttpQueryBuilder<
7475
return this.withVars({ method }) as any;
7576
}
7677

77-
declare withData: <TData$>() => HttpQueryBuilder<TParam, TSearch, TBody, THeader, TMeta, TData$, TError, TKey>;
78-
declare withError: <TError$>() => HttpQueryBuilder<TParam, TSearch, TBody, THeader, TMeta, TData, TError$, TKey>;
78+
declare withData: <TData$>() => HttpQueryBuilder<TParam, TSearch, TBody, THeader, TMeta, TData$, TError, TTags, TKey>;
79+
declare withError: <TError$>() => HttpQueryBuilder<TParam, TSearch, TBody, THeader, TMeta, TData, TError$, TTags, TKey>;
80+
81+
withTagTypes<TTag extends string, T = unknown>(): HttpQueryBuilder<
82+
TParam,
83+
TSearch,
84+
TBody,
85+
THeader,
86+
TMeta,
87+
TData,
88+
TError,
89+
TTags & Record<TTag, T>,
90+
TKey
91+
>;
92+
withTagTypes<TTags$ extends Record<string, unknown>>(): HttpQueryBuilder<
93+
TParam,
94+
TSearch,
95+
TBody,
96+
THeader,
97+
TMeta,
98+
TData,
99+
TError,
100+
TTags$,
101+
TKey
102+
>;
103+
withTagTypes(): this {
104+
return this as any;
105+
}
106+
107+
protected MutationBuilderConstructor = HttpMutationBuilder as typeof MutationBuilder;
108+
109+
asMutationBuilder(): HttpMutationBuilder<TParam, TSearch, TBody, THeader, TMeta, TData, TError, TTags, TKey> {
110+
const { options, ...restConfig } = this.config;
111+
return new this.MutationBuilderConstructor(restConfig) as any;
112+
}
79113
}

src/builder/MutationBuilder.ts

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,29 @@ import { MiddlewareFn, createMiddlewareFunction } from './createMiddlewareFuncti
44
import { createUpdateMiddleware } from './createUpdateMiddleware';
55
import { mergeVars } from './utils';
66

7-
export class MutationBuilder<TVars, TData, TError, TKey extends unknown[]> extends MutationBuilderFrozen<TVars, TData, TError, TKey> {
7+
export class MutationBuilder<
8+
TVars,
9+
TData,
10+
TError,
11+
TKey extends unknown[],
12+
TTags extends Record<string, unknown>,
13+
> extends MutationBuilderFrozen<TVars, TData, TError, TKey, TTags> {
814
withVars<TVars$ = TVars, const TReset extends boolean = false>(
915
vars?: TVars$,
1016
resetVars = false as TReset,
11-
): MutationBuilder<TVars$, TData, TError, TKey> {
17+
): MutationBuilder<TVars$, TData, TError, TKey, TTags> {
1218
if (!vars) return this as any;
1319

1420
return this.withConfig({
1521
vars: resetVars ? vars : mergeVars([this.config.vars, vars], this.config.mergeVars),
1622
}) as any;
1723
}
1824

19-
withData<TData$>(): MutationBuilder<TVars, TData$, TError, TKey> {
25+
withData<TData$>(): MutationBuilder<TVars, TData$, TError, TKey, TTags> {
2026
return this as any;
2127
}
2228

23-
withError<TError$>(): MutationBuilder<TVars, TData, TError$, TKey> {
29+
withError<TError$>(): MutationBuilder<TVars, TData, TError$, TKey, TTags> {
2430
return this as any;
2531
}
2632

@@ -32,21 +38,19 @@ export class MutationBuilder<TVars, TData, TError, TKey extends unknown[]> exten
3238

3339
withMiddleware<TVars$ = TVars, TData$ = TData, TError$ = TError>(
3440
middleware: MiddlewareFn<TVars$, TData$, TError$, TKey>,
35-
): MutationBuilder<TVars$, TData$, TError$, TKey> {
36-
const newBuilder = this as unknown as MutationBuilder<TVars$, TData$, TError$, TKey>;
41+
): MutationBuilder<TVars$, TData$, TError$, TKey, TTags> {
42+
const newBuilder = this as unknown as MutationBuilder<TVars$, TData$, TError$, TKey, TTags>;
3743

3844
return newBuilder.withConfig({
3945
queryFn: createMiddlewareFunction(this.config.queryFn, middleware, newBuilder.config),
4046
});
4147
}
4248

43-
withUpdates<TTarget = unknown>(
44-
...tags: QueryTagOption<TVars, TData, TError, QueryUpdateTagObject<TVars, TData, TError, TTarget>>[]
45-
): this {
46-
return this.withMiddleware(createUpdateMiddleware<TVars, TData, TError, TKey>(tags)) as unknown as this;
49+
withUpdates(...tags: QueryTagOption<TVars, TData, TError, QueryUpdateTagObject<TVars, TData, TError, TTags>>[]): this {
50+
return this.withMiddleware(createUpdateMiddleware<TVars, TData, TError, TKey, TTags>(tags)) as unknown as this;
4751
}
4852

49-
freeze(): MutationBuilderFrozen<TVars, TData, TError, TKey> {
53+
freeze(): MutationBuilderFrozen<TVars, TData, TError, TKey, TTags> {
5054
return this;
5155
}
5256
}

src/builder/MutationBuilderFrozen.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export type MutationBuilderConfig<TVars, TData, TError, TKey extends unknown[]>
1919
options?: UseMutationOptions<TData, TError, TVars>;
2020
};
2121

22-
export class MutationBuilderFrozen<TVars, TData, TError, TKey extends unknown[]> {
22+
export class MutationBuilderFrozen<TVars, TData, TError, TKey extends unknown[], TTags extends Record<string, unknown>> {
2323
protected declare _config: MutationBuilderConfig<TVars, TData, TError, TKey>;
2424
protected declare _options: typeof this._config.options;
2525
protected declare _vars: TVars;

0 commit comments

Comments
 (0)