@radically-straightforward/utilities 1.0.0 → 1.1.0
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 +23 -16
- package/build/index.d.mts +19 -16
- package/build/index.d.mts.map +1 -1
- package/build/index.mjs +47 -39
- package/build/index.mjs.map +1 -1
- package/build/index.test.mjs +24 -11
- package/build/index.test.mjs.map +1 -1
- package/package.json +5 -3
- package/source/index.mts +54 -48
- package/source/index.test.mts +28 -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()`
|
@@ -86,7 +85,7 @@ backgroundJob.stop();
|
|
86
85
|
export function sleep(duration: number): Promise<void>;
|
87
86
|
```
|
88
87
|
|
89
|
-
A promisified version of `setTimeout()`. It doesn’t offer a way to `clearTimeout()`. Useful in the browser—
|
88
|
+
A promisified version of `setTimeout()`. Bare-bones: It doesn’t even offer a way to `clearTimeout()`. Useful in JavaScript that may run in the browser—if you’re only targeting Node.js then you’re better served by [`timersPromises.setTimeout()`](https://nodejs.org/dist/latest-v21.x/docs/api/timers.html#timerspromisessettimeoutdelay-value-options).
|
90
89
|
|
91
90
|
### `randomString()`
|
92
91
|
|
@@ -94,7 +93,15 @@ A promisified version of `setTimeout()`. It doesn’t offer a way to `clearTimeo
|
|
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).
|
97
|
+
|
98
|
+
### `log()`
|
99
|
+
|
100
|
+
```typescript
|
101
|
+
export function log(...messageParts: string[]): void;
|
102
|
+
```
|
103
|
+
|
104
|
+
Tab-separated logging.
|
98
105
|
|
99
106
|
### `Intern`
|
100
107
|
|
@@ -128,12 +135,12 @@ Utility type for `intern()`.
|
|
128
135
|
|
129
136
|
```typescript
|
130
137
|
export function intern<
|
131
|
-
|
138
|
+
Type extends
|
132
139
|
| Array<InternInnerValue>
|
133
140
|
| {
|
134
141
|
[key: string]: InternInnerValue;
|
135
142
|
},
|
136
|
-
>(value:
|
143
|
+
>(value: Type): Intern<Type>;
|
137
144
|
```
|
138
145
|
|
139
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:
|
@@ -199,7 +206,7 @@ A proposal to include immutable objects (Records) and immutable arrays (Tuples)
|
|
199
206
|
|
200
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.
|
201
208
|
|
202
|
-
**[`collections-deep-equal`](https://
|
209
|
+
**[`collections-deep-equal`](https://www.npmjs.com/package/collections-deep-equal)**
|
203
210
|
|
204
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.
|
205
212
|
|
@@ -209,21 +216,21 @@ A previous solution to this problem which took a different approach: Instead of
|
|
209
216
|
|
210
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.
|
211
218
|
|
212
|
-
**[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**
|
213
220
|
|
214
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.
|
215
222
|
|
216
223
|
The advantage of these libraries over interning is that they may be faster.
|
217
224
|
|
218
|
-
**[`immer`](https://
|
225
|
+
**[`immer`](https://www.npmjs.com/package/immer) and [`icepick`](https://www.npmjs.com/package/icepick)**
|
219
226
|
|
220
227
|
Introduce a new way to create values based on existing values.
|
221
228
|
|
222
|
-
**[`seamless-immutable`](https://
|
229
|
+
**[`seamless-immutable`](https://www.npmjs.com/package/seamless-immutable)**
|
223
230
|
|
224
231
|
Modifies existing values more profoundly than freezing.
|
225
232
|
|
226
|
-
**[`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**
|
227
234
|
|
228
235
|
Similar to `collections-deep-equal` but either incomplete, or lacking type definitions, and so forth.
|
229
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, }: {
|
@@ -53,13 +52,17 @@ export declare function backgroundJob({ interval, intervalVariance, }: {
|
|
53
52
|
stop: () => void;
|
54
53
|
};
|
55
54
|
/**
|
56
|
-
* A promisified version of `setTimeout()`. It doesn’t offer a way to `clearTimeout()`. Useful in the browser—
|
55
|
+
* A promisified version of `setTimeout()`. Bare-bones: It doesn’t even offer a way to `clearTimeout()`. Useful in JavaScript that may run in the browser—if you’re only targeting Node.js then you’re better served by [`timersPromises.setTimeout()`](https://nodejs.org/dist/latest-v21.x/docs/api/timers.html#timerspromisessettimeoutdelay-value-options).
|
57
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;
|
62
|
+
/**
|
63
|
+
* Tab-separated logging.
|
64
|
+
*/
|
65
|
+
export declare function log(...messageParts: string[]): void;
|
63
66
|
/**
|
64
67
|
* Utility type for `intern()`.
|
65
68
|
*/
|
@@ -134,7 +137,7 @@ export type InternInnerValue = string | number | bigint | boolean | symbol | und
|
|
134
137
|
*
|
135
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.
|
136
139
|
*
|
137
|
-
* **[`collections-deep-equal`](https://
|
140
|
+
* **[`collections-deep-equal`](https://www.npmjs.com/package/collections-deep-equal)**
|
138
141
|
*
|
139
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.
|
140
143
|
*
|
@@ -144,21 +147,21 @@ export type InternInnerValue = string | number | bigint | boolean | symbol | und
|
|
144
147
|
*
|
145
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.
|
146
149
|
*
|
147
|
-
* **[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**
|
148
151
|
*
|
149
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.
|
150
153
|
*
|
151
154
|
* The advantage of these libraries over interning is that they may be faster.
|
152
155
|
*
|
153
|
-
* **[`immer`](https://
|
156
|
+
* **[`immer`](https://www.npmjs.com/package/immer) and [`icepick`](https://www.npmjs.com/package/icepick)**
|
154
157
|
*
|
155
158
|
* Introduce a new way to create values based on existing values.
|
156
159
|
*
|
157
|
-
* **[`seamless-immutable`](https://
|
160
|
+
* **[`seamless-immutable`](https://www.npmjs.com/package/seamless-immutable)**
|
158
161
|
*
|
159
162
|
* Modifies existing values more profoundly than freezing.
|
160
163
|
*
|
161
|
-
* **[`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**
|
162
165
|
*
|
163
166
|
* Similar to `collections-deep-equal` but either incomplete, or lacking type definitions, and so forth.
|
164
167
|
*
|
@@ -172,9 +175,9 @@ export type InternInnerValue = string | number | bigint | boolean | symbol | und
|
|
172
175
|
* - <https://twitter.com/swannodette/status/1067962983924539392>
|
173
176
|
* - <https://gist.github.com/modernserf/c000e62d40f678cf395e3f360b9b0e43>
|
174
177
|
*/
|
175
|
-
export declare function intern<
|
178
|
+
export declare function intern<Type extends Array<InternInnerValue> | {
|
176
179
|
[key: string]: InternInnerValue;
|
177
|
-
}>(value:
|
180
|
+
}>(value: Type): Intern<Type>;
|
178
181
|
export declare namespace intern {
|
179
182
|
var pool: {
|
180
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,24 +71,35 @@ 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
|
-
* A promisified version of `setTimeout()`. It doesn’t offer a way to `clearTimeout()`. Useful in the browser—
|
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).
|
83
87
|
*/
|
84
88
|
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);
|
92
96
|
}
|
97
|
+
/**
|
98
|
+
* Tab-separated logging.
|
99
|
+
*/
|
100
|
+
export function log(...messageParts) {
|
101
|
+
console.log(messageParts.join(" \t"));
|
102
|
+
}
|
93
103
|
/**
|
94
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:
|
95
105
|
*
|
@@ -154,7 +164,7 @@ export function randomString() {
|
|
154
164
|
*
|
155
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.
|
156
166
|
*
|
157
|
-
* **[`collections-deep-equal`](https://
|
167
|
+
* **[`collections-deep-equal`](https://www.npmjs.com/package/collections-deep-equal)**
|
158
168
|
*
|
159
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.
|
160
170
|
*
|
@@ -164,21 +174,21 @@ export function randomString() {
|
|
164
174
|
*
|
165
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.
|
166
176
|
*
|
167
|
-
* **[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**
|
168
178
|
*
|
169
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.
|
170
180
|
*
|
171
181
|
* The advantage of these libraries over interning is that they may be faster.
|
172
182
|
*
|
173
|
-
* **[`immer`](https://
|
183
|
+
* **[`immer`](https://www.npmjs.com/package/immer) and [`icepick`](https://www.npmjs.com/package/icepick)**
|
174
184
|
*
|
175
185
|
* Introduce a new way to create values based on existing values.
|
176
186
|
*
|
177
|
-
* **[`seamless-immutable`](https://
|
187
|
+
* **[`seamless-immutable`](https://www.npmjs.com/package/seamless-immutable)**
|
178
188
|
*
|
179
189
|
* Modifies existing values more profoundly than freezing.
|
180
190
|
*
|
181
|
-
* **[`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**
|
182
192
|
*
|
183
193
|
* Similar to `collections-deep-equal` but either incomplete, or lacking type definitions, and so forth.
|
184
194
|
*
|
@@ -210,14 +220,12 @@ export function intern(value) {
|
|
210
220
|
return internValue;
|
211
221
|
}
|
212
222
|
for (const innerValue of Object.values(value))
|
213
|
-
if (!(
|
214
|
-
"
|
215
|
-
"
|
216
|
-
"
|
217
|
-
"
|
218
|
-
|
219
|
-
"undefined",
|
220
|
-
].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 ||
|
221
229
|
innerValue === null ||
|
222
230
|
innerValue[internSymbol] === true))
|
223
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();
|
@@ -32,6 +27,9 @@ test("randomString()", () => {
|
|
32
27
|
assert(10 <= randomString.length && randomString.length <= 11);
|
33
28
|
assert.match(randomString, /^[0-9a-z]+$/);
|
34
29
|
});
|
30
|
+
test("randomString()", () => {
|
31
|
+
utilities.log("EXAMPLE", "OF", "TAB-SEPARATED LOGGING");
|
32
|
+
});
|
35
33
|
test("intern()", () => {
|
36
34
|
// @ts-expect-error
|
37
35
|
assert(([1] === [1]) === false);
|
@@ -75,5 +73,20 @@ test("intern()", () => {
|
|
75
73
|
// @ts-expect-error
|
76
74
|
$([1])[0] = 2;
|
77
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
|
+
}
|
78
91
|
});
|
79
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.0",
|
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,26 +81,39 @@ 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
|
/**
|
96
|
-
* A promisified version of `setTimeout()`. It doesn’t offer a way to `clearTimeout()`. Useful in the browser—
|
97
|
+
* A promisified version of `setTimeout()`. Bare-bones: It doesn’t even offer a way to `clearTimeout()`. Useful in JavaScript that may run in the browser—if you’re only targeting Node.js then you’re better served by [`timersPromises.setTimeout()`](https://nodejs.org/dist/latest-v21.x/docs/api/timers.html#timerspromisessettimeoutdelay-value-options).
|
97
98
|
*/
|
98
99
|
export function sleep(duration: number): Promise<void> {
|
99
100
|
return new Promise((resolve) => setTimeout(resolve, duration));
|
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);
|
107
108
|
}
|
108
109
|
|
110
|
+
/**
|
111
|
+
* Tab-separated logging.
|
112
|
+
*/
|
113
|
+
export function log(...messageParts: string[]): void {
|
114
|
+
console.log(messageParts.join(" \t"));
|
115
|
+
}
|
116
|
+
|
109
117
|
/**
|
110
118
|
* Utility type for `intern()`.
|
111
119
|
*/
|
@@ -188,7 +196,7 @@ export type InternInnerValue =
|
|
188
196
|
*
|
189
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.
|
190
198
|
*
|
191
|
-
* **[`collections-deep-equal`](https://
|
199
|
+
* **[`collections-deep-equal`](https://www.npmjs.com/package/collections-deep-equal)**
|
192
200
|
*
|
193
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.
|
194
202
|
*
|
@@ -198,21 +206,21 @@ export type InternInnerValue =
|
|
198
206
|
*
|
199
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.
|
200
208
|
*
|
201
|
-
* **[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**
|
202
210
|
*
|
203
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.
|
204
212
|
*
|
205
213
|
* The advantage of these libraries over interning is that they may be faster.
|
206
214
|
*
|
207
|
-
* **[`immer`](https://
|
215
|
+
* **[`immer`](https://www.npmjs.com/package/immer) and [`icepick`](https://www.npmjs.com/package/icepick)**
|
208
216
|
*
|
209
217
|
* Introduce a new way to create values based on existing values.
|
210
218
|
*
|
211
|
-
* **[`seamless-immutable`](https://
|
219
|
+
* **[`seamless-immutable`](https://www.npmjs.com/package/seamless-immutable)**
|
212
220
|
*
|
213
221
|
* Modifies existing values more profoundly than freezing.
|
214
222
|
*
|
215
|
-
* **[`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**
|
216
224
|
*
|
217
225
|
* Similar to `collections-deep-equal` but either incomplete, or lacking type definitions, and so forth.
|
218
226
|
*
|
@@ -227,8 +235,8 @@ export type InternInnerValue =
|
|
227
235
|
* - <https://gist.github.com/modernserf/c000e62d40f678cf395e3f360b9b0e43>
|
228
236
|
*/
|
229
237
|
export function intern<
|
230
|
-
|
231
|
-
>(value:
|
238
|
+
Type extends Array<InternInnerValue> | { [key: string]: InternInnerValue },
|
239
|
+
>(value: Type): Intern<Type> {
|
232
240
|
const type = Array.isArray(value)
|
233
241
|
? "tuple"
|
234
242
|
: typeof value === "object" && value !== null
|
@@ -250,14 +258,12 @@ export function intern<
|
|
250
258
|
for (const innerValue of Object.values(value))
|
251
259
|
if (
|
252
260
|
!(
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
"undefined",
|
260
|
-
].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 ||
|
261
267
|
innerValue === null ||
|
262
268
|
(innerValue as any)[internSymbol] === true
|
263
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
|
|
@@ -45,6 +40,10 @@ test("randomString()", () => {
|
|
45
40
|
assert.match(randomString, /^[0-9a-z]+$/);
|
46
41
|
});
|
47
42
|
|
43
|
+
test("randomString()", () => {
|
44
|
+
utilities.log("EXAMPLE", "OF", "TAB-SEPARATED LOGGING");
|
45
|
+
});
|
46
|
+
|
48
47
|
test("intern()", () => {
|
49
48
|
// @ts-expect-error
|
50
49
|
assert(([1] === [1]) === false);
|
@@ -95,4 +94,20 @@ test("intern()", () => {
|
|
95
94
|
// @ts-expect-error
|
96
95
|
$([1])[0] = 2;
|
97
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
|
+
}
|
98
113
|
});
|