@7nohe/openapi-react-query-codegen 0.5.3 → 1.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +60 -22
- package/dist/cli.mjs +35 -0
- package/dist/common.mjs +64 -0
- package/dist/constants.mjs +3 -0
- package/dist/createExports.mjs +40 -0
- package/dist/createImports.mjs +47 -0
- package/dist/createSource.mjs +74 -0
- package/dist/createUseMutation.mjs +98 -0
- package/dist/createUseQuery.mjs +187 -0
- package/dist/generate.mjs +43 -0
- package/dist/print.mjs +23 -0
- package/dist/service.mjs +94 -0
- package/dist/{src/util.js → util.mjs} +3 -10
- package/package.json +17 -6
- package/dist/node_modules/.bin/glob +0 -17
- package/dist/node_modules/.bin/openapi +0 -17
- package/dist/node_modules/.bin/tsc +0 -17
- package/dist/node_modules/.bin/tsserver +0 -17
- package/dist/package.json +0 -47
- package/dist/src/cli.js +0 -27
- package/dist/src/common.js +0 -11
- package/dist/src/constants.js +0 -6
- package/dist/src/createExports.js +0 -61
- package/dist/src/createImports.js +0 -37
- package/dist/src/createSource.js +0 -25
- package/dist/src/createUseMutation.js +0 -66
- package/dist/src/createUseQuery.js +0 -90
- package/dist/src/generate.js +0 -22
- package/dist/src/print.js +0 -24
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
|
|
7
7
|
- Supports generation of custom react hooks that use React Query's `useQuery` and `useMutation` hooks
|
|
8
8
|
- Supports generation of query keys for query caching
|
|
9
|
-
- Supports the option to use pure TypeScript clients generated by [
|
|
9
|
+
- Supports the option to use pure TypeScript clients generated by [@hey-api/openapi-ts](https://github.com/hey-api/openapi-ts)
|
|
10
10
|
|
|
11
11
|
## Install
|
|
12
12
|
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
$ npm install -D @7nohe/openapi-react-query-codegen
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
Register the command to the `scripts` property in your package.json file.
|
|
18
18
|
|
|
19
19
|
```json
|
|
20
20
|
{
|
|
@@ -30,7 +30,6 @@ You can also run the command without installing it in your project using the npx
|
|
|
30
30
|
$ npx --package @7nohe/openapi-react-query-codegen openapi-rq -i ./petstore.yaml -c axios
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
|
|
34
33
|
## Usage
|
|
35
34
|
|
|
36
35
|
```
|
|
@@ -45,12 +44,14 @@ Options:
|
|
|
45
44
|
-i, --input <value> OpenAPI specification, can be a path, url or string content (required)
|
|
46
45
|
-o, --output <value> Output directory (default: "openapi")
|
|
47
46
|
-c, --client <value> HTTP client to generate [fetch, xhr, node, axios, angular] (default: "fetch")
|
|
48
|
-
--useUnionTypes Use union types (default: false)
|
|
49
|
-
--exportSchemas <value> Write schemas to disk (default: false)
|
|
50
|
-
--indent <value> Indentation options [4, 2, tabs] (default: "4")
|
|
51
|
-
--postfixServices <value> Service name postfix (default: "Service")
|
|
52
|
-
--postfixModels <value> Modal name postfix
|
|
53
47
|
--request <value> Path to custom request file
|
|
48
|
+
--useDateType Use Date type instead of string for date types for models, this will not convert the data to a Date object
|
|
49
|
+
--enums Generate JavaScript objects from enum definitions?
|
|
50
|
+
--base <value> Manually set base in OpenAPI config instead of inferring from server value
|
|
51
|
+
--serviceResponse <value> Define shape of returned value from service calls ['body', 'generics', 'response']
|
|
52
|
+
--operationId Use operation ID to generate operation names?
|
|
53
|
+
--lint Process output folder with linter?
|
|
54
|
+
--format Process output folder with formatter?
|
|
54
55
|
-h, --help display help for command
|
|
55
56
|
```
|
|
56
57
|
|
|
@@ -67,17 +68,18 @@ $ openapi-rq -i ./petstore.yaml
|
|
|
67
68
|
```
|
|
68
69
|
- openapi
|
|
69
70
|
- queries
|
|
70
|
-
- index.ts <-
|
|
71
|
-
|
|
71
|
+
- index.ts <- main file that exports common types, variables, and hooks
|
|
72
|
+
- common.ts <- common types
|
|
73
|
+
- queries.ts <- generated query hooks
|
|
74
|
+
- suspenses.ts <- generated suspense hooks
|
|
75
|
+
- requests <- output code generated by @hey-api/openapi-ts
|
|
72
76
|
```
|
|
73
77
|
|
|
74
78
|
### In your app
|
|
75
79
|
|
|
76
80
|
```tsx
|
|
77
81
|
// App.tsx
|
|
78
|
-
import {
|
|
79
|
-
usePetServiceFindPetsByStatus,
|
|
80
|
-
} from "../openapi/queries";
|
|
82
|
+
import { usePetServiceFindPetsByStatus } from "../openapi/queries";
|
|
81
83
|
function App() {
|
|
82
84
|
const { data } = usePetServiceFindPetsByStatus({ status: ["available"] });
|
|
83
85
|
|
|
@@ -100,22 +102,57 @@ You can also use pure TS clients.
|
|
|
100
102
|
|
|
101
103
|
```tsx
|
|
102
104
|
import { useQuery } from "@tanstack/react-query";
|
|
103
|
-
import { PetService } from
|
|
104
|
-
import {
|
|
105
|
-
usePetServiceFindPetsByStatusKey,
|
|
106
|
-
} from "../openapi/queries";
|
|
105
|
+
import { PetService } from "../openapi/requests/services";
|
|
106
|
+
import { usePetServiceFindPetsByStatusKey } from "../openapi/queries";
|
|
107
107
|
|
|
108
108
|
function App() {
|
|
109
109
|
// You can still use the auto-generated query key
|
|
110
|
-
const { data } = useQuery(
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
110
|
+
const { data } = useQuery({
|
|
111
|
+
queryKey: [usePetServiceFindPetsByStatusKey],
|
|
112
|
+
queryFn: () => {
|
|
113
|
+
// Do something here
|
|
114
|
+
return PetService.findPetsByStatus(["available"]);
|
|
115
|
+
},
|
|
114
116
|
});
|
|
115
117
|
|
|
118
|
+
return <div className="App">{/* .... */}</div>;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export default App;
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
You can also use suspense hooks.
|
|
125
|
+
|
|
126
|
+
```tsx
|
|
127
|
+
// App.tsx
|
|
128
|
+
import { useDefaultClientFindPetsSuspense } from "../openapi/queries/suspense";
|
|
129
|
+
function ChildComponent() {
|
|
130
|
+
const { data } = useDefaultClientFindPetsSuspense({ tags: [], limit: 10 });
|
|
131
|
+
|
|
132
|
+
return (
|
|
133
|
+
<ul>
|
|
134
|
+
{data?.map((pet, index) => (
|
|
135
|
+
<li key={pet.id}>{pet.name}</li>
|
|
136
|
+
))}
|
|
137
|
+
</ul>
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function ParentComponent() {
|
|
142
|
+
return (
|
|
143
|
+
<>
|
|
144
|
+
<Suspense fallback={<>loading...</>}>
|
|
145
|
+
<ChildComponent />
|
|
146
|
+
</Suspense>
|
|
147
|
+
</>
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function App() {
|
|
116
152
|
return (
|
|
117
153
|
<div className="App">
|
|
118
|
-
|
|
154
|
+
<h1>Pet List</h1>
|
|
155
|
+
<ParentComponent />
|
|
119
156
|
</div>
|
|
120
157
|
);
|
|
121
158
|
}
|
|
@@ -124,4 +161,5 @@ export default App;
|
|
|
124
161
|
```
|
|
125
162
|
|
|
126
163
|
## License
|
|
164
|
+
|
|
127
165
|
MIT
|
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { generate } from "./generate.mjs";
|
|
3
|
+
import { Command, Option } from "commander";
|
|
4
|
+
import { readFile } from "fs/promises";
|
|
5
|
+
import { dirname, join } from "path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
const program = new Command();
|
|
8
|
+
async function setupProgram() {
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = dirname(__filename);
|
|
11
|
+
const file = await readFile(join(__dirname, "../package.json"), "utf-8");
|
|
12
|
+
const packageJson = JSON.parse(file);
|
|
13
|
+
const version = packageJson.version;
|
|
14
|
+
program
|
|
15
|
+
.name("openapi-rq")
|
|
16
|
+
.version(version)
|
|
17
|
+
.description("Generate React Query code based on OpenAPI")
|
|
18
|
+
.requiredOption("-i, --input <value>", "OpenAPI specification, can be a path, url or string content (required)")
|
|
19
|
+
.option("-o, --output <value>", "Output directory", "openapi")
|
|
20
|
+
.addOption(new Option("-c, --client <value>", "HTTP client to generate")
|
|
21
|
+
.choices(["angular", "axios", "fetch", "node", "xhr"])
|
|
22
|
+
.default("fetch"))
|
|
23
|
+
.option("--request <value>", "Path to custom request file")
|
|
24
|
+
.option("--format", "Process output folder with formatter?")
|
|
25
|
+
.option("--lint", "Process output folder with linter?")
|
|
26
|
+
.option("--operationId", "Use operation ID to generate operation names?")
|
|
27
|
+
.addOption(new Option("--serviceResponse <value>", "Define shape of returned value from service calls").choices(["body", "generics", "response"]))
|
|
28
|
+
.option("--base <value>", "Manually set base in OpenAPI config instead of inferring from server value")
|
|
29
|
+
.option("--enums", "Generate JavaScript objects from enum definitions?")
|
|
30
|
+
.option("--useDateType", "Use Date type instead of string for date types for models, this will not convert the data to a Date object")
|
|
31
|
+
.parse();
|
|
32
|
+
const options = program.opts();
|
|
33
|
+
generate(options, version);
|
|
34
|
+
}
|
|
35
|
+
setupProgram();
|
package/dist/common.mjs
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { stat } from "fs/promises";
|
|
2
|
+
import ts from "typescript";
|
|
3
|
+
export const TData = ts.factory.createIdentifier("TData");
|
|
4
|
+
export const TError = ts.factory.createIdentifier("TError");
|
|
5
|
+
export const TContext = ts.factory.createIdentifier("TContext");
|
|
6
|
+
export const queryKeyGenericType = ts.factory.createTypeReferenceNode("TQueryKey");
|
|
7
|
+
export const queryKeyConstraint = ts.factory.createTypeReferenceNode("Array", [
|
|
8
|
+
ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword),
|
|
9
|
+
]);
|
|
10
|
+
export const capitalizeFirstLetter = (str) => {
|
|
11
|
+
return str.charAt(0).toUpperCase() + str.slice(1);
|
|
12
|
+
};
|
|
13
|
+
export const lowercaseFirstLetter = (str) => {
|
|
14
|
+
return str.charAt(0).toLowerCase() + str.slice(1);
|
|
15
|
+
};
|
|
16
|
+
export const getNameFromMethod = (method) => {
|
|
17
|
+
return method.getName();
|
|
18
|
+
};
|
|
19
|
+
export async function exists(f) {
|
|
20
|
+
try {
|
|
21
|
+
await stat(f);
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
const Common = "Common";
|
|
29
|
+
/**
|
|
30
|
+
* Build a common type name by prepending the Common namespace.
|
|
31
|
+
*/
|
|
32
|
+
export function BuildCommonTypeName(name) {
|
|
33
|
+
if (typeof name === "string") {
|
|
34
|
+
return ts.factory.createIdentifier(`${Common}.${name}`);
|
|
35
|
+
}
|
|
36
|
+
return ts.factory.createIdentifier(`${Common}.${name.text}`);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Safely parse a value into a number. Checks for NaN and Infinity.
|
|
40
|
+
* Returns NaN if the string is not a valid number.
|
|
41
|
+
* @param value The value to parse.
|
|
42
|
+
* @returns The parsed number or NaN if the value is not a valid number.
|
|
43
|
+
*/
|
|
44
|
+
export function safeParseNumber(value) {
|
|
45
|
+
const parsed = Number(value);
|
|
46
|
+
if (!isNaN(parsed) && isFinite(parsed)) {
|
|
47
|
+
return parsed;
|
|
48
|
+
}
|
|
49
|
+
return NaN;
|
|
50
|
+
}
|
|
51
|
+
export function extractPropertiesFromObjectParam(param) {
|
|
52
|
+
const referenced = param.findReferences()[0];
|
|
53
|
+
const def = referenced.getDefinition();
|
|
54
|
+
const paramNodes = def
|
|
55
|
+
.getNode()
|
|
56
|
+
.getType()
|
|
57
|
+
.getProperties()
|
|
58
|
+
.map((prop) => ({
|
|
59
|
+
name: prop.getName(),
|
|
60
|
+
optional: prop.isOptional(),
|
|
61
|
+
type: prop.getValueDeclaration()?.getType(),
|
|
62
|
+
}));
|
|
63
|
+
return paramNodes;
|
|
64
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { createUseQuery } from "./createUseQuery.mjs";
|
|
2
|
+
import { createUseMutation } from "./createUseMutation.mjs";
|
|
3
|
+
export const createExports = (service) => {
|
|
4
|
+
const { klasses } = service;
|
|
5
|
+
const methods = klasses.map((k) => k.methods).flat();
|
|
6
|
+
const allGet = methods.filter((m) => m.httpMethodName === "'GET'");
|
|
7
|
+
const allPost = methods.filter((m) => m.httpMethodName === "'POST'");
|
|
8
|
+
const allQueries = allGet.map((m) => createUseQuery(m));
|
|
9
|
+
const allMutations = allPost.map((m) => createUseMutation(m));
|
|
10
|
+
const commonInQueries = allQueries
|
|
11
|
+
.map(({ apiResponse, returnType, key }) => [apiResponse, returnType, key])
|
|
12
|
+
.flat();
|
|
13
|
+
const commonInMutations = allMutations
|
|
14
|
+
.map(({ mutationResult }) => [mutationResult])
|
|
15
|
+
.flat();
|
|
16
|
+
const allCommon = [...commonInQueries, ...commonInMutations];
|
|
17
|
+
const mainQueries = allQueries.map(({ queryHook }) => [queryHook]).flat();
|
|
18
|
+
const mainMutations = allMutations
|
|
19
|
+
.map(({ mutationHook }) => [mutationHook])
|
|
20
|
+
.flat();
|
|
21
|
+
const mainExports = [...mainQueries, ...mainMutations];
|
|
22
|
+
const suspenseQueries = allQueries
|
|
23
|
+
.map(({ suspenseQueryHook }) => [suspenseQueryHook])
|
|
24
|
+
.flat();
|
|
25
|
+
const suspenseExports = [...suspenseQueries];
|
|
26
|
+
return {
|
|
27
|
+
/**
|
|
28
|
+
* Common types and variables between queries (regular and suspense) and mutations
|
|
29
|
+
*/
|
|
30
|
+
allCommon,
|
|
31
|
+
/**
|
|
32
|
+
* Main exports are the hooks that are used in the components
|
|
33
|
+
*/
|
|
34
|
+
mainExports,
|
|
35
|
+
/**
|
|
36
|
+
* Suspense exports are the hooks that are used in the suspense components
|
|
37
|
+
*/
|
|
38
|
+
suspenseExports,
|
|
39
|
+
};
|
|
40
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import { posix } from "path";
|
|
3
|
+
const { join } = posix;
|
|
4
|
+
export const createImports = ({ service, serviceEndName, project, }) => {
|
|
5
|
+
const { klasses } = service;
|
|
6
|
+
// get all class names
|
|
7
|
+
const classNames = klasses.map(({ className }) => className);
|
|
8
|
+
// remove duplicates
|
|
9
|
+
const uniqueClassNames = [...new Set(classNames)];
|
|
10
|
+
const modelsFile = project
|
|
11
|
+
.getSourceFiles()
|
|
12
|
+
.find((sourceFile) => sourceFile.getFilePath().includes("models.ts"));
|
|
13
|
+
const serviceFile = project
|
|
14
|
+
.getSourceFiles()
|
|
15
|
+
.find((sourceFile) => sourceFile.getFilePath().includes("services.ts"));
|
|
16
|
+
if (!modelsFile) {
|
|
17
|
+
throw new Error("No models file found");
|
|
18
|
+
}
|
|
19
|
+
if (!serviceFile) {
|
|
20
|
+
throw new Error("No service file found");
|
|
21
|
+
}
|
|
22
|
+
const modalNames = Array.from(modelsFile.getExportedDeclarations().keys());
|
|
23
|
+
const serviceExports = Array.from(serviceFile.getExportedDeclarations().keys());
|
|
24
|
+
const serviceNames = serviceExports.filter((name) => name.endsWith(serviceEndName));
|
|
25
|
+
const serviceNamesData = serviceExports.filter((name) => name.endsWith("Data"));
|
|
26
|
+
return [
|
|
27
|
+
ts.factory.createImportDeclaration(undefined, ts.factory.createImportClause(false, undefined, ts.factory.createNamedImports([
|
|
28
|
+
ts.factory.createImportSpecifier(false, undefined, ts.factory.createIdentifier("useQuery")),
|
|
29
|
+
ts.factory.createImportSpecifier(false, undefined, ts.factory.createIdentifier("useSuspenseQuery")),
|
|
30
|
+
ts.factory.createImportSpecifier(false, undefined, ts.factory.createIdentifier("useMutation")),
|
|
31
|
+
ts.factory.createImportSpecifier(false, undefined, ts.factory.createIdentifier("UseQueryResult")),
|
|
32
|
+
ts.factory.createImportSpecifier(false, undefined, ts.factory.createIdentifier("UseQueryOptions")),
|
|
33
|
+
ts.factory.createImportSpecifier(false, undefined, ts.factory.createIdentifier("UseMutationOptions")),
|
|
34
|
+
ts.factory.createImportSpecifier(false, undefined, ts.factory.createIdentifier("UseMutationResult")),
|
|
35
|
+
])), ts.factory.createStringLiteral("@tanstack/react-query"), undefined),
|
|
36
|
+
ts.factory.createImportDeclaration(undefined, ts.factory.createImportClause(false, undefined, ts.factory.createNamedImports([
|
|
37
|
+
// import all class names from service file
|
|
38
|
+
...serviceNames.map((serviceName) => ts.factory.createImportSpecifier(false, undefined, ts.factory.createIdentifier(serviceName))),
|
|
39
|
+
// import all data objects from service file
|
|
40
|
+
...serviceNamesData.map((dataName) => ts.factory.createImportSpecifier(false, undefined, ts.factory.createIdentifier(dataName))),
|
|
41
|
+
])), ts.factory.createStringLiteral(join("../requests")), undefined),
|
|
42
|
+
// import all the models by name
|
|
43
|
+
ts.factory.createImportDeclaration(undefined, ts.factory.createImportClause(false, undefined, ts.factory.createNamedImports([
|
|
44
|
+
...modalNames.map((modelName) => ts.factory.createImportSpecifier(false, undefined, ts.factory.createIdentifier(modelName))),
|
|
45
|
+
])), ts.factory.createStringLiteral(join("../requests/models")), undefined),
|
|
46
|
+
];
|
|
47
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import { createImports } from "./createImports.mjs";
|
|
3
|
+
import { createExports } from "./createExports.mjs";
|
|
4
|
+
import { getServices } from "./service.mjs";
|
|
5
|
+
import { Project } from "ts-morph";
|
|
6
|
+
import { join } from "path";
|
|
7
|
+
const createSourceFile = async (outputPath, serviceEndName) => {
|
|
8
|
+
const project = new Project({
|
|
9
|
+
// Optionally specify compiler options, tsconfig.json, in-memory file system, and more here.
|
|
10
|
+
// If you initialize with a tsconfig.json, then it will automatically populate the project
|
|
11
|
+
// with the associated source files.
|
|
12
|
+
// Read more: https://ts-morph.com/setup/
|
|
13
|
+
skipAddingFilesFromTsConfig: true,
|
|
14
|
+
});
|
|
15
|
+
const sourceFiles = join(process.cwd(), outputPath);
|
|
16
|
+
project.addSourceFilesAtPaths(`${sourceFiles}/**/*`);
|
|
17
|
+
const service = await getServices(project);
|
|
18
|
+
const imports = createImports({
|
|
19
|
+
service,
|
|
20
|
+
serviceEndName,
|
|
21
|
+
project,
|
|
22
|
+
});
|
|
23
|
+
const exports = createExports(service);
|
|
24
|
+
const commonSource = ts.factory.createSourceFile([...imports, ...exports.allCommon], ts.factory.createToken(ts.SyntaxKind.EndOfFileToken), ts.NodeFlags.None);
|
|
25
|
+
const commonImport = ts.factory.createImportDeclaration(undefined, ts.factory.createImportClause(false, ts.factory.createIdentifier("* as Common"), undefined), ts.factory.createStringLiteral("./common"), undefined);
|
|
26
|
+
const commonExport = ts.factory.createExportDeclaration(undefined, false, undefined, ts.factory.createStringLiteral("./common"), undefined);
|
|
27
|
+
const queriesExport = ts.factory.createExportDeclaration(undefined, false, undefined, ts.factory.createStringLiteral("./queries"), undefined);
|
|
28
|
+
const mainSource = ts.factory.createSourceFile([commonImport, ...imports, ...exports.mainExports], ts.factory.createToken(ts.SyntaxKind.EndOfFileToken), ts.NodeFlags.None);
|
|
29
|
+
const suspenseSource = ts.factory.createSourceFile([commonImport, ...imports, ...exports.suspenseExports], ts.factory.createToken(ts.SyntaxKind.EndOfFileToken), ts.NodeFlags.None);
|
|
30
|
+
const indexSource = ts.factory.createSourceFile([commonExport, queriesExport], ts.factory.createToken(ts.SyntaxKind.EndOfFileToken), ts.NodeFlags.None);
|
|
31
|
+
return {
|
|
32
|
+
commonSource,
|
|
33
|
+
mainSource,
|
|
34
|
+
suspenseSource,
|
|
35
|
+
indexSource,
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
export const createSource = async ({ outputPath, version, serviceEndName, }) => {
|
|
39
|
+
const queriesFile = ts.createSourceFile("queries.ts", "", ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
|
|
40
|
+
const commonFile = ts.createSourceFile("common.ts", "", ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
|
|
41
|
+
const suspenseFile = ts.createSourceFile("suspense.ts", "", ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
|
|
42
|
+
const indexFile = ts.createSourceFile("index.ts", "", ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
|
|
43
|
+
const printer = ts.createPrinter({
|
|
44
|
+
newLine: ts.NewLineKind.LineFeed,
|
|
45
|
+
removeComments: false,
|
|
46
|
+
});
|
|
47
|
+
const { commonSource, mainSource, suspenseSource, indexSource } = await createSourceFile(outputPath, serviceEndName);
|
|
48
|
+
const commonResult = `// generated with @7nohe/openapi-react-query-codegen@${version} \n` +
|
|
49
|
+
printer.printNode(ts.EmitHint.Unspecified, commonSource, commonFile);
|
|
50
|
+
const mainResult = `// generated with @7nohe/openapi-react-query-codegen@${version} \n` +
|
|
51
|
+
printer.printNode(ts.EmitHint.Unspecified, mainSource, queriesFile);
|
|
52
|
+
const suspenseResult = `// generated with @7nohe/openapi-react-query-codegen@${version} \n` +
|
|
53
|
+
printer.printNode(ts.EmitHint.Unspecified, suspenseSource, suspenseFile);
|
|
54
|
+
const indexResult = `// generated with @7nohe/openapi-react-query-codegen@${version} \n` +
|
|
55
|
+
printer.printNode(ts.EmitHint.Unspecified, indexSource, indexFile);
|
|
56
|
+
return [
|
|
57
|
+
{
|
|
58
|
+
name: "index.ts",
|
|
59
|
+
content: indexResult,
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: "common.ts",
|
|
63
|
+
content: commonResult,
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: "queries.ts",
|
|
67
|
+
content: mainResult,
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: "suspense.ts",
|
|
71
|
+
content: suspenseResult,
|
|
72
|
+
},
|
|
73
|
+
];
|
|
74
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import { BuildCommonTypeName, TContext, TData, TError, capitalizeFirstLetter, extractPropertiesFromObjectParam, getNameFromMethod, } from "./common.mjs";
|
|
3
|
+
import { addJSDocToNode } from "./util.mjs";
|
|
4
|
+
/**
|
|
5
|
+
* Awaited<ReturnType<typeof myClass.myMethod>>
|
|
6
|
+
*/
|
|
7
|
+
function generateAwaitedReturnType({ className, methodName, }) {
|
|
8
|
+
return ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("Awaited"), [
|
|
9
|
+
ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("ReturnType"), [
|
|
10
|
+
ts.factory.createTypeQueryNode(ts.factory.createQualifiedName(ts.factory.createIdentifier(className), ts.factory.createIdentifier(methodName)), undefined),
|
|
11
|
+
]),
|
|
12
|
+
]);
|
|
13
|
+
}
|
|
14
|
+
export const createUseMutation = ({ node, className, method, jsDoc = [], isDeprecated = false, }) => {
|
|
15
|
+
const methodName = getNameFromMethod(method);
|
|
16
|
+
const awaitedResponseDataType = generateAwaitedReturnType({
|
|
17
|
+
className,
|
|
18
|
+
methodName,
|
|
19
|
+
});
|
|
20
|
+
const mutationResult = ts.factory.createTypeAliasDeclaration([ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], ts.factory.createIdentifier(`${className}${capitalizeFirstLetter(methodName)}MutationResult`), undefined, awaitedResponseDataType);
|
|
21
|
+
const responseDataType = ts.factory.createTypeParameterDeclaration(undefined, TData, undefined, ts.factory.createTypeReferenceNode(BuildCommonTypeName(mutationResult.name)));
|
|
22
|
+
const methodParameters = method.getParameters().length !== 0
|
|
23
|
+
? ts.factory.createTypeLiteralNode(method
|
|
24
|
+
.getParameters()
|
|
25
|
+
.map((param) => {
|
|
26
|
+
const paramNodes = extractPropertiesFromObjectParam(param);
|
|
27
|
+
return paramNodes.map((refParam) => ts.factory.createPropertySignature(undefined, ts.factory.createIdentifier(refParam.name), refParam.optional
|
|
28
|
+
? ts.factory.createToken(ts.SyntaxKind.QuestionToken)
|
|
29
|
+
: undefined,
|
|
30
|
+
// refParam.questionToken ?? refParam.initializer
|
|
31
|
+
// ? ts.factory.createToken(ts.SyntaxKind.QuestionToken)
|
|
32
|
+
// : refParam.questionToken,
|
|
33
|
+
ts.factory.createTypeReferenceNode(refParam.type.getText(param))));
|
|
34
|
+
})
|
|
35
|
+
.flat()
|
|
36
|
+
// return ts.factory.createPropertySignature(
|
|
37
|
+
// undefined,
|
|
38
|
+
// ts.factory.createIdentifier(param.getName()),
|
|
39
|
+
// param.compilerNode.questionToken ?? param.compilerNode.initializer
|
|
40
|
+
// ? ts.factory.createToken(ts.SyntaxKind.QuestionToken)
|
|
41
|
+
// : param.compilerNode.questionToken,
|
|
42
|
+
// param.compilerNode.type
|
|
43
|
+
// );
|
|
44
|
+
)
|
|
45
|
+
: ts.factory.createKeywordTypeNode(ts.SyntaxKind.VoidKeyword);
|
|
46
|
+
const exportHook = ts.factory.createVariableStatement([ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], ts.factory.createVariableDeclarationList([
|
|
47
|
+
ts.factory.createVariableDeclaration(ts.factory.createIdentifier(`use${className}${capitalizeFirstLetter(methodName)}`), undefined, undefined, ts.factory.createArrowFunction(undefined, ts.factory.createNodeArray([
|
|
48
|
+
responseDataType,
|
|
49
|
+
ts.factory.createTypeParameterDeclaration(undefined, TError, undefined, ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword)),
|
|
50
|
+
ts.factory.createTypeParameterDeclaration(undefined, TContext, undefined, ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword)),
|
|
51
|
+
]), [
|
|
52
|
+
ts.factory.createParameterDeclaration(undefined, undefined, ts.factory.createIdentifier("options"), ts.factory.createToken(ts.SyntaxKind.QuestionToken), ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("Omit"), [
|
|
53
|
+
ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("UseMutationOptions"), [
|
|
54
|
+
ts.factory.createTypeReferenceNode(TData),
|
|
55
|
+
ts.factory.createTypeReferenceNode(TError),
|
|
56
|
+
methodParameters,
|
|
57
|
+
ts.factory.createTypeReferenceNode(TContext),
|
|
58
|
+
]),
|
|
59
|
+
ts.factory.createLiteralTypeNode(ts.factory.createStringLiteral("mutationFn")),
|
|
60
|
+
]), undefined),
|
|
61
|
+
], undefined, ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), ts.factory.createCallExpression(ts.factory.createIdentifier("useMutation"), [
|
|
62
|
+
ts.factory.createTypeReferenceNode(TData),
|
|
63
|
+
ts.factory.createTypeReferenceNode(TError),
|
|
64
|
+
methodParameters,
|
|
65
|
+
ts.factory.createTypeReferenceNode(TContext),
|
|
66
|
+
], [
|
|
67
|
+
ts.factory.createObjectLiteralExpression([
|
|
68
|
+
ts.factory.createPropertyAssignment(ts.factory.createIdentifier("mutationFn"), ts.factory.createArrowFunction(undefined, undefined, method.getParameters().length !== 0
|
|
69
|
+
? [
|
|
70
|
+
ts.factory.createParameterDeclaration(undefined, undefined, ts.factory.createObjectBindingPattern(method
|
|
71
|
+
.getParameters()
|
|
72
|
+
.map((param) => {
|
|
73
|
+
const paramNodes = extractPropertiesFromObjectParam(param);
|
|
74
|
+
return paramNodes.map((refParam) => ts.factory.createBindingElement(undefined, undefined, ts.factory.createIdentifier(refParam.name), undefined));
|
|
75
|
+
})
|
|
76
|
+
.flat()), undefined, undefined, undefined),
|
|
77
|
+
]
|
|
78
|
+
: [], undefined, ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), ts.factory.createAsExpression(ts.factory.createAsExpression(ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(className), ts.factory.createIdentifier(methodName)), undefined, method.getParameters().length !== 0
|
|
79
|
+
? [
|
|
80
|
+
ts.factory.createObjectLiteralExpression(method
|
|
81
|
+
.getParameters()
|
|
82
|
+
.map((params) => {
|
|
83
|
+
const paramNodes = extractPropertiesFromObjectParam(params);
|
|
84
|
+
return paramNodes.map((refParam) => ts.factory.createShorthandPropertyAssignment(refParam.name));
|
|
85
|
+
})
|
|
86
|
+
.flat()),
|
|
87
|
+
]
|
|
88
|
+
: []), ts.factory.createKeywordTypeNode(ts.SyntaxKind.UnknownKeyword)), ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("Promise"), [ts.factory.createTypeReferenceNode(TData)])))),
|
|
89
|
+
ts.factory.createSpreadAssignment(ts.factory.createIdentifier("options")),
|
|
90
|
+
]),
|
|
91
|
+
]))),
|
|
92
|
+
], ts.NodeFlags.Const));
|
|
93
|
+
const hookWithJsDoc = addJSDocToNode(exportHook, node, isDeprecated, jsDoc);
|
|
94
|
+
return {
|
|
95
|
+
mutationResult,
|
|
96
|
+
mutationHook: hookWithJsDoc,
|
|
97
|
+
};
|
|
98
|
+
};
|