Skip to content

Commit 5a86c5d

Browse files
authored
Merge pull request #31 from poviolabs/feature/mutation-effects
Remove invalidate queries & Add mutation effects
2 parents b972a15 + 111f463 commit 5a86c5d

24 files changed

+315
-171
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ yarn openapi-codegen generate --input http://localhost:3001/docs-json --standalo
4949
5050
--axiosRequestConfig Include Axios request config parameters in query hooks (default: false)
5151
--infiniteQueries Generate infinite queries for paginated API endpoints (default: false)
52-
--invalidateQueryOptions Add query invalidation options to mutation hooks (default: true)
52+
--mutationEffects Add mutation effects options to mutation hooks (default: true)
5353
5454
--standalone Generate any missing supporting classes/types, e.g., REST client class, React Query type extensions, etc. (default: false)
5555
--baseUrl (Requires `--standalone`) Base URL for the REST client; falls back to the OpenAPI spec if not provided

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@povio/openapi-codegen-cli",
3-
"version": "0.10.8",
3+
"version": "0.11.0",
44
"main": "./dist/index.js",
55
"bin": {
66
"openapi-codegen": "./dist/sh.js"
@@ -18,7 +18,8 @@
1818
"lint": "eslint --fix",
1919
"format:check": "prettier --check .",
2020
"format:fix": "prettier --write .",
21-
"push": "yarn exec ./scripts/publish.sh"
21+
"push": "yarn exec ./scripts/publish.sh",
22+
"dev:generate": "rm -rf ./output && yarn start generate --input http://localhost:4000/docs-json"
2223
},
2324
"files": [
2425
"dist/*",

src/assets/queryConfig.context.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { createContext, use, useMemo } from "react";
2+
3+
export namespace QueryConfig {
4+
interface Type {
5+
preferUpdate?: boolean;
6+
}
7+
8+
const Context = createContext<Type>({});
9+
10+
type ProviderProps = Type;
11+
12+
export const Provider = ({ preferUpdate, children }: React.PropsWithChildren<ProviderProps>) => {
13+
const value = useMemo(() => ({ preferUpdate }), [preferUpdate]);
14+
15+
return <Context.Provider value={value}>{children}</Context.Provider>;
16+
};
17+
18+
export const useConfig = () => {
19+
const context = use(Context);
20+
return context ?? {};
21+
};
22+
}

src/assets/useMutationEffects.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { QueryKey, useQueryClient } from "@tanstack/react-query";
2+
import { useCallback } from "react";
3+
4+
import { QueryConfig } from "./queryConfig.context";
5+
import { QueryModule } from "./queryModules";
6+
7+
export interface MutationEffectsOptions {
8+
invalidateCurrentModule?: boolean;
9+
invalidateModules?: QueryModule[];
10+
invalidateKeys?: QueryKey[];
11+
preferUpdate?: boolean;
12+
}
13+
14+
export interface UseMutationEffectsOptions {
15+
currentModule: QueryModule;
16+
}
17+
18+
export function useMutationEffects({ currentModule }: UseMutationEffectsOptions) {
19+
const queryClient = useQueryClient();
20+
const config = QueryConfig.useConfig();
21+
22+
const runMutationEffects = useCallback(
23+
async <TData>(data: TData, options: MutationEffectsOptions = {}, updateKeys?: QueryKey[]) => {
24+
const { invalidateCurrentModule, invalidateModules, invalidateKeys, preferUpdate } = options;
25+
const shouldUpdate = preferUpdate || (preferUpdate === undefined && config.preferUpdate);
26+
27+
const isQueryKeyEqual = (keyA: QueryKey, keyB: QueryKey) =>
28+
keyA.length === keyB.length && keyA.every((item, index) => item === keyB[index]);
29+
30+
queryClient.invalidateQueries({
31+
predicate: ({ queryKey }) => {
32+
const isUpdateKey = updateKeys?.some((key) => isQueryKeyEqual(queryKey, key));
33+
if (shouldUpdate && isUpdateKey) {
34+
return false;
35+
}
36+
37+
const isCurrentModule = invalidateCurrentModule && queryKey[0] === currentModule;
38+
const isInvalidateModule = !!invalidateModules && invalidateModules.some((module) => queryKey[0] === module);
39+
const isInvalidateKey = !!invalidateKeys && invalidateKeys.some((key) => isQueryKeyEqual(queryKey, key));
40+
return isCurrentModule || isInvalidateModule || isInvalidateKey;
41+
},
42+
});
43+
44+
if (shouldUpdate && updateKeys) {
45+
updateKeys.map((queryKey) => queryClient.setQueryData(queryKey, data));
46+
}
47+
},
48+
[queryClient, currentModule, config.preferUpdate],
49+
);
50+
51+
return { runMutationEffects };
52+
}

src/commands/generate.command.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,11 @@ class GenerateOptions implements GenerateParams {
6363
infiniteQueries!: boolean;
6464

6565
@YargOption({
66-
envAlias: "invalidateQueryOptions",
67-
default: DEFAULT_GENERATE_OPTIONS.invalidateQueryOptions,
66+
envAlias: "mutationEffects",
67+
default: DEFAULT_GENERATE_OPTIONS.mutationEffects,
6868
type: "boolean",
6969
})
70-
invalidateQueryOptions!: boolean;
70+
mutationEffects!: boolean;
7171

7272
@YargOption({ envAlias: "axiosRequestConfig", default: DEFAULT_GENERATE_OPTIONS.axiosRequestConfig, type: "boolean" })
7373
axiosRequestConfig!: boolean;

src/commands/generate.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ export type GenerateParams = {
2727
| "replaceOptionalWithNullish"
2828
| "infiniteQueries"
2929
| "axiosRequestConfig"
30-
| "invalidateQueryOptions"
30+
| "mutationEffects"
3131
>;
3232

3333
export async function generate({ input, excludeTags, monorepo, prettier, verbose, ...params }: GenerateParams) {

src/generators/const/deps.const.ts

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,20 @@ export const STANDALONE_ASSETS: Record<StandaloneAssetEnum, GenerateFile> = {
5050

5151
export const STANDALONE_APP_REST_CLIENT_FILE: GenerateFile = { fileName: "app-rest-client", extension: "ts" };
5252

53-
// InvalidateQueryOptions
53+
// QueryModules
5454
export const QUERY_MODULE_ENUM = "QueryModule";
55-
export const INVALIDATE_QUERIES = {
56-
queryModuleEnum: QUERY_MODULE_ENUM,
57-
optionsType: "InvalidateQueryOptions",
58-
functionName: "invalidateQueries",
55+
export const QUERY_MODULES_FILE: GenerateFile = { fileName: "queryModules", extension: "ts" };
56+
57+
// QueryConfig
58+
export const QUERY_CONFIG_FILE: GenerateFile = { fileName: "queryConfig.context", extension: "tsx" };
59+
60+
// MutationEffects
61+
export const MUTATION_EFFECTS = {
62+
optionsType: "MutationEffectsOptions",
63+
hookName: "useMutationEffects",
64+
runFunctionName: "runMutationEffects",
5965
};
60-
export const INVALIDATE_QUERY_OPTIONS_FILE: GenerateFile = { fileName: "invalidateQueries", extension: "ts" };
66+
export const MUTATION_EFFECTS_FILE: GenerateFile = { fileName: "useMutationEffects", extension: "ts" };
6167

6268
// ZodExtended
6369
export const ZOD_EXTENDED = {

src/generators/const/options.const.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,5 +48,5 @@ export const DEFAULT_GENERATE_OPTIONS: GenerateOptions = {
4848
queryTypesImportPath: TEMPLATE_IMPORTS.queryTypes.template,
4949
axiosRequestConfig: false,
5050
infiniteQueries: false,
51-
invalidateQueryOptions: true,
51+
mutationEffects: true,
5252
};

src/generators/const/queries.const.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@ export const QUERY_HOOKS = {
44
query: "useQuery",
55
infiniteQuery: "useInfiniteQuery",
66
mutation: "useMutation",
7-
queryClient: "useQueryClient",
87
};
98
export const QUERY_IMPORT: Import = {
10-
bindings: [QUERY_HOOKS.query, QUERY_HOOKS.infiniteQuery, QUERY_HOOKS.mutation, QUERY_HOOKS.queryClient],
9+
bindings: [QUERY_HOOKS.query, QUERY_HOOKS.infiniteQuery, QUERY_HOOKS.mutation],
1110
from: "@tanstack/react-query",
1211
};
1312

src/generators/generate/generateQueries.ts

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
1-
import { INVALIDATE_QUERIES, QUERY_OPTIONS_TYPES } from "../const/deps.const";
1+
import { MUTATION_EFFECTS, QUERY_MODULE_ENUM, QUERY_OPTIONS_TYPES } from "../const/deps.const";
22
import { AXIOS_DEFAULT_IMPORT_NAME, AXIOS_IMPORT, AXIOS_REQUEST_CONFIG_TYPE } from "../const/endpoints.const";
33
import { QUERIES_MODULE_NAME, QUERY_HOOKS, QUERY_IMPORT } from "../const/queries.const";
44
import { EndpointParameter } from "../types/endpoint";
55
import { GenerateType, GenerateTypeParams, Import } from "../types/generate";
66
import { getUniqueArray } from "../utils/array.utils";
77
import { getEndpointsImports, getModelsImports } from "../utils/generate/generate.imports.utils";
88
import {
9-
getInvalidateQueriesImportPath,
9+
getMutationEffectsImportPath,
1010
getNamespaceName,
11+
getQueryModulesImportPath,
1112
getQueryTypesImportPath,
1213
} from "../utils/generate/generate.utils";
1314
import { getHbsTemplateDelegate } from "../utils/hbs/hbs-template.utils";
@@ -37,17 +38,21 @@ export function generateQueries({ resolver, data, tag = "" }: GenerateTypeParams
3738
bindings: [
3839
...(queryEndpoints.length > 0 ? [QUERY_HOOKS.query] : []),
3940
...(resolver.options.infiniteQueries && infiniteQueryEndpoints.length > 0 ? [QUERY_HOOKS.infiniteQuery] : []),
40-
...(mutationEndpoints.length > 0 ? [QUERY_HOOKS.mutation, QUERY_HOOKS.queryClient] : []),
41+
...(mutationEndpoints.length > 0 ? [QUERY_HOOKS.mutation] : []),
4142
],
4243
from: QUERY_IMPORT.from,
4344
};
4445

45-
const invalidateQueriesImport: Import = {
46-
bindings: [
47-
INVALIDATE_QUERIES.queryModuleEnum,
48-
...(mutationEndpoints.length > 0 ? [INVALIDATE_QUERIES.optionsType, INVALIDATE_QUERIES.functionName] : []),
49-
],
50-
from: getInvalidateQueriesImportPath(resolver.options),
46+
const hasMutationEffects = resolver.options.mutationEffects;
47+
const queryModulesImport: Import = {
48+
bindings: [QUERY_MODULE_ENUM],
49+
from: getQueryModulesImportPath(resolver.options),
50+
};
51+
52+
const hasMutationEffectsImport = hasMutationEffects && mutationEndpoints.length > 0;
53+
const mutationEffectsImport: Import = {
54+
bindings: [...(mutationEndpoints.length > 0 ? [MUTATION_EFFECTS.optionsType, MUTATION_EFFECTS.hookName] : [])],
55+
from: getMutationEffectsImportPath(resolver.options),
5156
};
5257

5358
const queryTypesImport: Import = {
@@ -80,16 +85,18 @@ export function generateQueries({ resolver, data, tag = "" }: GenerateTypeParams
8085
hasAxiosImport,
8186
axiosImport,
8287
queryImport,
83-
hasInvalidateQueryOptions: resolver.options.invalidateQueryOptions,
84-
invalidateQueriesImport,
88+
hasMutationEffects,
89+
queryModulesImport,
90+
hasMutationEffectsImport,
91+
mutationEffectsImport,
8592
queryTypesImport,
8693
modelsImports,
8794
endpointsImports,
8895
includeNamespace: resolver.options.tsNamespaces,
8996
tag,
9097
namespace: getNamespaceName({ type: GenerateType.Queries, tag, options: resolver.options }),
9198
queriesModuleName: QUERIES_MODULE_NAME,
92-
queryModuleEnum: INVALIDATE_QUERIES.queryModuleEnum,
99+
queryModuleEnum: QUERY_MODULE_ENUM,
93100
endpoints,
94101
queryEndpoints,
95102
});

0 commit comments

Comments
 (0)