api 5.0.0-beta.2 → 5.0.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 (50) hide show
  1. package/README.md +7 -8
  2. package/dist/bin.js +1 -1
  3. package/dist/cache.d.ts +38 -3
  4. package/dist/cache.js +7 -26
  5. package/dist/cli/codegen/index.d.ts +1 -1
  6. package/dist/cli/codegen/language.d.ts +1 -1
  7. package/dist/cli/codegen/language.js +13 -0
  8. package/dist/cli/codegen/languages/typescript/util.d.ts +21 -0
  9. package/dist/cli/codegen/languages/typescript/util.js +185 -0
  10. package/dist/cli/codegen/languages/typescript.d.ts +36 -41
  11. package/dist/cli/codegen/languages/typescript.js +394 -414
  12. package/dist/cli/commands/install.js +6 -6
  13. package/dist/cli/storage.d.ts +1 -1
  14. package/dist/cli/storage.js +2 -2
  15. package/dist/core/errors/fetchError.d.ts +12 -0
  16. package/dist/core/errors/fetchError.js +36 -0
  17. package/dist/core/getJSONSchemaDefaults.d.ts +1 -1
  18. package/dist/core/index.d.ts +12 -4
  19. package/dist/core/index.js +36 -11
  20. package/dist/core/parseResponse.d.ts +6 -1
  21. package/dist/core/parseResponse.js +9 -3
  22. package/dist/core/prepareAuth.js +47 -18
  23. package/dist/core/prepareParams.d.ts +0 -3
  24. package/dist/core/prepareParams.js +102 -41
  25. package/dist/fetcher.d.ts +1 -1
  26. package/dist/fetcher.js +3 -3
  27. package/dist/index.d.ts +1 -1
  28. package/dist/index.js +24 -40
  29. package/dist/packageInfo.d.ts +1 -1
  30. package/dist/packageInfo.js +1 -1
  31. package/package.json +31 -17
  32. package/src/bin.ts +2 -1
  33. package/src/cache.ts +9 -31
  34. package/src/cli/codegen/index.ts +1 -1
  35. package/src/cli/codegen/language.ts +18 -1
  36. package/src/cli/codegen/languages/typescript/util.ts +183 -0
  37. package/src/cli/codegen/languages/typescript.ts +348 -340
  38. package/src/cli/commands/install.ts +6 -8
  39. package/src/cli/storage.ts +4 -4
  40. package/src/core/errors/fetchError.ts +31 -0
  41. package/src/core/getJSONSchemaDefaults.ts +3 -2
  42. package/src/core/index.ts +53 -18
  43. package/src/core/parseResponse.ts +8 -2
  44. package/src/core/prepareAuth.ts +55 -31
  45. package/src/core/prepareParams.ts +112 -41
  46. package/src/fetcher.ts +5 -4
  47. package/src/index.ts +24 -32
  48. package/src/packageInfo.ts +1 -1
  49. package/src/typings.d.ts +0 -1
  50. package/tsconfig.json +1 -1
@@ -76,18 +76,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
76
76
  exports.__esModule = true;
77
77
  var fs_1 = __importDefault(require("fs"));
78
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
79
  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
- }
80
+ var lodash_setwith_1 = __importDefault(require("lodash.setwith"));
81
+ var ts_morph_1 = require("ts-morph");
82
+ var logger_1 = __importDefault(require("../../logger"));
83
+ var language_1 = __importDefault(require("../language"));
84
+ var util_1 = require("./typescript/util");
91
85
  var TSGenerator = /** @class */ (function (_super) {
92
86
  __extends(TSGenerator, _super);
93
87
  function TSGenerator(spec, specPath, identifier, opts) {
@@ -99,11 +93,16 @@ var TSGenerator = /** @class */ (function (_super) {
99
93
  options.compilerTarget = 'esm';
100
94
  }
101
95
  _this = _super.call(this, spec, specPath, identifier) || this;
96
+ _this.usesHTTPMethodRangeInterface = false;
102
97
  _this.requiredPackages = {
103
- 'api@beta': {
98
+ api: {
104
99
  reason: "Required for the `api/dist/core` library that the codegen'd SDK uses for making requests.",
105
100
  url: 'https://npm.im/api'
106
101
  },
102
+ 'json-schema-to-ts': {
103
+ reason: 'Required for TypeScript type handling.',
104
+ url: 'https://npm.im/json-schema-to-ts'
105
+ },
107
106
  oas: {
108
107
  reason: 'Used within `api/dist/core` and is also loaded for TypeScript types.',
109
108
  url: 'https://npm.im/oas'
@@ -114,24 +113,17 @@ var TSGenerator = /** @class */ (function (_super) {
114
113
  indentationText: ts_morph_1.IndentationText.TwoSpaces,
115
114
  quoteKind: ts_morph_1.QuoteKind.Single
116
115
  },
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 } : {}))
116
+ compilerOptions: __assign({
117
+ // If we're exporting a TypeScript SDK then we don't need to pollute the codegen directory
118
+ // with unnecessary declaration `.d.ts` files.
119
+ declaration: options.outputJS, outDir: 'dist', resolveJsonModule: true, target: options.compilerTarget === 'cjs' ? ts_morph_1.ScriptTarget.ES5 : ts_morph_1.ScriptTarget.ES2020 }, (options.compilerTarget === 'cjs' ? { esModuleInterop: true } : {}))
118
120
  });
119
121
  _this.compilerTarget = options.compilerTarget;
120
122
  _this.outputJS = options.outputJS;
121
123
  _this.types = new Map();
122
- _this.methodGenerics = new Map();
123
- _this.schemas = new Map();
124
+ _this.schemas = {};
124
125
  return _this;
125
126
  }
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
127
  TSGenerator.prototype.installer = function (storage, opts) {
136
128
  if (opts === void 0) { opts = {}; }
137
129
  return __awaiter(this, void 0, void 0, function () {
@@ -162,7 +154,7 @@ var TSGenerator = /** @class */ (function (_super) {
162
154
  // This will install the installed SDK as a dependency within the current working directory,
163
155
  // adding `@api/<sdk identifier>` as a dependency there so you can load it with
164
156
  // `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) {
157
+ return [2 /*return*/, (0, execa_1["default"])('npm', __spreadArray([], npmInstall, true).filter(Boolean), { cwd: storage.getIdentifierStorageDir() }).then(function (res) {
166
158
  if (opts.dryRun) {
167
159
  (opts.logger ? opts.logger : logger_1["default"])(res.command);
168
160
  (opts.logger ? opts.logger : logger_1["default"])(res.stdout);
@@ -178,232 +170,278 @@ var TSGenerator = /** @class */ (function (_super) {
178
170
  */
179
171
  TSGenerator.prototype.generator = function () {
180
172
  return __awaiter(this, void 0, void 0, function () {
181
- var _a, operations, methods, sdkSource;
173
+ var sdkSource, types;
182
174
  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;
175
+ return __generator(this, function (_a) {
176
+ sdkSource = this.createSourceFile();
177
+ if (Object.keys(this.schemas).length) {
178
+ this.createSchemasFile();
179
+ this.createTypesFile();
180
+ types = Array.from(this.types.keys());
181
+ types.sort();
182
+ sdkSource.addExportDeclarations([
183
+ {
184
+ isTypeOnly: true,
185
+ namedExports: types,
186
+ moduleSpecifier: './types'
187
+ },
188
+ ]);
189
+ }
190
+ else {
191
+ // If we don't have any schemas then we shouldn't import a `types` file that doesn't exist.
192
+ sdkSource
193
+ .getImportDeclarations()
194
+ .find(function (id) { return id.getText() === "import type * as types from './types';"; })
195
+ .remove();
196
+ }
197
+ // If this SDK doesn't use the `HTTPMethodRange` interface for handling `2XX` response status
198
+ // codes then we should remove it from being imported.
199
+ if (!this.usesHTTPMethodRangeInterface) {
200
+ sdkSource
201
+ .getImportDeclarations()
202
+ .find(function (id) { return id.getText().includes('HTTPMethodRange'); })
203
+ .replaceWithText("import type { ConfigOptions, FetchResponse } from 'api/dist/core'");
204
+ }
205
+ if (this.outputJS) {
206
+ return [2 /*return*/, this.project
207
+ .emitToMemory()
208
+ .getFiles()
209
+ .map(function (sourceFile) {
210
+ var _a;
211
+ var file = path_1["default"].basename(sourceFile.filePath);
212
+ if (file === 'schemas.js' || file === 'types.js') {
213
+ // If we're generating a JS SDK then we don't need to generate these two files as the
214
+ // user will have `.d.ts` files for them instead.
215
+ return {};
227
216
  }
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
- },
217
+ var code = (0, util_1.formatter)(sourceFile.text);
218
+ if (file === 'index.js' && _this.compilerTarget === 'cjs') {
219
+ /**
220
+ * There's an annoying quirk with `ts-morph` where if we're exporting a default export
221
+ * to a CJS environment, it'll export it as `exports.default`. Because we don't want
222
+ * folks in these environments to have to load their SDKs with
223
+ * `require('@api/sdk').default` we're overriding that here to change it to being the
224
+ * module exports.
225
+ *
226
+ * `ts-morph` unfortunately doesn't give us any options for programatically doing this
227
+ * so we need to resort to modifying the emitted JS code.
228
+ */
229
+ code = code
230
+ .replace(/Object\.defineProperty\(exports, '__esModule', { value: true }\);\n/, '')
231
+ .replace('exports.default = createSDK;', 'module.exports = createSDK;');
232
+ }
233
+ return _a = {},
234
+ _a[file] = code,
235
+ _a;
236
+ })
237
+ .reduce(function (prev, next) { return Object.assign(prev, next); })];
238
+ }
239
+ return [2 /*return*/, __spreadArray(__spreadArray([], this.project.getSourceFiles().map(function (sourceFile) {
240
+ var _a;
241
+ return (_a = {},
242
+ _a[sourceFile.getBaseName()] = (0, util_1.formatter)(sourceFile.getFullText()),
243
+ _a);
244
+ }), true), this.project
245
+ .emitToMemory({ emitOnlyDtsFiles: true })
246
+ .getFiles()
247
+ .map(function (sourceFile) {
248
+ var _a;
249
+ return (_a = {},
250
+ _a[path_1["default"].basename(sourceFile.filePath)] = (0, util_1.formatter)(sourceFile.text),
251
+ _a);
252
+ }), true).reduce(function (prev, next) { return Object.assign(prev, next); })];
253
+ });
254
+ });
255
+ };
256
+ /**
257
+ * Create our main SDK source file.
258
+ *
259
+ */
260
+ TSGenerator.prototype.createSourceFile = function () {
261
+ var _this = this;
262
+ var operations = this.loadOperationsAndMethods().operations;
263
+ var sourceFile = this.project.createSourceFile('index.ts', '');
264
+ sourceFile.addImportDeclarations([
265
+ // This import will be automatically removed later if the SDK ends up not having any types.
266
+ { defaultImport: 'type * as types', moduleSpecifier: './types' },
267
+ {
268
+ // `HTTPMethodRange` will be conditionally removed later if it ends up not being used.
269
+ defaultImport: 'type { ConfigOptions, FetchResponse, HTTPMethodRange }',
270
+ moduleSpecifier: 'api/dist/core'
271
+ },
272
+ { defaultImport: 'Oas', moduleSpecifier: 'oas' },
273
+ { defaultImport: 'APICore', moduleSpecifier: 'api/dist/core' },
274
+ { defaultImport: 'definition', moduleSpecifier: this.specPath },
275
+ ]);
276
+ // @todo add TOS, License, info.* to a docblock at the top of the SDK.
277
+ this.sdk = sourceFile.addClass({
278
+ name: 'SDK',
279
+ properties: [
280
+ { name: 'spec', type: 'Oas' },
281
+ { name: 'core', type: 'APICore' },
282
+ ]
283
+ });
284
+ this.sdk.addConstructor({
285
+ statements: function (writer) {
286
+ writer.writeLine('this.spec = Oas.init(definition);');
287
+ writer.write('this.core = new APICore(this.spec, ').quote(_this.userAgent).write(');');
288
+ return writer;
289
+ }
290
+ });
291
+ // Add our core API methods for controlling auth, servers, and various configurable abilities.
292
+ this.sdk.addMethods([
293
+ {
294
+ name: 'config',
295
+ parameters: [{ name: 'config', type: 'ConfigOptions' }],
296
+ statements: function (writer) { return writer.writeLine('this.core.setConfig(config);'); },
297
+ docs: [
298
+ {
299
+ description: function (writer) {
300
+ return writer.writeLine((0, util_1.wordWrap)('Optionally configure various options that the SDK allows.'));
301
+ },
302
+ tags: [
303
+ { tagName: 'param', text: 'config Object of supported SDK options and toggles.' },
262
304
  {
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
- ]
305
+ tagName: 'param',
306
+ text: (0, util_1.wordWrap)('config.timeout Override the default `fetch` request timeout of 30 seconds. This number should be represented in milliseconds.')
285
307
  },
308
+ ]
309
+ },
310
+ ]
311
+ },
312
+ {
313
+ name: 'auth',
314
+ parameters: [{ name: '...values', type: 'string[] | number[]' }],
315
+ statements: function (writer) {
316
+ writer.writeLine('this.core.setAuth(...values);');
317
+ writer.writeLine('return this;');
318
+ return writer;
319
+ },
320
+ docs: [
321
+ {
322
+ description: function (writer) {
323
+ return writer.writeLine((0, util_1.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');"));
324
+ },
325
+ tags: [
326
+ { tagName: 'see', text: '{@link https://spec.openapis.org/oas/v3.0.3#fixed-fields-22}' },
327
+ { tagName: 'see', text: '{@link https://spec.openapis.org/oas/v3.1.0#fixed-fields-22}' },
286
328
  {
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
- ]
329
+ tagName: 'param',
330
+ text: 'values Your auth credentials for the API; can specify up to two strings or numbers.'
304
331
  },
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); })];
332
+ ]
333
+ },
334
+ ]
335
+ },
336
+ {
337
+ name: 'server',
338
+ parameters: [
339
+ { name: 'url', type: 'string' },
340
+ { name: 'variables', initializer: '{}' },
341
+ ],
342
+ statements: function (writer) { return writer.writeLine('this.core.setServer(url, variables);'); },
343
+ docs: [
344
+ {
345
+ description: function (writer) {
346
+ return writer.writeLine((0, util_1.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');"));
347
+ },
348
+ tags: [
349
+ { tagName: 'param', text: 'url Server URL' },
350
+ { tagName: 'param', text: 'variables An object of variables to replace into the server URL.' },
351
+ ]
352
+ },
353
+ ]
354
+ },
355
+ ]);
356
+ // Add all available operation ID accessors into the SDK.
357
+ Object.entries(operations).forEach(function (_a) {
358
+ var operationId = _a[0], data = _a[1];
359
+ _this.createOperationAccessor(data.operation, operationId, data.types.params, data.types.responses);
360
+ });
361
+ // Export our SDK into the source file.
362
+ sourceFile.addVariableStatement({
363
+ declarationKind: ts_morph_1.VariableDeclarationKind.Const,
364
+ declarations: [
365
+ {
366
+ name: 'createSDK',
367
+ initializer: function (writer) {
368
+ // `ts-morph` doesn't have any way to cleanly create an IFEE.
369
+ writer.writeLine('(() => { return new SDK(); })()');
370
+ return writer;
371
+ }
372
+ },
373
+ ]
374
+ });
375
+ sourceFile.addExportAssignment({ isExportEquals: false, expression: 'createSDK' });
376
+ return sourceFile;
377
+ };
378
+ /**
379
+ * Create our main schemas file. This is where all of the JSON Schema that our TypeScript typing
380
+ * infrastructure sources its data from. Without this there are no types.
381
+ *
382
+ */
383
+ TSGenerator.prototype.createSchemasFile = function () {
384
+ var sourceFile = this.project.createSourceFile('schemas.ts', '');
385
+ var sortedSchemas = new Map(Array.from(Object.entries(this.schemas)).sort());
386
+ Array.from(sortedSchemas).forEach(function (_a) {
387
+ var schemaName = _a[0], schema = _a[1];
388
+ sourceFile.addVariableStatement({
389
+ declarationKind: ts_morph_1.VariableDeclarationKind.Const,
390
+ declarations: [
391
+ {
392
+ name: schemaName,
393
+ initializer: function (writer) {
394
+ /**
395
+ * This is the conversion prefix that we add to all `$ref` pointers we find in
396
+ * generated JSON Schema.
397
+ *
398
+ * Because the pointer name is a string we want to have it reference the schema
399
+ * constant we're adding into the codegen'd schema file. As there's no way, not even
400
+ * using `eval()` in this case, to convert a string to a constant we're prefixing
401
+ * them with this so we can later remove it and rewrite the value to a literal.
402
+ * eg. `'Pet'` becomes `Pet`.
403
+ *
404
+ * And because our TypeScript type name generator properly ignores `:`, this is safe
405
+ * to prepend to all generated type names.
406
+ */
407
+ var str = JSON.stringify(schema);
408
+ str = str.replace(/"::convert::([a-zA-Z_$\\d]*)"/g, '$1');
409
+ writer.writeLine("".concat(str, " as const"));
410
+ return writer;
329
411
  }
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
- }
412
+ },
413
+ ]
345
414
  });
346
415
  });
416
+ sourceFile.addStatements("export { ".concat(Array.from(sortedSchemas.keys()).join(', '), " }"));
417
+ return sourceFile;
347
418
  };
348
419
  /**
349
- * Create a generic HTTP method accessor on the SDK.
420
+ * Create our main types file. This sources its data from the JSON Schema `schemas.ts` file and
421
+ * will re-export types to be used in TypeScript implementations and IDE intellisense. This
422
+ * typing work is functional with the `json-schema-to-ts` library.
350
423
  *
351
- * @param method
424
+ * @see {@link https://npm.im/json-schema-to-ts}
352
425
  */
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.toUpperCase(), " 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.'
426
+ TSGenerator.prototype.createTypesFile = function () {
427
+ var sourceFile = this.project.createSourceFile('types.ts', '');
428
+ sourceFile.addImportDeclarations([
429
+ { defaultImport: 'type { FromSchema }', moduleSpecifier: 'json-schema-to-ts' },
430
+ { defaultImport: '* as schemas', moduleSpecifier: './schemas' },
431
+ ]);
432
+ Array.from(new Map(Array.from(this.types.entries()).sort())).forEach(function (_a) {
433
+ var typeName = _a[0], typeExpression = _a[1];
434
+ sourceFile.addTypeAlias({ isExported: true, name: typeName, type: typeExpression });
371
435
  });
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
- }));
436
+ return sourceFile;
395
437
  };
396
438
  /**
397
439
  * Create operation accessors on the SDK.
398
440
  *
399
- * @param operation
400
- * @param operationId
401
- * @param paramTypes
402
- * @param responseTypes
403
441
  */
404
442
  TSGenerator.prototype.createOperationAccessor = function (operation, operationId, paramTypes, responseTypes) {
405
443
  var _this = this;
406
- var docblock = { tags: [] };
444
+ var docblock = {};
407
445
  var summary = operation.getSummary();
408
446
  var description = operation.getDescription();
409
447
  if (summary || description) {
@@ -412,16 +450,16 @@ var TSGenerator = /** @class */ (function (_super) {
412
450
  // what we surface the main docblock description.
413
451
  docblock.description = function (writer) {
414
452
  if (description) {
415
- writer.writeLine(description);
453
+ writer.writeLine((0, util_1.docblockEscape)((0, util_1.wordWrap)(description)));
416
454
  }
417
455
  else if (summary) {
418
- writer.writeLine(summary);
456
+ writer.writeLine((0, util_1.docblockEscape)((0, util_1.wordWrap)(summary)));
419
457
  }
420
458
  writer.newLineIfLastNot();
421
459
  return writer;
422
460
  };
423
461
  if (summary && description) {
424
- docblock.tags.push({ tagName: 'summary', text: summary });
462
+ docblock.tags = [{ tagName: 'summary', text: (0, util_1.docblockEscape)((0, util_1.wordWrap)(summary)) }];
425
463
  }
426
464
  }
427
465
  var hasOptionalBody = false;
@@ -434,9 +472,7 @@ var TSGenerator = /** @class */ (function (_super) {
434
472
  hasOptionalBody = !operation.hasRequiredRequestBody();
435
473
  parameters.body = {
436
474
  name: 'body',
437
- type: paramTypes.body
438
- ? this.schemas.get(paramTypes.body).tsType
439
- : this.schemas.get(paramTypes.formData).tsType,
475
+ type: paramTypes.body ? paramTypes.body : paramTypes.formData,
440
476
  hasQuestionToken: hasOptionalBody
441
477
  };
442
478
  }
@@ -444,27 +480,37 @@ var TSGenerator = /** @class */ (function (_super) {
444
480
  hasOptionalMetadata = !operation.hasRequiredParameters();
445
481
  parameters.metadata = {
446
482
  name: 'metadata',
447
- type: this.schemas.get(paramTypes.metadata).tsType,
483
+ type: paramTypes.metadata,
448
484
  hasQuestionToken: hasOptionalMetadata
449
485
  };
450
486
  }
451
487
  }
452
- var returnType = 'Promise<T>';
453
- var typeParameters = null;
488
+ var returnType = 'Promise<FetchResponse<number, unknown>>';
454
489
  if (responseTypes) {
455
- returnType = "Promise<".concat(Object.values(responseTypes)
456
- .map(function (hash) { return _this.schemas.get(hash).tsType; })
490
+ returnType = "Promise<".concat(Object.entries(responseTypes)
491
+ .map(function (_a) {
492
+ var status = _a[0], responseType = _a[1];
493
+ if (status.toLowerCase() === 'default') {
494
+ return "FetchResponse<number, ".concat(responseType, ">");
495
+ }
496
+ else if (status.length === 3 && status.toUpperCase().endsWith('XX')) {
497
+ var statusPrefix = status.slice(0, 1);
498
+ if (!Number.isInteger(Number(statusPrefix))) {
499
+ // If this matches the `_XX` format, but it isn't `{number}XX` then we can't handle
500
+ // it and should instead fall back to treating it as an unknown number.
501
+ return "FetchResponse<number, ".concat(responseType, ">");
502
+ }
503
+ _this.usesHTTPMethodRangeInterface = true;
504
+ return "FetchResponse<HTTPMethodRange<".concat(statusPrefix, "00, ").concat(statusPrefix, "99>, ").concat(responseType, ">");
505
+ }
506
+ return "FetchResponse<".concat(status, ", ").concat(responseType, ">");
507
+ })
457
508
  .join(' | '), ">");
458
509
  }
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
510
  var operationIdAccessor = this.sdk.addMethod({
464
511
  name: operationId,
465
- typeParameters: typeParameters,
466
512
  returnType: returnType,
467
- docs: docblock ? [docblock] : null,
513
+ docs: Object.keys(docblock).length ? [docblock] : null,
468
514
  statements: function (writer) {
469
515
  /**
470
516
  * @example return this.core.fetch('/pet/findByStatus', 'get', body, metadata);
@@ -501,20 +547,18 @@ var TSGenerator = /** @class */ (function (_super) {
501
547
  if (shouldAddAltTypedOverloads) {
502
548
  // Create an overload that has both `body` and `metadata` parameters as required.
503
549
  operationIdAccessor.addOverload({
504
- typeParameters: typeParameters,
505
550
  parameters: [
506
551
  __assign(__assign({}, parameters.body), { hasQuestionToken: false }),
507
552
  __assign(__assign({}, parameters.metadata), { hasQuestionToken: false }),
508
553
  ],
509
554
  returnType: returnType,
510
- docs: docblock ? [docblock] : null
555
+ docs: Object.keys(docblock).length ? [docblock] : null
511
556
  });
512
557
  // Create an overload that just has a single `metadata` parameter.
513
558
  operationIdAccessor.addOverload({
514
- typeParameters: typeParameters,
515
559
  parameters: [__assign({}, parameters.metadata)],
516
560
  returnType: returnType,
517
- docs: docblock ? [docblock] : null
561
+ docs: Object.keys(docblock).length ? [docblock] : null
518
562
  });
519
563
  // Create an overload that has both `body` and `metadata` parameters as optional. Even though
520
564
  // our `metadata` parameter is actually required for this operation this is the only way we're
@@ -524,88 +568,18 @@ var TSGenerator = /** @class */ (function (_super) {
524
568
  // see if what the user is supplying is `metadata` or `body` content when they supply one or
525
569
  // both.
526
570
  operationIdAccessor.addParameters([
527
- __assign(__assign({}, parameters.body), { hasQuestionToken: true }),
571
+ __assign(__assign({}, parameters.body), {
572
+ // Overloads have to be the most distilled version of the method so that's why we need to
573
+ // type `body` as either `body` or `metadata`. If we didn't do this, if `body` was a JSON
574
+ // Schema type that didn't allow `additionalProperties` then the implementation overload
575
+ // would throw type errors.
576
+ type: "".concat(parameters.body.type, " | ").concat(parameters.metadata.type), hasQuestionToken: true }),
528
577
  __assign(__assign({}, parameters.metadata), { hasQuestionToken: true }),
529
578
  ]);
530
579
  }
531
580
  else {
532
581
  operationIdAccessor.addParameters(Object.values(parameters));
533
582
  }
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: "'".concat(operation.path, "'") },
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: "'".concat(operation.path, "'") }, 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: "'".concat(operation.path, "'") }], 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
583
  };
610
584
  /**
611
585
  * Scour through the current OpenAPI definition and compile a store of every operation, along
@@ -614,78 +588,59 @@ var TSGenerator = /** @class */ (function (_super) {
614
588
  *
615
589
  */
616
590
  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
- // This `camelCase` option will clean up any weird characters that might be present in
634
- // the `operationId` so as we don't break TS compilation with an invalid method accessor.
635
- camelCase: true
636
- });
637
- var params = _this.prepareParameterTypesForOperation(operation, operationId);
638
- var responses = _this.prepareResponseTypesForOperation(operation, operationId);
639
- if (operation.hasOperationId()) {
640
- operations[operationId] = {
641
- types: {
642
- params: params,
643
- responses: responses
644
- },
645
- operation: operation
646
- };
647
- }
648
- });
649
- });
650
- // Run through and convert every schema we need to use into TS types.
651
- return [4 /*yield*/, Promise.all(Array.from(this.schemas.entries()).map(function (_a) {
652
- var hash = _a[0], _b = _a[1], schema = _b.schema, schemaName = _b.name;
653
- return __awaiter(_this, void 0, void 0, function () {
654
- var ts;
655
- return __generator(this, function (_c) {
656
- switch (_c.label) {
657
- case 0: return [4 /*yield*/, this.convertJSONSchemaToTypescript(schema, schemaName)];
658
- case 1:
659
- ts = _c.sent();
660
- this.schemas.set(hash, __assign(__assign({}, this.schemas.get(hash)), { tsType: ts.primaryType }));
661
- return [2 /*return*/];
662
- }
663
- });
664
- });
665
- }))];
666
- case 1:
667
- // Run through and convert every schema we need to use into TS types.
668
- _a.sent();
669
- return [2 /*return*/, {
670
- operations: operations,
671
- methods: methods
672
- }];
673
- }
591
+ var _this = this;
592
+ var operations = {};
593
+ var methods = new Set();
594
+ // Prepare all of the schemas that we need to process for every operation within this API
595
+ // definition.
596
+ Object.entries(this.spec.getPaths()).forEach(function (_a) {
597
+ var ops = _a[1];
598
+ Object.entries(ops).forEach(function (_a) {
599
+ var method = _a[0], operation = _a[1];
600
+ methods.add(method);
601
+ var operationId = operation.getOperationId({
602
+ // This `camelCase` option will clean up any weird characters that might be present in
603
+ // the `operationId` so as we don't break TS compilation with an invalid method accessor.
604
+ camelCase: true
605
+ });
606
+ operations[operationId] = {
607
+ types: {
608
+ params: _this.prepareParameterTypesForOperation(operation, operationId),
609
+ responses: _this.prepareResponseTypesForOperation(operation, operationId)
610
+ },
611
+ operation: operation
612
+ };
674
613
  });
675
614
  });
615
+ if (!Object.keys(operations).length) {
616
+ throw new Error('Sorry, this OpenAPI definition does not have any operation paths to generate an SDK for.');
617
+ }
618
+ return {
619
+ operations: operations,
620
+ methods: methods
621
+ };
676
622
  };
677
623
  /**
678
624
  * Compile the parameter (path, query, cookie, and header) schemas for an API operation into
679
625
  * usable TypeScript types.
680
626
  *
681
- * @param operation
682
- * @param operationId
683
627
  */
684
628
  TSGenerator.prototype.prepareParameterTypesForOperation = function (operation, operationId) {
685
629
  var _this = this;
686
- var schemas = operation.getParametersAsJsonSchema({
630
+ var schemas = operation.getParametersAsJSONSchema({
631
+ includeDiscriminatorMappingRefs: false,
687
632
  mergeIntoBodyAndMetadata: true,
688
- retainDeprecatedProperties: true
633
+ retainDeprecatedProperties: true,
634
+ transformer: function (s) {
635
+ // As our schemas are dereferenced in the `oas` library we don't want to pollute our
636
+ // codegen'd schemas file with duplicate schemas.
637
+ if ('x-readme-ref-name' in s) {
638
+ var typeName = (0, util_1.generateTypeName)(s['x-readme-ref-name']);
639
+ _this.addSchemaToExport(s, typeName, typeName);
640
+ return "::convert::".concat(typeName);
641
+ }
642
+ return s;
643
+ }
689
644
  });
690
645
  if (!schemas || !schemas.length) {
691
646
  return false;
@@ -700,19 +655,20 @@ var TSGenerator = /** @class */ (function (_super) {
700
655
  .map(function (_a) {
701
656
  var _b;
702
657
  var paramType = _a[0], schema = _a[1];
703
- var schemaName = schema['x-readme-ref-name'] || "".concat(operationId, "_").concat(paramType, "_param");
704
- var hash = (0, object_hash_1["default"])({
705
- name: schemaName,
706
- schema: schema
707
- });
708
- if (!_this.schemas.has(hash)) {
709
- _this.schemas.set(hash, {
710
- schema: schema,
711
- name: schemaName
712
- });
658
+ var typeName;
659
+ if (typeof schema === 'string' && schema.startsWith('::convert::')) {
660
+ // If this schema is a string and has our conversion prefix then we've already created
661
+ // a type for it.
662
+ typeName = schema.replace('::convert::', '');
663
+ }
664
+ else {
665
+ typeName = (0, util_1.generateTypeName)(operationId, paramType, 'param');
666
+ _this.addSchemaToExport(schema, typeName, "".concat((0, util_1.generateTypeName)(operationId), ".").concat(paramType));
713
667
  }
714
668
  return _b = {},
715
- _b[paramType] = hash,
669
+ // Types are prefixed with `types.` because that's how we're importing them from
670
+ // `types.d.ts`.
671
+ _b[paramType] = "types.".concat(typeName),
716
672
  _b;
717
673
  })
718
674
  .reduce(function (prev, next) { return Object.assign(prev, next); }, {});
@@ -720,9 +676,6 @@ var TSGenerator = /** @class */ (function (_super) {
720
676
  /**
721
677
  * Compile the response schemas for an API operation into usable TypeScript types.
722
678
  *
723
- * @todo what does this do for a spec that has no responses?
724
- * @param operation
725
- * @param operationId
726
679
  */
727
680
  TSGenerator.prototype.prepareResponseTypesForOperation = function (operation, operationId) {
728
681
  var _this = this;
@@ -733,7 +686,19 @@ var TSGenerator = /** @class */ (function (_super) {
733
686
  var schemas = responseStatusCodes
734
687
  .map(function (status) {
735
688
  var _a;
736
- var schema = operation.getResponseAsJsonSchema(status);
689
+ var schema = operation.getResponseAsJSONSchema(status, {
690
+ includeDiscriminatorMappingRefs: false,
691
+ transformer: function (s) {
692
+ // As our schemas are dereferenced in the `oas` library we don't want to pollute our
693
+ // codegen'd schemas file with duplicate schemas.
694
+ if ('x-readme-ref-name' in s) {
695
+ var typeName = (0, util_1.generateTypeName)(s['x-readme-ref-name']);
696
+ _this.addSchemaToExport(s, typeName, "".concat(typeName));
697
+ return "::convert::".concat(typeName);
698
+ }
699
+ return s;
700
+ }
701
+ });
737
702
  if (!schema) {
738
703
  return false;
739
704
  }
@@ -746,24 +711,39 @@ var TSGenerator = /** @class */ (function (_super) {
746
711
  .map(function (_a) {
747
712
  var _b;
748
713
  var status = _a[0], schema = _a[1].schema;
749
- var schemaName = schema['x-readme-ref-name'] || "".concat(operationId, "_Response_").concat(status);
750
- var hash = (0, object_hash_1["default"])({
751
- name: schemaName,
752
- schema: schema
753
- });
754
- if (!_this.schemas.has(hash)) {
755
- _this.schemas.set(hash, {
756
- schema: schema,
757
- name: schemaName
758
- });
714
+ var typeName;
715
+ if (typeof schema === 'string' && schema.startsWith('::convert::')) {
716
+ // If this schema is a string and has our conversion prefix then we've already created
717
+ // a type for it.
718
+ typeName = schema.replace('::convert::', '');
719
+ }
720
+ else {
721
+ typeName = (0, util_1.generateTypeName)(operationId, 'response', status);
722
+ // Because `status` will usually be a number here we need to set the pointer for it
723
+ // within an `[]` as if we do `FromSchema<typeof schemas.operation.response.200>`,
724
+ // TypeScript will throw a compilation error.
725
+ _this.addSchemaToExport(schema, typeName, "".concat((0, util_1.generateTypeName)(operationId), ".response['").concat(status, "']"));
759
726
  }
760
727
  return _b = {},
761
- _b[status] = hash,
728
+ // Types are prefixed with `types.` because that's how we're importing them from
729
+ // `types.d.ts`.
730
+ _b[status] = "types.".concat(typeName),
762
731
  _b;
763
732
  })
764
733
  .reduce(function (prev, next) { return Object.assign(prev, next); }, {});
765
734
  return Object.keys(res).length ? res : undefined;
766
735
  };
736
+ /**
737
+ * Add a given schema into our schema dataset that we'll be be exporting as types.
738
+ *
739
+ */
740
+ TSGenerator.prototype.addSchemaToExport = function (schema, typeName, pointer) {
741
+ if (this.types.has(typeName)) {
742
+ return;
743
+ }
744
+ (0, lodash_setwith_1["default"])(this.schemas, pointer, schema, Object);
745
+ this.types.set(typeName, "FromSchema<typeof schemas.".concat(pointer, ">"));
746
+ };
767
747
  return TSGenerator;
768
748
  }(language_1["default"]));
769
749
  exports["default"] = TSGenerator;