Skip to content

Commit 2d02a8e

Browse files
author
svolkov
committed
Merge branch 'next' of https://github.com/js2me/swagger-typescript-api into next
2 parents fd43b16 + 06fbc7c commit 2d02a8e

33 files changed

+446
-455
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,10 @@ Usage: swagger-typescript-api [options]
3232
Options:
3333
-v, --version output the current version
3434
-p, --path <path> path/url to swagger scheme
35-
-o, --output <output> output path of typescript api file (default: ".")
35+
-o, --output <output> output path of typescript api file (default: "./")
3636
-n, --name <name> name of output typescript api file (default: "api.ts")
37+
--route-types generate type definitions for API routes (default: false)
38+
--no-client do not generate an API class
3739
-h, --help output usage information
3840
```
3941

index.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,19 @@ program
1616
.description("Generate api via swagger scheme.\nSupports OA 3.0, 2.0, JSON, yaml.")
1717
.requiredOption('-p, --path <path>', 'path/url to swagger scheme')
1818
.option('-o, --output <output>', 'output path of typescript api file', './')
19-
.option('-n, --name <name>', 'name of output typescript api file', 'api.ts');
19+
.option('-n, --name <name>', 'name of output typescript api file', 'api.ts')
20+
.option('--route-types', 'generate type definitions for API routes', false)
21+
.option('--no-client', 'do not generate an API class', false);
2022

2123
program.parse(process.argv);
2224

23-
const { path, output, name } = program;
25+
const { path, output, name, routeTypes, client } = program;
2426

2527
generateApi({
2628
name,
2729
url: path,
30+
generateRouteTypes: routeTypes,
31+
generateClient: client,
2832
input: resolve(process.cwd(), path),
2933
output: resolve(process.cwd(), output || '.')
3034
})

src/index.js

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,21 @@ const { getSwaggerObject } = require('./swagger');
1111
mustache.escape = value => value
1212

1313
module.exports = {
14-
generateApi: ({ input, output, url, name }) => new Promise((resolve, reject) => {
14+
generateApi: ({
15+
input,
16+
output,
17+
url,
18+
name,
19+
generateRouteTypes = true,
20+
generateClient = true,
21+
}) => new Promise((resolve, reject) => {
1522
getSwaggerObject(input, url).then(({ info, paths, servers, components }) => {
1623
console.log('☄️ start generating your typescript api')
1724

18-
const apiTemplate = fs.readFileSync(path.resolve(__dirname, './templates/api.mustache'), { encoding: 'UTF-8' })
19-
25+
const apiTemplate = fs.readFileSync(path.resolve(__dirname, './templates/api.mustache'), 'utf-8');
26+
const clientTemplate = fs.readFileSync(path.resolve(__dirname, './templates/client.mustache'), 'utf-8');
27+
const routeTypesTemplate = fs.readFileSync(path.resolve(__dirname, './templates/route-types.mustache'), 'utf-8');
28+
2029
const parsedSchemas = _.map(_.get(components, "schemas"), parseSchema)
2130
const routes = parseRoutes(paths, parsedSchemas, components);
2231
const hasSecurityRoutes = routes.some(route => route.security);
@@ -29,9 +38,13 @@ module.exports = {
2938
hasSecurityRoutes,
3039
hasQueryRoutes,
3140
routes: groupRoutes(routes),
32-
}
41+
};
3342

34-
const sourceFile = mustache.render(apiTemplate, configuration)
43+
const sourceFile = [
44+
mustache.render(apiTemplate, configuration),
45+
generateRouteTypes ? mustache.render(routeTypesTemplate, configuration) : '',
46+
generateClient ? mustache.render(clientTemplate, configuration) : '',
47+
].join('');
3548

3649
if (output && fs.existsSync(output)) {
3750
fs.writeFileSync(path.resolve(__dirname, output, `./${name}`), sourceFile, _.noop)

src/templates/api.mustache

Lines changed: 0 additions & 159 deletions
Original file line numberDiff line numberDiff line change
@@ -19,162 +19,3 @@
1919
{{/description}}
2020
export {{typeIdentifier}} {{name}} {{content}}
2121
{{/modelTypes}}
22-
23-
export type RequestParams = Omit<RequestInit, "body" | "method"> & {
24-
secure?: boolean;
25-
}
26-
27-
type ApiConfig{{apiConfig.generic}} = {
28-
{{#apiConfig.props}}
29-
{{name}}{{#optional}}?{{/optional}}: {{type}},
30-
{{/apiConfig.props}}
31-
}
32-
33-
{{#routes}}
34-
{{#outOfModule}}
35-
{{#routes}}
36-
37-
/**
38-
{{#comments}}
39-
* {{.}}
40-
{{/comments}}
41-
*/
42-
export namespace {{pascalName}} {
43-
export type RequestQuery = {{queryType}};
44-
export type RequestBody = {{bodyType}};
45-
export type ResponseBody = {{returnType}};
46-
}
47-
{{/routes}}
48-
{{/outOfModule}}
49-
50-
{{#combined}}
51-
export namespace {{moduleName}} {
52-
{{#routes}}
53-
54-
/**
55-
{{#comments}}
56-
* {{.}}
57-
{{/comments}}
58-
*/
59-
export namespace {{pascalName}} {
60-
export type RequestQuery = {{queryType}};
61-
export type RequestBody = {{bodyType}};
62-
export type ResponseBody = {{returnType}};
63-
}
64-
{{/routes}}
65-
}
66-
{{/combined}}
67-
{{/routes}}
68-
69-
{{#apiConfig.description}}
70-
/** {{.}} */
71-
{{/apiConfig.description}}
72-
export class Api{{apiConfig.generic}} {
73-
74-
public baseUrl = "{{apiConfig.baseUrl}}";
75-
public title = "{{apiConfig.title}}";
76-
public version = "{{apiConfig.version}}";
77-
78-
private securityData: SecurityDataType = (null as any);
79-
private securityWorker: ApiConfig{{apiConfig.generic}}["securityWorker"] = (() => {}) as any
80-
81-
private baseApiParams: RequestParams = {
82-
credentials: 'same-origin',
83-
headers: {
84-
'Content-Type': 'application/json'
85-
},
86-
redirect: 'follow',
87-
referrerPolicy: 'no-referrer',
88-
}
89-
90-
constructor({ {{#apiConfig.props}}{{name}},{{/apiConfig.props}} }: ApiConfig{{apiConfig.generic}} = {}) {
91-
{{#apiConfig.props}}
92-
this.{{name}} = {{name}} || this.{{name}};
93-
{{/apiConfig.props}}
94-
}
95-
96-
public setSecurityData = (data: SecurityDataType) => {
97-
this.securityData = data
98-
}
99-
100-
{{#hasQueryRoutes}}
101-
private addQueryParams(query: object): string {
102-
const keys = Object.keys(query);
103-
return keys.length ? (
104-
'?' +
105-
keys.reduce((paramsArray, param) => [
106-
...paramsArray,
107-
param + '=' + encodeURIComponent(query[param])
108-
], []).join('&')
109-
) : ''
110-
}
111-
{{/hasQueryRoutes}}
112-
113-
private mergeRequestOptions(params: RequestParams, securityParams?: RequestParams): RequestParams {
114-
return {
115-
...this.baseApiParams,
116-
...params,
117-
...(securityParams || {}),
118-
headers: {
119-
...(this.baseApiParams.headers || {}),
120-
...(params.headers || {}),
121-
...((securityParams && securityParams.headers) || {})
122-
}
123-
}
124-
}
125-
126-
private safeParseResponse = <T = any>(response: Response): Promise<T> =>
127-
response.json()
128-
.then(data => data)
129-
.catch(e => response.text);
130-
131-
public request = <T = any>(
132-
path: string,
133-
method: string,
134-
{ secure, ...params }: RequestParams = {},
135-
body?: any,
136-
secureByDefault?: boolean,
137-
): Promise<T> =>
138-
fetch(`${this.baseUrl}${path}`, {
139-
// @ts-ignore
140-
...this.mergeRequestOptions(params, (secureByDefault || secure) && this.securityWorker(this.securityData)),
141-
method,
142-
body: body ? JSON.stringify(body) : null,
143-
}).then(async response => {
144-
const data = await this.safeParseResponse<T>(response);
145-
if (!response.ok) throw data
146-
return data
147-
})
148-
149-
{{#routes}}
150-
151-
{{#outOfModule}}
152-
153-
154-
/**
155-
{{#comments}}
156-
* {{.}}
157-
{{/comments}}
158-
*/
159-
{{name}} = ({{#args}}{{name}}{{#optional}}?{{/optional}}: {{type}}, {{/args}}params?: RequestParams) =>
160-
this.request<{{returnType}}>(`{{path}}{{#hasQuery}}${this.addQueryParams(query)}{{/hasQuery}}`, "{{method}}", params, {{#bodyArg}}{{.}}{{#security}}, {{/security}}{{/bodyArg}}{{#security}}true{{/security}})
161-
{{/outOfModule}}
162-
163-
{{#combined}}
164-
{{moduleName}} = {
165-
{{#routes}}
166-
167-
168-
/**
169-
{{#comments}}
170-
* {{.}}
171-
{{/comments}}
172-
*/
173-
{{name}}: ({{#args}}{{name}}{{#optional}}?{{/optional}}: {{type}}, {{/args}}params?: RequestParams) =>
174-
this.request<{{returnType}}>(`{{path}}{{#hasQuery}}${this.addQueryParams(query)}{{/hasQuery}}`, "{{method}}", params, {{#bodyArg}}{{.}}{{#security}}, {{/security}}{{/bodyArg}}{{#security}}true{{/security}}),
175-
{{/routes}}
176-
}
177-
{{/combined}}
178-
{{/routes}}
179-
180-
}

src/templates/client.mustache

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
2+
export type RequestParams = Omit<RequestInit, "body" | "method"> & {
3+
secure?: boolean;
4+
}
5+
6+
type ApiConfig{{apiConfig.generic}} = {
7+
{{#apiConfig.props}}
8+
{{name}}{{#optional}}?{{/optional}}: {{type}},
9+
{{/apiConfig.props}}
10+
}
11+
12+
{{#apiConfig.description}}
13+
/** {{.}} */
14+
{{/apiConfig.description}}
15+
export class Api{{apiConfig.generic}} {
16+
17+
public baseUrl = "{{apiConfig.baseUrl}}";
18+
public title = "{{apiConfig.title}}";
19+
public version = "{{apiConfig.version}}";
20+
21+
private securityData: SecurityDataType = (null as any);
22+
private securityWorker: ApiConfig{{apiConfig.generic}}["securityWorker"] = (() => {}) as any
23+
24+
private baseApiParams: RequestParams = {
25+
credentials: 'same-origin',
26+
headers: {
27+
'Content-Type': 'application/json'
28+
},
29+
redirect: 'follow',
30+
referrerPolicy: 'no-referrer',
31+
}
32+
33+
constructor({ {{#apiConfig.props}}{{name}},{{/apiConfig.props}} }: ApiConfig{{apiConfig.generic}} = {}) {
34+
{{#apiConfig.props}}
35+
this.{{name}} = {{name}} || this.{{name}};
36+
{{/apiConfig.props}}
37+
}
38+
39+
public setSecurityData = (data: SecurityDataType) => {
40+
this.securityData = data
41+
}
42+
43+
{{#hasQueryRoutes}}
44+
private addQueryParams(query: object): string {
45+
const keys = Object.keys(query);
46+
return keys.length ? (
47+
'?' +
48+
keys.reduce((paramsArray, param) => [
49+
...paramsArray,
50+
param + '=' + encodeURIComponent(query[param])
51+
], []).join('&')
52+
) : ''
53+
}
54+
{{/hasQueryRoutes}}
55+
56+
private mergeRequestOptions(params: RequestParams, securityParams?: RequestParams): RequestParams {
57+
return {
58+
...this.baseApiParams,
59+
...params,
60+
...(securityParams || {}),
61+
headers: {
62+
...(this.baseApiParams.headers || {}),
63+
...(params.headers || {}),
64+
...((securityParams && securityParams.headers) || {})
65+
}
66+
}
67+
}
68+
69+
private safeParseResponse = <T = any>(response: Response): Promise<T> =>
70+
response.json()
71+
.then(data => data)
72+
.catch(e => response.text);
73+
74+
public request = <T = any>(
75+
path: string,
76+
method: string,
77+
{ secure, ...params }: RequestParams = {},
78+
body?: any,
79+
secureByDefault?: boolean,
80+
): Promise<T> =>
81+
fetch(`${this.baseUrl}${path}`, {
82+
// @ts-ignore
83+
...this.mergeRequestOptions(params, (secureByDefault || secure) && this.securityWorker(this.securityData)),
84+
method,
85+
body: body ? JSON.stringify(body) : null,
86+
}).then(async response => {
87+
const data = await this.safeParseResponse<T>(response);
88+
if (!response.ok) throw data
89+
return data
90+
})
91+
92+
{{#routes}}
93+
94+
{{#outOfModule}}
95+
96+
97+
/**
98+
{{#comments}}
99+
* {{.}}
100+
{{/comments}}
101+
*/
102+
{{name}} = ({{#args}}{{name}}{{#optional}}?{{/optional}}: {{type}}, {{/args}}params?: RequestParams) =>
103+
this.request<{{returnType}}>(`{{path}}{{#hasQuery}}${this.addQueryParams(query)}{{/hasQuery}}`, "{{method}}", params, {{#bodyArg}}{{.}}{{#security}}, {{/security}}{{/bodyArg}}{{#security}}true{{/security}})
104+
{{/outOfModule}}
105+
106+
{{#combined}}
107+
{{moduleName}} = {
108+
{{#routes}}
109+
110+
111+
/**
112+
{{#comments}}
113+
* {{.}}
114+
{{/comments}}
115+
*/
116+
{{name}}: ({{#args}}{{name}}{{#optional}}?{{/optional}}: {{type}}, {{/args}}params?: RequestParams) =>
117+
this.request<{{returnType}}>(`{{path}}{{#hasQuery}}${this.addQueryParams(query)}{{/hasQuery}}`, "{{method}}", params, {{#bodyArg}}{{.}}{{#security}}, {{/security}}{{/bodyArg}}{{#security}}true{{/security}}),
118+
{{/routes}}
119+
}
120+
{{/combined}}
121+
{{/routes}}
122+
123+
}

src/templates/route-types.mustache

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
{{#routes}}
2+
{{#outOfModule}}
3+
{{#routes}}
4+
5+
/**
6+
{{#comments}}
7+
* {{.}}
8+
{{/comments}}
9+
*/
10+
export namespace {{pascalName}} {
11+
export type RequestQuery = {{queryType}};
12+
export type RequestBody = {{bodyType}};
13+
export type ResponseBody = {{returnType}};
14+
}
15+
{{/routes}}
16+
{{/outOfModule}}
17+
18+
{{#combined}}
19+
export namespace {{moduleName}} {
20+
{{#routes}}
21+
22+
/**
23+
{{#comments}}
24+
* {{.}}
25+
{{/comments}}
26+
*/
27+
export namespace {{pascalName}} {
28+
export type RequestQuery = {{queryType}};
29+
export type RequestBody = {{bodyType}};
30+
export type ResponseBody = {{returnType}};
31+
}
32+
{{/routes}}
33+
}
34+
{{/combined}}
35+
{{/routes}}

0 commit comments

Comments
 (0)