@radically-straightforward/utilities 1.2.2 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +4 -0
- package/README.md +42 -71
- package/build/index.d.mts +32 -57
- package/build/index.d.mts.map +1 -1
- package/build/index.mjs +59 -89
- package/build/index.mjs.map +1 -1
- package/build/index.test.mjs +18 -39
- package/build/index.test.mjs.map +1 -1
- package/package.json +1 -4
- package/source/index.mts +76 -101
- package/source/index.test.mts +25 -57
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
@@ -16,69 +16,6 @@ import * as utilities from "@radically-straightforward/utilities";
|
|
16
16
|
|
17
17
|
<!-- DOCUMENTATION START: ./source/index.mts -->
|
18
18
|
|
19
|
-
### `backgroundJob()`
|
20
|
-
|
21
|
-
```typescript
|
22
|
-
export function backgroundJob(
|
23
|
-
{
|
24
|
-
interval,
|
25
|
-
intervalVariance = 0.1,
|
26
|
-
}: {
|
27
|
-
interval: number;
|
28
|
-
intervalVariance?: number;
|
29
|
-
},
|
30
|
-
job: () => void | Promise<void>,
|
31
|
-
): {
|
32
|
-
run: () => void;
|
33
|
-
stop: () => void;
|
34
|
-
};
|
35
|
-
```
|
36
|
-
|
37
|
-
Start a background job that runs every `interval`.
|
38
|
-
|
39
|
-
This is different from `setInterval()` in the following ways:
|
40
|
-
|
41
|
-
1. The interval counts **between** jobs, so slow background jobs don’t get called concurrently:
|
42
|
-
|
43
|
-
```
|
44
|
-
setInterval()
|
45
|
-
| SLOW BACKGROUND JOB |
|
46
|
-
| INTERVAL | SLOW BACKGROUND JOB |
|
47
|
-
| INTERVAL | ...
|
48
|
-
|
49
|
-
backgroundJob()
|
50
|
-
| SLOW BACKGROUND JOB | INTERVAL | SLOW BACKGROUND JOB | INTERVAL | ...
|
51
|
-
```
|
52
|
-
|
53
|
-
2. We introduce a random `intervalVariance` to avoid many background jobs from starting at the same time and overloading the machine.
|
54
|
-
|
55
|
-
3. You may use `backgroundJob.run()` to force the background job to run right away. If the background job is already running, calling `backgroundJob.run()` schedules it to run again as soon as possible (with a wait interval of 0).
|
56
|
-
|
57
|
-
4. You may use `backgroundJob.stop()` to stop the background job. If the background job is running, it will finish but it will not be scheduled to run again. This is similar to how an HTTP server may terminate gracefully by stopping accepting new requests but finishing responding to existing requests. After a job has been stopped, you may not `backgroundJob.run()` it again (calling `backgroundJob.run()` has no effect).
|
58
|
-
|
59
|
-
5. In Node.js the background job is stopped on [`"gracefulTermination"`](https://github.com/radically-straightforward/radically-straightforward/tree/main/node#graceful-termination).
|
60
|
-
|
61
|
-
**Example**
|
62
|
-
|
63
|
-
```javascript
|
64
|
-
import * as utilities from "@radically-straightforward/utilities";
|
65
|
-
|
66
|
-
const backgroundJob = utilities.backgroundJob(
|
67
|
-
{ interval: 3 * 1000 },
|
68
|
-
async () => {
|
69
|
-
console.log("backgroundJob(): Running background job...");
|
70
|
-
await utilities.sleep(3 * 1000);
|
71
|
-
console.log("backgroundJob(): ...finished running background job.");
|
72
|
-
},
|
73
|
-
);
|
74
|
-
process.on("SIGTSTP", () => {
|
75
|
-
backgroundJob.run();
|
76
|
-
});
|
77
|
-
console.log(
|
78
|
-
"backgroundJob(): Press ⌃Z to force background job to run and ⌃C to continue...",
|
79
|
-
);
|
80
|
-
```
|
81
|
-
|
82
19
|
### `sleep()`
|
83
20
|
|
84
21
|
```typescript
|
@@ -158,14 +95,6 @@ export const ISODateRegExp: RegExp;
|
|
158
95
|
|
159
96
|
A regular expression that matches ISO dates, for example, `2024-04-01T14:19:48.162Z`.
|
160
97
|
|
161
|
-
### `localizedDateRegExp`
|
162
|
-
|
163
|
-
```typescript
|
164
|
-
export const localizedDateRegExp: RegExp;
|
165
|
-
```
|
166
|
-
|
167
|
-
A regular expression that matches localized dates, for example, `2024-04-01 15:20`.
|
168
|
-
|
169
98
|
### `Intern`
|
170
99
|
|
171
100
|
```typescript
|
@@ -307,4 +236,46 @@ Similar to `collections-deep-equal` but either incomplete, or lacking type defin
|
|
307
236
|
- <https://twitter.com/swannodette/status/1067962983924539392>
|
308
237
|
- <https://gist.github.com/modernserf/c000e62d40f678cf395e3f360b9b0e43>
|
309
238
|
|
239
|
+
### `backgroundJob()`
|
240
|
+
|
241
|
+
```typescript
|
242
|
+
export function backgroundJob(
|
243
|
+
{
|
244
|
+
interval,
|
245
|
+
onStop = () => {},
|
246
|
+
}: {
|
247
|
+
interval: number;
|
248
|
+
onStop?: () => void | Promise<void>;
|
249
|
+
},
|
250
|
+
job: () => void | Promise<void>,
|
251
|
+
): {
|
252
|
+
run: () => Promise<void>;
|
253
|
+
stop: () => Promise<void>;
|
254
|
+
};
|
255
|
+
```
|
256
|
+
|
257
|
+
> **Note:** This is a lower level utility. See `@radically-straightforward/node`’s and `@radically-straightforward/javascript`’s extensions to `backgroundJob()` that are better suited for their specific environments.
|
258
|
+
|
259
|
+
Start a background job that runs every `interval`.
|
260
|
+
|
261
|
+
`backgroundJob()` is different from `setInterval()` in the following ways:
|
262
|
+
|
263
|
+
1. The interval counts **between** jobs, so slow background jobs don’t get called concurrently:
|
264
|
+
|
265
|
+
```
|
266
|
+
setInterval()
|
267
|
+
| SLOW BACKGROUND JOB |
|
268
|
+
| INTERVAL | SLOW BACKGROUND JOB |
|
269
|
+
| INTERVAL | ...
|
270
|
+
|
271
|
+
backgroundJob()
|
272
|
+
| SLOW BACKGROUND JOB | INTERVAL | SLOW BACKGROUND JOB | INTERVAL | ...
|
273
|
+
```
|
274
|
+
|
275
|
+
2. You may use `backgroundJob.run()` to force the background job to run right away. If the background job is already running, calling `backgroundJob.run()` schedules it to run again as soon as possible (with a wait interval of `0`).
|
276
|
+
|
277
|
+
3. You may use `backgroundJob.stop()` to stop the background job. If the background job is in the middle of running, it will finish but it will not be scheduled to run again. This is similar to how an HTTP server may terminate gracefully by stopping accepting new requests but finishing responding to existing requests. After a job has been stopped, you may not `backgroundJob.run()` it again (calling `backgroundJob.run()` has no effect).
|
278
|
+
|
279
|
+
4. We introduce a random interval variance of 10% on top of the given `interval` to avoid many background jobs from starting at the same time and overloading the machine.
|
280
|
+
|
310
281
|
<!-- DOCUMENTATION END: ./source/index.mts -->
|
package/build/index.d.mts
CHANGED
@@ -1,56 +1,3 @@
|
|
1
|
-
/**
|
2
|
-
* Start a background job that runs every `interval`.
|
3
|
-
*
|
4
|
-
* This is different from `setInterval()` in the following ways:
|
5
|
-
*
|
6
|
-
* 1. The interval counts **between** jobs, so slow background jobs don’t get called concurrently:
|
7
|
-
*
|
8
|
-
* ```
|
9
|
-
* setInterval()
|
10
|
-
* | SLOW BACKGROUND JOB |
|
11
|
-
* | INTERVAL | SLOW BACKGROUND JOB |
|
12
|
-
* | INTERVAL | ...
|
13
|
-
*
|
14
|
-
* backgroundJob()
|
15
|
-
* | SLOW BACKGROUND JOB | INTERVAL | SLOW BACKGROUND JOB | INTERVAL | ...
|
16
|
-
* ```
|
17
|
-
*
|
18
|
-
* 2. We introduce a random `intervalVariance` to avoid many background jobs from starting at the same time and overloading the machine.
|
19
|
-
*
|
20
|
-
* 3. You may use `backgroundJob.run()` to force the background job to run right away. If the background job is already running, calling `backgroundJob.run()` schedules it to run again as soon as possible (with a wait interval of 0).
|
21
|
-
*
|
22
|
-
* 4. You may use `backgroundJob.stop()` to stop the background job. If the background job is running, it will finish but it will not be scheduled to run again. This is similar to how an HTTP server may terminate gracefully by stopping accepting new requests but finishing responding to existing requests. After a job has been stopped, you may not `backgroundJob.run()` it again (calling `backgroundJob.run()` has no effect).
|
23
|
-
*
|
24
|
-
* 5. In Node.js the background job is stopped on [`"gracefulTermination"`](https://github.com/radically-straightforward/radically-straightforward/tree/main/node#graceful-termination).
|
25
|
-
*
|
26
|
-
* **Example**
|
27
|
-
*
|
28
|
-
* ```javascript
|
29
|
-
* import * as utilities from "@radically-straightforward/utilities";
|
30
|
-
*
|
31
|
-
* const backgroundJob = utilities.backgroundJob(
|
32
|
-
* { interval: 3 * 1000 },
|
33
|
-
* async () => {
|
34
|
-
* console.log("backgroundJob(): Running background job...");
|
35
|
-
* await utilities.sleep(3 * 1000);
|
36
|
-
* console.log("backgroundJob(): ...finished running background job.");
|
37
|
-
* },
|
38
|
-
* );
|
39
|
-
* process.on("SIGTSTP", () => {
|
40
|
-
* backgroundJob.run();
|
41
|
-
* });
|
42
|
-
* console.log(
|
43
|
-
* "backgroundJob(): Press ⌃Z to force background job to run and ⌃C to continue...",
|
44
|
-
* );
|
45
|
-
* ```
|
46
|
-
*/
|
47
|
-
export declare function backgroundJob({ interval, intervalVariance, }: {
|
48
|
-
interval: number;
|
49
|
-
intervalVariance?: number;
|
50
|
-
}, job: () => void | Promise<void>): {
|
51
|
-
run: () => void;
|
52
|
-
stop: () => void;
|
53
|
-
};
|
54
1
|
/**
|
55
2
|
* A promisified version of `setTimeout()`. Bare-bones: It doesn’t even offer a way to `clearTimeout()`. Useful in JavaScript that may run in the browser—if you’re only targeting Node.js then you’re better served by [`timersPromises.setTimeout()`](https://nodejs.org/api/timers.html#timerspromisessettimeoutdelay-value-options).
|
56
3
|
*/
|
@@ -100,10 +47,6 @@ export declare const emailRegExp: RegExp;
|
|
100
47
|
* A regular expression that matches ISO dates, for example, `2024-04-01T14:19:48.162Z`.
|
101
48
|
*/
|
102
49
|
export declare const ISODateRegExp: RegExp;
|
103
|
-
/**
|
104
|
-
* A regular expression that matches localized dates, for example, `2024-04-01 15:20`.
|
105
|
-
*/
|
106
|
-
export declare const localizedDateRegExp: RegExp;
|
107
50
|
/**
|
108
51
|
* Utility type for `intern()`.
|
109
52
|
*/
|
@@ -236,4 +179,36 @@ export declare namespace intern {
|
|
236
179
|
}>;
|
237
180
|
}
|
238
181
|
export declare const internSymbol: unique symbol;
|
182
|
+
/**
|
183
|
+
* > **Note:** This is a lower level utility. See `@radically-straightforward/node`’s and `@radically-straightforward/javascript`’s extensions to `backgroundJob()` that are better suited for their specific environments.
|
184
|
+
*
|
185
|
+
* Start a background job that runs every `interval`.
|
186
|
+
*
|
187
|
+
* `backgroundJob()` is different from `setInterval()` in the following ways:
|
188
|
+
*
|
189
|
+
* 1. The interval counts **between** jobs, so slow background jobs don’t get called concurrently:
|
190
|
+
*
|
191
|
+
* ```
|
192
|
+
* setInterval()
|
193
|
+
* | SLOW BACKGROUND JOB |
|
194
|
+
* | INTERVAL | SLOW BACKGROUND JOB |
|
195
|
+
* | INTERVAL | ...
|
196
|
+
*
|
197
|
+
* backgroundJob()
|
198
|
+
* | SLOW BACKGROUND JOB | INTERVAL | SLOW BACKGROUND JOB | INTERVAL | ...
|
199
|
+
* ```
|
200
|
+
*
|
201
|
+
* 2. You may use `backgroundJob.run()` to force the background job to run right away. If the background job is already running, calling `backgroundJob.run()` schedules it to run again as soon as possible (with a wait interval of `0`).
|
202
|
+
*
|
203
|
+
* 3. You may use `backgroundJob.stop()` to stop the background job. If the background job is in the middle of running, it will finish but it will not be scheduled to run again. This is similar to how an HTTP server may terminate gracefully by stopping accepting new requests but finishing responding to existing requests. After a job has been stopped, you may not `backgroundJob.run()` it again (calling `backgroundJob.run()` has no effect).
|
204
|
+
*
|
205
|
+
* 4. We introduce a random interval variance of 10% on top of the given `interval` to avoid many background jobs from starting at the same time and overloading the machine.
|
206
|
+
*/
|
207
|
+
export declare function backgroundJob({ interval, onStop, }: {
|
208
|
+
interval: number;
|
209
|
+
onStop?: () => void | Promise<void>;
|
210
|
+
}, job: () => void | Promise<void>): {
|
211
|
+
run: () => Promise<void>;
|
212
|
+
stop: () => Promise<void>;
|
213
|
+
};
|
239
214
|
//# sourceMappingURL=index.d.mts.map
|
package/build/index.d.mts.map
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../source/index.mts"],"names":[],"mappings":"
|
1
|
+
{"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../source/index.mts"],"names":[],"mappings":"AAAA;;GAEG;AACH,wBAAgB,KAAK,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAErD;AAED;;GAEG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAED;;GAEG;AACH,wBAAgB,GAAG,CAAC,GAAG,YAAY,EAAE,MAAM,EAAE,GAAG,IAAI,CAEnD;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,wBAAyB,SAAQ,eAAe;;CAkB5D;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAIjD;AAED;;GAEG;AACH,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAI9C;AAED;;GAEG;AACH,eAAO,MAAM,WAAW,EAAE,MAAmD,CAAC;AAE9E;;GAEG;AACH,eAAO,MAAM,aAAa,EAAE,MACqB,CAAC;AAElD;;GAEG;AACH,MAAM,MAAM,MAAM,CAAC,IAAI,IAAI,QAAQ,CAAC,IAAI,GAAG;IAAE,CAAC,YAAY,CAAC,EAAE,IAAI,CAAA;CAAE,CAAC,CAAC;AAErE;;GAEG;AACH,MAAM,MAAM,gBAAgB,GACxB,MAAM,GACN,MAAM,GACN,MAAM,GACN,OAAO,GACP,MAAM,GACN,SAAS,GACT,IAAI,GACJ,MAAM,CAAC,OAAO,CAAC,CAAC;AAEpB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqGG;AACH,wBAAgB,MAAM,CACpB,IAAI,SAAS,KAAK,CAAC,gBAAgB,CAAC,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,CAAA;CAAE,EAC1E,KAAK,EAAE,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAyC3B;yBA3Ce,MAAM;;;;;;;;;;;;;;;;AA6CtB,eAAO,MAAM,YAAY,eAAmB,CAAC;AAiB7C;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,aAAa,CAC3B,EACE,QAAQ,EACR,MAAiB,GAClB,EAAE;IACD,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrC,EACD,GAAG,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GAC9B;IACD,GAAG,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B,CAqCA"}
|
package/build/index.mjs
CHANGED
@@ -1,88 +1,3 @@
|
|
1
|
-
if (process !== undefined)
|
2
|
-
await import("@radically-straightforward/node");
|
3
|
-
/**
|
4
|
-
* Start a background job that runs every `interval`.
|
5
|
-
*
|
6
|
-
* This is different from `setInterval()` in the following ways:
|
7
|
-
*
|
8
|
-
* 1. The interval counts **between** jobs, so slow background jobs don’t get called concurrently:
|
9
|
-
*
|
10
|
-
* ```
|
11
|
-
* setInterval()
|
12
|
-
* | SLOW BACKGROUND JOB |
|
13
|
-
* | INTERVAL | SLOW BACKGROUND JOB |
|
14
|
-
* | INTERVAL | ...
|
15
|
-
*
|
16
|
-
* backgroundJob()
|
17
|
-
* | SLOW BACKGROUND JOB | INTERVAL | SLOW BACKGROUND JOB | INTERVAL | ...
|
18
|
-
* ```
|
19
|
-
*
|
20
|
-
* 2. We introduce a random `intervalVariance` to avoid many background jobs from starting at the same time and overloading the machine.
|
21
|
-
*
|
22
|
-
* 3. You may use `backgroundJob.run()` to force the background job to run right away. If the background job is already running, calling `backgroundJob.run()` schedules it to run again as soon as possible (with a wait interval of 0).
|
23
|
-
*
|
24
|
-
* 4. You may use `backgroundJob.stop()` to stop the background job. If the background job is running, it will finish but it will not be scheduled to run again. This is similar to how an HTTP server may terminate gracefully by stopping accepting new requests but finishing responding to existing requests. After a job has been stopped, you may not `backgroundJob.run()` it again (calling `backgroundJob.run()` has no effect).
|
25
|
-
*
|
26
|
-
* 5. In Node.js the background job is stopped on [`"gracefulTermination"`](https://github.com/radically-straightforward/radically-straightforward/tree/main/node#graceful-termination).
|
27
|
-
*
|
28
|
-
* **Example**
|
29
|
-
*
|
30
|
-
* ```javascript
|
31
|
-
* import * as utilities from "@radically-straightforward/utilities";
|
32
|
-
*
|
33
|
-
* const backgroundJob = utilities.backgroundJob(
|
34
|
-
* { interval: 3 * 1000 },
|
35
|
-
* async () => {
|
36
|
-
* console.log("backgroundJob(): Running background job...");
|
37
|
-
* await utilities.sleep(3 * 1000);
|
38
|
-
* console.log("backgroundJob(): ...finished running background job.");
|
39
|
-
* },
|
40
|
-
* );
|
41
|
-
* process.on("SIGTSTP", () => {
|
42
|
-
* backgroundJob.run();
|
43
|
-
* });
|
44
|
-
* console.log(
|
45
|
-
* "backgroundJob(): Press ⌃Z to force background job to run and ⌃C to continue...",
|
46
|
-
* );
|
47
|
-
* ```
|
48
|
-
*/
|
49
|
-
export function backgroundJob({ interval, intervalVariance = 0.1, }, job) {
|
50
|
-
let state = "sleeping";
|
51
|
-
let timeout = undefined;
|
52
|
-
const scheduler = {
|
53
|
-
run: async () => {
|
54
|
-
switch (state) {
|
55
|
-
case "sleeping":
|
56
|
-
clearTimeout(timeout);
|
57
|
-
state = "running";
|
58
|
-
await job();
|
59
|
-
if (state === "running" || state === "runningAndMarkedForRerun") {
|
60
|
-
timeout = setTimeout(() => {
|
61
|
-
scheduler.run();
|
62
|
-
}, state === "runningAndMarkedForRerun"
|
63
|
-
? 0
|
64
|
-
: interval + interval * intervalVariance * Math.random());
|
65
|
-
state = "sleeping";
|
66
|
-
}
|
67
|
-
break;
|
68
|
-
case "running":
|
69
|
-
state = "runningAndMarkedForRerun";
|
70
|
-
break;
|
71
|
-
}
|
72
|
-
},
|
73
|
-
stop: () => {
|
74
|
-
clearTimeout(timeout);
|
75
|
-
state = "stopped";
|
76
|
-
process?.off?.("gracefulTermination", gracefulTerminationEventListener);
|
77
|
-
},
|
78
|
-
};
|
79
|
-
scheduler.run();
|
80
|
-
const gracefulTerminationEventListener = () => {
|
81
|
-
scheduler.stop();
|
82
|
-
};
|
83
|
-
process?.once?.("gracefulTermination", gracefulTerminationEventListener);
|
84
|
-
return scheduler;
|
85
|
-
}
|
86
1
|
/**
|
87
2
|
* A promisified version of `setTimeout()`. Bare-bones: It doesn’t even offer a way to `clearTimeout()`. Useful in JavaScript that may run in the browser—if you’re only targeting Node.js then you’re better served by [`timersPromises.setTimeout()`](https://nodejs.org/api/timers.html#timerspromisessettimeoutdelay-value-options).
|
88
3
|
*/
|
@@ -161,10 +76,6 @@ export const emailRegExp = /^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$/i;
|
|
161
76
|
* A regular expression that matches ISO dates, for example, `2024-04-01T14:19:48.162Z`.
|
162
77
|
*/
|
163
78
|
export const ISODateRegExp = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
|
164
|
-
/**
|
165
|
-
* A regular expression that matches localized dates, for example, `2024-04-01 15:20`.
|
166
|
-
*/
|
167
|
-
export const localizedDateRegExp = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/;
|
168
79
|
/**
|
169
80
|
* [Interning](<https://en.wikipedia.org/wiki/Interning_(computer_science)>) a value makes it unique across the program, which is useful for checking equality with `===` (reference equality), using it as a key in a `Map`, adding it to a `Set`, and so forth:
|
170
81
|
*
|
@@ -309,4 +220,63 @@ intern.pool = {
|
|
309
220
|
intern.finalizationRegistry = new FinalizationRegistry(({ type, key }) => {
|
310
221
|
intern.pool[type].delete(key);
|
311
222
|
});
|
223
|
+
/**
|
224
|
+
* > **Note:** This is a lower level utility. See `@radically-straightforward/node`’s and `@radically-straightforward/javascript`’s extensions to `backgroundJob()` that are better suited for their specific environments.
|
225
|
+
*
|
226
|
+
* Start a background job that runs every `interval`.
|
227
|
+
*
|
228
|
+
* `backgroundJob()` is different from `setInterval()` in the following ways:
|
229
|
+
*
|
230
|
+
* 1. The interval counts **between** jobs, so slow background jobs don’t get called concurrently:
|
231
|
+
*
|
232
|
+
* ```
|
233
|
+
* setInterval()
|
234
|
+
* | SLOW BACKGROUND JOB |
|
235
|
+
* | INTERVAL | SLOW BACKGROUND JOB |
|
236
|
+
* | INTERVAL | ...
|
237
|
+
*
|
238
|
+
* backgroundJob()
|
239
|
+
* | SLOW BACKGROUND JOB | INTERVAL | SLOW BACKGROUND JOB | INTERVAL | ...
|
240
|
+
* ```
|
241
|
+
*
|
242
|
+
* 2. You may use `backgroundJob.run()` to force the background job to run right away. If the background job is already running, calling `backgroundJob.run()` schedules it to run again as soon as possible (with a wait interval of `0`).
|
243
|
+
*
|
244
|
+
* 3. You may use `backgroundJob.stop()` to stop the background job. If the background job is in the middle of running, it will finish but it will not be scheduled to run again. This is similar to how an HTTP server may terminate gracefully by stopping accepting new requests but finishing responding to existing requests. After a job has been stopped, you may not `backgroundJob.run()` it again (calling `backgroundJob.run()` has no effect).
|
245
|
+
*
|
246
|
+
* 4. We introduce a random interval variance of 10% on top of the given `interval` to avoid many background jobs from starting at the same time and overloading the machine.
|
247
|
+
*/
|
248
|
+
export function backgroundJob({ interval, onStop = () => { }, }, job) {
|
249
|
+
let state = "sleeping";
|
250
|
+
let timeout = setTimeout(() => { });
|
251
|
+
const scheduler = {
|
252
|
+
run: async () => {
|
253
|
+
switch (state) {
|
254
|
+
case "sleeping":
|
255
|
+
clearTimeout(timeout);
|
256
|
+
state = "running";
|
257
|
+
await job();
|
258
|
+
if (state === "running" || state === "runningAndMarkedForRerun") {
|
259
|
+
timeout = setTimeout(() => {
|
260
|
+
scheduler.run();
|
261
|
+
}, state ===
|
262
|
+
"runningAndMarkedForRerun"
|
263
|
+
? 0
|
264
|
+
: interval * (1 + 0.1 * Math.random()));
|
265
|
+
state = "sleeping";
|
266
|
+
}
|
267
|
+
break;
|
268
|
+
case "running":
|
269
|
+
state = "runningAndMarkedForRerun";
|
270
|
+
break;
|
271
|
+
}
|
272
|
+
},
|
273
|
+
stop: async () => {
|
274
|
+
clearTimeout(timeout);
|
275
|
+
state = "stopped";
|
276
|
+
await onStop();
|
277
|
+
},
|
278
|
+
};
|
279
|
+
scheduler.run();
|
280
|
+
return scheduler;
|
281
|
+
}
|
312
282
|
//# sourceMappingURL=index.mjs.map
|
package/build/index.mjs.map
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"index.mjs","sourceRoot":"","sources":["../source/index.mts"],"names":[],"mappings":"AAAA
|
1
|
+
{"version":3,"file":"index.mjs","sourceRoot":"","sources":["../source/index.mts"],"names":[],"mappings":"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"}
|
package/build/index.test.mjs
CHANGED
@@ -2,26 +2,6 @@ import test from "node:test";
|
|
2
2
|
import assert from "node:assert/strict";
|
3
3
|
import * as utilities from "@radically-straightforward/utilities";
|
4
4
|
import { intern as $ } from "@radically-straightforward/utilities";
|
5
|
-
test("backgroundJob()", {
|
6
|
-
skip: process.stdin.isTTY
|
7
|
-
? false
|
8
|
-
: "Run interactive test with ‘node ./build/index.test.mjs’.",
|
9
|
-
}, async () => {
|
10
|
-
for (let iteration = 0; iteration < 1000; iteration++) {
|
11
|
-
const backgroundJob = utilities.backgroundJob({ interval: 3 * 1000 }, () => { });
|
12
|
-
backgroundJob.stop();
|
13
|
-
// If background jobs leak ‘process.once("gracefulTermination")’ event listeners, then we get a warning in the console.
|
14
|
-
}
|
15
|
-
const backgroundJob = utilities.backgroundJob({ interval: 3 * 1000 }, async () => {
|
16
|
-
console.log("backgroundJob(): Running background job...");
|
17
|
-
await utilities.sleep(3 * 1000);
|
18
|
-
console.log("backgroundJob(): ...finished running background job.");
|
19
|
-
});
|
20
|
-
process.on("SIGTSTP", () => {
|
21
|
-
backgroundJob.run();
|
22
|
-
});
|
23
|
-
console.log("backgroundJob(): Press ⌃Z to force background job to run and ⌃C to gracefully terminate...");
|
24
|
-
});
|
25
5
|
test("sleep()", async () => {
|
26
6
|
const before = Date.now();
|
27
7
|
await utilities.sleep(1000);
|
@@ -78,10 +58,6 @@ test("ISODateRegExp", () => {
|
|
78
58
|
assert.match("2024-04-01T14:19:48.162Z", utilities.ISODateRegExp);
|
79
59
|
assert.doesNotMatch("2024-04-01 15:20", utilities.ISODateRegExp);
|
80
60
|
});
|
81
|
-
test("localizedDateRegExp", () => {
|
82
|
-
assert.match("2024-04-01 15:20", utilities.localizedDateRegExp);
|
83
|
-
assert.doesNotMatch("2024-04-01T14:19:48.162Z", utilities.localizedDateRegExp);
|
84
|
-
});
|
85
61
|
test("intern()", () => {
|
86
62
|
// @ts-expect-error
|
87
63
|
assert(([1] === [1]) === false);
|
@@ -125,20 +101,23 @@ test("intern()", () => {
|
|
125
101
|
// @ts-expect-error
|
126
102
|
$([1])[0] = 2;
|
127
103
|
});
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
104
|
+
});
|
105
|
+
test("backgroundJob()", {
|
106
|
+
skip: process.stdin.isTTY
|
107
|
+
? false
|
108
|
+
: "Run interactive test with ‘node ./build/index.test.mjs’.",
|
109
|
+
}, async () => {
|
110
|
+
const backgroundJob = utilities.backgroundJob({ interval: 3 * 1000 }, async () => {
|
111
|
+
console.log("backgroundJob(): Running background job...");
|
112
|
+
await utilities.sleep(3 * 1000);
|
113
|
+
console.log("backgroundJob(): ...finished running background job.");
|
114
|
+
});
|
115
|
+
process.on("SIGTSTP", () => {
|
116
|
+
backgroundJob.run();
|
117
|
+
});
|
118
|
+
process.on("SIGINT", () => {
|
119
|
+
backgroundJob.stop();
|
120
|
+
});
|
121
|
+
console.log("backgroundJob(): Press ⌃Z to ‘run()’ and ⌃C to ‘stop()’...");
|
143
122
|
});
|
144
123
|
//# sourceMappingURL=index.test.mjs.map
|
package/build/index.test.mjs.map
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"index.test.mjs","sourceRoot":"","sources":["../source/index.test.mts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,KAAK,SAAS,MAAM,sCAAsC,CAAC;AAClE,OAAO,EAAE,MAAM,IAAI,CAAC,EAAE,MAAM,sCAAsC,CAAC;AAEnE,IAAI,
|
1
|
+
{"version":3,"file":"index.test.mjs","sourceRoot":"","sources":["../source/index.test.mts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@radically-straightforward/utilities",
|
3
|
-
"version": "
|
3
|
+
"version": "2.0.0",
|
4
4
|
"description": "🛠️ Utilities for Node.js and the browser",
|
5
5
|
"keywords": [
|
6
6
|
"node",
|
@@ -27,9 +27,6 @@
|
|
27
27
|
"prepare": "tsc && documentation",
|
28
28
|
"test": "npm run prepare && node --test && prettier --check \"./README.md\" --check \"./CHANGELOG.md\" --check \"./package.json\" --check \"./tsconfig.json\" --check \"./source/**/*.mts\""
|
29
29
|
},
|
30
|
-
"dependencies": {
|
31
|
-
"@radically-straightforward/node": "^3.0.0"
|
32
|
-
},
|
33
30
|
"devDependencies": {
|
34
31
|
"@radically-straightforward/documentation": "^1.0.1",
|
35
32
|
"@radically-straightforward/tsconfig": "^1.0.0",
|
package/source/index.mts
CHANGED
@@ -1,99 +1,3 @@
|
|
1
|
-
if (process !== undefined) await import("@radically-straightforward/node");
|
2
|
-
|
3
|
-
/**
|
4
|
-
* Start a background job that runs every `interval`.
|
5
|
-
*
|
6
|
-
* This is different from `setInterval()` in the following ways:
|
7
|
-
*
|
8
|
-
* 1. The interval counts **between** jobs, so slow background jobs don’t get called concurrently:
|
9
|
-
*
|
10
|
-
* ```
|
11
|
-
* setInterval()
|
12
|
-
* | SLOW BACKGROUND JOB |
|
13
|
-
* | INTERVAL | SLOW BACKGROUND JOB |
|
14
|
-
* | INTERVAL | ...
|
15
|
-
*
|
16
|
-
* backgroundJob()
|
17
|
-
* | SLOW BACKGROUND JOB | INTERVAL | SLOW BACKGROUND JOB | INTERVAL | ...
|
18
|
-
* ```
|
19
|
-
*
|
20
|
-
* 2. We introduce a random `intervalVariance` to avoid many background jobs from starting at the same time and overloading the machine.
|
21
|
-
*
|
22
|
-
* 3. You may use `backgroundJob.run()` to force the background job to run right away. If the background job is already running, calling `backgroundJob.run()` schedules it to run again as soon as possible (with a wait interval of 0).
|
23
|
-
*
|
24
|
-
* 4. You may use `backgroundJob.stop()` to stop the background job. If the background job is running, it will finish but it will not be scheduled to run again. This is similar to how an HTTP server may terminate gracefully by stopping accepting new requests but finishing responding to existing requests. After a job has been stopped, you may not `backgroundJob.run()` it again (calling `backgroundJob.run()` has no effect).
|
25
|
-
*
|
26
|
-
* 5. In Node.js the background job is stopped on [`"gracefulTermination"`](https://github.com/radically-straightforward/radically-straightforward/tree/main/node#graceful-termination).
|
27
|
-
*
|
28
|
-
* **Example**
|
29
|
-
*
|
30
|
-
* ```javascript
|
31
|
-
* import * as utilities from "@radically-straightforward/utilities";
|
32
|
-
*
|
33
|
-
* const backgroundJob = utilities.backgroundJob(
|
34
|
-
* { interval: 3 * 1000 },
|
35
|
-
* async () => {
|
36
|
-
* console.log("backgroundJob(): Running background job...");
|
37
|
-
* await utilities.sleep(3 * 1000);
|
38
|
-
* console.log("backgroundJob(): ...finished running background job.");
|
39
|
-
* },
|
40
|
-
* );
|
41
|
-
* process.on("SIGTSTP", () => {
|
42
|
-
* backgroundJob.run();
|
43
|
-
* });
|
44
|
-
* console.log(
|
45
|
-
* "backgroundJob(): Press ⌃Z to force background job to run and ⌃C to continue...",
|
46
|
-
* );
|
47
|
-
* ```
|
48
|
-
*/
|
49
|
-
export function backgroundJob(
|
50
|
-
{
|
51
|
-
interval,
|
52
|
-
intervalVariance = 0.1,
|
53
|
-
}: { interval: number; intervalVariance?: number },
|
54
|
-
job: () => void | Promise<void>,
|
55
|
-
): { run: () => void; stop: () => void } {
|
56
|
-
let state: "sleeping" | "running" | "runningAndMarkedForRerun" | "stopped" =
|
57
|
-
"sleeping";
|
58
|
-
let timeout: any = undefined;
|
59
|
-
const scheduler = {
|
60
|
-
run: async () => {
|
61
|
-
switch (state) {
|
62
|
-
case "sleeping":
|
63
|
-
clearTimeout(timeout);
|
64
|
-
state = "running";
|
65
|
-
await job();
|
66
|
-
if (state === "running" || state === "runningAndMarkedForRerun") {
|
67
|
-
timeout = setTimeout(
|
68
|
-
() => {
|
69
|
-
scheduler.run();
|
70
|
-
},
|
71
|
-
(state as any) === "runningAndMarkedForRerun"
|
72
|
-
? 0
|
73
|
-
: interval + interval * intervalVariance * Math.random(),
|
74
|
-
);
|
75
|
-
state = "sleeping";
|
76
|
-
}
|
77
|
-
break;
|
78
|
-
case "running":
|
79
|
-
state = "runningAndMarkedForRerun";
|
80
|
-
break;
|
81
|
-
}
|
82
|
-
},
|
83
|
-
stop: () => {
|
84
|
-
clearTimeout(timeout);
|
85
|
-
state = "stopped";
|
86
|
-
process?.off?.("gracefulTermination", gracefulTerminationEventListener);
|
87
|
-
},
|
88
|
-
};
|
89
|
-
scheduler.run();
|
90
|
-
const gracefulTerminationEventListener = () => {
|
91
|
-
scheduler.stop();
|
92
|
-
};
|
93
|
-
process?.once?.("gracefulTermination", gracefulTerminationEventListener);
|
94
|
-
return scheduler;
|
95
|
-
}
|
96
|
-
|
97
1
|
/**
|
98
2
|
* A promisified version of `setTimeout()`. Bare-bones: It doesn’t even offer a way to `clearTimeout()`. Useful in JavaScript that may run in the browser—if you’re only targeting Node.js then you’re better served by [`timersPromises.setTimeout()`](https://nodejs.org/api/timers.html#timerspromisessettimeoutdelay-value-options).
|
99
3
|
*/
|
@@ -182,11 +86,6 @@ export const emailRegExp: RegExp = /^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$/i;
|
|
182
86
|
export const ISODateRegExp: RegExp =
|
183
87
|
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
|
184
88
|
|
185
|
-
/**
|
186
|
-
* A regular expression that matches localized dates, for example, `2024-04-01 15:20`.
|
187
|
-
*/
|
188
|
-
export const localizedDateRegExp: RegExp = /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$/;
|
189
|
-
|
190
89
|
/**
|
191
90
|
* Utility type for `intern()`.
|
192
91
|
*/
|
@@ -368,3 +267,79 @@ intern.finalizationRegistry = new FinalizationRegistry<{
|
|
368
267
|
}>(({ type, key }) => {
|
369
268
|
intern.pool[type].delete(key);
|
370
269
|
});
|
270
|
+
|
271
|
+
/**
|
272
|
+
* > **Note:** This is a lower level utility. See `@radically-straightforward/node`’s and `@radically-straightforward/javascript`’s extensions to `backgroundJob()` that are better suited for their specific environments.
|
273
|
+
*
|
274
|
+
* Start a background job that runs every `interval`.
|
275
|
+
*
|
276
|
+
* `backgroundJob()` is different from `setInterval()` in the following ways:
|
277
|
+
*
|
278
|
+
* 1. The interval counts **between** jobs, so slow background jobs don’t get called concurrently:
|
279
|
+
*
|
280
|
+
* ```
|
281
|
+
* setInterval()
|
282
|
+
* | SLOW BACKGROUND JOB |
|
283
|
+
* | INTERVAL | SLOW BACKGROUND JOB |
|
284
|
+
* | INTERVAL | ...
|
285
|
+
*
|
286
|
+
* backgroundJob()
|
287
|
+
* | SLOW BACKGROUND JOB | INTERVAL | SLOW BACKGROUND JOB | INTERVAL | ...
|
288
|
+
* ```
|
289
|
+
*
|
290
|
+
* 2. You may use `backgroundJob.run()` to force the background job to run right away. If the background job is already running, calling `backgroundJob.run()` schedules it to run again as soon as possible (with a wait interval of `0`).
|
291
|
+
*
|
292
|
+
* 3. You may use `backgroundJob.stop()` to stop the background job. If the background job is in the middle of running, it will finish but it will not be scheduled to run again. This is similar to how an HTTP server may terminate gracefully by stopping accepting new requests but finishing responding to existing requests. After a job has been stopped, you may not `backgroundJob.run()` it again (calling `backgroundJob.run()` has no effect).
|
293
|
+
*
|
294
|
+
* 4. We introduce a random interval variance of 10% on top of the given `interval` to avoid many background jobs from starting at the same time and overloading the machine.
|
295
|
+
*/
|
296
|
+
export function backgroundJob(
|
297
|
+
{
|
298
|
+
interval,
|
299
|
+
onStop = () => {},
|
300
|
+
}: {
|
301
|
+
interval: number;
|
302
|
+
onStop?: () => void | Promise<void>;
|
303
|
+
},
|
304
|
+
job: () => void | Promise<void>,
|
305
|
+
): {
|
306
|
+
run: () => Promise<void>;
|
307
|
+
stop: () => Promise<void>;
|
308
|
+
} {
|
309
|
+
let state: "sleeping" | "running" | "runningAndMarkedForRerun" | "stopped" =
|
310
|
+
"sleeping";
|
311
|
+
let timeout = setTimeout(() => {});
|
312
|
+
const scheduler = {
|
313
|
+
run: async () => {
|
314
|
+
switch (state) {
|
315
|
+
case "sleeping":
|
316
|
+
clearTimeout(timeout);
|
317
|
+
state = "running";
|
318
|
+
await job();
|
319
|
+
if (state === "running" || state === "runningAndMarkedForRerun") {
|
320
|
+
timeout = setTimeout(
|
321
|
+
() => {
|
322
|
+
scheduler.run();
|
323
|
+
},
|
324
|
+
(state as "running" | "runningAndMarkedForRerun") ===
|
325
|
+
"runningAndMarkedForRerun"
|
326
|
+
? 0
|
327
|
+
: interval * (1 + 0.1 * Math.random()),
|
328
|
+
);
|
329
|
+
state = "sleeping";
|
330
|
+
}
|
331
|
+
break;
|
332
|
+
case "running":
|
333
|
+
state = "runningAndMarkedForRerun";
|
334
|
+
break;
|
335
|
+
}
|
336
|
+
},
|
337
|
+
stop: async () => {
|
338
|
+
clearTimeout(timeout);
|
339
|
+
state = "stopped";
|
340
|
+
await onStop();
|
341
|
+
},
|
342
|
+
};
|
343
|
+
scheduler.run();
|
344
|
+
return scheduler;
|
345
|
+
}
|
package/source/index.test.mts
CHANGED
@@ -3,40 +3,6 @@ import assert from "node:assert/strict";
|
|
3
3
|
import * as utilities from "@radically-straightforward/utilities";
|
4
4
|
import { intern as $ } from "@radically-straightforward/utilities";
|
5
5
|
|
6
|
-
test(
|
7
|
-
"backgroundJob()",
|
8
|
-
{
|
9
|
-
skip: process.stdin.isTTY
|
10
|
-
? false
|
11
|
-
: "Run interactive test with ‘node ./build/index.test.mjs’.",
|
12
|
-
},
|
13
|
-
async () => {
|
14
|
-
for (let iteration = 0; iteration < 1000; iteration++) {
|
15
|
-
const backgroundJob = utilities.backgroundJob(
|
16
|
-
{ interval: 3 * 1000 },
|
17
|
-
() => {},
|
18
|
-
);
|
19
|
-
backgroundJob.stop();
|
20
|
-
// If background jobs leak ‘process.once("gracefulTermination")’ event listeners, then we get a warning in the console.
|
21
|
-
}
|
22
|
-
|
23
|
-
const backgroundJob = utilities.backgroundJob(
|
24
|
-
{ interval: 3 * 1000 },
|
25
|
-
async () => {
|
26
|
-
console.log("backgroundJob(): Running background job...");
|
27
|
-
await utilities.sleep(3 * 1000);
|
28
|
-
console.log("backgroundJob(): ...finished running background job.");
|
29
|
-
},
|
30
|
-
);
|
31
|
-
process.on("SIGTSTP", () => {
|
32
|
-
backgroundJob.run();
|
33
|
-
});
|
34
|
-
console.log(
|
35
|
-
"backgroundJob(): Press ⌃Z to force background job to run and ⌃C to gracefully terminate...",
|
36
|
-
);
|
37
|
-
},
|
38
|
-
);
|
39
|
-
|
40
6
|
test("sleep()", async () => {
|
41
7
|
const before = Date.now();
|
42
8
|
await utilities.sleep(1000);
|
@@ -105,14 +71,6 @@ test("ISODateRegExp", () => {
|
|
105
71
|
assert.doesNotMatch("2024-04-01 15:20", utilities.ISODateRegExp);
|
106
72
|
});
|
107
73
|
|
108
|
-
test("localizedDateRegExp", () => {
|
109
|
-
assert.match("2024-04-01 15:20", utilities.localizedDateRegExp);
|
110
|
-
assert.doesNotMatch(
|
111
|
-
"2024-04-01T14:19:48.162Z",
|
112
|
-
utilities.localizedDateRegExp,
|
113
|
-
);
|
114
|
-
});
|
115
|
-
|
116
74
|
test("intern()", () => {
|
117
75
|
// @ts-expect-error
|
118
76
|
assert(([1] === [1]) === false);
|
@@ -163,20 +121,30 @@ test("intern()", () => {
|
|
163
121
|
// @ts-expect-error
|
164
122
|
$([1])[0] = 2;
|
165
123
|
});
|
124
|
+
});
|
166
125
|
|
126
|
+
test(
|
127
|
+
"backgroundJob()",
|
167
128
|
{
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
129
|
+
skip: process.stdin.isTTY
|
130
|
+
? false
|
131
|
+
: "Run interactive test with ‘node ./build/index.test.mjs’.",
|
132
|
+
},
|
133
|
+
async () => {
|
134
|
+
const backgroundJob = utilities.backgroundJob(
|
135
|
+
{ interval: 3 * 1000 },
|
136
|
+
async () => {
|
137
|
+
console.log("backgroundJob(): Running background job...");
|
138
|
+
await utilities.sleep(3 * 1000);
|
139
|
+
console.log("backgroundJob(): ...finished running background job.");
|
140
|
+
},
|
141
|
+
);
|
142
|
+
process.on("SIGTSTP", () => {
|
143
|
+
backgroundJob.run();
|
144
|
+
});
|
145
|
+
process.on("SIGINT", () => {
|
146
|
+
backgroundJob.stop();
|
147
|
+
});
|
148
|
+
console.log("backgroundJob(): Press ⌃Z to ‘run()’ and ⌃C to ‘stop()’...");
|
149
|
+
},
|
150
|
+
);
|