swagger-typescript-api 10.0.2 → 11.0.0--alpha

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.
Files changed (55) hide show
  1. package/README.md +263 -41
  2. package/index.d.ts +97 -0
  3. package/index.js +242 -115
  4. package/package.json +121 -116
  5. package/src/code-formatter.js +101 -0
  6. package/src/code-gen-process.js +456 -0
  7. package/src/configuration.js +425 -0
  8. package/src/constants.js +14 -31
  9. package/src/index.js +20 -271
  10. package/src/schema-components-map.js +60 -0
  11. package/src/schema-parser/schema-formatters.js +145 -0
  12. package/src/schema-parser/schema-parser.js +497 -0
  13. package/src/schema-parser/schema-routes.js +902 -0
  14. package/src/swagger-schema-resolver.js +187 -0
  15. package/src/templates.js +174 -155
  16. package/src/translators/JavaScript.js +3 -14
  17. package/src/type-name.js +79 -0
  18. package/src/util/file-system.js +76 -0
  19. package/src/{utils → util}/id.js +9 -9
  20. package/src/util/internal-case.js +5 -0
  21. package/src/util/logger.js +100 -0
  22. package/src/{utils/resolveName.js → util/name-resolver.js} +94 -97
  23. package/src/util/object-assign.js +11 -0
  24. package/src/util/pascal-case.js +5 -0
  25. package/src/{utils → util}/random.js +14 -14
  26. package/templates/base/data-contract-jsdoc.ejs +29 -24
  27. package/templates/base/data-contracts.ejs +3 -3
  28. package/templates/base/interface-data-contract.ejs +1 -0
  29. package/templates/base/route-docs.ejs +3 -4
  30. package/templates/base/route-type.ejs +2 -2
  31. package/templates/default/procedure-call.ejs +2 -2
  32. package/templates/default/route-types.ejs +2 -2
  33. package/templates/modular/api.ejs +2 -2
  34. package/templates/modular/procedure-call.ejs +2 -2
  35. package/templates/modular/route-types.ejs +2 -2
  36. package/src/apiConfig.js +0 -30
  37. package/src/common.js +0 -28
  38. package/src/components.js +0 -91
  39. package/src/config.js +0 -106
  40. package/src/filePrefix.js +0 -14
  41. package/src/files.js +0 -56
  42. package/src/formatFileContent.js +0 -81
  43. package/src/logger.js +0 -59
  44. package/src/modelNames.js +0 -78
  45. package/src/modelTypes.js +0 -31
  46. package/src/output.js +0 -165
  47. package/src/prettierOptions.js +0 -23
  48. package/src/render/utils/fmtToJSDocLine.js +0 -10
  49. package/src/render/utils/index.js +0 -31
  50. package/src/render/utils/templateRequire.js +0 -17
  51. package/src/routeNames.js +0 -46
  52. package/src/routes.js +0 -809
  53. package/src/schema.js +0 -474
  54. package/src/swagger.js +0 -152
  55. package/src/typeFormatters.js +0 -121
@@ -0,0 +1,101 @@
1
+ const _ = require("lodash");
2
+ const ts = require("typescript");
3
+ const prettier = require("prettier");
4
+
5
+ class CodeFormatter {
6
+ /**
7
+ * @type {Configuration}
8
+ */
9
+ config;
10
+
11
+ constructor(config) {
12
+ this.config = config;
13
+ }
14
+
15
+ removeUnusedImports = (content) => {
16
+ const tempFileName = "file.ts";
17
+
18
+ const host = new TsLanguageServiceHost(tempFileName, content);
19
+ const languageService = ts.createLanguageService(host);
20
+
21
+ const fileTextChanges = languageService.organizeImports(
22
+ { type: "file", fileName: tempFileName },
23
+ { newLineCharacter: ts.sys.newLine },
24
+ )[0];
25
+
26
+ if (fileTextChanges && fileTextChanges.textChanges.length) {
27
+ return _.reduceRight(
28
+ fileTextChanges.textChanges,
29
+ (content, { span, newText }) =>
30
+ `${content.slice(0, span.start)}${newText}${content.slice(span.start + span.length)}`,
31
+ content,
32
+ );
33
+ }
34
+
35
+ return content;
36
+ };
37
+
38
+ prettierFormat = (content) => {
39
+ return prettier.format(content, this.config.prettierOptions);
40
+ };
41
+
42
+ formatCode = (code, { removeUnusedImports = true, prettierFormat = true } = {}) => {
43
+ if (removeUnusedImports) {
44
+ code = this.removeUnusedImports(code);
45
+ }
46
+ if (prettierFormat) {
47
+ code = this.prettierFormat(code);
48
+ }
49
+ return code;
50
+ };
51
+ }
52
+
53
+ class TsLanguageServiceHost {
54
+ constructor(fileName, content) {
55
+ const tsconfig = ts.findConfigFile(fileName, ts.sys.fileExists);
56
+
57
+ Object.assign(this, {
58
+ fileName,
59
+ content,
60
+ compilerOptions: tsconfig
61
+ ? ts.convertCompilerOptionsFromJson(ts.readConfigFile(tsconfig, ts.sys.readFile).config.compilerOptions).options
62
+ : ts.getDefaultCompilerOptions(),
63
+ });
64
+ }
65
+
66
+ getNewLine() {
67
+ return "newLine" in ts.sys ? ts.sys.newLine : "\n";
68
+ }
69
+ getScriptFileNames() {
70
+ return [this.fileName];
71
+ }
72
+ getCompilationSettings() {
73
+ return this.compilerOptions;
74
+ }
75
+ getDefaultLibFileName() {
76
+ return ts.getDefaultLibFileName(this.getCompilationSettings());
77
+ }
78
+ getCurrentDirectory() {
79
+ return process.cwd();
80
+ }
81
+ getScriptVersion() {
82
+ return ts.version;
83
+ }
84
+ getScriptSnapshot() {
85
+ return ts.ScriptSnapshot.fromString(this.content);
86
+ }
87
+ readFile(fileName, encoding) {
88
+ if (fileName === this.fileName) {
89
+ return this.content;
90
+ }
91
+
92
+ return ts.sys.readFile(fileName, encoding);
93
+ }
94
+ fileExists(path) {
95
+ return ts.sys.fileExists(path);
96
+ }
97
+ }
98
+
99
+ module.exports = {
100
+ CodeFormatter,
101
+ };
@@ -0,0 +1,456 @@
1
+ const { SwaggerSchemaResolver } = require("./swagger-schema-resolver.js");
2
+ const { SchemaComponentsMap } = require("./schema-components-map.js");
3
+ const { NameResolver } = require("./util/name-resolver");
4
+ const { Logger } = require("./util/logger.js");
5
+ const { TypeName } = require("./type-name.js");
6
+ const _ = require("lodash");
7
+ const { SchemaParser } = require("./schema-parser/schema-parser.js");
8
+ const { SchemaRoutes } = require("./schema-parser/schema-routes.js");
9
+ const { Configuration } = require("./configuration.js");
10
+ const { FileSystem } = require("./util/file-system");
11
+ const { Templates } = require("./templates");
12
+ const { translate: translateToJS } = require("./translators/JavaScript");
13
+ const ts = require("typescript");
14
+ const { CodeFormatter } = require("./code-formatter");
15
+ const { pascalCase } = require("./util/pascal-case");
16
+ const { internalCase } = require("./util/internal-case");
17
+
18
+ class CodeGenProcess {
19
+ /**
20
+ * @type {Configuration}
21
+ */
22
+ config;
23
+ /**
24
+ * @type {SwaggerSchemaResolver}
25
+ */
26
+ swaggerSchemaResolver;
27
+ /**
28
+ * @type {SchemaComponentsMap}
29
+ */
30
+ schemaComponentMap;
31
+ /**
32
+ * @type {Logger}
33
+ */
34
+ logger;
35
+ /**
36
+ * @type {TypeName}
37
+ */
38
+ typeName;
39
+ /**
40
+ * @type {SchemaParser}
41
+ */
42
+ schemaParser;
43
+ /**
44
+ * @type {SchemaRoutes}
45
+ */
46
+ schemaRoutes;
47
+ /**
48
+ * @type {FileSystem}
49
+ */
50
+ fileSystem;
51
+ /**
52
+ * @type {CodeFormatter}
53
+ */
54
+ codeFormatter;
55
+
56
+ constructor(config) {
57
+ this.config = new Configuration(config);
58
+ this.logger = new Logger(this.config);
59
+ this.fileSystem = new FileSystem();
60
+ this.swaggerSchemaResolver = new SwaggerSchemaResolver(this.config, this.logger, this.fileSystem);
61
+ this.schemaComponentMap = new SchemaComponentsMap(this.config);
62
+ this.typeName = new TypeName(this.config, this.logger);
63
+ this.templates = new Templates(this.config, this.logger, this.fileSystem, this.getRenderTemplateData);
64
+ this.codeFormatter = new CodeFormatter(this.config);
65
+ this.schemaParser = new SchemaParser(
66
+ this.config,
67
+ this.logger,
68
+ this.templates,
69
+ this.schemaComponentMap,
70
+ this.typeName,
71
+ );
72
+ this.schemaRoutes = new SchemaRoutes(
73
+ this.config,
74
+ this.schemaParser,
75
+ this.schemaComponentMap,
76
+ this.logger,
77
+ this.templates,
78
+ this.typeName,
79
+ );
80
+ }
81
+
82
+ async start() {
83
+ this.config.update({ templatePaths: this.templates.getTemplatePaths(this.config) });
84
+ this.config.update({ templatesToRender: this.templates.getTemplates(this.config) });
85
+
86
+ const swagger = await this.swaggerSchemaResolver.create();
87
+
88
+ this.swaggerSchemaResolver.fixSwaggerSchema(swagger);
89
+
90
+ this.config.update({
91
+ swaggerSchema: swagger.usageSchema,
92
+ originalSchema: swagger.originalSchema,
93
+ });
94
+
95
+ this.logger.event("start generating your typescript api");
96
+
97
+ this.config.update(this.config.hooks.onInit(this.config) || this.config);
98
+
99
+ this.schemaComponentMap.processSchema(swagger.usageSchema);
100
+
101
+ const componentSchemaNames = this.schemaComponentMap.filter("schemas").map((c) => c.typeName);
102
+
103
+ this.config.componentTypeNameResolver.reserve(componentSchemaNames);
104
+
105
+ const parsedSchemas = _.map(_.get(swagger.usageSchema.components, "schemas"), (schema, typeName) =>
106
+ this.schemaParser.parseSchema(schema, typeName),
107
+ );
108
+
109
+ this.schemaRoutes.attachSchema({
110
+ usageSchema: swagger.usageSchema,
111
+ parsedSchemas,
112
+ });
113
+
114
+ const usageComponentSchemas = this.schemaComponentMap.filter("schemas");
115
+ const sortByProperty = (propertyName) => (o1, o2) => {
116
+ if (o1[propertyName] > o2[propertyName]) {
117
+ return 1;
118
+ }
119
+ if (o1[propertyName] < o2[propertyName]) {
120
+ return -1;
121
+ }
122
+ return 0;
123
+ };
124
+
125
+ const sortSchemas = (schemas) => {
126
+ if (this.config.sortTypes) {
127
+ return schemas.sort(sortByProperty("typeName")).map((schema) => {
128
+ if (schema.rawTypeData?.properties) {
129
+ return {
130
+ ...schema,
131
+ rawTypeData: {
132
+ ...schema.rawTypeData,
133
+ $parsed: schema.rawTypeData["$parsed"] && {
134
+ ...schema.rawTypeData["$parsed"],
135
+ content: Array.isArray(schema.rawTypeData["$parsed"].content)
136
+ ? schema.rawTypeData["$parsed"].content.sort(sortByProperty("name"))
137
+ : schema.rawTypeData["$parsed"].content,
138
+ },
139
+ },
140
+ };
141
+ }
142
+ return schema;
143
+ });
144
+ }
145
+ return schemas;
146
+ };
147
+
148
+ const rawConfiguration = {
149
+ apiConfig: this.createApiConfig(swagger.usageSchema),
150
+ config: this.config,
151
+ modelTypes: _.map(sortSchemas(usageComponentSchemas), this.prepareModelType).filter(Boolean),
152
+ rawModelTypes: usageComponentSchemas,
153
+ hasSecurityRoutes: this.schemaRoutes.hasSecurityRoutes,
154
+ hasQueryRoutes: this.schemaRoutes.hasQueryRoutes,
155
+ hasFormDataRoutes: this.schemaRoutes.hasFormDataRoutes,
156
+ generateResponses: this.config.generateResponses,
157
+ routes: this.schemaRoutes.getGroupedRoutes(),
158
+ extraTemplates: this.config.extraTemplates,
159
+ fileName: this.config.fileName,
160
+ translateToJavaScript: this.config.toJS,
161
+ utils: this.getRenderTemplateData().utils,
162
+ };
163
+
164
+ const configuration = this.config.hooks.onPrepareConfig(rawConfiguration) || rawConfiguration;
165
+
166
+ if (this.fileSystem.pathIsExist(this.config.output)) {
167
+ if (this.config.cleanOutput) {
168
+ this.fileSystem.cleanDir(this.config.output);
169
+ }
170
+ } else {
171
+ this.fileSystem.createDir(this.config.output);
172
+ }
173
+
174
+ const files = this.generateOutputFiles({
175
+ configuration: configuration,
176
+ });
177
+
178
+ const isDirPath = this.fileSystem.pathIsDir(this.config.output);
179
+
180
+ const generatedFiles = files.map((file) => {
181
+ if (!isDirPath) return file;
182
+
183
+ if (this.config.toJS) {
184
+ this.fileSystem.createFile({
185
+ path: this.config.output,
186
+ fileName: file.name,
187
+ content: file.content,
188
+ withPrefix: true,
189
+ });
190
+ this.fileSystem.createFile({
191
+ path: this.config.output,
192
+ fileName: file.declaration.name,
193
+ content: file.declaration.content,
194
+ withPrefix: true,
195
+ });
196
+ this.logger.success(`javascript api file`, file.name, `created in ${this.config.output}`);
197
+ } else {
198
+ this.fileSystem.createFile({
199
+ path: this.config.output,
200
+ fileName: file.name,
201
+ content: file.content,
202
+ withPrefix: true,
203
+ });
204
+ this.logger.success(`typescript api file`, file.name, `created in ${this.config.output}`);
205
+ }
206
+
207
+ return file;
208
+ });
209
+
210
+ return {
211
+ files: generatedFiles,
212
+ configuration,
213
+ getTemplate: this.templates.getTemplate,
214
+ renderTemplate: this.templates.renderTemplate,
215
+ createFile: this.fileSystem.createFile,
216
+ formatTSContent: this.codeFormatter.formatCode,
217
+ };
218
+ }
219
+
220
+ getRenderTemplateData = () => {
221
+ return {
222
+ utils: {
223
+ Ts: this.config.Ts,
224
+ formatDescription: this.schemaParser.schemaFormatters.formatDescription,
225
+ internalCase: internalCase,
226
+ classNameCase: pascalCase,
227
+ pascalCase: pascalCase,
228
+ getInlineParseContent: this.schemaParser.getInlineParseContent,
229
+ getParseContent: this.schemaParser.getParseContent,
230
+ getComponentByRef: this.schemaComponentMap.get,
231
+ parseSchema: this.schemaParser.parseSchema,
232
+ checkAndAddNull: this.schemaParser.checkAndAddNull,
233
+ isNeedToAddNull: this.schemaParser.isNeedToAddNull,
234
+ inlineExtraFormatters: this.schemaParser.schemaFormatters.inline,
235
+ formatters: this.schemaParser.schemaFormatters.base,
236
+ formatModelName: this.typeName.format,
237
+ fmtToJSDocLine: function fmtToJSDocLine(line, { eol = true }) {
238
+ return ` * ${line}${eol ? "\n" : ""}`;
239
+ },
240
+ NameResolver: NameResolver,
241
+ _,
242
+ require: this.templates.requireFnFromTemplate,
243
+ },
244
+ config: this.config,
245
+ };
246
+ };
247
+
248
+ prepareModelType = (typeInfo) => {
249
+ if (!typeInfo.typeData) {
250
+ typeInfo.typeData = this.schemaParser.parseSchema(typeInfo.rawTypeData, typeInfo.typeName);
251
+ }
252
+ const rawTypeData = typeInfo.typeData;
253
+ const typeData = this.schemaParser.schemaFormatters.base[rawTypeData.type]
254
+ ? this.schemaParser.schemaFormatters.base[rawTypeData.type](rawTypeData)
255
+ : rawTypeData;
256
+ let { typeIdentifier, name: originalName, content, description } = typeData;
257
+ const name = this.typeName.format(originalName);
258
+
259
+ if (name === null) return null;
260
+
261
+ return {
262
+ ...typeData,
263
+ typeIdentifier,
264
+ name,
265
+ description,
266
+ $content: rawTypeData.content,
267
+ rawContent: rawTypeData.content,
268
+ content: content,
269
+ typeData,
270
+ };
271
+ };
272
+
273
+ generateOutputFiles = ({ configuration }) => {
274
+ const { modular, templatesToRender } = this.config;
275
+
276
+ const output = modular
277
+ ? this.createMultipleFileInfos(templatesToRender, configuration)
278
+ : this.createSingleFileInfo(templatesToRender, configuration);
279
+
280
+ if (!_.isEmpty(configuration.extraTemplates)) {
281
+ output.push(
282
+ ..._.map(configuration.extraTemplates, (extraTemplate) => {
283
+ return this.createOutputFileInfo(
284
+ configuration,
285
+ extraTemplate.name,
286
+ this.templates.renderTemplate(this.fileSystem.getFileContent(extraTemplate.path), configuration),
287
+ );
288
+ }),
289
+ );
290
+ }
291
+
292
+ return output.filter((fileInfo) => !!fileInfo && !!fileInfo.content);
293
+ };
294
+
295
+ createMultipleFileInfos = (templatesToRender, configuration) => {
296
+ const { routes } = configuration;
297
+ const { fileNames, generateRouteTypes, generateClient } = configuration.config;
298
+ const modularApiFileInfos = [];
299
+
300
+ if (routes.$outOfModule) {
301
+ if (generateRouteTypes) {
302
+ const outOfModuleRouteContent = this.templates.renderTemplate(templatesToRender.routeTypes, {
303
+ ...configuration,
304
+ route: configuration.routes.$outOfModule,
305
+ });
306
+
307
+ modularApiFileInfos.push(
308
+ this.createOutputFileInfo(configuration, fileNames.outOfModuleApi, outOfModuleRouteContent),
309
+ );
310
+ }
311
+ if (generateClient) {
312
+ const outOfModuleApiContent = this.templates.renderTemplate(templatesToRender.api, {
313
+ ...configuration,
314
+ route: configuration.routes.$outOfModule,
315
+ });
316
+
317
+ modularApiFileInfos.push(
318
+ this.createOutputFileInfo(configuration, fileNames.outOfModuleApi, outOfModuleApiContent),
319
+ );
320
+ }
321
+ }
322
+
323
+ if (routes.combined) {
324
+ modularApiFileInfos.push(
325
+ ..._.reduce(
326
+ routes.combined,
327
+ (apiFileInfos, route) => {
328
+ if (generateRouteTypes) {
329
+ const routeModuleContent = this.templates.renderTemplate(templatesToRender.routeTypes, {
330
+ ...configuration,
331
+ route,
332
+ });
333
+
334
+ apiFileInfos.push(
335
+ this.createOutputFileInfo(configuration, pascalCase(`${route.moduleName}_Route`), routeModuleContent),
336
+ );
337
+ }
338
+
339
+ if (generateClient) {
340
+ const apiModuleContent = this.templates.renderTemplate(templatesToRender.api, {
341
+ ...configuration,
342
+ route,
343
+ });
344
+
345
+ apiFileInfos.push(
346
+ this.createOutputFileInfo(configuration, pascalCase(route.moduleName), apiModuleContent),
347
+ );
348
+ }
349
+
350
+ return apiFileInfos;
351
+ },
352
+ [],
353
+ ),
354
+ );
355
+ }
356
+
357
+ return [
358
+ this.createOutputFileInfo(
359
+ configuration,
360
+ fileNames.dataContracts,
361
+ this.templates.renderTemplate(templatesToRender.dataContracts, configuration),
362
+ ),
363
+ generateClient &&
364
+ this.createOutputFileInfo(
365
+ configuration,
366
+ fileNames.httpClient,
367
+ this.templates.renderTemplate(templatesToRender.httpClient, configuration),
368
+ ),
369
+ ...modularApiFileInfos,
370
+ ];
371
+ };
372
+
373
+ createSingleFileInfo = (templatesToRender, configuration) => {
374
+ const { generateRouteTypes, generateClient } = configuration.config;
375
+
376
+ return [
377
+ this.createOutputFileInfo(
378
+ configuration,
379
+ configuration.fileName,
380
+ _.compact([
381
+ this.templates.renderTemplate(templatesToRender.dataContracts, configuration),
382
+ generateRouteTypes && this.templates.renderTemplate(templatesToRender.routeTypes, configuration),
383
+ generateClient && this.templates.renderTemplate(templatesToRender.httpClient, configuration),
384
+ generateClient && this.templates.renderTemplate(templatesToRender.api, configuration),
385
+ ]).join("\n"),
386
+ ),
387
+ ];
388
+ };
389
+
390
+ createOutputFileInfo = (configuration, fileName, content) => {
391
+ const fixedFileName = this.fileSystem.cropExtension(fileName);
392
+
393
+ if (configuration.translateToJavaScript) {
394
+ const { sourceContent, declarationContent } = translateToJS(`${fixedFileName}${ts.Extension.Ts}`, content);
395
+
396
+ if (this.config.debug) {
397
+ console.info("generating output for", `${fixedFileName}${ts.Extension.Js}`);
398
+ console.info(sourceContent);
399
+ }
400
+
401
+ if (this.config.debug) {
402
+ console.info("generating output for", `${fixedFileName}${ts.Extension.Dts}`);
403
+ console.info(declarationContent);
404
+ }
405
+
406
+ return {
407
+ name: `${fixedFileName}${ts.Extension.Js}`,
408
+ content: this.codeFormatter.formatCode(sourceContent),
409
+ declaration: {
410
+ name: `${fixedFileName}${ts.Extension.Dts}`,
411
+ content: this.codeFormatter.formatCode(declarationContent),
412
+ },
413
+ };
414
+ }
415
+
416
+ if (this.config.debug) {
417
+ console.info("generating output for", `${fixedFileName}${ts.Extension.Ts}`);
418
+ console.info(content);
419
+ }
420
+
421
+ return {
422
+ name: `${fixedFileName}${ts.Extension.Ts}`,
423
+ content: this.codeFormatter.formatCode(content),
424
+ declaration: null,
425
+ };
426
+ };
427
+
428
+ createApiConfig = (swaggerSchema) => {
429
+ const { info, servers, host, basePath, externalDocs, tags } = swaggerSchema;
430
+ const server = (servers && servers[0]) || { url: "" };
431
+ const { title = "No title", version, description: schemaDescription = "" } = info || {};
432
+ const { url: serverUrl } = server;
433
+
434
+ return {
435
+ info: info || {},
436
+ servers: servers || [],
437
+ basePath,
438
+ host,
439
+ externalDocs: _.merge(
440
+ {
441
+ url: "",
442
+ description: "",
443
+ },
444
+ externalDocs,
445
+ ),
446
+ tags: _.compact(tags),
447
+ baseUrl: serverUrl,
448
+ title,
449
+ version,
450
+ };
451
+ };
452
+ }
453
+
454
+ module.exports = {
455
+ CodeGenProcess,
456
+ };