@radically-straightforward/utilities 0.0.2 → 1.0.0
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 +120 -10
- package/build/index.d.mts +93 -13
- package/build/index.d.mts.map +1 -1
- package/build/index.mjs +107 -103
- package/build/index.mjs.map +1 -1
- package/build/index.test.mjs +35 -24
- package/build/index.test.mjs.map +1 -1
- package/package.json +2 -1
- package/source/index.mts +152 -112
- package/source/index.test.mts +51 -29
package/CHANGELOG.md
CHANGED
@@ -1,5 +1,15 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## 1.0.0 · 2024-01-06
|
4
|
+
|
5
|
+
- Added `backgroundJob()`.
|
6
|
+
- Added `sleep()`.
|
7
|
+
- Added `randomString()`.
|
8
|
+
|
9
|
+
## 0.0.3 · 2024-01-05
|
10
|
+
|
11
|
+
- Made `intern()` more strict in terms of types and provide auxiliary types for it.
|
12
|
+
|
3
13
|
## 0.0.2 · 2024-01-05
|
4
14
|
|
5
15
|
- **Breaking Change:** Modified `intern()` to be shallow, which may be easier to reason about and faster.
|
package/README.md
CHANGED
@@ -11,33 +11,141 @@ $ npm install @radically-straightforward/utilities
|
|
11
11
|
## Usage
|
12
12
|
|
13
13
|
```typescript
|
14
|
-
import * as
|
14
|
+
import * as utilities from "@radically-straightforward/utilities";
|
15
15
|
```
|
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
|
+
|
99
|
+
### `Intern`
|
100
|
+
|
101
|
+
```typescript
|
102
|
+
export type Intern<Type> = Readonly<
|
103
|
+
Type & {
|
104
|
+
[internSymbol]: true;
|
105
|
+
}
|
106
|
+
>;
|
107
|
+
```
|
108
|
+
|
109
|
+
Utility type for `intern()`.
|
110
|
+
|
111
|
+
### `InternInnerValue`
|
112
|
+
|
113
|
+
```typescript
|
114
|
+
export type InternInnerValue =
|
115
|
+
| string
|
116
|
+
| number
|
117
|
+
| bigint
|
118
|
+
| boolean
|
119
|
+
| symbol
|
120
|
+
| undefined
|
121
|
+
| null
|
122
|
+
| Intern<unknown>;
|
123
|
+
```
|
124
|
+
|
125
|
+
Utility type for `intern()`.
|
126
|
+
|
19
127
|
### `intern()`
|
20
128
|
|
21
129
|
```typescript
|
22
130
|
export function intern<
|
23
131
|
T extends
|
24
|
-
| Array<
|
132
|
+
| Array<InternInnerValue>
|
25
133
|
| {
|
26
|
-
[key: string]:
|
134
|
+
[key: string]: InternInnerValue;
|
27
135
|
},
|
28
|
-
>(value: T): T
|
136
|
+
>(value: T): Intern<T>;
|
29
137
|
```
|
30
138
|
|
31
139
|
[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:
|
32
140
|
|
33
|
-
```
|
141
|
+
```typescript
|
34
142
|
import { intern as $ } from "@radically-straightforward/utilities";
|
35
143
|
|
36
144
|
[1] === [1]; // => false
|
37
145
|
$([1]) === $([1]); // => true
|
38
146
|
|
39
147
|
{
|
40
|
-
const map = new Map();
|
148
|
+
const map = new Map<number[], number>();
|
41
149
|
map.set([1], 1);
|
42
150
|
map.set([1], 2);
|
43
151
|
map.size; // => 2
|
@@ -45,7 +153,7 @@ $([1]) === $([1]); // => true
|
|
45
153
|
}
|
46
154
|
|
47
155
|
{
|
48
|
-
const map = new Map();
|
156
|
+
const map = new Map<utilities.Intern<number[]>, number>();
|
49
157
|
map.set($([1]), 1);
|
50
158
|
map.set($([1]), 2);
|
51
159
|
map.size; // => 1
|
@@ -53,7 +161,7 @@ $([1]) === $([1]); // => true
|
|
53
161
|
}
|
54
162
|
|
55
163
|
{
|
56
|
-
const set = new Set();
|
164
|
+
const set = new Set<number[]>();
|
57
165
|
set.add([1]);
|
58
166
|
set.add([1]);
|
59
167
|
set.size; // => 2
|
@@ -61,7 +169,7 @@ $([1]) === $([1]); // => true
|
|
61
169
|
}
|
62
170
|
|
63
171
|
{
|
64
|
-
const set = new Set();
|
172
|
+
const set = new Set<utilities.Intern<number[]>>();
|
65
173
|
set.add($([1]));
|
66
174
|
set.add($([1]));
|
67
175
|
set.size; // => 1
|
@@ -79,7 +187,9 @@ $([1]) === $([1]); // => true
|
|
79
187
|
|
80
188
|
> **Note:** Interning a value is a costly operation which grows more expensive as you intern more values. Only intern values when really necessary.
|
81
189
|
|
82
|
-
> **Note:**
|
190
|
+
> **Note:** Interned objects do not preserve the order of the attributes: `$({ a: 1, b: 2 }) === $({ b: 2, a: 1 })`.
|
191
|
+
|
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.
|
83
193
|
|
84
194
|
**Related Work**
|
85
195
|
|
package/build/index.d.mts
CHANGED
@@ -1,14 +1,86 @@
|
|
1
1
|
/**
|
2
|
-
*
|
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**
|
3
25
|
*
|
4
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;
|
63
|
+
/**
|
64
|
+
* Utility type for `intern()`.
|
65
|
+
*/
|
66
|
+
export type Intern<Type> = Readonly<Type & {
|
67
|
+
[internSymbol]: true;
|
68
|
+
}>;
|
69
|
+
/**
|
70
|
+
* Utility type for `intern()`.
|
71
|
+
*/
|
72
|
+
export type InternInnerValue = string | number | bigint | boolean | symbol | undefined | null | Intern<unknown>;
|
73
|
+
/**
|
74
|
+
* [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:
|
75
|
+
*
|
76
|
+
* ```typescript
|
5
77
|
* import { intern as $ } from "@radically-straightforward/utilities";
|
6
78
|
*
|
7
79
|
* [1] === [1]; // => false
|
8
80
|
* $([1]) === $([1]); // => true
|
9
81
|
*
|
10
82
|
* {
|
11
|
-
* const map = new Map();
|
83
|
+
* const map = new Map<number[], number>();
|
12
84
|
* map.set([1], 1);
|
13
85
|
* map.set([1], 2);
|
14
86
|
* map.size; // => 2
|
@@ -16,7 +88,7 @@
|
|
16
88
|
* }
|
17
89
|
*
|
18
90
|
* {
|
19
|
-
* const map = new Map();
|
91
|
+
* const map = new Map<utilities.Intern<number[]>, number>();
|
20
92
|
* map.set($([1]), 1);
|
21
93
|
* map.set($([1]), 2);
|
22
94
|
* map.size; // => 1
|
@@ -24,7 +96,7 @@
|
|
24
96
|
* }
|
25
97
|
*
|
26
98
|
* {
|
27
|
-
* const set = new Set();
|
99
|
+
* const set = new Set<number[]>();
|
28
100
|
* set.add([1]);
|
29
101
|
* set.add([1]);
|
30
102
|
* set.size; // => 2
|
@@ -32,7 +104,7 @@
|
|
32
104
|
* }
|
33
105
|
*
|
34
106
|
* {
|
35
|
-
* const set = new Set();
|
107
|
+
* const set = new Set<utilities.Intern<number[]>>();
|
36
108
|
* set.add($([1]));
|
37
109
|
* set.add($([1]));
|
38
110
|
* set.size; // => 1
|
@@ -50,7 +122,9 @@
|
|
50
122
|
*
|
51
123
|
* > **Note:** Interning a value is a costly operation which grows more expensive as you intern more values. Only intern values when really necessary.
|
52
124
|
*
|
53
|
-
* > **Note:**
|
125
|
+
* > **Note:** Interned objects do not preserve the order of the attributes: `$({ a: 1, b: 2 }) === $({ b: 2, a: 1 })`.
|
126
|
+
*
|
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.
|
54
128
|
*
|
55
129
|
* **Related Work**
|
56
130
|
*
|
@@ -98,18 +172,24 @@
|
|
98
172
|
* - <https://twitter.com/swannodette/status/1067962983924539392>
|
99
173
|
* - <https://gist.github.com/modernserf/c000e62d40f678cf395e3f360b9b0e43>
|
100
174
|
*/
|
101
|
-
export declare function intern<T extends Array<
|
102
|
-
[key: string]:
|
103
|
-
}>(value: T): T
|
175
|
+
export declare function intern<T extends Array<InternInnerValue> | {
|
176
|
+
[key: string]: InternInnerValue;
|
177
|
+
}>(value: T): Intern<T>;
|
104
178
|
export declare namespace intern {
|
105
|
-
var
|
106
|
-
|
107
|
-
|
108
|
-
|
179
|
+
var pool: {
|
180
|
+
tuple: Map<Symbol, WeakRef<Readonly<InternInnerValue[] & {
|
181
|
+
[internSymbol]: true;
|
182
|
+
}>>>;
|
183
|
+
record: Map<Symbol, WeakRef<Readonly<{
|
184
|
+
[key: string]: InternInnerValue;
|
185
|
+
} & {
|
186
|
+
[internSymbol]: true;
|
187
|
+
}>>>;
|
109
188
|
};
|
110
189
|
var finalizationRegistry: FinalizationRegistry<{
|
111
190
|
type: "tuple" | "record";
|
112
191
|
key: Symbol;
|
113
192
|
}>;
|
114
193
|
}
|
194
|
+
export declare const internSymbol: unique symbol;
|
115
195
|
//# 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":"AAAA
|
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,14 +1,106 @@
|
|
1
1
|
/**
|
2
|
-
*
|
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**
|
3
25
|
*
|
4
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
|
+
}
|
93
|
+
/**
|
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:
|
95
|
+
*
|
96
|
+
* ```typescript
|
5
97
|
* import { intern as $ } from "@radically-straightforward/utilities";
|
6
98
|
*
|
7
99
|
* [1] === [1]; // => false
|
8
100
|
* $([1]) === $([1]); // => true
|
9
101
|
*
|
10
102
|
* {
|
11
|
-
* const map = new Map();
|
103
|
+
* const map = new Map<number[], number>();
|
12
104
|
* map.set([1], 1);
|
13
105
|
* map.set([1], 2);
|
14
106
|
* map.size; // => 2
|
@@ -16,7 +108,7 @@
|
|
16
108
|
* }
|
17
109
|
*
|
18
110
|
* {
|
19
|
-
* const map = new Map();
|
111
|
+
* const map = new Map<utilities.Intern<number[]>, number>();
|
20
112
|
* map.set($([1]), 1);
|
21
113
|
* map.set($([1]), 2);
|
22
114
|
* map.size; // => 1
|
@@ -24,7 +116,7 @@
|
|
24
116
|
* }
|
25
117
|
*
|
26
118
|
* {
|
27
|
-
* const set = new Set();
|
119
|
+
* const set = new Set<number[]>();
|
28
120
|
* set.add([1]);
|
29
121
|
* set.add([1]);
|
30
122
|
* set.size; // => 2
|
@@ -32,7 +124,7 @@
|
|
32
124
|
* }
|
33
125
|
*
|
34
126
|
* {
|
35
|
-
* const set = new Set();
|
127
|
+
* const set = new Set<utilities.Intern<number[]>>();
|
36
128
|
* set.add($([1]));
|
37
129
|
* set.add($([1]));
|
38
130
|
* set.size; // => 1
|
@@ -50,7 +142,9 @@
|
|
50
142
|
*
|
51
143
|
* > **Note:** Interning a value is a costly operation which grows more expensive as you intern more values. Only intern values when really necessary.
|
52
144
|
*
|
53
|
-
* > **Note:**
|
145
|
+
* > **Note:** Interned objects do not preserve the order of the attributes: `$({ a: 1, b: 2 }) === $({ b: 2, a: 1 })`.
|
146
|
+
*
|
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.
|
54
148
|
*
|
55
149
|
* **Related Work**
|
56
150
|
*
|
@@ -107,7 +201,7 @@ export function intern(value) {
|
|
107
201
|
throw new Error(`Failed to intern value.`);
|
108
202
|
})();
|
109
203
|
const keys = Object.keys(value);
|
110
|
-
for (const internWeakRef of intern.
|
204
|
+
for (const internWeakRef of intern.pool[type].values()) {
|
111
205
|
const internValue = internWeakRef.deref();
|
112
206
|
if (internValue === undefined ||
|
113
207
|
keys.length !== Object.keys(internValue).length)
|
@@ -125,111 +219,21 @@ export function intern(value) {
|
|
125
219
|
"undefined",
|
126
220
|
].includes(typeof innerValue) ||
|
127
221
|
innerValue === null ||
|
128
|
-
innerValue[
|
222
|
+
innerValue[internSymbol] === true))
|
129
223
|
throw new Error(`Failed to intern value because of non-interned inner value.`);
|
130
224
|
const key = Symbol();
|
131
|
-
value[
|
225
|
+
value[internSymbol] = true;
|
132
226
|
Object.freeze(value);
|
133
|
-
intern.
|
227
|
+
intern.pool[type].set(key, new WeakRef(value));
|
134
228
|
intern.finalizationRegistry.register(value, { type, key });
|
135
229
|
return value;
|
136
230
|
}
|
137
|
-
|
138
|
-
intern.
|
231
|
+
export const internSymbol = Symbol("intern");
|
232
|
+
intern.pool = {
|
139
233
|
tuple: new Map(),
|
140
234
|
record: new Map(),
|
141
235
|
};
|
142
236
|
intern.finalizationRegistry = new FinalizationRegistry(({ type, key }) => {
|
143
|
-
intern.
|
237
|
+
intern.pool[type].delete(key);
|
144
238
|
});
|
145
|
-
/*
|
146
|
-
|
147
|
-
|
148
|
-
Math.random().toString(36).slice(2)
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
https://npm.im/package/p-timeout
|
154
|
-
https://npm.im/package/delay
|
155
|
-
https://npm.im/package/sleep-promise
|
156
|
-
https://npm.im/package/promise-timeout
|
157
|
-
https://npm.im/package/sleep
|
158
|
-
https://npm.im/package/timeout-as-promise
|
159
|
-
https://npm.im/package/delayed
|
160
|
-
https://npm.im/package/sleep-async
|
161
|
-
https://npm.im/package/promise.timeout
|
162
|
-
|
163
|
-
*/
|
164
|
-
// /**
|
165
|
-
// - Remove uses of `node:timers/promises`?
|
166
|
-
// *
|
167
|
-
// * 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`).
|
168
|
-
// * - value objects
|
169
|
-
// * - https://lodash.com/docs/4.17.15#isEqual
|
170
|
-
// * TODO: Implement using setTimeout and let it be usable in client-side JavaScript as well.
|
171
|
-
// * TODO: Explain the differences between this and `setInterval()` (wait for completion before setting the next scheduler, and force a job to run)
|
172
|
-
// *
|
173
|
-
// * Start a background job that runs every given `interval`.
|
174
|
-
// *
|
175
|
-
// * You may use `backgroundJob.run()` to force the background job to run right away.
|
176
|
-
// *
|
177
|
-
// * **Note:** If a background job is running when `backgroundJob.continue()` is called.
|
178
|
-
// *
|
179
|
-
// * You may use `backgroundJob.stop()` to stop the background job.
|
180
|
-
// *
|
181
|
-
// * **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.
|
182
|
-
// *
|
183
|
-
// * **Note:** The `intervalVariance` prevents many background jobs from starting at the same and overloading the machine.
|
184
|
-
// *
|
185
|
-
// * **Example**
|
186
|
-
// *
|
187
|
-
// * ```javascript
|
188
|
-
// * import * as node from "@radically-straightforward/node";
|
189
|
-
// *
|
190
|
-
// * const backgroundJob = node.backgroundJob({ interval: 3 * 1000 }, () => {
|
191
|
-
// * console.log("backgroundJob(): Running background job...");
|
192
|
-
// * });
|
193
|
-
// * process.on("SIGTSTP", () => {
|
194
|
-
// * backgroundJob.run();
|
195
|
-
// * });
|
196
|
-
// * console.log(
|
197
|
-
// * "backgroundJob(): Press ⌃Z to force background job to run and ⌃C to continue...",
|
198
|
-
// * );
|
199
|
-
// * await node.shouldTerminate();
|
200
|
-
// * backgroundJob.stop();
|
201
|
-
// * ```
|
202
|
-
// */
|
203
|
-
// export function backgroundJob(
|
204
|
-
// {
|
205
|
-
// interval,
|
206
|
-
// intervalVariance = 0.1,
|
207
|
-
// }: { interval: number; intervalVariance?: number },
|
208
|
-
// function_: () => void | Promise<void>,
|
209
|
-
// ): { run: () => void; stop: () => void } {
|
210
|
-
// let shouldContinue = true;
|
211
|
-
// let abortController = new AbortController();
|
212
|
-
// (async () => {
|
213
|
-
// while (shouldContinue) {
|
214
|
-
// await function_();
|
215
|
-
// await timers
|
216
|
-
// .setTimeout(
|
217
|
-
// interval + interval * intervalVariance * Math.random(),
|
218
|
-
// undefined,
|
219
|
-
// { signal: abortController.signal },
|
220
|
-
// )
|
221
|
-
// .catch(() => {});
|
222
|
-
// abortController = new AbortController();
|
223
|
-
// }
|
224
|
-
// })();
|
225
|
-
// return {
|
226
|
-
// run: () => {
|
227
|
-
// abortController.abort();
|
228
|
-
// },
|
229
|
-
// stop: () => {
|
230
|
-
// shouldContinue = false;
|
231
|
-
// abortController.abort();
|
232
|
-
// },
|
233
|
-
// };
|
234
|
-
// }
|
235
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":"AAAA
|
1
|
+
{"version":3,"file":"index.mjs","sourceRoot":"","sources":["../source/index.mts"],"names":[],"mappings":"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"}
|
package/build/index.test.mjs
CHANGED
@@ -1,10 +1,42 @@
|
|
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
|
-
// @ts-
|
36
|
+
// @ts-expect-error
|
6
37
|
assert(([1] === [1]) === false);
|
7
38
|
assert($([1]) === $([1]));
|
39
|
+
assert($({ a: 1, b: 2 }) === $({ b: 2, a: 1 }));
|
8
40
|
assert($([1]) !== $([2]));
|
9
41
|
{
|
10
42
|
const map = new Map();
|
@@ -35,34 +67,13 @@ test("intern()", () => {
|
|
35
67
|
assert(set.has($([1])));
|
36
68
|
}
|
37
69
|
assert.throws(() => {
|
70
|
+
// @ts-expect-error
|
38
71
|
$([1, {}]);
|
39
72
|
});
|
40
73
|
assert($([1, $({})]) === $([1, $({})]));
|
41
74
|
assert.throws(() => {
|
75
|
+
// @ts-expect-error
|
42
76
|
$([1])[0] = 2;
|
43
77
|
});
|
44
78
|
});
|
45
|
-
// test(
|
46
|
-
// "backgroundJob()",
|
47
|
-
// {
|
48
|
-
// ...(!process.stdin.isTTY
|
49
|
-
// ? {
|
50
|
-
// skip: "Run interactive test with ‘node ./build/index.test.mjs’.",
|
51
|
-
// }
|
52
|
-
// : {}),
|
53
|
-
// },
|
54
|
-
// async () => {
|
55
|
-
// const backgroundJob = node.backgroundJob({ interval: 3 * 1000 }, () => {
|
56
|
-
// console.log("backgroundJob(): Running background job...");
|
57
|
-
// });
|
58
|
-
// process.on("SIGTSTP", () => {
|
59
|
-
// backgroundJob.run();
|
60
|
-
// });
|
61
|
-
// console.log(
|
62
|
-
// "backgroundJob(): Press ⌃Z to force background job to run and ⌃C to continue...",
|
63
|
-
// );
|
64
|
-
// await node.shouldTerminate();
|
65
|
-
// backgroundJob.stop();
|
66
|
-
// },
|
67
|
-
// );
|
68
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,14 +1,140 @@
|
|
1
1
|
/**
|
2
|
-
*
|
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**
|
3
25
|
*
|
4
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
|
+
|
109
|
+
/**
|
110
|
+
* Utility type for `intern()`.
|
111
|
+
*/
|
112
|
+
export type Intern<Type> = Readonly<Type & { [internSymbol]: true }>;
|
113
|
+
|
114
|
+
/**
|
115
|
+
* Utility type for `intern()`.
|
116
|
+
*/
|
117
|
+
export type InternInnerValue =
|
118
|
+
| string
|
119
|
+
| number
|
120
|
+
| bigint
|
121
|
+
| boolean
|
122
|
+
| symbol
|
123
|
+
| undefined
|
124
|
+
| null
|
125
|
+
| Intern<unknown>;
|
126
|
+
|
127
|
+
/**
|
128
|
+
* [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:
|
129
|
+
*
|
130
|
+
* ```typescript
|
5
131
|
* import { intern as $ } from "@radically-straightforward/utilities";
|
6
132
|
*
|
7
133
|
* [1] === [1]; // => false
|
8
134
|
* $([1]) === $([1]); // => true
|
9
135
|
*
|
10
136
|
* {
|
11
|
-
* const map = new Map();
|
137
|
+
* const map = new Map<number[], number>();
|
12
138
|
* map.set([1], 1);
|
13
139
|
* map.set([1], 2);
|
14
140
|
* map.size; // => 2
|
@@ -16,7 +142,7 @@
|
|
16
142
|
* }
|
17
143
|
*
|
18
144
|
* {
|
19
|
-
* const map = new Map();
|
145
|
+
* const map = new Map<utilities.Intern<number[]>, number>();
|
20
146
|
* map.set($([1]), 1);
|
21
147
|
* map.set($([1]), 2);
|
22
148
|
* map.size; // => 1
|
@@ -24,7 +150,7 @@
|
|
24
150
|
* }
|
25
151
|
*
|
26
152
|
* {
|
27
|
-
* const set = new Set();
|
153
|
+
* const set = new Set<number[]>();
|
28
154
|
* set.add([1]);
|
29
155
|
* set.add([1]);
|
30
156
|
* set.size; // => 2
|
@@ -32,7 +158,7 @@
|
|
32
158
|
* }
|
33
159
|
*
|
34
160
|
* {
|
35
|
-
* const set = new Set();
|
161
|
+
* const set = new Set<utilities.Intern<number[]>>();
|
36
162
|
* set.add($([1]));
|
37
163
|
* set.add($([1]));
|
38
164
|
* set.size; // => 1
|
@@ -50,7 +176,9 @@
|
|
50
176
|
*
|
51
177
|
* > **Note:** Interning a value is a costly operation which grows more expensive as you intern more values. Only intern values when really necessary.
|
52
178
|
*
|
53
|
-
* > **Note:**
|
179
|
+
* > **Note:** Interned objects do not preserve the order of the attributes: `$({ a: 1, b: 2 }) === $({ b: 2, a: 1 })`.
|
180
|
+
*
|
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.
|
54
182
|
*
|
55
183
|
* **Related Work**
|
56
184
|
*
|
@@ -98,9 +226,9 @@
|
|
98
226
|
* - <https://twitter.com/swannodette/status/1067962983924539392>
|
99
227
|
* - <https://gist.github.com/modernserf/c000e62d40f678cf395e3f360b9b0e43>
|
100
228
|
*/
|
101
|
-
export function intern<
|
102
|
-
|
103
|
-
): T {
|
229
|
+
export function intern<
|
230
|
+
T extends Array<InternInnerValue> | { [key: string]: InternInnerValue },
|
231
|
+
>(value: T): Intern<T> {
|
104
232
|
const type = Array.isArray(value)
|
105
233
|
? "tuple"
|
106
234
|
: typeof value === "object" && value !== null
|
@@ -109,15 +237,15 @@ export function intern<T extends Array<unknown> | { [key: string]: unknown }>(
|
|
109
237
|
throw new Error(`Failed to intern value.`);
|
110
238
|
})();
|
111
239
|
const keys = Object.keys(value);
|
112
|
-
for (const internWeakRef of intern.
|
240
|
+
for (const internWeakRef of intern.pool[type].values()) {
|
113
241
|
const internValue = internWeakRef.deref();
|
114
242
|
if (
|
115
243
|
internValue === undefined ||
|
116
244
|
keys.length !== Object.keys(internValue).length
|
117
245
|
)
|
118
246
|
continue;
|
119
|
-
if (keys.every((key) => (value as any)[key] === internValue[key]))
|
120
|
-
return internValue;
|
247
|
+
if (keys.every((key) => (value as any)[key] === (internValue as any)[key]))
|
248
|
+
return internValue as any;
|
121
249
|
}
|
122
250
|
for (const innerValue of Object.values(value))
|
123
251
|
if (
|
@@ -131,121 +259,33 @@ export function intern<T extends Array<unknown> | { [key: string]: unknown }>(
|
|
131
259
|
"undefined",
|
132
260
|
].includes(typeof innerValue) ||
|
133
261
|
innerValue === null ||
|
134
|
-
(innerValue as any)[
|
262
|
+
(innerValue as any)[internSymbol] === true
|
135
263
|
)
|
136
264
|
)
|
137
265
|
throw new Error(
|
138
266
|
`Failed to intern value because of non-interned inner value.`,
|
139
267
|
);
|
140
268
|
const key = Symbol();
|
141
|
-
(value as any)[
|
269
|
+
(value as any)[internSymbol] = true;
|
142
270
|
Object.freeze(value);
|
143
|
-
intern.
|
271
|
+
intern.pool[type].set(key, new WeakRef(value as any));
|
144
272
|
intern.finalizationRegistry.register(value, { type, key });
|
145
|
-
return value;
|
273
|
+
return value as any;
|
146
274
|
}
|
147
275
|
|
148
|
-
|
276
|
+
export const internSymbol = Symbol("intern");
|
149
277
|
|
150
|
-
intern.
|
151
|
-
tuple: new Map<Symbol, WeakRef<
|
152
|
-
record: new Map<
|
278
|
+
intern.pool = {
|
279
|
+
tuple: new Map<Symbol, WeakRef<Intern<InternInnerValue[]>>>(),
|
280
|
+
record: new Map<
|
281
|
+
Symbol,
|
282
|
+
WeakRef<Intern<{ [key: string]: InternInnerValue }>>
|
283
|
+
>(),
|
153
284
|
};
|
154
285
|
|
155
286
|
intern.finalizationRegistry = new FinalizationRegistry<{
|
156
287
|
type: "tuple" | "record";
|
157
288
|
key: Symbol;
|
158
289
|
}>(({ type, key }) => {
|
159
|
-
intern.
|
290
|
+
intern.pool[type].delete(key);
|
160
291
|
});
|
161
|
-
|
162
|
-
/*
|
163
|
-
|
164
|
-
|
165
|
-
Math.random().toString(36).slice(2)
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
https://npm.im/package/p-timeout
|
171
|
-
https://npm.im/package/delay
|
172
|
-
https://npm.im/package/sleep-promise
|
173
|
-
https://npm.im/package/promise-timeout
|
174
|
-
https://npm.im/package/sleep
|
175
|
-
https://npm.im/package/timeout-as-promise
|
176
|
-
https://npm.im/package/delayed
|
177
|
-
https://npm.im/package/sleep-async
|
178
|
-
https://npm.im/package/promise.timeout
|
179
|
-
|
180
|
-
*/
|
181
|
-
// /**
|
182
|
-
// - Remove uses of `node:timers/promises`?
|
183
|
-
// *
|
184
|
-
// * 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`).
|
185
|
-
// * - value objects
|
186
|
-
// * - https://lodash.com/docs/4.17.15#isEqual
|
187
|
-
// * TODO: Implement using setTimeout and let it be usable in client-side JavaScript as well.
|
188
|
-
// * TODO: Explain the differences between this and `setInterval()` (wait for completion before setting the next scheduler, and force a job to run)
|
189
|
-
// *
|
190
|
-
// * Start a background job that runs every given `interval`.
|
191
|
-
// *
|
192
|
-
// * You may use `backgroundJob.run()` to force the background job to run right away.
|
193
|
-
// *
|
194
|
-
// * **Note:** If a background job is running when `backgroundJob.continue()` is called.
|
195
|
-
// *
|
196
|
-
// * You may use `backgroundJob.stop()` to stop the background job.
|
197
|
-
// *
|
198
|
-
// * **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.
|
199
|
-
// *
|
200
|
-
// * **Note:** The `intervalVariance` prevents many background jobs from starting at the same and overloading the machine.
|
201
|
-
// *
|
202
|
-
// * **Example**
|
203
|
-
// *
|
204
|
-
// * ```javascript
|
205
|
-
// * import * as node from "@radically-straightforward/node";
|
206
|
-
// *
|
207
|
-
// * const backgroundJob = node.backgroundJob({ interval: 3 * 1000 }, () => {
|
208
|
-
// * console.log("backgroundJob(): Running background job...");
|
209
|
-
// * });
|
210
|
-
// * process.on("SIGTSTP", () => {
|
211
|
-
// * backgroundJob.run();
|
212
|
-
// * });
|
213
|
-
// * console.log(
|
214
|
-
// * "backgroundJob(): Press ⌃Z to force background job to run and ⌃C to continue...",
|
215
|
-
// * );
|
216
|
-
// * await node.shouldTerminate();
|
217
|
-
// * backgroundJob.stop();
|
218
|
-
// * ```
|
219
|
-
// */
|
220
|
-
// export function backgroundJob(
|
221
|
-
// {
|
222
|
-
// interval,
|
223
|
-
// intervalVariance = 0.1,
|
224
|
-
// }: { interval: number; intervalVariance?: number },
|
225
|
-
// function_: () => void | Promise<void>,
|
226
|
-
// ): { run: () => void; stop: () => void } {
|
227
|
-
// let shouldContinue = true;
|
228
|
-
// let abortController = new AbortController();
|
229
|
-
// (async () => {
|
230
|
-
// while (shouldContinue) {
|
231
|
-
// await function_();
|
232
|
-
// await timers
|
233
|
-
// .setTimeout(
|
234
|
-
// interval + interval * intervalVariance * Math.random(),
|
235
|
-
// undefined,
|
236
|
-
// { signal: abortController.signal },
|
237
|
-
// )
|
238
|
-
// .catch(() => {});
|
239
|
-
// abortController = new AbortController();
|
240
|
-
// }
|
241
|
-
// })();
|
242
|
-
// return {
|
243
|
-
// run: () => {
|
244
|
-
// abortController.abort();
|
245
|
-
// },
|
246
|
-
// stop: () => {
|
247
|
-
// shouldContinue = false;
|
248
|
-
// abortController.abort();
|
249
|
-
// },
|
250
|
-
// };
|
251
|
-
// }
|
package/source/index.test.mts
CHANGED
@@ -1,16 +1,60 @@
|
|
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
|
-
// @ts-
|
49
|
+
// @ts-expect-error
|
8
50
|
assert(([1] === [1]) === false);
|
9
51
|
assert($([1]) === $([1]));
|
52
|
+
assert($({ a: 1, b: 2 }) === $({ b: 2, a: 1 }));
|
53
|
+
|
10
54
|
assert($([1]) !== $([2]));
|
11
55
|
|
12
56
|
{
|
13
|
-
const map = new Map();
|
57
|
+
const map = new Map<number[], number>();
|
14
58
|
map.set([1], 1);
|
15
59
|
map.set([1], 2);
|
16
60
|
assert.equal(map.size, 2);
|
@@ -18,7 +62,7 @@ test("intern()", () => {
|
|
18
62
|
}
|
19
63
|
|
20
64
|
{
|
21
|
-
const map = new Map();
|
65
|
+
const map = new Map<utilities.Intern<number[]>, number>();
|
22
66
|
map.set($([1]), 1);
|
23
67
|
map.set($([1]), 2);
|
24
68
|
assert.equal(map.size, 1);
|
@@ -26,7 +70,7 @@ test("intern()", () => {
|
|
26
70
|
}
|
27
71
|
|
28
72
|
{
|
29
|
-
const set = new Set();
|
73
|
+
const set = new Set<number[]>();
|
30
74
|
set.add([1]);
|
31
75
|
set.add([1]);
|
32
76
|
assert.equal(set.size, 2);
|
@@ -34,7 +78,7 @@ test("intern()", () => {
|
|
34
78
|
}
|
35
79
|
|
36
80
|
{
|
37
|
-
const set = new Set();
|
81
|
+
const set = new Set<utilities.Intern<number[]>>();
|
38
82
|
set.add($([1]));
|
39
83
|
set.add($([1]));
|
40
84
|
assert.equal(set.size, 1);
|
@@ -42,35 +86,13 @@ test("intern()", () => {
|
|
42
86
|
}
|
43
87
|
|
44
88
|
assert.throws(() => {
|
89
|
+
// @ts-expect-error
|
45
90
|
$([1, {}]);
|
46
91
|
});
|
47
92
|
assert($([1, $({})]) === $([1, $({})]));
|
48
93
|
|
49
94
|
assert.throws(() => {
|
95
|
+
// @ts-expect-error
|
50
96
|
$([1])[0] = 2;
|
51
97
|
});
|
52
98
|
});
|
53
|
-
|
54
|
-
// test(
|
55
|
-
// "backgroundJob()",
|
56
|
-
// {
|
57
|
-
// ...(!process.stdin.isTTY
|
58
|
-
// ? {
|
59
|
-
// skip: "Run interactive test with ‘node ./build/index.test.mjs’.",
|
60
|
-
// }
|
61
|
-
// : {}),
|
62
|
-
// },
|
63
|
-
// async () => {
|
64
|
-
// const backgroundJob = node.backgroundJob({ interval: 3 * 1000 }, () => {
|
65
|
-
// console.log("backgroundJob(): Running background job...");
|
66
|
-
// });
|
67
|
-
// process.on("SIGTSTP", () => {
|
68
|
-
// backgroundJob.run();
|
69
|
-
// });
|
70
|
-
// console.log(
|
71
|
-
// "backgroundJob(): Press ⌃Z to force background job to run and ⌃C to continue...",
|
72
|
-
// );
|
73
|
-
// await node.shouldTerminate();
|
74
|
-
// backgroundJob.stop();
|
75
|
-
// },
|
76
|
-
// );
|