@7nohe/openapi-react-query-codegen 1.1.0 → 1.2.1
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 +57 -29
- package/dist/cli.mjs +10 -4
- package/dist/common.mjs +72 -1
- package/dist/constants.mjs +9 -0
- package/dist/createExports.mjs +10 -0
- package/dist/createImports.mjs +5 -12
- package/dist/createPrefetch.mjs +58 -0
- package/dist/createSource.mjs +29 -18
- package/dist/createUseMutation.mjs +5 -18
- package/dist/createUseQuery.mjs +8 -20
- package/dist/format.mjs +21 -0
- package/dist/generate.mjs +28 -30
- package/dist/print.mjs +3 -4
- package/dist/service.mjs +10 -25
- package/dist/util.mjs +13 -25
- package/package.json +12 -9
package/README.md
CHANGED
|
@@ -2,13 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
> Node.js library that generates [React Query (also called TanStack Query)](https://tanstack.com/query) hooks based on an OpenAPI specification file.
|
|
4
4
|
|
|
5
|
+
[](https://badge.fury.io/js/%407nohe%2Fopenapi-react-query-codegen)
|
|
6
|
+
|
|
5
7
|
## Features
|
|
6
8
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
9
|
+
- Generates custom react hooks that use React Query's `useQuery`, `useSuspenseQuery` and `useMutation` hooks
|
|
10
|
+
- Generates query keys and functions for query caching
|
|
11
|
+
- Generates pure TypeScript clients generated by [@hey-api/openapi-ts](https://github.com/hey-api/openapi-ts)
|
|
10
12
|
|
|
11
|
-
##
|
|
13
|
+
## Installation
|
|
12
14
|
|
|
13
15
|
```
|
|
14
16
|
$ npm install -D @7nohe/openapi-react-query-codegen
|
|
@@ -45,37 +47,45 @@ Options:
|
|
|
45
47
|
-o, --output <value> Output directory (default: "openapi")
|
|
46
48
|
-c, --client <value> HTTP client to generate [fetch, xhr, node, axios, angular] (default: "fetch")
|
|
47
49
|
--request <value> Path to custom request file
|
|
48
|
-
--
|
|
49
|
-
--
|
|
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']
|
|
50
|
+
--format <value> Process output folder with formatter? ['biome', 'prettier']
|
|
51
|
+
--lint <value> Process output folder with linter? ['eslint', 'biome']
|
|
52
52
|
--operationId Use operation ID to generate operation names?
|
|
53
|
-
--
|
|
54
|
-
--
|
|
53
|
+
--serviceResponse <value> Define shape of returned value from service calls ['body', 'response'] (default: "body")
|
|
54
|
+
--base <value> Manually set base in OpenAPI config instead of inferring from server value
|
|
55
|
+
--enums <value> Generate JavaScript objects from enum definitions? ['javascript', 'typescript']
|
|
56
|
+
--useDateType Use Date type instead of string for date types for models, this will not convert the data to a Date object
|
|
57
|
+
--debug Enable debug mode
|
|
58
|
+
--noSchemas Disable generating schemas for request and response objects
|
|
59
|
+
--schemaTypes <value> Define the type of schema generation ['form', 'json'] (default: "json")
|
|
55
60
|
-h, --help display help for command
|
|
56
61
|
```
|
|
57
62
|
|
|
58
|
-
|
|
63
|
+
### Example Usage
|
|
59
64
|
|
|
60
|
-
|
|
65
|
+
#### Command
|
|
61
66
|
|
|
62
67
|
```
|
|
63
68
|
$ openapi-rq -i ./petstore.yaml
|
|
64
69
|
```
|
|
65
70
|
|
|
66
|
-
|
|
71
|
+
#### Output directory structure
|
|
67
72
|
|
|
68
73
|
```
|
|
69
74
|
- openapi
|
|
70
75
|
- queries
|
|
71
|
-
- index.ts <- main file that exports common types, variables, and hooks
|
|
76
|
+
- index.ts <- main file that exports common types, variables, and queries. Does not export suspense or prefetch hooks
|
|
72
77
|
- common.ts <- common types
|
|
73
78
|
- queries.ts <- generated query hooks
|
|
74
79
|
- suspenses.ts <- generated suspense hooks
|
|
80
|
+
- prefetch.ts <- generated prefetch hooks learn more about prefetching in in link below
|
|
75
81
|
- requests <- output code generated by @hey-api/openapi-ts
|
|
76
82
|
```
|
|
77
83
|
|
|
78
|
-
|
|
84
|
+
- [Prefetching docs](https://tanstack.com/query/latest/docs/framework/react/guides/advanced-ssr#prefetching-and-dehydrating-data)
|
|
85
|
+
|
|
86
|
+
#### In your app
|
|
87
|
+
|
|
88
|
+
##### Using the generated hooks
|
|
79
89
|
|
|
80
90
|
```tsx
|
|
81
91
|
// App.tsx
|
|
@@ -86,11 +96,7 @@ function App() {
|
|
|
86
96
|
return (
|
|
87
97
|
<div className="App">
|
|
88
98
|
<h1>Pet List</h1>
|
|
89
|
-
<ul>
|
|
90
|
-
{data?.map((pet) => (
|
|
91
|
-
<li key={pet.id}>{pet.name}</li>
|
|
92
|
-
))}
|
|
93
|
-
</ul>
|
|
99
|
+
<ul>{data?.map((pet) => <li key={pet.id}>{pet.name}</li>)}</ul>
|
|
94
100
|
</div>
|
|
95
101
|
);
|
|
96
102
|
}
|
|
@@ -98,7 +104,7 @@ function App() {
|
|
|
98
104
|
export default App;
|
|
99
105
|
```
|
|
100
106
|
|
|
101
|
-
|
|
107
|
+
##### Using the generated typescript client
|
|
102
108
|
|
|
103
109
|
```tsx
|
|
104
110
|
import { useQuery } from "@tanstack/react-query";
|
|
@@ -121,7 +127,7 @@ function App() {
|
|
|
121
127
|
export default App;
|
|
122
128
|
```
|
|
123
129
|
|
|
124
|
-
|
|
130
|
+
##### Using Suspense Hooks
|
|
125
131
|
|
|
126
132
|
```tsx
|
|
127
133
|
// App.tsx
|
|
@@ -129,13 +135,7 @@ import { useDefaultClientFindPetsSuspense } from "../openapi/queries/suspense";
|
|
|
129
135
|
function ChildComponent() {
|
|
130
136
|
const { data } = useDefaultClientFindPetsSuspense({ tags: [], limit: 10 });
|
|
131
137
|
|
|
132
|
-
return (
|
|
133
|
-
<ul>
|
|
134
|
-
{data?.map((pet, index) => (
|
|
135
|
-
<li key={pet.id}>{pet.name}</li>
|
|
136
|
-
))}
|
|
137
|
-
</ul>
|
|
138
|
-
);
|
|
138
|
+
return <ul>{data?.map((pet, index) => <li key={pet.id}>{pet.name}</li>)}</ul>;
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
function ParentComponent() {
|
|
@@ -160,6 +160,34 @@ function App() {
|
|
|
160
160
|
export default App;
|
|
161
161
|
```
|
|
162
162
|
|
|
163
|
+
##### Runtime Configuration
|
|
164
|
+
|
|
165
|
+
You can modify the default values used by the generated service calls by modifying the OpenAPI configuration singleton object.
|
|
166
|
+
|
|
167
|
+
It's default location is `openapi/requests/core/OpenAPI.ts` and it is also exported from `openapi/index.ts`
|
|
168
|
+
|
|
169
|
+
Import the constant into your runtime and modify it before setting up the react app.
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
/** main.tsx */
|
|
173
|
+
import { OpenAPI as OpenAPIConfig } from './openapi/requests/core/OpenAPI';
|
|
174
|
+
...
|
|
175
|
+
OpenAPIConfig.BASE = 'www.domain.com/api';
|
|
176
|
+
OpenAPIConfig.HEADERS = {
|
|
177
|
+
'x-header-1': 'value-1',
|
|
178
|
+
'x-header-2': 'value-2',
|
|
179
|
+
};
|
|
180
|
+
...
|
|
181
|
+
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
|
182
|
+
<React.StrictMode>
|
|
183
|
+
<QueryClientProvider client={queryClient}>
|
|
184
|
+
<App />
|
|
185
|
+
</QueryClientProvider>
|
|
186
|
+
</React.StrictMode>
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
```
|
|
190
|
+
|
|
163
191
|
## License
|
|
164
192
|
|
|
165
193
|
MIT
|
package/dist/cli.mjs
CHANGED
|
@@ -4,6 +4,7 @@ import { Command, Option } from "commander";
|
|
|
4
4
|
import { readFile } from "fs/promises";
|
|
5
5
|
import { dirname, join } from "path";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
|
+
import { defaultOutputPath } from "./constants.mjs";
|
|
7
8
|
const program = new Command();
|
|
8
9
|
async function setupProgram() {
|
|
9
10
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -16,18 +17,23 @@ async function setupProgram() {
|
|
|
16
17
|
.version(version)
|
|
17
18
|
.description("Generate React Query code based on OpenAPI")
|
|
18
19
|
.requiredOption("-i, --input <value>", "OpenAPI specification, can be a path, url or string content (required)")
|
|
19
|
-
.option("-o, --output <value>", "Output directory",
|
|
20
|
+
.option("-o, --output <value>", "Output directory", defaultOutputPath)
|
|
20
21
|
.addOption(new Option("-c, --client <value>", "HTTP client to generate")
|
|
21
22
|
.choices(["angular", "axios", "fetch", "node", "xhr"])
|
|
22
23
|
.default("fetch"))
|
|
23
24
|
.option("--request <value>", "Path to custom request file")
|
|
24
|
-
.
|
|
25
|
-
.
|
|
25
|
+
.addOption(new Option("--format <value>", "Process output folder with formatter?").choices(["biome", "prettier"]))
|
|
26
|
+
.addOption(new Option("--lint <value>", "Process output folder with linter?").choices(["biome", "eslint"]))
|
|
26
27
|
.option("--operationId", "Use operation ID to generate operation names?")
|
|
27
|
-
.addOption(new Option("--serviceResponse <value>", "Define shape of returned value from service calls")
|
|
28
|
+
.addOption(new Option("--serviceResponse <value>", "Define shape of returned value from service calls")
|
|
29
|
+
.choices(["body", "response"])
|
|
30
|
+
.default("body"))
|
|
28
31
|
.option("--base <value>", "Manually set base in OpenAPI config instead of inferring from server value")
|
|
29
32
|
.addOption(new Option("--enums <value>", "Generate JavaScript objects from enum definitions?").choices(["javascript", "typescript"]))
|
|
30
33
|
.option("--useDateType", "Use Date type instead of string for date types for models, this will not convert the data to a Date object")
|
|
34
|
+
.option("--debug", "Run in debug mode?")
|
|
35
|
+
.option("--noSchemas", "Disable generating JSON schemas")
|
|
36
|
+
.addOption(new Option("--schemaType <value>", "Type of JSON schema [Default: 'json']").choices(["form", "json"]))
|
|
31
37
|
.parse();
|
|
32
38
|
const options = program.opts();
|
|
33
39
|
await generate(options, version);
|
package/dist/common.mjs
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { stat } from "fs/promises";
|
|
2
2
|
import ts from "typescript";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { queriesOutputPath, requestsOutputPath } from "./constants.mjs";
|
|
3
5
|
export const TData = ts.factory.createIdentifier("TData");
|
|
4
6
|
export const TError = ts.factory.createIdentifier("TError");
|
|
5
7
|
export const TContext = ts.factory.createIdentifier("TContext");
|
|
@@ -14,7 +16,11 @@ export const lowercaseFirstLetter = (str) => {
|
|
|
14
16
|
return str.charAt(0).toLowerCase() + str.slice(1);
|
|
15
17
|
};
|
|
16
18
|
export const getNameFromMethod = (method) => {
|
|
17
|
-
|
|
19
|
+
const methodName = method.getName();
|
|
20
|
+
if (!methodName) {
|
|
21
|
+
throw new Error("Method name not found");
|
|
22
|
+
}
|
|
23
|
+
return methodName;
|
|
18
24
|
};
|
|
19
25
|
export async function exists(f) {
|
|
20
26
|
try {
|
|
@@ -62,3 +68,68 @@ export function extractPropertiesFromObjectParam(param) {
|
|
|
62
68
|
}));
|
|
63
69
|
return paramNodes;
|
|
64
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* Replace the import("...") surrounding the type if there is one.
|
|
73
|
+
* This can happen when the type is imported from another file, but
|
|
74
|
+
* we are already importing all the types from that file.
|
|
75
|
+
*
|
|
76
|
+
* https://regex101.com/r/3DyHaQ/1
|
|
77
|
+
*
|
|
78
|
+
* TODO: Replace with a more robust solution.
|
|
79
|
+
*/
|
|
80
|
+
export function getShortType(type) {
|
|
81
|
+
return type.replaceAll(/import\(".*"\)\./g, "");
|
|
82
|
+
}
|
|
83
|
+
export function getClassesFromService(node) {
|
|
84
|
+
const klasses = node.getClasses();
|
|
85
|
+
if (!klasses.length) {
|
|
86
|
+
throw new Error("No classes found");
|
|
87
|
+
}
|
|
88
|
+
return klasses.map((klass) => {
|
|
89
|
+
const className = klass.getName();
|
|
90
|
+
if (!className) {
|
|
91
|
+
throw new Error("Class name not found");
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
className,
|
|
95
|
+
klass,
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
export function getClassNameFromClassNode(klass) {
|
|
100
|
+
const className = klass.getName();
|
|
101
|
+
if (!className) {
|
|
102
|
+
throw new Error("Class name not found");
|
|
103
|
+
}
|
|
104
|
+
return className;
|
|
105
|
+
}
|
|
106
|
+
export function formatOptions(options) {
|
|
107
|
+
// loop through properties on the options object
|
|
108
|
+
// if the property is a string of number then convert it to a number
|
|
109
|
+
// if the property is a string of boolean then convert it to a boolean
|
|
110
|
+
const formattedOptions = Object.entries(options).reduce((acc, [key, value]) => {
|
|
111
|
+
const typedKey = key;
|
|
112
|
+
const typedValue = value;
|
|
113
|
+
const parsedNumber = safeParseNumber(typedValue);
|
|
114
|
+
if (value === "true" || value === true) {
|
|
115
|
+
acc[typedKey] = true;
|
|
116
|
+
}
|
|
117
|
+
else if (value === "false" || value === false) {
|
|
118
|
+
acc[typedKey] = false;
|
|
119
|
+
}
|
|
120
|
+
else if (!isNaN(parsedNumber)) {
|
|
121
|
+
acc[typedKey] = parsedNumber;
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
acc[typedKey] = typedValue;
|
|
125
|
+
}
|
|
126
|
+
return acc;
|
|
127
|
+
}, options);
|
|
128
|
+
return formattedOptions;
|
|
129
|
+
}
|
|
130
|
+
export function buildRequestsOutputPath(outputPath) {
|
|
131
|
+
return path.join(outputPath, requestsOutputPath);
|
|
132
|
+
}
|
|
133
|
+
export function buildQueriesOutputPath(outputPath) {
|
|
134
|
+
return path.join(outputPath, queriesOutputPath);
|
|
135
|
+
}
|
package/dist/constants.mjs
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
1
|
export const defaultOutputPath = "openapi";
|
|
2
2
|
export const queriesOutputPath = "queries";
|
|
3
3
|
export const requestsOutputPath = "requests";
|
|
4
|
+
export const serviceFileName = "services.gen";
|
|
5
|
+
export const modalsFileName = "types.gen";
|
|
6
|
+
export const OpenApiRqFiles = {
|
|
7
|
+
queries: "queries",
|
|
8
|
+
common: "common",
|
|
9
|
+
suspense: "suspense",
|
|
10
|
+
index: "index",
|
|
11
|
+
prefetch: "prefetch",
|
|
12
|
+
};
|
package/dist/createExports.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createUseQuery } from "./createUseQuery.mjs";
|
|
2
2
|
import { createUseMutation } from "./createUseMutation.mjs";
|
|
3
|
+
import { createPrefetch } from "./createPrefetch.mjs";
|
|
3
4
|
export const createExports = (service) => {
|
|
4
5
|
const { klasses } = service;
|
|
5
6
|
const methods = klasses.map((k) => k.methods).flat();
|
|
@@ -9,6 +10,7 @@ export const createExports = (service) => {
|
|
|
9
10
|
const allPatch = methods.filter((m) => m.httpMethodName.toUpperCase().includes("PATCH"));
|
|
10
11
|
const allDelete = methods.filter((m) => m.httpMethodName.toUpperCase().includes("DELETE"));
|
|
11
12
|
const allGetQueries = allGet.map((m) => createUseQuery(m));
|
|
13
|
+
const allPrefetchQueries = allGet.map((m) => createPrefetch(m));
|
|
12
14
|
const allPostMutations = allPost.map((m) => createUseMutation(m));
|
|
13
15
|
const allPutMutations = allPut.map((m) => createUseMutation(m));
|
|
14
16
|
const allPatchMutations = allPatch.map((m) => createUseMutation(m));
|
|
@@ -36,6 +38,10 @@ export const createExports = (service) => {
|
|
|
36
38
|
.map(({ suspenseQueryHook }) => [suspenseQueryHook])
|
|
37
39
|
.flat();
|
|
38
40
|
const suspenseExports = [...suspenseQueries];
|
|
41
|
+
const allPrefetches = allPrefetchQueries
|
|
42
|
+
.map(({ prefetchHook }) => [prefetchHook])
|
|
43
|
+
.flat();
|
|
44
|
+
const allPrefetchExports = [...allPrefetches];
|
|
39
45
|
return {
|
|
40
46
|
/**
|
|
41
47
|
* Common types and variables between queries (regular and suspense) and mutations
|
|
@@ -49,5 +55,9 @@ export const createExports = (service) => {
|
|
|
49
55
|
* Suspense exports are the hooks that are used in the suspense components
|
|
50
56
|
*/
|
|
51
57
|
suspenseExports,
|
|
58
|
+
/**
|
|
59
|
+
* Prefetch exports are the hooks that are used in the prefetch components
|
|
60
|
+
*/
|
|
61
|
+
allPrefetchExports,
|
|
52
62
|
};
|
|
53
63
|
};
|
package/dist/createImports.mjs
CHANGED
|
@@ -1,27 +1,22 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
2
|
import { posix } from "path";
|
|
3
|
+
import { modalsFileName, serviceFileName } from "./constants.mjs";
|
|
3
4
|
const { join } = posix;
|
|
4
5
|
export const createImports = ({ serviceEndName, project, }) => {
|
|
5
6
|
const modelsFile = project
|
|
6
7
|
.getSourceFiles()
|
|
7
|
-
.find((sourceFile) => sourceFile.getFilePath().includes(
|
|
8
|
-
const serviceFile = project
|
|
9
|
-
.getSourceFiles()
|
|
10
|
-
.find((sourceFile) => sourceFile.getFilePath().includes("services.ts"));
|
|
8
|
+
.find((sourceFile) => sourceFile.getFilePath().includes(modalsFileName));
|
|
9
|
+
const serviceFile = project.getSourceFileOrThrow(`${serviceFileName}.ts`);
|
|
11
10
|
if (!modelsFile) {
|
|
12
11
|
console.warn(`
|
|
13
12
|
⚠️ WARNING: No models file found.
|
|
14
13
|
This may be an error if \`.components.schemas\` or \`.components.parameters\` is defined in your OpenAPI input.`);
|
|
15
14
|
}
|
|
16
|
-
if (!serviceFile) {
|
|
17
|
-
throw new Error("No service file found");
|
|
18
|
-
}
|
|
19
15
|
const modelNames = modelsFile
|
|
20
16
|
? Array.from(modelsFile.getExportedDeclarations().keys())
|
|
21
17
|
: [];
|
|
22
18
|
const serviceExports = Array.from(serviceFile.getExportedDeclarations().keys());
|
|
23
19
|
const serviceNames = serviceExports.filter((name) => name.endsWith(serviceEndName));
|
|
24
|
-
const serviceNamesData = serviceExports.filter((name) => name.endsWith("Data"));
|
|
25
20
|
const imports = [
|
|
26
21
|
ts.factory.createImportDeclaration(undefined, ts.factory.createImportClause(false, undefined, ts.factory.createNamedImports([
|
|
27
22
|
ts.factory.createImportSpecifier(false, undefined, ts.factory.createIdentifier("useQuery")),
|
|
@@ -35,15 +30,13 @@ export const createImports = ({ serviceEndName, project, }) => {
|
|
|
35
30
|
ts.factory.createImportDeclaration(undefined, ts.factory.createImportClause(false, undefined, ts.factory.createNamedImports([
|
|
36
31
|
// import all class names from service file
|
|
37
32
|
...serviceNames.map((serviceName) => ts.factory.createImportSpecifier(false, undefined, ts.factory.createIdentifier(serviceName))),
|
|
38
|
-
|
|
39
|
-
...serviceNamesData.map((dataName) => ts.factory.createImportSpecifier(false, undefined, ts.factory.createIdentifier(dataName))),
|
|
40
|
-
])), ts.factory.createStringLiteral(join("../requests")), undefined),
|
|
33
|
+
])), ts.factory.createStringLiteral(join("../requests", serviceFileName)), undefined),
|
|
41
34
|
];
|
|
42
35
|
if (modelsFile) {
|
|
43
36
|
// import all the models by name
|
|
44
37
|
imports.push(ts.factory.createImportDeclaration(undefined, ts.factory.createImportClause(false, undefined, ts.factory.createNamedImports([
|
|
45
38
|
...modelNames.map((modelName) => ts.factory.createImportSpecifier(false, undefined, ts.factory.createIdentifier(modelName))),
|
|
46
|
-
])), ts.factory.createStringLiteral(join("../requests/
|
|
39
|
+
])), ts.factory.createStringLiteral(join("../requests/", modalsFileName)), undefined));
|
|
47
40
|
}
|
|
48
41
|
return imports;
|
|
49
42
|
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import ts from "typescript";
|
|
2
|
+
import { BuildCommonTypeName, extractPropertiesFromObjectParam, getNameFromMethod, } from "./common.mjs";
|
|
3
|
+
import { createQueryKeyFromMethod, getRequestParamFromMethod, hookNameFromMethod, } from "./createUseQuery.mjs";
|
|
4
|
+
import { addJSDocToNode } from "./util.mjs";
|
|
5
|
+
/**
|
|
6
|
+
* Creates a prefetch function for a query
|
|
7
|
+
*/
|
|
8
|
+
function createPrefetchHook({ requestParams, method, className, }) {
|
|
9
|
+
const methodName = getNameFromMethod(method);
|
|
10
|
+
const queryName = hookNameFromMethod({ method, className });
|
|
11
|
+
const customHookName = `prefetch${queryName.charAt(0).toUpperCase() + queryName.slice(1)}`;
|
|
12
|
+
const queryKey = createQueryKeyFromMethod({ method, className });
|
|
13
|
+
// const
|
|
14
|
+
const hookExport = ts.factory.createVariableStatement(
|
|
15
|
+
// export
|
|
16
|
+
[ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], ts.factory.createVariableDeclarationList([
|
|
17
|
+
ts.factory.createVariableDeclaration(ts.factory.createIdentifier(customHookName), undefined, undefined, ts.factory.createArrowFunction(undefined, undefined, [
|
|
18
|
+
ts.factory.createParameterDeclaration(undefined, undefined, "queryClient", undefined, ts.factory.createTypeReferenceNode(ts.factory.createIdentifier("QueryClient"))),
|
|
19
|
+
...requestParams,
|
|
20
|
+
], undefined, ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), ts.factory.createCallExpression(ts.factory.createIdentifier("queryClient.prefetchQuery"), undefined, [
|
|
21
|
+
ts.factory.createObjectLiteralExpression([
|
|
22
|
+
ts.factory.createPropertyAssignment(ts.factory.createIdentifier("queryKey"), ts.factory.createArrayLiteralExpression([
|
|
23
|
+
BuildCommonTypeName(queryKey),
|
|
24
|
+
method.getParameters().length
|
|
25
|
+
? ts.factory.createArrayLiteralExpression([
|
|
26
|
+
ts.factory.createObjectLiteralExpression(method
|
|
27
|
+
.getParameters()
|
|
28
|
+
.map((param) => extractPropertiesFromObjectParam(param).map((p) => ts.factory.createShorthandPropertyAssignment(ts.factory.createIdentifier(p.name))))
|
|
29
|
+
.flat()),
|
|
30
|
+
])
|
|
31
|
+
: ts.factory.createArrayLiteralExpression([]),
|
|
32
|
+
], false)),
|
|
33
|
+
ts.factory.createPropertyAssignment(ts.factory.createIdentifier("queryFn"), ts.factory.createArrowFunction(undefined, undefined, [], undefined, ts.factory.createToken(ts.SyntaxKind.EqualsGreaterThanToken), ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(ts.factory.createIdentifier(className), ts.factory.createIdentifier(methodName)), undefined, method.getParameters().length
|
|
34
|
+
? [
|
|
35
|
+
ts.factory.createObjectLiteralExpression(method
|
|
36
|
+
.getParameters()
|
|
37
|
+
.map((param) => extractPropertiesFromObjectParam(param).map((p) => ts.factory.createShorthandPropertyAssignment(ts.factory.createIdentifier(p.name))))
|
|
38
|
+
.flat()),
|
|
39
|
+
]
|
|
40
|
+
: undefined))),
|
|
41
|
+
]),
|
|
42
|
+
]))),
|
|
43
|
+
], ts.NodeFlags.Const));
|
|
44
|
+
return hookExport;
|
|
45
|
+
}
|
|
46
|
+
export const createPrefetch = ({ className, method, jsDoc, }) => {
|
|
47
|
+
const requestParam = getRequestParamFromMethod(method);
|
|
48
|
+
const requestParams = requestParam ? [requestParam] : [];
|
|
49
|
+
const prefetchHook = createPrefetchHook({
|
|
50
|
+
requestParams,
|
|
51
|
+
method,
|
|
52
|
+
className,
|
|
53
|
+
});
|
|
54
|
+
const hookWithJsDoc = addJSDocToNode(prefetchHook, jsDoc);
|
|
55
|
+
return {
|
|
56
|
+
prefetchHook: hookWithJsDoc,
|
|
57
|
+
};
|
|
58
|
+
};
|
package/dist/createSource.mjs
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
|
+
import { Project } from "ts-morph";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
import { OpenApiRqFiles } from "./constants.mjs";
|
|
2
5
|
import { createImports } from "./createImports.mjs";
|
|
3
6
|
import { createExports } from "./createExports.mjs";
|
|
4
7
|
import { getServices } from "./service.mjs";
|
|
5
|
-
import { Project } from "ts-morph";
|
|
6
|
-
import { join } from "path";
|
|
7
8
|
const createSourceFile = async (outputPath, serviceEndName) => {
|
|
8
9
|
const project = new Project({
|
|
9
10
|
// Optionally specify compiler options, tsconfig.json, in-memory file system, and more here.
|
|
@@ -21,53 +22,63 @@ const createSourceFile = async (outputPath, serviceEndName) => {
|
|
|
21
22
|
});
|
|
22
23
|
const exports = createExports(service);
|
|
23
24
|
const commonSource = ts.factory.createSourceFile([...imports, ...exports.allCommon], ts.factory.createToken(ts.SyntaxKind.EndOfFileToken), ts.NodeFlags.None);
|
|
24
|
-
const commonImport = ts.factory.createImportDeclaration(undefined, ts.factory.createImportClause(false, ts.factory.createIdentifier("* as Common"), undefined), ts.factory.createStringLiteral(
|
|
25
|
-
const commonExport = ts.factory.createExportDeclaration(undefined, false, undefined, ts.factory.createStringLiteral(
|
|
26
|
-
const queriesExport = ts.factory.createExportDeclaration(undefined, false, undefined, ts.factory.createStringLiteral(
|
|
25
|
+
const commonImport = ts.factory.createImportDeclaration(undefined, ts.factory.createImportClause(false, ts.factory.createIdentifier("* as Common"), undefined), ts.factory.createStringLiteral(`./${OpenApiRqFiles.common}`), undefined);
|
|
26
|
+
const commonExport = ts.factory.createExportDeclaration(undefined, false, undefined, ts.factory.createStringLiteral(`./${OpenApiRqFiles.common}`), undefined);
|
|
27
|
+
const queriesExport = ts.factory.createExportDeclaration(undefined, false, undefined, ts.factory.createStringLiteral(`./${OpenApiRqFiles.queries}`), undefined);
|
|
27
28
|
const mainSource = ts.factory.createSourceFile([commonImport, ...imports, ...exports.mainExports], ts.factory.createToken(ts.SyntaxKind.EndOfFileToken), ts.NodeFlags.None);
|
|
28
29
|
const suspenseSource = ts.factory.createSourceFile([commonImport, ...imports, ...exports.suspenseExports], ts.factory.createToken(ts.SyntaxKind.EndOfFileToken), ts.NodeFlags.None);
|
|
29
30
|
const indexSource = ts.factory.createSourceFile([commonExport, queriesExport], ts.factory.createToken(ts.SyntaxKind.EndOfFileToken), ts.NodeFlags.None);
|
|
31
|
+
const prefetchSource = ts.factory.createSourceFile([commonImport, ...imports, ...exports.allPrefetchExports], ts.factory.createToken(ts.SyntaxKind.EndOfFileToken), ts.NodeFlags.None);
|
|
30
32
|
return {
|
|
31
33
|
commonSource,
|
|
32
34
|
mainSource,
|
|
33
35
|
suspenseSource,
|
|
34
36
|
indexSource,
|
|
37
|
+
prefetchSource,
|
|
35
38
|
};
|
|
36
39
|
};
|
|
37
40
|
export const createSource = async ({ outputPath, version, serviceEndName, }) => {
|
|
38
|
-
const queriesFile = ts.createSourceFile(
|
|
39
|
-
const commonFile = ts.createSourceFile(
|
|
40
|
-
const suspenseFile = ts.createSourceFile(
|
|
41
|
-
const indexFile = ts.createSourceFile(
|
|
41
|
+
const queriesFile = ts.createSourceFile(`${OpenApiRqFiles.queries}.ts`, "", ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
|
|
42
|
+
const commonFile = ts.createSourceFile(`${OpenApiRqFiles.common}.ts`, "", ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
|
|
43
|
+
const suspenseFile = ts.createSourceFile(`${OpenApiRqFiles.suspense}.ts`, "", ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
|
|
44
|
+
const indexFile = ts.createSourceFile(`${OpenApiRqFiles.index}.ts`, "", ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
|
|
45
|
+
const prefetchFile = ts.createSourceFile(`${OpenApiRqFiles.prefetch}.ts`, "", ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
|
|
42
46
|
const printer = ts.createPrinter({
|
|
43
47
|
newLine: ts.NewLineKind.LineFeed,
|
|
44
48
|
removeComments: false,
|
|
45
49
|
});
|
|
46
|
-
const { commonSource, mainSource, suspenseSource, indexSource } = await createSourceFile(outputPath, serviceEndName);
|
|
47
|
-
const
|
|
50
|
+
const { commonSource, mainSource, suspenseSource, indexSource, prefetchSource, } = await createSourceFile(outputPath, serviceEndName);
|
|
51
|
+
const comment = `// generated with @7nohe/openapi-react-query-codegen@${version} \n\n`;
|
|
52
|
+
const commonResult = comment +
|
|
48
53
|
printer.printNode(ts.EmitHint.Unspecified, commonSource, commonFile);
|
|
49
|
-
const mainResult =
|
|
54
|
+
const mainResult = comment +
|
|
50
55
|
printer.printNode(ts.EmitHint.Unspecified, mainSource, queriesFile);
|
|
51
|
-
const suspenseResult =
|
|
56
|
+
const suspenseResult = comment +
|
|
52
57
|
printer.printNode(ts.EmitHint.Unspecified, suspenseSource, suspenseFile);
|
|
53
|
-
const indexResult =
|
|
58
|
+
const indexResult = comment +
|
|
54
59
|
printer.printNode(ts.EmitHint.Unspecified, indexSource, indexFile);
|
|
60
|
+
const prefetchResult = comment +
|
|
61
|
+
printer.printNode(ts.EmitHint.Unspecified, prefetchSource, prefetchFile);
|
|
55
62
|
return [
|
|
56
63
|
{
|
|
57
|
-
name:
|
|
64
|
+
name: `${OpenApiRqFiles.index}.ts`,
|
|
58
65
|
content: indexResult,
|
|
59
66
|
},
|
|
60
67
|
{
|
|
61
|
-
name:
|
|
68
|
+
name: `${OpenApiRqFiles.common}.ts`,
|
|
62
69
|
content: commonResult,
|
|
63
70
|
},
|
|
64
71
|
{
|
|
65
|
-
name:
|
|
72
|
+
name: `${OpenApiRqFiles.queries}.ts`,
|
|
66
73
|
content: mainResult,
|
|
67
74
|
},
|
|
68
75
|
{
|
|
69
|
-
name:
|
|
76
|
+
name: `${OpenApiRqFiles.suspense}.ts`,
|
|
70
77
|
content: suspenseResult,
|
|
71
78
|
},
|
|
79
|
+
{
|
|
80
|
+
name: `${OpenApiRqFiles.prefetch}.ts`,
|
|
81
|
+
content: prefetchResult,
|
|
82
|
+
},
|
|
72
83
|
];
|
|
73
84
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
|
-
import { BuildCommonTypeName, TContext, TData, TError, capitalizeFirstLetter, extractPropertiesFromObjectParam, getNameFromMethod, } from "./common.mjs";
|
|
2
|
+
import { BuildCommonTypeName, TContext, TData, TError, capitalizeFirstLetter, extractPropertiesFromObjectParam, getNameFromMethod, getShortType, } from "./common.mjs";
|
|
3
3
|
import { addJSDocToNode } from "./util.mjs";
|
|
4
4
|
/**
|
|
5
5
|
* Awaited<ReturnType<typeof myClass.myMethod>>
|
|
@@ -11,7 +11,7 @@ function generateAwaitedReturnType({ className, methodName, }) {
|
|
|
11
11
|
]),
|
|
12
12
|
]);
|
|
13
13
|
}
|
|
14
|
-
export const createUseMutation = ({
|
|
14
|
+
export const createUseMutation = ({ className, method, jsDoc, }) => {
|
|
15
15
|
const methodName = getNameFromMethod(method);
|
|
16
16
|
const awaitedResponseDataType = generateAwaitedReturnType({
|
|
17
17
|
className,
|
|
@@ -26,22 +26,9 @@ export const createUseMutation = ({ node, className, method, jsDoc = [], isDepre
|
|
|
26
26
|
const paramNodes = extractPropertiesFromObjectParam(param);
|
|
27
27
|
return paramNodes.map((refParam) => ts.factory.createPropertySignature(undefined, ts.factory.createIdentifier(refParam.name), refParam.optional
|
|
28
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))));
|
|
29
|
+
: undefined, ts.factory.createTypeReferenceNode(getShortType(refParam.type.getText(param)))));
|
|
34
30
|
})
|
|
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
|
-
)
|
|
31
|
+
.flat())
|
|
45
32
|
: ts.factory.createKeywordTypeNode(ts.SyntaxKind.VoidKeyword);
|
|
46
33
|
const exportHook = ts.factory.createVariableStatement([ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], ts.factory.createVariableDeclarationList([
|
|
47
34
|
ts.factory.createVariableDeclaration(ts.factory.createIdentifier(`use${className}${capitalizeFirstLetter(methodName)}`), undefined, undefined, ts.factory.createArrowFunction(undefined, ts.factory.createNodeArray([
|
|
@@ -90,7 +77,7 @@ export const createUseMutation = ({ node, className, method, jsDoc = [], isDepre
|
|
|
90
77
|
]),
|
|
91
78
|
]))),
|
|
92
79
|
], ts.NodeFlags.Const));
|
|
93
|
-
const hookWithJsDoc = addJSDocToNode(exportHook,
|
|
80
|
+
const hookWithJsDoc = addJSDocToNode(exportHook, jsDoc);
|
|
94
81
|
return {
|
|
95
82
|
mutationResult,
|
|
96
83
|
mutationHook: hookWithJsDoc,
|
package/dist/createUseQuery.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
|
-
import { BuildCommonTypeName, capitalizeFirstLetter, extractPropertiesFromObjectParam, getNameFromMethod, queryKeyConstraint, queryKeyGenericType, TData, TError, } from "./common.mjs";
|
|
2
|
+
import { BuildCommonTypeName, capitalizeFirstLetter, extractPropertiesFromObjectParam, getNameFromMethod, getShortType, queryKeyConstraint, queryKeyGenericType, TData, TError, } from "./common.mjs";
|
|
3
3
|
import { addJSDocToNode } from "./util.mjs";
|
|
4
4
|
export const createApiResponseType = ({ className, methodName, }) => {
|
|
5
5
|
/** Awaited<ReturnType<typeof myClass.myMethod>> */
|
|
@@ -25,14 +25,6 @@ export const createApiResponseType = ({ className, methodName, }) => {
|
|
|
25
25
|
responseDataType,
|
|
26
26
|
};
|
|
27
27
|
};
|
|
28
|
-
/**
|
|
29
|
-
* Replace the import("...") surrounding the type if there is one.
|
|
30
|
-
* This can happen when the type is imported from another file, but
|
|
31
|
-
* we are already importing all the types from that file.
|
|
32
|
-
*/
|
|
33
|
-
function getShortType(type) {
|
|
34
|
-
return type.replaceAll(/import\("[a-zA-Z\/\.-]*"\)\./g, "");
|
|
35
|
-
}
|
|
36
28
|
export function getRequestParamFromMethod(method) {
|
|
37
29
|
if (!method.getParameters().length) {
|
|
38
30
|
return null;
|
|
@@ -52,11 +44,7 @@ export function getRequestParamFromMethod(method) {
|
|
|
52
44
|
return ts.factory.createParameterDeclaration(undefined, undefined, ts.factory.createObjectBindingPattern(params.map((refParam) => ts.factory.createBindingElement(undefined, undefined, ts.factory.createIdentifier(refParam.name), undefined))), undefined, ts.factory.createTypeLiteralNode(params.map((refParam) => {
|
|
53
45
|
return ts.factory.createPropertySignature(undefined, ts.factory.createIdentifier(refParam.name), refParam.optional
|
|
54
46
|
? ts.factory.createToken(ts.SyntaxKind.QuestionToken)
|
|
55
|
-
: undefined,
|
|
56
|
-
// param.hasQuestionToken() ?? param.getInitializer()?.compilerNode
|
|
57
|
-
// ? ts.factory.createToken(ts.SyntaxKind.QuestionToken)
|
|
58
|
-
// : param.getQuestionTokenNode()?.compilerNode,
|
|
59
|
-
ts.factory.createTypeReferenceNode(refParam.typeName));
|
|
47
|
+
: undefined, ts.factory.createTypeReferenceNode(refParam.typeName));
|
|
60
48
|
})),
|
|
61
49
|
// if all params are optional, we create an empty object literal
|
|
62
50
|
// so the hook can be called without any parameters
|
|
@@ -85,11 +73,11 @@ export function createQueryKeyExport({ className, methodName, queryKey, }) {
|
|
|
85
73
|
ts.factory.createVariableDeclaration(ts.factory.createIdentifier(queryKey), undefined, undefined, ts.factory.createStringLiteral(`${className}${capitalizeFirstLetter(methodName)}`)),
|
|
86
74
|
], ts.NodeFlags.Const));
|
|
87
75
|
}
|
|
88
|
-
function hookNameFromMethod({ method, className, }) {
|
|
76
|
+
export function hookNameFromMethod({ method, className, }) {
|
|
89
77
|
const methodName = getNameFromMethod(method);
|
|
90
78
|
return `use${className}${capitalizeFirstLetter(methodName)}`;
|
|
91
79
|
}
|
|
92
|
-
function createQueryKeyFromMethod({ method, className, }) {
|
|
80
|
+
export function createQueryKeyFromMethod({ method, className, }) {
|
|
93
81
|
const customHookName = hookNameFromMethod({ method, className });
|
|
94
82
|
const queryKey = `${customHookName}Key`;
|
|
95
83
|
return queryKey;
|
|
@@ -99,7 +87,7 @@ function createQueryKeyFromMethod({ method, className, }) {
|
|
|
99
87
|
* @param queryString The type of query to use from react-query
|
|
100
88
|
* @param suffix The suffix to append to the hook name
|
|
101
89
|
*/
|
|
102
|
-
function createQueryHook({ queryString, suffix, responseDataType, requestParams, method, className, }) {
|
|
90
|
+
export function createQueryHook({ queryString, suffix, responseDataType, requestParams, method, className, }) {
|
|
103
91
|
const methodName = getNameFromMethod(method);
|
|
104
92
|
const customHookName = hookNameFromMethod({ method, className });
|
|
105
93
|
const queryKey = createQueryKeyFromMethod({ method, className });
|
|
@@ -151,7 +139,7 @@ function createQueryHook({ queryString, suffix, responseDataType, requestParams,
|
|
|
151
139
|
], ts.NodeFlags.Const));
|
|
152
140
|
return hookExport;
|
|
153
141
|
}
|
|
154
|
-
export const createUseQuery = ({
|
|
142
|
+
export const createUseQuery = ({ className, method, jsDoc, }) => {
|
|
155
143
|
const methodName = getNameFromMethod(method);
|
|
156
144
|
const queryKey = createQueryKeyFromMethod({ method, className });
|
|
157
145
|
const { apiResponse: defaultApiResponse, responseDataType } = createApiResponseType({
|
|
@@ -176,8 +164,8 @@ export const createUseQuery = ({ node, className, method, jsDoc = [], isDeprecat
|
|
|
176
164
|
method,
|
|
177
165
|
className,
|
|
178
166
|
});
|
|
179
|
-
const hookWithJsDoc = addJSDocToNode(queryHook,
|
|
180
|
-
const suspenseHookWithJsDoc = addJSDocToNode(suspenseQueryHook,
|
|
167
|
+
const hookWithJsDoc = addJSDocToNode(queryHook, jsDoc);
|
|
168
|
+
const suspenseHookWithJsDoc = addJSDocToNode(suspenseQueryHook, jsDoc);
|
|
181
169
|
const returnTypeExport = createReturnTypeExport({
|
|
182
170
|
className,
|
|
183
171
|
methodName,
|
package/dist/format.mjs
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { IndentationText, NewLineKind, Project, QuoteKind } from "ts-morph";
|
|
2
|
+
export const formatOutput = async (outputPath) => {
|
|
3
|
+
const project = new Project({
|
|
4
|
+
skipAddingFilesFromTsConfig: true,
|
|
5
|
+
manipulationSettings: {
|
|
6
|
+
indentationText: IndentationText.TwoSpaces,
|
|
7
|
+
newLineKind: NewLineKind.LineFeed,
|
|
8
|
+
quoteKind: QuoteKind.Double,
|
|
9
|
+
usePrefixAndSuffixTextForRename: false,
|
|
10
|
+
useTrailingCommas: true,
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
const sourceFiles = project.addSourceFilesAtPaths(`${outputPath}/**/*`);
|
|
14
|
+
const tasks = sourceFiles.map((sourceFile) => {
|
|
15
|
+
sourceFile.formatText();
|
|
16
|
+
sourceFile.fixMissingImports();
|
|
17
|
+
sourceFile.organizeImports();
|
|
18
|
+
return sourceFile.save();
|
|
19
|
+
});
|
|
20
|
+
await Promise.all(tasks);
|
|
21
|
+
};
|
package/dist/generate.mjs
CHANGED
|
@@ -1,40 +1,36 @@
|
|
|
1
1
|
import { createClient } from "@hey-api/openapi-ts";
|
|
2
2
|
import { print } from "./print.mjs";
|
|
3
|
-
import path from "path";
|
|
4
3
|
import { createSource } from "./createSource.mjs";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { buildQueriesOutputPath, buildRequestsOutputPath, formatOptions, } from "./common.mjs";
|
|
5
|
+
import { formatOutput } from "./format.mjs";
|
|
7
6
|
export async function generate(options, version) {
|
|
8
|
-
const openApiOutputPath =
|
|
9
|
-
|
|
10
|
-
// if the property is a string of number then convert it to a number
|
|
11
|
-
// if the property is a string of boolean then convert it to a boolean
|
|
12
|
-
const formattedOptions = Object.entries(options).reduce((acc, [key, value]) => {
|
|
13
|
-
const typedKey = key;
|
|
14
|
-
const typedValue = value;
|
|
15
|
-
const parsedNumber = safeParseNumber(typedValue);
|
|
16
|
-
if (!isNaN(parsedNumber)) {
|
|
17
|
-
acc[typedKey] = parsedNumber;
|
|
18
|
-
}
|
|
19
|
-
else if (value === "true") {
|
|
20
|
-
acc[typedKey] = true;
|
|
21
|
-
}
|
|
22
|
-
else if (value === "false") {
|
|
23
|
-
acc[typedKey] = false;
|
|
24
|
-
}
|
|
25
|
-
else {
|
|
26
|
-
acc[typedKey] = typedValue;
|
|
27
|
-
}
|
|
28
|
-
return acc;
|
|
29
|
-
}, options);
|
|
7
|
+
const openApiOutputPath = buildRequestsOutputPath(options.output);
|
|
8
|
+
const formattedOptions = formatOptions(options);
|
|
30
9
|
const config = {
|
|
31
|
-
|
|
10
|
+
base: formattedOptions.base,
|
|
11
|
+
client: formattedOptions.client,
|
|
12
|
+
debug: formattedOptions.debug,
|
|
13
|
+
dryRun: false,
|
|
14
|
+
enums: formattedOptions.enums,
|
|
15
|
+
exportCore: true,
|
|
16
|
+
format: formattedOptions.format,
|
|
17
|
+
input: formattedOptions.input,
|
|
18
|
+
lint: formattedOptions.lint,
|
|
32
19
|
output: openApiOutputPath,
|
|
20
|
+
request: formattedOptions.request,
|
|
21
|
+
schemas: {
|
|
22
|
+
export: !formattedOptions.noSchemas,
|
|
23
|
+
type: formattedOptions.schemaType,
|
|
24
|
+
},
|
|
25
|
+
services: {
|
|
26
|
+
export: true,
|
|
27
|
+
response: formattedOptions.serviceResponse,
|
|
28
|
+
},
|
|
29
|
+
types: {
|
|
30
|
+
dates: formattedOptions.useDateType,
|
|
31
|
+
export: true,
|
|
32
|
+
},
|
|
33
33
|
useOptions: true,
|
|
34
|
-
exportCore: true,
|
|
35
|
-
exportModels: true,
|
|
36
|
-
exportServices: true,
|
|
37
|
-
write: true,
|
|
38
34
|
};
|
|
39
35
|
await createClient(config);
|
|
40
36
|
const source = await createSource({
|
|
@@ -43,4 +39,6 @@ export async function generate(options, version) {
|
|
|
43
39
|
serviceEndName: "Service", // we are hard coding this because changing the service end name was depreciated in @hey-api/openapi-ts
|
|
44
40
|
});
|
|
45
41
|
await print(source, formattedOptions);
|
|
42
|
+
const queriesOutputPath = buildQueriesOutputPath(options.output);
|
|
43
|
+
await formatOutput(queriesOutputPath);
|
|
46
44
|
}
|
package/dist/print.mjs
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { mkdir, writeFile } from "fs/promises";
|
|
2
2
|
import path from "path";
|
|
3
|
-
import {
|
|
4
|
-
import { exists } from "./common.mjs";
|
|
3
|
+
import { buildQueriesOutputPath, exists } from "./common.mjs";
|
|
5
4
|
async function printGeneratedTS(result, options) {
|
|
6
|
-
const dir =
|
|
5
|
+
const dir = buildQueriesOutputPath(options.output);
|
|
7
6
|
const dirExists = await exists(dir);
|
|
8
7
|
if (!dirExists) {
|
|
9
8
|
await mkdir(dir, { recursive: true });
|
|
@@ -11,7 +10,7 @@ async function printGeneratedTS(result, options) {
|
|
|
11
10
|
await writeFile(path.join(dir, result.name), result.content);
|
|
12
11
|
}
|
|
13
12
|
export async function print(results, options) {
|
|
14
|
-
const outputPath = options.output
|
|
13
|
+
const outputPath = options.output;
|
|
15
14
|
const dirExists = await exists(outputPath);
|
|
16
15
|
if (!dirExists) {
|
|
17
16
|
await mkdir(outputPath);
|
package/dist/service.mjs
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
|
+
import { getClassNameFromClassNode, getClassesFromService, } from "./common.mjs";
|
|
3
|
+
import { serviceFileName } from "./constants.mjs";
|
|
2
4
|
export async function getServices(project) {
|
|
3
5
|
const node = project
|
|
4
6
|
.getSourceFiles()
|
|
5
|
-
.find((sourceFile) => sourceFile.getFilePath().includes(
|
|
7
|
+
.find((sourceFile) => sourceFile.getFilePath().includes(serviceFileName));
|
|
6
8
|
if (!node) {
|
|
7
9
|
throw new Error("No service node found");
|
|
8
10
|
}
|
|
@@ -16,29 +18,6 @@ export async function getServices(project) {
|
|
|
16
18
|
node,
|
|
17
19
|
};
|
|
18
20
|
}
|
|
19
|
-
function getClassesFromService(node) {
|
|
20
|
-
const klasses = node.getClasses();
|
|
21
|
-
if (!klasses.length) {
|
|
22
|
-
throw new Error("No classes found");
|
|
23
|
-
}
|
|
24
|
-
return klasses.map((klass) => {
|
|
25
|
-
const className = klass.getName();
|
|
26
|
-
if (!className) {
|
|
27
|
-
throw new Error("Class name not found");
|
|
28
|
-
}
|
|
29
|
-
return {
|
|
30
|
-
className,
|
|
31
|
-
klass,
|
|
32
|
-
};
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
function getClassNameFromClassNode(klass) {
|
|
36
|
-
const className = klass.getName();
|
|
37
|
-
if (!className) {
|
|
38
|
-
throw new Error("Class name not found");
|
|
39
|
-
}
|
|
40
|
-
return className;
|
|
41
|
-
}
|
|
42
21
|
function getMethodsFromService(node, klass) {
|
|
43
22
|
const methods = klass.getMethods();
|
|
44
23
|
if (!methods.length) {
|
|
@@ -78,7 +57,13 @@ function getMethodsFromService(node, klass) {
|
|
|
78
57
|
return [tsNode];
|
|
79
58
|
};
|
|
80
59
|
const children = getAllChildren(method.compilerNode);
|
|
81
|
-
|
|
60
|
+
// get all JSDoc comments
|
|
61
|
+
// this should be an array of 1 or 0
|
|
62
|
+
const jsDocs = children
|
|
63
|
+
.filter((c) => c.kind === ts.SyntaxKind.JSDoc)
|
|
64
|
+
.map((c) => c.getText(node.compilerNode));
|
|
65
|
+
// get the first JSDoc comment
|
|
66
|
+
const jsDoc = jsDocs?.[0];
|
|
82
67
|
const isDeprecated = children.some((c) => c.kind === ts.SyntaxKind.JSDocDeprecatedTag);
|
|
83
68
|
const className = getClassNameFromClassNode(klass);
|
|
84
69
|
return {
|
package/dist/util.mjs
CHANGED
|
@@ -1,28 +1,16 @@
|
|
|
1
1
|
import ts from "typescript";
|
|
2
|
-
export function addJSDocToNode(node,
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
.filter(Boolean)
|
|
16
|
-
// trim
|
|
17
|
-
.map((comment) => comment.trim())
|
|
18
|
-
// add * to each line
|
|
19
|
-
.map((comment) => `* ${comment}`)
|
|
20
|
-
// join lines
|
|
21
|
-
.join("\n")
|
|
22
|
-
// replace new lines with \n *
|
|
23
|
-
.replace(/\n/g, "\n * ");
|
|
24
|
-
const nodeWithJSDoc = jsDocString
|
|
25
|
-
? ts.addSyntheticLeadingComment(node, ts.SyntaxKind.MultiLineCommentTrivia, `*\n ${jsDocString}\n `, true)
|
|
26
|
-
: node;
|
|
2
|
+
export function addJSDocToNode(node, jsDoc) {
|
|
3
|
+
if (!jsDoc) {
|
|
4
|
+
return node;
|
|
5
|
+
}
|
|
6
|
+
// replace the first /** with *
|
|
7
|
+
// we do this because ts.addSyntheticLeadingComment will add /* to the beginning but we want /**
|
|
8
|
+
const removedFirstLine = jsDoc.trim().replace(/^\/\*\*/, "*");
|
|
9
|
+
// remove the last */ because ts.addSyntheticLeadingComment will add it
|
|
10
|
+
const removedSecondLine = removedFirstLine.replace(/\*\/$/, "");
|
|
11
|
+
const split = removedSecondLine.split("\n");
|
|
12
|
+
const trimmed = split.map((line) => line.trim());
|
|
13
|
+
const joined = trimmed.join("\n");
|
|
14
|
+
const nodeWithJSDoc = ts.addSyntheticLeadingComment(node, ts.SyntaxKind.MultiLineCommentTrivia, joined, true);
|
|
27
15
|
return nodeWithJSDoc;
|
|
28
16
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@7nohe/openapi-react-query-codegen",
|
|
3
|
-
"version": "1.1
|
|
3
|
+
"version": "1.2.1",
|
|
4
4
|
"description": "OpenAPI React Query Codegen",
|
|
5
5
|
"bin": {
|
|
6
6
|
"openapi-rq": "dist/cli.mjs"
|
|
@@ -31,20 +31,22 @@
|
|
|
31
31
|
"author": "Daiki Urata (@7nohe)",
|
|
32
32
|
"license": "MIT",
|
|
33
33
|
"devDependencies": {
|
|
34
|
-
"@hey-api/openapi-ts": "0.
|
|
34
|
+
"@hey-api/openapi-ts": "0.42.1",
|
|
35
35
|
"@types/node": "^20.10.6",
|
|
36
|
+
"@vitest/coverage-v8": "^1.5.0",
|
|
36
37
|
"commander": "^12.0.0",
|
|
37
38
|
"glob": "^10.3.10",
|
|
38
39
|
"rimraf": "^5.0.5",
|
|
39
40
|
"ts-morph": "^22.0.0",
|
|
40
|
-
"typescript": "^5.3.3"
|
|
41
|
+
"typescript": "^5.3.3",
|
|
42
|
+
"vitest": "^1.5.0"
|
|
41
43
|
},
|
|
42
44
|
"peerDependencies": {
|
|
43
|
-
"@hey-api/openapi-ts": "0.
|
|
44
|
-
"commander": "
|
|
45
|
-
"glob": "
|
|
46
|
-
"ts-morph": "
|
|
47
|
-
"typescript": "
|
|
45
|
+
"@hey-api/openapi-ts": "0.42.1",
|
|
46
|
+
"commander": "12.x",
|
|
47
|
+
"glob": "10.x",
|
|
48
|
+
"ts-morph": "22.x",
|
|
49
|
+
"typescript": "5.x"
|
|
48
50
|
},
|
|
49
51
|
"engines": {
|
|
50
52
|
"node": ">=14"
|
|
@@ -52,6 +54,7 @@
|
|
|
52
54
|
"scripts": {
|
|
53
55
|
"build": "rimraf dist && tsc -p tsconfig.json",
|
|
54
56
|
"preview": "npm run build && npm -C examples/react-app run generate:api",
|
|
55
|
-
"release": "npx git-ensure -a && npx bumpp --commit --tag --push"
|
|
57
|
+
"release": "npx git-ensure -a && npx bumpp --commit --tag --push",
|
|
58
|
+
"test": "vitest --coverage.enabled true"
|
|
56
59
|
}
|
|
57
60
|
}
|