Skip to content

Commit 48a2d91

Browse files
kahirokunnLenz Weber
authored andcommitted
rtk-query-codegen-openapi: If tag is specified and tags exist in a schema, they are now mapped to providesTags or invalidatesTags. (#2071)
* If tags are specified, they are now mapped to providesTags or invalidatesTags. * npm run format * npm run test --updateSnapshot * addTagTypes * Exported addTagTypes so that the list of addTagTypes can be referenced from outside. * The tagging function has been made optional. * add tag test * code style & method naming * docs * addTagTypes does not support injectEndpoints, so it has been moved to enhanceEndpoints. * Fixed a mistake where providesTags and invalidatesTags were written in the wrong place. Co-authored-by: Lenz Weber <lenz.weber@mayflower.de>
1 parent 31af24c commit 48a2d91

File tree

7 files changed

+417
-243
lines changed

7 files changed

+417
-243
lines changed

docs/rtk-query/usage/code-generation.mdx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,15 @@ and then call the code generator:
6262
npx @rtk-query/codegen-openapi openapi-config.ts
6363
```
6464

65+
#### Generating tags
66+
67+
If your OpenAPI specification uses [tags](https://swagger.io/docs/specification/grouping-operations-with-tags/), you can specify the `tag` option to the codegen.
68+
That will result in all generated endpoints having `providesTags`/`invalidatesTags` declarations for the `tags` of their respective operation definition.
69+
70+
Note that this will only result in string tags with no ids, so it might lead to scenarios where too much is invalidated and unneccessary requests are made on mutation.
71+
72+
In that case it is still recommended to manually specify tags by using [`enhanceEndpoints`](./code-splitting.mdx) on top of the generated api and manually declare `providesTags`/`invalidatesTags`.
73+
6574
### Programmatic usage
6675

6776
```ts no-transpile title="src/store/petApi.ts"
@@ -90,6 +99,7 @@ interface SimpleUsage {
9099
hooks?:
91100
| boolean
92101
| { queries: boolean; lazyQueries: boolean; mutations: boolean }
102+
tag?: boolean
93103
outputFile: string
94104
filterEndpoints?:
95105
| string

packages/rtk-query-codegen-openapi/src/codegen.ts

Lines changed: 99 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@ import { factory } from './utils/factory';
33

44
const defaultEndpointBuilder = factory.createIdentifier('build');
55

6-
export type ObjectPropertyDefinitions = Record<string, ts.Expression>;
6+
export type ObjectPropertyDefinitions = Record<string, ts.Expression | undefined>;
77
export function generateObjectProperties(obj: ObjectPropertyDefinitions) {
8-
return Object.entries(obj).map(([k, v]) => factory.createPropertyAssignment(factory.createIdentifier(k), v));
8+
return Object.entries(obj)
9+
.filter(([_, v]) => v)
10+
.map(([k, v]) => factory.createPropertyAssignment(factory.createIdentifier(k), v as ts.Expression));
911
}
1012

1113
export function generateImportNode(pkg: string, namedImports: Record<string, string>, defaultImportName?: string) {
@@ -31,10 +33,69 @@ export function generateImportNode(pkg: string, namedImports: Record<string, str
3133
export function generateCreateApiCall({
3234
endpointBuilder = defaultEndpointBuilder,
3335
endpointDefinitions,
36+
tag,
3437
}: {
3538
endpointBuilder?: ts.Identifier;
3639
endpointDefinitions: ts.ObjectLiteralExpression;
40+
tag: boolean;
3741
}) {
42+
const injectEndpointsObjectLiteralExpression = factory.createObjectLiteralExpression(
43+
generateObjectProperties({
44+
endpoints: factory.createArrowFunction(
45+
undefined,
46+
undefined,
47+
[
48+
factory.createParameterDeclaration(
49+
undefined,
50+
undefined,
51+
undefined,
52+
endpointBuilder,
53+
undefined,
54+
undefined,
55+
undefined
56+
),
57+
],
58+
undefined,
59+
factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
60+
factory.createParenthesizedExpression(endpointDefinitions)
61+
),
62+
overrideExisting: factory.createFalse(),
63+
}),
64+
true
65+
);
66+
if (tag) {
67+
const enhanceEndpointsObjectLiteralExpression = factory.createObjectLiteralExpression(
68+
[factory.createShorthandPropertyAssignment(factory.createIdentifier('addTagTypes'), undefined)],
69+
true
70+
)
71+
return factory.createVariableStatement(
72+
undefined,
73+
factory.createVariableDeclarationList(
74+
[factory.createVariableDeclaration(
75+
factory.createIdentifier("injectedRtkApi"),
76+
undefined,
77+
undefined,
78+
factory.createCallExpression(
79+
factory.createPropertyAccessExpression(
80+
factory.createCallExpression(
81+
factory.createPropertyAccessExpression(
82+
factory.createIdentifier("api"),
83+
factory.createIdentifier("enhanceEndpoints")
84+
),
85+
undefined,
86+
[enhanceEndpointsObjectLiteralExpression]
87+
),
88+
factory.createIdentifier("injectEndpoints")
89+
),
90+
undefined,
91+
[injectEndpointsObjectLiteralExpression]
92+
)
93+
)],
94+
ts.NodeFlags.Const
95+
)
96+
);
97+
}
98+
3899
return factory.createVariableStatement(
39100
undefined,
40101
factory.createVariableDeclarationList(
@@ -49,32 +110,7 @@ export function generateCreateApiCall({
49110
factory.createIdentifier('injectEndpoints')
50111
),
51112
undefined,
52-
[
53-
factory.createObjectLiteralExpression(
54-
generateObjectProperties({
55-
endpoints: factory.createArrowFunction(
56-
undefined,
57-
undefined,
58-
[
59-
factory.createParameterDeclaration(
60-
undefined,
61-
undefined,
62-
undefined,
63-
endpointBuilder,
64-
undefined,
65-
undefined,
66-
undefined
67-
),
68-
],
69-
undefined,
70-
factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken),
71-
factory.createParenthesizedExpression(endpointDefinitions)
72-
),
73-
overrideExisting: factory.createFalse(),
74-
}),
75-
true
76-
),
77-
]
113+
[injectEndpointsObjectLiteralExpression]
78114
)
79115
),
80116
],
@@ -91,6 +127,7 @@ export function generateEndpointDefinition({
91127
queryFn,
92128
endpointBuilder = defaultEndpointBuilder,
93129
extraEndpointsProps,
130+
tags,
94131
}: {
95132
operationName: string;
96133
type: 'query' | 'mutation';
@@ -99,7 +136,17 @@ export function generateEndpointDefinition({
99136
queryFn: ts.Expression;
100137
endpointBuilder?: ts.Identifier;
101138
extraEndpointsProps: ObjectPropertyDefinitions;
139+
tags: string[];
102140
}) {
141+
const objectProperties = generateObjectProperties({ query: queryFn, ...extraEndpointsProps });
142+
if (tags.length > 0) {
143+
objectProperties.push(
144+
factory.createPropertyAssignment(
145+
factory.createIdentifier(type === 'query' ? 'providesTags' : 'invalidatesTags'),
146+
factory.createArrayLiteralExpression(tags.map((tag) => factory.createStringLiteral(tag), false))
147+
)
148+
)
149+
}
103150
return factory.createPropertyAssignment(
104151
factory.createIdentifier(operationName),
105152

@@ -108,10 +155,33 @@ export function generateEndpointDefinition({
108155
[Response, QueryArg],
109156
[
110157
factory.createObjectLiteralExpression(
111-
generateObjectProperties({ query: queryFn, ...extraEndpointsProps }),
158+
objectProperties,
112159
true
113160
),
114161
]
162+
),
163+
);
164+
}
165+
166+
export function generateTagTypes({ addTagTypes }: { addTagTypes: string[] }) {
167+
return factory.createVariableStatement(
168+
[factory.createModifier(ts.SyntaxKind.ExportKeyword)],
169+
factory.createVariableDeclarationList(
170+
[
171+
factory.createVariableDeclaration(
172+
factory.createIdentifier('addTagTypes'),
173+
undefined,
174+
undefined,
175+
factory.createAsExpression(
176+
factory.createArrayLiteralExpression(
177+
addTagTypes.map((tagType) => factory.createStringLiteral(tagType)),
178+
true
179+
),
180+
factory.createTypeReferenceNode(factory.createIdentifier('const'), undefined)
181+
)
182+
),
183+
],
184+
ts.NodeFlags.Const
115185
)
116186
);
117187
}

packages/rtk-query-codegen-openapi/src/generate.ts

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import type { OpenAPIV3 } from 'openapi-types';
1717
import { generateReactHooks } from './generators/react-hooks';
1818
import type { EndpointMatcher, EndpointOverrides, GenerationOptions, OperationDefinition, TextMatcher } from './types';
1919
import { capitalize, getOperationDefinitions, getV3Doc, isQuery as testIsQuery, removeUndefined } from './utils';
20+
import { generateTagTypes } from './codegen';
2021
import type { ObjectPropertyDefinitions } from './codegen';
2122
import { generateCreateApiCall, generateEndpointDefinition, generateImportNode } from './codegen';
2223
import { factory } from './utils/factory';
@@ -32,6 +33,10 @@ function getOperationName({ verb, path, operation }: Pick<OperationDefinition, '
3233
return _getOperationName(verb, path, operation.operationId);
3334
}
3435

36+
function getTags({ verb, pathItem }: Pick<OperationDefinition, 'verb' | 'pathItem'>): string[] {
37+
return verb ? pathItem[verb]?.tags || [] : [];
38+
}
39+
3540
function patternMatches(pattern?: TextMatcher) {
3641
const filters = Array.isArray(pattern) ? pattern : [pattern];
3742
return function matcher(operationName: string) {
@@ -67,6 +72,7 @@ export async function generateApi(
6772
argSuffix = 'ApiArg',
6873
responseSuffix = 'ApiResponse',
6974
hooks = false,
75+
tag = false,
7076
outputFile,
7177
isDataResponse = defaultIsDataResponse,
7278
filterEndpoints,
@@ -116,7 +122,9 @@ export async function generateApi(
116122
factory.createSourceFile(
117123
[
118124
generateImportNode(apiFile, { [apiImport]: 'api' }),
125+
...(tag ? [generateTagTypes({ addTagTypes: extractAllTagTypes({ operationDefinitions }) })] : []),
119126
generateCreateApiCall({
127+
tag,
120128
endpointDefinitions: factory.createObjectLiteralExpression(
121129
operationDefinitions.map((operationDefinition) =>
122130
generateEndpoint({
@@ -160,6 +168,18 @@ export async function generateApi(
160168

161169
return sourceCode;
162170

171+
function extractAllTagTypes({ operationDefinitions }: { operationDefinitions: OperationDefinition[] }) {
172+
let allTagTypes = new Set<string>();
173+
174+
for (const operationDefinition of operationDefinitions) {
175+
const { verb, pathItem } = operationDefinition;
176+
for (const tag of getTags({ verb, pathItem })) {
177+
allTagTypes.add(tag);
178+
}
179+
}
180+
return [...allTagTypes];
181+
}
182+
163183
function generateEndpoint({
164184
operationDefinition,
165185
overrides,
@@ -175,7 +195,7 @@ export async function generateApi(
175195
operation: { responses, requestBody },
176196
} = operationDefinition;
177197
const operationName = getOperationName({ verb, path, operation });
178-
198+
const tags = tag ? getTags({ verb, pathItem }) : [];
179199
const isQuery = testIsQuery(verb, overrides);
180200

181201
const returnsJson = apiGen.getResponseType(responses) === 'json';
@@ -309,21 +329,24 @@ export async function generateApi(
309329
type: isQuery ? 'query' : 'mutation',
310330
Response: ResponseTypeName,
311331
QueryArg,
312-
queryFn: generateQueryFn({ operationDefinition, queryArg, isQuery }),
332+
queryFn: generateQueryFn({ operationDefinition, queryArg, isQuery, tags }),
313333
extraEndpointsProps: isQuery
314334
? generateQueryEndpointProps({ operationDefinition })
315335
: generateMutationEndpointProps({ operationDefinition }),
336+
tags,
316337
});
317338
}
318339

319340
function generateQueryFn({
320341
operationDefinition,
321342
queryArg,
322343
isQuery,
344+
tags,
323345
}: {
324346
operationDefinition: OperationDefinition;
325347
queryArg: QueryArgDefinitions;
326348
isQuery: boolean;
349+
tags: string[];
327350
}) {
328351
const { path, verb } = operationDefinition;
329352

packages/rtk-query-codegen-openapi/src/types.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,14 @@ export interface CommonOptions {
5151
* `true` will generate hooks for queries and mutations, but no lazyQueries
5252
*/
5353
hooks?: boolean | { queries: boolean; lazyQueries: boolean; mutations: boolean };
54-
5554
/**
5655
* defaults to false
5756
*/
5857
unionUndefined?: boolean;
58+
/**
59+
* defaults to false
60+
*/
61+
tag?: boolean;
5962
}
6063

6164
export type TextMatcher = string | RegExp | (string | RegExp)[];

packages/rtk-query-codegen-openapi/src/utils/getOperationDefinitions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { OpenAPIV3 } from 'openapi-types';
2-
import type { OperationDefinition} from '../types';
2+
import type { OperationDefinition } from '../types';
33
import { operationKeys } from '../types';
44

55
export function getOperationDefinitions(v3Doc: OpenAPIV3.Document): OperationDefinition[] {

0 commit comments

Comments
 (0)