@radically-straightforward/utilities 0.0.3 → 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +10 -0
- package/README.md +89 -1
- package/build/index.d.mts +67 -1
- package/build/index.d.mts.map +1 -1
- package/build/index.mjs +99 -91
- package/build/index.mjs.map +1 -1
- package/build/index.test.mjs +34 -23
- package/build/index.test.mjs.map +1 -1
- package/package.json +2 -1
- package/source/index.mts +116 -92
- package/source/index.test.mts +46 -24
package/CHANGELOG.md
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 1.0.1 · 2024-01-09
|
4
|
+
|
5
|
+
- Added `log()`.
|
6
|
+
|
7
|
+
## 1.0.0 · 2024-01-06
|
8
|
+
|
9
|
+
- Added `backgroundJob()`.
|
10
|
+
- Added `sleep()`.
|
11
|
+
- Added `randomString()`.
|
12
|
+
|
3
13
|
## 0.0.3 · 2024-01-05
|
4
14
|
|
5
15
|
- Made `intern()` more strict in terms of types and provide auxiliary types for it.
|
package/README.md
CHANGED
@@ -16,6 +16,94 @@ 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 (not waiting the interval).
|
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
|
+
**Example**
|
60
|
+
|
61
|
+
```javascript
|
62
|
+
import * as utilities from "@radically-straightforward/utilities";
|
63
|
+
import * as node from "@radically-straightforward/node";
|
64
|
+
|
65
|
+
const backgroundJob = utilities.backgroundJob(
|
66
|
+
{ interval: 3 * 1000 },
|
67
|
+
async () => {
|
68
|
+
console.log("backgroundJob(): Running background job...");
|
69
|
+
await utilities.sleep(3 * 1000);
|
70
|
+
console.log("backgroundJob(): ...finished running background job.");
|
71
|
+
},
|
72
|
+
);
|
73
|
+
console.log(
|
74
|
+
"backgroundJob(): Press ⌃Z to force background job to run and ⌃C to continue...",
|
75
|
+
);
|
76
|
+
process.on("SIGTSTP", () => {
|
77
|
+
backgroundJob.run();
|
78
|
+
});
|
79
|
+
await node.shouldTerminate();
|
80
|
+
backgroundJob.stop();
|
81
|
+
```
|
82
|
+
|
83
|
+
### `sleep()`
|
84
|
+
|
85
|
+
```typescript
|
86
|
+
export function sleep(duration: number): Promise<void>;
|
87
|
+
```
|
88
|
+
|
89
|
+
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/dist/latest-v21.x/docs/api/timers.html#timerspromisessettimeoutdelay-value-options).
|
90
|
+
|
91
|
+
### `randomString()`
|
92
|
+
|
93
|
+
```typescript
|
94
|
+
export function randomString(): string;
|
95
|
+
```
|
96
|
+
|
97
|
+
A fast random string generator. The generated strings are 10 or 11 characters in length. The generated strings include the characters `[0-9a-z]`. The generated strings are **not** cryptographically secure—if you need that, then use [`crypto-random-string`](https://npm.im/crypto-random-string).
|
98
|
+
|
99
|
+
### `log()`
|
100
|
+
|
101
|
+
```typescript
|
102
|
+
export function log(...messageParts: string[]): void;
|
103
|
+
```
|
104
|
+
|
105
|
+
Tab-separated logging.
|
106
|
+
|
19
107
|
### `Intern`
|
20
108
|
|
21
109
|
```typescript
|
@@ -109,7 +197,7 @@ $([1]) === $([1]); // => true
|
|
109
197
|
|
110
198
|
> **Note:** Interned objects do not preserve the order of the attributes: `$({ a: 1, b: 2 }) === $({ b: 2, a: 1 })`.
|
111
199
|
|
112
|
-
> **Note:** The pool of interned values is available as `intern.pool`. There’s a `FinalizationRegistry` at `intern.finalizationRegistry` that cleans up interned values that have been garbage collected.
|
200
|
+
> **Note:** The pool of interned values is available as `intern.pool`. The interned values are kept with `WeakRef`s to allow them to be garbage collected when they aren’t referenced anywhere else anymore. There’s a `FinalizationRegistry` at `intern.finalizationRegistry` that cleans up interned values that have been garbage collected.
|
113
201
|
|
114
202
|
**Related Work**
|
115
203
|
|
package/build/index.d.mts
CHANGED
@@ -1,3 +1,69 @@
|
|
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 (not waiting the interval).
|
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
|
+
* **Example**
|
25
|
+
*
|
26
|
+
* ```javascript
|
27
|
+
* import * as utilities from "@radically-straightforward/utilities";
|
28
|
+
* import * as node from "@radically-straightforward/node";
|
29
|
+
*
|
30
|
+
* const backgroundJob = utilities.backgroundJob(
|
31
|
+
* { interval: 3 * 1000 },
|
32
|
+
* async () => {
|
33
|
+
* console.log("backgroundJob(): Running background job...");
|
34
|
+
* await utilities.sleep(3 * 1000);
|
35
|
+
* console.log("backgroundJob(): ...finished running background job.");
|
36
|
+
* },
|
37
|
+
* );
|
38
|
+
* console.log(
|
39
|
+
* "backgroundJob(): Press ⌃Z to force background job to run and ⌃C to continue...",
|
40
|
+
* );
|
41
|
+
* process.on("SIGTSTP", () => {
|
42
|
+
* backgroundJob.run();
|
43
|
+
* });
|
44
|
+
* await node.shouldTerminate();
|
45
|
+
* backgroundJob.stop();
|
46
|
+
* ```
|
47
|
+
*/
|
48
|
+
export declare function backgroundJob({ interval, intervalVariance, }: {
|
49
|
+
interval: number;
|
50
|
+
intervalVariance?: number;
|
51
|
+
}, job: () => void | Promise<void>): {
|
52
|
+
run: () => void;
|
53
|
+
stop: () => void;
|
54
|
+
};
|
55
|
+
/**
|
56
|
+
* 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/dist/latest-v21.x/docs/api/timers.html#timerspromisessettimeoutdelay-value-options).
|
57
|
+
*/
|
58
|
+
export declare function sleep(duration: number): Promise<void>;
|
59
|
+
/**
|
60
|
+
* A fast random string generator. The generated strings are 10 or 11 characters in length. The generated strings include the characters `[0-9a-z]`. The generated strings are **not** cryptographically secure—if you need that, then use [`crypto-random-string`](https://npm.im/crypto-random-string).
|
61
|
+
*/
|
62
|
+
export declare function randomString(): string;
|
63
|
+
/**
|
64
|
+
* Tab-separated logging.
|
65
|
+
*/
|
66
|
+
export declare function log(...messageParts: string[]): void;
|
1
67
|
/**
|
2
68
|
* Utility type for `intern()`.
|
3
69
|
*/
|
@@ -62,7 +128,7 @@ export type InternInnerValue = string | number | bigint | boolean | symbol | und
|
|
62
128
|
*
|
63
129
|
* > **Note:** Interned objects do not preserve the order of the attributes: `$({ a: 1, b: 2 }) === $({ b: 2, a: 1 })`.
|
64
130
|
*
|
65
|
-
* > **Note:** The pool of interned values is available as `intern.pool`. There’s a `FinalizationRegistry` at `intern.finalizationRegistry` that cleans up interned values that have been garbage collected.
|
131
|
+
* > **Note:** The pool of interned values is available as `intern.pool`. The interned values are kept with `WeakRef`s to allow them to be garbage collected when they aren’t referenced anywhere else anymore. There’s a `FinalizationRegistry` at `intern.finalizationRegistry` that cleans up interned values that have been garbage collected.
|
66
132
|
*
|
67
133
|
* **Related Work**
|
68
134
|
*
|
package/build/index.d.mts.map
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../source/index.mts"],"names":[],"mappings":"AAAA;;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,CAAC,SAAS,KAAK,CAAC,gBAAgB,CAAC,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,CAAA;CAAE,EACvE,KAAK,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CA2CrB;yBA7Ce,MAAM;;;;;;;;;;;;;;;;AA+CtB,eAAO,MAAM,YAAY,eAAmB,CAAC"}
|
1
|
+
{"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../source/index.mts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8CG;AACH,wBAAgB,aAAa,CAC3B,EACE,QAAQ,EACR,gBAAsB,GACvB,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAAE,EAClD,GAAG,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GAC9B;IAAE,GAAG,EAAE,MAAM,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,IAAI,CAAA;CAAE,CAuCvC;AAED;;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;;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,CAAC,SAAS,KAAK,CAAC,gBAAgB,CAAC,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,CAAA;CAAE,EACvE,KAAK,EAAE,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CA2CrB;yBA7Ce,MAAM;;;;;;;;;;;;;;;;AA+CtB,eAAO,MAAM,YAAY,eAAmB,CAAC"}
|
package/build/index.mjs
CHANGED
@@ -1,3 +1,101 @@
|
|
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 (not waiting the interval).
|
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
|
+
* **Example**
|
25
|
+
*
|
26
|
+
* ```javascript
|
27
|
+
* import * as utilities from "@radically-straightforward/utilities";
|
28
|
+
* import * as node from "@radically-straightforward/node";
|
29
|
+
*
|
30
|
+
* const backgroundJob = utilities.backgroundJob(
|
31
|
+
* { interval: 3 * 1000 },
|
32
|
+
* async () => {
|
33
|
+
* console.log("backgroundJob(): Running background job...");
|
34
|
+
* await utilities.sleep(3 * 1000);
|
35
|
+
* console.log("backgroundJob(): ...finished running background job.");
|
36
|
+
* },
|
37
|
+
* );
|
38
|
+
* console.log(
|
39
|
+
* "backgroundJob(): Press ⌃Z to force background job to run and ⌃C to continue...",
|
40
|
+
* );
|
41
|
+
* process.on("SIGTSTP", () => {
|
42
|
+
* backgroundJob.run();
|
43
|
+
* });
|
44
|
+
* await node.shouldTerminate();
|
45
|
+
* backgroundJob.stop();
|
46
|
+
* ```
|
47
|
+
*/
|
48
|
+
export function backgroundJob({ interval, intervalVariance = 0.1, }, job) {
|
49
|
+
let state = "initial";
|
50
|
+
let timeout = undefined;
|
51
|
+
async function run() {
|
52
|
+
state = "running";
|
53
|
+
await job();
|
54
|
+
if (state === "running" || state === "runningAndMarkedForRerun") {
|
55
|
+
timeout = setTimeout(run, state === "runningAndMarkedForRerun"
|
56
|
+
? 0
|
57
|
+
: interval + interval * intervalVariance * Math.random());
|
58
|
+
state = "sleeping";
|
59
|
+
}
|
60
|
+
}
|
61
|
+
run();
|
62
|
+
return {
|
63
|
+
run: () => {
|
64
|
+
switch (state) {
|
65
|
+
case "sleeping":
|
66
|
+
clearTimeout(timeout);
|
67
|
+
run();
|
68
|
+
break;
|
69
|
+
case "running":
|
70
|
+
state = "runningAndMarkedForRerun";
|
71
|
+
break;
|
72
|
+
}
|
73
|
+
},
|
74
|
+
stop: () => {
|
75
|
+
if (state === "sleeping")
|
76
|
+
clearTimeout(timeout);
|
77
|
+
state = "stopped";
|
78
|
+
},
|
79
|
+
};
|
80
|
+
}
|
81
|
+
/**
|
82
|
+
* 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/dist/latest-v21.x/docs/api/timers.html#timerspromisessettimeoutdelay-value-options).
|
83
|
+
*/
|
84
|
+
export function sleep(duration) {
|
85
|
+
return new Promise((resolve) => setTimeout(resolve, duration));
|
86
|
+
}
|
87
|
+
/**
|
88
|
+
* A fast random string generator. The generated strings are 10 or 11 characters in length. The generated strings include the characters `[0-9a-z]`. The generated strings are **not** cryptographically secure—if you need that, then use [`crypto-random-string`](https://npm.im/crypto-random-string).
|
89
|
+
*/
|
90
|
+
export function randomString() {
|
91
|
+
return Math.random().toString(36).slice(2);
|
92
|
+
}
|
93
|
+
/**
|
94
|
+
* Tab-separated logging.
|
95
|
+
*/
|
96
|
+
export function log(...messageParts) {
|
97
|
+
console.log(messageParts.join(" \t"));
|
98
|
+
}
|
1
99
|
/**
|
2
100
|
* [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:
|
3
101
|
*
|
@@ -52,7 +150,7 @@
|
|
52
150
|
*
|
53
151
|
* > **Note:** Interned objects do not preserve the order of the attributes: `$({ a: 1, b: 2 }) === $({ b: 2, a: 1 })`.
|
54
152
|
*
|
55
|
-
* > **Note:** The pool of interned values is available as `intern.pool`. There’s a `FinalizationRegistry` at `intern.finalizationRegistry` that cleans up interned values that have been garbage collected.
|
153
|
+
* > **Note:** The pool of interned values is available as `intern.pool`. The interned values are kept with `WeakRef`s to allow them to be garbage collected when they aren’t referenced anywhere else anymore. There’s a `FinalizationRegistry` at `intern.finalizationRegistry` that cleans up interned values that have been garbage collected.
|
56
154
|
*
|
57
155
|
* **Related Work**
|
58
156
|
*
|
@@ -144,94 +242,4 @@ intern.pool = {
|
|
144
242
|
intern.finalizationRegistry = new FinalizationRegistry(({ type, key }) => {
|
145
243
|
intern.pool[type].delete(key);
|
146
244
|
});
|
147
|
-
/*
|
148
|
-
|
149
|
-
|
150
|
-
Math.random().toString(36).slice(2)
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
https://npm.im/package/p-timeout
|
156
|
-
https://npm.im/package/delay
|
157
|
-
https://npm.im/package/sleep-promise
|
158
|
-
https://npm.im/package/promise-timeout
|
159
|
-
https://npm.im/package/sleep
|
160
|
-
https://npm.im/package/timeout-as-promise
|
161
|
-
https://npm.im/package/delayed
|
162
|
-
https://npm.im/package/sleep-async
|
163
|
-
https://npm.im/package/promise.timeout
|
164
|
-
|
165
|
-
*/
|
166
|
-
// /**
|
167
|
-
// - Remove uses of `node:timers/promises`?
|
168
|
-
// *
|
169
|
-
// * TODO: In universal JavaScript, implement a way to **canonicalize** objects using deepEquals to be used in Sets and as Map keys (then deprecate `collections-deep-equal`).
|
170
|
-
// * - value objects
|
171
|
-
// * - https://lodash.com/docs/4.17.15#isEqual
|
172
|
-
// * TODO: Implement using setTimeout and let it be usable in client-side JavaScript as well.
|
173
|
-
// * TODO: Explain the differences between this and `setInterval()` (wait for completion before setting the next scheduler, and force a job to run)
|
174
|
-
// *
|
175
|
-
// * Start a background job that runs every given `interval`.
|
176
|
-
// *
|
177
|
-
// * You may use `backgroundJob.run()` to force the background job to run right away.
|
178
|
-
// *
|
179
|
-
// * **Note:** If a background job is running when `backgroundJob.continue()` is called.
|
180
|
-
// *
|
181
|
-
// * You may use `backgroundJob.stop()` to stop the background job.
|
182
|
-
// *
|
183
|
-
// * **Note:** If a background job is running when `backgroundJob.stop()` is called, then that background job is run to completion, but a future background job run is not scheduled. This is similar to how an HTTP server may stop finish processing existing requests but don’t accept new requests.
|
184
|
-
// *
|
185
|
-
// * **Note:** The `intervalVariance` prevents many background jobs from starting at the same and overloading the machine.
|
186
|
-
// *
|
187
|
-
// * **Example**
|
188
|
-
// *
|
189
|
-
// * ```javascript
|
190
|
-
// * import * as node from "@radically-straightforward/node";
|
191
|
-
// *
|
192
|
-
// * const backgroundJob = node.backgroundJob({ interval: 3 * 1000 }, () => {
|
193
|
-
// * console.log("backgroundJob(): Running background job...");
|
194
|
-
// * });
|
195
|
-
// * process.on("SIGTSTP", () => {
|
196
|
-
// * backgroundJob.run();
|
197
|
-
// * });
|
198
|
-
// * console.log(
|
199
|
-
// * "backgroundJob(): Press ⌃Z to force background job to run and ⌃C to continue...",
|
200
|
-
// * );
|
201
|
-
// * await node.shouldTerminate();
|
202
|
-
// * backgroundJob.stop();
|
203
|
-
// * ```
|
204
|
-
// */
|
205
|
-
// export function backgroundJob(
|
206
|
-
// {
|
207
|
-
// interval,
|
208
|
-
// intervalVariance = 0.1,
|
209
|
-
// }: { interval: number; intervalVariance?: number },
|
210
|
-
// function_: () => void | Promise<void>,
|
211
|
-
// ): { run: () => void; stop: () => void } {
|
212
|
-
// let shouldContinue = true;
|
213
|
-
// let abortController = new AbortController();
|
214
|
-
// (async () => {
|
215
|
-
// while (shouldContinue) {
|
216
|
-
// await function_();
|
217
|
-
// await timers
|
218
|
-
// .setTimeout(
|
219
|
-
// interval + interval * intervalVariance * Math.random(),
|
220
|
-
// undefined,
|
221
|
-
// { signal: abortController.signal },
|
222
|
-
// )
|
223
|
-
// .catch(() => {});
|
224
|
-
// abortController = new AbortController();
|
225
|
-
// }
|
226
|
-
// })();
|
227
|
-
// return {
|
228
|
-
// run: () => {
|
229
|
-
// abortController.abort();
|
230
|
-
// },
|
231
|
-
// stop: () => {
|
232
|
-
// shouldContinue = false;
|
233
|
-
// abortController.abort();
|
234
|
-
// },
|
235
|
-
// };
|
236
|
-
// }
|
237
245
|
//# 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":"
|
1
|
+
{"version":3,"file":"index.mjs","sourceRoot":"","sources":["../source/index.mts"],"names":[],"mappings":"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"}
|
package/build/index.test.mjs
CHANGED
@@ -1,6 +1,40 @@
|
|
1
1
|
import test from "node:test";
|
2
2
|
import assert from "node:assert/strict";
|
3
|
+
import * as node from "@radically-straightforward/node";
|
4
|
+
import * as utilities from "./index.mjs";
|
3
5
|
import { intern as $ } from "./index.mjs";
|
6
|
+
test("backgroundJob()", {
|
7
|
+
...(!process.stdin.isTTY
|
8
|
+
? {
|
9
|
+
skip: "Run interactive test with ‘node ./build/index.test.mjs’.",
|
10
|
+
}
|
11
|
+
: {}),
|
12
|
+
}, async () => {
|
13
|
+
const backgroundJob = utilities.backgroundJob({ interval: 3 * 1000 }, async () => {
|
14
|
+
console.log("backgroundJob(): Running background job...");
|
15
|
+
await utilities.sleep(3 * 1000);
|
16
|
+
console.log("backgroundJob(): ...finished running background job.");
|
17
|
+
});
|
18
|
+
console.log("backgroundJob(): Press ⌃Z to force background job to run and ⌃C to continue...");
|
19
|
+
process.on("SIGTSTP", () => {
|
20
|
+
backgroundJob.run();
|
21
|
+
});
|
22
|
+
await node.shouldTerminate();
|
23
|
+
backgroundJob.stop();
|
24
|
+
});
|
25
|
+
test("sleep()", async () => {
|
26
|
+
const before = Date.now();
|
27
|
+
await utilities.sleep(1000);
|
28
|
+
assert(Date.now() - before >= 1000);
|
29
|
+
});
|
30
|
+
test("randomString()", () => {
|
31
|
+
const randomString = utilities.randomString();
|
32
|
+
assert(10 <= randomString.length && randomString.length <= 11);
|
33
|
+
assert.match(randomString, /^[0-9a-z]+$/);
|
34
|
+
});
|
35
|
+
test("randomString()", () => {
|
36
|
+
utilities.log("EXAMPLE", "OF", "TAB-SEPARATED LOGGING");
|
37
|
+
});
|
4
38
|
test("intern()", () => {
|
5
39
|
// @ts-expect-error
|
6
40
|
assert(([1] === [1]) === false);
|
@@ -45,27 +79,4 @@ test("intern()", () => {
|
|
45
79
|
$([1])[0] = 2;
|
46
80
|
});
|
47
81
|
});
|
48
|
-
// test(
|
49
|
-
// "backgroundJob()",
|
50
|
-
// {
|
51
|
-
// ...(!process.stdin.isTTY
|
52
|
-
// ? {
|
53
|
-
// skip: "Run interactive test with ‘node ./build/index.test.mjs’.",
|
54
|
-
// }
|
55
|
-
// : {}),
|
56
|
-
// },
|
57
|
-
// async () => {
|
58
|
-
// const backgroundJob = node.backgroundJob({ interval: 3 * 1000 }, () => {
|
59
|
-
// console.log("backgroundJob(): Running background job...");
|
60
|
-
// });
|
61
|
-
// process.on("SIGTSTP", () => {
|
62
|
-
// backgroundJob.run();
|
63
|
-
// });
|
64
|
-
// console.log(
|
65
|
-
// "backgroundJob(): Press ⌃Z to force background job to run and ⌃C to continue...",
|
66
|
-
// );
|
67
|
-
// await node.shouldTerminate();
|
68
|
-
// backgroundJob.stop();
|
69
|
-
// },
|
70
|
-
// );
|
71
82
|
//# 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;
|
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": "1.0.1",
|
4
4
|
"description": "🛠️ Utilities for Node.js and the browser",
|
5
5
|
"keywords": [
|
6
6
|
"node",
|
@@ -29,6 +29,7 @@
|
|
29
29
|
},
|
30
30
|
"devDependencies": {
|
31
31
|
"@radically-straightforward/documentation": "^1.0.1",
|
32
|
+
"@radically-straightforward/node": "^2.0.1",
|
32
33
|
"@radically-straightforward/tsconfig": "^1.0.0",
|
33
34
|
"@types/node": "^20.10.6",
|
34
35
|
"prettier": "^3.1.1",
|
package/source/index.mts
CHANGED
@@ -1,3 +1,118 @@
|
|
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 (not waiting the interval).
|
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
|
+
* **Example**
|
25
|
+
*
|
26
|
+
* ```javascript
|
27
|
+
* import * as utilities from "@radically-straightforward/utilities";
|
28
|
+
* import * as node from "@radically-straightforward/node";
|
29
|
+
*
|
30
|
+
* const backgroundJob = utilities.backgroundJob(
|
31
|
+
* { interval: 3 * 1000 },
|
32
|
+
* async () => {
|
33
|
+
* console.log("backgroundJob(): Running background job...");
|
34
|
+
* await utilities.sleep(3 * 1000);
|
35
|
+
* console.log("backgroundJob(): ...finished running background job.");
|
36
|
+
* },
|
37
|
+
* );
|
38
|
+
* console.log(
|
39
|
+
* "backgroundJob(): Press ⌃Z to force background job to run and ⌃C to continue...",
|
40
|
+
* );
|
41
|
+
* process.on("SIGTSTP", () => {
|
42
|
+
* backgroundJob.run();
|
43
|
+
* });
|
44
|
+
* await node.shouldTerminate();
|
45
|
+
* backgroundJob.stop();
|
46
|
+
* ```
|
47
|
+
*/
|
48
|
+
export function backgroundJob(
|
49
|
+
{
|
50
|
+
interval,
|
51
|
+
intervalVariance = 0.1,
|
52
|
+
}: { interval: number; intervalVariance?: number },
|
53
|
+
job: () => void | Promise<void>,
|
54
|
+
): { run: () => void; stop: () => void } {
|
55
|
+
let state:
|
56
|
+
| "initial"
|
57
|
+
| "running"
|
58
|
+
| "runningAndMarkedForRerun"
|
59
|
+
| "sleeping"
|
60
|
+
| "stopped" = "initial";
|
61
|
+
let timeout: any = undefined;
|
62
|
+
async function run() {
|
63
|
+
state = "running";
|
64
|
+
await job();
|
65
|
+
if (state === "running" || state === "runningAndMarkedForRerun") {
|
66
|
+
timeout = setTimeout(
|
67
|
+
run,
|
68
|
+
(state as any) === "runningAndMarkedForRerun"
|
69
|
+
? 0
|
70
|
+
: interval + interval * intervalVariance * Math.random(),
|
71
|
+
);
|
72
|
+
state = "sleeping";
|
73
|
+
}
|
74
|
+
}
|
75
|
+
run();
|
76
|
+
return {
|
77
|
+
run: () => {
|
78
|
+
switch (state) {
|
79
|
+
case "sleeping":
|
80
|
+
clearTimeout(timeout);
|
81
|
+
run();
|
82
|
+
break;
|
83
|
+
case "running":
|
84
|
+
state = "runningAndMarkedForRerun";
|
85
|
+
break;
|
86
|
+
}
|
87
|
+
},
|
88
|
+
stop: () => {
|
89
|
+
if (state === "sleeping") clearTimeout(timeout);
|
90
|
+
state = "stopped";
|
91
|
+
},
|
92
|
+
};
|
93
|
+
}
|
94
|
+
|
95
|
+
/**
|
96
|
+
* 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/dist/latest-v21.x/docs/api/timers.html#timerspromisessettimeoutdelay-value-options).
|
97
|
+
*/
|
98
|
+
export function sleep(duration: number): Promise<void> {
|
99
|
+
return new Promise((resolve) => setTimeout(resolve, duration));
|
100
|
+
}
|
101
|
+
|
102
|
+
/**
|
103
|
+
* A fast random string generator. The generated strings are 10 or 11 characters in length. The generated strings include the characters `[0-9a-z]`. The generated strings are **not** cryptographically secure—if you need that, then use [`crypto-random-string`](https://npm.im/crypto-random-string).
|
104
|
+
*/
|
105
|
+
export function randomString(): string {
|
106
|
+
return Math.random().toString(36).slice(2);
|
107
|
+
}
|
108
|
+
|
109
|
+
/**
|
110
|
+
* Tab-separated logging.
|
111
|
+
*/
|
112
|
+
export function log(...messageParts: string[]): void {
|
113
|
+
console.log(messageParts.join(" \t"));
|
114
|
+
}
|
115
|
+
|
1
116
|
/**
|
2
117
|
* Utility type for `intern()`.
|
3
118
|
*/
|
@@ -70,7 +185,7 @@ export type InternInnerValue =
|
|
70
185
|
*
|
71
186
|
* > **Note:** Interned objects do not preserve the order of the attributes: `$({ a: 1, b: 2 }) === $({ b: 2, a: 1 })`.
|
72
187
|
*
|
73
|
-
* > **Note:** The pool of interned values is available as `intern.pool`. There’s a `FinalizationRegistry` at `intern.finalizationRegistry` that cleans up interned values that have been garbage collected.
|
188
|
+
* > **Note:** The pool of interned values is available as `intern.pool`. The interned values are kept with `WeakRef`s to allow them to be garbage collected when they aren’t referenced anywhere else anymore. There’s a `FinalizationRegistry` at `intern.finalizationRegistry` that cleans up interned values that have been garbage collected.
|
74
189
|
*
|
75
190
|
* **Related Work**
|
76
191
|
*
|
@@ -181,94 +296,3 @@ intern.finalizationRegistry = new FinalizationRegistry<{
|
|
181
296
|
}>(({ type, key }) => {
|
182
297
|
intern.pool[type].delete(key);
|
183
298
|
});
|
184
|
-
|
185
|
-
/*
|
186
|
-
|
187
|
-
|
188
|
-
Math.random().toString(36).slice(2)
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
https://npm.im/package/p-timeout
|
194
|
-
https://npm.im/package/delay
|
195
|
-
https://npm.im/package/sleep-promise
|
196
|
-
https://npm.im/package/promise-timeout
|
197
|
-
https://npm.im/package/sleep
|
198
|
-
https://npm.im/package/timeout-as-promise
|
199
|
-
https://npm.im/package/delayed
|
200
|
-
https://npm.im/package/sleep-async
|
201
|
-
https://npm.im/package/promise.timeout
|
202
|
-
|
203
|
-
*/
|
204
|
-
// /**
|
205
|
-
// - Remove uses of `node:timers/promises`?
|
206
|
-
// *
|
207
|
-
// * TODO: In universal JavaScript, implement a way to **canonicalize** objects using deepEquals to be used in Sets and as Map keys (then deprecate `collections-deep-equal`).
|
208
|
-
// * - value objects
|
209
|
-
// * - https://lodash.com/docs/4.17.15#isEqual
|
210
|
-
// * TODO: Implement using setTimeout and let it be usable in client-side JavaScript as well.
|
211
|
-
// * TODO: Explain the differences between this and `setInterval()` (wait for completion before setting the next scheduler, and force a job to run)
|
212
|
-
// *
|
213
|
-
// * Start a background job that runs every given `interval`.
|
214
|
-
// *
|
215
|
-
// * You may use `backgroundJob.run()` to force the background job to run right away.
|
216
|
-
// *
|
217
|
-
// * **Note:** If a background job is running when `backgroundJob.continue()` is called.
|
218
|
-
// *
|
219
|
-
// * You may use `backgroundJob.stop()` to stop the background job.
|
220
|
-
// *
|
221
|
-
// * **Note:** If a background job is running when `backgroundJob.stop()` is called, then that background job is run to completion, but a future background job run is not scheduled. This is similar to how an HTTP server may stop finish processing existing requests but don’t accept new requests.
|
222
|
-
// *
|
223
|
-
// * **Note:** The `intervalVariance` prevents many background jobs from starting at the same and overloading the machine.
|
224
|
-
// *
|
225
|
-
// * **Example**
|
226
|
-
// *
|
227
|
-
// * ```javascript
|
228
|
-
// * import * as node from "@radically-straightforward/node";
|
229
|
-
// *
|
230
|
-
// * const backgroundJob = node.backgroundJob({ interval: 3 * 1000 }, () => {
|
231
|
-
// * console.log("backgroundJob(): Running background job...");
|
232
|
-
// * });
|
233
|
-
// * process.on("SIGTSTP", () => {
|
234
|
-
// * backgroundJob.run();
|
235
|
-
// * });
|
236
|
-
// * console.log(
|
237
|
-
// * "backgroundJob(): Press ⌃Z to force background job to run and ⌃C to continue...",
|
238
|
-
// * );
|
239
|
-
// * await node.shouldTerminate();
|
240
|
-
// * backgroundJob.stop();
|
241
|
-
// * ```
|
242
|
-
// */
|
243
|
-
// export function backgroundJob(
|
244
|
-
// {
|
245
|
-
// interval,
|
246
|
-
// intervalVariance = 0.1,
|
247
|
-
// }: { interval: number; intervalVariance?: number },
|
248
|
-
// function_: () => void | Promise<void>,
|
249
|
-
// ): { run: () => void; stop: () => void } {
|
250
|
-
// let shouldContinue = true;
|
251
|
-
// let abortController = new AbortController();
|
252
|
-
// (async () => {
|
253
|
-
// while (shouldContinue) {
|
254
|
-
// await function_();
|
255
|
-
// await timers
|
256
|
-
// .setTimeout(
|
257
|
-
// interval + interval * intervalVariance * Math.random(),
|
258
|
-
// undefined,
|
259
|
-
// { signal: abortController.signal },
|
260
|
-
// )
|
261
|
-
// .catch(() => {});
|
262
|
-
// abortController = new AbortController();
|
263
|
-
// }
|
264
|
-
// })();
|
265
|
-
// return {
|
266
|
-
// run: () => {
|
267
|
-
// abortController.abort();
|
268
|
-
// },
|
269
|
-
// stop: () => {
|
270
|
-
// shouldContinue = false;
|
271
|
-
// abortController.abort();
|
272
|
-
// },
|
273
|
-
// };
|
274
|
-
// }
|
package/source/index.test.mts
CHANGED
@@ -1,8 +1,54 @@
|
|
1
1
|
import test from "node:test";
|
2
2
|
import assert from "node:assert/strict";
|
3
|
+
import * as node from "@radically-straightforward/node";
|
3
4
|
import * as utilities from "./index.mjs";
|
4
5
|
import { intern as $ } from "./index.mjs";
|
5
6
|
|
7
|
+
test(
|
8
|
+
"backgroundJob()",
|
9
|
+
{
|
10
|
+
...(!process.stdin.isTTY
|
11
|
+
? {
|
12
|
+
skip: "Run interactive test with ‘node ./build/index.test.mjs’.",
|
13
|
+
}
|
14
|
+
: {}),
|
15
|
+
},
|
16
|
+
async () => {
|
17
|
+
const backgroundJob = utilities.backgroundJob(
|
18
|
+
{ interval: 3 * 1000 },
|
19
|
+
async () => {
|
20
|
+
console.log("backgroundJob(): Running background job...");
|
21
|
+
await utilities.sleep(3 * 1000);
|
22
|
+
console.log("backgroundJob(): ...finished running background job.");
|
23
|
+
},
|
24
|
+
);
|
25
|
+
console.log(
|
26
|
+
"backgroundJob(): Press ⌃Z to force background job to run and ⌃C to continue...",
|
27
|
+
);
|
28
|
+
process.on("SIGTSTP", () => {
|
29
|
+
backgroundJob.run();
|
30
|
+
});
|
31
|
+
await node.shouldTerminate();
|
32
|
+
backgroundJob.stop();
|
33
|
+
},
|
34
|
+
);
|
35
|
+
|
36
|
+
test("sleep()", async () => {
|
37
|
+
const before = Date.now();
|
38
|
+
await utilities.sleep(1000);
|
39
|
+
assert(Date.now() - before >= 1000);
|
40
|
+
});
|
41
|
+
|
42
|
+
test("randomString()", () => {
|
43
|
+
const randomString = utilities.randomString();
|
44
|
+
assert(10 <= randomString.length && randomString.length <= 11);
|
45
|
+
assert.match(randomString, /^[0-9a-z]+$/);
|
46
|
+
});
|
47
|
+
|
48
|
+
test("randomString()", () => {
|
49
|
+
utilities.log("EXAMPLE", "OF", "TAB-SEPARATED LOGGING");
|
50
|
+
});
|
51
|
+
|
6
52
|
test("intern()", () => {
|
7
53
|
// @ts-expect-error
|
8
54
|
assert(([1] === [1]) === false);
|
@@ -54,27 +100,3 @@ test("intern()", () => {
|
|
54
100
|
$([1])[0] = 2;
|
55
101
|
});
|
56
102
|
});
|
57
|
-
|
58
|
-
// test(
|
59
|
-
// "backgroundJob()",
|
60
|
-
// {
|
61
|
-
// ...(!process.stdin.isTTY
|
62
|
-
// ? {
|
63
|
-
// skip: "Run interactive test with ‘node ./build/index.test.mjs’.",
|
64
|
-
// }
|
65
|
-
// : {}),
|
66
|
-
// },
|
67
|
-
// async () => {
|
68
|
-
// const backgroundJob = node.backgroundJob({ interval: 3 * 1000 }, () => {
|
69
|
-
// console.log("backgroundJob(): Running background job...");
|
70
|
-
// });
|
71
|
-
// process.on("SIGTSTP", () => {
|
72
|
-
// backgroundJob.run();
|
73
|
-
// });
|
74
|
-
// console.log(
|
75
|
-
// "backgroundJob(): Press ⌃Z to force background job to run and ⌃C to continue...",
|
76
|
-
// );
|
77
|
-
// await node.shouldTerminate();
|
78
|
-
// backgroundJob.stop();
|
79
|
-
// },
|
80
|
-
// );
|