@radically-straightforward/utilities 1.0.1 → 1.1.1
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +8 -0
- package/README.md +14 -15
- package/build/index.d.mts +14 -15
- package/build/index.d.mts.map +1 -1
- package/build/index.mjs +41 -39
- package/build/index.mjs.map +1 -1
- package/build/index.test.mjs +21 -11
- package/build/index.test.mjs.map +1 -1
- package/package.json +5 -3
- package/source/index.mts +47 -48
- package/source/index.test.mts +24 -13
package/CHANGELOG.md
CHANGED
package/README.md
CHANGED
@@ -52,15 +52,16 @@ This is different from `setInterval()` in the following ways:
|
|
52
52
|
|
53
53
|
2. We introduce a random `intervalVariance` to avoid many background jobs from starting at the same time and overloading the machine.
|
54
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 (
|
55
|
+
3. You may use `backgroundJob.run()` to force the background job to run right away. If the background job is already running, calling `backgroundJob.run()` schedules it to run again as soon as possible (with a wait interval of 0).
|
56
56
|
|
57
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
58
|
|
59
|
+
5. In Node.js the background job is stopped on [`"gracefulTermination"`](https://github.com/radically-straightforward/radically-straightforward/tree/main/node#graceful-termination).
|
60
|
+
|
59
61
|
**Example**
|
60
62
|
|
61
63
|
```javascript
|
62
64
|
import * as utilities from "@radically-straightforward/utilities";
|
63
|
-
import * as node from "@radically-straightforward/node";
|
64
65
|
|
65
66
|
const backgroundJob = utilities.backgroundJob(
|
66
67
|
{ interval: 3 * 1000 },
|
@@ -70,14 +71,12 @@ const backgroundJob = utilities.backgroundJob(
|
|
70
71
|
console.log("backgroundJob(): ...finished running background job.");
|
71
72
|
},
|
72
73
|
);
|
73
|
-
console.log(
|
74
|
-
"backgroundJob(): Press ⌃Z to force background job to run and ⌃C to continue...",
|
75
|
-
);
|
76
74
|
process.on("SIGTSTP", () => {
|
77
75
|
backgroundJob.run();
|
78
76
|
});
|
79
|
-
|
80
|
-
backgroundJob
|
77
|
+
console.log(
|
78
|
+
"backgroundJob(): Press ⌃Z to force background job to run and ⌃C to continue...",
|
79
|
+
);
|
81
80
|
```
|
82
81
|
|
83
82
|
### `sleep()`
|
@@ -94,7 +93,7 @@ A promisified version of `setTimeout()`. Bare-bones: It doesn’t even offer a w
|
|
94
93
|
export function randomString(): string;
|
95
94
|
```
|
96
95
|
|
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://
|
96
|
+
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://www.npmjs.com/package/crypto-random-string).
|
98
97
|
|
99
98
|
### `log()`
|
100
99
|
|
@@ -136,12 +135,12 @@ Utility type for `intern()`.
|
|
136
135
|
|
137
136
|
```typescript
|
138
137
|
export function intern<
|
139
|
-
|
138
|
+
Type extends
|
140
139
|
| Array<InternInnerValue>
|
141
140
|
| {
|
142
141
|
[key: string]: InternInnerValue;
|
143
142
|
},
|
144
|
-
>(value:
|
143
|
+
>(value: Type): Intern<Type>;
|
145
144
|
```
|
146
145
|
|
147
146
|
[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:
|
@@ -207,7 +206,7 @@ A proposal to include immutable objects (Records) and immutable arrays (Tuples)
|
|
207
206
|
|
208
207
|
It includes a [polyfill](https://github.com/bloomberg/record-tuple-polyfill) which works very similarly to `intern()` but requires different functions for different data types.
|
209
208
|
|
210
|
-
**[`collections-deep-equal`](https://
|
209
|
+
**[`collections-deep-equal`](https://www.npmjs.com/package/collections-deep-equal)**
|
211
210
|
|
212
211
|
A previous solution to this problem which took a different approach: Instead of interning the values and allowing you to use JavaScript’s `Map`s and `Set`s, `collections-deep-equal` extends `Map`s and `Set`s with a different notion of equality.
|
213
212
|
|
@@ -217,21 +216,21 @@ A previous solution to this problem which took a different approach: Instead of
|
|
217
216
|
|
218
217
|
`collections-deep-equal` has different intern pools for each `Map` and `Set` instead of `intern()`’s single global intern pool, which may be advantageous because smaller pools may be faster to traverse.
|
219
218
|
|
220
|
-
**[Immutable.js](https://
|
219
|
+
**[Immutable.js](https://www.npmjs.com/package/immutable), [`collections`](https://www.npmjs.com/package/collections), [`mori`](https://www.npmjs.com/package/mori), [TypeScript Collections](https://www.npmjs.com/package/typescript-collections), [`prelude-ts`](https://www.npmjs.com/package/prelude-ts), [`collectable`](https://www.npmjs.com/package/collectable), and so forth**
|
221
220
|
|
222
221
|
Similar to `collections-deep-equal`, these libraries implement their own data structures instead of relying on JavaScript’s `Map`s and `Set`s. Some of them go a step further and add their own notions of objects and arrays, which requires you to convert your values back and forth, may not show up nicely in the JavaScript inspector, may be less ergonomic to use with TypeScript, and so forth.
|
223
222
|
|
224
223
|
The advantage of these libraries over interning is that they may be faster.
|
225
224
|
|
226
|
-
**[`immer`](https://
|
225
|
+
**[`immer`](https://www.npmjs.com/package/immer) and [`icepick`](https://www.npmjs.com/package/icepick)**
|
227
226
|
|
228
227
|
Introduce a new way to create values based on existing values.
|
229
228
|
|
230
|
-
**[`seamless-immutable`](https://
|
229
|
+
**[`seamless-immutable`](https://www.npmjs.com/package/seamless-immutable)**
|
231
230
|
|
232
231
|
Modifies existing values more profoundly than freezing.
|
233
232
|
|
234
|
-
**[`es6-array-map`](https://
|
233
|
+
**[`es6-array-map`](https://www.npmjs.com/package/es6-array-map), [`valuecollection`](https://www.npmjs.com/package/valuecollection), [`@strong-roots-capital/map-objects`](https://www.npmjs.com/package/@strong-roots-capital/map-objects), and so forth**
|
235
234
|
|
236
235
|
Similar to `collections-deep-equal` but either incomplete, or lacking type definitions, and so forth.
|
237
236
|
|
package/build/index.d.mts
CHANGED
@@ -17,15 +17,16 @@
|
|
17
17
|
*
|
18
18
|
* 2. We introduce a random `intervalVariance` to avoid many background jobs from starting at the same time and overloading the machine.
|
19
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 (
|
20
|
+
* 3. You may use `backgroundJob.run()` to force the background job to run right away. If the background job is already running, calling `backgroundJob.run()` schedules it to run again as soon as possible (with a wait interval of 0).
|
21
21
|
*
|
22
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
23
|
*
|
24
|
+
* 5. In Node.js the background job is stopped on [`"gracefulTermination"`](https://github.com/radically-straightforward/radically-straightforward/tree/main/node#graceful-termination).
|
25
|
+
*
|
24
26
|
* **Example**
|
25
27
|
*
|
26
28
|
* ```javascript
|
27
29
|
* import * as utilities from "@radically-straightforward/utilities";
|
28
|
-
* import * as node from "@radically-straightforward/node";
|
29
30
|
*
|
30
31
|
* const backgroundJob = utilities.backgroundJob(
|
31
32
|
* { interval: 3 * 1000 },
|
@@ -35,14 +36,12 @@
|
|
35
36
|
* console.log("backgroundJob(): ...finished running background job.");
|
36
37
|
* },
|
37
38
|
* );
|
38
|
-
* console.log(
|
39
|
-
* "backgroundJob(): Press ⌃Z to force background job to run and ⌃C to continue...",
|
40
|
-
* );
|
41
39
|
* process.on("SIGTSTP", () => {
|
42
40
|
* backgroundJob.run();
|
43
41
|
* });
|
44
|
-
*
|
45
|
-
*
|
42
|
+
* console.log(
|
43
|
+
* "backgroundJob(): Press ⌃Z to force background job to run and ⌃C to continue...",
|
44
|
+
* );
|
46
45
|
* ```
|
47
46
|
*/
|
48
47
|
export declare function backgroundJob({ interval, intervalVariance, }: {
|
@@ -57,7 +56,7 @@ export declare function backgroundJob({ interval, intervalVariance, }: {
|
|
57
56
|
*/
|
58
57
|
export declare function sleep(duration: number): Promise<void>;
|
59
58
|
/**
|
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://
|
59
|
+
* 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://www.npmjs.com/package/crypto-random-string).
|
61
60
|
*/
|
62
61
|
export declare function randomString(): string;
|
63
62
|
/**
|
@@ -138,7 +137,7 @@ export type InternInnerValue = string | number | bigint | boolean | symbol | und
|
|
138
137
|
*
|
139
138
|
* It includes a [polyfill](https://github.com/bloomberg/record-tuple-polyfill) which works very similarly to `intern()` but requires different functions for different data types.
|
140
139
|
*
|
141
|
-
* **[`collections-deep-equal`](https://
|
140
|
+
* **[`collections-deep-equal`](https://www.npmjs.com/package/collections-deep-equal)**
|
142
141
|
*
|
143
142
|
* A previous solution to this problem which took a different approach: Instead of interning the values and allowing you to use JavaScript’s `Map`s and `Set`s, `collections-deep-equal` extends `Map`s and `Set`s with a different notion of equality.
|
144
143
|
*
|
@@ -148,21 +147,21 @@ export type InternInnerValue = string | number | bigint | boolean | symbol | und
|
|
148
147
|
*
|
149
148
|
* `collections-deep-equal` has different intern pools for each `Map` and `Set` instead of `intern()`’s single global intern pool, which may be advantageous because smaller pools may be faster to traverse.
|
150
149
|
*
|
151
|
-
* **[Immutable.js](https://
|
150
|
+
* **[Immutable.js](https://www.npmjs.com/package/immutable), [`collections`](https://www.npmjs.com/package/collections), [`mori`](https://www.npmjs.com/package/mori), [TypeScript Collections](https://www.npmjs.com/package/typescript-collections), [`prelude-ts`](https://www.npmjs.com/package/prelude-ts), [`collectable`](https://www.npmjs.com/package/collectable), and so forth**
|
152
151
|
*
|
153
152
|
* Similar to `collections-deep-equal`, these libraries implement their own data structures instead of relying on JavaScript’s `Map`s and `Set`s. Some of them go a step further and add their own notions of objects and arrays, which requires you to convert your values back and forth, may not show up nicely in the JavaScript inspector, may be less ergonomic to use with TypeScript, and so forth.
|
154
153
|
*
|
155
154
|
* The advantage of these libraries over interning is that they may be faster.
|
156
155
|
*
|
157
|
-
* **[`immer`](https://
|
156
|
+
* **[`immer`](https://www.npmjs.com/package/immer) and [`icepick`](https://www.npmjs.com/package/icepick)**
|
158
157
|
*
|
159
158
|
* Introduce a new way to create values based on existing values.
|
160
159
|
*
|
161
|
-
* **[`seamless-immutable`](https://
|
160
|
+
* **[`seamless-immutable`](https://www.npmjs.com/package/seamless-immutable)**
|
162
161
|
*
|
163
162
|
* Modifies existing values more profoundly than freezing.
|
164
163
|
*
|
165
|
-
* **[`es6-array-map`](https://
|
164
|
+
* **[`es6-array-map`](https://www.npmjs.com/package/es6-array-map), [`valuecollection`](https://www.npmjs.com/package/valuecollection), [`@strong-roots-capital/map-objects`](https://www.npmjs.com/package/@strong-roots-capital/map-objects), and so forth**
|
166
165
|
*
|
167
166
|
* Similar to `collections-deep-equal` but either incomplete, or lacking type definitions, and so forth.
|
168
167
|
*
|
@@ -176,9 +175,9 @@ export type InternInnerValue = string | number | bigint | boolean | symbol | und
|
|
176
175
|
* - <https://twitter.com/swannodette/status/1067962983924539392>
|
177
176
|
* - <https://gist.github.com/modernserf/c000e62d40f678cf395e3f360b9b0e43>
|
178
177
|
*/
|
179
|
-
export declare function intern<
|
178
|
+
export declare function intern<Type extends Array<InternInnerValue> | {
|
180
179
|
[key: string]: InternInnerValue;
|
181
|
-
}>(value:
|
180
|
+
}>(value: Type): Intern<Type>;
|
182
181
|
export declare namespace intern {
|
183
182
|
var pool: {
|
184
183
|
tuple: Map<Symbol, WeakRef<Readonly<InternInnerValue[] & {
|
package/build/index.d.mts.map
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../source/index.mts"],"names":[],"mappings":"
|
1
|
+
{"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../source/index.mts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;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,IAAI,SAAS,KAAK,CAAC,gBAAgB,CAAC,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,gBAAgB,CAAA;CAAE,EAC1E,KAAK,EAAE,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAyC3B;yBA3Ce,MAAM;;;;;;;;;;;;;;;;AA6CtB,eAAO,MAAM,YAAY,eAAmB,CAAC"}
|
package/build/index.mjs
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
if (process !== undefined)
|
2
|
+
await import("@radically-straightforward/node");
|
1
3
|
/**
|
2
4
|
* Start a background job that runs every `interval`.
|
3
5
|
*
|
@@ -17,15 +19,16 @@
|
|
17
19
|
*
|
18
20
|
* 2. We introduce a random `intervalVariance` to avoid many background jobs from starting at the same time and overloading the machine.
|
19
21
|
*
|
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 (
|
22
|
+
* 3. You may use `backgroundJob.run()` to force the background job to run right away. If the background job is already running, calling `backgroundJob.run()` schedules it to run again as soon as possible (with a wait interval of 0).
|
21
23
|
*
|
22
24
|
* 4. You may use `backgroundJob.stop()` to stop the background job. If the background job is running, it will finish but it will not be scheduled to run again. This is similar to how an HTTP server may terminate gracefully by stopping accepting new requests but finishing responding to existing requests. After a job has been stopped, you may not `backgroundJob.run()` it again (calling `backgroundJob.run()` has no effect).
|
23
25
|
*
|
26
|
+
* 5. In Node.js the background job is stopped on [`"gracefulTermination"`](https://github.com/radically-straightforward/radically-straightforward/tree/main/node#graceful-termination).
|
27
|
+
*
|
24
28
|
* **Example**
|
25
29
|
*
|
26
30
|
* ```javascript
|
27
31
|
* import * as utilities from "@radically-straightforward/utilities";
|
28
|
-
* import * as node from "@radically-straightforward/node";
|
29
32
|
*
|
30
33
|
* const backgroundJob = utilities.backgroundJob(
|
31
34
|
* { interval: 3 * 1000 },
|
@@ -35,36 +38,32 @@
|
|
35
38
|
* console.log("backgroundJob(): ...finished running background job.");
|
36
39
|
* },
|
37
40
|
* );
|
38
|
-
* console.log(
|
39
|
-
* "backgroundJob(): Press ⌃Z to force background job to run and ⌃C to continue...",
|
40
|
-
* );
|
41
41
|
* process.on("SIGTSTP", () => {
|
42
42
|
* backgroundJob.run();
|
43
43
|
* });
|
44
|
-
*
|
45
|
-
*
|
44
|
+
* console.log(
|
45
|
+
* "backgroundJob(): Press ⌃Z to force background job to run and ⌃C to continue...",
|
46
|
+
* );
|
46
47
|
* ```
|
47
48
|
*/
|
48
49
|
export function backgroundJob({ interval, intervalVariance = 0.1, }, job) {
|
49
|
-
let state = "
|
50
|
+
let state = "sleeping";
|
50
51
|
let timeout = undefined;
|
51
|
-
|
52
|
-
|
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: () => {
|
52
|
+
const scheduler = {
|
53
|
+
run: async () => {
|
64
54
|
switch (state) {
|
65
55
|
case "sleeping":
|
66
56
|
clearTimeout(timeout);
|
67
|
-
|
57
|
+
state = "running";
|
58
|
+
await job();
|
59
|
+
if (state === "running" || state === "runningAndMarkedForRerun") {
|
60
|
+
timeout = setTimeout(() => {
|
61
|
+
scheduler.run();
|
62
|
+
}, state === "runningAndMarkedForRerun"
|
63
|
+
? 0
|
64
|
+
: interval + interval * intervalVariance * Math.random());
|
65
|
+
state = "sleeping";
|
66
|
+
}
|
68
67
|
break;
|
69
68
|
case "running":
|
70
69
|
state = "runningAndMarkedForRerun";
|
@@ -72,11 +71,16 @@ export function backgroundJob({ interval, intervalVariance = 0.1, }, job) {
|
|
72
71
|
}
|
73
72
|
},
|
74
73
|
stop: () => {
|
75
|
-
|
76
|
-
clearTimeout(timeout);
|
74
|
+
clearTimeout(timeout);
|
77
75
|
state = "stopped";
|
78
76
|
},
|
79
77
|
};
|
78
|
+
scheduler.run();
|
79
|
+
if (process !== undefined)
|
80
|
+
process.once("gracefulTermination", () => {
|
81
|
+
scheduler.stop();
|
82
|
+
});
|
83
|
+
return scheduler;
|
80
84
|
}
|
81
85
|
/**
|
82
86
|
* 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).
|
@@ -85,7 +89,7 @@ export function sleep(duration) {
|
|
85
89
|
return new Promise((resolve) => setTimeout(resolve, duration));
|
86
90
|
}
|
87
91
|
/**
|
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://
|
92
|
+
* 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://www.npmjs.com/package/crypto-random-string).
|
89
93
|
*/
|
90
94
|
export function randomString() {
|
91
95
|
return Math.random().toString(36).slice(2);
|
@@ -94,7 +98,7 @@ export function randomString() {
|
|
94
98
|
* Tab-separated logging.
|
95
99
|
*/
|
96
100
|
export function log(...messageParts) {
|
97
|
-
console.log(messageParts.join(" \t"));
|
101
|
+
console.log([new Date().toISOString(), ...messageParts].join(" \t"));
|
98
102
|
}
|
99
103
|
/**
|
100
104
|
* [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:
|
@@ -160,7 +164,7 @@ export function log(...messageParts) {
|
|
160
164
|
*
|
161
165
|
* It includes a [polyfill](https://github.com/bloomberg/record-tuple-polyfill) which works very similarly to `intern()` but requires different functions for different data types.
|
162
166
|
*
|
163
|
-
* **[`collections-deep-equal`](https://
|
167
|
+
* **[`collections-deep-equal`](https://www.npmjs.com/package/collections-deep-equal)**
|
164
168
|
*
|
165
169
|
* A previous solution to this problem which took a different approach: Instead of interning the values and allowing you to use JavaScript’s `Map`s and `Set`s, `collections-deep-equal` extends `Map`s and `Set`s with a different notion of equality.
|
166
170
|
*
|
@@ -170,21 +174,21 @@ export function log(...messageParts) {
|
|
170
174
|
*
|
171
175
|
* `collections-deep-equal` has different intern pools for each `Map` and `Set` instead of `intern()`’s single global intern pool, which may be advantageous because smaller pools may be faster to traverse.
|
172
176
|
*
|
173
|
-
* **[Immutable.js](https://
|
177
|
+
* **[Immutable.js](https://www.npmjs.com/package/immutable), [`collections`](https://www.npmjs.com/package/collections), [`mori`](https://www.npmjs.com/package/mori), [TypeScript Collections](https://www.npmjs.com/package/typescript-collections), [`prelude-ts`](https://www.npmjs.com/package/prelude-ts), [`collectable`](https://www.npmjs.com/package/collectable), and so forth**
|
174
178
|
*
|
175
179
|
* Similar to `collections-deep-equal`, these libraries implement their own data structures instead of relying on JavaScript’s `Map`s and `Set`s. Some of them go a step further and add their own notions of objects and arrays, which requires you to convert your values back and forth, may not show up nicely in the JavaScript inspector, may be less ergonomic to use with TypeScript, and so forth.
|
176
180
|
*
|
177
181
|
* The advantage of these libraries over interning is that they may be faster.
|
178
182
|
*
|
179
|
-
* **[`immer`](https://
|
183
|
+
* **[`immer`](https://www.npmjs.com/package/immer) and [`icepick`](https://www.npmjs.com/package/icepick)**
|
180
184
|
*
|
181
185
|
* Introduce a new way to create values based on existing values.
|
182
186
|
*
|
183
|
-
* **[`seamless-immutable`](https://
|
187
|
+
* **[`seamless-immutable`](https://www.npmjs.com/package/seamless-immutable)**
|
184
188
|
*
|
185
189
|
* Modifies existing values more profoundly than freezing.
|
186
190
|
*
|
187
|
-
* **[`es6-array-map`](https://
|
191
|
+
* **[`es6-array-map`](https://www.npmjs.com/package/es6-array-map), [`valuecollection`](https://www.npmjs.com/package/valuecollection), [`@strong-roots-capital/map-objects`](https://www.npmjs.com/package/@strong-roots-capital/map-objects), and so forth**
|
188
192
|
*
|
189
193
|
* Similar to `collections-deep-equal` but either incomplete, or lacking type definitions, and so forth.
|
190
194
|
*
|
@@ -216,14 +220,12 @@ export function intern(value) {
|
|
216
220
|
return internValue;
|
217
221
|
}
|
218
222
|
for (const innerValue of Object.values(value))
|
219
|
-
if (!(
|
220
|
-
"
|
221
|
-
"
|
222
|
-
"
|
223
|
-
"
|
224
|
-
|
225
|
-
"undefined",
|
226
|
-
].includes(typeof innerValue) ||
|
223
|
+
if (!(typeof innerValue === "string" ||
|
224
|
+
typeof innerValue === "number" ||
|
225
|
+
typeof innerValue === "bigint" ||
|
226
|
+
typeof innerValue === "boolean" ||
|
227
|
+
typeof innerValue === "symbol" ||
|
228
|
+
innerValue === undefined ||
|
227
229
|
innerValue === null ||
|
228
230
|
innerValue[internSymbol] === true))
|
229
231
|
throw new Error(`Failed to intern value because of non-interned inner value.`);
|
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,26 +1,21 @@
|
|
1
1
|
import test from "node:test";
|
2
2
|
import assert from "node:assert/strict";
|
3
|
-
import * as
|
4
|
-
import
|
5
|
-
import { intern as $ } from "./index.mjs";
|
3
|
+
import * as utilities from "@radically-straightforward/utilities";
|
4
|
+
import { intern as $ } from "@radically-straightforward/utilities";
|
6
5
|
test("backgroundJob()", {
|
7
|
-
|
8
|
-
?
|
9
|
-
|
10
|
-
}
|
11
|
-
: {}),
|
6
|
+
skip: process.stdin.isTTY
|
7
|
+
? false
|
8
|
+
: "Run interactive test with ‘node ./build/index.test.mjs’.",
|
12
9
|
}, async () => {
|
13
10
|
const backgroundJob = utilities.backgroundJob({ interval: 3 * 1000 }, async () => {
|
14
11
|
console.log("backgroundJob(): Running background job...");
|
15
12
|
await utilities.sleep(3 * 1000);
|
16
13
|
console.log("backgroundJob(): ...finished running background job.");
|
17
14
|
});
|
18
|
-
console.log("backgroundJob(): Press ⌃Z to force background job to run and ⌃C to continue...");
|
19
15
|
process.on("SIGTSTP", () => {
|
20
16
|
backgroundJob.run();
|
21
17
|
});
|
22
|
-
|
23
|
-
backgroundJob.stop();
|
18
|
+
console.log("backgroundJob(): Press ⌃Z to force background job to run and ⌃C to gracefully terminate...");
|
24
19
|
});
|
25
20
|
test("sleep()", async () => {
|
26
21
|
const before = Date.now();
|
@@ -78,5 +73,20 @@ test("intern()", () => {
|
|
78
73
|
// @ts-expect-error
|
79
74
|
$([1])[0] = 2;
|
80
75
|
});
|
76
|
+
{
|
77
|
+
const iterations = 1000;
|
78
|
+
console.time("intern()");
|
79
|
+
const objects = [];
|
80
|
+
for (let iteration = 0; iteration < iterations; iteration++) {
|
81
|
+
const entries = [];
|
82
|
+
for (let key = 0; key < Math.floor(Math.random() * 15); key++) {
|
83
|
+
entries.push([String(key + Math.floor(Math.random() * 15)), true]);
|
84
|
+
}
|
85
|
+
objects.push($(Object.fromEntries(entries)));
|
86
|
+
objects.push($(entries.flat()));
|
87
|
+
}
|
88
|
+
// console.log($.pool.record.size);
|
89
|
+
console.timeEnd("intern()");
|
90
|
+
}
|
81
91
|
});
|
82
92
|
//# sourceMappingURL=index.test.mjs.map
|
package/build/index.test.mjs.map
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"index.test.mjs","sourceRoot":"","sources":["../source/index.test.mts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,KAAK,
|
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": "1.
|
3
|
+
"version": "1.1.1",
|
4
4
|
"description": "🛠️ Utilities for Node.js and the browser",
|
5
5
|
"keywords": [
|
6
6
|
"node",
|
@@ -25,11 +25,13 @@
|
|
25
25
|
"types": "./build/index.d.mts",
|
26
26
|
"scripts": {
|
27
27
|
"prepare": "tsc && documentation",
|
28
|
-
"test": "npm run prepare && node --test && prettier --check \"./README.md\" --check \"./package.json\" --check \"./tsconfig.json\" --check \"./source/**/*.mts\""
|
28
|
+
"test": "npm run prepare && node --test && prettier --check \"./README.md\" --check \"./CHANGELOG.md\" --check \"./package.json\" --check \"./tsconfig.json\" --check \"./source/**/*.mts\""
|
29
|
+
},
|
30
|
+
"dependencies": {
|
31
|
+
"@radically-straightforward/node": "^3.0.0"
|
29
32
|
},
|
30
33
|
"devDependencies": {
|
31
34
|
"@radically-straightforward/documentation": "^1.0.1",
|
32
|
-
"@radically-straightforward/node": "^2.0.1",
|
33
35
|
"@radically-straightforward/tsconfig": "^1.0.0",
|
34
36
|
"@types/node": "^20.10.6",
|
35
37
|
"prettier": "^3.1.1",
|
package/source/index.mts
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
if (process !== undefined) await import("@radically-straightforward/node");
|
2
|
+
|
1
3
|
/**
|
2
4
|
* Start a background job that runs every `interval`.
|
3
5
|
*
|
@@ -17,15 +19,16 @@
|
|
17
19
|
*
|
18
20
|
* 2. We introduce a random `intervalVariance` to avoid many background jobs from starting at the same time and overloading the machine.
|
19
21
|
*
|
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 (
|
22
|
+
* 3. You may use `backgroundJob.run()` to force the background job to run right away. If the background job is already running, calling `backgroundJob.run()` schedules it to run again as soon as possible (with a wait interval of 0).
|
21
23
|
*
|
22
24
|
* 4. You may use `backgroundJob.stop()` to stop the background job. If the background job is running, it will finish but it will not be scheduled to run again. This is similar to how an HTTP server may terminate gracefully by stopping accepting new requests but finishing responding to existing requests. After a job has been stopped, you may not `backgroundJob.run()` it again (calling `backgroundJob.run()` has no effect).
|
23
25
|
*
|
26
|
+
* 5. In Node.js the background job is stopped on [`"gracefulTermination"`](https://github.com/radically-straightforward/radically-straightforward/tree/main/node#graceful-termination).
|
27
|
+
*
|
24
28
|
* **Example**
|
25
29
|
*
|
26
30
|
* ```javascript
|
27
31
|
* import * as utilities from "@radically-straightforward/utilities";
|
28
|
-
* import * as node from "@radically-straightforward/node";
|
29
32
|
*
|
30
33
|
* const backgroundJob = utilities.backgroundJob(
|
31
34
|
* { interval: 3 * 1000 },
|
@@ -35,14 +38,12 @@
|
|
35
38
|
* console.log("backgroundJob(): ...finished running background job.");
|
36
39
|
* },
|
37
40
|
* );
|
38
|
-
* console.log(
|
39
|
-
* "backgroundJob(): Press ⌃Z to force background job to run and ⌃C to continue...",
|
40
|
-
* );
|
41
41
|
* process.on("SIGTSTP", () => {
|
42
42
|
* backgroundJob.run();
|
43
43
|
* });
|
44
|
-
*
|
45
|
-
*
|
44
|
+
* console.log(
|
45
|
+
* "backgroundJob(): Press ⌃Z to force background job to run and ⌃C to continue...",
|
46
|
+
* );
|
46
47
|
* ```
|
47
48
|
*/
|
48
49
|
export function backgroundJob(
|
@@ -52,33 +53,27 @@ export function backgroundJob(
|
|
52
53
|
}: { interval: number; intervalVariance?: number },
|
53
54
|
job: () => void | Promise<void>,
|
54
55
|
): { run: () => void; stop: () => void } {
|
55
|
-
let state:
|
56
|
-
|
57
|
-
| "running"
|
58
|
-
| "runningAndMarkedForRerun"
|
59
|
-
| "sleeping"
|
60
|
-
| "stopped" = "initial";
|
56
|
+
let state: "sleeping" | "running" | "runningAndMarkedForRerun" | "stopped" =
|
57
|
+
"sleeping";
|
61
58
|
let timeout: any = undefined;
|
62
|
-
|
63
|
-
|
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: () => {
|
59
|
+
const scheduler = {
|
60
|
+
run: async () => {
|
78
61
|
switch (state) {
|
79
62
|
case "sleeping":
|
80
63
|
clearTimeout(timeout);
|
81
|
-
|
64
|
+
state = "running";
|
65
|
+
await job();
|
66
|
+
if (state === "running" || state === "runningAndMarkedForRerun") {
|
67
|
+
timeout = setTimeout(
|
68
|
+
() => {
|
69
|
+
scheduler.run();
|
70
|
+
},
|
71
|
+
(state as any) === "runningAndMarkedForRerun"
|
72
|
+
? 0
|
73
|
+
: interval + interval * intervalVariance * Math.random(),
|
74
|
+
);
|
75
|
+
state = "sleeping";
|
76
|
+
}
|
82
77
|
break;
|
83
78
|
case "running":
|
84
79
|
state = "runningAndMarkedForRerun";
|
@@ -86,10 +81,16 @@ export function backgroundJob(
|
|
86
81
|
}
|
87
82
|
},
|
88
83
|
stop: () => {
|
89
|
-
|
84
|
+
clearTimeout(timeout);
|
90
85
|
state = "stopped";
|
91
86
|
},
|
92
87
|
};
|
88
|
+
scheduler.run();
|
89
|
+
if (process !== undefined)
|
90
|
+
process.once("gracefulTermination", () => {
|
91
|
+
scheduler.stop();
|
92
|
+
});
|
93
|
+
return scheduler;
|
93
94
|
}
|
94
95
|
|
95
96
|
/**
|
@@ -100,7 +101,7 @@ export function sleep(duration: number): Promise<void> {
|
|
100
101
|
}
|
101
102
|
|
102
103
|
/**
|
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://
|
104
|
+
* 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://www.npmjs.com/package/crypto-random-string).
|
104
105
|
*/
|
105
106
|
export function randomString(): string {
|
106
107
|
return Math.random().toString(36).slice(2);
|
@@ -110,7 +111,7 @@ export function randomString(): string {
|
|
110
111
|
* Tab-separated logging.
|
111
112
|
*/
|
112
113
|
export function log(...messageParts: string[]): void {
|
113
|
-
console.log(messageParts.join(" \t"));
|
114
|
+
console.log([new Date().toISOString(), ...messageParts].join(" \t"));
|
114
115
|
}
|
115
116
|
|
116
117
|
/**
|
@@ -195,7 +196,7 @@ export type InternInnerValue =
|
|
195
196
|
*
|
196
197
|
* It includes a [polyfill](https://github.com/bloomberg/record-tuple-polyfill) which works very similarly to `intern()` but requires different functions for different data types.
|
197
198
|
*
|
198
|
-
* **[`collections-deep-equal`](https://
|
199
|
+
* **[`collections-deep-equal`](https://www.npmjs.com/package/collections-deep-equal)**
|
199
200
|
*
|
200
201
|
* A previous solution to this problem which took a different approach: Instead of interning the values and allowing you to use JavaScript’s `Map`s and `Set`s, `collections-deep-equal` extends `Map`s and `Set`s with a different notion of equality.
|
201
202
|
*
|
@@ -205,21 +206,21 @@ export type InternInnerValue =
|
|
205
206
|
*
|
206
207
|
* `collections-deep-equal` has different intern pools for each `Map` and `Set` instead of `intern()`’s single global intern pool, which may be advantageous because smaller pools may be faster to traverse.
|
207
208
|
*
|
208
|
-
* **[Immutable.js](https://
|
209
|
+
* **[Immutable.js](https://www.npmjs.com/package/immutable), [`collections`](https://www.npmjs.com/package/collections), [`mori`](https://www.npmjs.com/package/mori), [TypeScript Collections](https://www.npmjs.com/package/typescript-collections), [`prelude-ts`](https://www.npmjs.com/package/prelude-ts), [`collectable`](https://www.npmjs.com/package/collectable), and so forth**
|
209
210
|
*
|
210
211
|
* Similar to `collections-deep-equal`, these libraries implement their own data structures instead of relying on JavaScript’s `Map`s and `Set`s. Some of them go a step further and add their own notions of objects and arrays, which requires you to convert your values back and forth, may not show up nicely in the JavaScript inspector, may be less ergonomic to use with TypeScript, and so forth.
|
211
212
|
*
|
212
213
|
* The advantage of these libraries over interning is that they may be faster.
|
213
214
|
*
|
214
|
-
* **[`immer`](https://
|
215
|
+
* **[`immer`](https://www.npmjs.com/package/immer) and [`icepick`](https://www.npmjs.com/package/icepick)**
|
215
216
|
*
|
216
217
|
* Introduce a new way to create values based on existing values.
|
217
218
|
*
|
218
|
-
* **[`seamless-immutable`](https://
|
219
|
+
* **[`seamless-immutable`](https://www.npmjs.com/package/seamless-immutable)**
|
219
220
|
*
|
220
221
|
* Modifies existing values more profoundly than freezing.
|
221
222
|
*
|
222
|
-
* **[`es6-array-map`](https://
|
223
|
+
* **[`es6-array-map`](https://www.npmjs.com/package/es6-array-map), [`valuecollection`](https://www.npmjs.com/package/valuecollection), [`@strong-roots-capital/map-objects`](https://www.npmjs.com/package/@strong-roots-capital/map-objects), and so forth**
|
223
224
|
*
|
224
225
|
* Similar to `collections-deep-equal` but either incomplete, or lacking type definitions, and so forth.
|
225
226
|
*
|
@@ -234,8 +235,8 @@ export type InternInnerValue =
|
|
234
235
|
* - <https://gist.github.com/modernserf/c000e62d40f678cf395e3f360b9b0e43>
|
235
236
|
*/
|
236
237
|
export function intern<
|
237
|
-
|
238
|
-
>(value:
|
238
|
+
Type extends Array<InternInnerValue> | { [key: string]: InternInnerValue },
|
239
|
+
>(value: Type): Intern<Type> {
|
239
240
|
const type = Array.isArray(value)
|
240
241
|
? "tuple"
|
241
242
|
: typeof value === "object" && value !== null
|
@@ -257,14 +258,12 @@ export function intern<
|
|
257
258
|
for (const innerValue of Object.values(value))
|
258
259
|
if (
|
259
260
|
!(
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
"undefined",
|
267
|
-
].includes(typeof innerValue) ||
|
261
|
+
typeof innerValue === "string" ||
|
262
|
+
typeof innerValue === "number" ||
|
263
|
+
typeof innerValue === "bigint" ||
|
264
|
+
typeof innerValue === "boolean" ||
|
265
|
+
typeof innerValue === "symbol" ||
|
266
|
+
innerValue === undefined ||
|
268
267
|
innerValue === null ||
|
269
268
|
(innerValue as any)[internSymbol] === true
|
270
269
|
)
|
package/source/index.test.mts
CHANGED
@@ -1,17 +1,14 @@
|
|
1
1
|
import test from "node:test";
|
2
2
|
import assert from "node:assert/strict";
|
3
|
-
import * as
|
4
|
-
import
|
5
|
-
import { intern as $ } from "./index.mjs";
|
3
|
+
import * as utilities from "@radically-straightforward/utilities";
|
4
|
+
import { intern as $ } from "@radically-straightforward/utilities";
|
6
5
|
|
7
6
|
test(
|
8
7
|
"backgroundJob()",
|
9
8
|
{
|
10
|
-
|
11
|
-
?
|
12
|
-
|
13
|
-
}
|
14
|
-
: {}),
|
9
|
+
skip: process.stdin.isTTY
|
10
|
+
? false
|
11
|
+
: "Run interactive test with ‘node ./build/index.test.mjs’.",
|
15
12
|
},
|
16
13
|
async () => {
|
17
14
|
const backgroundJob = utilities.backgroundJob(
|
@@ -22,14 +19,12 @@ test(
|
|
22
19
|
console.log("backgroundJob(): ...finished running background job.");
|
23
20
|
},
|
24
21
|
);
|
25
|
-
console.log(
|
26
|
-
"backgroundJob(): Press ⌃Z to force background job to run and ⌃C to continue...",
|
27
|
-
);
|
28
22
|
process.on("SIGTSTP", () => {
|
29
23
|
backgroundJob.run();
|
30
24
|
});
|
31
|
-
|
32
|
-
|
25
|
+
console.log(
|
26
|
+
"backgroundJob(): Press ⌃Z to force background job to run and ⌃C to gracefully terminate...",
|
27
|
+
);
|
33
28
|
},
|
34
29
|
);
|
35
30
|
|
@@ -99,4 +94,20 @@ test("intern()", () => {
|
|
99
94
|
// @ts-expect-error
|
100
95
|
$([1])[0] = 2;
|
101
96
|
});
|
97
|
+
|
98
|
+
{
|
99
|
+
const iterations = 1000;
|
100
|
+
console.time("intern()");
|
101
|
+
const objects = [];
|
102
|
+
for (let iteration = 0; iteration < iterations; iteration++) {
|
103
|
+
const entries = [];
|
104
|
+
for (let key = 0; key < Math.floor(Math.random() * 15); key++) {
|
105
|
+
entries.push([String(key + Math.floor(Math.random() * 15)), true]);
|
106
|
+
}
|
107
|
+
objects.push($(Object.fromEntries(entries)));
|
108
|
+
objects.push($(entries.flat()));
|
109
|
+
}
|
110
|
+
// console.log($.pool.record.size);
|
111
|
+
console.timeEnd("intern()");
|
112
|
+
}
|
102
113
|
});
|