Source: api/metadata.js

  1. /*global process, Buffer */
  2. /**
  3. * @file Manages Salesforce Metadata API
  4. * @author Shinichi Tomita <shinichi.tomita@gmail.com>
  5. */
  6. 'use strict';
  7. var inherits = require('inherits'),
  8. events = require('events'),
  9. stream = require('readable-stream'),
  10. _ = require('lodash/core'),
  11. jsforce = require('../core'),
  12. Promise = require('../promise'),
  13. SOAP = require('../soap');
  14. /*--------------------------------------------*/
  15. /**
  16. * Class for Salesforce Metadata API
  17. *
  18. * @class
  19. * @param {Connection} conn - Connection object
  20. */
  21. var Metadata = module.exports = function(conn) {
  22. this._conn = conn;
  23. };
  24. /**
  25. * Polling interval in milliseconds
  26. * @type {Number}
  27. */
  28. Metadata.prototype.pollInterval = 1000;
  29. /**
  30. * Polling timeout in milliseconds
  31. * @type {Number}
  32. */
  33. Metadata.prototype.pollTimeout = 10000;
  34. /**
  35. * Call Metadata API SOAP endpoint
  36. *
  37. * @private
  38. */
  39. Metadata.prototype._invoke = function(method, message, callback) {
  40. var soapEndpoint = new SOAP(this._conn, {
  41. xmlns: "http://soap.sforce.com/2006/04/metadata",
  42. endpointUrl: this._conn.instanceUrl + "/services/Soap/m/" + this._conn.version
  43. });
  44. return soapEndpoint.invoke(method, message).then(function(res) {
  45. return res.result;
  46. }).thenCall(callback);
  47. };
  48. /**
  49. * @typedef {Object} Metadata~MetadataInfo
  50. * @prop {String} fullName - The name of the component
  51. */
  52. /**
  53. * Asynchronously adds one or more new metadata components to the organization.
  54. *
  55. * @param {String} type - The type of metadata to create
  56. * @param {Metadata~MetadataInfo|Array.<Metadata~MetadataInfo>} metadata - Metadata to create
  57. * @param {Callback.<Metadata~AsyncResult|Array.<Metadata~AsyncResult>>} [callback] - Callback function
  58. * @returns {Metadata~AsyncResultLocator}
  59. */
  60. Metadata.prototype.createAsync = function(type, metadata, callback) {
  61. if (Number(this._conn.version) > 30) {
  62. throw new Error("Async metadata CRUD calls are not supported on ver 31.0 or later.");
  63. }
  64. var convert = function(md) {
  65. md["@xsi:type"] = type;
  66. return md;
  67. };
  68. var isArray = _.isArray(metadata);
  69. metadata = isArray ? _.map(metadata, convert) : convert(metadata);
  70. var res = this._invoke("create", { metadata: metadata });
  71. return new AsyncResultLocator(this, res, isArray).thenCall(callback);
  72. };
  73. /**
  74. * @typedef {Object} Metadata~SaveResult
  75. * @prop {Boolean} success - True if metadata is successfully saved
  76. * @prop {String} fullName - Full name of metadata object
  77. */
  78. /**
  79. * @private
  80. */
  81. function convertToSaveResult(result) {
  82. var saveResult = _.clone(result);
  83. saveResult.success = saveResult.success === 'true';
  84. return saveResult;
  85. }
  86. /**
  87. * @typedef {Object} Metadata~UpsertResult
  88. * @prop {Boolean} success - True if metadata is successfully saved
  89. * @prop {String} fullName - Full name of metadata object
  90. * @prop {Boolean} created - True if metadata is newly created
  91. */
  92. /**
  93. * @private
  94. */
  95. function convertToUpsertResult(result) {
  96. var upsertResult = convertToSaveResult(result);
  97. upsertResult.created = upsertResult.created === 'true';
  98. return upsertResult;
  99. }
  100. /**
  101. * Synonym of Metadata#create().
  102. *
  103. * @method Metadata#createSync
  104. * @param {String} type - The type of metadata to create
  105. * @param {Metadata~MetadataInfo|Array.<Metadata~MetadataInfo>} metadata - Metadata to create
  106. * @param {Callback.<Metadata~SaveResult|Array.<Metadata~SaveResult>>} [callback] - Callback function
  107. * @returns {Promise.<Metadata~SaveResult|Array.<Metadata~SaveResult>>}
  108. */
  109. /**
  110. * Synchronously adds one or more new metadata components to the organization.
  111. *
  112. * @method Metadata#create
  113. * @param {String} type - The type of metadata to create
  114. * @param {Metadata~MetadataInfo|Array.<Metadata~MetadataInfo>} metadata - Metadata to create
  115. * @param {Callback.<Metadata~SaveResult|Array.<Metadata~SaveResult>>} [callback] - Callback function
  116. * @returns {Promise.<Metadata~SaveResult|Array.<Metadata~SaveResult>>}
  117. */
  118. Metadata.prototype.createSync =
  119. Metadata.prototype.create = function(type, metadata, callback) {
  120. var convert = function(md) {
  121. md["@xsi:type"] = type;
  122. return md;
  123. };
  124. var isArray = _.isArray(metadata);
  125. metadata = isArray ? _.map(metadata, convert) : convert(metadata);
  126. return this._invoke("createMetadata", { metadata: metadata }).then(function(results) {
  127. return _.isArray(results) ? _.map(results, convertToSaveResult) : convertToSaveResult(results);
  128. }).thenCall(callback);
  129. };
  130. /**
  131. * @private
  132. */
  133. function convertToMetadataInfo(rec) {
  134. var metadataInfo = _.clone(rec);
  135. delete metadataInfo.$;
  136. return metadataInfo;
  137. }
  138. /**
  139. * Synonym of Metadata#read()
  140. *
  141. * @method Metadata#readSync
  142. * @param {String} type - The type of metadata to read
  143. * @param {String|Array.<String>} fullNames - full name(s) of metadata objects to read
  144. * @param {Callback.<Metadata~MetadataInfo|Array.<Metadata~MetadataInfo>>} [callback] - Callback function
  145. * @returns {Promise.<Array.<Metadata~MetadataInfo|Array.<Metadata~MetadataInfo>>>}
  146. */
  147. /**
  148. * Synchronously read specified metadata components in the organization.
  149. *
  150. * @method Metadata#read
  151. * @param {String} type - The type of metadata to read
  152. * @param {String|Array.<String>} fullNames - full name(s) of metadata objects to read
  153. * @param {Callback.<Metadata~MetadataInfo|Array.<Metadata~MetadataInfo>>} [callback] - Callback function
  154. * @returns {Promise.<Array.<Metadata~MetadataInfo|Array.<Metadata~MetadataInfo>>>}
  155. */
  156. Metadata.prototype.readSync =
  157. Metadata.prototype.read = function(type, fullNames, callback) {
  158. return this._invoke("readMetadata", { type: type, fullNames: fullNames }).then(function(res) {
  159. return _.isArray(res.records) ? _.map(res.records, convertToMetadataInfo) : convertToMetadataInfo(res.records);
  160. }).thenCall(callback);
  161. };
  162. /**
  163. * @typedef {Object} Metadata~UpdateMetadataInfo
  164. * @prop {String} currentName - The API name of the component or field before the update
  165. * @prop {Metadata~MetadataInfo} metadata - Full specification of the component or field you wish to update
  166. */
  167. /**
  168. * Asynchronously updates one or more metadata components in the organization.
  169. *
  170. * @param {String} type - The type of metadata to update
  171. * @param {Metadata~UpdateMetadataInfo|Array.<Metadata~UpdateMetadataInfo>} updateMetadata - Updating metadata
  172. * @param {Callback.<Metadata~AsyncResult|Array.<Metadata~AsyncResult>>} [callback] - Callback function
  173. * @returns {Metadata~AsyncResultLocator}
  174. */
  175. Metadata.prototype.updateAsync = function(type, updateMetadata, callback) {
  176. if (Number(this._conn.version) > 30) {
  177. throw new Error("Async metadata CRUD calls are not supported on ver 31.0 or later.");
  178. }
  179. var convert = function(umd) {
  180. umd.metadata["@xsi:type"] = type;
  181. return umd;
  182. };
  183. var isArray = _.isArray(updateMetadata);
  184. updateMetadata = isArray ? _.map(updateMetadata, convert) : convert(updateMetadata);
  185. var res = this._invoke("update", { updateMetadata: updateMetadata });
  186. return new AsyncResultLocator(this, res, isArray).thenCall(callback);
  187. };
  188. /**
  189. * Synonym of Metadata#update().
  190. *
  191. * @method Metadata#updateSync
  192. * @param {String} type - The type of metadata to update
  193. * @param {Metadata~MetadataInfo|Array.<Metadata~MetadataInfo>} updateMetadata - Updating metadata
  194. * @param {Callback.<Metadata~SaveResult|Array.<Metadata~SaveResult>>} [callback] - Callback function
  195. * @returns {Promise.<Metadata~SaveResult|Array.<Metadata~SaveResult>>}
  196. */
  197. /**
  198. * Synchronously updates one or more metadata components in the organization.
  199. *
  200. * @method Metadata#update
  201. * @param {String} type - The type of metadata to update
  202. * @param {Metadata~MetadataInfo|Array.<Metadata~MetadataInfo>} updateMetadata - Updating metadata
  203. * @param {Callback.<Metadata~SaveResult|Array.<Metadata~SaveResult>>} [callback] - Callback function
  204. * @returns {Promise.<Metadata~SaveResult|Array.<Metadata~SaveResult>>}
  205. */
  206. Metadata.prototype.updateSync =
  207. Metadata.prototype.update = function(type, metadata, callback) {
  208. var convert = function(md) {
  209. md["@xsi:type"] = type;
  210. return md;
  211. };
  212. var isArray = _.isArray(metadata);
  213. metadata = isArray ? _.map(metadata, convert) : convert(metadata);
  214. return this._invoke("updateMetadata", { metadata: metadata }).then(function(results) {
  215. return _.isArray(results) ? _.map(results, convertToSaveResult) : convertToSaveResult(results);
  216. }).thenCall(callback);
  217. };
  218. /**
  219. * Synonym of Metadata#upsert().
  220. *
  221. * @method Metadata#upsertSync
  222. * @param {String} type - The type of metadata to upsert
  223. * @param {Metadata~MetadataInfo|Array.<Metadata~MetadataInfo>} metadata - Upserting metadata
  224. * @param {Callback.<Metadata~UpsertResult|Array.<Metadata~UpsertResult>>} [callback] - Callback function
  225. * @returns {Promise.<Metadata~UpsertResult|Array.<Metadata~UpsertResult>>}
  226. */
  227. /**
  228. * Upserts one or more components in your organization's data.
  229. *
  230. * @method Metadata#upsert
  231. * @param {String} type - The type of metadata to upsert
  232. * @param {Metadata~MetadataInfo|Array.<Metadata~MetadataInfo>} metadata - Upserting metadata
  233. * @param {Callback.<Metadata~UpsertResult|Array.<Metadata~UpsertResult>>} [callback] - Callback function
  234. * @returns {Promise.<Metadata~UpsertResult|Array.<Metadata~UpsertResult>>}
  235. */
  236. Metadata.prototype.upsertSync =
  237. Metadata.prototype.upsert = function(type, metadata, callback) {
  238. var convert = function(md) {
  239. md["@xsi:type"] = type;
  240. return md;
  241. };
  242. var isArray = _.isArray(metadata);
  243. metadata = isArray ? _.map(metadata, convert) : convert(metadata);
  244. return this._invoke("upsertMetadata", { metadata: metadata }).then(function(results) {
  245. return _.isArray(results) ? _.map(results, convertToUpsertResult) : convertToUpsertResult(results);
  246. }).thenCall(callback);
  247. };
  248. /**
  249. * Asynchronously deletes specified metadata components in the organization.
  250. *
  251. * @param {String} type - The type of metadata to delete
  252. * @param {String|Metadata~MetadataInfo|Array.<String>|Array.<Metadata~MetadataInfo>} metadata - The fullName of metadata or metadata info to delete. If it is passed in fullName, the type parameter should not be empty.
  253. * @param {Callback.<Metadata~AsyncResult|Array.<Metadata~AsyncResult>>} [callback] - Callback function
  254. * @returns {Metadata~AsyncResultLocator}
  255. */
  256. Metadata.prototype.deleteAsync = function(type, metadata, callback) {
  257. if (Number(this._conn.version) > 30) {
  258. throw new Error("Async metadata CRUD calls are not supported on ver 31.0 or later.");
  259. }
  260. var convert = function(md) {
  261. if (_.isString(md)) {
  262. md = { fullName : md };
  263. }
  264. md["@xsi:type"] = type;
  265. return md;
  266. };
  267. var isArray = _.isArray(metadata);
  268. metadata = isArray ? _.map(metadata, convert) : convert(metadata);
  269. var res = this._invoke("delete", { metadata: metadata });
  270. return new AsyncResultLocator(this, res, isArray).thenCall(callback);
  271. };
  272. /**
  273. * Synonym of Metadata#delete().
  274. *
  275. * @deprecated
  276. * @method Metadata#del
  277. * @param {String} [type] - The type of metadata to delete
  278. * @param {String|Metadata~MetadataInfo|Array.<String>|Array.<Metadata~MetadataInfo>} metadata - The fullName of metadata or metadata info to delete. If it is passed in fullName, the type parameter should not be empty.
  279. * @param {Callback.<Metadata~AsyncResult|Array.<Metadata~AsyncResult>>} [callback] - Callback function
  280. * @returns {Metadata~AsyncResultLocator}
  281. */
  282. /**
  283. * Synonym of Metadata#delete().
  284. *
  285. * @method Metadata#deleteSync
  286. * @param {String} type - The type of metadata to delete
  287. * @param {String|Array.<String>} fullNames - The fullName of metadata to delete.
  288. * @param {Callback.<Metadata~SaveResult|Array.<Metadata~SaveResult>>} [callback] - Callback function
  289. * @returns {Promise.<Metadata~SaveResult|Array.<Metadata~SaveResult>>}
  290. */
  291. /**
  292. * Synchronously deletes specified metadata components in the organization.
  293. *
  294. * @method Metadata#delete
  295. * @param {String} type - The type of metadata to delete
  296. * @param {String|Array.<String>} fullNames - The fullName of metadata to delete.
  297. * @param {Callback.<Metadata~SaveResult|Array.<Metadata~SaveResult>>} [callback] - Callback function
  298. * @returns {Promise.<Metadata~SaveResult|Array.<Metadata~SaveResult>>}
  299. */
  300. Metadata.prototype.del =
  301. Metadata.prototype.deleteSync =
  302. Metadata.prototype["delete"] = function(type, fullNames, callback) {
  303. return this._invoke("deleteMetadata", { type: type, fullNames: fullNames }).then(function(results) {
  304. return _.isArray(results) ? _.map(results, convertToSaveResult) : convertToSaveResult(results);
  305. }).thenCall(callback);
  306. };
  307. /**
  308. * Rename fullname of a metadata component in the organization
  309. *
  310. * @param {String} type - The type of metadata to delete
  311. * @param {String} oldFullName - The original fullName of metadata
  312. * @param {String} newFullName - The new fullName of metadata
  313. * @param {Callback.<Metadata~SaveResult>} [callback] - Callback function
  314. * @returns {Promise.<Metadata~SaveResult>}
  315. */
  316. Metadata.prototype.rename = function(type, oldFullName, newFullName, callback) {
  317. return this._invoke("renameMetadata", { type: type, oldFullName: oldFullName, newFullName: newFullName }).then(function(result) {
  318. return convertToSaveResult(result);
  319. }).thenCall(callback);
  320. };
  321. /**
  322. * Checks the status of asynchronous metadata calls
  323. *
  324. * @param {String|Array.<String>} ids - The asynchronous process ID(s)
  325. * @param {Callback.<Metadata~AsyncResult|Array.<Metadata~AsyncResult>>} [callback] - Callback function
  326. * @returns {Metadata~AsyncResultLocator}
  327. */
  328. Metadata.prototype.checkStatus = function(ids, callback) {
  329. var isArray = _.isArray(ids);
  330. var res = this._invoke("checkStatus", { asyncProcessId: ids });
  331. return new AsyncResultLocator(this, res, isArray).thenCall(callback);
  332. };
  333. /**
  334. * @typedef {Object} Metadata~DescribeMetadataResult
  335. * @prop {Array.<Object>} metadataObjects - One or more metadata components and their attributes
  336. * @prop {Array.<String>} metadataObjects.childXmlNames - List of child sub-components for this component
  337. * @prop {String} metadataObjects.directoryName - The name of the directory in the .zip file that contains this component
  338. * @prop {Boolean} metadataObjects.inFolder - Indicates whether the component is in a folder or not
  339. * @prop {Boolean} metadataObjects.metaFile - Indicates whether the component requires an accompanying metadata file
  340. * @prop {String} metadataObjects.suffix - The file suffix for this component
  341. * @prop {String} metadataObjects.xmlName - The name of the root element in the metadata file for this component
  342. * @prop {String} organizationNamespace - The namespace of the organization
  343. * @prop {Boolean} partialSaveAllowed - Indicates whether rollbackOnError is allowed or not
  344. * @prop {Boolean} testRequired - Indicates whether tests are required or not
  345. */
  346. /**
  347. * Retrieves the metadata which describes your organization, including Apex classes and triggers,
  348. * custom objects, custom fields on standard objects, tab sets that define an app,
  349. * and many other components.
  350. *
  351. * @param {String} [version] - The API version for which you want metadata; for example, 29.0
  352. * @param {Callback.<Metadata~DescribeMetadataResult>} [callback] - Callback function
  353. * @returns {Promise.<Metadata~DescribeMetadataResult>}
  354. */
  355. Metadata.prototype.describe = function(version, callback) {
  356. if (!_.isString(version)) {
  357. callback = version;
  358. version = this._conn.version;
  359. }
  360. return this._invoke("describeMetadata", { asOfVersion: version }).then(function(res) {
  361. res.metadataObjects = _.isArray(res.metadataObjects) ? res.metadataObjects : [ res.metadataObjects ];
  362. res.metadataObjects = _.map(res.metadataObjects, function(mo) {
  363. if (mo.childXmlNames) {
  364. mo.childXmlNames = _.isArray(mo.childXmlNames) ? mo.childXmlNames: [ mo.childXmlNames ];
  365. }
  366. mo.inFolder = mo.inFolder === 'true';
  367. mo.metaFile = mo.metaFile === 'true';
  368. return mo;
  369. });
  370. res.partialSaveAllowed = res.partialSaveAllowed === 'true';
  371. res.testRequired = res.testRequired === 'true';
  372. return res;
  373. }).thenCall(callback);
  374. };
  375. /**
  376. * @typedef {Object} Metadata~ListMetadataQuery
  377. * @prop {String} type - The metadata type, such as CustomObject, CustomField, or ApexClass
  378. * @prop {String} [folder] - The folder associated with the component.
  379. */
  380. /**
  381. * @typedef {Object} Metadata~FileProperties
  382. * @prop {String} type - The metadata type, such as CustomObject, CustomField, or ApexClass
  383. * @prop {String} createdById - ID of the user who created the file
  384. * @prop {String} createdByName - Name of the user who created the file
  385. * @prop {String} createdDate - Date and time when the file was created
  386. * @prop {String} fileName - Name of the file
  387. * @prop {String} fullName - The file developer name used as a unique identifier for API access
  388. * @prop {String} id - ID of the file
  389. * @prop {String} lastModifiedById - ID of the user who last modified the file
  390. * @prop {String} lastModifiedByName - Name of the user who last modified the file
  391. * @prop {String} lastModifiedDate - Date and time that the file was last modified
  392. * @prop {String} [manageableState] - Indicates the manageable state of the specified component if it is contained in a package
  393. * @prop {String} [namespacePrefix] - The namespace prefix of the component
  394. */
  395. /**
  396. * Retrieves property information about metadata components in your organization
  397. *
  398. * @param {Metadata~ListMetadataQuery|Array.<Metadata~ListMetadataQuery>} queries - The criteria object(s) specifing metadata to list
  399. * @param {String} [version] - The API version for which you want metadata; for example, 29.0
  400. * @param {Callback.<Array.<Metadata~FileProperties>>} [callback] - Callback function
  401. * @returns {Promise.<Array.<Metadata~FileProperties>>}
  402. */
  403. Metadata.prototype.list = function(queries, version, callback) {
  404. if (!_.isString(version)) {
  405. callback = version;
  406. version = this._conn.version;
  407. }
  408. if (!_.isArray(queries)) {
  409. queries = [ queries ];
  410. }
  411. return this._invoke("listMetadata", { queries: queries, asOfVersion: version }, callback);
  412. };
  413. /**
  414. * @typedef {Object} Metadata~RetrieveRequest
  415. */
  416. /**
  417. * Retrieves XML file representations of components in an organization
  418. *
  419. * @param {Metadata~RetrieveRequest} request - Options for determining which packages or files are retrieved
  420. * @param {Callback.<Metadata~AsyncResult>} [callback] - Callback function
  421. * @returns {Metadata~RetrieveResultLocator}
  422. */
  423. Metadata.prototype.retrieve = function(request, callback) {
  424. var res = this._invoke("retrieve", { request: request });
  425. return new RetrieveResultLocator(this, res).thenCall(callback);
  426. };
  427. /**
  428. * Checks the status of declarative metadata call retrieve() and returns the zip file contents
  429. *
  430. * @param {String} id - Async process id returned from previous retrieve request
  431. * @param {Callback.<Metadata~RetrieveResult>} [callback] - Callback function
  432. * @returns {Promise.<Metadata~RetrieveResult>}
  433. */
  434. Metadata.prototype.checkRetrieveStatus = function(id, callback) {
  435. return this._invoke("checkRetrieveStatus", { asyncProcessId: id }, callback);
  436. };
  437. /**
  438. * Deploy components into an organization using zipped file representations
  439. *
  440. * @param {stream.Stream|Buffer|String} zipInput - Zipped file input source in readable stream, binary buffer or Base64-encoded string
  441. * @param {Object} [options] - Options used in deployment
  442. * @param {Boolean} [options.allowMissingFiles] - Specifies whether a deploy succeeds even if files that are specified in package.xml but are not in the .zip file or not.
  443. * @param {Boolean} [options.autoUpdatePackage] - If a file is in the .zip file but not specified in package.xml, specifies whether the file should be automatically added to the package or not.
  444. * @param {Boolean} [options.checkOnly] - Indicates whether Apex classes and triggers are saved to the organization as part of the deployment (false) or not (true).
  445. * @param {Boolean} [options.ignoreWarnings] - Indicates whether a warning should allow a deployment to complete successfully (true) or not (false). Defaults to false.
  446. * @param {Boolean} [options.performRetrieve] - Indicates whether a retrieve() call is performed immediately after the deployment (true) or not (false).
  447. * @param {Boolean} [options.purgeOnDelete] - If true, the deleted components in the destructiveChanges.xml manifest file aren't stored in the Recycle Bin.
  448. * @param {Boolean} [options.rollbackOnError] - Indicates whether any failure causes a complete rollback (true) or not (false).
  449. * @param {Boolean} [options.runAllTests] - If true, all Apex tests defined in the organization are run.
  450. * @param {Array.<String>} [options.runTests] - A list of Apex tests to be run during deployment.
  451. * @param {Boolean} [options.singlePackage] - Indicates whether the specified .zip file points to a directory structure with a single package (true) or a set of packages (false).
  452. * @param {Callback.<Metadata~AsyncResult>} [callback] - Callback function
  453. * @returns {Metadata~DeployResultLocator}
  454. */
  455. Metadata.prototype.deploy = function(zipInput, options, callback) {
  456. if (!options || _.isFunction(options)) {
  457. callback = options;
  458. options = {};
  459. }
  460. var deferred = Promise.defer();
  461. if (_.isObject(zipInput) && _.isFunction(zipInput.pipe)) {
  462. var bufs = [];
  463. zipInput.on('data', function(d) {
  464. bufs.push(d);
  465. });
  466. zipInput.on('end', function() {
  467. deferred.resolve(Buffer.concat(bufs).toString('base64'));
  468. });
  469. // zipInput.resume();
  470. } else if (zipInput instanceof Buffer) {
  471. deferred.resolve(zipInput.toString('base64'));
  472. } else if (zipInput instanceof String || typeof zipInput === 'string') {
  473. deferred.resolve(zipInput);
  474. } else {
  475. throw "Unexpected zipInput type";
  476. }
  477. var self = this;
  478. var res = deferred.promise.then(function(zipContentB64) {
  479. return self._invoke("deploy", {
  480. ZipFile: zipContentB64,
  481. DeployOptions: options
  482. }, callback);
  483. });
  484. return new DeployResultLocator(this, res).thenCall(callback);
  485. };
  486. /**
  487. * Checks the status of declarative metadata call deploy()
  488. *
  489. * @param {String} id - Async process id returned from previous deploy request
  490. * @param {Boolean} [includeDetails] - Sets the DeployResult object to include details information (default: false)
  491. * @param {Callback.<Metadata~DeployResult>} [callback] - Callback function
  492. * @returns {Promise.<Metadata~DeployResult>}
  493. */
  494. Metadata.prototype.checkDeployStatus = function(id, includeDetails, callback) {
  495. if (_.isObject(includeDetails) || _.isBoolean(includeDetails)) {
  496. includeDetails = !!includeDetails;
  497. } else {
  498. callback = includeDetails;
  499. includeDetails = false;
  500. }
  501. return this._invoke("checkDeployStatus", {
  502. asyncProcessId: id,
  503. includeDetails : includeDetails
  504. }).then(function(res) {
  505. res.done = res.done === 'true';
  506. res.success = res.success === 'true';
  507. res.checkOnly = res.checkOnly === 'true';
  508. if (res.ignoreWarnings) {
  509. res.ignoreWarnings = res.ignoreWarnings === 'true';
  510. }
  511. if (res.rollbackOnError) {
  512. res.rollbackOnError = res.rollbackOnError === 'true';
  513. }
  514. res.numberComponentErrors = Number(res.numberComponentErrors);
  515. res.numberComponentsDeployed = Number(res.numberComponentsDeployed);
  516. res.numberComponentsTotal = Number(res.numberComponentsTotal);
  517. res.numberTestErrors = Number(res.numberTestErrors);
  518. res.numberTestsCompleted = Number(res.numberTestsCompleted);
  519. res.numberTestsTotal = Number(res.numberTestsTotal);
  520. return res;
  521. }).thenCall(callback);
  522. };
  523. /*--------------------------------------------*/
  524. /**
  525. * @typedef {Object} Metadata~AsyncResult
  526. * @prop {Boolean} done - Indicates whether the call has completed or not
  527. * @prop {String} id - ID of the component being created, updated, deleted, deployed, or retrieved
  528. * @prop {String} state - The state four possible values: Queued, InProgress, Completed, and Error.
  529. * @prop {String} [statusCode] - If an error occurred during the create(), update(), or delete() call, a status code is returned
  530. * @prop {String} [message] - Message corresponding to the statusCode field returned
  531. */
  532. /**
  533. * The locator class for Metadata API asynchronous call result
  534. *
  535. * @protected
  536. * @class Metadata~AsyncResultLocator
  537. * @extends events.EventEmitter
  538. * @implements Promise.<Metadata~AsyncResult|Array.<Metadata~AsyncResult>>
  539. * @param {Metadata} meta - Metadata API object
  540. * @param {Promise.<Metadata~AsyncResult|Array.<Metadata~AsyncResult>>} results - Promise object for async result info
  541. * @param {Boolean} [isArray] - Indicates whether the async request is given in array or single object
  542. */
  543. var AsyncResultLocator = function(meta, results, isArray) {
  544. this._meta = meta;
  545. this._results = results;
  546. this._isArray = isArray;
  547. };
  548. inherits(AsyncResultLocator, events.EventEmitter);
  549. /**
  550. * Promise/A+ interface
  551. * http://promises-aplus.github.io/promises-spec/
  552. *
  553. * Delegate to deferred promise, return promise instance for batch result
  554. *
  555. * @method Metadata~AsyncResultLocator#then
  556. */
  557. AsyncResultLocator.prototype.then = function(onResolve, onReject) {
  558. var self = this;
  559. return this._results.then(function(results) {
  560. var convertType = function(res) {
  561. if (res.$ && res.$["xsi:nil"] === 'true') {
  562. return null;
  563. }
  564. res.done = res.done === 'true';
  565. return res;
  566. };
  567. results = _.isArray(results) ? _.map(results, convertType) : convertType(results);
  568. if (self._isArray && !_.isArray(results)) {
  569. results = [ results ];
  570. }
  571. return onResolve(results);
  572. }, onReject);
  573. };
  574. /**
  575. * Promise/A+ extension
  576. * Call "then" using given node-style callback function
  577. *
  578. * @method Metadata~AsyncResultLocator#thenCall
  579. */
  580. AsyncResultLocator.prototype.thenCall = function(callback) {
  581. return _.isFunction(callback) ? this.then(function(res) {
  582. process.nextTick(function() {
  583. callback(null, res);
  584. });
  585. }, function(err) {
  586. process.nextTick(function() {
  587. callback(err);
  588. });
  589. }) : this;
  590. };
  591. /**
  592. * Check the status of async request
  593. *
  594. * @method Metadata~AsyncResultLocator#check
  595. * @param {Callback.<Metadata~AsyncResult|Array.<Metadata~AsyncResult>>} [callback] - Callback function
  596. * @returns {Promise.<Metadata~AsyncResult|Array.<Metadata~AsyncResult>>}
  597. */
  598. AsyncResultLocator.prototype.check = function(callback) {
  599. var self = this;
  600. var meta = this._meta;
  601. return this.then(function(results) {
  602. var ids = _.isArray(results) ? _.map(results, function(res){ return res.id; }) : results.id;
  603. self._ids = ids;
  604. return meta.checkStatus(ids);
  605. }).thenCall(callback);
  606. };
  607. /**
  608. * Polling until async call status becomes complete or error
  609. *
  610. * @method Metadata~AsyncResultLocator#poll
  611. * @param {Number} interval - Polling interval in milliseconds
  612. * @param {Number} timeout - Polling timeout in milliseconds
  613. */
  614. AsyncResultLocator.prototype.poll = function(interval, timeout) {
  615. var self = this;
  616. var startTime = new Date().getTime();
  617. var poll = function() {
  618. var now = new Date().getTime();
  619. if (startTime + timeout < now) {
  620. var errMsg = "Polling time out.";
  621. if (self._ids) {
  622. errMsg += " Process Id = " + self._ids;
  623. }
  624. self.emit('error', new Error(errMsg));
  625. return;
  626. }
  627. self.check().then(function(results) {
  628. var done = true;
  629. var resultArr = _.isArray(results) ? results : [ results ];
  630. for (var i=0, len=resultArr.length; i<len; i++) {
  631. var result = resultArr[i];
  632. if (result && !result.done) {
  633. self.emit('progress', result);
  634. done = false;
  635. }
  636. }
  637. if (done) {
  638. self.emit('complete', results);
  639. } else {
  640. setTimeout(poll, interval);
  641. }
  642. }, function(err) {
  643. self.emit('error', err);
  644. });
  645. };
  646. setTimeout(poll, interval);
  647. };
  648. /**
  649. * Check and wait until the async requests become in completed status
  650. *
  651. * @method Metadata~AsyncResultLocator#complete
  652. * @param {Callback.<Metadata~AsyncResult|Array.<Metadata~AsyncResult>>} [callback] - Callback function
  653. * @returns {Promise.<Metadata~AsyncResult|Array.<Metadata~AsyncResult>>}
  654. */
  655. AsyncResultLocator.prototype.complete = function(callback) {
  656. var deferred = Promise.defer();
  657. this.on('complete', function(results) {
  658. deferred.resolve(results);
  659. });
  660. this.on('error', function(err) {
  661. deferred.reject(err);
  662. });
  663. var meta = this._meta;
  664. this.poll(meta.pollInterval, meta.pollTimeout);
  665. return deferred.promise.thenCall(callback);
  666. };
  667. /*--------------------------------------------*/
  668. /**
  669. * The locator class to track retreive() Metadata API call result
  670. *
  671. * @protected
  672. * @class Metadata~RetrieveResultLocator
  673. * @extends Metadata~AsyncResultLocator
  674. * @param {Metadata} meta - Metadata API object
  675. * @param {Promise.<Metadata~AsyncResult>} result - Promise object for async result of retrieve call()
  676. */
  677. var RetrieveResultLocator = function(meta, result) {
  678. RetrieveResultLocator.super_.call(this, meta, result);
  679. };
  680. inherits(RetrieveResultLocator, AsyncResultLocator);
  681. /**
  682. * @typedef {Object} Metadata~RetrieveResult
  683. * @prop {Array.<Metadata~FileProperties>} fileProperties - Contains information about the properties of each component in the .zip file, and the manifest file package.xml
  684. * @prop {String} id - ID of the component being retrieved
  685. * @prop {Array.<Object>} messages - Contains information about the success or failure of the retrieve() call
  686. * @prop {String} zipFile - The zip file returned by the retrieve request. Base 64-encoded binary data
  687. */
  688. /**
  689. * Check and wait until the async request becomes in completed status,
  690. * and retrieve the result data.
  691. *
  692. * @memthod Metadata~RetrieveResultLocator#complete
  693. * @param {Callback.<Metadata~RetrieveResult>} [callback] - Callback function
  694. * @returns {Promise.<Metadata~RetrieveResult>}
  695. */
  696. RetrieveResultLocator.prototype.complete = function(callback) {
  697. var meta = this._meta;
  698. return RetrieveResultLocator.super_.prototype.complete.call(this).then(function(result) {
  699. return meta.checkRetrieveStatus(result.id);
  700. }).thenCall(callback);
  701. };
  702. /**
  703. * Change the retrieved result to Node.js readable stream
  704. *
  705. * @method Metadata~RetrieveResultLocator#stream
  706. * @returns {stream.Readable}
  707. */
  708. RetrieveResultLocator.prototype.stream = function() {
  709. var self = this;
  710. var resultStream = new stream.Readable();
  711. var reading = false;
  712. resultStream._read = function() {
  713. if (reading) { return; }
  714. reading = true;
  715. self.complete(function(err, result) {
  716. if (err) {
  717. resultStream.emit('error', err);
  718. } else {
  719. resultStream.push(new Buffer(result.zipFile, 'base64'));
  720. resultStream.push(null);
  721. }
  722. });
  723. };
  724. return resultStream;
  725. };
  726. /*--------------------------------------------*/
  727. /**
  728. * The locator class to track deploy() Metadata API call result
  729. *
  730. * @protected
  731. * @class Metadata~DeployResultLocator
  732. * @extends Metadata~AsyncResultLocator
  733. * @param {Metadata} meta - Metadata API object
  734. * @param {Promise.<Metadata~AsyncResult>} result - Promise object for async result of deploy() call
  735. */
  736. var DeployResultLocator = function(meta, result) {
  737. DeployResultLocator.super_.call(this, meta, result);
  738. };
  739. inherits(DeployResultLocator, AsyncResultLocator);
  740. /**
  741. * @typedef {Object} Metadata~DeployResult
  742. * @prop {String} id - ID of the component being deployed
  743. * @prop {Boolean} checkOnly - Indicates whether this deployment is being used to check the validity of the deployed files without making any changes in the organization or not
  744. * @prop {String} completedDate - Timestamp for when the deployment process ended
  745. * @prop {String} createdDate - Timestamp for when the deploy() call was received
  746. * @prop {Array.<Object>} [details] - Provides the details of a deployment that is in-progress or ended, if includeDetails is set to true in checkDeployStatus() call
  747. * @prop {Boolean} done - Indicates whether the server finished processing the deploy() call for the specified id
  748. * @prop {String} [errorMessage] - Message corresponding to the values in the errorStatusCode field
  749. * @prop {String} [errorStatusCode] - If an error occurred during the deploy() call, a status code is returned, and the message corresponding to the status code is returned in the errorMessagefield
  750. * @prop {Boolean} [ignoreWarnings] - Specifies whether a deployment should continue even if the deployment generates warnings
  751. * @prop {String} lastModifiedDate - Timestamp of the last update for the deployment process
  752. * @prop {Number} numberComponentErrors - The number of components that generated errors during this deployment
  753. * @prop {Number} numberComponentsDeployed - The number of components deployed in the deployment process
  754. * @prop {Number} numberComponentsTotal - The total number of components in the deployment
  755. * @prop {Number} numberTestErrors - The number of Apex tests that have generated errors during this deployment
  756. * @prop {Number} numberTestsCompleted - The number of completedApex tests for this deployment
  757. * @prop {Number} numberTestsTotal - The total number of Apex tests for this deployment
  758. * @prop {Boolean} [rollbackOnError] - Indicates whether any failure causes a complete rollback or not. Default is true.
  759. * @prop {String} startDate - Timestamp for when the deployment process began
  760. * @prop {String} status - Indicates the current state of the deployment
  761. * @prop {Boolean} success - Indicates whether the deployment was successful or not
  762. */
  763. /**
  764. * Check and wait until the async request becomes in completed status,
  765. * and retrieve the result data.
  766. *
  767. * @method Metadata~DeployResultLocator#complete
  768. * @param {Callback.<Metadata~DeployResult>} [callback] - Callback function
  769. * @returns {Promise.<Metadata~DeployResult>}
  770. */
  771. DeployResultLocator.prototype.complete = function(includeDetails, callback) {
  772. if (_.isFunction(includeDetails)) {
  773. callback = includeDetails;
  774. includeDetails = false;
  775. }
  776. var meta = this._meta;
  777. return DeployResultLocator.super_.prototype.complete.call(this).then(function(result) {
  778. return meta.checkDeployStatus(result.id, includeDetails);
  779. }).thenCall(callback);
  780. };
  781. /*--------------------------------------------*/
  782. /*
  783. * Register hook in connection instantiation for dynamically adding this API module features
  784. */
  785. jsforce.on('connection:new', function(conn) {
  786. conn.metadata = new Metadata(conn);
  787. });