@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 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 node from "@radically-straightforward/utilities";
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<unknown>
132
+ | Array<InternInnerValue>
25
133
  | {
26
- [key: string]: unknown;
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
- ```javascript
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:** The pool of interned values is available as `intern.pool`. There’s a `FinalizationRegistry` at `intern.finalizationRegistry` that cleans up interned values that have been garbage collected.
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
- * [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:
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:** The pool of interned values is available as `intern.pool`. There’s a `FinalizationRegistry` at `intern.finalizationRegistry` that cleans up interned values that have been garbage collected.
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<unknown> | {
102
- [key: string]: unknown;
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 interned: symbol;
106
- var pools: {
107
- tuple: Map<Symbol, WeakRef<any>>;
108
- record: Map<Symbol, WeakRef<any>>;
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
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../source/index.mts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmGG;AACH,wBAAgB,MAAM,CAAC,CAAC,SAAS,KAAK,CAAC,OAAO,CAAC,GAAG;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAA;CAAE,EAC1E,KAAK,EAAE,CAAC,GACP,CAAC,CA2CH;yBA7Ce,MAAM"}
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
- * [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:
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:** The pool of interned values is available as `intern.pool`. There’s a `FinalizationRegistry` at `intern.finalizationRegistry` that cleans up interned values that have been garbage collected.
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.pools[type].values()) {
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[intern.interned] === true))
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[intern.interned] = true;
225
+ value[internSymbol] = true;
132
226
  Object.freeze(value);
133
- intern.pools[type].set(key, new WeakRef(value));
227
+ intern.pool[type].set(key, new WeakRef(value));
134
228
  intern.finalizationRegistry.register(value, { type, key });
135
229
  return value;
136
230
  }
137
- intern.interned = Symbol("interned");
138
- intern.pools = {
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.pools[type].delete(key);
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
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sourceRoot":"","sources":["../source/index.mts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","sourceRoot":"","sources":["../source/index.mts"],"names":[],"mappings":"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"}
@@ -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-ignore
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
@@ -1 +1 @@
1
- {"version":3,"file":"index.test.mjs","sourceRoot":"","sources":["../source/index.test.mts"],"names":[],"mappings":"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"}
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.2",
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
- * [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:
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:** The pool of interned values is available as `intern.pool`. There’s a `FinalizationRegistry` at `intern.finalizationRegistry` that cleans up interned values that have been garbage collected.
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<T extends Array<unknown> | { [key: string]: unknown }>(
102
- value: T,
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.pools[type].values()) {
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)[intern.interned] === true
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)[intern.interned] = true;
269
+ (value as any)[internSymbol] = true;
142
270
  Object.freeze(value);
143
- intern.pools[type].set(key, new WeakRef(value));
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
- intern.interned = Symbol("interned");
276
+ export const internSymbol = Symbol("intern");
149
277
 
150
- intern.pools = {
151
- tuple: new Map<Symbol, WeakRef<any>>(),
152
- record: new Map<Symbol, WeakRef<any>>(),
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.pools[type].delete(key);
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
- // }
@@ -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-ignore
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
- // );