Skip to content

Commit 326e0dc

Browse files
js2mejomarquez21
andauthored
Release 2.0.0 (acacode#86)
* fix: security configuration in methods (acacode#84) * chore: update generated schemas * feat: add --js option (to generate js api module) * docs: update CHANGELOG * feat: up typescript to 4.0.2; fix: problem at the top level comments in generated modules * BREAKING_CHANGE: responses from HttpClients returns Fetch responses with data and error fields * BREAKING_CHANGES: update client.mustache template, remove interpolations in ApiConfig interface * chore: add docs for toJS property Co-authored-by: Jose Enrique Marquez <jmrqz21@gmail.com>
1 parent a163999 commit 326e0dc

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+31983
-894
lines changed

.prettierignore

+2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,4 @@
11
tests/**/*.ts
2+
tests/**/schema.js
3+
tests/**/*.d.js
24
*.md

CHANGELOG.md

+18
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,23 @@
11
# next release
22

3+
Features:
4+
- `--js` CLI option. [[feature request]](https://github.com/acacode/swagger-typescript-api/issues/79)
5+
6+
BREAKING_CHANGES:
7+
- Requests returns `Promise<HttpResponse<Data, Error>>` type.
8+
`HttpResponse` it is [Fetch.Response](https://developer.mozilla.org/en-US/docs/Web/API/Response) wrapper with fields `data` and `error`
9+
Example:
10+
```ts
11+
const api = new Api()
12+
13+
//
14+
const response: HttpResponse<Data, Error> = await api.fruits.getAll()
15+
16+
response.data // Data (can be null if response.ok is false)
17+
response.error // Error (can be null if response.ok is true)
18+
```
19+
- Breaking changes in the `client.mustache` template. Needs to update local custom templates.
20+
321

422
# 1.12.0
523

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ Options:
4343
--union-enums generate all "enum" types as union types (T1 | T2 | TN) (default: false)
4444
--route-types generate type definitions for API routes (default: false)
4545
--no-client do not generate an API class
46+
--js generate js api module with declaration file (default: false)
4647
-h, --help output usage information
4748
```
4849

index.d.ts

+5
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ interface GenerateApiParams {
5050
* also add typings for bad responses
5151
*/
5252
generateResponses?: boolean;
53+
54+
/**
55+
* generate js api module with declaration file (default: false)
56+
*/
57+
toJS?: boolean;
5358
}
5459

5560
export declare function generateApi(

index.js

+15-2
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,23 @@ program
3232
)
3333
.option("--union-enums", 'generate all "enum" types as union types (T1 | T2 | TN)', false)
3434
.option("--route-types", "generate type definitions for API routes", false)
35-
.option("--no-client", "do not generate an API class", false);
35+
.option("--no-client", "do not generate an API class", false)
36+
.option("--js", "generate js api module with declaration file", false);
3637

3738
program.parse(process.argv);
3839

39-
const { path, output, name, templates, unionEnums, routeTypes, client, defaultAsSuccess, responses } = program;
40+
const {
41+
path,
42+
output,
43+
name,
44+
templates,
45+
unionEnums,
46+
routeTypes,
47+
client,
48+
defaultAsSuccess,
49+
responses,
50+
js,
51+
} = program;
4052

4153
generateApi({
4254
name,
@@ -49,4 +61,5 @@ generateApi({
4961
input: resolve(process.cwd(), path),
5062
output: resolve(process.cwd(), output || "."),
5163
templates: resolve(templates ? process.cwd() : __dirname, templates || "./src/templates"),
64+
toJS: !!js,
5265
});

package-lock.json

+3-4
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+6-5
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"cli:debug:json": "node --nolazy index.js -p ./swagger-test-cli.json -n swagger-test-cli.ts --union-enums",
99
"cli:debug:yaml": "node --nolazy index.js -p ./swagger-test-cli.yaml -n swagger-test-cli.ts",
1010
"cli:help": "node index.js -h",
11-
"test:all": "npm-run-all generate validate test:routeTypes test:noClient test:defaultAsSuccess test:responses test:templates test:unionEnums test:specProperty --continue-on-error",
11+
"test:all": "npm-run-all generate validate test:routeTypes test:noClient test:defaultAsSuccess test:responses test:templates test:unionEnums test:specProperty test:js --continue-on-error",
1212
"generate": "node tests/generate.js",
1313
"generate:debug": "node --nolazy tests/generate.js",
1414
"validate": "node tests/validate.js",
@@ -19,7 +19,8 @@
1919
"test:templates": "node tests/spec/templates/test.js",
2020
"test:unionEnums": "node tests/spec/unionEnums/test.js",
2121
"test:responses": "node tests/spec/responses/test.js",
22-
"test:specProperty": "node tests/spec/specProperty/test.js"
22+
"test:specProperty": "node tests/spec/specProperty/test.js",
23+
"test:js": "node tests/spec/js/test.js"
2324
},
2425
"author": "acacode",
2526
"license": "MIT",
@@ -31,8 +32,7 @@
3132
"@types/prettier": "^2.0.1",
3233
"husky": "^4.2.3",
3334
"npm-run-all": "^4.1.5",
34-
"pretty-quick": "^2.0.1",
35-
"typescript": "^3.9.3"
35+
"pretty-quick": "^2.0.1"
3636
},
3737
"dependencies": {
3838
"@types/swagger-schema-official": "2.0.21",
@@ -43,7 +43,8 @@
4343
"mustache": "^4.0.1",
4444
"prettier": "^2.0.5",
4545
"swagger-schema-official": "2.0.0-bab6bed",
46-
"swagger2openapi": "^6.0.3"
46+
"swagger2openapi": "^6.0.3",
47+
"typescript": "^4.0.2"
4748
},
4849
"bin": {
4950
"swagger-typescript-api": "index.js",

src/index.js

+37-20
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,13 @@ const { getTemplates } = require("./templates");
2222

2323
mustache.escape = (value) => value;
2424

25-
const prettierConfig = {
26-
printWidth: 120,
27-
tabWidth: 2,
28-
trailingComma: "all",
29-
parser: "typescript",
30-
};
25+
const prettierFormat = (content) =>
26+
prettier.format(content, {
27+
printWidth: 120,
28+
tabWidth: 2,
29+
trailingComma: "all",
30+
parser: "typescript",
31+
});
3132

3233
module.exports = {
3334
generateApi: ({
@@ -36,6 +37,7 @@ module.exports = {
3637
url,
3738
spec,
3839
name,
40+
toJS,
3941
templates = resolve(__dirname, config.templates),
4042
generateResponses = config.generateResponses,
4143
defaultResponseAsSuccess = config.defaultResponseAsSuccess,
@@ -87,21 +89,36 @@ module.exports = {
8789
routes: groupRoutes(routes),
8890
};
8991

90-
const sourceFile = prettier.format(
91-
[
92-
mustache.render(apiTemplate, configuration),
93-
generateRouteTypes ? mustache.render(routeTypesTemplate, configuration) : "",
94-
generateClient ? mustache.render(clientTemplate, configuration) : "",
95-
].join(""),
96-
prettierConfig,
97-
);
98-
99-
if (pathIsExist(output)) {
100-
createFile(output, name, sourceFile);
101-
console.log(`✔️ your typescript api file created in "${output}"`);
102-
}
92+
let sourceFileContent = [
93+
mustache.render(apiTemplate, configuration),
94+
generateRouteTypes ? mustache.render(routeTypesTemplate, configuration) : "",
95+
generateClient ? mustache.render(clientTemplate, configuration) : "",
96+
].join("");
97+
98+
if (toJS) {
99+
const {
100+
sourceFile: sourceJsFile,
101+
declarationFile,
102+
} = require("./translators/JavaScript").translate(name, sourceFileContent);
103103

104-
resolve(sourceFile);
104+
sourceJsFile.content = prettierFormat(sourceJsFile.content);
105+
declarationFile.content = prettierFormat(declarationFile.content);
106+
107+
if (pathIsExist(output)) {
108+
createFile(output, sourceJsFile.name, sourceJsFile.content);
109+
createFile(output, declarationFile.name, declarationFile.content);
110+
console.log(`✔️ your javascript api file created in "${output}"`);
111+
}
112+
resolve(sourceJsFile.content);
113+
} else {
114+
sourceFileContent = prettierFormat(sourceFileContent);
115+
116+
if (pathIsExist(output)) {
117+
createFile(output, name, sourceFileContent);
118+
console.log(`✔️ your typescript api file created in "${output}"`);
119+
}
120+
resolve(sourceFileContent);
121+
}
105122
})
106123
.catch((e) => {
107124
reject(e);

src/routes.js

+5-2
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ const createRequestsMap = (requestInfoByMethodsMap) => {
191191
);
192192
};
193193

194-
const parseRoutes = ({ paths }, parsedSchemas) =>
194+
const parseRoutes = ({ paths, security: globalSecurity }, parsedSchemas) =>
195195
_.entries(paths).reduce((routes, [route, requestInfoByMethodsMap]) => {
196196
if (route.startsWith("x-")) return routes;
197197

@@ -210,7 +210,10 @@ const parseRoutes = ({ paths }, parsedSchemas) =>
210210
tags,
211211
responses,
212212
} = requestInfo;
213-
const hasSecurity = !!(security && security.length);
213+
const hasSecurity = !!(
214+
(globalSecurity && globalSecurity.length) ||
215+
(security && security.length)
216+
);
214217

215218
const formDataParams = getRouteParams(parameters, "formData");
216219
const pathParams = getRouteParams(parameters, "path");

src/templates/api.mustache

-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
/* tslint:disable */
22
/* eslint-disable */
3-
43
/*
54
* ---------------------------------------------------------------
65
* ## THIS FILE WAS GENERATED VIA SWAGGER-TYPESCRIPT-API ##

src/templates/client.mustache

+40-18
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,10 @@ export type RequestParams = Omit<RequestInit, "body" | "method"> & {
77
export type RequestQueryParamsType = Record<string | number, any>;
88
{{/hasQueryRoutes}}
99

10-
type ApiConfig<{{#apiConfig.generic}}{{name}},{{/apiConfig.generic}}> = {
11-
{{#apiConfig.props}}
12-
{{name}}{{#optional}}?{{/optional}}: {{type}},
13-
{{/apiConfig.props}}
10+
interface ApiConfig<{{#apiConfig.generic}}{{name}},{{/apiConfig.generic}}> {
11+
baseUrl?: string;
12+
baseApiParams?: RequestParams;
13+
securityWorker?: (securityData: SecurityDataType) => RequestParams;
1414
}
1515

1616
{{#generateResponses}}
@@ -21,6 +21,10 @@ type TPromise<ResolveType, RejectType = any> = Omit<Promise<ResolveType>, "then"
2121
}
2222
{{/generateResponses}}
2323

24+
interface HttpResponse<D extends unknown, E extends unknown = unknown> extends Response {
25+
data: D | null;
26+
error: E | null;
27+
}
2428

2529
enum BodyType {
2630
Json,
@@ -32,7 +36,7 @@ enum BodyType {
3236
class HttpClient<{{#apiConfig.generic}}{{name}},{{/apiConfig.generic}}> {
3337
public baseUrl: string = "{{apiConfig.baseUrl}}";
3438
private securityData: SecurityDataType = (null as any);
35-
private securityWorker: ApiConfig<{{#apiConfig.generic}}{{name}},{{/apiConfig.generic}}>["securityWorker"] = (() => {}) as any
39+
private securityWorker: null | ApiConfig<{{#apiConfig.generic}}{{name}},{{/apiConfig.generic}}>["securityWorker"] = null;
3640

3741
private baseApiParams: RequestParams = {
3842
credentials: 'same-origin',
@@ -43,10 +47,8 @@ class HttpClient<{{#apiConfig.generic}}{{name}},{{/apiConfig.generic}}> {
4347
referrerPolicy: 'no-referrer',
4448
}
4549

46-
constructor({ {{#apiConfig.props}}{{name}},{{/apiConfig.props}} }: ApiConfig<{{#apiConfig.generic}}{{name}},{{/apiConfig.generic}}> = {}) {
47-
{{#apiConfig.props}}
48-
this.{{name}} = {{name}} || this.{{name}};
49-
{{/apiConfig.props}}
50+
constructor(apiConfig: ApiConfig<{{#apiConfig.generic}}{{name}},{{/apiConfig.generic}}> = {}) {
51+
Object.assign(this, apiConfig);
5052
}
5153

5254
public setSecurityData = (data: SecurityDataType) => {
@@ -93,10 +95,26 @@ class HttpClient<{{#apiConfig.generic}}{{name}},{{/apiConfig.generic}}> {
9395
}
9496
}
9597

96-
private safeParseResponse = <T = any, E = any>(response: Response): {{#generateResponses}}TPromise<T, E>{{/generateResponses}}{{^generateResponses}}Promise<T>{{/generateResponses}} =>
97-
response.json()
98-
.then(data => data)
99-
.catch(e => response.text);
98+
private safeParseResponse = <T = any, E = any>(response: Response): Promise<HttpResponse<T, E>> => {
99+
const r = response.clone() as HttpResponse<T, E>;
100+
r.data = null;
101+
r.error = null;
102+
103+
return response
104+
.json()
105+
.then((data) => {
106+
if (r.ok) {
107+
r.data = data;
108+
} else {
109+
r.error = data;
110+
}
111+
return r;
112+
})
113+
.catch((e) => {
114+
r.error = e;
115+
return r;
116+
});
117+
}
100118

101119
public request = <T = any, E = any>(
102120
path: string,
@@ -105,17 +123,21 @@ class HttpClient<{{#apiConfig.generic}}{{name}},{{/apiConfig.generic}}> {
105123
body?: any,
106124
bodyType?: BodyType,
107125
secureByDefault?: boolean,
108-
): {{#generateResponses}}TPromise<T, E>{{/generateResponses}}{{^generateResponses}}Promise<T>{{/generateResponses}} =>
109-
fetch(`${this.baseUrl}${path}`, {
110-
// @ts-ignore
111-
...this.mergeRequestOptions(params, (secureByDefault || secure) && this.securityWorker(this.securityData)),
126+
): {{#generateResponses}}TPromise<HttpResponse<T, E>>{{/generateResponses}}{{^generateResponses}}Promise<HttpResponse<T>>{{/generateResponses}} => {
127+
const requestUrl = `${this.baseUrl}${path}`;
128+
const secureOptions = (secureByDefault || secure) && this.securityWorker ? this.securityWorker(this.securityData) : {};
129+
const requestOptions = {
130+
...this.mergeRequestOptions(params, secureOptions),
112131
method,
113132
body: body ? this.bodyFormatters[bodyType || BodyType.Json](body) : null,
114-
}).then(async response => {
133+
}
134+
135+
return fetch(requestUrl, requestOptions).then(async response => {
115136
const data = await this.safeParseResponse<T, E>(response);
116137
if (!response.ok) throw data
117138
return data
118139
})
140+
}
119141
}
120142

121143
{{#apiConfig.hasDescription}}

0 commit comments

Comments
 (0)