@valkyriestudios/utils 12.34.0 → 12.35.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/README.md +197 -2
- package/array/mapFn.d.ts +7 -2
- package/array/mapFn.js +4 -3
- package/array/mapFnAsMap.d.ts +7 -2
- package/array/mapFnAsMap.js +5 -3
- package/array/mapKey.d.ts +7 -2
- package/array/mapKey.js +5 -3
- package/array/mapKeyAsMap.d.ts +7 -2
- package/array/mapKeyAsMap.js +5 -3
- package/date/format.js +4 -1
- package/index.d.ts +63 -8
- package/modules/PubSub.js +1 -1
- package/modules/Scheduler.d.ts +139 -0
- package/modules/Scheduler.js +347 -0
- package/package.json +16 -4
package/README.md
CHANGED
|
@@ -30,7 +30,7 @@ isNotEmptyArray([]); // FALSE
|
|
|
30
30
|
isNotEmptyArray([0, 1, 2]); // TRUE
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
### array/mapKey(val:Record[], key:string, opts?:{merge?:boolean;filter_fn?:(el:T) => boolean})
|
|
33
|
+
### array/mapKey(val:Record[], key:string, opts?:{merge?:boolean;filter_fn?:(el:T) => boolean;transform_fn?:(el:T) => U})
|
|
34
34
|
Map a non-primitive object array into an object map by key.
|
|
35
35
|
|
|
36
36
|
**Take Note**: The function `array/mapKeyAsMap` has the same behavior as the mapKey function with the sole difference being that it returns a
|
|
@@ -110,7 +110,7 @@ mapKey([
|
|
|
110
110
|
{name: 'Alana', isActive: true},
|
|
111
111
|
{uid: 87, name: 'Josh', isActive: false},
|
|
112
112
|
{uid: 12, name: 'Farah', isActive: false},
|
|
113
|
-
], 'uid', {merge: true})
|
|
113
|
+
], 'uid', {merge: true, filter_fn: el => el.isActive})
|
|
114
114
|
/* Expected output: */
|
|
115
115
|
{
|
|
116
116
|
12: {uid: 12, name: 'Peter', isActive: true},
|
|
@@ -118,6 +118,24 @@ mapKey([
|
|
|
118
118
|
}
|
|
119
119
|
```
|
|
120
120
|
|
|
121
|
+
also allows transforming objects at the same time with a custom transform_fn. Take Note that all of these operations are run in O(n):
|
|
122
|
+
```typescript
|
|
123
|
+
import mapKey from '@valkyriestudios/utils/array/mapKey';
|
|
124
|
+
mapKey([
|
|
125
|
+
{uid: 12, name: 'Peter', isActive: true},
|
|
126
|
+
{uid: 15, name: 'Jonas', dob: '2022-02-07', isActive: true},
|
|
127
|
+
{uid: 15, name: 'Bob', isActive: false},
|
|
128
|
+
{name: 'Alana', isActive: true},
|
|
129
|
+
{uid: 87, name: 'Josh', isActive: false},
|
|
130
|
+
{uid: 12, name: 'Farah', isActive: false},
|
|
131
|
+
], 'uid', {merge: true, filter_fn: el => el.isActive, transform_fn: el => pick(el, ["name"])})
|
|
132
|
+
/* Expected output: */
|
|
133
|
+
{
|
|
134
|
+
12: {name: 'Peter'},
|
|
135
|
+
15: {name: 'Jonas'},
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
121
139
|
### array/mapKeyAsMap(val:Record[], key:string, opts?:{merge?:boolean;filter_fn?:(el:T) => boolean})
|
|
122
140
|
Same behavior as mapKey but returns a Map instead of an object
|
|
123
141
|
|
|
@@ -560,6 +578,7 @@ Format a date according to a spec/locale and zone
|
|
|
560
578
|
| `L` | Locale-Specific date | 15 jul 2024 |
|
|
561
579
|
| `t` | Locale-specific short time | 10:28 AM |
|
|
562
580
|
| `T` | Locale-specific time with seconds | 10:28:30 AM |
|
|
581
|
+
| `ISO` | Alias for `YYYY-MM-DD[T]HH:mm:ss.SSS[Z]` | 2025-03-16T14:24:59.010Z |
|
|
563
582
|
|
|
564
583
|
**Additional**:
|
|
565
584
|
Format has several additional functions defined which help usage inside of an ecosystem (eg: webapp) by **overriding the global defaults** used by format.
|
|
@@ -1111,6 +1130,182 @@ Both synchronous and asynchronous errors are logged via the custom logger (if pr
|
|
|
1111
1130
|
- **Flexibility:**
|
|
1112
1131
|
The storage behavior can be controlled globally through the constructor or overridden for individual publish calls.
|
|
1113
1132
|
|
|
1133
|
+
### modules/Scheduler
|
|
1134
|
+
A Lightweight scheduler module that works with a cron-like syntax and can be easily configured/run at runtime.
|
|
1135
|
+
|
|
1136
|
+
**Take Note**: This does **not replace** cron, it is a module that runs at runtime and could be used to simulate cron-like behavior, but it is **not a replacement**.
|
|
1137
|
+
|
|
1138
|
+
##### Usage
|
|
1139
|
+
Supports baseline operation:
|
|
1140
|
+
```typescript
|
|
1141
|
+
import { Scheduler } from '@valkyriestudios/utils/modules/PubSub';
|
|
1142
|
+
import * as Handlers from "..."; /* Example methods */
|
|
1143
|
+
|
|
1144
|
+
const mySchedule = new Scheduler({name: 'MyScheduler'});
|
|
1145
|
+
|
|
1146
|
+
mySchedule.add({schedule: '0 * * * *', name: 'send_emails', fn: Handlers.SendEmails});
|
|
1147
|
+
mySchedule.add({schedule: '0 */3 * * *', name: 'cleanup', fn: Handlers.Cleanup});
|
|
1148
|
+
mySchedule.add({schedule: '0,15,30,45 * * * *', name: 'synchronize', fn: Handlers.Synchronize});
|
|
1149
|
+
await mySchedule.run();
|
|
1150
|
+
```
|
|
1151
|
+
|
|
1152
|
+
Let's say you need to send something out in different timezones:
|
|
1153
|
+
```typescript
|
|
1154
|
+
import { Scheduler } from '@valkyriestudios/utils/modules/PubSub';
|
|
1155
|
+
import * as Handlers from "..."; /* Example methods */
|
|
1156
|
+
|
|
1157
|
+
...
|
|
1158
|
+
|
|
1159
|
+
const mySchedule = new Scheduler({name: 'MyScheduler'});
|
|
1160
|
+
|
|
1161
|
+
/* This is an example */
|
|
1162
|
+
for (const user of users) {
|
|
1163
|
+
mySchedule.add({
|
|
1164
|
+
schedule: '0 * * * *',
|
|
1165
|
+
name: 'send_emails',
|
|
1166
|
+
fn: Handlers.SendEmail,
|
|
1167
|
+
timeZone: user.timeZone, /* Given a user has a timezone */
|
|
1168
|
+
});
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
await mySchedule.run();
|
|
1172
|
+
```
|
|
1173
|
+
|
|
1174
|
+
Too much flooding! let's turn off parallelization and have it run them in linear fashion:
|
|
1175
|
+
```typescript
|
|
1176
|
+
import { Scheduler } from '@valkyriestudios/utils/modules/PubSub';
|
|
1177
|
+
import * as Handlers from "..."; /* Example methods */
|
|
1178
|
+
|
|
1179
|
+
...
|
|
1180
|
+
|
|
1181
|
+
const mySchedule = new Scheduler({
|
|
1182
|
+
name: 'MyScheduler',
|
|
1183
|
+
parallel: false, /* By setting parallel to false we will ensure one at a time */
|
|
1184
|
+
});
|
|
1185
|
+
|
|
1186
|
+
/* This is an example */
|
|
1187
|
+
for (const user of users) {
|
|
1188
|
+
mySchedule.add({
|
|
1189
|
+
schedule: '0 * * * *',
|
|
1190
|
+
name: 'send_emails',
|
|
1191
|
+
fn: Handlers.SendEmail,
|
|
1192
|
+
timeZone: user.timeZone, /* Given a user has a timezone */
|
|
1193
|
+
});
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
await mySchedule.run();
|
|
1197
|
+
```
|
|
1198
|
+
|
|
1199
|
+
Okay we can actually send 3 at a time, let's set that up:
|
|
1200
|
+
```typescript
|
|
1201
|
+
import { Scheduler } from '@valkyriestudios/utils/modules/PubSub';
|
|
1202
|
+
import * as Handlers from "..."; /* Example methods */
|
|
1203
|
+
|
|
1204
|
+
...
|
|
1205
|
+
|
|
1206
|
+
const mySchedule = new Scheduler({
|
|
1207
|
+
name: 'MyScheduler',
|
|
1208
|
+
parallel: 3, /* By setting parallel to a specific integer above 0 we will run X jobs in parallel at a time */
|
|
1209
|
+
});
|
|
1210
|
+
|
|
1211
|
+
/* This is an example */
|
|
1212
|
+
for (const user of users) {
|
|
1213
|
+
mySchedule.add({
|
|
1214
|
+
schedule: '0 * * * *',
|
|
1215
|
+
name: 'send_emails',
|
|
1216
|
+
fn: Handlers.SendEmail,
|
|
1217
|
+
timeZone: user.timeZone, /* Given a user has a timezone */
|
|
1218
|
+
});
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
await mySchedule.run();
|
|
1222
|
+
```
|
|
1223
|
+
|
|
1224
|
+
Oh no the emails aren't going out to the right user because we didn't pass our data:
|
|
1225
|
+
```typescript
|
|
1226
|
+
import { Scheduler } from '@valkyriestudios/utils/modules/PubSub';
|
|
1227
|
+
import * as Handlers from "..."; /* Example methods */
|
|
1228
|
+
|
|
1229
|
+
...
|
|
1230
|
+
|
|
1231
|
+
const mySchedule = new Scheduler({
|
|
1232
|
+
name: 'MyScheduler',
|
|
1233
|
+
parallel: 3,
|
|
1234
|
+
});
|
|
1235
|
+
|
|
1236
|
+
/* This is an example */
|
|
1237
|
+
for (const user of users) {
|
|
1238
|
+
mySchedule.add({
|
|
1239
|
+
schedule: '0 * * * *',
|
|
1240
|
+
name: 'send_emails',
|
|
1241
|
+
fn: Handlers.SendEmail,
|
|
1242
|
+
timeZone: user.timeZone, /* Given a user has a timezone */
|
|
1243
|
+
data: user, /* You will have automatic type hinting on this with the first val of SendEmail handler */
|
|
1244
|
+
});
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
await mySchedule.run();
|
|
1248
|
+
```
|
|
1249
|
+
|
|
1250
|
+
I want this to run continuously so that I can just leave it running on a server:
|
|
1251
|
+
```typescript
|
|
1252
|
+
import { Scheduler } from '@valkyriestudios/utils/modules/PubSub';
|
|
1253
|
+
import * as Handlers from "..."; /* Example methods */
|
|
1254
|
+
|
|
1255
|
+
...
|
|
1256
|
+
|
|
1257
|
+
const mySchedule = new Scheduler({
|
|
1258
|
+
name: 'MyScheduler',
|
|
1259
|
+
parallel: 3,
|
|
1260
|
+
auto: true, /* By enabling this the schedule will automatically check once per minute which ones it needs to run */
|
|
1261
|
+
});
|
|
1262
|
+
|
|
1263
|
+
/* This is an example */
|
|
1264
|
+
for (const user of users) {
|
|
1265
|
+
mySchedule.add({
|
|
1266
|
+
schedule: '0 * * * *',
|
|
1267
|
+
name: 'send_emails',
|
|
1268
|
+
fn: Handlers.SendEmail,
|
|
1269
|
+
timeZone: user.timeZone,
|
|
1270
|
+
data: user,
|
|
1271
|
+
});
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
await mySchedule.run();
|
|
1275
|
+
```
|
|
1276
|
+
|
|
1277
|
+
##### API Overview
|
|
1278
|
+
- **new Scheduler(options?: { logger?: LogFn; name?: string; timeZone?: string|null; parallel?:boolean|number; auto?: boolean })**
|
|
1279
|
+
Creates a new Scheduler instance.
|
|
1280
|
+
-- **logger (optional)**: Custom logging function to capture errors.
|
|
1281
|
+
-- **name (optional)**: A non‑empty string to name the instance.
|
|
1282
|
+
-- **timeZone (optional)**: (default=local timezone) The default timeZone the scheduler is run in
|
|
1283
|
+
-- **parallel (optional)**: (default=true) Scheduler will run jobs that need to run in parallel, set to false to run linear, set to a number to run X jobs in parallel
|
|
1284
|
+
-- **auto (optional)**: (default=false) Set to true to automatically run the schedule every 60 seconds
|
|
1285
|
+
- **add(job: {name:string; schedule:string; fn: Function; timeZone?: string | null; data?: unknown}): boolean**
|
|
1286
|
+
Add a job to the schedule
|
|
1287
|
+
-- If a timeZone is passed we will automatically check against local time in that timeZone on whether or not the job needs to run.
|
|
1288
|
+
-- If a data object is passed we will pass this data object to the provided function when run.
|
|
1289
|
+
-- Schedule uses cron-like schedule and is compatible with most of the standard cron formats.
|
|
1290
|
+
- **remove(name:string|string[]):void**
|
|
1291
|
+
Removes one or multiple jobs by name from the schedule.
|
|
1292
|
+
- **run(): Promise<void>**
|
|
1293
|
+
Runs the schedule.
|
|
1294
|
+
- **stopAutomaticRun():void**
|
|
1295
|
+
Stops automatic running (if enabled)
|
|
1296
|
+
- **startAutomaticRun():void**
|
|
1297
|
+
Start automatic running (if not enabled)
|
|
1298
|
+
- **static isCronSchedule(val:string):boolean**
|
|
1299
|
+
Returns true if the provided value is a valid cron schedule, false if it isnt
|
|
1300
|
+
- **static cronShouldRun(val:string, timeZone: string|null):boolean**
|
|
1301
|
+
Given a cron schedule and an optional timeZone returns whether or not the cron schedule should be run now
|
|
1302
|
+
|
|
1303
|
+
##### Notes:
|
|
1304
|
+
- **cron:**
|
|
1305
|
+
This is not a cron replacement but rather a module that can be used at runtime to handle cron-like schedules in a seamless way.
|
|
1306
|
+
- **auto:**
|
|
1307
|
+
If running this on a system that is replicated it **is highly advised** to not turn on automatic runs but rather build a way to trigger the schedule on a single instance into your networking.
|
|
1308
|
+
|
|
1114
1309
|
### number/is(val:unknown)
|
|
1115
1310
|
Check if a variable is a number
|
|
1116
1311
|
```typescript
|
package/array/mapFn.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
type MapOptions = {
|
|
1
|
+
type MapOptions<T, U = T> = {
|
|
2
2
|
/**
|
|
3
3
|
* Allow merging existing keys or not, if not keys will be overriden if they exist
|
|
4
4
|
* (default=false)
|
|
@@ -11,6 +11,11 @@ type MapOptions = {
|
|
|
11
11
|
* {12: {uid: 12, b: 'ho'}}
|
|
12
12
|
*/
|
|
13
13
|
merge?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* A custom transform function that is applied to each element before mapping.
|
|
16
|
+
* Its output type `U` becomes the value type of the resulting map.
|
|
17
|
+
*/
|
|
18
|
+
transform_fn?: (el: T) => U;
|
|
14
19
|
};
|
|
15
20
|
type MapFn<T extends Record<string, any>> = (entry: T) => (string | number | boolean);
|
|
16
21
|
/**
|
|
@@ -26,5 +31,5 @@ type MapFn<T extends Record<string, any>> = (entry: T) => (string | number | boo
|
|
|
26
31
|
* @param {MapFn} fn - Handler function which is run for each of the objects and should return a string or number
|
|
27
32
|
* @param {MapOptions?} opts - Options object to override built-in defaults
|
|
28
33
|
*/
|
|
29
|
-
declare function mapFn<T extends Record<string, any
|
|
34
|
+
declare function mapFn<T extends Record<string, any>, U extends Record<string, any> = T>(arr: T[], fn: MapFn<T>, opts?: MapOptions<T, U>): Record<string, U>;
|
|
30
35
|
export { mapFn, mapFn as default };
|
package/array/mapFn.js
CHANGED
|
@@ -8,16 +8,17 @@ function mapFn(arr, fn, opts) {
|
|
|
8
8
|
typeof fn !== 'function')
|
|
9
9
|
return {};
|
|
10
10
|
const MERGE = opts?.merge === true;
|
|
11
|
+
const TRANSFORM_FN = opts?.transform_fn;
|
|
11
12
|
const map = {};
|
|
12
13
|
for (let i = 0; i < arr.length; i++) {
|
|
13
14
|
const el = arr[i];
|
|
14
15
|
if (Object.prototype.toString.call(el) !== '[object Object]')
|
|
15
16
|
continue;
|
|
16
17
|
let hash = fn(el);
|
|
17
|
-
if (Number.isFinite(hash) ||
|
|
18
|
-
(typeof hash === 'string' && hash.length)) {
|
|
18
|
+
if (Number.isFinite(hash) || (typeof hash === 'string' && hash.length)) {
|
|
19
19
|
hash = hash + '';
|
|
20
|
-
|
|
20
|
+
const transformed = TRANSFORM_FN ? TRANSFORM_FN(el) : el;
|
|
21
|
+
map[hash] = MERGE && hash in map ? (0, merge_1.merge)(map[hash], transformed, { union: true }) : transformed;
|
|
21
22
|
}
|
|
22
23
|
}
|
|
23
24
|
return map;
|
package/array/mapFnAsMap.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
type MapOptions = {
|
|
1
|
+
type MapOptions<T, U = T> = {
|
|
2
2
|
/**
|
|
3
3
|
* Allow merging existing keys or not, if not keys will be overriden if they exist
|
|
4
4
|
* (default=false)
|
|
@@ -11,6 +11,11 @@ type MapOptions = {
|
|
|
11
11
|
* {12: {uid: 12, b: 'ho'}}
|
|
12
12
|
*/
|
|
13
13
|
merge?: boolean;
|
|
14
|
+
/**
|
|
15
|
+
* A custom transform function that is applied to each element before mapping.
|
|
16
|
+
* Its output type `U` becomes the value type of the resulting map.
|
|
17
|
+
*/
|
|
18
|
+
transform_fn?: (el: T) => U;
|
|
14
19
|
};
|
|
15
20
|
type MapFn<T extends Record<string, any>> = (entry: T) => string | number | null;
|
|
16
21
|
/**
|
|
@@ -29,5 +34,5 @@ type MapFn<T extends Record<string, any>> = (entry: T) => string | number | null
|
|
|
29
34
|
* @param {MapFn} fn - Handler function which is run for each of the objects and should return a string or number
|
|
30
35
|
* @param {MapOptions?} opts - Options object to override built-in defaults
|
|
31
36
|
*/
|
|
32
|
-
declare function mapFnAsMap<T extends Record<string, any>, TFN extends MapFn<T
|
|
37
|
+
declare function mapFnAsMap<T extends Record<string, any>, TFN extends MapFn<T>, U extends Record<string, any> = T>(arr: T[], fn: TFN, opts?: MapOptions<T, U>): Map<NonNullable<ReturnType<TFN>>, U>;
|
|
33
38
|
export { mapFnAsMap, mapFnAsMap as default };
|
package/array/mapFnAsMap.js
CHANGED
|
@@ -8,15 +8,17 @@ function mapFnAsMap(arr, fn, opts) {
|
|
|
8
8
|
typeof fn !== 'function')
|
|
9
9
|
return new Map();
|
|
10
10
|
const MERGE = opts?.merge === true;
|
|
11
|
+
const TRANSFORM_FN = opts?.transform_fn;
|
|
11
12
|
const map = new Map();
|
|
12
13
|
for (let i = 0; i < arr.length; i++) {
|
|
13
14
|
const el = arr[i];
|
|
14
15
|
if (Object.prototype.toString.call(el) !== '[object Object]')
|
|
15
16
|
continue;
|
|
16
17
|
const hash = fn(el);
|
|
17
|
-
if (Number.isFinite(hash) ||
|
|
18
|
-
|
|
19
|
-
map.set(hash, MERGE && map.has(hash) ? (0, merge_1.merge)(map.get(hash),
|
|
18
|
+
if (Number.isFinite(hash) || (typeof hash === 'string' && hash.trim().length)) {
|
|
19
|
+
const transformed = TRANSFORM_FN ? TRANSFORM_FN(el) : el;
|
|
20
|
+
map.set(hash, MERGE && map.has(hash) ? (0, merge_1.merge)(map.get(hash), transformed, { union: true }) : transformed);
|
|
21
|
+
}
|
|
20
22
|
}
|
|
21
23
|
return map;
|
|
22
24
|
}
|
package/array/mapKey.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
type MapOptions<T> = {
|
|
1
|
+
type MapOptions<T, U = T> = {
|
|
2
2
|
/**
|
|
3
3
|
* Allow merging existing keys or not, if not keys will be overriden if they exist
|
|
4
4
|
* (default=false)
|
|
@@ -15,6 +15,11 @@ type MapOptions<T> = {
|
|
|
15
15
|
* Pass a custom filter function which will be run in O(n) while iterating
|
|
16
16
|
*/
|
|
17
17
|
filter_fn?: (el: T) => boolean;
|
|
18
|
+
/**
|
|
19
|
+
* A custom transformer function to modify each element before mapping.
|
|
20
|
+
* Its output type `U` becomes the value type of the resulting map.
|
|
21
|
+
*/
|
|
22
|
+
transform_fn?: (el: T) => U;
|
|
18
23
|
};
|
|
19
24
|
/**
|
|
20
25
|
* Map an object array into a kv-object by passing a common key that exists on the objects. Objects for
|
|
@@ -29,5 +34,5 @@ type MapOptions<T> = {
|
|
|
29
34
|
* @param {string} key - Key to map by
|
|
30
35
|
* @param {MapOptions?} opts - Options object to override built-in defaults
|
|
31
36
|
*/
|
|
32
|
-
declare function mapKey<T extends Record<string, any>, TKey extends keyof T>(arr: T[], key: TKey, opts?: MapOptions<T>): Record<string,
|
|
37
|
+
declare function mapKey<T extends Record<string, any>, U extends Record<string, any> = T, TKey extends keyof T = string>(arr: T[], key: TKey, opts?: MapOptions<T, U>): Record<string, U>;
|
|
33
38
|
export { mapKey, mapKey as default };
|
package/array/mapKey.js
CHANGED
|
@@ -11,13 +11,15 @@ function mapKey(arr, key, opts) {
|
|
|
11
11
|
return {};
|
|
12
12
|
const FILTER_FN = opts?.filter_fn;
|
|
13
13
|
const MERGE = opts?.merge === true;
|
|
14
|
+
const TRANSFORMER = opts?.transform_fn;
|
|
14
15
|
const map = {};
|
|
15
16
|
for (let i = 0; i < arr.length; i++) {
|
|
16
17
|
const el = arr[i];
|
|
17
18
|
const el_key = el?.[key];
|
|
18
|
-
if (el_key !== undefined &&
|
|
19
|
-
|
|
20
|
-
map[el_key] =
|
|
19
|
+
if (el_key !== undefined && (!FILTER_FN || FILTER_FN(el))) {
|
|
20
|
+
const transformed = TRANSFORMER ? TRANSFORMER(el) : el;
|
|
21
|
+
map[el_key] = MERGE && el_key in map ? (0, merge_1.merge)(map[el_key], transformed, { union: true }) : transformed;
|
|
22
|
+
}
|
|
21
23
|
}
|
|
22
24
|
return map;
|
|
23
25
|
}
|
package/array/mapKeyAsMap.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
type MapOptions<T> = {
|
|
1
|
+
type MapOptions<T, U = T> = {
|
|
2
2
|
/**
|
|
3
3
|
* Allow merging existing keys or not, if not keys will be overriden if they exist
|
|
4
4
|
* (default=false)
|
|
@@ -15,6 +15,11 @@ type MapOptions<T> = {
|
|
|
15
15
|
* Pass a custom filter function which will be run in O(n) while iterating
|
|
16
16
|
*/
|
|
17
17
|
filter_fn?: (el: T) => boolean;
|
|
18
|
+
/**
|
|
19
|
+
* A custom transformer function to modify each element before mapping.
|
|
20
|
+
* Its output type `U` becomes the value type of the resulting map.
|
|
21
|
+
*/
|
|
22
|
+
transform_fn?: (el: T) => U;
|
|
18
23
|
};
|
|
19
24
|
/**
|
|
20
25
|
* Map an object array into a Map by passing a common key that exists on the objects. Objects for
|
|
@@ -32,5 +37,5 @@ type MapOptions<T> = {
|
|
|
32
37
|
* @param {string} key - Key to map by
|
|
33
38
|
* @param {MapOptions?} opts - Options object to override built-in defaults
|
|
34
39
|
*/
|
|
35
|
-
declare function mapKeyAsMap<T extends Record<string, any>, TKey extends keyof T>(arr: T[], key: TKey, opts?: MapOptions<T>): Map<T[TKey],
|
|
40
|
+
declare function mapKeyAsMap<T extends Record<string, any>, U extends Record<string, any> = T, TKey extends keyof T = string>(arr: T[], key: TKey, opts?: MapOptions<T, U>): Map<T[TKey], U>;
|
|
36
41
|
export { mapKeyAsMap, mapKeyAsMap as default };
|
package/array/mapKeyAsMap.js
CHANGED
|
@@ -11,13 +11,15 @@ function mapKeyAsMap(arr, key, opts) {
|
|
|
11
11
|
return new Map();
|
|
12
12
|
const FILTER_FN = opts?.filter_fn;
|
|
13
13
|
const MERGE = opts?.merge === true;
|
|
14
|
+
const TRANSFORMER = opts?.transform_fn;
|
|
14
15
|
const map = new Map();
|
|
15
16
|
for (let i = 0; i < arr.length; i++) {
|
|
16
17
|
const el = arr[i];
|
|
17
18
|
const el_key = el?.[key];
|
|
18
|
-
if (el_key !== undefined &&
|
|
19
|
-
(
|
|
20
|
-
map.set(el_key, MERGE && map.has(el_key) ? (0, merge_1.merge)(map.get(el_key),
|
|
19
|
+
if (el_key !== undefined && (!FILTER_FN || FILTER_FN(el))) {
|
|
20
|
+
const transformed = (TRANSFORMER ? TRANSFORMER(el) : el);
|
|
21
|
+
map.set(el_key, MERGE && map.has(el_key) ? (0, merge_1.merge)(map.get(el_key), transformed, { union: true }) : transformed);
|
|
22
|
+
}
|
|
21
23
|
}
|
|
22
24
|
return map;
|
|
23
25
|
}
|
package/date/format.js
CHANGED
|
@@ -178,6 +178,9 @@ function getSpecChain(spec) {
|
|
|
178
178
|
spec_cache.set(spec, result);
|
|
179
179
|
return result;
|
|
180
180
|
}
|
|
181
|
+
const SPEC_ALIASES = {
|
|
182
|
+
ISO: 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]',
|
|
183
|
+
};
|
|
181
184
|
function format(val, spec, locale = DEFAULT_LOCALE, zone = DEFAULT_TZ, sow = DEFAULT_SOW) {
|
|
182
185
|
const n_val = (0, convertToDate_1.convertToDate)(val);
|
|
183
186
|
if (n_val === null)
|
|
@@ -188,7 +191,7 @@ function format(val, spec, locale = DEFAULT_LOCALE, zone = DEFAULT_TZ, sow = DEF
|
|
|
188
191
|
throw new TypeError('format: locale must be a string');
|
|
189
192
|
if (typeof zone !== 'string')
|
|
190
193
|
throw new TypeError('format: zone must be a string');
|
|
191
|
-
const n_spec = getSpecChain(spec);
|
|
194
|
+
const n_spec = getSpecChain(SPEC_ALIASES[spec] || spec);
|
|
192
195
|
if (!n_spec)
|
|
193
196
|
return n_val.toISOString();
|
|
194
197
|
const d = toZone(n_val, zone);
|
package/index.d.ts
CHANGED
|
@@ -52,35 +52,39 @@ declare module "object/merge" {
|
|
|
52
52
|
export { merge, merge as default };
|
|
53
53
|
}
|
|
54
54
|
declare module "array/mapFn" {
|
|
55
|
-
type MapOptions = {
|
|
55
|
+
type MapOptions<T, U = T> = {
|
|
56
56
|
merge?: boolean;
|
|
57
|
+
transform_fn?: (el: T) => U;
|
|
57
58
|
};
|
|
58
59
|
type MapFn<T extends Record<string, any>> = (entry: T) => (string | number | boolean);
|
|
59
|
-
function mapFn<T extends Record<string, any
|
|
60
|
+
function mapFn<T extends Record<string, any>, U extends Record<string, any> = T>(arr: T[], fn: MapFn<T>, opts?: MapOptions<T, U>): Record<string, U>;
|
|
60
61
|
export { mapFn, mapFn as default };
|
|
61
62
|
}
|
|
62
63
|
declare module "array/mapFnAsMap" {
|
|
63
|
-
type MapOptions = {
|
|
64
|
+
type MapOptions<T, U = T> = {
|
|
64
65
|
merge?: boolean;
|
|
66
|
+
transform_fn?: (el: T) => U;
|
|
65
67
|
};
|
|
66
68
|
type MapFn<T extends Record<string, any>> = (entry: T) => string | number | null;
|
|
67
|
-
function mapFnAsMap<T extends Record<string, any>, TFN extends MapFn<T
|
|
69
|
+
function mapFnAsMap<T extends Record<string, any>, TFN extends MapFn<T>, U extends Record<string, any> = T>(arr: T[], fn: TFN, opts?: MapOptions<T, U>): Map<NonNullable<ReturnType<TFN>>, U>;
|
|
68
70
|
export { mapFnAsMap, mapFnAsMap as default };
|
|
69
71
|
}
|
|
70
72
|
declare module "array/mapKey" {
|
|
71
|
-
type MapOptions<T> = {
|
|
73
|
+
type MapOptions<T, U = T> = {
|
|
72
74
|
merge?: boolean;
|
|
73
75
|
filter_fn?: (el: T) => boolean;
|
|
76
|
+
transform_fn?: (el: T) => U;
|
|
74
77
|
};
|
|
75
|
-
function mapKey<T extends Record<string, any>, TKey extends keyof T>(arr: T[], key: TKey, opts?: MapOptions<T>): Record<string,
|
|
78
|
+
function mapKey<T extends Record<string, any>, U extends Record<string, any> = T, TKey extends keyof T = string>(arr: T[], key: TKey, opts?: MapOptions<T, U>): Record<string, U>;
|
|
76
79
|
export { mapKey, mapKey as default };
|
|
77
80
|
}
|
|
78
81
|
declare module "array/mapKeyAsMap" {
|
|
79
|
-
type MapOptions<T> = {
|
|
82
|
+
type MapOptions<T, U = T> = {
|
|
80
83
|
merge?: boolean;
|
|
81
84
|
filter_fn?: (el: T) => boolean;
|
|
85
|
+
transform_fn?: (el: T) => U;
|
|
82
86
|
};
|
|
83
|
-
function mapKeyAsMap<T extends Record<string, any>, TKey extends keyof T>(arr: T[], key: TKey, opts?: MapOptions<T>): Map<T[TKey],
|
|
87
|
+
function mapKeyAsMap<T extends Record<string, any>, U extends Record<string, any> = T, TKey extends keyof T = string>(arr: T[], key: TKey, opts?: MapOptions<T, U>): Map<T[TKey], U>;
|
|
84
88
|
export { mapKeyAsMap, mapKeyAsMap as default };
|
|
85
89
|
}
|
|
86
90
|
declare module "array/mapPrimitive" {
|
|
@@ -691,3 +695,54 @@ declare module "modules/PubSub" {
|
|
|
691
695
|
}
|
|
692
696
|
export { PubSub, PubSub as default };
|
|
693
697
|
}
|
|
698
|
+
declare module "modules/Scheduler" {
|
|
699
|
+
export type LogObject = {
|
|
700
|
+
name: string;
|
|
701
|
+
msg: string;
|
|
702
|
+
on: Date;
|
|
703
|
+
data?: unknown;
|
|
704
|
+
err: Error;
|
|
705
|
+
};
|
|
706
|
+
export type LogFn = (log: LogObject) => void;
|
|
707
|
+
export type SchedulerOptions = {
|
|
708
|
+
logger?: LogFn;
|
|
709
|
+
name?: string;
|
|
710
|
+
timeZone?: string | null;
|
|
711
|
+
parallel?: boolean | number;
|
|
712
|
+
auto?: boolean;
|
|
713
|
+
};
|
|
714
|
+
type SchedulerJobFn = ((data: any) => void | Promise<void>);
|
|
715
|
+
type SchedulerJobFnDefault = (data: unknown) => void | Promise<void>;
|
|
716
|
+
export type SchedulerJob<T extends SchedulerJobFn = SchedulerJobFnDefault> = {
|
|
717
|
+
name: string;
|
|
718
|
+
schedule: string;
|
|
719
|
+
timeZone?: string | null;
|
|
720
|
+
fn: T;
|
|
721
|
+
data?: Parameters<T>[0];
|
|
722
|
+
};
|
|
723
|
+
class Scheduler {
|
|
724
|
+
#private;
|
|
725
|
+
constructor(options?: SchedulerOptions);
|
|
726
|
+
get name(): string;
|
|
727
|
+
get timeZone(): string | null;
|
|
728
|
+
get parallel(): number | boolean;
|
|
729
|
+
get isAutomatic(): boolean;
|
|
730
|
+
get jobs(): {
|
|
731
|
+
data?: {} | undefined;
|
|
732
|
+
name: string;
|
|
733
|
+
schedule: string;
|
|
734
|
+
timeZone: string | null;
|
|
735
|
+
}[];
|
|
736
|
+
add<T extends SchedulerJobFn>(job: SchedulerJob<T>): boolean;
|
|
737
|
+
remove(name: string | string[]): void;
|
|
738
|
+
run(): Promise<void>;
|
|
739
|
+
stopAutomaticRun(): void;
|
|
740
|
+
startAutomaticRun(): void;
|
|
741
|
+
private static convertToMap;
|
|
742
|
+
private static getTimeParts;
|
|
743
|
+
private static checkTimeAgainstMap;
|
|
744
|
+
static isCronSchedule(raw: string): boolean;
|
|
745
|
+
static cronShouldRun(schedule: string, timeZone?: string | null): boolean;
|
|
746
|
+
}
|
|
747
|
+
export { Scheduler, Scheduler as default };
|
|
748
|
+
}
|
package/modules/PubSub.js
CHANGED
|
@@ -163,7 +163,7 @@ _PubSub_subscriptions = new WeakMap(), _PubSub_name = new WeakMap(), _PubSub_log
|
|
|
163
163
|
const out = sub.run(data);
|
|
164
164
|
if (sub.once)
|
|
165
165
|
map.subscribers.delete(client_id);
|
|
166
|
-
if (
|
|
166
|
+
if ((0, is_2.isFunction)(out?.catch) && (0, is_2.isFunction)(out?.then)) {
|
|
167
167
|
Promise.resolve(out).catch(err => {
|
|
168
168
|
__classPrivateFieldGet(this, _PubSub_log, "f").call(this, {
|
|
169
169
|
name: __classPrivateFieldGet(this, _PubSub_name, "f"),
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
export type LogObject = {
|
|
2
|
+
name: string;
|
|
3
|
+
msg: string;
|
|
4
|
+
on: Date;
|
|
5
|
+
data?: unknown;
|
|
6
|
+
err: Error;
|
|
7
|
+
};
|
|
8
|
+
export type LogFn = (log: LogObject) => void;
|
|
9
|
+
export type SchedulerOptions = {
|
|
10
|
+
/**
|
|
11
|
+
* Custom logger function, will receive an instance of LogObject when an error is thrown
|
|
12
|
+
*/
|
|
13
|
+
logger?: LogFn;
|
|
14
|
+
/**
|
|
15
|
+
* Name of the pubsub (used in logs as well)
|
|
16
|
+
*/
|
|
17
|
+
name?: string;
|
|
18
|
+
/**
|
|
19
|
+
* Default timeZone to use
|
|
20
|
+
*/
|
|
21
|
+
timeZone?: string | null;
|
|
22
|
+
/**
|
|
23
|
+
* Whether or not to run async jobs in parallel or not (defaults to true)
|
|
24
|
+
*/
|
|
25
|
+
parallel?: boolean | number;
|
|
26
|
+
/**
|
|
27
|
+
* If set to true will automatically run the schedule every 60 seconds (defaults to false)
|
|
28
|
+
*/
|
|
29
|
+
auto?: boolean;
|
|
30
|
+
};
|
|
31
|
+
type SchedulerJobFn = ((data: any) => void | Promise<void>);
|
|
32
|
+
type SchedulerJobFnDefault = (data: unknown) => void | Promise<void>;
|
|
33
|
+
export type SchedulerJob<T extends SchedulerJobFn = SchedulerJobFnDefault> = {
|
|
34
|
+
name: string;
|
|
35
|
+
schedule: string;
|
|
36
|
+
timeZone?: string | null;
|
|
37
|
+
fn: T;
|
|
38
|
+
data?: Parameters<T>[0];
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* The Scheduler class.
|
|
42
|
+
*
|
|
43
|
+
* Instantiate this module, add multiple scheduled tasks,
|
|
44
|
+
* and then call run() to execute those whose cron schedule matches the current time.
|
|
45
|
+
*/
|
|
46
|
+
declare class Scheduler {
|
|
47
|
+
#private;
|
|
48
|
+
constructor(options?: SchedulerOptions);
|
|
49
|
+
/**
|
|
50
|
+
* Getter returning the name of the scheduler.
|
|
51
|
+
*/
|
|
52
|
+
get name(): string;
|
|
53
|
+
/**
|
|
54
|
+
* Getter returning the scheduler's timeZone setting
|
|
55
|
+
*/
|
|
56
|
+
get timeZone(): string | null;
|
|
57
|
+
/**
|
|
58
|
+
* Getter returning the scheduler's parallel setting
|
|
59
|
+
*/
|
|
60
|
+
get parallel(): number | boolean;
|
|
61
|
+
/**
|
|
62
|
+
* Getter returning whether or not the schedule is automatically running every 60 seconds
|
|
63
|
+
*/
|
|
64
|
+
get isAutomatic(): boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Returns the configured jobs array (bar fn/internals) to allow for introspection and debugging
|
|
67
|
+
*/
|
|
68
|
+
get jobs(): {
|
|
69
|
+
data?: {} | undefined;
|
|
70
|
+
name: string;
|
|
71
|
+
schedule: string;
|
|
72
|
+
timeZone: string | null;
|
|
73
|
+
}[];
|
|
74
|
+
/**
|
|
75
|
+
* Add a job to the scheduler.
|
|
76
|
+
*
|
|
77
|
+
* @param {SchedulerJob} job - Raw job object to be added to the jobs list
|
|
78
|
+
*/
|
|
79
|
+
add<T extends SchedulerJobFn>(job: SchedulerJob<T>): boolean;
|
|
80
|
+
/**
|
|
81
|
+
* Remove a job from the schedule by name
|
|
82
|
+
*
|
|
83
|
+
* @param {string|string[]} name - Name or array of names of the job(s) to remove
|
|
84
|
+
*/
|
|
85
|
+
remove(name: string | string[]): void;
|
|
86
|
+
/**
|
|
87
|
+
* Iterate through all added jobs and execute those that should run at the current time.
|
|
88
|
+
*/
|
|
89
|
+
run(): Promise<void>;
|
|
90
|
+
/**
|
|
91
|
+
* Stops automatic running of the schedule
|
|
92
|
+
*/
|
|
93
|
+
stopAutomaticRun(): void;
|
|
94
|
+
/**
|
|
95
|
+
* Starts automatic running of the schedule
|
|
96
|
+
*/
|
|
97
|
+
startAutomaticRun(): void;
|
|
98
|
+
/**
|
|
99
|
+
* MARK: Private
|
|
100
|
+
*/
|
|
101
|
+
/**
|
|
102
|
+
* Helper which converts a cron schedule to a map for easy processing down the line
|
|
103
|
+
*
|
|
104
|
+
* @param {string} schedule - Raw cron schedule to process
|
|
105
|
+
*/
|
|
106
|
+
private static convertToMap;
|
|
107
|
+
/**
|
|
108
|
+
* Returns the time in the provided zone as an object of parts
|
|
109
|
+
*
|
|
110
|
+
* @param {Date} date - Date to get parts from
|
|
111
|
+
* @param {string|null} timeZone - Zone to use
|
|
112
|
+
*/
|
|
113
|
+
private static getTimeParts;
|
|
114
|
+
/**
|
|
115
|
+
* Checks the processed time against the processed map and returns true/false if matches or not
|
|
116
|
+
*
|
|
117
|
+
* @param {CronMap} map - Cron Map
|
|
118
|
+
* @param {TimeMap} time - Time Map
|
|
119
|
+
*/
|
|
120
|
+
private static checkTimeAgainstMap;
|
|
121
|
+
/**
|
|
122
|
+
* MARK: Static
|
|
123
|
+
*/
|
|
124
|
+
/**
|
|
125
|
+
* Checks if a string is a valid cron schedule
|
|
126
|
+
*
|
|
127
|
+
* @param {string} raw - Value to verify is a valid schedule
|
|
128
|
+
*/
|
|
129
|
+
static isCronSchedule(raw: string): boolean;
|
|
130
|
+
/**
|
|
131
|
+
* Check if a given cron schedule should run at the current (or specified) time.
|
|
132
|
+
*
|
|
133
|
+
* @param schedule A cron string (5 fields: minute, hour, day of month, month, day of week).
|
|
134
|
+
* @param timeZone Optional IANA time zone string.
|
|
135
|
+
* @returns true if the schedule matches the current time.
|
|
136
|
+
*/
|
|
137
|
+
static cronShouldRun(schedule: string, timeZone?: string | null): boolean;
|
|
138
|
+
}
|
|
139
|
+
export { Scheduler, Scheduler as default };
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
3
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
4
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
5
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
6
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
7
|
+
};
|
|
8
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
9
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
10
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
11
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
12
|
+
};
|
|
13
|
+
var _Scheduler_jobs, _Scheduler_name, _Scheduler_log, _Scheduler_timeZone, _Scheduler_parallel, _Scheduler_timer;
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.default = exports.Scheduler = void 0;
|
|
16
|
+
const format_1 = require("../date/format");
|
|
17
|
+
const toUTC_1 = require("../date/toUTC");
|
|
18
|
+
const isNotEmpty_1 = require("../string/isNotEmpty");
|
|
19
|
+
const is_1 = require("../function/is");
|
|
20
|
+
const number_1 = require("../number");
|
|
21
|
+
const object_1 = require("../object");
|
|
22
|
+
const function_1 = require("../function");
|
|
23
|
+
const boolean_1 = require("../boolean");
|
|
24
|
+
const array_1 = require("../array");
|
|
25
|
+
const RGX_DIGITS = /^\d+$/;
|
|
26
|
+
const LIMITS = {
|
|
27
|
+
minute: [0, 59],
|
|
28
|
+
hour: [0, 23],
|
|
29
|
+
day_of_month: [1, 31],
|
|
30
|
+
month: [1, 12],
|
|
31
|
+
day_of_week: [0, 6],
|
|
32
|
+
};
|
|
33
|
+
function convertPart(part, min, max) {
|
|
34
|
+
if (part === '*')
|
|
35
|
+
return '*';
|
|
36
|
+
const set = new Set();
|
|
37
|
+
if (part.indexOf('/') > -1) {
|
|
38
|
+
const [base, raw_step] = part.split('/', 2);
|
|
39
|
+
const step = parseInt(raw_step, 10);
|
|
40
|
+
let start;
|
|
41
|
+
let end = max;
|
|
42
|
+
if (base === '*') {
|
|
43
|
+
start = min;
|
|
44
|
+
}
|
|
45
|
+
else if (base.indexOf('-') > -1) {
|
|
46
|
+
const chunks = base.split('-', 2);
|
|
47
|
+
start = parseInt(chunks[0], 10);
|
|
48
|
+
end = parseInt(chunks[1], 10);
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
start = parseInt(base, 10);
|
|
52
|
+
}
|
|
53
|
+
for (let i = start; i <= end; i += step)
|
|
54
|
+
set.add(i);
|
|
55
|
+
}
|
|
56
|
+
else if (part.indexOf('-') > -1) {
|
|
57
|
+
const chunks = part.split('-', 2);
|
|
58
|
+
const start = parseInt(chunks[0], 10);
|
|
59
|
+
const end = parseInt(chunks[1], 10);
|
|
60
|
+
for (let i = start; i <= end; i++)
|
|
61
|
+
set.add(i);
|
|
62
|
+
}
|
|
63
|
+
else if (part.indexOf(',') > -1) {
|
|
64
|
+
const chunks = part.split(',');
|
|
65
|
+
for (let i = 0; i < chunks.length; i++)
|
|
66
|
+
set.add(parseInt(chunks[i], 10));
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
set.add(parseInt(part, 10));
|
|
70
|
+
}
|
|
71
|
+
return set;
|
|
72
|
+
}
|
|
73
|
+
function isCronSubpart(part, min, max) {
|
|
74
|
+
if (!RGX_DIGITS.test(part))
|
|
75
|
+
return null;
|
|
76
|
+
const normalized = parseFloat(part);
|
|
77
|
+
if (!(0, number_1.isIntegerBetween)(normalized, min, max))
|
|
78
|
+
return null;
|
|
79
|
+
return normalized;
|
|
80
|
+
}
|
|
81
|
+
function isCronPart(part, min, max) {
|
|
82
|
+
if (part === '*') {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
else if (part.indexOf('/') > -1) {
|
|
86
|
+
const [base, stepStr] = part.split('/', 2);
|
|
87
|
+
const step = parseFloat(stepStr);
|
|
88
|
+
if (!(0, number_1.isIntegerAbove)(step, 0) || !(0, number_1.isIntegerBetween)(step, min, max))
|
|
89
|
+
return false;
|
|
90
|
+
if (base === '*')
|
|
91
|
+
return true;
|
|
92
|
+
let start = min;
|
|
93
|
+
let end = max;
|
|
94
|
+
if (base.indexOf('-') > -1) {
|
|
95
|
+
const chunks = base.split('-');
|
|
96
|
+
if (chunks.length !== 2)
|
|
97
|
+
return false;
|
|
98
|
+
start = isCronSubpart(chunks[0], min, max);
|
|
99
|
+
end = isCronSubpart(chunks[1], min, max);
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
start = isCronSubpart(base, min, max);
|
|
103
|
+
}
|
|
104
|
+
if (start === null || end === null)
|
|
105
|
+
return false;
|
|
106
|
+
if (start > end)
|
|
107
|
+
return false;
|
|
108
|
+
if (step > (end - start))
|
|
109
|
+
return false;
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
else if (part.indexOf('-') > -1) {
|
|
113
|
+
const chunks = part.split('-');
|
|
114
|
+
if (chunks.length !== 2)
|
|
115
|
+
return false;
|
|
116
|
+
const start = isCronSubpart(chunks[0], min, max);
|
|
117
|
+
const end = isCronSubpart(chunks[1], min, max);
|
|
118
|
+
if (start === null || end === null)
|
|
119
|
+
return false;
|
|
120
|
+
return start < end;
|
|
121
|
+
}
|
|
122
|
+
else if (part.indexOf(',') > -1) {
|
|
123
|
+
const chunks = part.split(',');
|
|
124
|
+
for (let i = 0; i < chunks.length; i++) {
|
|
125
|
+
if (isCronSubpart(chunks[i], min, max) === null)
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
return isCronSubpart(part, min, max) !== null;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
class Scheduler {
|
|
135
|
+
constructor(options = {}) {
|
|
136
|
+
_Scheduler_jobs.set(this, []);
|
|
137
|
+
_Scheduler_name.set(this, 'Scheduler');
|
|
138
|
+
_Scheduler_log.set(this, function_1.noop);
|
|
139
|
+
_Scheduler_timeZone.set(this, null);
|
|
140
|
+
_Scheduler_parallel.set(this, true);
|
|
141
|
+
_Scheduler_timer.set(this, null);
|
|
142
|
+
if (!(0, object_1.isObject)(options))
|
|
143
|
+
throw new Error('Scheduler@ctor: options should be an object');
|
|
144
|
+
if ('logger' in options) {
|
|
145
|
+
if (!(0, is_1.isFunction)(options.logger))
|
|
146
|
+
throw new Error('Scheduler@ctor: logger should be a function');
|
|
147
|
+
__classPrivateFieldSet(this, _Scheduler_log, options.logger, "f");
|
|
148
|
+
}
|
|
149
|
+
if ('name' in options) {
|
|
150
|
+
if (!(0, isNotEmpty_1.isNotEmptyString)(options.name))
|
|
151
|
+
throw new Error('Scheduler@ctor: name should be a non-empty string');
|
|
152
|
+
__classPrivateFieldSet(this, _Scheduler_name, options.name.trim(), "f");
|
|
153
|
+
}
|
|
154
|
+
if ('timeZone' in options) {
|
|
155
|
+
if (options.timeZone !== null &&
|
|
156
|
+
!(0, isNotEmpty_1.isNotEmptyString)(options.timeZone))
|
|
157
|
+
throw new Error('Scheduler@ctor: timeZone should be null or a non-empty string');
|
|
158
|
+
__classPrivateFieldSet(this, _Scheduler_timeZone, options.timeZone, "f");
|
|
159
|
+
}
|
|
160
|
+
if ('parallel' in options) {
|
|
161
|
+
if (!(0, boolean_1.isBoolean)(options.parallel) &&
|
|
162
|
+
!(0, number_1.isIntegerAbove)(options.parallel, 0))
|
|
163
|
+
throw new Error('Scheduler@ctor: parallel should be passed as a boolean or int above 0');
|
|
164
|
+
__classPrivateFieldSet(this, _Scheduler_parallel, options.parallel, "f");
|
|
165
|
+
}
|
|
166
|
+
if (options.auto === true)
|
|
167
|
+
this.startAutomaticRun();
|
|
168
|
+
}
|
|
169
|
+
get name() {
|
|
170
|
+
return __classPrivateFieldGet(this, _Scheduler_name, "f");
|
|
171
|
+
}
|
|
172
|
+
get timeZone() {
|
|
173
|
+
return __classPrivateFieldGet(this, _Scheduler_timeZone, "f");
|
|
174
|
+
}
|
|
175
|
+
get parallel() {
|
|
176
|
+
return __classPrivateFieldGet(this, _Scheduler_parallel, "f");
|
|
177
|
+
}
|
|
178
|
+
get isAutomatic() {
|
|
179
|
+
return __classPrivateFieldGet(this, _Scheduler_timer, "f") !== null;
|
|
180
|
+
}
|
|
181
|
+
get jobs() {
|
|
182
|
+
const acc = [];
|
|
183
|
+
for (let i = 0; i < __classPrivateFieldGet(this, _Scheduler_jobs, "f").length; i++) {
|
|
184
|
+
const { name, schedule, timeZone, data = null } = __classPrivateFieldGet(this, _Scheduler_jobs, "f")[i];
|
|
185
|
+
acc.push({
|
|
186
|
+
name,
|
|
187
|
+
schedule,
|
|
188
|
+
timeZone,
|
|
189
|
+
...data !== null && { data: { ...data } },
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
return acc;
|
|
193
|
+
}
|
|
194
|
+
add(job) {
|
|
195
|
+
try {
|
|
196
|
+
if (!Scheduler.isCronSchedule(job?.schedule))
|
|
197
|
+
throw new Error(`${__classPrivateFieldGet(this, _Scheduler_name, "f")}@add: Invalid cron schedule`);
|
|
198
|
+
if (!(0, is_1.isFunction)(job.fn))
|
|
199
|
+
throw new Error(`${__classPrivateFieldGet(this, _Scheduler_name, "f")}@add: Invalid function for job`);
|
|
200
|
+
if (!(0, isNotEmpty_1.isNotEmptyString)(job.name))
|
|
201
|
+
throw new Error(`${__classPrivateFieldGet(this, _Scheduler_name, "f")}@add: Invalid name for job`);
|
|
202
|
+
if ('data' in job && !(0, object_1.isObject)(job.data))
|
|
203
|
+
throw new Error(`${__classPrivateFieldGet(this, _Scheduler_name, "f")}@add: Job data should be an object`);
|
|
204
|
+
__classPrivateFieldGet(this, _Scheduler_jobs, "f").push({
|
|
205
|
+
name: job.name,
|
|
206
|
+
schedule: job.schedule,
|
|
207
|
+
fn: job.fn,
|
|
208
|
+
timeZone: (0, isNotEmpty_1.isNotEmptyString)(job.timeZone) ? job.timeZone : __classPrivateFieldGet(this, _Scheduler_timeZone, "f"),
|
|
209
|
+
map: Scheduler.convertToMap(job.schedule),
|
|
210
|
+
...job.data ? { data: job.data } : {},
|
|
211
|
+
});
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
catch (err) {
|
|
215
|
+
__classPrivateFieldGet(this, _Scheduler_log, "f").call(this, {
|
|
216
|
+
name: __classPrivateFieldGet(this, _Scheduler_name, "f"),
|
|
217
|
+
msg: err.message,
|
|
218
|
+
on: new Date(),
|
|
219
|
+
data: job,
|
|
220
|
+
err: err,
|
|
221
|
+
});
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
remove(name) {
|
|
226
|
+
const names = new Set((0, isNotEmpty_1.isNotEmptyString)(name) ? [name] : Array.isArray(name) ? name : []);
|
|
227
|
+
const jobs = [];
|
|
228
|
+
for (let i = 0; i < __classPrivateFieldGet(this, _Scheduler_jobs, "f").length; i++) {
|
|
229
|
+
const el = __classPrivateFieldGet(this, _Scheduler_jobs, "f")[i];
|
|
230
|
+
if (!names.has(el.name))
|
|
231
|
+
jobs.push(el);
|
|
232
|
+
}
|
|
233
|
+
__classPrivateFieldSet(this, _Scheduler_jobs, jobs, "f");
|
|
234
|
+
}
|
|
235
|
+
async run() {
|
|
236
|
+
const promises = [];
|
|
237
|
+
for (let i = 0; i < __classPrivateFieldGet(this, _Scheduler_jobs, "f").length; i++) {
|
|
238
|
+
const job = __classPrivateFieldGet(this, _Scheduler_jobs, "f")[i];
|
|
239
|
+
const time = Scheduler.getTimeParts(new Date(), job.timeZone);
|
|
240
|
+
if (Scheduler.checkTimeAgainstMap(job.map, time)) {
|
|
241
|
+
try {
|
|
242
|
+
if (__classPrivateFieldGet(this, _Scheduler_parallel, "f")) {
|
|
243
|
+
promises.push(job);
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
await job.fn(job.data);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
catch (err) {
|
|
250
|
+
__classPrivateFieldGet(this, _Scheduler_log, "f").call(this, {
|
|
251
|
+
name: __classPrivateFieldGet(this, _Scheduler_name, "f"),
|
|
252
|
+
msg: `${job.name}: ${err.message}`,
|
|
253
|
+
on: new Date(),
|
|
254
|
+
data: (0, object_1.pick)(job, ['schedule', 'timeZone', 'name', 'data']),
|
|
255
|
+
err,
|
|
256
|
+
});
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
if (__classPrivateFieldGet(this, _Scheduler_parallel, "f") && promises.length) {
|
|
261
|
+
const batches = __classPrivateFieldGet(this, _Scheduler_parallel, "f") === true
|
|
262
|
+
? [promises]
|
|
263
|
+
: (0, array_1.split)(promises, __classPrivateFieldGet(this, _Scheduler_parallel, "f"));
|
|
264
|
+
for (const batch of batches) {
|
|
265
|
+
await Promise.allSettled(batch.map(el => (async () => {
|
|
266
|
+
try {
|
|
267
|
+
await el.fn(el.data);
|
|
268
|
+
}
|
|
269
|
+
catch (err) {
|
|
270
|
+
__classPrivateFieldGet(this, _Scheduler_log, "f").call(this, {
|
|
271
|
+
name: __classPrivateFieldGet(this, _Scheduler_name, "f"),
|
|
272
|
+
msg: `${el.name}: ${err.message}`,
|
|
273
|
+
on: new Date(),
|
|
274
|
+
data: (0, object_1.pick)(el, ['schedule', 'timeZone', 'name', 'data']),
|
|
275
|
+
err: err,
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
})()));
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
stopAutomaticRun() {
|
|
283
|
+
if (!__classPrivateFieldGet(this, _Scheduler_timer, "f"))
|
|
284
|
+
return;
|
|
285
|
+
clearInterval(__classPrivateFieldGet(this, _Scheduler_timer, "f"));
|
|
286
|
+
__classPrivateFieldSet(this, _Scheduler_timer, null, "f");
|
|
287
|
+
}
|
|
288
|
+
startAutomaticRun() {
|
|
289
|
+
this.stopAutomaticRun();
|
|
290
|
+
__classPrivateFieldSet(this, _Scheduler_timer, setInterval(this.run.bind(this), 60000), "f");
|
|
291
|
+
}
|
|
292
|
+
static convertToMap(schedule) {
|
|
293
|
+
const parts = schedule.split(' ');
|
|
294
|
+
return {
|
|
295
|
+
minute: convertPart(parts[0], LIMITS.minute[0], LIMITS.minute[1]),
|
|
296
|
+
hour: convertPart(parts[1], LIMITS.hour[0], LIMITS.hour[1]),
|
|
297
|
+
day_of_month: convertPart(parts[2], LIMITS.day_of_month[0], LIMITS.day_of_month[1]),
|
|
298
|
+
month: convertPart(parts[3], LIMITS.month[0], LIMITS.month[1]),
|
|
299
|
+
day_of_week: convertPart(parts[4], LIMITS.day_of_week[0], LIMITS.day_of_week[1]),
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
static getTimeParts(date, timeZone) {
|
|
303
|
+
const now = (0, toUTC_1.toUTC)(timeZone !== null
|
|
304
|
+
? new Date((0, format_1.format)(date, 'ISO', 'en-US', timeZone))
|
|
305
|
+
: date);
|
|
306
|
+
return {
|
|
307
|
+
minute: now.getUTCMinutes(),
|
|
308
|
+
hour: now.getUTCHours(),
|
|
309
|
+
day_of_month: now.getUTCDate(),
|
|
310
|
+
month: now.getUTCMonth() + 1,
|
|
311
|
+
day_of_week: now.getUTCDay(),
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
static checkTimeAgainstMap(map, time) {
|
|
315
|
+
const { minute, hour, day_of_month, month, day_of_week } = map;
|
|
316
|
+
if (minute !== '*' && !minute.has(time.minute))
|
|
317
|
+
return false;
|
|
318
|
+
if (hour !== '*' && !hour.has(time.hour))
|
|
319
|
+
return false;
|
|
320
|
+
if (day_of_month !== '*' && !day_of_month.has(time.day_of_month))
|
|
321
|
+
return false;
|
|
322
|
+
if (month !== '*' && !month.has(time.month))
|
|
323
|
+
return false;
|
|
324
|
+
if (day_of_week !== '*' && !day_of_week.has(time.day_of_week))
|
|
325
|
+
return false;
|
|
326
|
+
return true;
|
|
327
|
+
}
|
|
328
|
+
static isCronSchedule(raw) {
|
|
329
|
+
if (!(0, isNotEmpty_1.isNotEmptyString)(raw))
|
|
330
|
+
return false;
|
|
331
|
+
const parts = raw.split(' ');
|
|
332
|
+
return (parts.length === 5 &&
|
|
333
|
+
isCronPart(parts[0], LIMITS.minute[0], LIMITS.minute[1]) &&
|
|
334
|
+
isCronPart(parts[1], LIMITS.hour[0], LIMITS.hour[1]) &&
|
|
335
|
+
isCronPart(parts[2], LIMITS.day_of_month[0], LIMITS.day_of_month[1]) &&
|
|
336
|
+
isCronPart(parts[3], LIMITS.month[0], LIMITS.month[1]) &&
|
|
337
|
+
isCronPart(parts[4], LIMITS.day_of_week[0], LIMITS.day_of_week[1]));
|
|
338
|
+
}
|
|
339
|
+
static cronShouldRun(schedule, timeZone = null) {
|
|
340
|
+
if (!Scheduler.isCronSchedule(schedule))
|
|
341
|
+
return false;
|
|
342
|
+
return Scheduler.checkTimeAgainstMap(Scheduler.convertToMap(schedule), Scheduler.getTimeParts(new Date(), (0, isNotEmpty_1.isNotEmptyString)(timeZone) ? timeZone : null));
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
exports.Scheduler = Scheduler;
|
|
346
|
+
exports.default = Scheduler;
|
|
347
|
+
_Scheduler_jobs = new WeakMap(), _Scheduler_name = new WeakMap(), _Scheduler_log = new WeakMap(), _Scheduler_timeZone = new WeakMap(), _Scheduler_parallel = new WeakMap(), _Scheduler_timer = new WeakMap();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@valkyriestudios/utils",
|
|
3
|
-
"version": "12.
|
|
3
|
+
"version": "12.35.0",
|
|
4
4
|
"description": "A collection of single-function utilities for common tasks",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Peter Vermeulen",
|
|
@@ -722,6 +722,18 @@
|
|
|
722
722
|
"default": "./modules/PubSub.js",
|
|
723
723
|
"types": "./modules/PubSub.d.ts"
|
|
724
724
|
},
|
|
725
|
+
"./modules/Scheduler": {
|
|
726
|
+
"import": {
|
|
727
|
+
"default": "./modules/Scheduler.js",
|
|
728
|
+
"types": "./modules/Scheduler.d.ts"
|
|
729
|
+
},
|
|
730
|
+
"require": {
|
|
731
|
+
"default": "./modules/Scheduler.js",
|
|
732
|
+
"types": "./modules/Scheduler.d.ts"
|
|
733
|
+
},
|
|
734
|
+
"default": "./modules/Scheduler.js",
|
|
735
|
+
"types": "./modules/Scheduler.d.ts"
|
|
736
|
+
},
|
|
725
737
|
"./number": {
|
|
726
738
|
"import": {
|
|
727
739
|
"default": "./number/index.js",
|
|
@@ -1144,11 +1156,11 @@
|
|
|
1144
1156
|
}
|
|
1145
1157
|
},
|
|
1146
1158
|
"devDependencies": {
|
|
1147
|
-
"@types/node": "^22.13.
|
|
1159
|
+
"@types/node": "^22.13.11",
|
|
1148
1160
|
"esbuild-register": "^3.6.0",
|
|
1149
|
-
"eslint": "^9.
|
|
1161
|
+
"eslint": "^9.23.0",
|
|
1150
1162
|
"nyc": "^17.1.0",
|
|
1151
1163
|
"typescript": "^5.8.2",
|
|
1152
|
-
"typescript-eslint": "^8.
|
|
1164
|
+
"typescript-eslint": "^8.27.0"
|
|
1153
1165
|
}
|
|
1154
1166
|
}
|