ts-generator.js 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686
  1. var fs = require('fs');
  2. var path = require('path');
  3. var pruneShapes = require('./prune-shapes').pruneShapes;
  4. var CUSTOM_CONFIG_ENUMS = {
  5. DUALSTACK: {
  6. FILE_NAME: 'config_use_dualstack',
  7. INTERFACE: 'UseDualstackConfigOptions'
  8. }
  9. };
  10. function TSGenerator(options) {
  11. this._sdkRootDir = options.SdkRootDirectory || process.cwd();
  12. this._apiRootDir = path.join(this._sdkRootDir, 'apis');
  13. this._metadataPath = path.join(this._apiRootDir, 'metadata.json');
  14. this._clientsDir = path.join(this._sdkRootDir, 'clients');
  15. // Lazy loading values on usage to avoid side-effects in constructor
  16. this.metadata = null;
  17. this.typings = {};
  18. this.streamTypes = {};
  19. }
  20. /**
  21. * Loads the AWS SDK metadata.json file.
  22. */
  23. TSGenerator.prototype.loadMetadata = function loadMetadata() {
  24. var metadataFile = fs.readFileSync(this._metadataPath);
  25. this.metadata = JSON.parse(metadataFile);
  26. return this.metadata;
  27. };
  28. /**
  29. * Modifies metadata to include api model filenames.
  30. */
  31. TSGenerator.prototype.fillApiModelFileNames = function fillApiModelFileNames() {
  32. var modelPaths = fs.readdirSync(this._apiRootDir);
  33. if (!this.metadata) {
  34. this.loadMetadata();
  35. }
  36. var metadata = this.metadata;
  37. // sort paths so latest versions appear first
  38. modelPaths = modelPaths.sort(function sort(a, b) {
  39. if (a < b) {
  40. return 1;
  41. } else if (a > b) {
  42. return -1;
  43. } else {
  44. return 0;
  45. }
  46. });
  47. // Only get latest version of models
  48. var foundModels = Object.create(null);
  49. modelPaths.forEach(function(modelFileName) {
  50. var match = modelFileName.match(/^(.+)(-[\d]{4}-[\d]{2}-[\d]{2})\.normal\.json$/i);
  51. if (match) {
  52. var model = match[1];
  53. // add version
  54. var version = match[2].substring(1);
  55. if (!foundModels[model]) {
  56. foundModels[model] = {
  57. latestFileName: modelFileName,
  58. versions: [version]
  59. };
  60. } else {
  61. foundModels[model].versions.push(version);
  62. }
  63. }
  64. });
  65. // now update the metadata
  66. var keys = Object.keys(metadata);
  67. keys.forEach(function(key) {
  68. var modelName = metadata[key].prefix || key;
  69. var modelInfo = foundModels[modelName];
  70. metadata[key].api_path = modelInfo.latestFileName;
  71. // find waiters file
  72. var baseName = modelInfo.latestFileName.split('.')[0];
  73. if (modelPaths.indexOf(baseName + '.waiters2.json') >= 0) {
  74. metadata[key].waiters_path = baseName + '.waiters2.json';
  75. }
  76. // add versions
  77. if (!metadata[key].versions) {
  78. metadata[key].versions = [];
  79. }
  80. metadata[key].versions = [].concat(metadata[key].versions, modelInfo.versions);
  81. });
  82. };
  83. TSGenerator.prototype.updateDynamoDBDocumentClient = function updateDynamoDBDocumentClient() {
  84. // read in document client customization
  85. var docClientCustomCode = fs.readFileSync(path.join(this._sdkRootDir, 'lib', 'dynamodb', 'document_client.d.ts')).toString();
  86. var lines = docClientCustomCode.split('\n');
  87. var namespaceIndexStart = -1;
  88. var namespaceIndexEnd = -1;
  89. for (var i = 0, iLen = lines.length; i < iLen; i++) {
  90. var line = lines[i];
  91. // find exported namespace
  92. if (line.indexOf('//<!--auto-generated start-->') >= 0) {
  93. namespaceIndexStart = i;
  94. }
  95. if (line.indexOf('//<!--auto-generated end-->') >= 0) {
  96. namespaceIndexEnd = i;
  97. break;
  98. }
  99. }
  100. if (namespaceIndexStart >= 0 && namespaceIndexEnd >= 0) {
  101. // insert doc client interfaces
  102. lines.splice(namespaceIndexStart + 1, (namespaceIndexEnd - namespaceIndexStart - 1), this.generateDocumentClientInterfaces(1));
  103. var code = lines.join('\n');
  104. this.writeTypingsFile('document_client', path.join(this._sdkRootDir, 'lib', 'dynamodb'), code);
  105. }
  106. };
  107. /**
  108. * Generates the file containing DocumentClient interfaces.
  109. */
  110. TSGenerator.prototype.generateDocumentClientInterfaces = function generateDocumentClientInterfaces(tabCount) {
  111. tabCount = tabCount || 0;
  112. var self = this;
  113. // get the dynamodb model
  114. var dynamodbModel = this.loadServiceApi('dynamodb');
  115. var code = '';
  116. // stub Blob interface
  117. code += this.tabs(tabCount) + 'interface Blob {}\n';
  118. // generate shapes
  119. var modelShapes = dynamodbModel.shapes;
  120. // iterate over each shape
  121. var shapeKeys = Object.keys(modelShapes);
  122. shapeKeys.forEach(function (shapeKey) {
  123. var modelShape = modelShapes[shapeKey];
  124. // ignore exceptions
  125. if (modelShape.exception) {
  126. return;
  127. }
  128. // overwrite AttributeValue
  129. if (shapeKey === 'AttributeValue') {
  130. code += self.generateDocString('A JavaScript object or native type.', tabCount);
  131. code += self.tabs(tabCount) + 'export type ' + shapeKey + ' = any;\n';
  132. return;
  133. }
  134. code += self.generateTypingsFromShape(dynamodbModel, shapeKey, modelShape, tabCount, []);
  135. });
  136. return code;
  137. };
  138. /**
  139. * Returns a service model based on the serviceIdentifier.
  140. */
  141. TSGenerator.prototype.loadServiceApi = function loadServiceApi(serviceIdentifier) {
  142. // first, find the correct identifier
  143. var metadata = this.metadata;
  144. var serviceFilePath = path.join(this._apiRootDir, metadata[serviceIdentifier].api_path);
  145. var serviceModelFile = fs.readFileSync(serviceFilePath);
  146. var serviceModel = JSON.parse(serviceModelFile);
  147. // load waiters file if it exists
  148. var waiterFilePath;
  149. if (metadata[serviceIdentifier].waiters_path) {
  150. waiterFilePath = path.join(this._apiRootDir, metadata[serviceIdentifier].waiters_path);
  151. var waiterModelFile = fs.readFileSync(waiterFilePath);
  152. var waiterModel = JSON.parse(waiterModelFile);
  153. serviceModel.waiters = waiterModel.waiters;
  154. }
  155. return serviceModel;
  156. };
  157. /**
  158. * Determines if a member is required by checking for it in a list.
  159. */
  160. TSGenerator.prototype.checkRequired = function checkRequired(list, member) {
  161. if (list.indexOf(member) >= 0) {
  162. return true;
  163. }
  164. return false;
  165. };
  166. /**
  167. * Generates whitespace based on the count.
  168. */
  169. TSGenerator.prototype.tabs = function tabs(count) {
  170. var code = '';
  171. for (var i = 0; i < count; i++) {
  172. code += ' ';
  173. }
  174. return code;
  175. };
  176. /**
  177. * Transforms documentation string to a more readable format.
  178. */
  179. TSGenerator.prototype.transformDocumentation = function transformDocumentation(documentation) {
  180. if (!documentation) {
  181. return '';
  182. }
  183. documentation = documentation.replace(/<(?:.|\n)*?>/gm, '');
  184. documentation = documentation.replace(/\*\//g, '*');
  185. return documentation;
  186. };
  187. /**
  188. * Returns a doc string based on the supplied documentation.
  189. * Also tabs the doc string if a count is provided.
  190. */
  191. TSGenerator.prototype.generateDocString = function generateDocString(documentation, tabCount) {
  192. tabCount = tabCount || 0;
  193. var code = '';
  194. code += this.tabs(tabCount) + '/**\n';
  195. code += this.tabs(tabCount) + ' * ' + this.transformDocumentation(documentation) + '\n';
  196. code += this.tabs(tabCount) + ' */\n';
  197. return code;
  198. };
  199. /**
  200. * Returns an array of custom configuration options based on a service identiffier.
  201. * Custom configuration options are determined by checking the metadata.json file.
  202. */
  203. TSGenerator.prototype.generateCustomConfigFromMetadata = function generateCustomConfigFromMetadata(serviceIdentifier) {
  204. // some services have additional configuration options that are defined in the metadata.json file
  205. // i.e. dualstackAvailable = useDualstack
  206. // create reference to custom options
  207. var customConfigurations = [];
  208. var serviceMetadata = this.metadata[serviceIdentifier];
  209. // loop through metadata members
  210. for (var memberName in serviceMetadata) {
  211. if (!serviceMetadata.hasOwnProperty(memberName)) {
  212. continue;
  213. }
  214. // check configs
  215. switch (memberName) {
  216. case 'dualstackAvailable':
  217. customConfigurations.push(CUSTOM_CONFIG_ENUMS.DUALSTACK);
  218. break;
  219. }
  220. }
  221. return customConfigurations;
  222. };
  223. TSGenerator.prototype.generateSafeShapeName = function generateSafeShapeName(name, blacklist) {
  224. blacklist = blacklist || [];
  225. if (blacklist.indexOf(name) >= 0) {
  226. return '_' + name;
  227. }
  228. return name;
  229. };
  230. TSGenerator.prototype.extractTypesDependOnStream = function extractTypesDependOnStream(shapeKey, modelShape) {
  231. var streamTypeList = [];
  232. if (typeof modelShape !== "object" || Object.keys(modelShape).length === 0) {
  233. return [];
  234. }
  235. if (modelShape.streaming) {
  236. streamTypeList.push(shapeKey);
  237. return streamTypeList;
  238. }
  239. for (var subModelKey in modelShape) {
  240. var subModel = modelShape[subModelKey];
  241. var subStreamTypeList = this.extractTypesDependOnStream(subModelKey, subModel);
  242. if (Object.keys(subStreamTypeList).length !== 0) {
  243. for (var streamType of subStreamTypeList) {
  244. streamTypeList.push(streamType);
  245. }
  246. }
  247. }
  248. return streamTypeList;
  249. }
  250. TSGenerator.prototype.addReadableType = function addReadableType(shapeKey) {
  251. var code = '';
  252. if (this.streamTypes[shapeKey]) {
  253. code += '|Readable';
  254. } else if (shapeKey[0] === '_' && this.streamTypes[shapeKey.slice(1)]) {
  255. code += '|Readable';
  256. }
  257. return code;
  258. }
  259. /**
  260. * Generates a type or interface based on the shape.
  261. */
  262. TSGenerator.prototype.generateTypingsFromShape = function generateTypingsFromShape(model, shapeKey, shape, tabCount, customClassNames) {
  263. // some shapes shouldn't be generated if they are javascript primitives
  264. var jsPrimitives = ['string', 'boolean', 'number'];
  265. if (jsPrimitives.indexOf(shapeKey) >= 0) {
  266. return '';
  267. }
  268. if (['Date', 'Blob'].indexOf(shapeKey) >= 0) {
  269. shapeKey = '_' + shapeKey;
  270. }
  271. // In at least one (cloudfront.Signer) case, a class on a service namespace clashes with a shape
  272. shapeKey = this.generateSafeShapeName(shapeKey, customClassNames);
  273. var self = this;
  274. var code = '';
  275. tabCount = tabCount || 0;
  276. var tabs = this.tabs;
  277. var type = shape.type;
  278. if (shape.eventstream) {
  279. // eventstreams MUST be structures
  280. var members = Object.keys(shape.members);
  281. var events = members.map(function(member) {
  282. // each member is an individual event type, so each must be optional
  283. return member + '?:' + shape.members[member].shape;
  284. });
  285. return code += tabs(tabCount) + 'export type ' + shapeKey + ' = EventStream<{' + events.join(',') + '}>;\n';
  286. }
  287. if (type === 'structure') {
  288. if (shape.isDocument) {
  289. return code += tabs(tabCount) + 'export type ' + shapeKey + ' = DocumentType;\n'
  290. }
  291. code += tabs(tabCount) + 'export interface ' + shapeKey + ' {\n';
  292. var members = shape.members;
  293. // cycle through members
  294. var memberKeys = Object.keys(members);
  295. memberKeys.forEach(function(memberKey) {
  296. // docs
  297. var member = members[memberKey];
  298. if (member.documentation) {
  299. code += self.generateDocString(member.documentation, tabCount + 1);
  300. }
  301. var required = self.checkRequired(shape.required || [], memberKey) ? '' : '?';
  302. var memberType = member.shape;
  303. if (member.eventpayload) {
  304. // eventpayloads are always either structures, or buffers
  305. if (['blob', 'binary'].indexOf(model.shapes[memberType].type) >= 0) {
  306. memberType = 'Buffer';
  307. }
  308. }
  309. memberType = self.generateSafeShapeName(memberType, [].concat(customClassNames, ['Date', 'Blob']));
  310. code += tabs(tabCount + 1) + memberKey + required + ': ' + memberType + ';\n';
  311. });
  312. code += tabs(tabCount) + '}\n';
  313. } else if (type === 'list') {
  314. code += tabs(tabCount) + 'export type ' + shapeKey + ' = ' + this.generateSafeShapeName(shape.member.shape, customClassNames) + '[];\n';
  315. } else if (type === 'map') {
  316. code += tabs(tabCount) + 'export type ' + shapeKey + ' = {[key: string]: ' + this.generateSafeShapeName(shape.value.shape, customClassNames) + '};\n';
  317. } else if (type === 'string' || type === 'character') {
  318. var stringType = 'string';
  319. if (Array.isArray(shape.enum)) {
  320. stringType = shape.enum.map(function(s) {
  321. return '"' + s + '"';
  322. }).join('|') + '|' + stringType;
  323. }
  324. code += tabs(tabCount) + 'export type ' + shapeKey + ' = ' + stringType + ';\n';
  325. } else if (['double', 'long', 'short', 'biginteger', 'bigdecimal', 'integer', 'float'].indexOf(type) >= 0) {
  326. code += tabs(tabCount) + 'export type ' + shapeKey + ' = number;\n';
  327. } else if (type === 'timestamp') {
  328. code += tabs(tabCount) + 'export type ' + shapeKey + ' = Date;\n';
  329. } else if (type === 'boolean') {
  330. code += tabs(tabCount) + 'export type ' + shapeKey + ' = boolean;\n';
  331. } else if (type === 'blob' || type === 'binary') {
  332. code += tabs(tabCount) + 'export type ' + shapeKey + ' = Buffer|Uint8Array|Blob|string'
  333. + self.addReadableType(shapeKey)
  334. +';\n';
  335. }
  336. return code;
  337. };
  338. /**
  339. * Generates a class method type for an operation.
  340. */
  341. TSGenerator.prototype.generateTypingsFromOperations = function generateTypingsFromOperations(className, operation, operationName, tabCount) {
  342. var code = '';
  343. tabCount = tabCount || 0;
  344. var tabs = this.tabs;
  345. var input = operation.input;
  346. var output = operation.output;
  347. operationName = operationName.charAt(0).toLowerCase() + operationName.substring(1);
  348. var inputShape = input ? className + '.Types.' + input.shape : '{}';
  349. var outputShape = output ? className + '.Types.' + output.shape : '{}';
  350. if (input) {
  351. code += this.generateDocString(operation.documentation, tabCount);
  352. code += tabs(tabCount) + operationName + '(params: ' + inputShape + ', callback?: (err: AWSError, data: ' + outputShape + ') => void): Request<' + outputShape + ', AWSError>;\n';
  353. }
  354. code += this.generateDocString(operation.documentation, tabCount);
  355. code += tabs(tabCount) + operationName + '(callback?: (err: AWSError, data: ' + outputShape + ') => void): Request<' + outputShape + ', AWSError>;\n';
  356. return code;
  357. };
  358. TSGenerator.prototype.generateConfigurationServicePlaceholders = function generateConfigurationServicePlaceholders() {
  359. /**
  360. * Should create a config service placeholder
  361. */
  362. var self = this;
  363. var metadata = this.metadata;
  364. // Iterate over every service
  365. var serviceIdentifiers = Object.keys(metadata);
  366. var code = '';
  367. var configCode = '';
  368. var versionsCode = '';
  369. code += 'import * as AWS from \'../clients/all\';\n';
  370. configCode += 'export abstract class ConfigurationServicePlaceholders {\n';
  371. versionsCode += 'export interface ConfigurationServiceApiVersions {\n';
  372. serviceIdentifiers.forEach(function(serviceIdentifier) {
  373. var className = self.metadata[serviceIdentifier].name;
  374. configCode += self.tabs(1) + serviceIdentifier + '?: AWS.' + className + '.Types.ClientConfiguration;\n';
  375. versionsCode += self.tabs(1) + serviceIdentifier + '?: AWS.' + className + '.Types.apiVersion;\n';
  376. });
  377. configCode += '}\n';
  378. versionsCode += '}\n';
  379. code += configCode + versionsCode;
  380. this.writeTypingsFile('config_service_placeholders', path.join(this._sdkRootDir, 'lib'), code);
  381. };
  382. TSGenerator.prototype.getServiceApiVersions = function generateServiceApiVersions(serviceIdentifier) {
  383. var metadata = this.metadata;
  384. var versions = metadata[serviceIdentifier].versions || [];
  385. // transform results (to get rid of '*' and sort
  386. versions = versions.map(function(version) {
  387. return version.replace('*', '');
  388. }).sort();
  389. return versions;
  390. };
  391. /**
  392. * Generates class method types for a waiter.
  393. */
  394. TSGenerator.prototype.generateTypingsFromWaiters = function generateTypingsFromWaiters(className, waiterState, waiter, underlyingOperation, tabCount) {
  395. var code = '';
  396. tabCount = tabCount || 0;
  397. var operationName = waiter.operation.charAt(0).toLowerCase() + waiter.operation.substring(1);
  398. waiterState = waiterState.charAt(0).toLowerCase() + waiterState.substring(1);
  399. var docString = 'Waits for the ' + waiterState + ' state by periodically calling the underlying ' + className + '.' + operationName + 'operation every ' + waiter.delay + ' seconds (at most ' + waiter.maxAttempts + ' times).';
  400. if (waiter.description) {
  401. docString += ' ' + waiter.description;
  402. }
  403. // get input and output
  404. var inputShape = '{}';
  405. var outputShape = '{}';
  406. if (underlyingOperation.input) {
  407. inputShape = className + '.Types.' + underlyingOperation.input.shape;
  408. }
  409. if (underlyingOperation.output) {
  410. outputShape = className + '.Types.' + underlyingOperation.output.shape;
  411. }
  412. code += this.generateDocString(docString, tabCount);
  413. code += this.tabs(tabCount) + 'waitFor(state: "' + waiterState + '", params: ' + inputShape + ' & {$waiter?: WaiterConfiguration}, callback?: (err: AWSError, data: ' + outputShape + ') => void): Request<' + outputShape + ', AWSError>;\n';
  414. code += this.generateDocString(docString, tabCount);
  415. code += this.tabs(tabCount) + 'waitFor(state: "' + waiterState + '", callback?: (err: AWSError, data: ' + outputShape + ') => void): Request<' + outputShape + ', AWSError>;\n';
  416. return code;
  417. };
  418. /**
  419. * Returns whether a service has customizations to include.
  420. */
  421. TSGenerator.prototype.includeCustomService = function includeCustomService(serviceIdentifier) {
  422. // check services directory
  423. var servicesDir = path.join(this._sdkRootDir, 'lib', 'services');
  424. var fileNames = fs.readdirSync(servicesDir);
  425. fileNames = fileNames.filter(function(fileName) {
  426. return fileName === serviceIdentifier + '.d.ts';
  427. });
  428. return !!fileNames.length;
  429. };
  430. /**
  431. * Generates typings for classes that live on a service client namespace.
  432. */
  433. TSGenerator.prototype.generateCustomNamespaceTypes = function generateCustomNamespaceTypes(serviceIdentifier, className) {
  434. var self = this;
  435. var tsCustomizationsJson = require('./ts-customizations');
  436. var customClasses = [];
  437. var code = '';
  438. var serviceInfo = tsCustomizationsJson[serviceIdentifier] || null;
  439. // exit early if no customizations found
  440. if (!serviceInfo) {
  441. return null;
  442. }
  443. code += 'declare namespace ' + className + ' {\n';
  444. //generate import code
  445. var importCode = '';
  446. serviceInfo.forEach(function(data) {
  447. var aliases = [];
  448. var imports = data.imports || [];
  449. imports.forEach(function(pair) {
  450. aliases.push(pair.name + ' as ' + pair.alias);
  451. code += self.tabs(1) + 'export import ' + pair.name + ' = ' + pair.alias + ';\n';
  452. customClasses.push(pair.name);
  453. });
  454. if (aliases.length) {
  455. importCode += 'import {' + aliases.join(', ') + '} from \'../' + data.path + '\';\n';
  456. }
  457. });
  458. code += '}\n';
  459. return {
  460. importCode: importCode,
  461. namespaceCode: code,
  462. customClassNames: customClasses
  463. };
  464. };
  465. TSGenerator.prototype.containsEventStreams = function containsEventStreams(model) {
  466. var shapeNames = Object.keys(model.shapes);
  467. for (var name of shapeNames) {
  468. if (model.shapes[name].eventstream) {
  469. return true;
  470. }
  471. }
  472. return false;
  473. };
  474. TSGenerator.prototype.containsDocumentType = function containsDocumentType(model) {
  475. var shapeNames = Object.keys(model.shapes);
  476. for (var name of shapeNames) {
  477. if (model.shapes[name].isDocument) {
  478. return true;
  479. }
  480. }
  481. return false;
  482. };
  483. /**
  484. * Generates the typings for a service based on the serviceIdentifier.
  485. */
  486. TSGenerator.prototype.processServiceModel = function processServiceModel(serviceIdentifier) {
  487. var model = this.loadServiceApi(serviceIdentifier);
  488. pruneShapes(model);
  489. var self = this;
  490. var code = '';
  491. var className = this.metadata[serviceIdentifier].name;
  492. var customNamespaces = this.generateCustomNamespaceTypes(serviceIdentifier, className);
  493. var customClassNames = customNamespaces ? customNamespaces.customClassNames : [];
  494. var waiters = model.waiters || Object.create(null);
  495. var waiterKeys = Object.keys(waiters);
  496. // generate imports
  497. code += 'import {Request} from \'../lib/request\';\n';
  498. code += 'import {Response} from \'../lib/response\';\n';
  499. code += 'import {AWSError} from \'../lib/error\';\n';
  500. var hasCustomizations = this.includeCustomService(serviceIdentifier);
  501. var parentClass = hasCustomizations ? className + 'Customizations' : 'Service';
  502. if (hasCustomizations) {
  503. code += 'import {' + parentClass + '} from \'../lib/services/' + serviceIdentifier + '\';\n';
  504. } else {
  505. code += 'import {' + parentClass + '} from \'../lib/service\';\n';
  506. }
  507. if (waiterKeys.length) {
  508. code += 'import {WaiterConfiguration} from \'../lib/service\';\n';
  509. }
  510. code += 'import {ServiceConfigurationOptions} from \'../lib/service\';\n';
  511. // get any custom config options
  512. var customConfig = this.generateCustomConfigFromMetadata(serviceIdentifier);
  513. var hasCustomConfig = !!customConfig.length;
  514. var customConfigTypes = ['ServiceConfigurationOptions'];
  515. code += 'import {ConfigBase as Config} from \'../lib/config-base\';\n';
  516. if (hasCustomConfig) {
  517. // generate import statements and custom config type
  518. customConfig.forEach(function(config) {
  519. code += 'import {' + config.INTERFACE + '} from \'../lib/' + config.FILE_NAME + '\';\n';
  520. customConfigTypes.push(config.INTERFACE);
  521. });
  522. }
  523. if (this.containsEventStreams(model)) {
  524. code += 'import {EventStream} from \'../lib/event-stream/event-stream\';\n';
  525. }
  526. if (this.containsDocumentType(model)) {
  527. code += 'import {DocumentType} from \'../lib/model\';\n';
  528. }
  529. // import custom namespaces
  530. if (customNamespaces) {
  531. code += customNamespaces.importCode;
  532. }
  533. code += 'interface Blob {}\n';
  534. // generate methods
  535. var modelOperations = model.operations;
  536. var operationKeys = Object.keys(modelOperations);
  537. code += 'declare class ' + className + ' extends ' + parentClass + ' {\n';
  538. // create constructor
  539. code += this.generateDocString('Constructs a service object. This object has one method for each API operation.', 1);
  540. code += this.tabs(1) + 'constructor(options?: ' + className + '.Types.ClientConfiguration' + ')\n';
  541. code += this.tabs(1) + 'config: Config & ' + className + '.Types.ClientConfiguration' + ';\n';
  542. operationKeys.forEach(function (operationKey) {
  543. code += self.generateTypingsFromOperations(className, modelOperations[operationKey], operationKey, 1);
  544. });
  545. // generate waitFor methods
  546. waiterKeys.forEach(function (waitersKey) {
  547. var waiter = waiters[waitersKey];
  548. var operation = modelOperations[waiter.operation];
  549. code += self.generateTypingsFromWaiters(className, waitersKey, waiter, operation, 1);
  550. });
  551. code += '}\n';
  552. // check for static classes on namespace
  553. if (customNamespaces) {
  554. code += customNamespaces.namespaceCode;
  555. }
  556. // shapes should map to interfaces
  557. var modelShapes = model.shapes;
  558. // iterate over each shape
  559. var shapeKeys = Object.keys(modelShapes);
  560. code += 'declare namespace ' + className + ' {\n';
  561. // preprocess shapes to fetch out needed dependency. e.g. "streaming": true
  562. shapeKeys.forEach(function (shapeKey) {
  563. var modelShape = modelShapes[shapeKey];
  564. var streamTypeList = self.extractTypesDependOnStream(shapeKey, modelShape);
  565. for (var streamType of streamTypeList) {
  566. self.streamTypes[streamType] = true;
  567. }
  568. });
  569. shapeKeys.forEach(function (shapeKey) {
  570. var modelShape = modelShapes[shapeKey];
  571. code += self.generateTypingsFromShape(model, shapeKey, modelShape, 1, customClassNames);
  572. });
  573. //add extra dependencies like 'streaming'
  574. if (Object.keys(self.streamTypes).length !== 0) {
  575. var insertPos = code.indexOf('interface Blob {}');
  576. code = code.slice(0, insertPos) + 'import {Readable} from \'stream\';\n' + code.slice(insertPos);
  577. }
  578. this.streamTypes = {};
  579. code += this.generateDocString('A string in YYYY-MM-DD format that represents the latest possible API version that can be used in this service. Specify \'latest\' to use the latest possible version.', 1);
  580. code += this.tabs(1) + 'export type apiVersion = "' + this.getServiceApiVersions(serviceIdentifier).join('"|"') + '"|"latest"|string;\n';
  581. code += this.tabs(1) + 'export interface ClientApiVersions {\n';
  582. code += this.generateDocString('A string in YYYY-MM-DD format that represents the latest possible API version that can be used in this service. Specify \'latest\' to use the latest possible version.', 2);
  583. code += this.tabs(2) + 'apiVersion?: apiVersion;\n';
  584. code += this.tabs(1) + '}\n';
  585. code += this.tabs(1) + 'export type ClientConfiguration = ' + customConfigTypes.join(' & ') + ' & ClientApiVersions;\n';
  586. // export interfaces under Types namespace for backwards-compatibility
  587. code += this.generateDocString('Contains interfaces for use with the ' + className + ' client.', 1);
  588. code += this.tabs(1) + 'export import Types = ' + className + ';\n';
  589. code += '}\n';
  590. code += 'export = ' + className + ';\n';
  591. return code;
  592. };
  593. /**
  594. * Write Typings file to the specified directory.
  595. */
  596. TSGenerator.prototype.writeTypingsFile = function writeTypingsFile(name, directory, code) {
  597. fs.writeFileSync(path.join(directory, name + '.d.ts'), code);
  598. };
  599. /**
  600. * Create the typescript definition files for every service.
  601. */
  602. TSGenerator.prototype.generateAllClientTypings = function generateAllClientTypings() {
  603. this.fillApiModelFileNames();
  604. var self = this;
  605. var metadata = this.metadata;
  606. // Iterate over every service
  607. var serviceIdentifiers = Object.keys(metadata);
  608. serviceIdentifiers.forEach(function(serviceIdentifier) {
  609. var code = self.processServiceModel(serviceIdentifier);
  610. self.writeTypingsFile(serviceIdentifier, self._clientsDir, code);
  611. });
  612. };
  613. /**
  614. * Create the typescript definition files for the all and browser_default exports.
  615. */
  616. TSGenerator.prototype.generateGroupedClients = function generateGroupedClients() {
  617. var metadata = this.metadata;
  618. var allCode = '';
  619. var browserCode = '';
  620. // Iterate over every service
  621. var serviceIdentifiers = Object.keys(metadata);
  622. serviceIdentifiers.forEach(function(serviceIdentifier) {
  623. var className = metadata[serviceIdentifier].name;
  624. var code = 'export import ' + className + ' = require(\'./' + serviceIdentifier + '\');\n';
  625. allCode += code;
  626. if (metadata[serviceIdentifier].cors) {
  627. browserCode += code;
  628. }
  629. });
  630. this.writeTypingsFile('all', this._clientsDir, allCode);
  631. this.writeTypingsFile('browser_default', this._clientsDir, browserCode);
  632. };
  633. module.exports = TSGenerator;