api 5.0.0-beta.3 → 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 (46) hide show
  1. package/README.md +7 -7
  2. package/dist/bin.js +1 -1
  3. package/dist/cache.d.ts +37 -2
  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 +31 -38
  11. package/dist/cli/codegen/languages/typescript.js +390 -478
  12. package/dist/cli/commands/install.js +6 -6
  13. package/dist/cli/storage.js +2 -2
  14. package/dist/core/errors/fetchError.d.ts +12 -0
  15. package/dist/core/errors/fetchError.js +36 -0
  16. package/dist/core/index.d.ts +11 -3
  17. package/dist/core/index.js +36 -11
  18. package/dist/core/parseResponse.d.ts +6 -1
  19. package/dist/core/parseResponse.js +9 -3
  20. package/dist/core/prepareAuth.js +47 -18
  21. package/dist/core/prepareParams.d.ts +0 -3
  22. package/dist/core/prepareParams.js +81 -57
  23. package/dist/fetcher.js +3 -3
  24. package/dist/index.js +24 -40
  25. package/dist/packageInfo.d.ts +1 -1
  26. package/dist/packageInfo.js +1 -1
  27. package/package.json +28 -17
  28. package/src/bin.ts +2 -1
  29. package/src/cache.ts +8 -30
  30. package/src/cli/codegen/index.ts +1 -1
  31. package/src/cli/codegen/language.ts +18 -1
  32. package/src/cli/codegen/languages/typescript/util.ts +183 -0
  33. package/src/cli/codegen/languages/typescript.ts +340 -402
  34. package/src/cli/commands/install.ts +6 -8
  35. package/src/cli/storage.ts +3 -3
  36. package/src/core/errors/fetchError.ts +31 -0
  37. package/src/core/getJSONSchemaDefaults.ts +2 -1
  38. package/src/core/index.ts +52 -17
  39. package/src/core/parseResponse.ts +8 -2
  40. package/src/core/prepareAuth.ts +55 -31
  41. package/src/core/prepareParams.ts +88 -55
  42. package/src/fetcher.ts +4 -3
  43. package/src/index.ts +24 -32
  44. package/src/packageInfo.ts +1 -1
  45. package/src/typings.d.ts +0 -1
  46. package/tsconfig.json +1 -1
package/dist/index.js CHANGED
@@ -1,15 +1,4 @@
1
1
  "use strict";
2
- var __assign = (this && this.__assign) || function () {
3
- __assign = Object.assign || function(t) {
4
- for (var s, i = 1, n = arguments.length; i < n; i++) {
5
- s = arguments[i];
6
- for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
7
- t[p] = s[p];
8
- }
9
- return t;
10
- };
11
- return __assign.apply(this, arguments);
12
- };
13
2
  var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
14
3
  function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
15
4
  return new (P || (P = Promise))(function (resolve, reject) {
@@ -59,8 +48,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
59
48
  return (mod && mod.__esModule) ? mod : { "default": mod };
60
49
  };
61
50
  var oas_1 = __importDefault(require("oas"));
62
- var core_1 = __importDefault(require("./core"));
63
51
  var cache_1 = __importDefault(require("./cache"));
52
+ var core_1 = __importDefault(require("./core"));
64
53
  var packageInfo_1 = require("./packageInfo");
65
54
  var Sdk = /** @class */ (function () {
66
55
  function Sdk(uri, opts) {
@@ -77,28 +66,6 @@ var Sdk = /** @class */ (function () {
77
66
  var isLoaded = false;
78
67
  var isCached = cache.isCached();
79
68
  var sdk = {};
80
- /**
81
- * Create dynamic accessors for every HTTP method that the OpenAPI specification supports.
82
- *
83
- * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.3.md#fixed-fields-7}
84
- * @see {@link https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md#fixed-fields-7}
85
- */
86
- function loadMethods() {
87
- return ['get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace']
88
- .map(function (httpVerb) {
89
- var _a;
90
- return _a = {},
91
- _a[httpVerb] = (function (method, path) {
92
- var args = [];
93
- for (var _i = 2; _i < arguments.length; _i++) {
94
- args[_i - 2] = arguments[_i];
95
- }
96
- return core.fetch.apply(core, __spreadArray([path, method], args, false));
97
- }).bind(null, httpVerb),
98
- _a;
99
- })
100
- .reduce(function (prev, next) { return Object.assign(prev, next); });
101
- }
102
69
  /**
103
70
  * Create dynamic accessors for every operation with a defined operation ID. If an operation
104
71
  * does not have an operation ID it can be accessed by its `.method('/path')` accessor instead.
@@ -112,15 +79,15 @@ var Sdk = /** @class */ (function () {
112
79
  return Object.values(operations);
113
80
  })
114
81
  .reduce(function (prev, next) { return prev.concat(next); }, [])
115
- .filter(function (operation) { return operation.hasOperationId(); })
116
82
  .reduce(function (prev, next) {
117
83
  var _a;
118
84
  // `getOperationId()` creates dynamic operation IDs when one isn't available but we need
119
85
  // to know here if we actually have one present or not. The `camelCase` option here also
120
86
  // cleans up any `operationId` that we might have into something that can be used as a
121
87
  // valid JS method.
88
+ var originalOperationId = next.getOperationId();
122
89
  var operationId = next.getOperationId({ camelCase: true });
123
- return Object.assign(prev, (_a = {},
90
+ var op = (_a = {},
124
91
  _a[operationId] = (function (operation) {
125
92
  var args = [];
126
93
  for (var _i = 1; _i < arguments.length; _i++) {
@@ -128,7 +95,24 @@ var Sdk = /** @class */ (function () {
128
95
  }
129
96
  return core.fetchOperation.apply(core, __spreadArray([operation], args, false));
130
97
  }).bind(null, next),
131
- _a));
98
+ _a);
99
+ if (operationId !== originalOperationId) {
100
+ // If we cleaned up their operation ID into a friendly method accessor (`findPetById`
101
+ // versus `find pet by id`) we should still let them use the non-friendly version if
102
+ // they want.
103
+ //
104
+ // This work is to maintain backwards compatibility with `api@4` and does not exist
105
+ // within our code generated SDKs -- those only allow the cleaner camelCase
106
+ // `operationId` to be used.
107
+ op[originalOperationId] = (function (operation) {
108
+ var args = [];
109
+ for (var _i = 1; _i < arguments.length; _i++) {
110
+ args[_i - 1] = arguments[_i];
111
+ }
112
+ return core.fetchOperation.apply(core, __spreadArray([operation], args, false));
113
+ }).bind(null, next);
114
+ }
115
+ return Object.assign(prev, op);
132
116
  }, {});
133
117
  }
134
118
  function loadFromCache() {
@@ -150,7 +134,7 @@ var Sdk = /** @class */ (function () {
150
134
  case 4:
151
135
  spec = new oas_1["default"](cachedSpec);
152
136
  core.setSpec(spec);
153
- sdk = Object.assign(sdk, __assign(__assign({}, loadMethods()), loadOperations(spec)));
137
+ sdk = Object.assign(sdk, loadOperations(spec));
154
138
  isLoaded = true;
155
139
  return [2 /*return*/];
156
140
  }
@@ -234,10 +218,10 @@ var Sdk = /** @class */ (function () {
234
218
  core.setAuth.apply(core, values);
235
219
  },
236
220
  /**
237
- * Optionally configure various options, such as response parsing, that the SDK allows.
221
+ * Optionally configure various options that the SDK allows.
238
222
  *
239
223
  * @param config Object of supported SDK options and toggles.
240
- * @param config.parseResponse If responses are parsed according to its `Content-Type` header.
224
+ * @param config.timeout Override the default `fetch` request timeout of 30 seconds (30000ms).
241
225
  */
242
226
  config: function (config) {
243
227
  core.setConfig(config);
@@ -1,2 +1,2 @@
1
1
  export declare const PACKAGE_NAME = "api";
2
- export declare const PACKAGE_VERSION = "5.0.0-beta.3";
2
+ export declare const PACKAGE_VERSION = "5.0.0";
@@ -3,4 +3,4 @@ exports.__esModule = true;
3
3
  exports.PACKAGE_VERSION = exports.PACKAGE_NAME = void 0;
4
4
  // This file is automatically updated by the build script.
5
5
  exports.PACKAGE_NAME = 'api';
6
- exports.PACKAGE_VERSION = '5.0.0-beta.3';
6
+ exports.PACKAGE_VERSION = '5.0.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "api",
3
- "version": "5.0.0-beta.3",
3
+ "version": "5.0.0",
4
4
  "description": "Magical SDK generation from an OpenAPI definition 🪄",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -10,10 +10,11 @@
10
10
  "scripts": {
11
11
  "build": "tsc",
12
12
  "debug:bin": "node -r ts-node/register src/bin.ts",
13
- "prebuild": "rm -rf dist/; npm run prebuild.packageConfig",
14
- "prebuild.packageConfig": "node -p \"'// This file is automatically updated by the build script.\\nexport const PACKAGE_NAME = \\'' + require('./package.json').name + '\\';\\nexport const PACKAGE_VERSION = \\'' + require('./package.json').version + '\\';'\" > src/packageInfo.ts; git add src/packageInfo.ts",
13
+ "prebuild": "rm -rf dist/; npm run version",
15
14
  "prepack": "npm run build",
16
- "test": "nyc mocha \"test/**/*.test.ts\""
15
+ "test": "nyc mocha $(find test -name '*.test.ts' -not -path '*/smoketest.test.ts')",
16
+ "test:smoke": "npx mocha test/cli/codegen/languages/typescript/smoketest.test.ts",
17
+ "version": "node -p \"'// This file is automatically updated by the build script.\\nexport const PACKAGE_NAME = \\'' + require('./package.json').name + '\\';\\nexport const PACKAGE_VERSION = \\'' + require('./package.json').version + '\\';'\" > src/packageInfo.ts; git add src/packageInfo.ts"
17
18
  },
18
19
  "repository": {
19
20
  "type": "git",
@@ -36,14 +37,14 @@
36
37
  "swagger"
37
38
  ],
38
39
  "dependencies": {
39
- "@readme/oas-to-har": "^17.0.8",
40
+ "@readme/oas-to-har": "^20.0.0",
40
41
  "@readme/openapi-parser": "^2.2.0",
41
42
  "caseless": "^0.12.0",
42
43
  "chalk": "^4.1.2",
43
- "commander": "^9.2.0",
44
+ "commander": "^9.4.0",
44
45
  "datauri": "^4.1.0",
45
46
  "execa": "^5.1.1",
46
- "fetch-har": "^8.0.3",
47
+ "fetch-har": "^8.1.3",
47
48
  "figures": "^3.2.0",
48
49
  "find-cache-dir": "^3.3.1",
49
50
  "form-data-encoder": "^1.7.2",
@@ -51,28 +52,37 @@
51
52
  "get-stream": "^6.0.1",
52
53
  "isomorphic-fetch": "^3.0.0",
53
54
  "js-yaml": "^4.1.0",
54
- "json-schema-to-typescript": "^10.1.5",
55
+ "json-schema-to-ts": "^2.5.5",
55
56
  "json-schema-traverse": "^1.0.0",
57
+ "lodash.camelcase": "^4.3.0",
58
+ "lodash.deburr": "^4.1.0",
56
59
  "lodash.merge": "^4.6.2",
60
+ "lodash.setwith": "^4.3.2",
61
+ "lodash.startcase": "^4.4.0",
57
62
  "make-dir": "^3.1.0",
58
- "oas": "^18.3.4",
59
- "object-hash": "^3.0.0",
63
+ "node-abort-controller": "^3.0.1",
64
+ "oas": "^20.0.0",
60
65
  "ora": "^5.4.1",
66
+ "prettier": "^2.7.1",
61
67
  "prompts": "^2.4.2",
62
68
  "remove-undefined-objects": "^2.0.1",
63
- "ssri": "^9.0.0",
64
- "ts-morph": "^15.1.0",
65
- "validate-npm-package-name": "^4.0.0"
69
+ "ssri": "^10.0.0",
70
+ "ts-morph": "^16.0.0",
71
+ "validate-npm-package-name": "^5.0.0"
66
72
  },
67
73
  "devDependencies": {
68
- "@readme/oas-examples": "^5.4.1",
74
+ "@readme/oas-examples": "^5.7.1",
69
75
  "@types/caseless": "^0.12.2",
70
76
  "@types/chai": "^4.3.1",
71
77
  "@types/find-cache-dir": "^3.2.1",
72
78
  "@types/js-yaml": "^4.0.5",
79
+ "@types/lodash.camelcase": "^4.3.7",
80
+ "@types/lodash.deburr": "^4.1.7",
73
81
  "@types/lodash.merge": "^4.6.7",
74
- "@types/mocha": "^9.1.1",
75
- "@types/object-hash": "^2.2.1",
82
+ "@types/lodash.setwith": "^4.3.7",
83
+ "@types/lodash.startcase": "^4.4.7",
84
+ "@types/mocha": "^10.0.0",
85
+ "@types/prettier": "^2.7.1",
76
86
  "@types/prompts": "^2.0.14",
77
87
  "@types/sinon-chai": "^3.2.8",
78
88
  "@types/ssri": "^7.1.1",
@@ -82,6 +92,7 @@
82
92
  "mocha": "^10.0.0",
83
93
  "mock-require": "^3.0.3",
84
94
  "nyc": "^15.1.0",
95
+ "oas-normalize": "^7.1.0",
85
96
  "sinon": "^14.0.0",
86
97
  "sinon-chai": "^3.7.0",
87
98
  "typescript": "^4.7.4",
@@ -94,5 +105,5 @@
94
105
  "test/"
95
106
  ]
96
107
  },
97
- "gitHead": "24d5b83545735176786d212a69121a029cf6dea1"
108
+ "gitHead": "bb044ecdb3296c24838056764553fa7f18e6d228"
98
109
  }
package/src/bin.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { Command } from 'commander';
2
- import * as pkg from './packageInfo';
2
+
3
3
  import commands from './cli/commands';
4
+ import * as pkg from './packageInfo';
4
5
 
5
6
  (async () => {
6
7
  const program = new Command();
package/src/cache.ts CHANGED
@@ -1,17 +1,16 @@
1
1
  import type { OASDocument } from 'oas/dist/rmoas.types';
2
2
 
3
- import 'isomorphic-fetch';
4
- import OpenAPIParser from '@readme/openapi-parser';
5
3
  import crypto from 'crypto';
6
- import findCacheDir from 'find-cache-dir';
7
4
  import fs from 'fs';
8
5
  import os from 'os';
9
6
  import path from 'path';
10
- import makeDir from 'make-dir';
11
7
 
12
- import { PACKAGE_NAME } from './packageInfo';
8
+ import findCacheDir from 'find-cache-dir';
9
+ import 'isomorphic-fetch';
10
+ import makeDir from 'make-dir';
13
11
 
14
12
  import Fetcher from './fetcher';
13
+ import { PACKAGE_NAME } from './packageInfo';
15
14
 
16
15
  type CacheStore = Record<
17
16
  string,
@@ -103,28 +102,6 @@ export default class Cache {
103
102
  }
104
103
  }
105
104
 
106
- static validate(json: any) {
107
- if (json.swagger) {
108
- throw new Error('Sorry, this module only supports OpenAPI definitions.');
109
- }
110
-
111
- // The `validate` method handles dereferencing for us.
112
- return OpenAPIParser.validate(json, {
113
- dereference: {
114
- // If circular `$refs` are ignored they'll remain in the API definition as `$ref: String`.
115
- // This allows us to not only do easy circular reference detection but also stringify and
116
- // save dereferenced API definitions back into the cache directory.
117
- circular: 'ignore',
118
- },
119
- }).catch(err => {
120
- if (/is not a valid openapi definition/i.test(err.message)) {
121
- throw new Error("Sorry, that doesn't look like a valid OpenAPI definition.");
122
- }
123
-
124
- throw err;
125
- });
126
- }
127
-
128
105
  isCached() {
129
106
  const cache = this.getCache();
130
107
  return cache && this.uriHash in cache;
@@ -171,10 +148,11 @@ export default class Cache {
171
148
  }
172
149
 
173
150
  async load() {
174
- // If the class was supplied a raw object, just go ahead and bypass the caching system and
175
- // return that.
151
+ // If the class was supplied a raw object we should still validate and make sure that it's
152
+ // dereferenced in order for everything to function, but we shouldn't worry about saving it
153
+ // into the cache directory architecture.
176
154
  if (typeof this.uri === 'object') {
177
- return this.uri;
155
+ return Fetcher.validate(this.uri);
178
156
  }
179
157
 
180
158
  return this.fetcher.load().then(async spec => this.save(spec));
@@ -1,5 +1,5 @@
1
- import type Oas from 'oas';
2
1
  import type CodeGeneratorLanguage from './language';
2
+ import type Oas from 'oas';
3
3
 
4
4
  import TSGenerator from './languages/typescript';
5
5
 
@@ -1,5 +1,6 @@
1
- import type Oas from 'oas';
2
1
  import type Storage from '../storage';
2
+ import type Oas from 'oas';
3
+
3
4
  import { PACKAGE_NAME, PACKAGE_VERSION } from '../../packageInfo';
4
5
 
5
6
  export interface InstallerOptions {
@@ -35,6 +36,22 @@ export default abstract class CodeGeneratorLanguage {
35
36
  // a `petstore` spec installed on api@4.2.0.
36
37
  const info = spec.getDefinition().info;
37
38
  this.userAgent = `${identifier}/${info.version} (${PACKAGE_NAME}/${PACKAGE_VERSION})`;
39
+
40
+ /**
41
+ * This check is barbaric but there are a number of issues with how the `transformer` work we
42
+ * have in `oas` and in `.getParametersAsJSONSchema()` and `.getResponseAsJSONSchema()` that
43
+ * are fully crashing when attempting to codegen an SDK for an API definition that has a
44
+ * circular reference.
45
+ *
46
+ * In order to get v5 out the door we're not going to support this case initialy.
47
+ *
48
+ * @see {@link https://github.com/readmeio/api/issues/549}
49
+ */
50
+ if (JSON.stringify(spec.api).includes('"$ref":"#/')) {
51
+ throw new Error(
52
+ 'Sorry, this library does not yet support generating an SDK for an OpenAPI definition that contains circular references.'
53
+ );
54
+ }
38
55
  }
39
56
 
40
57
  abstract generator(): Promise<Record<string, string>>;
@@ -0,0 +1,183 @@
1
+ import camelCase from 'lodash.camelcase';
2
+ import deburr from 'lodash.deburr';
3
+ import startCase from 'lodash.startcase';
4
+ import { format as prettier } from 'prettier';
5
+
6
+ /**
7
+ * This is a mix of reserved JS words and keywords in TypeScript that might be reserved or
8
+ * allowable but functionally confusing (like `let any = 'buster';`)
9
+ *
10
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar}
11
+ */
12
+ const RESERVED_WORDS = [
13
+ 'abstract',
14
+ 'any',
15
+ 'arguments',
16
+ 'as',
17
+ 'async',
18
+ 'await',
19
+ 'boolean',
20
+ 'break',
21
+ 'byte',
22
+ 'case',
23
+ 'catch',
24
+ 'char',
25
+ 'class',
26
+ 'const',
27
+ 'continue',
28
+ 'constructor',
29
+ 'debugger',
30
+ 'default',
31
+ 'delete',
32
+ 'do',
33
+ 'double',
34
+ 'else',
35
+ 'enum',
36
+ 'eval',
37
+ 'export',
38
+ 'extends',
39
+ 'false',
40
+ 'final',
41
+ 'finally',
42
+ 'float',
43
+ 'for',
44
+ 'from',
45
+ 'function',
46
+ 'get',
47
+ 'goto',
48
+ 'if',
49
+ 'implements',
50
+ 'import',
51
+ 'interface',
52
+ 'in',
53
+ 'instanceof',
54
+ 'int',
55
+ 'let',
56
+ 'long',
57
+ 'native',
58
+ 'new',
59
+ 'null',
60
+ 'number',
61
+ 'of',
62
+ 'package',
63
+ 'private',
64
+ 'protected',
65
+ 'public',
66
+ 'module',
67
+ 'namespace',
68
+ 'return',
69
+ 'set',
70
+ 'short',
71
+ 'static',
72
+ 'string',
73
+ 'super',
74
+ 'switch',
75
+ 'synchronized',
76
+ 'this',
77
+ 'throw',
78
+ 'throws',
79
+ 'transient',
80
+ 'true',
81
+ 'try',
82
+ 'type',
83
+ 'typeof',
84
+ 'var',
85
+ 'void',
86
+ 'while',
87
+ 'with',
88
+ 'volatile',
89
+ 'yield',
90
+
91
+ // These aren't reserved keywords but because we maybe codegen'ing an SDK to be used in the
92
+ // browser it'd be very bad if we overwrote these. This obviously doesn't account for browser APIs
93
+ // you can access outside of the `Window` API (like `alert()`), but we can add checks for those
94
+ // later if we need to.
95
+ 'frames',
96
+ 'global',
97
+ 'globalThis',
98
+ 'navigator',
99
+ 'self',
100
+ 'window',
101
+ ];
102
+
103
+ export function formatter(content: string) {
104
+ return prettier(content, {
105
+ parser: 'typescript',
106
+ printWidth: 100,
107
+ singleQuote: true,
108
+ });
109
+ }
110
+
111
+ /**
112
+ * @see {@link https://www.30secondsofcode.org/js/s/word-wrap}
113
+ */
114
+ export function wordWrap(str: string, max = 88) {
115
+ return str.replace(new RegExp(`(?![^\\n]{1,${max}}$)([^\\n]{1,${max}})\\s`, 'g'), '$1\n');
116
+ }
117
+
118
+ /**
119
+ * Safely escape some string characters that may break a docblock.
120
+ *
121
+ */
122
+ export function docblockEscape(str: string) {
123
+ return str.replace(/\/\*/g, '/\\*').replace(/\*\//g, '*\\/');
124
+ }
125
+
126
+ /**
127
+ * Convert a string that might contain spaces or special characters to one that can safely be used
128
+ * as a TypeScript interface or enum name.
129
+ *
130
+ * This function has been adapted and slighty modified from `json-schema-to-typescript`.
131
+ *
132
+ * @license MIT
133
+ * @see {@link https://github.com/bcherny/json-schema-to-typescript}
134
+ */
135
+ export function toSafeString(str: string) {
136
+ // identifiers in javaScript/ts:
137
+ // First character: a-zA-Z | _ | $
138
+ // Rest: a-zA-Z | _ | $ | 0-9
139
+
140
+ // remove accents, umlauts, ... by their basic latin letters
141
+ return (
142
+ deburr(str)
143
+ // if the string starts with a number, prefix it with character that typescript can accept
144
+ // https://github.com/bcherny/json-schema-to-typescript/issues/489
145
+ .replace(/^(\d){1}/, '$$1')
146
+ // replace chars which are not valid for typescript identifiers with whitespace
147
+ .replace(/(^\s*[^a-zA-Z_$])|([^a-zA-Z_$\d])/g, ' ')
148
+ // uppercase leading underscores followed by lowercase
149
+ .replace(/^_[a-z]/g, (match: string) => match.toUpperCase())
150
+ // remove non-leading underscores followed by lowercase (convert snake_case)
151
+ .replace(/_[a-z]/g, (match: string) => match.substr(1, match.length).toUpperCase())
152
+ // uppercase letters after digits, dollars
153
+ .replace(/([\d$]+[a-zA-Z])/g, (match: string) => match.toUpperCase())
154
+ // uppercase first letter after whitespace
155
+ .replace(/\s+([a-zA-Z])/g, (match: string) => match.toUpperCase().trim())
156
+ // remove remaining whitespace
157
+ .replace(/\s/g, '')
158
+ );
159
+ }
160
+
161
+ export function generateTypeName(...parts: string[]) {
162
+ let str;
163
+
164
+ // If the end of our string ends with something like `2XX`, the combination of `startCase` and
165
+ // `camelCase` will transform it into `2Xx`.
166
+ if (parts.length > 1) {
167
+ const last = parts[parts.length - 1];
168
+ if (last.match(/^(\d)XX$/)) {
169
+ str = startCase(camelCase(parts.slice(0, -1).join(' ')));
170
+ str += ` ${last}`;
171
+ }
172
+ }
173
+
174
+ if (!str) {
175
+ str = startCase(camelCase(parts.join(' ')));
176
+ }
177
+
178
+ if (RESERVED_WORDS.includes(str.toLowerCase())) {
179
+ str = `$${str}`;
180
+ }
181
+
182
+ return toSafeString(str);
183
+ }