UNPKG

@jsforce/jsforce-node

Version:

Salesforce API Library for JavaScript

417 lines (416 loc) 15.1 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __exportStar = (this && this.__exportStar) || function(m, exports) { for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.DeployResultLocator = exports.RetrieveResultLocator = exports.AsyncResultLocator = exports.MetadataApi = void 0; /** * @file Manages Salesforce Metadata API * @author Shinichi Tomita <shinichi.tomita@gmail.com> */ const events_1 = require("events"); const stream_1 = require("stream"); const form_data_1 = __importDefault(require("form-data")); const jsforce_1 = require("../jsforce"); const soap_1 = __importDefault(require("../soap")); const function_1 = require("../util/function"); const schema_1 = require("./metadata/schema"); __exportStar(require("./metadata/schema"), exports); /** * */ function deallocateTypeWithMetadata(metadata) { const { $, ...md } = metadata; return md; } function assignTypeWithMetadata(metadata, type) { const convert = (md) => ({ ['@xsi:type']: type, ...md }); return Array.isArray(metadata) ? metadata.map(convert) : convert(metadata); } /** * Class for Salesforce Metadata API */ class MetadataApi { _conn; /** * Polling interval in milliseconds */ pollInterval = 1000; /** * Polling timeout in milliseconds */ pollTimeout = 10000; /** * */ constructor(conn) { this._conn = conn; } /** * Call Metadata API SOAP endpoint * * @private */ async _invoke(method, message, schema) { const soapEndpoint = new soap_1.default(this._conn, { xmlns: 'http://soap.sforce.com/2006/04/metadata', endpointUrl: `${this._conn.instanceUrl}/services/Soap/m/${this._conn.version}`, }); const res = await soapEndpoint.invoke(method, message, schema ? { result: schema } : undefined, schema_1.ApiSchemas); return res.result; } create(type, metadata) { const isArray = Array.isArray(metadata); metadata = assignTypeWithMetadata(metadata, type); const schema = isArray ? [schema_1.ApiSchemas.SaveResult] : schema_1.ApiSchemas.SaveResult; return this._invoke('createMetadata', { metadata }, schema); } async read(type, fullNames) { const ReadResultSchema = type in schema_1.ApiSchemas ? { type: schema_1.ApiSchemas.ReadResult.type, props: { records: [type], }, } : schema_1.ApiSchemas.ReadResult; const res = await this._invoke('readMetadata', { type, fullNames }, ReadResultSchema); return Array.isArray(fullNames) ? res.records.map(deallocateTypeWithMetadata) : deallocateTypeWithMetadata(res.records[0]); } update(type, metadata) { const isArray = Array.isArray(metadata); metadata = assignTypeWithMetadata(metadata, type); const schema = isArray ? [schema_1.ApiSchemas.SaveResult] : schema_1.ApiSchemas.SaveResult; return this._invoke('updateMetadata', { metadata }, schema); } upsert(type, metadata) { const isArray = Array.isArray(metadata); metadata = assignTypeWithMetadata(metadata, type); const schema = isArray ? [schema_1.ApiSchemas.UpsertResult] : schema_1.ApiSchemas.UpsertResult; return this._invoke('upsertMetadata', { metadata }, schema); } delete(type, fullNames) { const schema = Array.isArray(fullNames) ? [schema_1.ApiSchemas.SaveResult] : schema_1.ApiSchemas.SaveResult; return this._invoke('deleteMetadata', { type, fullNames }, schema); } /** * Rename fullname of a metadata component in the organization */ rename(type, oldFullName, newFullName) { return this._invoke('renameMetadata', { type, oldFullName, newFullName }, schema_1.ApiSchemas.SaveResult); } /** * Retrieves the metadata which describes your organization, including Apex classes and triggers, * custom objects, custom fields on standard objects, tab sets that define an app, * and many other components. */ describe(asOfVersion) { if (!asOfVersion) { asOfVersion = this._conn.version; } return this._invoke('describeMetadata', { asOfVersion }, schema_1.ApiSchemas.DescribeMetadataResult); } /** * Retrieves property information about metadata components in your organization */ list(queries, asOfVersion) { if (!asOfVersion) { asOfVersion = this._conn.version; } return this._invoke('listMetadata', { queries, asOfVersion }, [ schema_1.ApiSchemas.FileProperties, ]); } /** * Checks the status of asynchronous metadata calls */ checkStatus(asyncProcessId) { const res = this._invoke('checkStatus', { asyncProcessId }, schema_1.ApiSchemas.AsyncResult); return new AsyncResultLocator(this, res); } /** * Retrieves XML file representations of components in an organization */ retrieve(request) { const res = this._invoke('retrieve', { request }, schema_1.ApiSchemas.RetrieveResult); return new RetrieveResultLocator(this, res); } /** * Checks the status of declarative metadata call retrieve() and returns the zip file contents */ checkRetrieveStatus(asyncProcessId) { return this._invoke('checkRetrieveStatus', { asyncProcessId }, schema_1.ApiSchemas.RetrieveResult); } /** * Will deploy a recently validated deploy request * * @param options.id = the deploy ID that's been validated already from a previous checkOnly deploy request * @param options.rest = a boolean whether or not to use the REST API * @returns the deploy ID of the recent validation request */ async deployRecentValidation(options) { const { id, rest } = options; let response; if (rest) { const messageBody = JSON.stringify({ validatedDeployRequestId: id, }); const requestInfo = { method: 'POST', url: `${this._conn._baseUrl()}/metadata/deployRequest`, body: messageBody, headers: { 'content-type': 'application/json', }, }; const requestOptions = { headers: 'json' }; // This is the deploy ID of the deployRecentValidation response, not // the already validated deploy ID (i.e., validateddeployrequestid). // REST returns an object with an id property, SOAP returns the id as a string directly. response = (await this._conn.request(requestInfo, requestOptions)).id; } else { response = await this._invoke('deployRecentValidation', { validationId: id, }); } return response; } /** * Deploy components into an organization using zipped file representations * using the REST Metadata API instead of SOAP */ deployRest(zipInput, options = {}) { const form = new form_data_1.default(); form.append('file', zipInput, { contentType: 'application/zip', filename: 'package.xml', }); // Add the deploy options form.append('entity_content', JSON.stringify({ deployOptions: options }), { contentType: 'application/json', }); const request = { url: '/metadata/deployRequest', method: 'POST', headers: { ...form.getHeaders() }, body: form.getBuffer(), }; const res = this._conn.request(request); return new DeployResultLocator(this, res); } /** * Deploy components into an organization using zipped file representations */ deploy(zipInput, options = {}) { const res = (async () => { const zipContentB64 = await new Promise((resolve, reject) => { if ((0, function_1.isObject)(zipInput) && 'pipe' in zipInput && typeof zipInput.pipe === 'function') { const bufs = []; zipInput.on('data', (d) => bufs.push(d)); zipInput.on('error', reject); zipInput.on('end', () => { resolve(Buffer.concat(bufs).toString('base64')); }); // zipInput.resume(); } else if (zipInput instanceof Buffer) { resolve(zipInput.toString('base64')); } else if (zipInput instanceof String || typeof zipInput === 'string') { resolve(zipInput); } else { throw 'Unexpected zipInput type'; } }); return this._invoke('deploy', { ZipFile: zipContentB64, DeployOptions: options, }, schema_1.ApiSchemas.DeployResult); })(); return new DeployResultLocator(this, res); } /** * Checks the status of declarative metadata call deploy(), using either * SOAP or REST APIs. SOAP is the default. */ async checkDeployStatus(asyncProcessId, includeDetails = false, rest = false) { if (rest) { const url = `/metadata/deployRequest/${asyncProcessId}${includeDetails ? '?includeDetails=true' : ''}`; return (await this._conn.requestGet(url)).deployResult; } else { return this._invoke('checkDeployStatus', { asyncProcessId, includeDetails, }, schema_1.ApiSchemas.DeployResult); } } async cancelDeploy(id) { return this._invoke('cancelDeploy', { id }); } } exports.MetadataApi = MetadataApi; /*--------------------------------------------*/ /** * The locator class for Metadata API asynchronous call result */ class AsyncResultLocator extends events_1.EventEmitter { _meta; _promise; _id; /** * */ constructor(meta, promise) { super(); this._meta = meta; this._promise = promise; } /** * Promise/A+ interface * http://promises-aplus.github.io/promises-spec/ * * @method Metadata~AsyncResultLocator#then */ then(onResolve, onReject) { return this._promise.then(onResolve, onReject); } /** * Check the status of async request */ async check() { const result = await this._promise; this._id = result.id; return this._meta.checkStatus(result.id); } /** * Polling until async call status becomes complete or error */ poll(interval, timeout) { const startTime = new Date().getTime(); const poll = async () => { try { const now = new Date().getTime(); if (startTime + timeout < now) { let errMsg = 'Polling time out.'; if (this._id) { errMsg += ' Process Id = ' + this._id; } this.emit('error', new Error(errMsg)); return; } const result = await this.check(); if (result.done) { this.emit('complete', result); } else { this.emit('progress', result); setTimeout(poll, interval); } } catch (err) { this.emit('error', err); } }; setTimeout(poll, interval); } /** * Check and wait until the async requests become in completed status */ complete() { return new Promise((resolve, reject) => { this.on('complete', resolve); this.on('error', reject); this.poll(this._meta.pollInterval, this._meta.pollTimeout); }); } } exports.AsyncResultLocator = AsyncResultLocator; /*--------------------------------------------*/ /** * The locator class to track retreive() Metadata API call result */ class RetrieveResultLocator extends AsyncResultLocator { /** * Check and wait until the async request becomes in completed status, * and retrieve the result data. */ async complete() { const result = await super.complete(); return this._meta.checkRetrieveStatus(result.id); } /** * Change the retrieved result to Node.js readable stream */ stream() { const resultStream = new stream_1.Readable(); let reading = false; resultStream._read = async () => { if (reading) { return; } reading = true; try { const result = await this.complete(); resultStream.push(Buffer.from(result.zipFile, 'base64')); resultStream.push(null); } catch (e) { resultStream.emit('error', e); } }; return resultStream; } } exports.RetrieveResultLocator = RetrieveResultLocator; /*--------------------------------------------*/ /** * The locator class to track deploy() Metadata API call result * * @protected * @class Metadata~DeployResultLocator * @extends Metadata~AsyncResultLocator * @param {Metadata} meta - Metadata API object * @param {Promise.<Metadata~AsyncResult>} result - Promise object for async result of deploy() call */ class DeployResultLocator extends AsyncResultLocator { /** * Check and wait until the async request becomes in completed status, * and retrieve the result data. */ async complete(includeDetails) { const result = await super.complete(); return this._meta.checkDeployStatus(result.id, includeDetails); } } exports.DeployResultLocator = DeployResultLocator; /*--------------------------------------------*/ /* * Register hook in connection instantiation for dynamically adding this API module features */ (0, jsforce_1.registerModule)('metadata', (conn) => new MetadataApi(conn)); exports.default = MetadataApi;