api 4.4.0 → 5.0.0-beta.0

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 (70) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +18 -5
  3. package/bin/api +2 -0
  4. package/dist/bin.d.ts +1 -0
  5. package/dist/bin.js +91 -0
  6. package/dist/cache.d.ts +30 -0
  7. package/dist/cache.js +217 -0
  8. package/dist/cli/codegen/index.d.ts +4 -0
  9. package/dist/cli/codegen/index.js +23 -0
  10. package/dist/cli/codegen/language.d.ts +27 -0
  11. package/dist/cli/codegen/language.js +19 -0
  12. package/dist/cli/codegen/languages/typescript.d.ts +99 -0
  13. package/dist/cli/codegen/languages/typescript.js +762 -0
  14. package/dist/cli/commands/index.d.ts +4 -0
  15. package/dist/cli/commands/index.js +9 -0
  16. package/dist/cli/commands/install.d.ts +3 -0
  17. package/dist/cli/commands/install.js +230 -0
  18. package/dist/cli/lib/prompt.d.ts +9 -0
  19. package/dist/cli/lib/prompt.js +81 -0
  20. package/dist/cli/logger.d.ts +1 -0
  21. package/dist/cli/logger.js +16 -0
  22. package/dist/cli/storage.d.ts +105 -0
  23. package/dist/cli/storage.js +264 -0
  24. package/dist/core/getJSONSchemaDefaults.d.ts +15 -0
  25. package/dist/core/getJSONSchemaDefaults.js +62 -0
  26. package/dist/core/index.d.ts +32 -0
  27. package/dist/core/index.js +143 -0
  28. package/dist/core/parseResponse.d.ts +1 -0
  29. package/dist/core/parseResponse.js +65 -0
  30. package/dist/core/prepareAuth.d.ts +5 -0
  31. package/dist/core/prepareAuth.js +55 -0
  32. package/dist/core/prepareParams.d.ts +24 -0
  33. package/dist/core/prepareParams.js +351 -0
  34. package/dist/core/prepareServer.d.ts +13 -0
  35. package/dist/core/prepareServer.js +50 -0
  36. package/dist/fetcher.d.ts +53 -0
  37. package/dist/fetcher.js +149 -0
  38. package/dist/index.d.ts +6 -0
  39. package/dist/index.js +276 -0
  40. package/dist/packageInfo.d.ts +2 -0
  41. package/dist/packageInfo.js +6 -0
  42. package/package.json +65 -25
  43. package/src/.sink.d.ts +1 -0
  44. package/src/bin.ts +20 -0
  45. package/src/cache.ts +212 -0
  46. package/src/cli/codegen/index.ts +31 -0
  47. package/src/cli/codegen/language.ts +47 -0
  48. package/src/cli/codegen/languages/typescript.ts +798 -0
  49. package/src/cli/commands/index.ts +5 -0
  50. package/src/cli/commands/install.ts +196 -0
  51. package/src/cli/lib/prompt.ts +29 -0
  52. package/src/cli/logger.ts +10 -0
  53. package/src/cli/storage.ts +297 -0
  54. package/src/core/getJSONSchemaDefaults.ts +74 -0
  55. package/src/core/index.ts +108 -0
  56. package/src/{lib/parseResponse.js → core/parseResponse.ts} +5 -7
  57. package/src/core/prepareAuth.ts +85 -0
  58. package/src/core/prepareParams.ts +338 -0
  59. package/src/{lib/prepareServer.js → core/prepareServer.ts} +13 -12
  60. package/src/fetcher.ts +126 -0
  61. package/src/index.ts +212 -0
  62. package/src/packageInfo.ts +3 -0
  63. package/src/typings.d.ts +3 -0
  64. package/tsconfig.json +24 -0
  65. package/src/cache.js +0 -209
  66. package/src/index.js +0 -175
  67. package/src/lib/getSchema.js +0 -34
  68. package/src/lib/index.js +0 -11
  69. package/src/lib/prepareAuth.js +0 -69
  70. package/src/lib/prepareParams.js +0 -198
@@ -0,0 +1,762 @@
1
+ "use strict";
2
+ var __extends = (this && this.__extends) || (function () {
3
+ var extendStatics = function (d, b) {
4
+ extendStatics = Object.setPrototypeOf ||
5
+ ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
6
+ function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
7
+ return extendStatics(d, b);
8
+ };
9
+ return function (d, b) {
10
+ if (typeof b !== "function" && b !== null)
11
+ throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
12
+ extendStatics(d, b);
13
+ function __() { this.constructor = d; }
14
+ d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
15
+ };
16
+ })();
17
+ var __assign = (this && this.__assign) || function () {
18
+ __assign = Object.assign || function(t) {
19
+ for (var s, i = 1, n = arguments.length; i < n; i++) {
20
+ s = arguments[i];
21
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
22
+ t[p] = s[p];
23
+ }
24
+ return t;
25
+ };
26
+ return __assign.apply(this, arguments);
27
+ };
28
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
29
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
30
+ return new (P || (P = Promise))(function (resolve, reject) {
31
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
32
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
33
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
34
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
35
+ });
36
+ };
37
+ var __generator = (this && this.__generator) || function (thisArg, body) {
38
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
39
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
40
+ function verb(n) { return function (v) { return step([n, v]); }; }
41
+ function step(op) {
42
+ if (f) throw new TypeError("Generator is already executing.");
43
+ while (_) try {
44
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
45
+ if (y = 0, t) op = [op[0] & 2, t.value];
46
+ switch (op[0]) {
47
+ case 0: case 1: t = op; break;
48
+ case 4: _.label++; return { value: op[1], done: false };
49
+ case 5: _.label++; y = op[1]; op = [0]; continue;
50
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
51
+ default:
52
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
53
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
54
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
55
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
56
+ if (t[2]) _.ops.pop();
57
+ _.trys.pop(); continue;
58
+ }
59
+ op = body.call(thisArg, _);
60
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
61
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
62
+ }
63
+ };
64
+ var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
65
+ if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
66
+ if (ar || !(i in from)) {
67
+ if (!ar) ar = Array.prototype.slice.call(from, 0, i);
68
+ ar[i] = from[i];
69
+ }
70
+ }
71
+ return to.concat(ar || Array.prototype.slice.call(from));
72
+ };
73
+ var __importDefault = (this && this.__importDefault) || function (mod) {
74
+ return (mod && mod.__esModule) ? mod : { "default": mod };
75
+ };
76
+ exports.__esModule = true;
77
+ var fs_1 = __importDefault(require("fs"));
78
+ var path_1 = __importDefault(require("path"));
79
+ var language_1 = __importDefault(require("../language"));
80
+ var logger_1 = __importDefault(require("../../logger"));
81
+ var object_hash_1 = __importDefault(require("object-hash"));
82
+ var ts_morph_1 = require("ts-morph");
83
+ var json_schema_to_typescript_1 = require("json-schema-to-typescript");
84
+ var formatter_1 = require("json-schema-to-typescript/dist/src/formatter");
85
+ var execa_1 = __importDefault(require("execa"));
86
+ // https://www.30secondsofcode.org/js/s/word-wrap
87
+ function wordWrap(str, max) {
88
+ if (max === void 0) { max = 88; }
89
+ return str.replace(new RegExp("(?![^\\n]{1,".concat(max, "}$)([^\\n]{1,").concat(max, "})\\s"), 'g'), '$1\n');
90
+ }
91
+ var TSGenerator = /** @class */ (function (_super) {
92
+ __extends(TSGenerator, _super);
93
+ function TSGenerator(spec, specPath, identifier, opts) {
94
+ if (opts === void 0) { opts = {}; }
95
+ var _this = this;
96
+ var options = __assign({ outputJS: false, compilerTarget: 'cjs' }, opts);
97
+ if (!options.outputJS) {
98
+ // TypeScript compilation will always target towards ESM-like imports and exports.
99
+ options.compilerTarget = 'esm';
100
+ }
101
+ _this = _super.call(this, spec, specPath, identifier) || this;
102
+ _this.requiredPackages = {
103
+ 'api@beta': {
104
+ reason: "Required for the `api/dist/core` library that the codegen'd SDK uses for making requests.",
105
+ url: 'https://npm.im/api'
106
+ },
107
+ oas: {
108
+ reason: 'Used within `api/dist/core` and is also loaded for TypeScript types.',
109
+ url: 'https://npm.im/oas'
110
+ }
111
+ };
112
+ _this.project = new ts_morph_1.Project({
113
+ manipulationSettings: {
114
+ indentationText: ts_morph_1.IndentationText.TwoSpaces,
115
+ quoteKind: ts_morph_1.QuoteKind.Single
116
+ },
117
+ compilerOptions: __assign({ declaration: true, resolveJsonModule: true, target: options.compilerTarget === 'cjs' ? ts_morph_1.ScriptTarget.ES5 : ts_morph_1.ScriptTarget.ES2020, outDir: 'dist' }, (options.compilerTarget === 'cjs' ? { esModuleInterop: true } : {}))
118
+ });
119
+ _this.compilerTarget = options.compilerTarget;
120
+ _this.outputJS = options.outputJS;
121
+ _this.types = new Map();
122
+ _this.methodGenerics = new Map();
123
+ _this.schemas = new Map();
124
+ return _this;
125
+ }
126
+ TSGenerator.formatter = function (content) {
127
+ return (0, formatter_1.format)(content, {
128
+ format: true,
129
+ style: {
130
+ printWidth: 120,
131
+ singleQuote: true
132
+ }
133
+ });
134
+ };
135
+ TSGenerator.prototype.installer = function (storage, opts) {
136
+ if (opts === void 0) { opts = {}; }
137
+ return __awaiter(this, void 0, void 0, function () {
138
+ var installDir, pkg, npmInstall;
139
+ return __generator(this, function (_a) {
140
+ switch (_a.label) {
141
+ case 0:
142
+ installDir = storage.getIdentifierStorageDir();
143
+ pkg = {
144
+ name: "@api/".concat(storage.identifier),
145
+ main: "./index.".concat(this.outputJS ? 'js' : 'ts'),
146
+ types: './index.d.ts'
147
+ };
148
+ fs_1["default"].writeFileSync(path_1["default"].join(installDir, 'package.json'), JSON.stringify(pkg, null, 2));
149
+ npmInstall = ['install', '--save', opts.dryRun ? '--dry-run' : ''].filter(Boolean);
150
+ // This will install packages required for the SDK within its installed directory in `.apis/`.
151
+ return [4 /*yield*/, (0, execa_1["default"])('npm', __spreadArray(__spreadArray([], npmInstall, true), Object.keys(this.requiredPackages), true).filter(Boolean), {
152
+ cwd: installDir
153
+ }).then(function (res) {
154
+ if (opts.dryRun) {
155
+ (opts.logger ? opts.logger : logger_1["default"])(res.command);
156
+ (opts.logger ? opts.logger : logger_1["default"])(res.stdout);
157
+ }
158
+ })];
159
+ case 1:
160
+ // This will install packages required for the SDK within its installed directory in `.apis/`.
161
+ _a.sent();
162
+ // This will install the installed SDK as a dependency within the current working directory,
163
+ // adding `@api/<sdk identifier>` as a dependency there so you can load it with
164
+ // `require('@api/<sdk identifier>)`.
165
+ return [2 /*return*/, (0, execa_1["default"])('npm', __spreadArray(__spreadArray([], npmInstall, true), [storage.getIdentifierStorageDir()], false).filter(Boolean)).then(function (res) {
166
+ if (opts.dryRun) {
167
+ (opts.logger ? opts.logger : logger_1["default"])(res.command);
168
+ (opts.logger ? opts.logger : logger_1["default"])(res.stdout);
169
+ }
170
+ })];
171
+ }
172
+ });
173
+ });
174
+ };
175
+ /**
176
+ * Compile the current OpenAPI definition into a TypeScript library.
177
+ *
178
+ */
179
+ TSGenerator.prototype.generator = function () {
180
+ return __awaiter(this, void 0, void 0, function () {
181
+ var _a, operations, methods, sdkSource;
182
+ var _this = this;
183
+ return __generator(this, function (_b) {
184
+ switch (_b.label) {
185
+ case 0: return [4 /*yield*/, this.loadOperationsAndMethods()];
186
+ case 1:
187
+ _a = _b.sent(), operations = _a.operations, methods = _a.methods;
188
+ sdkSource = this.project.createSourceFile('index.ts', '');
189
+ sdkSource.addImportDeclarations([
190
+ { defaultImport: 'Oas', moduleSpecifier: 'oas' },
191
+ { defaultImport: 'APICore', moduleSpecifier: 'api/dist/core' },
192
+ { defaultImport: 'definition', moduleSpecifier: this.specPath },
193
+ ]);
194
+ // @todo add TOS, License, info.* to a docblock at the top of the SDK.
195
+ this.sdk = sdkSource.addClass({
196
+ name: 'SDK'
197
+ });
198
+ // There's an annoying quirk with `ts-morph` where if we set the SDK class to be the default
199
+ // export with `isDefaultExport` then when we compile it to an ES5 target for CJS environments
200
+ // it'll be exported as `export.default = SDK`, which when you try to load it you'll need to
201
+ // run `require('@api/sdk').default`.
202
+ //
203
+ // Instead here by plainly creating the SDK class in the source file and then setting this
204
+ // export assignment it'll export the SDK class as `module.exports = SDK` so people can cleanly
205
+ // load the SDK with `require('@api/sdk)`.
206
+ //
207
+ // A whole lot of debugging went into here to let people not have to worry about `.default`
208
+ // messes. I hope it's worth it!
209
+ if (this.compilerTarget === 'cjs') {
210
+ sdkSource.addExportAssignment({
211
+ expression: 'SDK'
212
+ });
213
+ }
214
+ else {
215
+ this.sdk.setIsDefaultExport(true);
216
+ }
217
+ this.sdk.addProperties([
218
+ { name: 'spec', type: 'Oas' },
219
+ { name: 'core', type: 'APICore' },
220
+ { name: 'authKeys', type: '(number | string)[][]', initializer: '[]' },
221
+ ]);
222
+ this.sdk.addConstructor({
223
+ statements: function (writer) {
224
+ writer.writeLine('this.spec = Oas.init(definition);');
225
+ writer.write('this.core = new APICore(this.spec, ').quote(_this.userAgent).write(');');
226
+ return writer;
227
+ }
228
+ });
229
+ // Add our core API methods for controlling auth, servers, and various configurable abilities.
230
+ sdkSource.addInterface({
231
+ name: 'ConfigOptions',
232
+ properties: [
233
+ {
234
+ name: 'parseResponse',
235
+ type: 'boolean',
236
+ docs: [
237
+ wordWrap('By default we parse the response based on the `Content-Type` header of the request. You can disable this functionality by negating this option.'),
238
+ ]
239
+ },
240
+ ]
241
+ });
242
+ this.sdk.addMethods([
243
+ {
244
+ name: 'config',
245
+ parameters: [{ name: 'config', type: 'ConfigOptions' }],
246
+ statements: function (writer) { return writer.writeLine('this.core.setConfig(config);'); },
247
+ docs: [
248
+ {
249
+ description: function (writer) {
250
+ return writer.writeLine(wordWrap('Optionally configure various options, such as response parsing, that the SDK allows.'));
251
+ },
252
+ tags: [
253
+ { tagName: 'param', text: 'config Object of supported SDK options and toggles.' },
254
+ {
255
+ tagName: 'param',
256
+ text: 'config.parseResponse If responses are parsed according to its `Content-Type` header.'
257
+ },
258
+ ]
259
+ },
260
+ ]
261
+ },
262
+ {
263
+ name: 'auth',
264
+ parameters: [{ name: '...values', type: 'string[] | number[]' }],
265
+ statements: function (writer) {
266
+ writer.writeLine('this.core.setAuth(...values);');
267
+ writer.writeLine('return this;');
268
+ return writer;
269
+ },
270
+ docs: [
271
+ {
272
+ description: function (writer) {
273
+ return writer.writeLine(wordWrap("If the API you're using requires authentication you can supply the required credentials through this method and the library will magically determine how they should be used within your API request.\n\nWith the exception of OpenID and MutualTLS, it supports all forms of authentication supported by the OpenAPI specification.\n\n@example <caption>HTTP Basic auth</caption>\nsdk.auth('username', 'password');\n\n@example <caption>Bearer tokens (HTTP or OAuth 2)</caption>\nsdk.auth('myBearerToken');\n\n@example <caption>API Keys</caption>\nsdk.auth('myApiKey');"));
274
+ },
275
+ tags: [
276
+ { tagName: 'see', text: '{@link https://spec.openapis.org/oas/v3.0.3#fixed-fields-22}' },
277
+ { tagName: 'see', text: '{@link https://spec.openapis.org/oas/v3.1.0#fixed-fields-22}' },
278
+ {
279
+ tagName: 'param',
280
+ text: 'values Your auth credentials for the API; can specify up to two strings or numbers.'
281
+ },
282
+ ]
283
+ },
284
+ ]
285
+ },
286
+ {
287
+ name: 'server',
288
+ parameters: [
289
+ { name: 'url', type: 'string' },
290
+ { name: 'variables', initializer: '{}' },
291
+ ],
292
+ statements: function (writer) { return writer.writeLine('this.core.setServer(url, variables);'); },
293
+ docs: [
294
+ {
295
+ description: function (writer) {
296
+ return writer.writeLine(wordWrap("If the API you're using offers alternate server URLs, and server variables, you can tell the SDK which one to use with this method. To use it you can supply either one of the server URLs that are contained within the OpenAPI definition (along with any server variables), or you can pass it a fully qualified URL to use (that may or may not exist within the OpenAPI definition).\n\n@example <caption>Server URL with server variables</caption>\nsdk.server('https://{region}.api.example.com/{basePath}', {\n name: 'eu',\n basePath: 'v14',\n});\n\n@example <caption>Fully qualified server URL</caption>\nsdk.server('https://eu.api.example.com/v14');"));
297
+ },
298
+ tags: [
299
+ { tagName: 'param', text: 'url Server URL' },
300
+ { tagName: 'param', text: 'variables An object of variables to replace into the server URL.' },
301
+ ]
302
+ },
303
+ ]
304
+ },
305
+ ]);
306
+ // Add all common method accessors into the SDK.
307
+ Array.from(methods).forEach(function (method) { return _this.createGenericMethodAccessor(method); });
308
+ // Add all available operation ID accessors into the SDK.
309
+ Object.entries(operations).forEach(function (_a) {
310
+ var operationId = _a[0], data = _a[1];
311
+ _this.createOperationAccessor(data.operation, operationId, data.types.params, data.types.responses);
312
+ });
313
+ // @todo should all of these isolated into their own file outside of the main sdk class file?
314
+ // Add all known types that we're using into the SDK.
315
+ Array.from(this.types.values()).forEach(function (exp) {
316
+ sdkSource.addStatements(exp);
317
+ });
318
+ if (this.outputJS) {
319
+ return [2 /*return*/, this.project
320
+ .emitToMemory()
321
+ .getFiles()
322
+ .map(function (sourceFile) {
323
+ var _a;
324
+ return (_a = {},
325
+ _a[path_1["default"].basename(sourceFile.filePath)] = TSGenerator.formatter(sourceFile.text),
326
+ _a);
327
+ })
328
+ .reduce(function (prev, next) { return Object.assign(prev, next); })];
329
+ }
330
+ return [2 /*return*/, __spreadArray(__spreadArray([], this.project.getSourceFiles().map(function (sourceFile) {
331
+ var _a;
332
+ return (_a = {},
333
+ _a[sourceFile.getBaseName()] = TSGenerator.formatter(sourceFile.getFullText()),
334
+ _a);
335
+ }), true), this.project
336
+ .emitToMemory({ emitOnlyDtsFiles: true })
337
+ .getFiles()
338
+ .map(function (sourceFile) {
339
+ var _a;
340
+ return (_a = {},
341
+ _a[path_1["default"].basename(sourceFile.filePath)] = TSGenerator.formatter(sourceFile.text),
342
+ _a);
343
+ }), true).reduce(function (prev, next) { return Object.assign(prev, next); })];
344
+ }
345
+ });
346
+ });
347
+ };
348
+ /**
349
+ * Create a generic HTTP method accessor on the SDK.
350
+ *
351
+ * @param method
352
+ */
353
+ TSGenerator.prototype.createGenericMethodAccessor = function (method) {
354
+ var parameters = [{ name: 'path', type: 'string' }];
355
+ var docblock = {
356
+ description: function (writer) {
357
+ writer.writeLine("Access any ".concat(method, " endpoint on your API."));
358
+ return writer;
359
+ },
360
+ tags: [{ tagName: 'param', text: 'path API path to make a request against.' }]
361
+ };
362
+ // Method generic body + metadata parameters are always optional.
363
+ if (method !== 'get') {
364
+ parameters.push({ name: 'body', type: 'unknown', hasQuestionToken: true });
365
+ docblock.tags.push({ tagName: 'param', text: 'body Request body payload data.' });
366
+ }
367
+ parameters.push({ name: 'metadata', type: 'Record<string, unknown>', hasQuestionToken: true });
368
+ docblock.tags.push({
369
+ tagName: 'param',
370
+ text: 'metadata Object containing all path, query, header, and cookie parameters to supply.'
371
+ });
372
+ this.methodGenerics.set(method, this.sdk.addMethod({
373
+ name: method,
374
+ returnType: 'Promise<T>',
375
+ parameters: parameters,
376
+ typeParameters: ['T = unknown'],
377
+ docs: [docblock],
378
+ statements: function (writer) {
379
+ /**
380
+ * @example return this.core.fetch(path, 'get', body, metadata);
381
+ * @example return this.core.fetch(path, 'get', metadata);
382
+ */
383
+ var fetchStmt = writer.write('return this.core.fetch(path, ').quote(method).write(', ');
384
+ var fetchArgs = parameters.slice(1).map(function (p) { return p.name; });
385
+ fetchArgs.forEach(function (arg, i) {
386
+ fetchStmt.write(arg);
387
+ if (fetchArgs.length > 1 && i !== fetchArgs.length) {
388
+ fetchStmt.write(', ');
389
+ }
390
+ });
391
+ fetchStmt.write(');');
392
+ return fetchStmt;
393
+ }
394
+ }));
395
+ };
396
+ /**
397
+ * Create operation accessors on the SDK.
398
+ *
399
+ * @param operation
400
+ * @param operationId
401
+ * @param paramTypes
402
+ * @param responseTypes
403
+ */
404
+ TSGenerator.prototype.createOperationAccessor = function (operation, operationId, paramTypes, responseTypes) {
405
+ var _this = this;
406
+ var docblock = { tags: [] };
407
+ var summary = operation.getSummary();
408
+ var description = operation.getDescription();
409
+ if (summary || description) {
410
+ // To keep our generated docblocks clean we should only add the `@summary` tag if we've
411
+ // got both a summary and a description present on the operation, otherwise we can alternate
412
+ // what we surface the main docblock description.
413
+ docblock.description = function (writer) {
414
+ if (description) {
415
+ writer.writeLine(description);
416
+ }
417
+ else if (summary) {
418
+ writer.writeLine(summary);
419
+ }
420
+ writer.newLineIfLastNot();
421
+ return writer;
422
+ };
423
+ if (summary && description) {
424
+ docblock.tags.push({ tagName: 'summary', text: summary });
425
+ }
426
+ }
427
+ var hasOptionalBody = false;
428
+ var hasOptionalMetadata = false;
429
+ var parameters = {};
430
+ if (paramTypes) {
431
+ // If an operation has a request body payload it will only ever have `body` or `formData`,
432
+ // never both, as these are determined upon the media type that's in use.
433
+ if (paramTypes.body || paramTypes.formData) {
434
+ hasOptionalBody = !operation.hasRequiredRequestBody();
435
+ parameters.body = {
436
+ name: 'body',
437
+ type: paramTypes.body
438
+ ? this.schemas.get(paramTypes.body).tsType
439
+ : this.schemas.get(paramTypes.formData).tsType,
440
+ hasQuestionToken: hasOptionalBody
441
+ };
442
+ }
443
+ if (paramTypes.metadata) {
444
+ hasOptionalMetadata = !operation.hasRequiredParameters();
445
+ parameters.metadata = {
446
+ name: 'metadata',
447
+ type: this.schemas.get(paramTypes.metadata).tsType,
448
+ hasQuestionToken: hasOptionalMetadata
449
+ };
450
+ }
451
+ }
452
+ var returnType = 'Promise<T>';
453
+ var typeParameters = null;
454
+ if (responseTypes) {
455
+ returnType = "Promise<".concat(Object.values(responseTypes)
456
+ .map(function (hash) { return _this.schemas.get(hash).tsType; })
457
+ .join(' | '), ">");
458
+ }
459
+ else {
460
+ // We should only add the `<T>` method typing if we don't have any response types present.
461
+ typeParameters = ['T = unknown'];
462
+ }
463
+ var operationIdAccessor = this.sdk.addMethod({
464
+ name: operationId,
465
+ typeParameters: typeParameters,
466
+ returnType: returnType,
467
+ docs: docblock ? [docblock] : null,
468
+ statements: function (writer) {
469
+ /**
470
+ * @example return this.core.fetch('/pet/findByStatus', 'get', body, metadata);
471
+ * @example return this.core.fetch('/pet/findByStatus', 'get', metadata);
472
+ */
473
+ var fetchStmt = writer
474
+ .write('return this.core.fetch(')
475
+ .quote(operation.path)
476
+ .write(', ')
477
+ .quote(operation.method);
478
+ var totalParams = Object.keys(parameters).length;
479
+ if (totalParams) {
480
+ Object.values(parameters).forEach(function (arg, i) {
481
+ if (i === 0) {
482
+ fetchStmt.write(', ');
483
+ }
484
+ fetchStmt.write(arg.name);
485
+ if (totalParams > 1 && i !== totalParams) {
486
+ fetchStmt.write(', ');
487
+ }
488
+ });
489
+ }
490
+ fetchStmt.write(');');
491
+ return fetchStmt;
492
+ }
493
+ });
494
+ // If we have both body and metadata parameters but only body is optional we need to create
495
+ // a couple function overloads as Typescript doesn't let us have an optional method parameter
496
+ // come before one that's required.
497
+ //
498
+ // None of these accessor overloads will receive a docblock because the original will have
499
+ // that covered.
500
+ var shouldAddAltTypedOverloads = Object.keys(parameters).length === 2 && hasOptionalBody && !hasOptionalMetadata;
501
+ if (shouldAddAltTypedOverloads) {
502
+ // Create an overload that has both `body` and `metadata` parameters as required.
503
+ operationIdAccessor.addOverload({
504
+ typeParameters: typeParameters,
505
+ parameters: [
506
+ __assign(__assign({}, parameters.body), { hasQuestionToken: false }),
507
+ __assign(__assign({}, parameters.metadata), { hasQuestionToken: false }),
508
+ ],
509
+ returnType: returnType,
510
+ docs: docblock ? [docblock] : null
511
+ });
512
+ // Create an overload that just has a single `metadata` parameter.
513
+ operationIdAccessor.addOverload({
514
+ typeParameters: typeParameters,
515
+ parameters: [__assign({}, parameters.metadata)],
516
+ returnType: returnType,
517
+ docs: docblock ? [docblock] : null
518
+ });
519
+ // Create an overload that has both `body` and `metadata` parameters as optional. Even though
520
+ // our `metadata` parameter is actually required for this operation this is the only way we're
521
+ // able to have an optional `body` parameter be present before `metadata`.
522
+ //
523
+ // Thankfully our core fetch work in `api/dist/core` is able to do the proper determination to
524
+ // see if what the user is supplying is `metadata` or `body` content when they supply one or
525
+ // both.
526
+ operationIdAccessor.addParameters([
527
+ __assign(__assign({}, parameters.body), { hasQuestionToken: true }),
528
+ __assign(__assign({}, parameters.metadata), { hasQuestionToken: true }),
529
+ ]);
530
+ }
531
+ else {
532
+ operationIdAccessor.addParameters(Object.values(parameters));
533
+ }
534
+ // Add a typed generic HTTP method overload for this operation.
535
+ if (this.methodGenerics.has(operation.method)) {
536
+ // If we created alternate overloads for the operation accessor then we need to do the same
537
+ // for its generic HTTP counterpart.
538
+ if (shouldAddAltTypedOverloads) {
539
+ // Create an overload that has both `body` and `metadata` parameters as required.
540
+ this.methodGenerics.get(operation.method).addOverload({
541
+ typeParameters: typeParameters,
542
+ parameters: [
543
+ { name: 'path', type: 'string' },
544
+ __assign(__assign({}, parameters.body), { hasQuestionToken: false }),
545
+ __assign(__assign({}, parameters.metadata), { hasQuestionToken: false }),
546
+ ],
547
+ returnType: returnType,
548
+ docs: docblock ? [docblock] : null
549
+ });
550
+ // Create an overload that just has a single `metadata` parameter.
551
+ this.methodGenerics.get(operation.method).addOverload({
552
+ typeParameters: typeParameters,
553
+ parameters: [{ name: 'path', type: 'string' }, parameters.metadata],
554
+ returnType: returnType,
555
+ docs: docblock ? [docblock] : null
556
+ });
557
+ }
558
+ else {
559
+ this.methodGenerics.get(operation.method).addOverload({
560
+ typeParameters: responseTypes ? null : ['T = unknown'],
561
+ parameters: __spreadArray([{ name: 'path', type: 'string' }], Object.values(parameters), true),
562
+ returnType: returnType,
563
+ docs: docblock ? [docblock] : null
564
+ });
565
+ }
566
+ }
567
+ };
568
+ /**
569
+ * Convert a JSON Schema object into a readily available TypeScript type or interface along with
570
+ * any `$ref` pointers that are in use and turn those into TS types too.
571
+ *
572
+ * Under the hood this uses https://npm.im/json-schema-to-typescript for all composition and
573
+ * conversion.
574
+ *
575
+ * @param schema
576
+ * @param name
577
+ */
578
+ TSGenerator.prototype.convertJSONSchemaToTypescript = function (schema, name) {
579
+ return __awaiter(this, void 0, void 0, function () {
580
+ var ts, primaryType, tempProject, declarations;
581
+ var _this = this;
582
+ return __generator(this, function (_a) {
583
+ switch (_a.label) {
584
+ case 0: return [4 /*yield*/, (0, json_schema_to_typescript_1.compile)(schema, name, {
585
+ bannerComment: '',
586
+ // Running Prettier here for every JSON Schema object we're generating is way too slow so
587
+ // we're instead running it at the very end after we've constructed the SDK.
588
+ format: false
589
+ })];
590
+ case 1:
591
+ ts = _a.sent();
592
+ tempProject = this.project.createSourceFile("".concat(name, ".types.tmp.ts"), ts);
593
+ declarations = tempProject.getExportedDeclarations();
594
+ Array.from(declarations.keys()).forEach(function (declarationName) {
595
+ if (!primaryType) {
596
+ primaryType = declarationName;
597
+ }
598
+ declarations.get(declarationName).forEach(function (declaration) {
599
+ _this.types.set(declarationName, declaration.getText());
600
+ });
601
+ });
602
+ this.project.removeSourceFile(tempProject);
603
+ return [2 /*return*/, {
604
+ primaryType: primaryType
605
+ }];
606
+ }
607
+ });
608
+ });
609
+ };
610
+ /**
611
+ * Scour through the current OpenAPI definition and compile a store of every operation, along
612
+ * with every HTTP method that's in use, and their available TypeScript types that we can use,
613
+ * along with every HTTP method that's in use.
614
+ *
615
+ */
616
+ TSGenerator.prototype.loadOperationsAndMethods = function () {
617
+ return __awaiter(this, void 0, void 0, function () {
618
+ var operations, methods;
619
+ var _this = this;
620
+ return __generator(this, function (_a) {
621
+ switch (_a.label) {
622
+ case 0:
623
+ operations = {};
624
+ methods = new Set();
625
+ // Prepare all of the schemas that we need to process for every operation within this API
626
+ // definition.
627
+ Object.entries(this.spec.getPaths()).forEach(function (_a) {
628
+ var ops = _a[1];
629
+ Object.entries(ops).forEach(function (_a) {
630
+ var method = _a[0], operation = _a[1];
631
+ methods.add(method);
632
+ var operationId = operation.getOperationId();
633
+ var params = _this.prepareParameterTypesForOperation(operation, operationId);
634
+ var responses = _this.prepareResponseTypesForOperation(operation, operationId);
635
+ if (operation.hasOperationId()) {
636
+ operations[operation.getOperationId()] = {
637
+ types: {
638
+ params: params,
639
+ responses: responses
640
+ },
641
+ operation: operation
642
+ };
643
+ }
644
+ });
645
+ });
646
+ // Run through and convert every schema we need to use into TS types.
647
+ return [4 /*yield*/, Promise.all(Array.from(this.schemas.entries()).map(function (_a) {
648
+ var hash = _a[0], _b = _a[1], schema = _b.schema, schemaName = _b.name;
649
+ return __awaiter(_this, void 0, void 0, function () {
650
+ var ts;
651
+ return __generator(this, function (_c) {
652
+ switch (_c.label) {
653
+ case 0: return [4 /*yield*/, this.convertJSONSchemaToTypescript(schema, schemaName)];
654
+ case 1:
655
+ ts = _c.sent();
656
+ this.schemas.set(hash, __assign(__assign({}, this.schemas.get(hash)), { tsType: ts.primaryType }));
657
+ return [2 /*return*/];
658
+ }
659
+ });
660
+ });
661
+ }))];
662
+ case 1:
663
+ // Run through and convert every schema we need to use into TS types.
664
+ _a.sent();
665
+ return [2 /*return*/, {
666
+ operations: operations,
667
+ methods: methods
668
+ }];
669
+ }
670
+ });
671
+ });
672
+ };
673
+ /**
674
+ * Compile the parameter (path, query, cookie, and header) schemas for an API operation into
675
+ * usable TypeScript types.
676
+ *
677
+ * @param operation
678
+ * @param operationId
679
+ */
680
+ TSGenerator.prototype.prepareParameterTypesForOperation = function (operation, operationId) {
681
+ var _this = this;
682
+ var schemas = operation.getParametersAsJsonSchema({
683
+ mergeIntoBodyAndMetadata: true,
684
+ retainDeprecatedProperties: true
685
+ });
686
+ if (!schemas || !schemas.length) {
687
+ return false;
688
+ }
689
+ var res = schemas
690
+ .map(function (param) {
691
+ var _a;
692
+ return (_a = {}, _a[param.type] = param.schema, _a);
693
+ })
694
+ .reduce(function (prev, next) { return Object.assign(prev, next); });
695
+ return Object.entries(res)
696
+ .map(function (_a) {
697
+ var _b;
698
+ var paramType = _a[0], schema = _a[1];
699
+ var schemaName = schema['x-readme-ref-name'] || "".concat(operationId, "_").concat(paramType, "_param");
700
+ var hash = (0, object_hash_1["default"])({
701
+ name: schemaName,
702
+ schema: schema
703
+ });
704
+ if (!_this.schemas.has(hash)) {
705
+ _this.schemas.set(hash, {
706
+ schema: schema,
707
+ name: schemaName
708
+ });
709
+ }
710
+ return _b = {},
711
+ _b[paramType] = hash,
712
+ _b;
713
+ })
714
+ .reduce(function (prev, next) { return Object.assign(prev, next); }, {});
715
+ };
716
+ /**
717
+ * Compile the response schemas for an API operation into usable TypeScript types.
718
+ *
719
+ * @todo what does this do for a spec that has no responses?
720
+ * @param operation
721
+ * @param operationId
722
+ */
723
+ TSGenerator.prototype.prepareResponseTypesForOperation = function (operation, operationId) {
724
+ var _this = this;
725
+ var schemas = operation
726
+ .getResponseStatusCodes()
727
+ .map(function (status) {
728
+ var _a;
729
+ var schema = operation.getResponseAsJsonSchema(status);
730
+ if (!schema) {
731
+ return false;
732
+ }
733
+ return _a = {},
734
+ _a[status] = schema.shift(),
735
+ _a;
736
+ })
737
+ .reduce(function (prev, next) { return Object.assign(prev, next); });
738
+ var res = Object.entries(schemas)
739
+ .map(function (_a) {
740
+ var _b;
741
+ var status = _a[0], schema = _a[1].schema;
742
+ var schemaName = schema['x-readme-ref-name'] || "".concat(operationId, "_Response_").concat(status);
743
+ var hash = (0, object_hash_1["default"])({
744
+ name: schemaName,
745
+ schema: schema
746
+ });
747
+ if (!_this.schemas.has(hash)) {
748
+ _this.schemas.set(hash, {
749
+ schema: schema,
750
+ name: schemaName
751
+ });
752
+ }
753
+ return _b = {},
754
+ _b[status] = hash,
755
+ _b;
756
+ })
757
+ .reduce(function (prev, next) { return Object.assign(prev, next); }, {});
758
+ return Object.keys(res).length ? res : undefined;
759
+ };
760
+ return TSGenerator;
761
+ }(language_1["default"]));
762
+ exports["default"] = TSGenerator;