@radically-straightforward/utilities 0.0.3 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +6 -0
- package/README.md +81 -1
- package/build/index.d.mts +63 -1
- package/build/index.d.mts.map +1 -1
- package/build/index.mjs +93 -91
- package/build/index.mjs.map +1 -1
- package/build/index.test.mjs +31 -23
- package/build/index.test.mjs.map +1 -1
- package/package.json +2 -1
- package/source/index.mts +109 -92
- package/source/index.test.mts +42 -24
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
@@ -16,6 +16,86 @@ 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()`. It doesn’t offer a way to `clearTimeout()`. Useful in the browser—in Node.js 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
|
+
|
19
99
|
### `Intern`
|
20
100
|
|
21
101
|
```typescript
|
@@ -109,7 +189,7 @@ $([1]) === $([1]); // => true
|
|
109
189
|
|
110
190
|
> **Note:** Interned objects do not preserve the order of the attributes: `$({ a: 1, b: 2 }) === $({ b: 2, a: 1 })`.
|
111
191
|
|
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.
|
192
|
+
> **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
193
|
|
114
194
|
**Related Work**
|
115
195
|
|
package/build/index.d.mts
CHANGED
@@ -1,3 +1,65 @@
|
|
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()`. It doesn’t offer a way to `clearTimeout()`. Useful in the browser—in Node.js 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;
|
1
63
|
/**
|
2
64
|
* Utility type for `intern()`.
|
3
65
|
*/
|
@@ -62,7 +124,7 @@ export type InternInnerValue = string | number | bigint | boolean | symbol | und
|
|
62
124
|
*
|
63
125
|
* > **Note:** Interned objects do not preserve the order of the attributes: `$({ a: 1, b: 2 }) === $({ b: 2, a: 1 })`.
|
64
126
|
*
|
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.
|
127
|
+
* > **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
128
|
*
|
67
129
|
* **Related Work**
|
68
130
|
*
|
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,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,95 @@
|
|
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()`. It doesn’t offer a way to `clearTimeout()`. Useful in the browser—in Node.js 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
|
+
}
|
1
93
|
/**
|
2
94
|
* [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
95
|
*
|
@@ -52,7 +144,7 @@
|
|
52
144
|
*
|
53
145
|
* > **Note:** Interned objects do not preserve the order of the attributes: `$({ a: 1, b: 2 }) === $({ b: 2, a: 1 })`.
|
54
146
|
*
|
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.
|
147
|
+
* > **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
148
|
*
|
57
149
|
* **Related Work**
|
58
150
|
*
|
@@ -144,94 +236,4 @@ intern.pool = {
|
|
144
236
|
intern.finalizationRegistry = new FinalizationRegistry(({ type, key }) => {
|
145
237
|
intern.pool[type].delete(key);
|
146
238
|
});
|
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
239
|
//# 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,37 @@
|
|
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
|
+
});
|
4
35
|
test("intern()", () => {
|
5
36
|
// @ts-expect-error
|
6
37
|
assert(([1] === [1]) === false);
|
@@ -45,27 +76,4 @@ test("intern()", () => {
|
|
45
76
|
$([1])[0] = 2;
|
46
77
|
});
|
47
78
|
});
|
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
79
|
//# 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": "0.0
|
3
|
+
"version": "1.0.0",
|
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,111 @@
|
|
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()`. It doesn’t offer a way to `clearTimeout()`. Useful in the browser—in Node.js 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
|
+
|
1
109
|
/**
|
2
110
|
* Utility type for `intern()`.
|
3
111
|
*/
|
@@ -70,7 +178,7 @@ export type InternInnerValue =
|
|
70
178
|
*
|
71
179
|
* > **Note:** Interned objects do not preserve the order of the attributes: `$({ a: 1, b: 2 }) === $({ b: 2, a: 1 })`.
|
72
180
|
*
|
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.
|
181
|
+
* > **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
182
|
*
|
75
183
|
* **Related Work**
|
76
184
|
*
|
@@ -181,94 +289,3 @@ intern.finalizationRegistry = new FinalizationRegistry<{
|
|
181
289
|
}>(({ type, key }) => {
|
182
290
|
intern.pool[type].delete(key);
|
183
291
|
});
|
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,50 @@
|
|
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
|
+
|
6
48
|
test("intern()", () => {
|
7
49
|
// @ts-expect-error
|
8
50
|
assert(([1] === [1]) === false);
|
@@ -54,27 +96,3 @@ test("intern()", () => {
|
|
54
96
|
$([1])[0] = 2;
|
55
97
|
});
|
56
98
|
});
|
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
|
-
// );
|