@jsforce/jsforce-node
Version:
Salesforce API Library for JavaScript
417 lines (416 loc) • 15.1 kB
JavaScript
"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;