Skip to content

Commit 08d9cf0

Browse files
add new request params: partitionid and parameter aliases
1 parent e8750ea commit 08d9cf0

File tree

11 files changed

+21188
-12323
lines changed

11 files changed

+21188
-12323
lines changed

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ import DynamicsWebApi from 'dynamics-web-api';
136136
```
137137

138138
At this moment DynamicsWebApi does not fetch authorization tokens, so you will need to acquire OAuth token in your code and pass it to the DynamicsWebApi.
139-
Token can be aquired using [MSAL for JS](https://github.com/AzureAD/microsoft-authentication-library-for-js) or you can write your own functionality, as it is described [here](http://alexanderdevelopment.net/post/2016/11/23/dynamics-365-and-node-js-integration-using-the-web-api/).
139+
Token can be acquired using [MSAL for JS](https://github.com/AzureAD/microsoft-authentication-library-for-js) or you can write your own functionality, as it is described [here](http://alexanderdevelopment.net/post/2016/11/23/dynamics-365-and-node-js-integration-using-the-web-api/).
140140

141141
Here is a sample using `@azure/msal-node`:
142142

@@ -286,6 +286,8 @@ navigationProperty | String | `retrieveRequest`, `createRequest`, `updateRequest
286286
navigationPropertyKey | String | `retrieveRequest`, `createRequest`, `updateRequest` | `v.1.4.3+` A String representing navigation property's Primary Key (GUID) or Alternate Key(s). (For example, to retrieve Attribute Metadata)
287287
noCache | Boolean | All | `v.1.4.0+` If set to `true`, DynamicsWebApi adds a request header `Cache-Control: no-cache`. Default value is `false`.
288288
orderBy | Array | `retrieveMultipleRequest`, `retrieveAllRequest` | An Array (of Strings) representing the order in which items are returned using the $orderby system query option. Use the asc or desc suffix to specify ascending or descending order respectively. The default is ascending if the suffix isn't applied.
289+
partitionId | String | `createRequest`, `updateRequest`, `upsertRequest`, `deleteRequest`, `retrieveRequest`, `retrieveMultipleRequest` | `v.1.7.7+` Sets a unique partition key value of a logical partition for non-relational custom entity data stored in NoSql tables of Azure heterogenous storage. [More Info](https://docs.microsoft.com/en-us/power-apps/developer/data-platform/webapi/azure-storage-partitioning)
290+
queryParams | Array | `retrieveMultipleRequest`, `retrieveAllRequest` | `v.1.7.7+` Additional query parameters that either have not been implemented yet or they are [parameter aliases](https://docs.microsoft.com/en-us/power-apps/developer/data-platform/webapi/query-data-web-api#use-parameter-aliases-with-system-query-options) for "$filter" and "$orderBy". **Important!** These parameters ARE NOT URI encoded!
289291
returnRepresentation | Boolean | `createRequest`, `updateRequest`, `upsertRequest` | Sets Prefer header request with value "return=representation". Use this property to return just created or updated entity in a single request.
290292
savedQuery | String | `retrieveRequest` | A String representing the GUID value of the saved query.
291293
select | Array | `retrieveRequest`, `retrieveMultipleRequest`, `retrieveAllRequest`, `updateRequest`, `upsertRequest` | An Array (of Strings) representing the $select OData System Query Option to control which attributes will be returned.
@@ -2172,6 +2174,7 @@ the config option "formatted" will enable developers to retrieve all information
21722174
- [X] Shrink size of an NPM package. `Added in v.1.7.1`.
21732175
- [X] Full proxy support. `Added in v.1.7.2`.
21742176
- [ ] Refactoring and conversion to TypeScript - coming with `v.2.0`! Stay tuned!
2177+
- [ ] Implement [Dataverse Search API](https://docs.microsoft.com/en-us/power-apps/developer/data-platform/webapi/relevance-search).
21752178

21762179
Many more features to come!
21772180

Lines changed: 84 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
var DWA = require('../../dwa');
2-
var Utility = require('../../utilities/Utility');
3-
var ErrorHelper = require('../../helpers/ErrorHelper');
4-
var dateReviver = require('./dateReviver');
1+
var DWA = require("../../dwa");
2+
var Utility = require("../../utilities/Utility");
3+
var ErrorHelper = require("../../helpers/ErrorHelper");
4+
var dateReviver = require("./dateReviver");
55

66
//string es6 polyfill
77
if (!String.prototype.endsWith || !String.prototype.startsWith) {
@@ -10,40 +10,30 @@ if (!String.prototype.endsWith || !String.prototype.startsWith) {
1010

1111
function getFormattedKeyValue(keyName, value) {
1212
var newKey = null;
13-
if (keyName.indexOf('@') !== -1) {
14-
var format = keyName.split('@');
13+
if (keyName.indexOf("@") !== -1) {
14+
var format = keyName.split("@");
1515
switch (format[1]) {
16-
case 'odata.context':
17-
newKey = 'oDataContext';
16+
case "odata.context":
17+
newKey = "oDataContext";
1818
break;
19-
case 'odata.count':
20-
newKey = 'oDataCount';
21-
value = value != null
22-
? parseInt(value)
23-
: 0;
19+
case "odata.count":
20+
newKey = "oDataCount";
21+
value = value != null ? parseInt(value) : 0;
2422
break;
25-
case '@Microsoft.Dynamics.CRM.totalrecordcount':
26-
newKey = "microsoftTotalRecordCount";
27-
value = value === "true";
23+
case "odata.nextLink":
24+
newKey = "oDataNextLink";
2825
break;
29-
case '@Microsoft.Dynamics.CRM.totalrecordcountlimitexceeded':
30-
newKey = "microsoftTotalRecordCountLimitExceeded";
31-
value = value === "true";
32-
break;
33-
case 'odata.nextLink':
34-
newKey = 'oDataNextLink';
35-
break;
36-
case 'odata.deltaLink':
37-
newKey = 'oDataDeltaLink';
26+
case "odata.deltaLink":
27+
newKey = "oDataDeltaLink";
3828
break;
3929
case DWA.Prefer.Annotations.FormattedValue:
40-
newKey = format[0] + '_Formatted';
30+
newKey = format[0] + "_Formatted";
4131
break;
4232
case DWA.Prefer.Annotations.AssociatedNavigationProperty:
43-
newKey = format[0] + '_NavigationProperty';
33+
newKey = format[0] + "_NavigationProperty";
4434
break;
4535
case DWA.Prefer.Annotations.LookupLogicalName:
46-
newKey = format[0] + '_LogicalName';
36+
newKey = format[0] + "_LogicalName";
4737
break;
4838
}
4939
}
@@ -64,7 +54,7 @@ function parseData(object, parseParams) {
6454
}
6555

6656
if (parseParams.toCount) {
67-
return getFormattedKeyValue('@odata.count', object['@odata.count'])[1] || 0;
57+
return getFormattedKeyValue("@odata.count", object["@odata.count"])[1] || 0;
6858
}
6959
}
7060

@@ -78,8 +68,7 @@ function parseData(object, parseParams) {
7868
for (var j = 0; j < object[currentKey].length; j++) {
7969
object[currentKey][j] = parseData(object[currentKey][j]);
8070
}
81-
}
82-
else if (typeof (object[currentKey]) === "object") {
71+
} else if (typeof object[currentKey] === "object") {
8372
parseData(object[currentKey]);
8473
}
8574
}
@@ -91,17 +80,18 @@ function parseData(object, parseParams) {
9180
}
9281

9382
//parse aliased values
94-
if (currentKey.indexOf('_x002e_') !== -1) {
95-
var aliasKeys = currentKey.split('_x002e_');
83+
if (currentKey.indexOf("_x002e_") !== -1) {
84+
var aliasKeys = currentKey.split("_x002e_");
9685

9786
if (!object.hasOwnProperty(aliasKeys[0])) {
98-
object[aliasKeys[0]] = { _dwaType: 'alias' };
87+
object[aliasKeys[0]] = { _dwaType: "alias" };
9988
}
10089
//throw an error if there is already a property which is not an 'alias'
10190
else if (
102-
typeof object[aliasKeys[0]] !== 'object' ||
103-
typeof object[aliasKeys[0]] === 'object' && !object[aliasKeys[0]].hasOwnProperty('_dwaType')) {
104-
throw new Error('The alias name of the linked entity must be unique!');
91+
typeof object[aliasKeys[0]] !== "object" ||
92+
(typeof object[aliasKeys[0]] === "object" && !object[aliasKeys[0]].hasOwnProperty("_dwaType"))
93+
) {
94+
throw new Error("The alias name of the linked entity must be unique!");
10595
}
10696

10797
object[aliasKeys[0]][aliasKeys[1]] = object[currentKey];
@@ -115,8 +105,8 @@ function parseData(object, parseParams) {
115105
}
116106

117107
if (parseParams) {
118-
if (parseParams.hasOwnProperty('pageNumber') && object['@' + DWA.Prefer.Annotations.FetchXmlPagingCookie] != null) {
119-
object.PagingInfo = Utility.getFetchXmlPagingCookie(object['@' + DWA.Prefer.Annotations.FetchXmlPagingCookie], parseParams.pageNumber);
108+
if (parseParams.hasOwnProperty("pageNumber") && object["@" + DWA.Prefer.Annotations.FetchXmlPagingCookie] != null) {
109+
object.PagingInfo = Utility.getFetchXmlPagingCookie(object["@" + DWA.Prefer.Annotations.FetchXmlPagingCookie], parseParams.pageNumber);
120110
}
121111
}
122112

@@ -139,8 +129,7 @@ function parseBatchHeaders(text) {
139129
parts = responseHeaderRegex.exec(line);
140130
if (parts !== null) {
141131
headers[parts[1].toLowerCase()] = parts[2];
142-
}
143-
else {
132+
} else {
144133
// Whatever was found is not a header, so reset the context position.
145134
ctx.position = pos;
146135
}
@@ -182,7 +171,7 @@ function readTo(text, ctx, str) {
182171
function parseBatchResponse(response, parseParams, requestNumber) {
183172
// Not the same delimiter in the response as we specify ourselves in the request,
184173
// so we have to extract it.
185-
var delimiter = response.substr(0, response.indexOf('\r\n'));
174+
var delimiter = response.substr(0, response.indexOf("\r\n"));
186175
var batchResponseParts = response.split(delimiter);
187176
// The first part will always be an empty string. Just remove it.
188177
batchResponseParts.shift();
@@ -194,14 +183,12 @@ function parseBatchResponse(response, parseParams, requestNumber) {
194183
var result = [];
195184
for (var i = 0; i < batchResponseParts.length; i++) {
196185
var batchResponse = batchResponseParts[i];
197-
if (batchResponse.indexOf('--changesetresponse_') > -1) {
186+
if (batchResponse.indexOf("--changesetresponse_") > -1) {
198187
batchResponse = batchResponse.trim();
199-
var batchToProcess = batchResponse
200-
.substring(batchResponse.indexOf('\r\n') + 1).trim();
188+
var batchToProcess = batchResponse.substring(batchResponse.indexOf("\r\n") + 1).trim();
201189

202190
result = result.concat(parseBatchResponse(batchToProcess, parseParams, requestNumber));
203-
}
204-
else {
191+
} else {
205192
//check http status
206193
var httpStatusReg = /HTTP\/?\s*[\d.]*\s+(\d{3})\s+([\w\s]*)$/gm.exec(batchResponse);
207194
var httpStatus = parseInt(httpStatusReg[1]);
@@ -216,38 +203,36 @@ function parseBatchResponse(response, parseParams, requestNumber) {
216203

217204
//check if a plain content is a number or not
218205
result.push(isNaN(plainContent) ? plainContent : parseInt(plainContent));
219-
}
220-
else
221-
if (parseParams.length && parseParams[requestNumber] && parseParams[requestNumber].hasOwnProperty('valueIfEmpty')) {
222-
result.push(parseParams[requestNumber].valueIfEmpty);
223-
}
224-
else {
225-
var entityUrl = /OData-EntityId.+/i.exec(batchResponse);
206+
} else if (parseParams.length && parseParams[requestNumber] && parseParams[requestNumber].hasOwnProperty("valueIfEmpty")) {
207+
result.push(parseParams[requestNumber].valueIfEmpty);
208+
} else {
209+
var entityUrl = /OData-EntityId.+/i.exec(batchResponse);
226210

227-
if (entityUrl && entityUrl.length) {
228-
var guidResult = /([0-9A-F]{8}[-]?([0-9A-F]{4}[-]?){3}[0-9A-F]{12})\)$/i.exec(entityUrl[0]);
211+
if (entityUrl && entityUrl.length) {
212+
var guidResult = /([0-9A-F]{8}[-]?([0-9A-F]{4}[-]?){3}[0-9A-F]{12})\)$/i.exec(entityUrl[0]);
229213

230-
result.push(guidResult ? guidResult[1] : undefined);
231-
}
232-
else {
233-
result.push(undefined);
234-
}
214+
result.push(guidResult ? guidResult[1] : undefined);
215+
} else {
216+
result.push(undefined);
235217
}
236-
}
237-
else {
218+
}
219+
} else {
238220
var parsedResponse = parseData(JSON.parse(responseData, dateReviver), parseParams[requestNumber]);
239221

240222
if (httpStatus >= 400) {
241-
var responseHeaders = parseBatchHeaders(batchResponse.substring(batchResponse.indexOf(httpStatusReg[0]) + httpStatusReg[0].length + 1, batchResponse.indexOf("{")));
242-
243-
result.push(ErrorHelper.handleHttpError(parsedResponse, {
244-
status: httpStatus,
245-
statusText: httpStatusMessage,
246-
statusMessage: httpStatusMessage,
247-
headers: responseHeaders
248-
}));
249-
}
250-
else {
223+
var responseHeaders = parseBatchHeaders(
224+
batchResponse.substring(batchResponse.indexOf(httpStatusReg[0]) + httpStatusReg[0].length + 1, batchResponse.indexOf("{"))
225+
);
226+
227+
result.push(
228+
ErrorHelper.handleHttpError(parsedResponse, {
229+
status: httpStatus,
230+
statusText: httpStatusMessage,
231+
statusMessage: httpStatusMessage,
232+
headers: responseHeaders,
233+
})
234+
);
235+
} else {
251236
result.push(parsedResponse);
252237
}
253238
}
@@ -263,32 +248,28 @@ function base64ToString(base64) {
263248
/* develblock:start */
264249
if (typeof process !== "undefined") {
265250
return Buffer.from(base64, "base64").toString("binary");
266-
}
267-
else if (typeof window !== "undefined")
251+
} else if (typeof window !== "undefined")
268252
/* develblock:end */
269253
return window.atob(base64);
270254
}
271255

272256
function parseFileResponse(response, responseHeaders, parseParams) {
273257
var data = response;
274258

275-
if (parseParams.hasOwnProperty('parse')) {
259+
if (parseParams.hasOwnProperty("parse")) {
276260
data = JSON.parse(data).value;
277261
data = base64ToString(data);
278262
}
279263

280264
var parseResult = {
281-
value: data
265+
value: data,
282266
};
283267

284-
if (responseHeaders['x-ms-file-name'])
285-
parseResult.fileName = responseHeaders['x-ms-file-name'];
268+
if (responseHeaders["x-ms-file-name"]) parseResult.fileName = responseHeaders["x-ms-file-name"];
286269

287-
if (responseHeaders['x-ms-file-size'])
288-
parseResult.fileSize = parseInt(responseHeaders['x-ms-file-size']);
270+
if (responseHeaders["x-ms-file-size"]) parseResult.fileSize = parseInt(responseHeaders["x-ms-file-size"]);
289271

290-
if (hasHeader(responseHeaders, 'Location'))
291-
parseResult.location = getHeader(responseHeaders, 'Location');
272+
if (hasHeader(responseHeaders, "Location")) parseResult.location = getHeader(responseHeaders, "Location");
292273

293274
return parseResult;
294275
}
@@ -298,8 +279,7 @@ function hasHeader(headers, name) {
298279
}
299280

300281
function getHeader(headers, name) {
301-
if (headers[name])
302-
return headers[name];
282+
if (headers[name]) return headers[name];
303283

304284
return headers[name.toLowerCase()];
305285
}
@@ -314,47 +294,38 @@ function getHeader(headers, name) {
314294
module.exports = function parseResponse(response, responseHeaders, parseParams) {
315295
var parseResult = undefined;
316296
if (response.length) {
317-
if (response.indexOf('--batchresponse_') > -1) {
297+
if (response.indexOf("--batchresponse_") > -1) {
318298
var batch = parseBatchResponse(response, parseParams);
319299

320-
parseResult = parseParams.length === 1 && parseParams[0].convertedToBatch
321-
? batch[0]
322-
: batch;
323-
}
324-
else {
325-
if (hasHeader(responseHeaders, 'Content-Disposition')) {
300+
parseResult = parseParams.length === 1 && parseParams[0].convertedToBatch ? batch[0] : batch;
301+
} else {
302+
if (hasHeader(responseHeaders, "Content-Disposition")) {
326303
parseResult = parseFileResponse(response, responseHeaders, parseParams[0]);
327-
}
328-
else {
304+
} else {
329305
parseResult = parseData(JSON.parse(response, dateReviver), parseParams[0]);
330306
}
331307
}
332-
}
333-
else {
334-
if (parseParams.length && parseParams[0].hasOwnProperty('valueIfEmpty')) {
308+
} else {
309+
if (parseParams.length && parseParams[0].hasOwnProperty("valueIfEmpty")) {
335310
parseResult = parseParams[0].valueIfEmpty;
336-
}
337-
else
338-
if (hasHeader(responseHeaders, 'OData-EntityId')) {
339-
var entityUrl = getHeader(responseHeaders, 'OData-EntityId');
311+
} else if (hasHeader(responseHeaders, "OData-EntityId")) {
312+
var entityUrl = getHeader(responseHeaders, "OData-EntityId");
340313

341-
var guidResult = /([0-9A-F]{8}[-]?([0-9A-F]{4}[-]?){3}[0-9A-F]{12})\)$/i.exec(entityUrl);
314+
var guidResult = /([0-9A-F]{8}[-]?([0-9A-F]{4}[-]?){3}[0-9A-F]{12})\)$/i.exec(entityUrl);
342315

343-
if (guidResult) {
344-
parseResult = guidResult[1];
345-
}
316+
if (guidResult) {
317+
parseResult = guidResult[1];
346318
}
347-
else if (hasHeader(responseHeaders, 'Location')) {
348-
parseResult = {
349-
location: getHeader(responseHeaders, 'Location')
350-
}
319+
} else if (hasHeader(responseHeaders, "Location")) {
320+
parseResult = {
321+
location: getHeader(responseHeaders, "Location"),
322+
};
351323

352-
if (responseHeaders['x-ms-chunk-size'])
353-
parseResult.chunkSize = parseInt(responseHeaders['x-ms-chunk-size']);
354-
}
324+
if (responseHeaders["x-ms-chunk-size"]) parseResult.chunkSize = parseInt(responseHeaders["x-ms-chunk-size"]);
325+
}
355326
}
356327

357328
//parseParams.length = 0;
358329

359330
return parseResult;
360-
}
331+
};

lib/utilities/RequestConverter.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,11 @@ function convertRequestOptions(request, functionName, url, joinSymbol, config) {
123123
requestArray.push("$orderby=" + request.orderBy.join(","));
124124
}
125125

126+
if (request.partitionId) {
127+
ErrorHelper.stringParameterCheck(request.partitionId, "DynamicsWebApi." + functionName, "request.partitionId");
128+
requestArray.push("partitionid='" + request.partitionId + "'");
129+
}
130+
126131
if (request.downloadSize) {
127132
ErrorHelper.stringParameterCheck(request.downloadSize, "DynamicsWebApi." + functionName, "request.downloadSize");
128133
requestArray.push("size=" + request.downloadSize);
@@ -133,6 +138,11 @@ function convertRequestOptions(request, functionName, url, joinSymbol, config) {
133138
requestArray.push("x-ms-file-name=" + request.fileName);
134139
}
135140

141+
if (request.queryParams != null && request.queryParams.length) {
142+
ErrorHelper.arrayParameterCheck(request.queryParams, "DynamicsWebApi." + functionName, "request.queryParams");
143+
requestArray.push(request.queryParams.join("&"));
144+
}
145+
136146
var prefer = buildPreferHeader(request, functionName, config);
137147

138148
if (prefer.length) {

0 commit comments

Comments
 (0)