@radically-straightforward/utilities 1.2.1 → 2.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,18 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.0.0 · 2024-04-09
4
+
5
+ - **Breaking Change:** Made `@radically-straightforward/utilities` truly independent of platform by extracting the Node.js especial treatment into `@radically-straightforward/node`.
6
+
7
+ ## 1.2.2 · 2024-04-01
8
+
9
+ - Added:
10
+ - `capitalize()`
11
+ - `isDate()`
12
+ - `emailRegExp`
13
+ - `ISODateRegExp`
14
+ - `localizedDateRegExp`
15
+
3
16
  ## 1.2.1 · 2024-03-12
4
17
 
5
18
  - Changed `backgroundJob()` so that in Node.js it doesn’t leak `process.once.("gracefulTermination")` event listeners to terminate gracefully.
package/README.md CHANGED
@@ -16,76 +16,13 @@ import * as utilities from "@radically-straightforward/utilities";
16
16
 
17
17
  <!-- DOCUMENTATION START: ./source/index.mts -->
18
18
 
19
- ### `backgroundJob()`
20
-
21
- ```typescript
22
- export function backgroundJob(
23
- {
24
- interval,
25
- intervalVariance = 0.1,
26
- }: {
27
- interval: number;
28
- intervalVariance?: number;
29
- },
30
- job: () => void | Promise<void>,
31
- ): {
32
- run: () => void;
33
- stop: () => void;
34
- };
35
- ```
36
-
37
- Start a background job that runs every `interval`.
38
-
39
- This is different from `setInterval()` in the following ways:
40
-
41
- 1. The interval counts **between** jobs, so slow background jobs don’t get called concurrently:
42
-
43
- ```
44
- setInterval()
45
- | SLOW BACKGROUND JOB |
46
- | INTERVAL | SLOW BACKGROUND JOB |
47
- | INTERVAL | ...
48
-
49
- backgroundJob()
50
- | SLOW BACKGROUND JOB | INTERVAL | SLOW BACKGROUND JOB | INTERVAL | ...
51
- ```
52
-
53
- 2. We introduce a random `intervalVariance` to avoid many background jobs from starting at the same time and overloading the machine.
54
-
55
- 3. You may use `backgroundJob.run()` to force the background job to run right away. If the background job is already running, calling `backgroundJob.run()` schedules it to run again as soon as possible (with a wait interval of 0).
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
- 5. In Node.js the background job is stopped on [`"gracefulTermination"`](https://github.com/radically-straightforward/radically-straightforward/tree/main/node#graceful-termination).
60
-
61
- **Example**
62
-
63
- ```javascript
64
- import * as utilities from "@radically-straightforward/utilities";
65
-
66
- const backgroundJob = utilities.backgroundJob(
67
- { interval: 3 * 1000 },
68
- async () => {
69
- console.log("backgroundJob(): Running background job...");
70
- await utilities.sleep(3 * 1000);
71
- console.log("backgroundJob(): ...finished running background job.");
72
- },
73
- );
74
- process.on("SIGTSTP", () => {
75
- backgroundJob.run();
76
- });
77
- console.log(
78
- "backgroundJob(): Press ⌃Z to force background job to run and ⌃C to continue...",
79
- );
80
- ```
81
-
82
19
  ### `sleep()`
83
20
 
84
21
  ```typescript
85
22
  export function sleep(duration: number): Promise<void>;
86
23
  ```
87
24
 
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).
25
+ 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/api/timers.html#timerspromisessettimeoutdelay-value-options).
89
26
 
90
27
  ### `randomString()`
91
28
 
@@ -126,6 +63,38 @@ const reader = new Blob([
126
63
  (await reader.read()).value; // => undefined
127
64
  ```
128
65
 
66
+ ### `capitalize()`
67
+
68
+ ```typescript
69
+ export function capitalize(string: string): string;
70
+ ```
71
+
72
+ Capitalizes the first letter of a string. It’s different from [Lodash’s `capitalize()`](https://lodash.com/docs/4.17.15#capitalize) in that it doesn’t lowercase the rest of the string.
73
+
74
+ ### `isDate()`
75
+
76
+ ```typescript
77
+ export function isDate(string: string): boolean;
78
+ ```
79
+
80
+ Determine whether the given `string` is a valid `Date`, that is, it’s in ISO format and corresponds to an existing date, for example, it is **not** April 32nd.
81
+
82
+ ### `emailRegExp`
83
+
84
+ ```typescript
85
+ export const emailRegExp: RegExp;
86
+ ```
87
+
88
+ A regular expression that matches valid email addresses. This regular expression is more restrictive than the RFC—it doesn’t match some email addresses that technically are valid, for example, `example@localhost`. But it strikes a good tradeoff for practical purposes, for example, signing up in a web application.
89
+
90
+ ### `ISODateRegExp`
91
+
92
+ ```typescript
93
+ export const ISODateRegExp: RegExp;
94
+ ```
95
+
96
+ A regular expression that matches ISO dates, for example, `2024-04-01T14:19:48.162Z`.
97
+
129
98
  ### `Intern`
130
99
 
131
100
  ```typescript
@@ -267,4 +236,46 @@ Similar to `collections-deep-equal` but either incomplete, or lacking type defin
267
236
  - <https://twitter.com/swannodette/status/1067962983924539392>
268
237
  - <https://gist.github.com/modernserf/c000e62d40f678cf395e3f360b9b0e43>
269
238
 
239
+ ### `backgroundJob()`
240
+
241
+ ```typescript
242
+ export function backgroundJob(
243
+ {
244
+ interval,
245
+ onStop = () => {},
246
+ }: {
247
+ interval: number;
248
+ onStop?: () => void | Promise<void>;
249
+ },
250
+ job: () => void | Promise<void>,
251
+ ): {
252
+ run: () => Promise<void>;
253
+ stop: () => Promise<void>;
254
+ };
255
+ ```
256
+
257
+ > **Note:** This is a lower level utility. See `@radically-straightforward/node`’s and `@radically-straightforward/javascript`’s extensions to `backgroundJob()` that are better suited for their specific environments.
258
+
259
+ Start a background job that runs every `interval`.
260
+
261
+ `backgroundJob()` is different from `setInterval()` in the following ways:
262
+
263
+ 1. The interval counts **between** jobs, so slow background jobs don’t get called concurrently:
264
+
265
+ ```
266
+ setInterval()
267
+ | SLOW BACKGROUND JOB |
268
+ | INTERVAL | SLOW BACKGROUND JOB |
269
+ | INTERVAL | ...
270
+
271
+ backgroundJob()
272
+ | SLOW BACKGROUND JOB | INTERVAL | SLOW BACKGROUND JOB | INTERVAL | ...
273
+ ```
274
+
275
+ 2. 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`).
276
+
277
+ 3. You may use `backgroundJob.stop()` to stop the background job. If the background job is in the middle of 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).
278
+
279
+ 4. We introduce a random interval variance of 10% on top of the given `interval` to avoid many background jobs from starting at the same time and overloading the machine.
280
+
270
281
  <!-- DOCUMENTATION END: ./source/index.mts -->
package/build/index.d.mts CHANGED
@@ -1,58 +1,5 @@
1
1
  /**
2
- * Start a background job that runs every `interval`.
3
- *
4
- * This is different from `setInterval()` in the following ways:
5
- *
6
- * 1. The interval counts **between** jobs, so slow background jobs don’t get called concurrently:
7
- *
8
- * ```
9
- * setInterval()
10
- * | SLOW BACKGROUND JOB |
11
- * | INTERVAL | SLOW BACKGROUND JOB |
12
- * | INTERVAL | ...
13
- *
14
- * backgroundJob()
15
- * | SLOW BACKGROUND JOB | INTERVAL | SLOW BACKGROUND JOB | INTERVAL | ...
16
- * ```
17
- *
18
- * 2. We introduce a random `intervalVariance` to avoid many background jobs from starting at the same time and overloading the machine.
19
- *
20
- * 3. You may use `backgroundJob.run()` to force the background job to run right away. If the background job is already running, calling `backgroundJob.run()` schedules it to run again as soon as possible (with a wait interval of 0).
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
- * 5. In Node.js the background job is stopped on [`"gracefulTermination"`](https://github.com/radically-straightforward/radically-straightforward/tree/main/node#graceful-termination).
25
- *
26
- * **Example**
27
- *
28
- * ```javascript
29
- * import * as utilities from "@radically-straightforward/utilities";
30
- *
31
- * const backgroundJob = utilities.backgroundJob(
32
- * { interval: 3 * 1000 },
33
- * async () => {
34
- * console.log("backgroundJob(): Running background job...");
35
- * await utilities.sleep(3 * 1000);
36
- * console.log("backgroundJob(): ...finished running background job.");
37
- * },
38
- * );
39
- * process.on("SIGTSTP", () => {
40
- * backgroundJob.run();
41
- * });
42
- * console.log(
43
- * "backgroundJob(): Press ⌃Z to force background job to run and ⌃C to continue...",
44
- * );
45
- * ```
46
- */
47
- export declare function backgroundJob({ interval, intervalVariance, }: {
48
- interval: number;
49
- intervalVariance?: number;
50
- }, job: () => void | Promise<void>): {
51
- run: () => void;
52
- stop: () => void;
53
- };
54
- /**
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).
2
+ * 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/api/timers.html#timerspromisessettimeoutdelay-value-options).
56
3
  */
57
4
  export declare function sleep(duration: number): Promise<void>;
58
5
  /**
@@ -84,6 +31,22 @@ export declare function log(...messageParts: string[]): void;
84
31
  export declare class JSONLinesTransformStream extends TransformStream {
85
32
  constructor();
86
33
  }
34
+ /**
35
+ * Capitalizes the first letter of a string. It’s different from [Lodash’s `capitalize()`](https://lodash.com/docs/4.17.15#capitalize) in that it doesn’t lowercase the rest of the string.
36
+ */
37
+ export declare function capitalize(string: string): string;
38
+ /**
39
+ * Determine whether the given `string` is a valid `Date`, that is, it’s in ISO format and corresponds to an existing date, for example, it is **not** April 32nd.
40
+ */
41
+ export declare function isDate(string: string): boolean;
42
+ /**
43
+ * A regular expression that matches valid email addresses. This regular expression is more restrictive than the RFC—it doesn’t match some email addresses that technically are valid, for example, `example@localhost`. But it strikes a good tradeoff for practical purposes, for example, signing up in a web application.
44
+ */
45
+ export declare const emailRegExp: RegExp;
46
+ /**
47
+ * A regular expression that matches ISO dates, for example, `2024-04-01T14:19:48.162Z`.
48
+ */
49
+ export declare const ISODateRegExp: RegExp;
87
50
  /**
88
51
  * Utility type for `intern()`.
89
52
  */
@@ -216,4 +179,36 @@ export declare namespace intern {
216
179
  }>;
217
180
  }
218
181
  export declare const internSymbol: unique symbol;
182
+ /**
183
+ * > **Note:** This is a lower level utility. See `@radically-straightforward/node`’s and `@radically-straightforward/javascript`’s extensions to `backgroundJob()` that are better suited for their specific environments.
184
+ *
185
+ * Start a background job that runs every `interval`.
186
+ *
187
+ * `backgroundJob()` is different from `setInterval()` in the following ways:
188
+ *
189
+ * 1. The interval counts **between** jobs, so slow background jobs don’t get called concurrently:
190
+ *
191
+ * ```
192
+ * setInterval()
193
+ * | SLOW BACKGROUND JOB |
194
+ * | INTERVAL | SLOW BACKGROUND JOB |
195
+ * | INTERVAL | ...
196
+ *
197
+ * backgroundJob()
198
+ * | SLOW BACKGROUND JOB | INTERVAL | SLOW BACKGROUND JOB | INTERVAL | ...
199
+ * ```
200
+ *
201
+ * 2. 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`).
202
+ *
203
+ * 3. You may use `backgroundJob.stop()` to stop the background job. If the background job is in the middle of 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).
204
+ *
205
+ * 4. We introduce a random interval variance of 10% on top of the given `interval` to avoid many background jobs from starting at the same time and overloading the machine.
206
+ */
207
+ export declare function backgroundJob({ interval, onStop, }: {
208
+ interval: number;
209
+ onStop?: () => void | Promise<void>;
210
+ }, job: () => void | Promise<void>): {
211
+ run: () => Promise<void>;
212
+ stop: () => Promise<void>;
213
+ };
219
214
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
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,CAwCvC;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;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,wBAAyB,SAAQ,eAAe;;CAkB5D;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"}
1
+ {"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../source/index.mts"],"names":[],"mappings":"AAAA;;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;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,wBAAyB,SAAQ,eAAe;;CAkB5D;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAIjD;AAED;;GAEG;AACH,wBAAgB,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAI9C;AAED;;GAEG;AACH,eAAO,MAAM,WAAW,EAAE,MAAmD,CAAC;AAE9E;;GAEG;AACH,eAAO,MAAM,aAAa,EAAE,MACqB,CAAC;AAElD;;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;AAiB7C;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,aAAa,CAC3B,EACE,QAAQ,EACR,MAAiB,GAClB,EAAE;IACD,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrC,EACD,GAAG,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GAC9B;IACD,GAAG,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IACzB,IAAI,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC3B,CAqCA"}
package/build/index.mjs CHANGED
@@ -1,90 +1,5 @@
1
- if (process !== undefined)
2
- await import("@radically-straightforward/node");
3
1
  /**
4
- * Start a background job that runs every `interval`.
5
- *
6
- * This is different from `setInterval()` in the following ways:
7
- *
8
- * 1. The interval counts **between** jobs, so slow background jobs don’t get called concurrently:
9
- *
10
- * ```
11
- * setInterval()
12
- * | SLOW BACKGROUND JOB |
13
- * | INTERVAL | SLOW BACKGROUND JOB |
14
- * | INTERVAL | ...
15
- *
16
- * backgroundJob()
17
- * | SLOW BACKGROUND JOB | INTERVAL | SLOW BACKGROUND JOB | INTERVAL | ...
18
- * ```
19
- *
20
- * 2. We introduce a random `intervalVariance` to avoid many background jobs from starting at the same time and overloading the machine.
21
- *
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).
23
- *
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).
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
- *
28
- * **Example**
29
- *
30
- * ```javascript
31
- * import * as utilities from "@radically-straightforward/utilities";
32
- *
33
- * const backgroundJob = utilities.backgroundJob(
34
- * { interval: 3 * 1000 },
35
- * async () => {
36
- * console.log("backgroundJob(): Running background job...");
37
- * await utilities.sleep(3 * 1000);
38
- * console.log("backgroundJob(): ...finished running background job.");
39
- * },
40
- * );
41
- * process.on("SIGTSTP", () => {
42
- * backgroundJob.run();
43
- * });
44
- * console.log(
45
- * "backgroundJob(): Press ⌃Z to force background job to run and ⌃C to continue...",
46
- * );
47
- * ```
48
- */
49
- export function backgroundJob({ interval, intervalVariance = 0.1, }, job) {
50
- let state = "sleeping";
51
- let timeout = undefined;
52
- const scheduler = {
53
- run: async () => {
54
- switch (state) {
55
- case "sleeping":
56
- clearTimeout(timeout);
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
- }
67
- break;
68
- case "running":
69
- state = "runningAndMarkedForRerun";
70
- break;
71
- }
72
- },
73
- stop: () => {
74
- clearTimeout(timeout);
75
- state = "stopped";
76
- process?.off?.("gracefulTermination", gracefulTerminationListener);
77
- },
78
- };
79
- scheduler.run();
80
- const gracefulTerminationListener = () => {
81
- scheduler.stop();
82
- };
83
- process?.once?.("gracefulTermination", gracefulTerminationListener);
84
- return scheduler;
85
- }
86
- /**
87
- * 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).
2
+ * 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/api/timers.html#timerspromisessettimeoutdelay-value-options).
88
3
  */
89
4
  export function sleep(duration) {
90
5
  return new Promise((resolve) => setTimeout(resolve, duration));
@@ -139,6 +54,28 @@ export class JSONLinesTransformStream extends TransformStream {
139
54
  });
140
55
  }
141
56
  }
57
+ /**
58
+ * Capitalizes the first letter of a string. It’s different from [Lodash’s `capitalize()`](https://lodash.com/docs/4.17.15#capitalize) in that it doesn’t lowercase the rest of the string.
59
+ */
60
+ export function capitalize(string) {
61
+ return string.length === 0
62
+ ? string
63
+ : `${string[0].toUpperCase()}${string.slice(1)}`;
64
+ }
65
+ /**
66
+ * Determine whether the given `string` is a valid `Date`, that is, it’s in ISO format and corresponds to an existing date, for example, it is **not** April 32nd.
67
+ */
68
+ export function isDate(string) {
69
+ return (string.match(ISODateRegExp) !== null && !isNaN(new Date(string).getTime()));
70
+ }
71
+ /**
72
+ * A regular expression that matches valid email addresses. This regular expression is more restrictive than the RFC—it doesn’t match some email addresses that technically are valid, for example, `example@localhost`. But it strikes a good tradeoff for practical purposes, for example, signing up in a web application.
73
+ */
74
+ export const emailRegExp = /^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$/i;
75
+ /**
76
+ * A regular expression that matches ISO dates, for example, `2024-04-01T14:19:48.162Z`.
77
+ */
78
+ export const ISODateRegExp = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
142
79
  /**
143
80
  * [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:
144
81
  *
@@ -283,4 +220,63 @@ intern.pool = {
283
220
  intern.finalizationRegistry = new FinalizationRegistry(({ type, key }) => {
284
221
  intern.pool[type].delete(key);
285
222
  });
223
+ /**
224
+ * > **Note:** This is a lower level utility. See `@radically-straightforward/node`’s and `@radically-straightforward/javascript`’s extensions to `backgroundJob()` that are better suited for their specific environments.
225
+ *
226
+ * Start a background job that runs every `interval`.
227
+ *
228
+ * `backgroundJob()` is different from `setInterval()` in the following ways:
229
+ *
230
+ * 1. The interval counts **between** jobs, so slow background jobs don’t get called concurrently:
231
+ *
232
+ * ```
233
+ * setInterval()
234
+ * | SLOW BACKGROUND JOB |
235
+ * | INTERVAL | SLOW BACKGROUND JOB |
236
+ * | INTERVAL | ...
237
+ *
238
+ * backgroundJob()
239
+ * | SLOW BACKGROUND JOB | INTERVAL | SLOW BACKGROUND JOB | INTERVAL | ...
240
+ * ```
241
+ *
242
+ * 2. 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`).
243
+ *
244
+ * 3. You may use `backgroundJob.stop()` to stop the background job. If the background job is in the middle of 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).
245
+ *
246
+ * 4. We introduce a random interval variance of 10% on top of the given `interval` to avoid many background jobs from starting at the same time and overloading the machine.
247
+ */
248
+ export function backgroundJob({ interval, onStop = () => { }, }, job) {
249
+ let state = "sleeping";
250
+ let timeout = setTimeout(() => { });
251
+ const scheduler = {
252
+ run: async () => {
253
+ switch (state) {
254
+ case "sleeping":
255
+ clearTimeout(timeout);
256
+ state = "running";
257
+ await job();
258
+ if (state === "running" || state === "runningAndMarkedForRerun") {
259
+ timeout = setTimeout(() => {
260
+ scheduler.run();
261
+ }, state ===
262
+ "runningAndMarkedForRerun"
263
+ ? 0
264
+ : interval * (1 + 0.1 * Math.random()));
265
+ state = "sleeping";
266
+ }
267
+ break;
268
+ case "running":
269
+ state = "runningAndMarkedForRerun";
270
+ break;
271
+ }
272
+ },
273
+ stop: async () => {
274
+ clearTimeout(timeout);
275
+ state = "stopped";
276
+ await onStop();
277
+ },
278
+ };
279
+ scheduler.run();
280
+ return scheduler;
281
+ }
286
282
  //# 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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"}
@@ -2,26 +2,6 @@ import test from "node:test";
2
2
  import assert from "node:assert/strict";
3
3
  import * as utilities from "@radically-straightforward/utilities";
4
4
  import { intern as $ } from "@radically-straightforward/utilities";
5
- test("backgroundJob()", {
6
- skip: process.stdin.isTTY
7
- ? false
8
- : "Run interactive test with ‘node ./build/index.test.mjs’.",
9
- }, async () => {
10
- for (let iteration = 0; iteration < 1000; iteration++) {
11
- const backgroundJob = utilities.backgroundJob({ interval: 3 * 1000 }, () => { });
12
- backgroundJob.stop();
13
- // If background jobs leak ‘process.once("gracefulTermination")’ event listeners, then we get a warning in the console.
14
- }
15
- const backgroundJob = utilities.backgroundJob({ interval: 3 * 1000 }, async () => {
16
- console.log("backgroundJob(): Running background job...");
17
- await utilities.sleep(3 * 1000);
18
- console.log("backgroundJob(): ...finished running background job.");
19
- });
20
- process.on("SIGTSTP", () => {
21
- backgroundJob.run();
22
- });
23
- console.log("backgroundJob(): Press ⌃Z to force background job to run and ⌃C to gracefully terminate...");
24
- });
25
5
  test("sleep()", async () => {
26
6
  const before = Date.now();
27
7
  await utilities.sleep(1000);
@@ -58,6 +38,26 @@ test("JSONLinesTransformStream", async () => {
58
38
  });
59
39
  }
60
40
  });
41
+ test("capitalize()", () => {
42
+ assert.equal(utilities.capitalize("leandro Facchinetti"), "Leandro Facchinetti");
43
+ });
44
+ test("isDate()", () => {
45
+ assert(utilities.isDate("2024-04-01T14:57:46.638Z"));
46
+ assert(!utilities.isDate("2024-04-01T14:57:46.68Z"));
47
+ assert(!utilities.isDate("2024-04-32T14:57:46.638Z"));
48
+ });
49
+ test("emailRegExp", () => {
50
+ assert.match("leandro@leafac.com", utilities.emailRegExp);
51
+ assert.doesNotMatch("leandro@leafac.c", utilities.emailRegExp);
52
+ assert.doesNotMatch("leandro@localhost", utilities.emailRegExp);
53
+ assert.doesNotMatch("leafac.com", utilities.emailRegExp);
54
+ assert.doesNotMatch("'hello'@leafac.com", utilities.emailRegExp);
55
+ assert.doesNotMatch("leandro@lea_fac.com", utilities.emailRegExp);
56
+ });
57
+ test("ISODateRegExp", () => {
58
+ assert.match("2024-04-01T14:19:48.162Z", utilities.ISODateRegExp);
59
+ assert.doesNotMatch("2024-04-01 15:20", utilities.ISODateRegExp);
60
+ });
61
61
  test("intern()", () => {
62
62
  // @ts-expect-error
63
63
  assert(([1] === [1]) === false);
@@ -101,20 +101,23 @@ test("intern()", () => {
101
101
  // @ts-expect-error
102
102
  $([1])[0] = 2;
103
103
  });
104
- {
105
- const iterations = 1000;
106
- console.time("intern()");
107
- const objects = [];
108
- for (let iteration = 0; iteration < iterations; iteration++) {
109
- const entries = [];
110
- for (let key = 0; key < Math.floor(Math.random() * 15); key++) {
111
- entries.push([String(key + Math.floor(Math.random() * 15)), true]);
112
- }
113
- objects.push($(Object.fromEntries(entries)));
114
- objects.push($(entries.flat()));
115
- }
116
- // console.log($.pool.record.size);
117
- console.timeEnd("intern()");
118
- }
104
+ });
105
+ test("backgroundJob()", {
106
+ skip: process.stdin.isTTY
107
+ ? false
108
+ : "Run interactive test with ‘node ./build/index.test.mjs’.",
109
+ }, async () => {
110
+ const backgroundJob = utilities.backgroundJob({ interval: 3 * 1000 }, async () => {
111
+ console.log("backgroundJob(): Running background job...");
112
+ await utilities.sleep(3 * 1000);
113
+ console.log("backgroundJob(): ...finished running background job.");
114
+ });
115
+ process.on("SIGTSTP", () => {
116
+ backgroundJob.run();
117
+ });
118
+ process.on("SIGINT", () => {
119
+ backgroundJob.stop();
120
+ });
121
+ console.log("backgroundJob(): Press ⌃Z to ‘run()’ and ⌃C to ‘stop()’...");
119
122
  });
120
123
  //# 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": "1.2.1",
3
+ "version": "2.0.0",
4
4
  "description": "🛠️ Utilities for Node.js and the browser",
5
5
  "keywords": [
6
6
  "node",
@@ -27,9 +27,6 @@
27
27
  "prepare": "tsc && documentation",
28
28
  "test": "npm run prepare && node --test && prettier --check \"./README.md\" --check \"./CHANGELOG.md\" --check \"./package.json\" --check \"./tsconfig.json\" --check \"./source/**/*.mts\""
29
29
  },
30
- "dependencies": {
31
- "@radically-straightforward/node": "^3.0.0"
32
- },
33
30
  "devDependencies": {
34
31
  "@radically-straightforward/documentation": "^1.0.1",
35
32
  "@radically-straightforward/tsconfig": "^1.0.0",
package/source/index.mts CHANGED
@@ -1,101 +1,5 @@
1
- if (process !== undefined) await import("@radically-straightforward/node");
2
-
3
- /**
4
- * Start a background job that runs every `interval`.
5
- *
6
- * This is different from `setInterval()` in the following ways:
7
- *
8
- * 1. The interval counts **between** jobs, so slow background jobs don’t get called concurrently:
9
- *
10
- * ```
11
- * setInterval()
12
- * | SLOW BACKGROUND JOB |
13
- * | INTERVAL | SLOW BACKGROUND JOB |
14
- * | INTERVAL | ...
15
- *
16
- * backgroundJob()
17
- * | SLOW BACKGROUND JOB | INTERVAL | SLOW BACKGROUND JOB | INTERVAL | ...
18
- * ```
19
- *
20
- * 2. We introduce a random `intervalVariance` to avoid many background jobs from starting at the same time and overloading the machine.
21
- *
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).
23
- *
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).
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
- *
28
- * **Example**
29
- *
30
- * ```javascript
31
- * import * as utilities from "@radically-straightforward/utilities";
32
- *
33
- * const backgroundJob = utilities.backgroundJob(
34
- * { interval: 3 * 1000 },
35
- * async () => {
36
- * console.log("backgroundJob(): Running background job...");
37
- * await utilities.sleep(3 * 1000);
38
- * console.log("backgroundJob(): ...finished running background job.");
39
- * },
40
- * );
41
- * process.on("SIGTSTP", () => {
42
- * backgroundJob.run();
43
- * });
44
- * console.log(
45
- * "backgroundJob(): Press ⌃Z to force background job to run and ⌃C to continue...",
46
- * );
47
- * ```
48
- */
49
- export function backgroundJob(
50
- {
51
- interval,
52
- intervalVariance = 0.1,
53
- }: { interval: number; intervalVariance?: number },
54
- job: () => void | Promise<void>,
55
- ): { run: () => void; stop: () => void } {
56
- let state: "sleeping" | "running" | "runningAndMarkedForRerun" | "stopped" =
57
- "sleeping";
58
- let timeout: any = undefined;
59
- const scheduler = {
60
- run: async () => {
61
- switch (state) {
62
- case "sleeping":
63
- clearTimeout(timeout);
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
- }
77
- break;
78
- case "running":
79
- state = "runningAndMarkedForRerun";
80
- break;
81
- }
82
- },
83
- stop: () => {
84
- clearTimeout(timeout);
85
- state = "stopped";
86
- process?.off?.("gracefulTermination", gracefulTerminationListener);
87
- },
88
- };
89
- scheduler.run();
90
- const gracefulTerminationListener = () => {
91
- scheduler.stop();
92
- };
93
- process?.once?.("gracefulTermination", gracefulTerminationListener);
94
- return scheduler;
95
- }
96
-
97
1
  /**
98
- * 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).
2
+ * 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/api/timers.html#timerspromisessettimeoutdelay-value-options).
99
3
  */
100
4
  export function sleep(duration: number): Promise<void> {
101
5
  return new Promise((resolve) => setTimeout(resolve, duration));
@@ -153,6 +57,35 @@ export class JSONLinesTransformStream extends TransformStream {
153
57
  }
154
58
  }
155
59
 
60
+ /**
61
+ * Capitalizes the first letter of a string. It’s different from [Lodash’s `capitalize()`](https://lodash.com/docs/4.17.15#capitalize) in that it doesn’t lowercase the rest of the string.
62
+ */
63
+ export function capitalize(string: string): string {
64
+ return string.length === 0
65
+ ? string
66
+ : `${string[0].toUpperCase()}${string.slice(1)}`;
67
+ }
68
+
69
+ /**
70
+ * Determine whether the given `string` is a valid `Date`, that is, it’s in ISO format and corresponds to an existing date, for example, it is **not** April 32nd.
71
+ */
72
+ export function isDate(string: string): boolean {
73
+ return (
74
+ string.match(ISODateRegExp) !== null && !isNaN(new Date(string).getTime())
75
+ );
76
+ }
77
+
78
+ /**
79
+ * A regular expression that matches valid email addresses. This regular expression is more restrictive than the RFC—it doesn’t match some email addresses that technically are valid, for example, `example@localhost`. But it strikes a good tradeoff for practical purposes, for example, signing up in a web application.
80
+ */
81
+ export const emailRegExp: RegExp = /^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$/i;
82
+
83
+ /**
84
+ * A regular expression that matches ISO dates, for example, `2024-04-01T14:19:48.162Z`.
85
+ */
86
+ export const ISODateRegExp: RegExp =
87
+ /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
88
+
156
89
  /**
157
90
  * Utility type for `intern()`.
158
91
  */
@@ -334,3 +267,79 @@ intern.finalizationRegistry = new FinalizationRegistry<{
334
267
  }>(({ type, key }) => {
335
268
  intern.pool[type].delete(key);
336
269
  });
270
+
271
+ /**
272
+ * > **Note:** This is a lower level utility. See `@radically-straightforward/node`’s and `@radically-straightforward/javascript`’s extensions to `backgroundJob()` that are better suited for their specific environments.
273
+ *
274
+ * Start a background job that runs every `interval`.
275
+ *
276
+ * `backgroundJob()` is different from `setInterval()` in the following ways:
277
+ *
278
+ * 1. The interval counts **between** jobs, so slow background jobs don’t get called concurrently:
279
+ *
280
+ * ```
281
+ * setInterval()
282
+ * | SLOW BACKGROUND JOB |
283
+ * | INTERVAL | SLOW BACKGROUND JOB |
284
+ * | INTERVAL | ...
285
+ *
286
+ * backgroundJob()
287
+ * | SLOW BACKGROUND JOB | INTERVAL | SLOW BACKGROUND JOB | INTERVAL | ...
288
+ * ```
289
+ *
290
+ * 2. 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`).
291
+ *
292
+ * 3. You may use `backgroundJob.stop()` to stop the background job. If the background job is in the middle of 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).
293
+ *
294
+ * 4. We introduce a random interval variance of 10% on top of the given `interval` to avoid many background jobs from starting at the same time and overloading the machine.
295
+ */
296
+ export function backgroundJob(
297
+ {
298
+ interval,
299
+ onStop = () => {},
300
+ }: {
301
+ interval: number;
302
+ onStop?: () => void | Promise<void>;
303
+ },
304
+ job: () => void | Promise<void>,
305
+ ): {
306
+ run: () => Promise<void>;
307
+ stop: () => Promise<void>;
308
+ } {
309
+ let state: "sleeping" | "running" | "runningAndMarkedForRerun" | "stopped" =
310
+ "sleeping";
311
+ let timeout = setTimeout(() => {});
312
+ const scheduler = {
313
+ run: async () => {
314
+ switch (state) {
315
+ case "sleeping":
316
+ clearTimeout(timeout);
317
+ state = "running";
318
+ await job();
319
+ if (state === "running" || state === "runningAndMarkedForRerun") {
320
+ timeout = setTimeout(
321
+ () => {
322
+ scheduler.run();
323
+ },
324
+ (state as "running" | "runningAndMarkedForRerun") ===
325
+ "runningAndMarkedForRerun"
326
+ ? 0
327
+ : interval * (1 + 0.1 * Math.random()),
328
+ );
329
+ state = "sleeping";
330
+ }
331
+ break;
332
+ case "running":
333
+ state = "runningAndMarkedForRerun";
334
+ break;
335
+ }
336
+ },
337
+ stop: async () => {
338
+ clearTimeout(timeout);
339
+ state = "stopped";
340
+ await onStop();
341
+ },
342
+ };
343
+ scheduler.run();
344
+ return scheduler;
345
+ }
@@ -3,40 +3,6 @@ import assert from "node:assert/strict";
3
3
  import * as utilities from "@radically-straightforward/utilities";
4
4
  import { intern as $ } from "@radically-straightforward/utilities";
5
5
 
6
- test(
7
- "backgroundJob()",
8
- {
9
- skip: process.stdin.isTTY
10
- ? false
11
- : "Run interactive test with ‘node ./build/index.test.mjs’.",
12
- },
13
- async () => {
14
- for (let iteration = 0; iteration < 1000; iteration++) {
15
- const backgroundJob = utilities.backgroundJob(
16
- { interval: 3 * 1000 },
17
- () => {},
18
- );
19
- backgroundJob.stop();
20
- // If background jobs leak ‘process.once("gracefulTermination")’ event listeners, then we get a warning in the console.
21
- }
22
-
23
- const backgroundJob = utilities.backgroundJob(
24
- { interval: 3 * 1000 },
25
- async () => {
26
- console.log("backgroundJob(): Running background job...");
27
- await utilities.sleep(3 * 1000);
28
- console.log("backgroundJob(): ...finished running background job.");
29
- },
30
- );
31
- process.on("SIGTSTP", () => {
32
- backgroundJob.run();
33
- });
34
- console.log(
35
- "backgroundJob(): Press ⌃Z to force background job to run and ⌃C to gracefully terminate...",
36
- );
37
- },
38
- );
39
-
40
6
  test("sleep()", async () => {
41
7
  const before = Date.now();
42
8
  await utilities.sleep(1000);
@@ -78,6 +44,33 @@ test("JSONLinesTransformStream", async () => {
78
44
  }
79
45
  });
80
46
 
47
+ test("capitalize()", () => {
48
+ assert.equal(
49
+ utilities.capitalize("leandro Facchinetti"),
50
+ "Leandro Facchinetti",
51
+ );
52
+ });
53
+
54
+ test("isDate()", () => {
55
+ assert(utilities.isDate("2024-04-01T14:57:46.638Z"));
56
+ assert(!utilities.isDate("2024-04-01T14:57:46.68Z"));
57
+ assert(!utilities.isDate("2024-04-32T14:57:46.638Z"));
58
+ });
59
+
60
+ test("emailRegExp", () => {
61
+ assert.match("leandro@leafac.com", utilities.emailRegExp);
62
+ assert.doesNotMatch("leandro@leafac.c", utilities.emailRegExp);
63
+ assert.doesNotMatch("leandro@localhost", utilities.emailRegExp);
64
+ assert.doesNotMatch("leafac.com", utilities.emailRegExp);
65
+ assert.doesNotMatch("'hello'@leafac.com", utilities.emailRegExp);
66
+ assert.doesNotMatch("leandro@lea_fac.com", utilities.emailRegExp);
67
+ });
68
+
69
+ test("ISODateRegExp", () => {
70
+ assert.match("2024-04-01T14:19:48.162Z", utilities.ISODateRegExp);
71
+ assert.doesNotMatch("2024-04-01 15:20", utilities.ISODateRegExp);
72
+ });
73
+
81
74
  test("intern()", () => {
82
75
  // @ts-expect-error
83
76
  assert(([1] === [1]) === false);
@@ -128,20 +121,30 @@ test("intern()", () => {
128
121
  // @ts-expect-error
129
122
  $([1])[0] = 2;
130
123
  });
124
+ });
131
125
 
126
+ test(
127
+ "backgroundJob()",
132
128
  {
133
- const iterations = 1000;
134
- console.time("intern()");
135
- const objects = [];
136
- for (let iteration = 0; iteration < iterations; iteration++) {
137
- const entries = [];
138
- for (let key = 0; key < Math.floor(Math.random() * 15); key++) {
139
- entries.push([String(key + Math.floor(Math.random() * 15)), true]);
140
- }
141
- objects.push($(Object.fromEntries(entries)));
142
- objects.push($(entries.flat()));
143
- }
144
- // console.log($.pool.record.size);
145
- console.timeEnd("intern()");
146
- }
147
- });
129
+ skip: process.stdin.isTTY
130
+ ? false
131
+ : "Run interactive test with ‘node ./build/index.test.mjs’.",
132
+ },
133
+ async () => {
134
+ const backgroundJob = utilities.backgroundJob(
135
+ { interval: 3 * 1000 },
136
+ async () => {
137
+ console.log("backgroundJob(): Running background job...");
138
+ await utilities.sleep(3 * 1000);
139
+ console.log("backgroundJob(): ...finished running background job.");
140
+ },
141
+ );
142
+ process.on("SIGTSTP", () => {
143
+ backgroundJob.run();
144
+ });
145
+ process.on("SIGINT", () => {
146
+ backgroundJob.stop();
147
+ });
148
+ console.log("backgroundJob(): Press ⌃Z to ‘run()’ and ⌃C to ‘stop()’...");
149
+ },
150
+ );