@onehat/data 1.22.13 → 1.22.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/Property/Currency.js +1 -1
- package/src/Repository/Ajax.js +155 -17
- package/src/Repository/OneBuild.js +25 -6
- package/src/Repository/Repository.js +1 -0
package/package.json
CHANGED
package/src/Property/Currency.js
CHANGED
package/src/Repository/Ajax.js
CHANGED
|
@@ -100,6 +100,11 @@ class AjaxRepository extends Repository {
|
|
|
100
100
|
* @private
|
|
101
101
|
*/
|
|
102
102
|
isOnline: true,
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* @member {boolean} disableLimitToOnlyOneLoadRequest - If true, disables automatic cancellation of duplicate load requests
|
|
106
|
+
*/
|
|
107
|
+
disableLimitToOnlyOneLoadRequest: false,
|
|
103
108
|
|
|
104
109
|
};
|
|
105
110
|
_.merge(this, defaults, config);
|
|
@@ -119,6 +124,12 @@ class AjaxRepository extends Repository {
|
|
|
119
124
|
* @private
|
|
120
125
|
*/
|
|
121
126
|
this._params = {};
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* @member {Map} _activeLoadRequests - Map of active requests keyed by URL
|
|
130
|
+
* @private
|
|
131
|
+
*/
|
|
132
|
+
this._activeLoadRequests = new Map();
|
|
122
133
|
|
|
123
134
|
this._operations = {
|
|
124
135
|
add: false,
|
|
@@ -316,7 +327,23 @@ class AjaxRepository extends Repository {
|
|
|
316
327
|
if (!this.hasBaseParam(name)) {
|
|
317
328
|
return null;
|
|
318
329
|
}
|
|
319
|
-
|
|
330
|
+
|
|
331
|
+
// Handle simple property access
|
|
332
|
+
if (this._baseParams.hasOwnProperty(name)) {
|
|
333
|
+
return this._baseParams[name];
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Handle array notation like "conditions[fleets__enterprise_id]"
|
|
337
|
+
const keys = name.split(/[\[\].]+/).filter(Boolean);
|
|
338
|
+
let current = this._baseParams;
|
|
339
|
+
|
|
340
|
+
for(const key of keys) {
|
|
341
|
+
if (!current || !current.hasOwnProperty(key)) {
|
|
342
|
+
return null;
|
|
343
|
+
}
|
|
344
|
+
current = current[key];
|
|
345
|
+
}
|
|
346
|
+
return current;
|
|
320
347
|
}
|
|
321
348
|
|
|
322
349
|
/**
|
|
@@ -332,7 +359,22 @@ class AjaxRepository extends Repository {
|
|
|
332
359
|
* @param {string} name - Param name
|
|
333
360
|
*/
|
|
334
361
|
hasParam(name) {
|
|
335
|
-
|
|
362
|
+
if (this._params.hasOwnProperty(name)) {
|
|
363
|
+
return true;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// Check for array notation
|
|
367
|
+
const keys = name.split(/[\[\].]+/).filter(Boolean);
|
|
368
|
+
let current = this._params,
|
|
369
|
+
key;
|
|
370
|
+
|
|
371
|
+
for(key of keys) {
|
|
372
|
+
if (!current || !current.hasOwnProperty(key)) {
|
|
373
|
+
return false;
|
|
374
|
+
}
|
|
375
|
+
current = current[key];
|
|
376
|
+
}
|
|
377
|
+
return true;
|
|
336
378
|
}
|
|
337
379
|
|
|
338
380
|
/**
|
|
@@ -439,6 +481,30 @@ class AjaxRepository extends Repository {
|
|
|
439
481
|
this.throwError('No "get" api endpoint defined.');
|
|
440
482
|
return;
|
|
441
483
|
}
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
if (!_.isNil(params) && _.isObject(params)) {
|
|
488
|
+
this.setParams(params);
|
|
489
|
+
}
|
|
490
|
+
const
|
|
491
|
+
url = this.getModel() + '/' + this.api.get,
|
|
492
|
+
data = _.merge({}, this._baseParams, this._params),
|
|
493
|
+
requestKey = this._generateRequestKey(url, data);
|
|
494
|
+
|
|
495
|
+
if (!this.disableLimitToOnlyOneLoadRequest && !this.isUnique) {
|
|
496
|
+
if (this._activeLoadRequests.has(requestKey)) {
|
|
497
|
+
// Identical request already in progress, ignore this one
|
|
498
|
+
if (this.debugMode) {
|
|
499
|
+
console.log('Ignoring duplicate load request for', url, data);
|
|
500
|
+
}
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Cancel any existing request for the same URL but different params
|
|
505
|
+
this._cancelExistingRequestsForUrl(url, requestKey);
|
|
506
|
+
}
|
|
507
|
+
|
|
442
508
|
this.emit('beforeLoad'); // TODO: canceling beforeLoad will cancel the load operation
|
|
443
509
|
this.markLoading();
|
|
444
510
|
|
|
@@ -454,16 +520,9 @@ class AjaxRepository extends Repository {
|
|
|
454
520
|
this.resumeEvents();
|
|
455
521
|
}
|
|
456
522
|
|
|
457
|
-
|
|
458
|
-
this.setParams(params);
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
const
|
|
462
|
-
repository = this,
|
|
463
|
-
url = this.getModel() + '/' + this.api.get,
|
|
464
|
-
data = _.merge({}, this._baseParams, this._params);
|
|
523
|
+
const repository = this;
|
|
465
524
|
|
|
466
|
-
|
|
525
|
+
return this._send(this.methods.get, url, data, { isLoadRequest: true, requestKey, })
|
|
467
526
|
.then(result => {
|
|
468
527
|
if (this.debugMode) {
|
|
469
528
|
console.log('Response for ' + this.name, result);
|
|
@@ -519,9 +578,69 @@ class AjaxRepository extends Repository {
|
|
|
519
578
|
})
|
|
520
579
|
.finally(() => {
|
|
521
580
|
this.markLoading(false);
|
|
581
|
+
if (!this.disableLimitToOnlyOneLoadRequest && !this.isUnique && requestKey) {
|
|
582
|
+
this._activeLoadRequests.delete(requestKey);
|
|
583
|
+
}
|
|
522
584
|
});
|
|
523
585
|
}
|
|
524
586
|
|
|
587
|
+
/**
|
|
588
|
+
* Generates a unique key for a request based on URL and parameters
|
|
589
|
+
* @param {string} url - The request URL
|
|
590
|
+
* @param {object} data - The request parameters
|
|
591
|
+
* @return {string} requestKey - Unique key for this URL+params combination
|
|
592
|
+
* @private
|
|
593
|
+
*/
|
|
594
|
+
_generateRequestKey(url, data) {
|
|
595
|
+
const sortedData = this._sortObjectDeep(data);
|
|
596
|
+
return url + '::' + JSON.stringify(sortedData);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* Deep sorts an object by keys to ensure consistent hashing
|
|
601
|
+
* @param {object} obj - Object to sort
|
|
602
|
+
* @return {object} sortedObj - Object with keys sorted recursively
|
|
603
|
+
* @private
|
|
604
|
+
*/
|
|
605
|
+
_sortObjectDeep(obj) {
|
|
606
|
+
if (_.isArray(obj)) {
|
|
607
|
+
return obj.map(item => this._sortObjectDeep(item));
|
|
608
|
+
} else if (_.isPlainObject(obj)) {
|
|
609
|
+
const sortedObj = {};
|
|
610
|
+
const sortedKeys = Object.keys(obj).sort();
|
|
611
|
+
for (const key of sortedKeys) {
|
|
612
|
+
sortedObj[key] = this._sortObjectDeep(obj[key]);
|
|
613
|
+
}
|
|
614
|
+
return sortedObj;
|
|
615
|
+
}
|
|
616
|
+
return obj;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/**
|
|
620
|
+
* Cancels any existing requests for the same URL but different params
|
|
621
|
+
* @param {string} url - The request URL
|
|
622
|
+
* @param {string} currentRequestKey - The current request key to exclude from cancellation
|
|
623
|
+
* @private
|
|
624
|
+
*/
|
|
625
|
+
_cancelExistingRequestsForUrl(url, currentRequestKey) {
|
|
626
|
+
const keysToCancel = [];
|
|
627
|
+
|
|
628
|
+
// Find all requests for the same URL but different params
|
|
629
|
+
for (const [requestKey, requestInfo] of this._activeLoadRequests.entries()) {
|
|
630
|
+
if (requestKey !== currentRequestKey && requestKey.startsWith(url + '::')) {
|
|
631
|
+
keysToCancel.push(requestKey);
|
|
632
|
+
if (requestInfo.controller) {
|
|
633
|
+
requestInfo.controller.abort();
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// Remove cancelled requests from tracking
|
|
639
|
+
keysToCancel.forEach(key => {
|
|
640
|
+
this._activeLoadRequests.delete(key);
|
|
641
|
+
});
|
|
642
|
+
}
|
|
643
|
+
|
|
525
644
|
showMore(params = {}, callback) {
|
|
526
645
|
params.showMore = true;
|
|
527
646
|
return this.load(params, callback);
|
|
@@ -1009,7 +1128,7 @@ class AjaxRepository extends Repository {
|
|
|
1009
1128
|
* Fires off axios request to server
|
|
1010
1129
|
* @private
|
|
1011
1130
|
*/
|
|
1012
|
-
_send(method, url, data) {
|
|
1131
|
+
_send(method, url, data, options = {}) {
|
|
1013
1132
|
|
|
1014
1133
|
if (!url) {
|
|
1015
1134
|
this.throwError('No url submitted');
|
|
@@ -1021,12 +1140,19 @@ class AjaxRepository extends Repository {
|
|
|
1021
1140
|
return;
|
|
1022
1141
|
}
|
|
1023
1142
|
|
|
1143
|
+
const controller = new AbortController();
|
|
1144
|
+
const { signal } = controller;
|
|
1145
|
+
|
|
1146
|
+
if (options.isLoadRequest && !this.disableLimitToOnlyOneLoadRequest && !this.isUnique && options.requestKey) {
|
|
1147
|
+
this._activeLoadRequests.set(options.requestKey, { controller });
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1024
1150
|
const headers = _.merge({
|
|
1025
1151
|
'Content-Type': 'application/json',
|
|
1026
1152
|
'Accept': 'application/json',
|
|
1027
|
-
}, this.headers);
|
|
1153
|
+
}, this.headers, options.headers);
|
|
1028
1154
|
|
|
1029
|
-
const
|
|
1155
|
+
const axiosOptions = {
|
|
1030
1156
|
url,
|
|
1031
1157
|
method,
|
|
1032
1158
|
baseURL: this.api.baseURL,
|
|
@@ -1035,22 +1161,34 @@ class AjaxRepository extends Repository {
|
|
|
1035
1161
|
params: method === 'GET' ? data : null,
|
|
1036
1162
|
data: method !== 'GET' ? qs.stringify(data) : null,
|
|
1037
1163
|
timeout: this.timeout,
|
|
1164
|
+
signal,
|
|
1038
1165
|
};
|
|
1039
1166
|
|
|
1040
1167
|
if (this.debugMode) {
|
|
1041
|
-
console.log(url,
|
|
1168
|
+
console.log(url, axiosOptions);
|
|
1042
1169
|
}
|
|
1043
1170
|
|
|
1044
|
-
this.lastSendOptions =
|
|
1171
|
+
this.lastSendOptions = axiosOptions;
|
|
1045
1172
|
|
|
1046
|
-
return this.axios(
|
|
1173
|
+
return this.axios(axiosOptions)
|
|
1047
1174
|
.catch(error => {
|
|
1175
|
+
// Don't log or throw error if request was aborted
|
|
1176
|
+
if (error.name === 'AbortError' || error.code === 'ERR_CANCELED') {
|
|
1177
|
+
return Promise.reject(new Error('Request cancelled'));
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1048
1180
|
if (this.debugMode) {
|
|
1049
1181
|
console.log(url + ' error', error);
|
|
1050
1182
|
console.log('response:', error.response);
|
|
1051
1183
|
}
|
|
1052
1184
|
this.throwError(error);
|
|
1053
1185
|
return;
|
|
1186
|
+
})
|
|
1187
|
+
.finally(() => {
|
|
1188
|
+
// Clean up tracking for GET requests
|
|
1189
|
+
if (options.isLoadRequest && !this.disableLimitToOnlyOneLoadRequest && !this.isUnique && options.requestKey) {
|
|
1190
|
+
this._activeLoadRequests.delete(options.requestKey);
|
|
1191
|
+
}
|
|
1054
1192
|
});
|
|
1055
1193
|
}
|
|
1056
1194
|
|
|
@@ -97,7 +97,7 @@ class OneBuildRepository extends AjaxRepository {
|
|
|
97
97
|
* Fires off axios request to server
|
|
98
98
|
* @private
|
|
99
99
|
*/
|
|
100
|
-
_send(method, url, data,
|
|
100
|
+
_send(method, url, data, options = {}) {
|
|
101
101
|
|
|
102
102
|
if (!url) {
|
|
103
103
|
this.throwError('No url submitted');
|
|
@@ -109,12 +109,20 @@ class OneBuildRepository extends AjaxRepository {
|
|
|
109
109
|
return;
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
// Create AbortController for this request
|
|
113
|
+
const controller = new AbortController();
|
|
114
|
+
const { signal } = controller;
|
|
115
|
+
|
|
116
|
+
if (options.isLoadRequest && !this.disableLimitToOnlyOneLoadRequest && !this.isUnique && options.requestKey) {
|
|
117
|
+
this._activeLoadRequests.set(options.requestKey, { controller });
|
|
118
|
+
}
|
|
119
|
+
|
|
112
120
|
const mergedHeaders = _.merge({
|
|
113
121
|
// 'Content-Type': 'application/json', // Stops axios from using 'application/x-www-form-urlencoded'
|
|
114
122
|
Accept: 'application/json',
|
|
115
|
-
}, this.headers, headers);
|
|
123
|
+
}, this.headers, options.headers);
|
|
116
124
|
|
|
117
|
-
const
|
|
125
|
+
const axiosOptions = {
|
|
118
126
|
url,
|
|
119
127
|
method,
|
|
120
128
|
baseURL: this.api.baseURL,
|
|
@@ -123,16 +131,22 @@ class OneBuildRepository extends AjaxRepository {
|
|
|
123
131
|
params: method === 'GET' ? data : null,
|
|
124
132
|
data: method !== 'GET' ? qs.stringify(data) : null,
|
|
125
133
|
timeout: this.timeout,
|
|
134
|
+
signal,
|
|
126
135
|
};
|
|
127
136
|
|
|
128
137
|
if (this.debugMode) {
|
|
129
|
-
console.log('Sending ' + url,
|
|
138
|
+
console.log('Sending ' + url, axiosOptions);
|
|
130
139
|
}
|
|
131
140
|
|
|
132
|
-
this.lastSendOptions =
|
|
141
|
+
this.lastSendOptions = axiosOptions;
|
|
133
142
|
|
|
134
|
-
return this.axios(
|
|
143
|
+
return this.axios(axiosOptions)
|
|
135
144
|
.catch(error => {
|
|
145
|
+
// Don't log or throw error if request was aborted
|
|
146
|
+
if (error.name === 'AbortError' || error.code === 'ERR_CANCELED') {
|
|
147
|
+
return Promise.reject(new Error('Request cancelled'));
|
|
148
|
+
}
|
|
149
|
+
|
|
136
150
|
if (this.debugMode) {
|
|
137
151
|
console.log(url + ' error', error);
|
|
138
152
|
console.log('response:', error.response);
|
|
@@ -147,6 +161,11 @@ class OneBuildRepository extends AjaxRepository {
|
|
|
147
161
|
|
|
148
162
|
this.throwError(error);
|
|
149
163
|
return;
|
|
164
|
+
})
|
|
165
|
+
.finally(() => {
|
|
166
|
+
if (options.isLoadRequest && !this.disableLimitToOnlyOneLoadRequest && !this.isUnique && options.requestKey) {
|
|
167
|
+
this._activeLoadRequests.delete(options.requestKey);
|
|
168
|
+
}
|
|
150
169
|
});
|
|
151
170
|
}
|
|
152
171
|
|