@onehat/data 1.22.15 → 1.22.17
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/Repository/Ajax.js +122 -15
- package/src/Repository/OneBuild.js +25 -6
- package/src/Repository/Repository.js +1 -0
- package/src/Schema/Schema.js +3 -3
package/package.json
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,
|
|
@@ -470,6 +481,30 @@ class AjaxRepository extends Repository {
|
|
|
470
481
|
this.throwError('No "get" api endpoint defined.');
|
|
471
482
|
return;
|
|
472
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
|
+
|
|
473
508
|
this.emit('beforeLoad'); // TODO: canceling beforeLoad will cancel the load operation
|
|
474
509
|
this.markLoading();
|
|
475
510
|
|
|
@@ -485,16 +520,9 @@ class AjaxRepository extends Repository {
|
|
|
485
520
|
this.resumeEvents();
|
|
486
521
|
}
|
|
487
522
|
|
|
488
|
-
|
|
489
|
-
this.setParams(params);
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
const
|
|
493
|
-
repository = this,
|
|
494
|
-
url = this.getModel() + '/' + this.api.get,
|
|
495
|
-
data = _.merge({}, this._baseParams, this._params);
|
|
523
|
+
const repository = this;
|
|
496
524
|
|
|
497
|
-
|
|
525
|
+
return this._send(this.methods.get, url, data, { isLoadRequest: true, requestKey, })
|
|
498
526
|
.then(result => {
|
|
499
527
|
if (this.debugMode) {
|
|
500
528
|
console.log('Response for ' + this.name, result);
|
|
@@ -550,9 +578,69 @@ class AjaxRepository extends Repository {
|
|
|
550
578
|
})
|
|
551
579
|
.finally(() => {
|
|
552
580
|
this.markLoading(false);
|
|
581
|
+
if (!this.disableLimitToOnlyOneLoadRequest && !this.isUnique && requestKey) {
|
|
582
|
+
this._activeLoadRequests.delete(requestKey);
|
|
583
|
+
}
|
|
553
584
|
});
|
|
554
585
|
}
|
|
555
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
|
+
|
|
556
644
|
showMore(params = {}, callback) {
|
|
557
645
|
params.showMore = true;
|
|
558
646
|
return this.load(params, callback);
|
|
@@ -1040,7 +1128,7 @@ class AjaxRepository extends Repository {
|
|
|
1040
1128
|
* Fires off axios request to server
|
|
1041
1129
|
* @private
|
|
1042
1130
|
*/
|
|
1043
|
-
_send(method, url, data) {
|
|
1131
|
+
_send(method, url, data, options = {}) {
|
|
1044
1132
|
|
|
1045
1133
|
if (!url) {
|
|
1046
1134
|
this.throwError('No url submitted');
|
|
@@ -1052,12 +1140,19 @@ class AjaxRepository extends Repository {
|
|
|
1052
1140
|
return;
|
|
1053
1141
|
}
|
|
1054
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
|
+
|
|
1055
1150
|
const headers = _.merge({
|
|
1056
1151
|
'Content-Type': 'application/json',
|
|
1057
1152
|
'Accept': 'application/json',
|
|
1058
|
-
}, this.headers);
|
|
1153
|
+
}, this.headers, options.headers);
|
|
1059
1154
|
|
|
1060
|
-
const
|
|
1155
|
+
const axiosOptions = {
|
|
1061
1156
|
url,
|
|
1062
1157
|
method,
|
|
1063
1158
|
baseURL: this.api.baseURL,
|
|
@@ -1066,22 +1161,34 @@ class AjaxRepository extends Repository {
|
|
|
1066
1161
|
params: method === 'GET' ? data : null,
|
|
1067
1162
|
data: method !== 'GET' ? qs.stringify(data) : null,
|
|
1068
1163
|
timeout: this.timeout,
|
|
1164
|
+
signal,
|
|
1069
1165
|
};
|
|
1070
1166
|
|
|
1071
1167
|
if (this.debugMode) {
|
|
1072
|
-
console.log(url,
|
|
1168
|
+
console.log(url, axiosOptions);
|
|
1073
1169
|
}
|
|
1074
1170
|
|
|
1075
|
-
this.lastSendOptions =
|
|
1171
|
+
this.lastSendOptions = axiosOptions;
|
|
1076
1172
|
|
|
1077
|
-
return this.axios(
|
|
1173
|
+
return this.axios(axiosOptions)
|
|
1078
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
|
+
|
|
1079
1180
|
if (this.debugMode) {
|
|
1080
1181
|
console.log(url + ' error', error);
|
|
1081
1182
|
console.log('response:', error.response);
|
|
1082
1183
|
}
|
|
1083
1184
|
this.throwError(error);
|
|
1084
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
|
+
}
|
|
1085
1192
|
});
|
|
1086
1193
|
}
|
|
1087
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
|
|
package/src/Schema/Schema.js
CHANGED
|
@@ -75,19 +75,19 @@ export default class Schema extends EventEmitter {
|
|
|
75
75
|
* @member {string} parentIdProperty - name of parent_id Property (e.g. 'categories__parent_id' for Adjacency Lists, and parent_id for Closure Tables)
|
|
76
76
|
* For trees only
|
|
77
77
|
*/
|
|
78
|
-
parentIdProperty:
|
|
78
|
+
parentIdProperty: 'parentId',
|
|
79
79
|
|
|
80
80
|
/**
|
|
81
81
|
* @member {string} depthIdProperty - name of depth Property (e.g. 'categories__depth' for Adjacency Lists, and depth for Closure Tables)
|
|
82
82
|
* For trees only
|
|
83
83
|
*/
|
|
84
|
-
depthProperty:
|
|
84
|
+
depthProperty: 'depth',
|
|
85
85
|
|
|
86
86
|
/**
|
|
87
87
|
* @member {string} hasChildrenProperty - name of hasChildren Property (e.g. 'categories__has_children' for Adjacency Lists, and has_children for Closure Tables)
|
|
88
88
|
* For trees only
|
|
89
89
|
*/
|
|
90
|
-
hasChildrenProperty:
|
|
90
|
+
hasChildrenProperty: 'hasChildren',
|
|
91
91
|
|
|
92
92
|
/**
|
|
93
93
|
* @member {boolean} isAdjacencyList - Whether this tree is an Adjacency List
|