@goast/typescript 0.3.1 → 0.3.3

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 (49) hide show
  1. package/README.md +2 -0
  2. package/assets/client/k6/request-builder.js +411 -0
  3. package/cjs/lib/ast/nodes/method.js +9 -6
  4. package/cjs/lib/ast/nodes/reference.js +9 -4
  5. package/cjs/lib/ast/references/index.js +2 -1
  6. package/cjs/lib/ast/references/k6.js +9 -0
  7. package/cjs/lib/file-builder.js +9 -4
  8. package/cjs/lib/generators/index.js +2 -1
  9. package/cjs/lib/generators/models/model-generator.js +1 -1
  10. package/cjs/lib/generators/services/k6-clients/index.js +6 -0
  11. package/cjs/lib/generators/services/k6-clients/k6-client-generator.js +311 -0
  12. package/cjs/lib/generators/services/k6-clients/k6-clients-generator.js +116 -0
  13. package/cjs/lib/generators/services/k6-clients/models.js +28 -0
  14. package/cjs/lib/generators/services/k6-clients/refs.js +12 -0
  15. package/cjs/lib/import-collection.js +49 -7
  16. package/esm/lib/ast/nodes/method.js +9 -6
  17. package/esm/lib/ast/nodes/reference.js +9 -4
  18. package/esm/lib/ast/references/index.js +2 -0
  19. package/esm/lib/ast/references/k6.js +6 -0
  20. package/esm/lib/file-builder.js +9 -4
  21. package/esm/lib/generators/index.js +2 -1
  22. package/esm/lib/generators/models/model-generator.js +1 -1
  23. package/esm/lib/generators/services/k6-clients/index.js +3 -0
  24. package/esm/lib/generators/services/k6-clients/k6-client-generator.js +307 -0
  25. package/esm/lib/generators/services/k6-clients/k6-clients-generator.js +112 -0
  26. package/esm/lib/generators/services/k6-clients/models.js +25 -0
  27. package/esm/lib/generators/services/k6-clients/refs.js +9 -0
  28. package/esm/lib/import-collection.js +49 -7
  29. package/package.json +3 -3
  30. package/types/lib/ast/nodes/method.d.ts +2 -0
  31. package/types/lib/ast/nodes/reference.d.ts +8 -2
  32. package/types/lib/ast/references/angular.d.ts +21 -7
  33. package/types/lib/ast/references/easy-network-stub.d.ts +12 -4
  34. package/types/lib/ast/references/index.d.ts +1 -0
  35. package/types/lib/ast/references/k6.d.ts +24 -0
  36. package/types/lib/ast/references/rxjs.d.ts +15 -5
  37. package/types/lib/ast/references/typescript.d.ts +87 -29
  38. package/types/lib/common-results.d.ts +2 -0
  39. package/types/lib/file-builder.d.ts +3 -3
  40. package/types/lib/generators/index.d.ts +2 -1
  41. package/types/lib/generators/services/angular-services/refs.d.ts +21 -7
  42. package/types/lib/generators/services/easy-network-stub/refs.d.ts +27 -9
  43. package/types/lib/generators/services/fetch-clients/refs.d.ts +9 -3
  44. package/types/lib/generators/services/k6-clients/index.d.ts +3 -0
  45. package/types/lib/generators/services/k6-clients/k6-client-generator.d.ts +39 -0
  46. package/types/lib/generators/services/k6-clients/k6-clients-generator.d.ts +27 -0
  47. package/types/lib/generators/services/k6-clients/models.d.ts +118 -0
  48. package/types/lib/generators/services/k6-clients/refs.d.ts +20 -0
  49. package/types/lib/import-collection.d.ts +10 -3
@@ -20,8 +20,8 @@ export class TypeScriptFileBuilder extends SourceBuilder {
20
20
  useSingleQuotes: this.options.useSingleQuotes,
21
21
  });
22
22
  }
23
- addImport(name, moduleNameOrfilePath) {
24
- this.imports.addImport(name, moduleNameOrfilePath);
23
+ addImport(name, moduleNameOrfilePath, options) {
24
+ this.imports.addImport(name, moduleNameOrfilePath, options);
25
25
  return this;
26
26
  }
27
27
  addExport(name, filePath) {
@@ -33,7 +33,12 @@ export class TypeScriptFileBuilder extends SourceBuilder {
33
33
  this.imports.clear();
34
34
  }
35
35
  toString(addPadding = true) {
36
+ var _a;
36
37
  const builder = new SourceBuilder(this.options);
38
+ if ((_a = this.filePath) === null || _a === void 0 ? void 0 : _a.match(/\.[cm]?js$/)) {
39
+ builder.appendLine('// @ts-check');
40
+ }
41
+ builder.ensurePreviousLineEmpty();
37
42
  this.imports.writeTo(builder);
38
43
  const baseString = super.toString();
39
44
  if (baseString && addPadding) {
@@ -51,10 +56,10 @@ export class TypeScriptFileBuilder extends SourceBuilder {
51
56
  value.write(this);
52
57
  }
53
58
  }
54
- appendModelUsage(type) {
59
+ appendModelUsage(type, options) {
55
60
  this.append(type.component);
56
61
  for (const i of type.imports) {
57
- this.addImport(i.name, i.modulePath);
62
+ this.addImport(i.name, i.modulePath, options);
58
63
  }
59
64
  return this;
60
65
  }
@@ -1,5 +1,6 @@
1
1
  export * from './file-generator';
2
2
  export * from './models';
3
3
  export * from './services/angular-services';
4
- export * from './services/fetch-clients';
5
4
  export * from './services/easy-network-stub';
5
+ export * from './services/fetch-clients';
6
+ export * from './services/k6-clients';
@@ -18,7 +18,7 @@ export class DefaultTypeScriptModelGenerator extends TypeScriptFileGenerator {
18
18
  return {
19
19
  component: name,
20
20
  filePath,
21
- imports: [{ kind: 'file', name, modulePath: filePath }],
21
+ imports: [{ kind: 'file', name, modulePath: filePath, type: 'import' }],
22
22
  additionalExports: fileContent.values
23
23
  .filter((x) => typeof x === 'object' && x !== null && 'export' in x && x.export === true && 'name' in x)
24
24
  .filter((x) => x.name !== name)
@@ -0,0 +1,3 @@
1
+ export * from './k6-client-generator';
2
+ export * from './k6-clients-generator';
3
+ export * from './models';
@@ -0,0 +1,307 @@
1
+ import { resolve } from 'path';
2
+ import { appendValueGroup, toCasing, builderTemplate as s, } from '@goast/core';
3
+ import { ts } from '../../../ast';
4
+ import { TypeScriptFileBuilder } from '../../../file-builder';
5
+ import { TypeScriptFileGenerator } from '../../file-generator';
6
+ export class DefaultTypeScriptK6ClientGenerator extends TypeScriptFileGenerator {
7
+ generate(ctx) {
8
+ const responseModels = this.generateResponseModels(ctx);
9
+ const filePath = this.getFilePath(ctx);
10
+ const name = this.getClassName(ctx);
11
+ TypeScriptFileBuilder.generate({
12
+ logName: `k6 client ${name}`,
13
+ filePath,
14
+ options: ctx.config,
15
+ generator: (b) => b.append(this.getClientFileContent(ctx)),
16
+ });
17
+ return {
18
+ filePath,
19
+ component: name,
20
+ responseModels,
21
+ };
22
+ }
23
+ // #region Response Model
24
+ generateResponseModels(ctx) {
25
+ const result = {};
26
+ const filePath = this.getResponseModelFilePath(ctx);
27
+ const builder = new TypeScriptFileBuilder(filePath, ctx.config);
28
+ for (const endpoint of ctx.service.endpoints) {
29
+ const name = this.getResponseModelName(ctx, endpoint);
30
+ builder.appendLine(this.getResponseModelFileContent(ctx, endpoint));
31
+ result[endpoint.id] = { component: name, filePath };
32
+ }
33
+ builder.writeToFile();
34
+ return result;
35
+ }
36
+ getResponseModelFileContent(ctx, endpoint) {
37
+ const result = appendValueGroup([]);
38
+ const statusCodes = this.getEndpointStatusCodes(ctx, endpoint);
39
+ let statusCodeType;
40
+ const statusCodesTypeName = toCasing(`${endpoint.name}_StatusCodes`, ctx.config.typeNameCasing);
41
+ result.values.push(ts.typeAlias(statusCodesTypeName, ts.unionType(Object.keys(statusCodes))));
42
+ if (ctx.config.strictResponseTypes) {
43
+ statusCodeType = ts.reference(statusCodesTypeName);
44
+ }
45
+ else {
46
+ statusCodeType = ctx.refs.httpStatusCode();
47
+ }
48
+ result.values.push(ts.typeAlias(this.getResponseModelName(ctx, endpoint), ts.intersectionType([
49
+ ts.unionType([
50
+ ...Object.entries(statusCodes).map(([key, value]) => {
51
+ return ts.intersectionType([
52
+ ts.refs.omit([ts.refs.k6.response(), ts.string('json')]),
53
+ ts.objectType({
54
+ members: [
55
+ ts.property('status', { type: key }),
56
+ ts.method('json', {
57
+ returnType: value.type,
58
+ }),
59
+ ts.method('json', {
60
+ parameters: [ts.parameter('selector', { type: ts.refs.string() })],
61
+ returnType: ts.refs.k6.jsonValue(),
62
+ }),
63
+ ],
64
+ }),
65
+ ]);
66
+ }),
67
+ !ctx.config.strictResponseTypes
68
+ ? ts.intersectionType([
69
+ ts.refs.k6.response(),
70
+ ts.objectType({
71
+ members: [
72
+ ts.property('status', {
73
+ type: ts.refs.exclude([ctx.refs.httpStatusCode(), ts.reference(statusCodesTypeName)]),
74
+ }),
75
+ ],
76
+ }),
77
+ ])
78
+ : null,
79
+ ]),
80
+ ts.objectType({ members: [ts.property('status', { type: 'TStatus' })] }),
81
+ ]), {
82
+ doc: ts.doc({
83
+ description: `Response model for operation ${this.getEndpointMethodName(ctx, endpoint)}`,
84
+ tags: [endpoint.deprecated ? ts.docTag('deprecated') : null],
85
+ }),
86
+ export: true,
87
+ generics: [
88
+ ts.genericParameter('TStatus', {
89
+ constraint: statusCodeType,
90
+ default: statusCodeType,
91
+ }),
92
+ ],
93
+ }));
94
+ return result;
95
+ }
96
+ getEndpointStatusCodes(ctx, endpoint) {
97
+ return Object.fromEntries([
98
+ ...Object.entries(ctx.config.defaultStatusCodeResponseTypes).map(([key, value]) => {
99
+ var _a;
100
+ return [
101
+ key,
102
+ {
103
+ parser: (_a = value === null || value === void 0 ? void 0 : value.parser) !== null && _a !== void 0 ? _a : 'text',
104
+ type: typeof (value === null || value === void 0 ? void 0 : value.type) === 'function'
105
+ ? this.getSchemaType(ctx, value.type(ctx.data.schemas))
106
+ : (value !== null && value !== void 0 ? value : ts.refs.never()),
107
+ },
108
+ ];
109
+ }),
110
+ ...endpoint.responses
111
+ .filter((x) => x.statusCode)
112
+ .map((x) => {
113
+ var _a;
114
+ return [
115
+ x.statusCode,
116
+ {
117
+ type: this.getSchemaType(ctx, (_a = x.contentOptions[0]) === null || _a === void 0 ? void 0 : _a.schema, { fallback: ts.refs.never() }),
118
+ },
119
+ ];
120
+ }),
121
+ ]);
122
+ }
123
+ // #endregion
124
+ getClientFileContent(ctx) {
125
+ const code = appendValueGroup([], '\n');
126
+ for (const endpoint of ctx.service.endpoints) {
127
+ if (endpoint.parameters.length > 0 || endpoint.requestBody !== undefined) {
128
+ code.values.push(this.getEndpointParamsType(ctx, endpoint));
129
+ }
130
+ }
131
+ code.values.push(appendValueGroup(ctx.service.endpoints.map((e) => ts.variable(this.getEndpointPathPropertyName(ctx, e), {
132
+ readonly: true,
133
+ value: ts.string(e.path),
134
+ }))));
135
+ code.values.push(this.getClass(ctx));
136
+ return code;
137
+ }
138
+ getEndpointParamsType(ctx, endpoint) {
139
+ const type = ts.doc({
140
+ description: `Parameters for operation ${this.getEndpointMethodName(ctx, endpoint)}`,
141
+ tags: [ts.docTag('typedef', this.getEndpointParamsTypeName(ctx, endpoint))],
142
+ });
143
+ for (const parameter of endpoint.parameters) {
144
+ const schema = parameter.schema;
145
+ const name = toCasing(parameter.name, ctx.config.propertyNameCasing);
146
+ type.tags.push(ts.docTag('property', parameter.required ? name : `[${name}]`, {
147
+ type: schema
148
+ ? (b) => b.appendModelUsage(ctx.input.typescript.models[schema.id], { type: 'js-doc' })
149
+ : this.getAnyType(ctx),
150
+ text: (parameter.deprecated ? 'Deprecated: ' : '') + parameter.description,
151
+ }));
152
+ }
153
+ if (endpoint.requestBody !== undefined) {
154
+ const body = endpoint.requestBody;
155
+ const schema = body.content[0].schema;
156
+ type.tags.push(ts.docTag('property', body.required ? 'body' : '[body]', {
157
+ type: schema
158
+ ? (b) => b.appendModelUsage(ctx.input.typescript.models[schema.id], { type: 'js-doc' })
159
+ : this.getAnyType(ctx),
160
+ text: body.description,
161
+ }));
162
+ }
163
+ return type;
164
+ }
165
+ getClass(ctx) {
166
+ return ts.class(this.getClassName(ctx), {
167
+ doc: ts.doc({
168
+ description: ctx.service.description,
169
+ tags: [
170
+ ts.docTag('property', 'rootUrl', 'The root URL for this client.', {
171
+ type: ts.refs.string(),
172
+ }),
173
+ ctx.service.endpoints.length === 0 || ctx.service.endpoints.some((x) => !x.deprecated)
174
+ ? null
175
+ : ts.docTag('deprecated'),
176
+ ],
177
+ }),
178
+ export: true,
179
+ members: [
180
+ ts.constructor({
181
+ doc: ts.doc({
182
+ description: 'Creates a new instance of the client.',
183
+ tags: [
184
+ ts.docTag('param', 'rootUrl', 'The root URL for this client.', { type: ts.refs.string() }),
185
+ ts.docTag('param', '[defaultK6ParamsFactory]', 'A factory function that returns the default K6 parameters.', { type: ts.functionType({ returnType: ts.refs.k6.params({ importType: 'js-doc' }) }) }),
186
+ ],
187
+ }),
188
+ parameters: [ts.constructorParameter('rootUrl'), ts.constructorParameter('defaultK6ParamsFactory')],
189
+ body: s `this.rootUrl = rootUrl;
190
+ this._defaultK6ParamsFactory = defaultK6ParamsFactory;`,
191
+ }),
192
+ ...ctx.service.endpoints.map((e) => this.getEndpointMethod(ctx, e)),
193
+ this.getGetK6ParamsMethod(ctx),
194
+ ],
195
+ });
196
+ }
197
+ getEndpointMethod(ctx, endpoint) {
198
+ var _a, _b, _c, _d;
199
+ const hasParams = this.hasEndpointParams(ctx, endpoint);
200
+ const paramsOptional = !endpoint.parameters.some((p) => p.required) && !((_a = endpoint.requestBody) === null || _a === void 0 ? void 0 : _a.required);
201
+ const responseModelType = ts.reference(this.getResponseModelName(ctx, endpoint), this.getResponseModelFilePath(ctx), { importType: 'js-doc' });
202
+ const returnType = responseModelType;
203
+ const accept = (_d = (_c = (_b = this.getEndpointSuccessResponse(ctx, endpoint)) === null || _b === void 0 ? void 0 : _b.contentOptions[0]) === null || _c === void 0 ? void 0 : _c.type) !== null && _d !== void 0 ? _d : '*/*';
204
+ return ts.method(this.getEndpointMethodName(ctx, endpoint), {
205
+ async: ctx.config.async,
206
+ parameters: [hasParams ? ts.parameter('params') : null, ts.parameter('k6Params')],
207
+ doc: ts.doc({
208
+ description: endpoint.description,
209
+ tags: [
210
+ hasParams
211
+ ? ts.docTag('param', paramsOptional ? '[params]' : 'params', {
212
+ type: ts.reference(this.getEndpointParamsTypeName(ctx, endpoint)),
213
+ })
214
+ : null,
215
+ ts.docTag('param', '[k6Params]', {
216
+ type: ts.refs.k6.params({ importType: 'js-doc' }),
217
+ }),
218
+ ts.docTag('returns', {
219
+ type: ctx.config.async ? ts.refs.promise([returnType]) : returnType,
220
+ }),
221
+ endpoint.deprecated ? ts.docTag('deprecated') : null,
222
+ ],
223
+ }),
224
+ body: appendValueGroup([
225
+ s `const rb = new ${ctx.refs.requestBuilder()}(this.rootUrl, ${this.getEndpointPathPropertyName(ctx, endpoint)}, '${endpoint.method}');`,
226
+ hasParams
227
+ ? (b) => {
228
+ var _a;
229
+ return b.appendIf(paramsOptional, 'if (params) ').parenthesizeIf(paramsOptional, '{}', appendValueGroup([
230
+ ...endpoint.parameters
231
+ .filter((p) => p.target === 'path' || p.target === 'query' || p.target === 'header')
232
+ .map((p) => {
233
+ const options = ts.object({
234
+ members: [
235
+ p.style !== undefined ? ts.property('style', { value: ts.string(p.style) }) : null,
236
+ p.explode !== undefined ? ts.property('explode', { value: ts.toNode(p.explode) }) : null,
237
+ ],
238
+ });
239
+ return s `rb.${p.target}(${ts.string(p.name)}, params.${toCasing(p.name, ctx.config.propertyNameCasing)}, ${options});`;
240
+ }),
241
+ endpoint.requestBody
242
+ ? s `rb.body(params.body, ${ts.string((_a = endpoint.requestBody.content[0].type) !== null && _a !== void 0 ? _a : 'application/json')});`
243
+ : null,
244
+ ], '\n'), { multiline: true });
245
+ }
246
+ : null,
247
+ '',
248
+ s `return /** @type {${returnType}} */ (${s.indent `
249
+ ${ctx.config.async ? 'await rb.buildAsync' : 'rb.build'}({${s.indent `
250
+ accept: ${ts.string(accept)},
251
+ params: this.getK6Params(k6Params),`}
252
+ })`}
253
+ );`,
254
+ ], '\n'),
255
+ });
256
+ }
257
+ getGetK6ParamsMethod(ctx) {
258
+ return ts.method('getK6Params', {
259
+ doc: ts.doc({
260
+ tags: [
261
+ ts.docTag('private'),
262
+ ts.docTag('param', '[k6Params]', { type: ts.refs.k6.params({ importType: 'js-doc' }) }),
263
+ ],
264
+ }),
265
+ parameters: [ts.parameter('k6Params')],
266
+ body: s `return Object.assign({}, this._defaultK6ParamsFactory ? this._defaultK6ParamsFactory() : {}, k6Params);`,
267
+ });
268
+ }
269
+ getEndpointSuccessResponse(ctx, endpoint) {
270
+ var _a;
271
+ return ((_a = endpoint.responses.find((x) => x.statusCode && x.statusCode >= 200 && x.statusCode < 300)) !== null && _a !== void 0 ? _a : endpoint.responses.find((x) => x.statusCode === undefined));
272
+ }
273
+ getSchemaType(ctx, schema, options) {
274
+ var _a;
275
+ const output = schema && ctx.input.typescript.models[schema.id];
276
+ if (!output)
277
+ return (_a = options === null || options === void 0 ? void 0 : options.fallback) !== null && _a !== void 0 ? _a : this.getAnyType(ctx);
278
+ return (b) => b.appendModelUsage(output, options);
279
+ }
280
+ getClassName(ctx) {
281
+ var _a;
282
+ return toCasing(ctx.service.name, (_a = ctx.config.clientNameCasing) !== null && _a !== void 0 ? _a : ctx.config.typeNameCasing);
283
+ }
284
+ getResponseModelName(ctx, endpoint) {
285
+ return toCasing(`${this.getEndpointMethodName(ctx, endpoint)}ApiResponse`, ctx.config.typeNameCasing);
286
+ }
287
+ getEndpointParamsTypeName(ctx, endpoint) {
288
+ return toCasing(this.getEndpointMethodName(ctx, endpoint) + '_Params', ctx.config.typeNameCasing);
289
+ }
290
+ getEndpointPathPropertyName(ctx, endpoint) {
291
+ return toCasing(this.getEndpointMethodName(ctx, endpoint) + '_Path', ctx.config.constantNameCasing);
292
+ }
293
+ getEndpointMethodName(ctx, endpoint) {
294
+ return toCasing(endpoint.name, ctx.config.functionNameCasing);
295
+ }
296
+ getFilePath(ctx) {
297
+ var _a;
298
+ return resolve(ctx.config.outputDir, ctx.config.clientDir, `${toCasing(ctx.service.name, (_a = ctx.config.clientFileNameCasing) !== null && _a !== void 0 ? _a : ctx.config.fileNameCasing)}.js`);
299
+ }
300
+ getResponseModelFilePath(ctx) {
301
+ var _a;
302
+ return resolve(ctx.config.outputDir, ctx.config.responseModelsDir, `${toCasing(ctx.service.name, (_a = ctx.config.responseModelFileNameCasing) !== null && _a !== void 0 ? _a : ctx.config.fileNameCasing)}.ts`);
303
+ }
304
+ hasEndpointParams(ctx, endpoint) {
305
+ return endpoint.parameters.length > 0 || endpoint.requestBody !== undefined;
306
+ }
307
+ }
@@ -0,0 +1,112 @@
1
+ import { dirname, resolve } from 'path';
2
+ import { ensureDirSync, copyFileSync } from 'fs-extra';
3
+ import { OpenApiServicesGenerationProviderBase, Factory, appendValueGroup, } from '@goast/core';
4
+ import { DefaultTypeScriptK6ClientGenerator } from './k6-client-generator';
5
+ import { defaultTypeScriptK6ClientsGeneratorConfig, } from './models';
6
+ import { getReferenceFactories } from './refs';
7
+ import { ts } from '../../../ast';
8
+ import { TypeScriptFileBuilder } from '../../../file-builder';
9
+ export class TypeScriptK6ClientsGenerator extends OpenApiServicesGenerationProviderBase {
10
+ constructor(clientGeneratorFactory) {
11
+ super();
12
+ this._clientGeneratorFactory =
13
+ clientGeneratorFactory !== null && clientGeneratorFactory !== void 0 ? clientGeneratorFactory : Factory.fromValue(new DefaultTypeScriptK6ClientGenerator());
14
+ }
15
+ initResult() {
16
+ return {
17
+ typescript: {
18
+ k6Clients: {},
19
+ indexFiles: {
20
+ k6Clients: undefined,
21
+ k6ResponseModels: undefined,
22
+ },
23
+ },
24
+ };
25
+ }
26
+ buildContext(context, config) {
27
+ const providerContext = this.getProviderContext(context, config, defaultTypeScriptK6ClientsGeneratorConfig);
28
+ return Object.assign(providerContext, {
29
+ refs: getReferenceFactories(providerContext.config),
30
+ });
31
+ }
32
+ onGenerate(ctx) {
33
+ const output = super.onGenerate(ctx);
34
+ this.generateUtilsFiles(ctx);
35
+ output.typescript.indexFiles = this.generateIndexFiles(ctx);
36
+ return output;
37
+ }
38
+ generateService(ctx, service) {
39
+ const clientGenerator = this._clientGeneratorFactory.create();
40
+ return clientGenerator.generate({
41
+ ...ctx,
42
+ service,
43
+ });
44
+ }
45
+ addServiceResult(ctx, service, result) {
46
+ ctx.output.typescript.k6Clients[service.id] = result;
47
+ }
48
+ generateUtilsFiles(ctx) {
49
+ const sourceDir = resolve(dirname(require.resolve('@goast/typescript')), '../assets/client/k6');
50
+ const targetDir = resolve(ctx.config.outputDir, ctx.config.utilsDir);
51
+ ensureDirSync(targetDir);
52
+ this.copyFile('Request Builder', sourceDir, targetDir, 'request-builder.js');
53
+ if (!ctx.config.strictResponseTypes) {
54
+ TypeScriptFileBuilder.generate({
55
+ logName: 'http status codes',
56
+ filePath: resolve(targetDir, 'http-status-code.ts'),
57
+ options: ctx.config,
58
+ generator: (b) => b.append(this.getHttpStatusCodeFileContent(ctx)),
59
+ });
60
+ }
61
+ }
62
+ getHttpStatusCodeFileContent(ctx) {
63
+ return appendValueGroup([
64
+ ts.typeAlias('HttpStatusCode', ts.unionType(ctx.config.possibleStatusCodes), {
65
+ doc: ts.doc({ description: 'The possible status codes that any API endpoint can return.' }),
66
+ export: true,
67
+ }),
68
+ ], '\n');
69
+ }
70
+ generateIndexFiles(ctx) {
71
+ const clientsIndexFilePath = this.getIndexFilePath(ctx);
72
+ const responseModelsIndexFilePath = this.getResponseModelsIndexFilePath(ctx);
73
+ TypeScriptFileBuilder.tryGenerate({
74
+ logName: 'clients index file',
75
+ filePath: clientsIndexFilePath,
76
+ options: ctx.config,
77
+ generator: (b) => b.append(this.getIndexFileContent(ctx)),
78
+ });
79
+ TypeScriptFileBuilder.tryGenerate({
80
+ logName: 'response models index file',
81
+ filePath: responseModelsIndexFilePath,
82
+ options: ctx.config,
83
+ generator: (b) => b.append(this.getResponseModelsIndexFileContent(ctx)),
84
+ });
85
+ return {
86
+ k6Clients: clientsIndexFilePath,
87
+ k6ResponseModels: responseModelsIndexFilePath,
88
+ };
89
+ }
90
+ getIndexFilePath(ctx) {
91
+ return ctx.config.clientsIndexFile ? resolve(ctx.config.outputDir, ctx.config.clientsIndexFile) : null;
92
+ }
93
+ getResponseModelsIndexFilePath(ctx) {
94
+ return ctx.config.responseModelsIndexFile
95
+ ? resolve(ctx.config.outputDir, ctx.config.responseModelsIndexFile)
96
+ : null;
97
+ }
98
+ getIndexFileContent(ctx) {
99
+ return appendValueGroup(Object.values(ctx.output.typescript.k6Clients).map((x) => ts.export(x.component, x.filePath)));
100
+ }
101
+ getResponseModelsIndexFileContent(ctx) {
102
+ return appendValueGroup(Object.values(ctx.output.typescript.k6Clients)
103
+ .flatMap((x) => Object.values(x.responseModels))
104
+ .map((x) => ts.export(x.component, x.filePath)));
105
+ }
106
+ copyFile(logName, sourceDir, targetDir, fileName) {
107
+ const source = resolve(sourceDir, fileName);
108
+ const target = resolve(targetDir, fileName);
109
+ console.log(`Generating ${logName} to ${target}...`);
110
+ copyFileSync(source, target);
111
+ }
112
+ }
@@ -0,0 +1,25 @@
1
+ import { defaultTypeScriptGeneratorConfig } from '../../../config';
2
+ export const defaultTypeScriptK6ClientsGeneratorConfig = {
3
+ ...defaultTypeScriptGeneratorConfig,
4
+ importModuleTransformer: 'js-extension',
5
+ strictResponseTypes: true,
6
+ defaultStatusCodeResponseTypes: {
7
+ 401: null,
8
+ 403: null,
9
+ 500: null,
10
+ },
11
+ possibleStatusCodes: [
12
+ 100, 101, 102, 103, 200, 201, 202, 203, 204, 205, 206, 207, 208, 226, 300, 301, 302, 303, 304, 305, 306, 307, 308,
13
+ 400, 401, 402, 403, 404, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 421, 422, 423, 424, 425,
14
+ 426, 428, 429, 431, 451, 500, 501, 502, 503, 504, 505, 506, 507, 508, 510, 511,
15
+ ],
16
+ clientFileNameCasing: { casing: 'kebab', suffix: '-client' },
17
+ responseModelFileNameCasing: { casing: 'kebab', suffix: '-responses' },
18
+ clientNameCasing: { casing: 'pascal', suffix: 'Client' },
19
+ clientDir: 'clients',
20
+ responseModelsDir: 'models/responses',
21
+ clientsIndexFile: 'clients.js',
22
+ responseModelsIndexFile: 'responses.ts',
23
+ utilsDir: 'utils',
24
+ async: true,
25
+ };
@@ -0,0 +1,9 @@
1
+ import { join, resolve } from 'path';
2
+ import { ts } from '../../../ast';
3
+ export function getReferenceFactories(options) {
4
+ const utilsDirPath = resolve(options.outputDir, options.utilsDir);
5
+ return {
6
+ requestBuilder: ts.reference.factory('RequestBuilder', join(utilsDirPath, 'request-builder.js')),
7
+ httpStatusCode: ts.reference.factory('HttpStatusCode', join(utilsDirPath, 'http-status-code.ts')),
8
+ };
9
+ }
@@ -9,31 +9,53 @@ export const defaultImportExportCollectionOptions = {
9
9
  export class ImportExportCollection {
10
10
  constructor(options) {
11
11
  this._imports = new Map();
12
+ this._typeImports = new Map();
13
+ this._jsDocImports = new Map();
12
14
  this._exports = new Map();
13
15
  this._options = { ...defaultImportExportCollectionOptions, ...options };
14
16
  }
15
17
  get hasImports() {
16
- return this._imports.size > 0;
18
+ return this._imports.size > 0 || this._typeImports.size > 0 || this._jsDocImports.size > 0;
17
19
  }
18
20
  get hasExports() {
19
21
  return this._exports.size > 0;
20
22
  }
21
23
  get imports() {
22
- return Array.from(this._imports.entries())
24
+ return [
25
+ ...this.toImport('import', this._imports),
26
+ ...this.toImport('type-import', this._typeImports),
27
+ ...this.toImport('js-doc', this._jsDocImports),
28
+ ];
29
+ }
30
+ toImport(type, map) {
31
+ return Array.from(map.entries())
23
32
  .map(([fromModule, importNames]) => Array.from(importNames).map((importName) => ({
24
33
  kind: this.getImportKind(fromModule),
34
+ type,
25
35
  modulePath: fromModule,
26
36
  name: importName,
27
37
  })))
28
38
  .flat();
29
39
  }
30
- addImport(importName, fromModule) {
31
- const existingImport = this._imports.get(fromModule);
40
+ addImport(importName, fromModule, options) {
41
+ var _a;
42
+ const map = this.getImportMap((_a = options === null || options === void 0 ? void 0 : options.type) !== null && _a !== void 0 ? _a : 'import');
43
+ const existingImport = map.get(fromModule);
32
44
  if (existingImport) {
33
45
  existingImport.add(importName);
34
46
  }
35
47
  else {
36
- this._imports.set(fromModule, new Set([importName]));
48
+ map.set(fromModule, new Set([importName]));
49
+ }
50
+ }
51
+ getImportMap(type) {
52
+ switch (type) {
53
+ case 'import':
54
+ return this._imports;
55
+ case 'type-import':
56
+ return this._typeImports;
57
+ case 'js-doc':
58
+ return this._jsDocImports;
37
59
  }
38
60
  }
39
61
  addExport(exportName, fromModule) {
@@ -56,14 +78,34 @@ export class ImportExportCollection {
56
78
  }
57
79
  writeTo(builder) {
58
80
  const { filePath, importModuleTransformer, useSingleQuotes } = this._options;
81
+ let hasPrevious = false;
59
82
  if (this._imports.size > 0) {
60
83
  const sortedImports = this.sortAndResolve(this._imports, filePath, importModuleTransformer);
61
84
  this.writeImportsExports(builder, 'import', sortedImports, { useSingleQuotes });
85
+ hasPrevious = true;
62
86
  }
63
- if (this._imports.size > 0 && this._exports.size > 0) {
64
- builder.appendLine();
87
+ if (this._typeImports.size > 0) {
88
+ if (hasPrevious) {
89
+ builder.appendLine();
90
+ }
91
+ const sortedImports = this.sortAndResolve(this._typeImports, filePath, importModuleTransformer);
92
+ this.writeImportsExports(builder, 'import type', sortedImports, { useSingleQuotes });
93
+ hasPrevious = true;
94
+ }
95
+ if (this._jsDocImports.size > 0) {
96
+ if (hasPrevious) {
97
+ builder.appendLine();
98
+ }
99
+ const sortedImports = this.sortAndResolve(this._jsDocImports, filePath, importModuleTransformer);
100
+ builder.appendLine('/**');
101
+ this.writeImportsExports(builder, ' * @import', sortedImports, { useSingleQuotes });
102
+ builder.appendLine(' */');
103
+ hasPrevious = true;
65
104
  }
66
105
  if (this._exports.size > 0) {
106
+ if (hasPrevious) {
107
+ builder.appendLine();
108
+ }
67
109
  const sortedExports = this.sortAndResolve(this._exports, filePath, importModuleTransformer);
68
110
  this.writeImportsExports(builder, 'export', sortedExports, { useSingleQuotes });
69
111
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goast/typescript",
3
- "version": "0.3.1",
3
+ "version": "0.3.3",
4
4
  "repository": "https://github.com/MaSch0212/goast.git",
5
5
  "description": "Provides gOAst generators for generating TypeScript code from OpenAPI specifications.",
6
6
  "keywords": [
@@ -25,9 +25,9 @@
25
25
  "type": "module",
26
26
  "dependencies": {
27
27
  "fs-extra": "^11.2.0",
28
- "node-fetch": "^3.3.2",
28
+ "node-fetch": "^2.7.0",
29
29
  "yaml": "^2.4.5",
30
- "@goast/core": "0.3.1",
30
+ "@goast/core": "0.3.2",
31
31
  "tslib": "2.6.3"
32
32
  },
33
33
  "packageManager": "pnpm@9.5.0",
@@ -12,6 +12,7 @@ type Options<TBuilder extends SourceBuilder, TInjects extends string = never> =
12
12
  doc?: Nullable<TsDoc<TBuilder>>;
13
13
  decorators?: Nullable<Nullable<TsDecorator<TBuilder>>[]>;
14
14
  accessModifier?: Nullable<TsAccessModifier>;
15
+ async?: Nullable<boolean>;
15
16
  static?: Nullable<boolean>;
16
17
  abstract?: Nullable<boolean>;
17
18
  override?: Nullable<boolean>;
@@ -29,6 +30,7 @@ export declare class TsMethod<TBuilder extends SourceBuilder, TInjects extends s
29
30
  static: boolean;
30
31
  abstract: boolean;
31
32
  override: boolean;
33
+ async: boolean;
32
34
  name: string;
33
35
  optional: boolean;
34
36
  generics: (TsGenericParameter<TBuilder> | BasicAppendValue<TBuilder>)[];