xcraft-core-utils 4.14.2 → 4.16.0
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/lib/arrayCollector.js +28 -4
- package/lib/calendar.js +227 -0
- package/lib/locks.js +4 -0
- package/lib/modules.js +21 -1
- package/lib/rest.js +19 -0
- package/package.json +1 -1
package/lib/arrayCollector.js
CHANGED
|
@@ -4,11 +4,27 @@ const {throttle} = require('lodash');
|
|
|
4
4
|
const watt = require('gigawatts');
|
|
5
5
|
|
|
6
6
|
class ArrayCollector {
|
|
7
|
-
constructor(resp, wait = 20, onCollect, leading = true) {
|
|
8
|
-
this.
|
|
7
|
+
constructor(resp, wait = 20, onCollect, leading = true, async = false) {
|
|
8
|
+
this._async = async;
|
|
9
|
+
if (!async) {
|
|
10
|
+
this.onCollect = watt(onCollect);
|
|
11
|
+
} else {
|
|
12
|
+
this.onCollect = onCollect;
|
|
13
|
+
}
|
|
9
14
|
this.entries = {};
|
|
10
15
|
this.resp = resp;
|
|
11
16
|
this.release = throttle(this._release, wait, {leading});
|
|
17
|
+
this.releaseAsync = throttle(this._releaseAsync, wait, {leading});
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async _releaseAsync() {
|
|
21
|
+
const copy = this.entries;
|
|
22
|
+
this.entries = {};
|
|
23
|
+
try {
|
|
24
|
+
await this.onCollect(copy, this.resp);
|
|
25
|
+
} catch (err) {
|
|
26
|
+
this.resp.log.err(err);
|
|
27
|
+
}
|
|
12
28
|
}
|
|
13
29
|
|
|
14
30
|
_release() {
|
|
@@ -26,11 +42,19 @@ class ArrayCollector {
|
|
|
26
42
|
|
|
27
43
|
grab(key, data) {
|
|
28
44
|
this._addByKey(key, data);
|
|
29
|
-
this.
|
|
45
|
+
if (this._async) {
|
|
46
|
+
this.releaseAsync();
|
|
47
|
+
} else {
|
|
48
|
+
this.release();
|
|
49
|
+
}
|
|
30
50
|
}
|
|
31
51
|
|
|
32
52
|
cancel() {
|
|
33
|
-
this.
|
|
53
|
+
if (this._async) {
|
|
54
|
+
this.releaseAsync.cancel();
|
|
55
|
+
} else {
|
|
56
|
+
this.release.cancel();
|
|
57
|
+
}
|
|
34
58
|
}
|
|
35
59
|
}
|
|
36
60
|
|
package/lib/calendar.js
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @param {string} dateOrDateTime
|
|
5
|
+
*/
|
|
6
|
+
function toPlannerDate(dateOrDateTime) {
|
|
7
|
+
if (!dateOrDateTime) {
|
|
8
|
+
return dateOrDateTime;
|
|
9
|
+
}
|
|
10
|
+
if (dateOrDateTime.includes('T')) {
|
|
11
|
+
// datetime
|
|
12
|
+
return plainDateTimeISO(toJsDate(dateOrDateTime));
|
|
13
|
+
}
|
|
14
|
+
// date
|
|
15
|
+
return dateOrDateTime;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const zonedDateTimeRegex = /^(?<datetime>(?<date>-?([1-9][0-9]{3,}|0[0-9]{3})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01]))T(?<time>([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9](\.[0-9]+)?|(24:00:00(\.0+)?)))(?<suffix>(?<offset>Z|(\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?(\[(?<timezone>!?[a-zA-Z0-9._+/-]+)\])?(?<tags>(\[(!?[a-z0-9_-]=[a-z0-9_-])\])*))$/;
|
|
19
|
+
const numOffsetRegex = /^(\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00)/;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {string} dateOrDateTime
|
|
23
|
+
* @returns {{datetime: string, date: string, time?: string, suffix?: string, offset?: string, timezone?: string, tags?: string}}
|
|
24
|
+
*/
|
|
25
|
+
function parseZonedDateTime(dateOrDateTime) {
|
|
26
|
+
if (!dateOrDateTime.includes('T')) {
|
|
27
|
+
return {datetime: dateOrDateTime, date: dateOrDateTime};
|
|
28
|
+
}
|
|
29
|
+
const match = dateOrDateTime.match(zonedDateTimeRegex);
|
|
30
|
+
if (!match || !match.groups) {
|
|
31
|
+
throw new Error(`Bad date '${dateOrDateTime}'`);
|
|
32
|
+
}
|
|
33
|
+
return /**@type {any} */ (match.groups);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* @param {{date: string, time?: string, offset?: string, timezone?: string, tags?: string}} parts
|
|
38
|
+
* @returns {string}
|
|
39
|
+
*/
|
|
40
|
+
function zonedDateTimeFromParts(parts) {
|
|
41
|
+
const {date, time, offset = '', timezone = null, tags = ''} = parts;
|
|
42
|
+
if (!time) {
|
|
43
|
+
return date;
|
|
44
|
+
}
|
|
45
|
+
return `${date}T${time}${offset}${timezone ? `[${timezone}]` : ''}${tags}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* @param {string} dateOrDateTime
|
|
50
|
+
* @param {boolean} [isEndDate=false]
|
|
51
|
+
*/
|
|
52
|
+
function toJsDate(dateOrDateTime, isEndDate = false) {
|
|
53
|
+
if (!dateOrDateTime.includes('T')) {
|
|
54
|
+
const plainDate = dateOrDateTime;
|
|
55
|
+
if (isEndDate) {
|
|
56
|
+
return new Date(`${plainDate}T24:00:00`);
|
|
57
|
+
}
|
|
58
|
+
return new Date(`${plainDate}T00:00:00`);
|
|
59
|
+
}
|
|
60
|
+
const {datetime, offset = '', timezone} = parseZonedDateTime(dateOrDateTime);
|
|
61
|
+
if (offset === 'Z') {
|
|
62
|
+
return new Date(`${datetime}${offset}`);
|
|
63
|
+
}
|
|
64
|
+
if (timezone) {
|
|
65
|
+
return localDate(datetime, timezone);
|
|
66
|
+
}
|
|
67
|
+
return new Date(`${datetime}${offset}`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @param {string} dateOrDateTime
|
|
72
|
+
* @param {boolean} [isEndDate=false]
|
|
73
|
+
*/
|
|
74
|
+
function getTimestamp(dateOrDateTime, isEndDate = false) {
|
|
75
|
+
return toJsDate(dateOrDateTime, isEndDate).getTime();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* @param {string} dateOrDateTime
|
|
80
|
+
* @returns {string | null}
|
|
81
|
+
*/
|
|
82
|
+
function getTimezone(dateOrDateTime) {
|
|
83
|
+
const {offset, timezone} = parseZonedDateTime(dateOrDateTime);
|
|
84
|
+
if (timezone) {
|
|
85
|
+
return timezone;
|
|
86
|
+
}
|
|
87
|
+
if (!offset) {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
return offset === 'Z' ? 'UTC' : offset;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @param {string} plainDateTime
|
|
95
|
+
* @param {string | null} timezone
|
|
96
|
+
* @returns {string}
|
|
97
|
+
*/
|
|
98
|
+
function addTimezone(plainDateTime, timezone) {
|
|
99
|
+
if (!timezone) {
|
|
100
|
+
return plainDateTime;
|
|
101
|
+
}
|
|
102
|
+
if (timezone === 'UTC') {
|
|
103
|
+
return `${plainDateTime}Z`;
|
|
104
|
+
}
|
|
105
|
+
if (timezone.match(numOffsetRegex)) {
|
|
106
|
+
return `${plainDateTime}${timezone}`;
|
|
107
|
+
}
|
|
108
|
+
return `${plainDateTime}[${timezone}]`;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* @param {string} dateTime
|
|
113
|
+
* @param {string | null} timezone
|
|
114
|
+
* @returns {string}
|
|
115
|
+
*/
|
|
116
|
+
function setTimezone(dateTime, timezone) {
|
|
117
|
+
const {datetime} = parseZonedDateTime(dateTime);
|
|
118
|
+
return addTimezone(datetime, timezone);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* @param {Date} date
|
|
123
|
+
* @param {string} timezone
|
|
124
|
+
* @returns {string}
|
|
125
|
+
*/
|
|
126
|
+
function dateToTimezone(date, timezone) {
|
|
127
|
+
return date
|
|
128
|
+
.toLocaleString('sv', timezone ? {timeZone: timezone} : undefined)
|
|
129
|
+
.replace(' ', 'T');
|
|
130
|
+
|
|
131
|
+
// // Other version
|
|
132
|
+
// const {year, month, day, hour, minute, second} = Object.fromEntries(
|
|
133
|
+
// new Intl.DateTimeFormat('latin', {
|
|
134
|
+
// year: 'numeric',
|
|
135
|
+
// month: '2-digit',
|
|
136
|
+
// day: '2-digit',
|
|
137
|
+
// hour: '2-digit',
|
|
138
|
+
// minute: '2-digit',
|
|
139
|
+
// second: '2-digit',
|
|
140
|
+
// timeZone: timezone || undefined,
|
|
141
|
+
// })
|
|
142
|
+
// .formatToParts(date)
|
|
143
|
+
// .map(({type, value}) => [type, value])
|
|
144
|
+
// );
|
|
145
|
+
// return `${year}-${month}-${day}T${hour}${minute}${second}`
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* @param {Date} date
|
|
150
|
+
* @param {string | null} [timezone]
|
|
151
|
+
* @returns {string}
|
|
152
|
+
*/
|
|
153
|
+
function dateToZonedDateTime(date, timezone) {
|
|
154
|
+
if (!timezone) {
|
|
155
|
+
return plainDateTimeISO(date);
|
|
156
|
+
}
|
|
157
|
+
const plainDateTime = dateToTimezone(date, timezone);
|
|
158
|
+
return addTimezone(plainDateTime, timezone);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* @param {string} plainDateTime
|
|
163
|
+
* @param {string} timezone
|
|
164
|
+
* @returns {Date}
|
|
165
|
+
*/
|
|
166
|
+
function localDate(plainDateTime, timezone) {
|
|
167
|
+
// Note: should be improved when the Temporal api will be available
|
|
168
|
+
const date = new Date(plainDateTime);
|
|
169
|
+
const dateTimestamp = date.getTime();
|
|
170
|
+
const zoneTimestamp = new Date(dateToTimezone(date, timezone)).getTime();
|
|
171
|
+
const timeDiff = dateTimestamp - zoneTimestamp;
|
|
172
|
+
return new Date(dateTimestamp + timeDiff);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* @param {Date} date
|
|
177
|
+
*/
|
|
178
|
+
function plainDateISO(date = new Date()) {
|
|
179
|
+
const year = date.getFullYear();
|
|
180
|
+
const month = (date.getMonth() + 1).toString().padStart(2, '0');
|
|
181
|
+
const day = date.getDate().toString().padStart(2, '0');
|
|
182
|
+
return `${year}-${month}-${day}`;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* @param {Date} date
|
|
187
|
+
*/
|
|
188
|
+
function plainTimeISO(date = new Date()) {
|
|
189
|
+
const hours = date.getHours().toString().padStart(2, '0');
|
|
190
|
+
const minutes = date.getMinutes().toString().padStart(2, '0');
|
|
191
|
+
const seconds = date.getSeconds().toString().padStart(2, '0');
|
|
192
|
+
const milliseconds = date.getMilliseconds().toString().padStart(3, '0');
|
|
193
|
+
return `${hours}:${minutes}:${seconds}.${milliseconds}`;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* @param {Date} date
|
|
198
|
+
*/
|
|
199
|
+
function plainDateTimeISO(date = new Date()) {
|
|
200
|
+
const dateISO = plainDateISO(date);
|
|
201
|
+
const timeISO = plainTimeISO(date);
|
|
202
|
+
return `${dateISO}T${timeISO}`;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function nowZonedDateTimeISO() {
|
|
206
|
+
const plainDateTime = plainDateTimeISO();
|
|
207
|
+
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
208
|
+
return addTimezone(plainDateTime, timezone);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
module.exports = {
|
|
212
|
+
toPlannerDate,
|
|
213
|
+
parseZonedDateTime,
|
|
214
|
+
zonedDateTimeFromParts,
|
|
215
|
+
toJsDate,
|
|
216
|
+
getTimestamp,
|
|
217
|
+
getTimezone,
|
|
218
|
+
addTimezone,
|
|
219
|
+
setTimezone,
|
|
220
|
+
dateToZonedDateTime,
|
|
221
|
+
dateToTimezone,
|
|
222
|
+
localDate,
|
|
223
|
+
plainDateISO,
|
|
224
|
+
plainTimeISO,
|
|
225
|
+
plainDateTimeISO,
|
|
226
|
+
nowZonedDateTimeISO,
|
|
227
|
+
};
|
package/lib/locks.js
CHANGED
package/lib/modules.js
CHANGED
|
@@ -4,6 +4,7 @@ const path = require('path');
|
|
|
4
4
|
const fse = require('fs-extra');
|
|
5
5
|
const xFs = require('xcraft-core-fs');
|
|
6
6
|
const _ = require('lodash');
|
|
7
|
+
const traverse = require('xcraft-traverse');
|
|
7
8
|
|
|
8
9
|
function merge(obj, overloads) {
|
|
9
10
|
_.mergeWith(obj, overloads, (_, src) =>
|
|
@@ -20,7 +21,26 @@ function applyOverloads(appDir, appId, variantId, app) {
|
|
|
20
21
|
const overloads = JSON.parse(
|
|
21
22
|
fse.readFileSync(path.join(appDir, appId, `app.${variantId}.json`))
|
|
22
23
|
);
|
|
24
|
+
|
|
23
25
|
merge(app.xcraft, overloads);
|
|
26
|
+
|
|
27
|
+
/* Case where a config.js file is generated for an app; (see xcraft-core-etc).
|
|
28
|
+
* This code removes the entry with '-0' in order to have fallback on the
|
|
29
|
+
* default values in the config.js files of each module.
|
|
30
|
+
*/
|
|
31
|
+
const tr = traverse(app.xcraft);
|
|
32
|
+
tr.forEach(function (x) {
|
|
33
|
+
/* The -0 number is used in order to ensure that the config uses the default
|
|
34
|
+
* value provided in the module's config.js file. The -0 value is interesting
|
|
35
|
+
* because it's mostly useless and it can be used with JSON. In Javascript,
|
|
36
|
+
* 0 === -0 is true. In order to detect -0, the trick is to compare for
|
|
37
|
+
* Infinity because 1/0 !== 1/-0
|
|
38
|
+
* -- See xcraft-core-etc
|
|
39
|
+
*/
|
|
40
|
+
if (x === 0 && 1 / 0 !== 1 / x) {
|
|
41
|
+
this.remove();
|
|
42
|
+
}
|
|
43
|
+
});
|
|
24
44
|
} catch (ex) {
|
|
25
45
|
if (ex.code !== 'ENOENT') {
|
|
26
46
|
throw ex;
|
|
@@ -70,7 +90,7 @@ exports.loadAppConfig = (appId, appDir, configJson = {}, variantId = null) => {
|
|
|
70
90
|
return;
|
|
71
91
|
}
|
|
72
92
|
|
|
73
|
-
configJson[appId] = exports.extractForEtc(appDir, appId, variantId);
|
|
93
|
+
configJson[appId] = exports.extractForEtc(appDir, appId, variantId, true);
|
|
74
94
|
const hordesCfg = configJson[appId]['xcraft-core-horde'];
|
|
75
95
|
|
|
76
96
|
if (hordesCfg && hordesCfg.hordes) {
|
package/lib/rest.js
CHANGED
|
@@ -24,7 +24,26 @@ class RestAPI {
|
|
|
24
24
|
|
|
25
25
|
#buildOptions(options) {
|
|
26
26
|
return {
|
|
27
|
+
retry: {
|
|
28
|
+
limit: 2,
|
|
29
|
+
statusCodes: [429],
|
|
30
|
+
},
|
|
27
31
|
hooks: {
|
|
32
|
+
afterResponse: [
|
|
33
|
+
async (response, retryWithMergedOptions) => {
|
|
34
|
+
if (response.statusCode === 429) {
|
|
35
|
+
const retryAfter = response.headers['retry-after'];
|
|
36
|
+
|
|
37
|
+
if (retryAfter) {
|
|
38
|
+
const waitTime = parseInt(retryAfter, 10) * 1000;
|
|
39
|
+
await new Promise((resolve) => setTimeout(resolve, waitTime));
|
|
40
|
+
return retryWithMergedOptions({});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return response;
|
|
45
|
+
},
|
|
46
|
+
],
|
|
28
47
|
beforeError: [
|
|
29
48
|
(error) => {
|
|
30
49
|
const {response} = error;
|