@leaddreamer/inventorylogic 0.0.3-beta.39 → 0.0.3-beta.40
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/README.md +752 -0
- package/business/Utilities/index.js +2 -0
- package/business/Utilities/other.js +469 -0
- package/business/Utilities/promise-queue/index.js +231 -0
- package/business/Utilities/rate-limited-queue/index.js +127 -0
- package/dist/InventoryLogic.05ef18c0.js +2 -0
- package/dist/InventoryLogic.0b64d0ad.js +2 -0
- package/dist/InventoryLogic.69764b18.js +2 -0
- package/dist/InventoryLogic.708f773f.js +2 -0
- package/dist/InventoryLogic.8aa9c9eb.js +2 -0
- package/dist/InventoryLogic.9b53b460.js +2 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typedefs, constants, support functions and database operations for
|
|
3
|
+
* general Utilities
|
|
4
|
+
* @module Utilities
|
|
5
|
+
*/
|
|
6
|
+
import dayjs from "dayjs";
|
|
7
|
+
import promisePool from "es6-promise-pool";
|
|
8
|
+
import isSameOrAfter from "dayjs/plugin/isSameOrAfter";
|
|
9
|
+
import isSameOrBefore from "dayjs/plugin/isSameOrBefore";
|
|
10
|
+
import {
|
|
11
|
+
fetchRecordByRefPath,
|
|
12
|
+
runTransaction,
|
|
13
|
+
writeBack,
|
|
14
|
+
deleteRecordByRefPath,
|
|
15
|
+
} from "@leaddreamer/firebase-wrapper";
|
|
16
|
+
//the following inits the dayjs plugins
|
|
17
|
+
|
|
18
|
+
dayjs.extend(isSameOrAfter);
|
|
19
|
+
dayjs.extend(isSameOrBefore);
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* A convenience function for some shared artist/host front-end pages
|
|
23
|
+
* @param {OrganizationType} host
|
|
24
|
+
* @returns {Promise.OrganizationType}
|
|
25
|
+
*/
|
|
26
|
+
export async function saveHost(host, batch = null) {
|
|
27
|
+
return writeBack(host, batch);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Simple top-level object compare - assumes objects have similar structure - or
|
|
32
|
+
* AT LEAST all the fields of object1 are present in object2
|
|
33
|
+
* @param {object} object1
|
|
34
|
+
* @param {object} object2
|
|
35
|
+
* @returns {boolean}
|
|
36
|
+
*/
|
|
37
|
+
export function simpleObjectCompare(object1, object2) {
|
|
38
|
+
let result = Object.keys(object1).reduce((accumulate, key) => {
|
|
39
|
+
// eslint-disable-next-line
|
|
40
|
+
return object1[key] == object2[key] ? accumulate : false;
|
|
41
|
+
}, true); // note simple (not object) compare
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Allows string substitution from an object
|
|
47
|
+
* field names support Dot notation
|
|
48
|
+
* field names allow ":" extension for alternate formatting
|
|
49
|
+
* @param {object} thisItem - object to create formated string from
|
|
50
|
+
* @param {string} thisFormat - string describing formatted output - fieldname wrapped in {} will be substituted. DOT notation allowed for fields.
|
|
51
|
+
* @returns {string}
|
|
52
|
+
*/
|
|
53
|
+
export function formatItem(thisObject, thisFormat) {
|
|
54
|
+
let final = thisFormat.replace(
|
|
55
|
+
/{.+?}/g, //global, non-greedy - i.e. multiple {} bracketed elements
|
|
56
|
+
function (field) {
|
|
57
|
+
let fieldRaw = field.slice(1, -1);
|
|
58
|
+
//below allows for DOT notation access within the item object
|
|
59
|
+
let fieldparts = fieldRaw.split(":");
|
|
60
|
+
let [fieldname, fieldStyle] = fieldparts;
|
|
61
|
+
let fieldArray = fieldname.split(".");
|
|
62
|
+
//below walks into an object - note: ASSUMES an object
|
|
63
|
+
const bob = fieldArray.reduce((lastObject, nextName) => {
|
|
64
|
+
return lastObject[nextName];
|
|
65
|
+
}, thisObject);
|
|
66
|
+
return bob ? specialFormat(bob, fieldStyle) : "----";
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
return final;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* @private
|
|
74
|
+
* @param {any} value
|
|
75
|
+
* @param {!string} style
|
|
76
|
+
* @returns {any|string}
|
|
77
|
+
*/
|
|
78
|
+
function specialFormat(value, style) {
|
|
79
|
+
switch (style) {
|
|
80
|
+
case "timestamp":
|
|
81
|
+
return dayjs(value.toDate()).format("MMM DD, YYYY");
|
|
82
|
+
default:
|
|
83
|
+
return value;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @param {object} thisItem - one of two objects being compared
|
|
89
|
+
* @param {object} anotherItem - the other of two objects being compared
|
|
90
|
+
* @param {!string} sort - string describing sort - format "fieldName:sortType" - sort Type is "string, "date" or "number" (default)
|
|
91
|
+
* @return {string}
|
|
92
|
+
*/
|
|
93
|
+
export function compare(thisItem, anotherItem, sort = "Id:number") {
|
|
94
|
+
let [field, type] = sort.split(":");
|
|
95
|
+
type = type ? type : "number";
|
|
96
|
+
switch (type) {
|
|
97
|
+
case "string":
|
|
98
|
+
let nameArray = thisItem[field].split("The ");
|
|
99
|
+
let anotherArray = anotherItem[field].split("The ");
|
|
100
|
+
return (nameArray[0] ? nameArray[0] : nameArray[1]).localeCompare(
|
|
101
|
+
anotherArray[0] ? anotherArray[0] : anotherArray[1]
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
case "date":
|
|
105
|
+
//if (!thisItem[field] || !anotherItem[field]) return 0;
|
|
106
|
+
return !thisItem[field] ||
|
|
107
|
+
dayjs(thisItem[field]).isBefore(anotherItem[field], "day")
|
|
108
|
+
? -1
|
|
109
|
+
: anotherItem[field] &&
|
|
110
|
+
dayjs(thisItem[field]).isSame(anotherItem[field], "day")
|
|
111
|
+
? 0
|
|
112
|
+
: 1;
|
|
113
|
+
|
|
114
|
+
default:
|
|
115
|
+
return Number(thisItem[field]) - Number(anotherItem[field]);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* An array based compare function - is any entry of findFrom present in findIn
|
|
121
|
+
* @param {Array.any} findFrom
|
|
122
|
+
* @param {Array.any} findIn
|
|
123
|
+
* @returns {boolean}
|
|
124
|
+
*/
|
|
125
|
+
export function anyInAny(findFrom, findIn) {
|
|
126
|
+
let mapFindIn = new Map();
|
|
127
|
+
|
|
128
|
+
findIn.forEach((entry, index) => {
|
|
129
|
+
mapFindIn.set(entry, true);
|
|
130
|
+
}); //speed up by avoiding second loop
|
|
131
|
+
|
|
132
|
+
const findFromLength = findFrom.length;
|
|
133
|
+
let found = false;
|
|
134
|
+
for (
|
|
135
|
+
let findFromIndex = 0;
|
|
136
|
+
findFromIndex < findFromLength && !found;
|
|
137
|
+
findFromIndex++
|
|
138
|
+
)
|
|
139
|
+
if (
|
|
140
|
+
mapFindIn.get(findFrom[findFromIndex].valueOf())
|
|
141
|
+
//findFrom[findFromIndex].valueOf() === findIn[arrayTwoIndex].valueOf()
|
|
142
|
+
)
|
|
143
|
+
found = true; //note this breaks both loops
|
|
144
|
+
|
|
145
|
+
return found;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* An Action Iterator Generator. Cycles through the entries in the passed array,
|
|
150
|
+
* yielding the result of a function for each entry.
|
|
151
|
+
* Only cares that inputs are an array, and a function that returns a
|
|
152
|
+
* promise
|
|
153
|
+
* @generator
|
|
154
|
+
* @param {Array.any} entryArray
|
|
155
|
+
* @param {callback} actionFunction
|
|
156
|
+
* @yields {function}
|
|
157
|
+
*/
|
|
158
|
+
function* generateIterator(entryArray, actionFunction) {
|
|
159
|
+
let countLimit = entryArray.length;
|
|
160
|
+
//next is testing remnant
|
|
161
|
+
//countLimit = lessorOf(10, countLimit);
|
|
162
|
+
for (let count = 0; count < countLimit; count++) {
|
|
163
|
+
yield actionFunction(entryArray[count]);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
*
|
|
169
|
+
*/
|
|
170
|
+
const PromisePool = promisePool.PromisePool;
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* @typedef {object} PromiseOptions
|
|
174
|
+
* @property {number} concurrency
|
|
175
|
+
* @property {number} rate
|
|
176
|
+
*/
|
|
177
|
+
/**
|
|
178
|
+
* @readonly
|
|
179
|
+
* @constant
|
|
180
|
+
* @type {PromiseOptions}
|
|
181
|
+
* @default
|
|
182
|
+
*/
|
|
183
|
+
const promiseOptionsDefault = {
|
|
184
|
+
concurrency: 3, //simultaneous
|
|
185
|
+
rate: 10, //per second
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* @param {Array.object} entryArray
|
|
190
|
+
* @param {callback} actionFunction
|
|
191
|
+
* @param {PromiseOptions} promiseOptions
|
|
192
|
+
* @returns {Promise}
|
|
193
|
+
*/
|
|
194
|
+
export async function PromisePoolRunner(
|
|
195
|
+
entryArray,
|
|
196
|
+
actionFunction,
|
|
197
|
+
promiseOptions = promiseOptionsDefault
|
|
198
|
+
) {
|
|
199
|
+
if (entryArray.length) {
|
|
200
|
+
const promiseIterator = generateIterator(entryArray, actionFunction);
|
|
201
|
+
const promisePool = new PromisePool(
|
|
202
|
+
promiseIterator,
|
|
203
|
+
promiseOptions.concurrency
|
|
204
|
+
);
|
|
205
|
+
return promisePool.start();
|
|
206
|
+
} else {
|
|
207
|
+
return Promise.resolve(null);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* A convenience function for legibility
|
|
213
|
+
* @param {any} args unbounded list of arguments
|
|
214
|
+
* @returns {number} the least of any of the arguments
|
|
215
|
+
b */
|
|
216
|
+
export function lesserOf(...args) {
|
|
217
|
+
return Math.min(...args);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* @param {any} args unbounded list of arguments
|
|
222
|
+
* @returns {number} the largest of any of the arguments
|
|
223
|
+
*/
|
|
224
|
+
export function largerOf(...args) {
|
|
225
|
+
return Math.max(...args);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* "Moves" a record from our standard tree structureto a parallel archival
|
|
230
|
+
* structure, by prepending a "_" to the collection names in the path
|
|
231
|
+
* This prevents the archived record from appearing in collectionGroup queries
|
|
232
|
+
* @param {RecordDocument} record
|
|
233
|
+
* @param {!string} record.refPath
|
|
234
|
+
* @returns {Promise}
|
|
235
|
+
*/
|
|
236
|
+
export async function archiveRecord(record) {
|
|
237
|
+
//split the reference path into it's constituent parts
|
|
238
|
+
const parts = record.refPath.split("/");
|
|
239
|
+
const partsCount = parts.length;
|
|
240
|
+
if (Math.floor(partsCount / 2) * 2 !== partsCount) {
|
|
241
|
+
//throw "safe" error
|
|
242
|
+
return; //returns a null promise resolved
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
let archivePath = "";
|
|
246
|
+
let index = 0;
|
|
247
|
+
//adde a "_" to the start of every collection named in the path
|
|
248
|
+
//this avoids collectionGroup issues, but follows a parallel path
|
|
249
|
+
do {
|
|
250
|
+
archivePath = `${archivePath}/_${parts[index]}/${parts[index + 1]}`;
|
|
251
|
+
index += 2;
|
|
252
|
+
} while (index < partsCount);
|
|
253
|
+
await runTransaction(async (batch) => {
|
|
254
|
+
//////////////////////////////
|
|
255
|
+
//Read a current full copy of the record
|
|
256
|
+
const realRecord = await fetchRecordByRefPath(record.refPath, batch);
|
|
257
|
+
//Write it to the "archivePath"
|
|
258
|
+
await writeBack({ ...realRecord, refPath: archivePath }, batch);
|
|
259
|
+
//delete the original copy
|
|
260
|
+
await deleteRecordByRefPath(realRecord.refPath, batch);
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* uses a passed refPath to create the most basic document record form
|
|
266
|
+
* @param {!string} refPath - "/" separated document path reference
|
|
267
|
+
* @returns {RecordDocument}
|
|
268
|
+
*/
|
|
269
|
+
export function refPathToBaseRecord(refPath) {
|
|
270
|
+
return {
|
|
271
|
+
Id: refPath.split("/").pop(),
|
|
272
|
+
refPath: refPath,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* clips value to given bounds
|
|
278
|
+
* @param {number} value
|
|
279
|
+
* @param {number} lowerBound
|
|
280
|
+
* @param {number} upperBound
|
|
281
|
+
* @returns {number}
|
|
282
|
+
*/
|
|
283
|
+
export function bound(value, lowerBound, upperBound) {
|
|
284
|
+
return largerOf(lesserOf(value, upperBound), lowerBound);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* function to push an element to an array which may not be initialized
|
|
289
|
+
* @param {Array.object} targetArray
|
|
290
|
+
* @param {object} newObject
|
|
291
|
+
*
|
|
292
|
+
*/
|
|
293
|
+
export function blindPush(targetArray, newObject) {
|
|
294
|
+
targetArray
|
|
295
|
+
? targetArray.push(newObject)
|
|
296
|
+
: (targetArray = [newObject]);
|
|
297
|
+
return targetArray;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* @param {Array.Array.object} arrayObject
|
|
302
|
+
* @param {string} key key or index to choose which Array.object to add *this* object to
|
|
303
|
+
* @param {object} object
|
|
304
|
+
*/
|
|
305
|
+
export function addToArrayObject(arrayObject, key, object) {
|
|
306
|
+
if (arrayObject[key] && arrayObject[key].length) {
|
|
307
|
+
//if the key is already present, *and* there are already objects in the keyed array
|
|
308
|
+
arrayObject[key].push(object); //push the object onto the indexed array
|
|
309
|
+
} else {
|
|
310
|
+
arrayObject[key] = [object];
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* @param {string} color name of color
|
|
318
|
+
* @returns {string} hex value of named color
|
|
319
|
+
*/
|
|
320
|
+
export function makeHexAsNeeded(color) {
|
|
321
|
+
var colours = {
|
|
322
|
+
aliceblue: "#f0f8ff",
|
|
323
|
+
antiquewhite: "#faebd7",
|
|
324
|
+
aqua: "#00ffff",
|
|
325
|
+
aquamarine: "#7fffd4",
|
|
326
|
+
azure: "#f0ffff",
|
|
327
|
+
beige: "#f5f5dc",
|
|
328
|
+
bisque: "#ffe4c4",
|
|
329
|
+
black: "#000000",
|
|
330
|
+
blanchedalmond: "#ffebcd",
|
|
331
|
+
blue: "#0000ff",
|
|
332
|
+
blueviolet: "#8a2be2",
|
|
333
|
+
brown: "#a52a2a",
|
|
334
|
+
burlywood: "#deb887",
|
|
335
|
+
cadetblue: "#5f9ea0",
|
|
336
|
+
chartreuse: "#7fff00",
|
|
337
|
+
chocolate: "#d2691e",
|
|
338
|
+
coral: "#ff7f50",
|
|
339
|
+
cornflowerblue: "#6495ed",
|
|
340
|
+
cornsilk: "#fff8dc",
|
|
341
|
+
crimson: "#dc143c",
|
|
342
|
+
cyan: "#00ffff",
|
|
343
|
+
darkblue: "#00008b",
|
|
344
|
+
darkcyan: "#008b8b",
|
|
345
|
+
darkgoldenrod: "#b8860b",
|
|
346
|
+
darkgray: "#a9a9a9",
|
|
347
|
+
darkgreen: "#006400",
|
|
348
|
+
darkkhaki: "#bdb76b",
|
|
349
|
+
darkmagenta: "#8b008b",
|
|
350
|
+
darkolivegreen: "#556b2f",
|
|
351
|
+
darkorange: "#ff8c00",
|
|
352
|
+
darkorchid: "#9932cc",
|
|
353
|
+
darkred: "#8b0000",
|
|
354
|
+
darksalmon: "#e9967a",
|
|
355
|
+
darkseagreen: "#8fbc8f",
|
|
356
|
+
darkslateblue: "#483d8b",
|
|
357
|
+
darkslategray: "#2f4f4f",
|
|
358
|
+
darkturquoise: "#00ced1",
|
|
359
|
+
darkviolet: "#9400d3",
|
|
360
|
+
deeppink: "#ff1493",
|
|
361
|
+
deepskyblue: "#00bfff",
|
|
362
|
+
dimgray: "#696969",
|
|
363
|
+
dodgerblue: "#1e90ff",
|
|
364
|
+
firebrick: "#b22222",
|
|
365
|
+
floralwhite: "#fffaf0",
|
|
366
|
+
forestgreen: "#228b22",
|
|
367
|
+
fuchsia: "#ff00ff",
|
|
368
|
+
gainsboro: "#dcdcdc",
|
|
369
|
+
ghostwhite: "#f8f8ff",
|
|
370
|
+
gold: "#ffd700",
|
|
371
|
+
goldenrod: "#daa520",
|
|
372
|
+
gray: "#808080",
|
|
373
|
+
green: "#008000",
|
|
374
|
+
greenyellow: "#adff2f",
|
|
375
|
+
honeydew: "#f0fff0",
|
|
376
|
+
hotpink: "#ff69b4",
|
|
377
|
+
"indianred ": "#cd5c5c",
|
|
378
|
+
indigo: "#4b0082",
|
|
379
|
+
ivory: "#fffff0",
|
|
380
|
+
khaki: "#f0e68c",
|
|
381
|
+
lavender: "#e6e6fa",
|
|
382
|
+
lavenderblush: "#fff0f5",
|
|
383
|
+
lawngreen: "#7cfc00",
|
|
384
|
+
lemonchiffon: "#fffacd",
|
|
385
|
+
lightblue: "#add8e6",
|
|
386
|
+
lightcoral: "#f08080",
|
|
387
|
+
lightcyan: "#e0ffff",
|
|
388
|
+
lightgoldenrodyellow: "#fafad2",
|
|
389
|
+
lightgrey: "#d3d3d3",
|
|
390
|
+
lightgreen: "#90ee90",
|
|
391
|
+
lightpink: "#ffb6c1",
|
|
392
|
+
lightsalmon: "#ffa07a",
|
|
393
|
+
lightseagreen: "#20b2aa",
|
|
394
|
+
lightskyblue: "#87cefa",
|
|
395
|
+
lightslategray: "#778899",
|
|
396
|
+
lightsteelblue: "#b0c4de",
|
|
397
|
+
lightyellow: "#ffffe0",
|
|
398
|
+
lime: "#00ff00",
|
|
399
|
+
limegreen: "#32cd32",
|
|
400
|
+
linen: "#faf0e6",
|
|
401
|
+
magenta: "#ff00ff",
|
|
402
|
+
maroon: "#800000",
|
|
403
|
+
mediumaquamarine: "#66cdaa",
|
|
404
|
+
mediumblue: "#0000cd",
|
|
405
|
+
mediumorchid: "#ba55d3",
|
|
406
|
+
mediumpurple: "#9370d8",
|
|
407
|
+
mediumseagreen: "#3cb371",
|
|
408
|
+
mediumslateblue: "#7b68ee",
|
|
409
|
+
mediumspringgreen: "#00fa9a",
|
|
410
|
+
mediumturquoise: "#48d1cc",
|
|
411
|
+
mediumvioletred: "#c71585",
|
|
412
|
+
midnightblue: "#191970",
|
|
413
|
+
mintcream: "#f5fffa",
|
|
414
|
+
mistyrose: "#ffe4e1",
|
|
415
|
+
moccasin: "#ffe4b5",
|
|
416
|
+
navajowhite: "#ffdead",
|
|
417
|
+
navy: "#000080",
|
|
418
|
+
oldlace: "#fdf5e6",
|
|
419
|
+
olive: "#808000",
|
|
420
|
+
olivedrab: "#6b8e23",
|
|
421
|
+
orange: "#ffa500",
|
|
422
|
+
orangered: "#ff4500",
|
|
423
|
+
orchid: "#da70d6",
|
|
424
|
+
palegoldenrod: "#eee8aa",
|
|
425
|
+
palegreen: "#98fb98",
|
|
426
|
+
paleturquoise: "#afeeee",
|
|
427
|
+
palevioletred: "#d87093",
|
|
428
|
+
papayawhip: "#ffefd5",
|
|
429
|
+
peachpuff: "#ffdab9",
|
|
430
|
+
peru: "#cd853f",
|
|
431
|
+
pink: "#ffc0cb",
|
|
432
|
+
plum: "#dda0dd",
|
|
433
|
+
powderblue: "#b0e0e6",
|
|
434
|
+
purple: "#800080",
|
|
435
|
+
rebeccapurple: "#663399",
|
|
436
|
+
red: "#ff0000",
|
|
437
|
+
rosybrown: "#bc8f8f",
|
|
438
|
+
royalblue: "#4169e1",
|
|
439
|
+
saddlebrown: "#8b4513",
|
|
440
|
+
salmon: "#fa8072",
|
|
441
|
+
sandybrown: "#f4a460",
|
|
442
|
+
seagreen: "#2e8b57",
|
|
443
|
+
seashell: "#fff5ee",
|
|
444
|
+
sienna: "#a0522d",
|
|
445
|
+
silver: "#c0c0c0",
|
|
446
|
+
skyblue: "#87ceeb",
|
|
447
|
+
slateblue: "#6a5acd",
|
|
448
|
+
slategray: "#708090",
|
|
449
|
+
snow: "#fffafa",
|
|
450
|
+
springgreen: "#00ff7f",
|
|
451
|
+
steelblue: "#4682b4",
|
|
452
|
+
tan: "#d2b48c",
|
|
453
|
+
teal: "#008080",
|
|
454
|
+
thistle: "#d8bfd8",
|
|
455
|
+
tomato: "#ff6347",
|
|
456
|
+
turquoise: "#40e0d0",
|
|
457
|
+
violet: "#ee82ee",
|
|
458
|
+
wheat: "#f5deb3",
|
|
459
|
+
white: "#ffffff",
|
|
460
|
+
whitesmoke: "#f5f5f5",
|
|
461
|
+
yellow: "#ffff00",
|
|
462
|
+
yellowgreen: "#9acd32",
|
|
463
|
+
};
|
|
464
|
+
|
|
465
|
+
if (colours[color.toLowerCase()]) return colours[color.toLowerCase()];
|
|
466
|
+
return color;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
export * from "./promise-queue";
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typedefs, constants, support functions and database operations related to Promise getQueueSize
|
|
3
|
+
* Thes era euseful for scheduling large serial asynchronous operations
|
|
4
|
+
* @module PromiseQueue
|
|
5
|
+
*/
|
|
6
|
+
(function () {
|
|
7
|
+
/**
|
|
8
|
+
* Creates a new rate limited queue. The queue is created with stopped state, i.e. you have to call start() to run jobs.
|
|
9
|
+
* @param {number} maxCallsPerSecond=1 - The rate limit of this queue in maximum calls per seconds which may be executed at any time. Pass values less than 1 to have less than one call per second, e.g. 1/3 for 3 one call in three seconds at maximum.
|
|
10
|
+
* @param logger
|
|
11
|
+
* @constructor
|
|
12
|
+
*/
|
|
13
|
+
function PromiseQueue(maxCallsPerSecond, logger) {
|
|
14
|
+
this.maxCallsPerSecond =
|
|
15
|
+
typeof maxCallsPerSecond === "undefined" ? 1 : maxCallsPerSecond;
|
|
16
|
+
this.logger = logger;
|
|
17
|
+
|
|
18
|
+
this.tasks = [];
|
|
19
|
+
this.isActive = false;
|
|
20
|
+
this.jobIntervalMillis = 1000 / maxCallsPerSecond;
|
|
21
|
+
|
|
22
|
+
this.lastTaskTimestamp = null;
|
|
23
|
+
this.currentTaskTimeoutId = null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Processes the next task and returns the id of the time used to wait for the next invocation of this method.
|
|
28
|
+
* @private
|
|
29
|
+
*/
|
|
30
|
+
PromiseQueue.prototype.updateTaskSchedule = function () {
|
|
31
|
+
if (this.logger && this.logger.debug) {
|
|
32
|
+
this.logger.debug("PromiseQueue.updateTaskSchedule");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (this.isEmpty() || this.isStopped()) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const queue = this;
|
|
40
|
+
|
|
41
|
+
function processNextTask() {
|
|
42
|
+
if (queue.logger && queue.logger.debug) {
|
|
43
|
+
queue.logger.debug("PromiseQueue.processNextTask", queue.tasks.length);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
var nextTaskItem = queue.tasks.shift();
|
|
47
|
+
if (nextTaskItem) {
|
|
48
|
+
try {
|
|
49
|
+
const result = nextTaskItem.task.call();
|
|
50
|
+
|
|
51
|
+
if (queue.logger && queue.logger.debug) {
|
|
52
|
+
queue.logger.debug(
|
|
53
|
+
"Resolving queued promise",
|
|
54
|
+
result,
|
|
55
|
+
result instanceof Promise
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
nextTaskItem.resolve(result);
|
|
60
|
+
} catch (e) {
|
|
61
|
+
queue.logger.info("rejecting queued promise result", e);
|
|
62
|
+
nextTaskItem.reject(e);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function scheduleNext(timeoutMillisOverride) {
|
|
68
|
+
if (queue.logger && queue.logger.debug) {
|
|
69
|
+
queue.logger.debug("PromiseQueue.scheduleNext", timeoutMillisOverride);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (queue.isEmpty()) {
|
|
73
|
+
queue.currentTaskTimeoutId = null;
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
queue.currentTaskTimeoutId = setTimeout(function () {
|
|
78
|
+
queue.lastTaskTimestamp = Date.now();
|
|
79
|
+
processNextTask();
|
|
80
|
+
|
|
81
|
+
scheduleNext(queue.jobIntervalMillis);
|
|
82
|
+
}, timeoutMillisOverride);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (this.currentTaskTimeoutId == null) {
|
|
86
|
+
if (this.lastTaskTimestamp) {
|
|
87
|
+
let passedTime = Date.now() - this.lastTaskTimestamp;
|
|
88
|
+
if (passedTime > this.jobIntervalMillis) {
|
|
89
|
+
scheduleNext(this.jobIntervalMillis);
|
|
90
|
+
} else {
|
|
91
|
+
scheduleNext(Math.max(0, this.jobIntervalMillis - passedTime));
|
|
92
|
+
}
|
|
93
|
+
} else {
|
|
94
|
+
scheduleNext(0);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Starts the task processing of this queue.
|
|
101
|
+
*
|
|
102
|
+
* If it is already started, then false will be returned.
|
|
103
|
+
* If it is not yet started then the first task will be processed in the background and true will be returned.
|
|
104
|
+
*
|
|
105
|
+
* @return {boolean} true if this queue wasn't started before and now started to process tasks.
|
|
106
|
+
*/
|
|
107
|
+
PromiseQueue.prototype.start = function () {
|
|
108
|
+
if (this.isActive) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
this.isActive = true;
|
|
113
|
+
|
|
114
|
+
this.updateTaskSchedule();
|
|
115
|
+
|
|
116
|
+
return true;
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Stops the processing of this queue.
|
|
121
|
+
* @return true if the queue was started and is now stopped. false if it was already stopped.
|
|
122
|
+
*/
|
|
123
|
+
PromiseQueue.prototype.stop = function () {
|
|
124
|
+
if (this.isActive) {
|
|
125
|
+
this.isActive = false;
|
|
126
|
+
|
|
127
|
+
if (this.currentTaskTimeoutId) {
|
|
128
|
+
clearInterval(this.currentTaskTimeoutId);
|
|
129
|
+
this.currentTaskTimeoutId = null;
|
|
130
|
+
this.lastTaskTimestamp = null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
PromiseQueue.prototype.getMaxCallsPerSecond = function () {
|
|
136
|
+
return this.maxCallsPerSecond;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
PromiseQueue.prototype.isStarted = function () {
|
|
140
|
+
return this.isActive;
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
PromiseQueue.prototype.isStopped = function () {
|
|
144
|
+
return !this.isStarted();
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* @returns {boolean} true if this queue is currently empty, i.e. if it has no tasks in the queue.
|
|
149
|
+
*/
|
|
150
|
+
PromiseQueue.prototype.isEmpty = function () {
|
|
151
|
+
return this.tasks.length === 0;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* @returns {boolean} true if this queue has one or more entries.
|
|
156
|
+
*/
|
|
157
|
+
PromiseQueue.prototype.isNotEmpty = function () {
|
|
158
|
+
return !this.isEmpty();
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* @param {function} task - The task to add.
|
|
163
|
+
* @param {boolean} append - if the task should be appended or prepended to the list of queued tasks
|
|
164
|
+
* @return {Promise} A promise which is resolved/rejected when the task was executed by the queue.
|
|
165
|
+
*/
|
|
166
|
+
PromiseQueue.prototype.add = function (task, append) {
|
|
167
|
+
var self = this;
|
|
168
|
+
|
|
169
|
+
var promise = new Promise(function (resolve, reject) {
|
|
170
|
+
var newTaskItem = { task: task, resolve: resolve, reject: reject };
|
|
171
|
+
|
|
172
|
+
if (self.isEmpty() || append) {
|
|
173
|
+
self.tasks.push(newTaskItem);
|
|
174
|
+
} else {
|
|
175
|
+
self.tasks.unshift(newTaskItem);
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
self.updateTaskSchedule();
|
|
180
|
+
|
|
181
|
+
return promise;
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Appends a new task at the end of the queue.
|
|
186
|
+
* @param {function} task - The task to perform. This function will be called with no arguments.
|
|
187
|
+
* @return {Promise} A promise which is resolved/rejected When the task was executed by the queue
|
|
188
|
+
*/
|
|
189
|
+
PromiseQueue.prototype.append = function (task) {
|
|
190
|
+
return this.add(task, true);
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Adds a new task as first item in the queue.
|
|
195
|
+
* @param task
|
|
196
|
+
* @return {Promise} A promise which is resolved/rejected When the task was executed by the queue
|
|
197
|
+
*/
|
|
198
|
+
PromiseQueue.prototype.prepend = function (task) {
|
|
199
|
+
return this.add(task, false);
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Removes the task from the qeueue. If the task is contained more than one time then all occurrences will be removed.
|
|
204
|
+
* @param {function} task - The task to remove.
|
|
205
|
+
* @return {number} - The number of items which were removed.
|
|
206
|
+
*/
|
|
207
|
+
PromiseQueue.prototype.remove = function (task) {
|
|
208
|
+
if (this.isEmpty()) {
|
|
209
|
+
return 0;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
var removedItems = 0;
|
|
213
|
+
for (var i = this.tasks.length - 1; i >= 0; i--) {
|
|
214
|
+
if (this.tasks[i].task === task) {
|
|
215
|
+
this.tasks.splice(i, 1);
|
|
216
|
+
removedItems++;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return removedItems;
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* @returns {Number} - The number of queued/waiting tasks in the queue.
|
|
225
|
+
*/
|
|
226
|
+
PromiseQueue.prototype.getQueueSize = function () {
|
|
227
|
+
return this.tasks.length;
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
module.exports = PromiseQueue;
|
|
231
|
+
})();
|