UNPKG

dataverse-webapi

Version:
796 lines (795 loc) 27.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getHeaders = getHeaders; exports.retrieve = retrieve; exports.retrieveProperty = retrieveProperty; exports.retrieveNavigationProperties = retrieveNavigationProperties; exports.retrieveMultiple = retrieveMultiple; exports.retrieveMultipleNextPage = retrieveMultipleNextPage; exports.create = create; exports.createWithReturnData = createWithReturnData; exports.update = update; exports.updateWithReturnData = updateWithReturnData; exports.updateProperty = updateProperty; exports.deleteRecord = deleteRecord; exports.deleteProperty = deleteProperty; exports.associate = associate; exports.disassociate = disassociate; exports.boundAction = boundAction; exports.unboundAction = unboundAction; exports.boundFunction = boundFunction; exports.unboundFunction = unboundFunction; exports.batchOperation = batchOperation; function parseGuid(id) { if (id === null || id === 'undefined' || id === '') { return ''; } id = id.replace(/[{}]/g, ''); if (/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/.test(id)) { return id.toUpperCase(); } else { throw Error(`Id ${id} is not a valid GUID`); } } function parseBatchResponse(responseBody) { if (responseBody == null) return []; const results = []; const boundaryIdentifier = responseBody.substring(0, responseBody.indexOf('\r\n')); const responses = responseBody .split(boundaryIdentifier) .map((r) => r.trim()) .filter((b) => b.length != 0 && b != '--'); for (let i = 0; i < responses.length; i++) { const changesetResponseIndex = responses[i].indexOf('--changesetresponse'); if (changesetResponseIndex > -1) { parseBatchResponse(responses[i].substring(changesetResponseIndex)).map((r) => results.push(r)); } else { const httpStatusMatch = /HTTP\/[^\s]*\s+(\d{3})\s+([\w\s]*)/i.exec(responses[i]); if (httpStatusMatch) { const response = {}; response.httpStatus = parseInt(httpStatusMatch[1]); response.httpStatusText = httpStatusMatch[2]?.split(`\r\n`).join(' '); if (response.httpStatus == 200) { const dataStartIndex = responses[i].indexOf('{'); const dataLastIndex = responses[i].lastIndexOf('}'); if (dataStartIndex > -1 && dataLastIndex > -1) { response.data = JSON.parse(responses[i].substr(dataStartIndex, dataLastIndex + 1)); } } else { const contentIdMatch = /Content-ID\:\s*([\w\d]*)/i.exec(responses[i]); if (contentIdMatch) { response.contentId = contentIdMatch[1]; } if (response.httpStatus < 400) { const locationMatch = /Location\:\s*(.*)/i.exec(responses[i]); if (locationMatch) { response.location = locationMatch[1]; } const entityIdMatch = /OData-EntityId\:\s*(.*)/i.exec(responses[i]); if (entityIdMatch) { const guidMatch = /[0-9a-fA-F]{8}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{4}\-[0-9a-fA-F]{12}/.exec(entityIdMatch[1]); if (guidMatch) { response.entityId = guidMatch[0]; } } } if (response.httpStatus >= 400) { const errorStartIndex = responses[i].indexOf('{'); const errorLastIndex = responses[i].lastIndexOf('}'); if (errorStartIndex > -1 && errorLastIndex > -1) { response.error = JSON.parse(responses[i].substr(errorStartIndex, errorLastIndex + 1)); } } } results.push(response); } } } return results; } function getHeaders(config) { let headers = {}; headers.Accept = 'application/json'; headers['OData-MaxVersion'] = '4.0'; headers['OData-Version'] = '4.0'; headers['Content-Type'] = config.contentType; headers['If-None-Match'] = 'null'; if (config.apiConfig.accessToken != null) { headers.Authorization = `Bearer ${config.apiConfig.accessToken}`; } headers.Prefer = getPreferHeader(config.queryOptions); if (config.queryOptions != null && typeof config.queryOptions !== 'undefined') { if (config.queryOptions.impersonateUserId != null) { headers.CallerObjectId = config.queryOptions.impersonateUserId; } if (config.queryOptions.customHeaders != null) { headers = { ...headers, ...config.queryOptions.customHeaders }; } } return headers; } function getPreferHeader(queryOptions) { const prefer = ['odata.include-annotations="*"']; // add max page size to prefer request header if (queryOptions?.maxPageSize) { prefer.push(`odata.maxpagesize=${queryOptions.maxPageSize}`); } // add formatted values to prefer request header if (queryOptions?.representation) { prefer.push('return=representation'); } return prefer.join(','); } function getFunctionInputs(queryString, inputs) { if (inputs == null) { return queryString + ')'; } const aliases = []; for (const input of inputs) { queryString += input.name; if (input.alias) { queryString += `=@${input.alias},`; aliases.push(`@${input.alias}=${input.value}`); } else { queryString += `=${input.value},`; } } queryString = queryString.substr(0, queryString.length - 1) + ')'; if (aliases.length > 0) { queryString += `?${aliases.join('&')}`; } return queryString; } function handleError(result) { try { return JSON.parse(result).error; } catch (e) { return new Error('Unexpected Error'); } } /** * Retrieve a record from Dataverse * @param apiConfig WebApiConfig object * @param entitySet Type of entity to retrieve * @param id Id of record to retrieve * @param queryString OData query string parameters * @param queryOptions Various query options for the query */ function retrieve(apiConfig, entitySet, id, submitRequest, queryString, queryOptions) { if (queryString != null && !/^[?]/.test(queryString)) { queryString = `?${queryString}`; } id = parseGuid(id); const query = queryString != null ? `${entitySet}(${id})${queryString}` : `${entitySet}(${id})`; const config = { method: 'GET', contentType: 'application/json; charset=utf-8', queryString: query, apiConfig: apiConfig, queryOptions: queryOptions }; return new Promise((resolve, reject) => { submitRequest(config, (result) => { if (result.error) { reject(handleError(result.response)); } else { resolve(JSON.parse(result.response)); } }); }); } /** * Retrieve a single property of a record from Dataverse * @param apiConfig WebApiConfig object * @param entitySet Type of entity to retrieve * @param id Id of record to retrieve * @param property Property to retrieve */ function retrieveProperty(apiConfig, entitySet, id, submitRequest, property) { id = parseGuid(id); const query = `${entitySet}(${id})/${property}`; const config = { method: 'GET', contentType: 'application/json; charset=utf-8', queryString: query, apiConfig: apiConfig, queryOptions: {} }; return new Promise((resolve, reject) => { submitRequest(config, (result) => { if (result.error) { reject(handleError(result.response)); } else { resolve(JSON.parse(result.response)); } }); }); } /** * Retrieve columns for a related navigation property of a record from Dataverse * @param apiConfig WebApiConfig object * @param entitySet Type of entity to retrieve * @param id Id of record to retrieve * @param property Navigation property to retrieve * @param queryString OData query string parameters * @param queryOptions Various query options for the query */ function retrieveNavigationProperties(apiConfig, entitySet, id, submitRequest, property, queryString, queryOptions) { id = parseGuid(id); if (queryString != null && !/^[?]/.test(queryString)) { queryString = `?${queryString}`; } const query = queryString != null ? `${entitySet}(${id})/${property}${queryString}` : `${entitySet}(${id})/${property}`; const config = { method: 'GET', contentType: 'application/json; charset=utf-8', queryString: query, apiConfig: apiConfig, queryOptions: queryOptions }; return new Promise((resolve, reject) => { submitRequest(config, (result) => { if (result.error) { reject(handleError(result.response)); } else { resolve(JSON.parse(result.response)); } }); }); } /** * Retrieve multiple records from Dataverse * @param apiConfig WebApiConfig object * @param entitySet Type of entity to retrieve * @param queryString OData query string parameters * @param queryOptions Various query options for the query */ function retrieveMultiple(apiConfig, entitySet, submitRequest, queryString, queryOptions) { if (queryString != null && !/^[?]/.test(queryString)) { queryString = `?${queryString}`; } const query = queryString != null ? entitySet + queryString : entitySet; const config = { method: 'GET', contentType: 'application/json; charset=utf-8', queryString: query, apiConfig: apiConfig, queryOptions: queryOptions }; return new Promise((resolve, reject) => { submitRequest(config, (result) => { if (result.error) { reject(handleError(result.response)); } else { resolve(JSON.parse(result.response)); } }); }); } /** * Retrieve next page from a retrieveMultiple request * @param apiConfig WebApiConfig object * @param url Query from the @odata.nextlink property of a retrieveMultiple * @param queryOptions Various query options for the query */ function retrieveMultipleNextPage(apiConfig, url, submitRequest, queryOptions) { apiConfig.url = url; const config = { method: 'GET', contentType: 'application/json; charset=utf-8', queryString: '', apiConfig: apiConfig, queryOptions: queryOptions }; return new Promise((resolve, reject) => { submitRequest(config, (result) => { if (result.error) { reject(handleError(result.response)); } else { resolve(JSON.parse(result.response)); } }); }); } /** * Create a record in Dataverse * @param apiConfig WebApiConfig object * @param entitySet Type of entity to create * @param entity Entity to create * @param queryOptions Various query options for the query */ function create(apiConfig, entitySet, entity, submitRequest, queryOptions) { const config = { method: 'POST', contentType: 'application/json; charset=utf-8', queryString: entitySet, body: JSON.stringify(entity), apiConfig: apiConfig, queryOptions: queryOptions }; return new Promise((resolve, reject) => { submitRequest(config, (result) => { if (result.error) { reject(handleError(result.response)); } else { resolve(); } }); }); } /** * Create a record in Dataverse and return data * @param apiConfig WebApiConfig object * @param entitySet Type of entity to create * @param entity Entity to create * @param select Select odata query parameter * @param queryOptions Various query options for the query */ function createWithReturnData(apiConfig, entitySet, entity, select, submitRequest, queryOptions) { if (select != null && !/^[?]/.test(select)) { select = `?${select}`; } // set representation if (queryOptions == null) { queryOptions = {}; } queryOptions.representation = true; const config = { method: 'POST', contentType: 'application/json; charset=utf-8', queryString: entitySet + select, body: JSON.stringify(entity), apiConfig: apiConfig, queryOptions: queryOptions }; return new Promise((resolve, reject) => { submitRequest(config, (result) => { if (result.error) { reject(handleError(result.response)); } else { resolve(JSON.parse(result.response)); } }); }); } /** * Update a record in Dataverse * @param apiConfig WebApiConfig object * @param entitySet Type of entity to update * @param id Id of record to update * @param entity Entity fields to update * @param queryOptions Various query options for the query */ function update(apiConfig, entitySet, id, entity, submitRequest, queryOptions) { id = parseGuid(id); const config = { method: 'PATCH', contentType: 'application/json; charset=utf-8', queryString: `${entitySet}(${id})`, body: JSON.stringify(entity), apiConfig: apiConfig, queryOptions: queryOptions }; return new Promise((resolve, reject) => { submitRequest(config, (result) => { if (result.error) { reject(handleError(result.response)); } else { resolve(); } }); }); } /** * Create a record in Dataverse and return data * @param apiConfig WebApiConfig object * @param entitySet Type of entity to create * @param id Id of record to update * @param entity Entity fields to update * @param select Select odata query parameter * @param queryOptions Various query options for the query */ function updateWithReturnData(apiConfig, entitySet, id, entity, select, submitRequest, queryOptions) { id = parseGuid(id); if (select != null && !/^[?]/.test(select)) { select = `?${select}`; } // set representation if (queryOptions == null) { queryOptions = {}; } queryOptions.representation = true; const config = { method: 'PATCH', contentType: 'application/json; charset=utf-8', queryString: `${entitySet}(${id})${select}`, body: JSON.stringify(entity), apiConfig: apiConfig, queryOptions: queryOptions }; return new Promise((resolve, reject) => { submitRequest(config, (result) => { if (result.error) { reject(handleError(result.response)); } else { resolve(JSON.parse(result.response)); } }); }); } /** * Update a single property of a record in Dataverse * @param apiConfig WebApiConfig object * @param entitySet Type of entity to update * @param id Id of record to update * @param attribute Attribute to update * @param queryOptions Various query options for the query */ function updateProperty(apiConfig, entitySet, id, attribute, value, submitRequest, queryOptions) { id = parseGuid(id); const config = { method: 'PUT', contentType: 'application/json; charset=utf-8', queryString: `${entitySet}(${id})/${attribute}`, body: JSON.stringify({ value: value }), apiConfig: apiConfig, queryOptions: queryOptions }; return new Promise((resolve, reject) => { submitRequest(config, (result) => { if (result.error) { reject(handleError(result.response)); } else { resolve(); } }); }); } /** * Delete a record from Dataverse * @param apiConfig WebApiConfig object * @param entitySet Type of entity to delete * @param id Id of record to delete */ function deleteRecord(apiConfig, entitySet, id, submitRequest) { id = parseGuid(id); const config = { method: 'DELETE', contentType: 'application/json; charset=utf-8', queryString: `${entitySet}(${id})`, apiConfig: apiConfig }; return new Promise((resolve, reject) => { submitRequest(config, (result) => { if (result.error) { reject(handleError(result.response)); } else { resolve(); } }); }); } /** * Delete a property from a record in Dataverse. Non navigation properties only * @param apiConfig WebApiConfig object * @param entitySet Type of entity to update * @param id Id of record to update * @param attribute Attribute to delete */ function deleteProperty(apiConfig, entitySet, id, attribute, submitRequest) { id = parseGuid(id); const queryString = `/${attribute}`; const config = { method: 'DELETE', contentType: 'application/json; charset=utf-8', queryString: `${entitySet}(${id})${queryString}`, apiConfig: apiConfig }; return new Promise((resolve, reject) => { submitRequest(config, (result) => { if (result.error) { reject(handleError(result.response)); } else { resolve(); } }); }); } /** * Associate two records * @param apiConfig WebApiConfig object * @param entitySet Type of entity for primary record * @param id Id of primary record * @param relationship Schema name of relationship * @param relatedEntitySet Type of entity for secondary record * @param relatedEntityId Id of secondary record * @param queryOptions Various query options for the query */ function associate(apiConfig, entitySet, id, relationship, relatedEntitySet, relatedEntityId, submitRequest, queryOptions) { id = parseGuid(id); const related = { '@odata.id': `${apiConfig.url}/${relatedEntitySet}(${relatedEntityId})` }; const config = { method: 'POST', contentType: 'application/json; charset=utf-8', queryString: `${entitySet}(${id})/${relationship}/$ref`, body: JSON.stringify(related), apiConfig: apiConfig, queryOptions: queryOptions }; return new Promise((resolve, reject) => { submitRequest(config, (result) => { if (result.error) { reject(handleError(result.response)); } else { resolve(); } }); }); } /** * Disassociate two records * @param apiConfig WebApiConfig obje * @param entitySet Type of entity for primary record * @param id Id of primary record * @param property Schema name of property or relationship * @param relatedEntityId Id of secondary record. Only needed for collection-valued navigation properties */ function disassociate(apiConfig, entitySet, id, property, submitRequest, relatedEntityId) { id = parseGuid(id); let queryString = property; if (relatedEntityId != null) { queryString += `(${relatedEntityId})`; } queryString += '/$ref'; const config = { method: 'DELETE', contentType: 'application/json; charset=utf-8', queryString: `${entitySet}(${id})/${queryString}`, apiConfig: apiConfig }; return new Promise((resolve, reject) => { submitRequest(config, (result) => { if (result.error) { reject(handleError(result.response)); } else { resolve(); } }); }); } /** * Execute a default or custom bound action in Dataverse * @param apiConfig WebApiConfig object * @param entitySet Type of entity to run the action against * @param id Id of record to run the action against * @param actionName Name of the action to run * @param inputs Any inputs required by the action * @param queryOptions Various query options for the query */ function boundAction(apiConfig, entitySet, id, actionName, submitRequest, inputs, queryOptions) { id = parseGuid(id); const config = { method: 'POST', contentType: 'application/json; charset=utf-8', queryString: `${entitySet}(${id})/Microsoft.Dynamics.CRM.${actionName}`, apiConfig: apiConfig, queryOptions: queryOptions }; if (inputs != null) { config.body = JSON.stringify(inputs); } return new Promise((resolve, reject) => { submitRequest(config, (result) => { if (result.error) { reject(handleError(result.response)); } else { if (result.response) { resolve(JSON.parse(result.response)); } else { resolve(null); } } }); }); } /** * Execute a default or custom unbound action in Dataverse * @param apiConfig WebApiConfig object * @param actionName Name of the action to run * @param inputs Any inputs required by the action * @param queryOptions Various query options for the query */ function unboundAction(apiConfig, actionName, submitRequest, inputs, queryOptions) { const config = { method: 'POST', contentType: 'application/json; charset=utf-8', queryString: actionName, apiConfig: apiConfig, queryOptions: queryOptions }; if (inputs != null) { config.body = JSON.stringify(inputs); } return new Promise((resolve, reject) => { submitRequest(config, (result) => { if (result.error) { reject(handleError(result.response)); } else { if (result.response) { resolve(JSON.parse(result.response)); } else { resolve(null); } } }); }); } /** * Execute a default or custom bound action in Dataverse * @param apiConfig WebApiConfig object * @param entitySet Type of entity to run the action against * @param id Id of record to run the action against * @param functionName Name of the action to run * @param inputs Any inputs required by the action * @param queryOptions Various query options for the query */ function boundFunction(apiConfig, entitySet, id, functionName, submitRequest, inputs, queryOptions) { id = parseGuid(id); let queryString = `${entitySet}(${id})/Microsoft.Dynamics.CRM.${functionName}(`; queryString = getFunctionInputs(queryString, inputs); const config = { method: 'GET', contentType: 'application/json; charset=utf-8', queryString: queryString, apiConfig: apiConfig, queryOptions: queryOptions }; return new Promise((resolve, reject) => { submitRequest(config, (result) => { if (result.error) { reject(handleError(result.response)); } else { if (result.response) { resolve(JSON.parse(result.response)); } else { resolve(null); } } }); }); } /** * Execute an unbound function in Dataverse * @param apiConfig WebApiConfig object * @param functionName Name of the action to run * @param inputs Any inputs required by the action * @param queryOptions Various query options for the query */ function unboundFunction(apiConfig, functionName, submitRequest, inputs, queryOptions) { let queryString = `${functionName}(`; queryString = getFunctionInputs(queryString, inputs); const config = { method: 'GET', contentType: 'application/json; charset=utf-8', queryString: queryString, apiConfig: apiConfig, queryOptions: queryOptions }; return new Promise((resolve, reject) => { submitRequest(config, (result) => { if (result.error) { reject(handleError(result.response)); } else { if (result.response) { resolve(JSON.parse(result.response)); } else { resolve(null); } } }); }); } /** * Execute a batch operation in Dataverse * @param apiConfig WebApiConfig object * @param batchId Unique batch id for the operation * @param changeSetId Unique change set id for any changesets in the operation * @param changeSets Array of change sets (create or update) for the operation * @param batchGets Array of get requests for the operation * @param queryOptions Various query options for the query */ function batchOperation(apiConfig, batchId, changeSetId, changeSets, batchGets, submitRequest, queryOptions) { // build post body const body = []; if (changeSets.length > 0) { body.push(`--batch_${batchId}`); body.push(`Content-Type: multipart/mixed;boundary=changeset_${changeSetId}`); body.push(''); } const relative = `/api/data/v${apiConfig.version}`; // push change sets to body for (let i = 0; i < changeSets.length; i++) { body.push(`--changeset_${changeSetId}`); body.push('Content-Type: application/http'); body.push('Content-Transfer-Encoding:binary'); body.push(`Content-ID: ${i + 1}`); body.push(''); body.push(`${changeSets[i].method} ${relative}/${changeSets[i].queryString} HTTP/1.1`); body.push('Content-Type: application/json;type=entry'); body.push(''); body.push(JSON.stringify(changeSets[i].entity)); } if (changeSets.length > 0) { body.push(`--changeset_${changeSetId}--`); body.push(''); } // push get requests to body for (const get of batchGets) { body.push(`--batch_${batchId}`); body.push('Content-Type: application/http'); body.push('Content-Transfer-Encoding:binary'); body.push(''); body.push(`GET ${relative}/${get} HTTP/1.1`); body.push('Accept: application/json'); body.push(''); } if (batchGets.length > 0) { body.push(''); } body.push(`--batch_${batchId}--`); const config = { method: 'POST', contentType: `multipart/mixed;boundary=batch_${batchId}`, queryString: '$batch', body: body.join('\r\n'), apiConfig: apiConfig, queryOptions: queryOptions }; return new Promise((resolve, reject) => { submitRequest(config, (result) => { if (result.error) { const response = parseBatchResponse(result.response); reject(response[0].error.error); } else { if (result.response) { const response = parseBatchResponse(result.response); resolve(response); } else { resolve(null); } } }); }); }