@ix-xs/node-comfort 1.2.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +340 -0
- package/README.md +137 -548
- package/index.js +80 -35
- package/package.json +161 -33
- package/src/Arr.js +1050 -418
- package/src/Async.js +817 -0
- package/src/Cache.js +433 -0
- package/src/Checker.js +1202 -539
- package/src/Cli.js +1293 -0
- package/src/Color.js +556 -0
- package/src/Crypto.js +1082 -0
- package/src/Emitter.js +356 -0
- package/src/Env.js +689 -0
- package/src/FS.js +1626 -894
- package/src/Func.js +823 -356
- package/src/Http.js +700 -0
- package/src/Id.js +568 -200
- package/src/Logger.js +963 -378
- package/src/Num.js +965 -360
- package/src/Obj.js +1077 -379
- package/src/SQLite.js +1629 -1005
- package/src/Schema.js +2101 -0
- package/src/Str.js +1209 -432
- package/src/Sys.js +654 -0
- package/src/Time.js +1533 -440
- package/src/Utils.js +342 -325
- package/src/errors.js +335 -0
- package/src/internal/shared.js +415 -0
- package/types/index.d.ts +735 -526
- package/types/src/Arr.d.ts +533 -145
- package/types/src/Arr.d.ts.map +1 -1
- package/types/src/Async.d.ts +372 -0
- package/types/src/Async.d.ts.map +1 -0
- package/types/src/Cache.d.ts +221 -0
- package/types/src/Cache.d.ts.map +1 -0
- package/types/src/Checker.d.ts +651 -232
- package/types/src/Checker.d.ts.map +1 -1
- package/types/src/Cli.d.ts +664 -0
- package/types/src/Cli.d.ts.map +1 -0
- package/types/src/Color.d.ts +300 -0
- package/types/src/Color.d.ts.map +1 -0
- package/types/src/Crypto.d.ts +620 -0
- package/types/src/Crypto.d.ts.map +1 -0
- package/types/src/Emitter.d.ts +189 -0
- package/types/src/Emitter.d.ts.map +1 -0
- package/types/src/Env.d.ts +412 -0
- package/types/src/Env.d.ts.map +1 -0
- package/types/src/FS.d.ts +717 -238
- package/types/src/FS.d.ts.map +1 -1
- package/types/src/Func.d.ts +508 -135
- package/types/src/Func.d.ts.map +1 -1
- package/types/src/Http.d.ts +444 -0
- package/types/src/Http.d.ts.map +1 -0
- package/types/src/Id.d.ts +199 -66
- package/types/src/Id.d.ts.map +1 -1
- package/types/src/Logger.d.ts +414 -46
- package/types/src/Logger.d.ts.map +1 -1
- package/types/src/Num.d.ts +514 -137
- package/types/src/Num.d.ts.map +1 -1
- package/types/src/Obj.d.ts +453 -104
- package/types/src/Obj.d.ts.map +1 -1
- package/types/src/SQLite.d.ts +754 -308
- package/types/src/SQLite.d.ts.map +1 -1
- package/types/src/Schema.d.ts +1189 -0
- package/types/src/Schema.d.ts.map +1 -0
- package/types/src/Str.d.ts +617 -166
- package/types/src/Str.d.ts.map +1 -1
- package/types/src/Sys.d.ts +314 -0
- package/types/src/Sys.d.ts.map +1 -0
- package/types/src/Time.d.ts +759 -204
- package/types/src/Time.d.ts.map +1 -1
- package/types/src/Utils.d.ts +158 -102
- package/types/src/Utils.d.ts.map +1 -1
- package/types/src/errors.d.ts +316 -0
- package/types/src/errors.d.ts.map +1 -0
- package/types/src/internal/shared.d.ts +123 -0
- package/types/src/internal/shared.d.ts.map +1 -0
- package/types/index.d.ts.map +0 -1
package/src/Utils.js
CHANGED
|
@@ -1,325 +1,342 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
*
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
on(event, handler)
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Everyday helpers: `wait`, `when`, `dontCrash`, and JSON functions that
|
|
5
|
+
* don't throw. They are also available at the top level (`nc.wait()`).
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* await nc.wait("1.5s");
|
|
9
|
+
* const settings = nc.JSONParse(text, {});
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const { types } = require("node:util");
|
|
13
|
+
const { sleep, toMs } = require("./internal/shared.js");
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Options for `wait()`.
|
|
17
|
+
* @typedef {object} WaitOptions
|
|
18
|
+
* @property {AbortSignal} [signal] Cancels the wait; the promise rejects with an `AbortError`.
|
|
19
|
+
* @property {boolean} [unref] Let the process exit even if the timer is still pending.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Options for `when()`.
|
|
24
|
+
* @typedef {object} WhenOptions
|
|
25
|
+
* @property {number | string} [interval] Time between two checks, in ms or like `"1s"`. Defaults to `50`.
|
|
26
|
+
* @property {number | string | null} [timeout] Give up after this long, in ms or like `"10s"`, and emit `"timeout"`.
|
|
27
|
+
* @property {number | null} [max] Stop after this many triggers.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The task returned by `when()`. Every method returns the task, so calls chain.
|
|
32
|
+
* @typedef {object} WhenTask
|
|
33
|
+
* @property {() => WhenTask} start Starts checking. A stopped task can't be restarted.
|
|
34
|
+
* @property {() => WhenTask} stop Stops checking and clears the timers.
|
|
35
|
+
* @property {<E extends "trigger" | "error" | "timeout">(event: E, handler: E extends "error" ? (error: unknown) => void : (payload: any) => void) => WhenTask} on Listens to `"trigger"` (the condition is true), `"error"` (it threw) or `"timeout"`.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Events you can customize on `dontCrash()`.
|
|
40
|
+
* @typedef {"error" | "exit" | "sig" | "beforeExit"} DontCrashEvent
|
|
41
|
+
*/
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Returned by `dontCrash()`.
|
|
45
|
+
* @typedef {object} DontCrashController
|
|
46
|
+
* @property {<E extends DontCrashEvent>(event: E, handler?: E extends "error" ? (error: unknown) => void : E extends "sig" ? (signal: NodeJS.Signals) => void : (code: number) => void) => DontCrashController} on Replaces the handler for an event. Call it without a handler to restore the default one.
|
|
47
|
+
* @property {() => void} dispose Removes everything `dontCrash()` installed.
|
|
48
|
+
*/
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Resolves after a delay, given in milliseconds or as a duration string.
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* await nc.wait(500);
|
|
55
|
+
* await nc.wait("2s");
|
|
56
|
+
* await nc.wait("1m", { signal: controller.signal });
|
|
57
|
+
*
|
|
58
|
+
* @param {number | string} duration Milliseconds, or a string like `"250ms"`, `"2s"`, `"1m30s"`.
|
|
59
|
+
* @param {WaitOptions} [options]
|
|
60
|
+
* @returns {Promise<void>}
|
|
61
|
+
* @throws {AbortError} If the signal is aborted.
|
|
62
|
+
* @throws {TypeError} If the duration can't be parsed.
|
|
63
|
+
*/
|
|
64
|
+
function wait(duration, options = {}) {
|
|
65
|
+
let ms;
|
|
66
|
+
try {
|
|
67
|
+
ms = toMs(duration);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
return Promise.reject(error);
|
|
70
|
+
}
|
|
71
|
+
if (!Number.isFinite(ms)) return Promise.reject(new TypeError(`Invalid duration: ${String(duration)}`));
|
|
72
|
+
return sleep(ms, options.signal, options.unref ?? false);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Checks a condition at a regular interval and tells you when it's true.
|
|
77
|
+
* Call `.start()` once your listeners are in place. If you just want to
|
|
78
|
+
* await a condition, `nc.async.poll()` is simpler.
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* nc.when(() => queue.length > 0, { reason: "jobs waiting" }, { interval: 500, max: 1, timeout: "10s" })
|
|
82
|
+
* .on("trigger", (payload) => nc.info(payload.reason))
|
|
83
|
+
* .on("timeout", () => nc.warn("Nothing happened in 10s"))
|
|
84
|
+
* .start();
|
|
85
|
+
*
|
|
86
|
+
* @param {boolean | PromiseLike<boolean> | (() => unknown)} predicate The condition, or a function (sync or async) that returns it.
|
|
87
|
+
* @param {any} [payload={}] Passed to the `"trigger"` and `"timeout"` listeners.
|
|
88
|
+
* @param {WhenOptions} [options]
|
|
89
|
+
* @returns {WhenTask}
|
|
90
|
+
*/
|
|
91
|
+
function when(predicate, payload = {}, options = {}) {
|
|
92
|
+
const interval = options.interval === undefined ? 50 : toMs(options.interval, "interval");
|
|
93
|
+
const timeout = options.timeout === undefined || options.timeout === null ? null : toMs(options.timeout, "timeout");
|
|
94
|
+
const max = options.max ?? null;
|
|
95
|
+
/** @type {Record<"trigger" | "error" | "timeout", Set<Function>>} */
|
|
96
|
+
const listeners = { trigger: new Set(), error: new Set(), timeout: new Set() };
|
|
97
|
+
/** @type {NodeJS.Timeout | undefined} */
|
|
98
|
+
let timer;
|
|
99
|
+
/** @type {NodeJS.Timeout | undefined} */
|
|
100
|
+
let timeoutTimer;
|
|
101
|
+
let stopped = false;
|
|
102
|
+
let started = false;
|
|
103
|
+
let count = 0;
|
|
104
|
+
|
|
105
|
+
const emit = (/** @type {"trigger" | "error" | "timeout"} */ event, /** @type {unknown} */ value) => {
|
|
106
|
+
for (const fn of listeners[event]) {
|
|
107
|
+
try {
|
|
108
|
+
fn(value);
|
|
109
|
+
} catch {
|
|
110
|
+
// A failing listener must not stop the task.
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
const halt = () => {
|
|
115
|
+
stopped = true;
|
|
116
|
+
clearTimeout(timer);
|
|
117
|
+
clearTimeout(timeoutTimer);
|
|
118
|
+
};
|
|
119
|
+
const tick = async () => {
|
|
120
|
+
if (stopped) return;
|
|
121
|
+
try {
|
|
122
|
+
const ok = typeof predicate === "function" ? await predicate() : await predicate;
|
|
123
|
+
if (stopped) return;
|
|
124
|
+
if (ok) {
|
|
125
|
+
count++;
|
|
126
|
+
emit("trigger", payload);
|
|
127
|
+
if (max !== null && count >= max) return halt();
|
|
128
|
+
}
|
|
129
|
+
} catch (error) {
|
|
130
|
+
emit("error", error);
|
|
131
|
+
}
|
|
132
|
+
if (!stopped) timer = setTimeout(tick, interval);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
/** @type {WhenTask} */
|
|
136
|
+
const task = {
|
|
137
|
+
start() {
|
|
138
|
+
if (stopped || started) return task;
|
|
139
|
+
started = true;
|
|
140
|
+
timer = setTimeout(tick, 0);
|
|
141
|
+
if (timeout !== null) {
|
|
142
|
+
timeoutTimer = setTimeout(() => {
|
|
143
|
+
if (stopped) return;
|
|
144
|
+
emit("timeout", payload);
|
|
145
|
+
halt();
|
|
146
|
+
}, timeout);
|
|
147
|
+
}
|
|
148
|
+
return task;
|
|
149
|
+
},
|
|
150
|
+
stop() {
|
|
151
|
+
halt();
|
|
152
|
+
return task;
|
|
153
|
+
},
|
|
154
|
+
on(event, handler) {
|
|
155
|
+
listeners[event]?.add(handler);
|
|
156
|
+
return task;
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
return task;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Keeps the process alive when something goes wrong. Uncaught exceptions and
|
|
164
|
+
* unhandled rejections are logged instead of crashing, and `SIGINT`/`SIGTERM`
|
|
165
|
+
* are logged before a clean exit. Customize any of it with `.on()`.
|
|
166
|
+
*
|
|
167
|
+
* Only its own handlers are ever removed; listeners added by your code or by
|
|
168
|
+
* other libraries are left alone.
|
|
169
|
+
*
|
|
170
|
+
* @example
|
|
171
|
+
* nc.dontCrash()
|
|
172
|
+
* .on("error", (error) => sentry.captureException(error))
|
|
173
|
+
* .on("sig", async () => {
|
|
174
|
+
* await server.close();
|
|
175
|
+
* process.exit(0);
|
|
176
|
+
* });
|
|
177
|
+
*
|
|
178
|
+
* @returns {DontCrashController}
|
|
179
|
+
*/
|
|
180
|
+
function dontCrash() {
|
|
181
|
+
_dontCrashDispose?.();
|
|
182
|
+
const logger = require("./Logger.js");
|
|
183
|
+
/** @type {Array<[string, (...args: any[]) => void]>} */
|
|
184
|
+
let installed = [];
|
|
185
|
+
|
|
186
|
+
const defaults = {
|
|
187
|
+
error: (/** @type {unknown} */ error) => logger.error("[@ix-xs/node-comfort] Uncaught error:", error),
|
|
188
|
+
exit: (/** @type {number} */ code) => logger.log(`<% gray [@ix-xs/node-comfort] Process exit with code ${code} %>`),
|
|
189
|
+
sig: (/** @type {NodeJS.Signals} */ signal) => {
|
|
190
|
+
logger.warn(`[@ix-xs/node-comfort] Received ${signal}, exiting`);
|
|
191
|
+
process.exit(0);
|
|
192
|
+
},
|
|
193
|
+
beforeExit: () => {},
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* @param {string} event
|
|
198
|
+
* @param {(...args: any[]) => void} handler
|
|
199
|
+
*/
|
|
200
|
+
const listen = (event, handler) => {
|
|
201
|
+
try {
|
|
202
|
+
process.on(/** @type {any} */ (event), handler);
|
|
203
|
+
installed.push([event, handler]);
|
|
204
|
+
} catch {
|
|
205
|
+
// Signal not supported on this platform (for example SIGQUIT on Windows).
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
/** @param {string[]} events */
|
|
209
|
+
const unlisten = (events) => {
|
|
210
|
+
installed = installed.filter(([event, handler]) => {
|
|
211
|
+
if (!events.includes(event)) return true;
|
|
212
|
+
process.off(/** @type {any} */ (event), handler);
|
|
213
|
+
return false;
|
|
214
|
+
});
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
/** @type {DontCrashController} */
|
|
218
|
+
const controller = {
|
|
219
|
+
on(event, handler) {
|
|
220
|
+
if (event === "error") {
|
|
221
|
+
unlisten(["uncaughtException", "unhandledRejection"]);
|
|
222
|
+
const fn = /** @type {(error: unknown) => void} */ (handler ?? defaults.error);
|
|
223
|
+
listen("uncaughtException", fn);
|
|
224
|
+
listen("unhandledRejection", fn);
|
|
225
|
+
} else if (event === "exit") {
|
|
226
|
+
unlisten(["exit"]);
|
|
227
|
+
listen("exit", /** @type {(code: number) => void} */ (handler ?? defaults.exit));
|
|
228
|
+
} else if (event === "sig") {
|
|
229
|
+
const signals = ["SIGINT", "SIGTERM", "SIGQUIT"];
|
|
230
|
+
unlisten(signals);
|
|
231
|
+
for (const signal of signals) listen(signal, /** @type {(signal: NodeJS.Signals) => void} */ (handler ?? defaults.sig));
|
|
232
|
+
} else if (event === "beforeExit") {
|
|
233
|
+
unlisten(["beforeExit"]);
|
|
234
|
+
listen("beforeExit", /** @type {(code: number) => void} */ (handler ?? defaults.beforeExit));
|
|
235
|
+
}
|
|
236
|
+
return controller;
|
|
237
|
+
},
|
|
238
|
+
dispose() {
|
|
239
|
+
unlisten(installed.map(([event]) => event));
|
|
240
|
+
if (_dontCrashDispose === controller.dispose) _dontCrashDispose = undefined;
|
|
241
|
+
},
|
|
242
|
+
};
|
|
243
|
+
_dontCrashDispose = controller.dispose;
|
|
244
|
+
controller.on("error").on("exit").on("sig");
|
|
245
|
+
return controller;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** @type {(() => void) | undefined} */
|
|
249
|
+
let _dontCrashDispose;
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* `JSON.stringify` that never throws. Circular references become
|
|
253
|
+
* `"[Circular]"`, bigints become strings, Maps become objects, Sets become
|
|
254
|
+
* arrays, and errors keep their name, message and stack.
|
|
255
|
+
*
|
|
256
|
+
* @example
|
|
257
|
+
* nc.JSONString({ a: 1 }); // '{\n "a": 1\n}'
|
|
258
|
+
* nc.JSONString({ id: 10n, tags: new Set(["x"]) }, 0); // '{"id":"10","tags":["x"]}'
|
|
259
|
+
* nc.JSONString({ error: new Error("boom") }, 0); // '{"error":{"name":"Error","message":"boom","stack":"..."}}'
|
|
260
|
+
*
|
|
261
|
+
* @param {unknown} value
|
|
262
|
+
* @param {number} [spaces=4] Indentation. Use `0` for a single line.
|
|
263
|
+
* @returns {string} The JSON text, or `"undefined"` for values JSON can't represent.
|
|
264
|
+
*/
|
|
265
|
+
function JSONString(value, spaces = 4) {
|
|
266
|
+
/** @type {Array<[holder: object, original: object]>} ancestors of the value being serialized */
|
|
267
|
+
const stack = [];
|
|
268
|
+
let text;
|
|
269
|
+
try {
|
|
270
|
+
text = JSON.stringify(
|
|
271
|
+
value,
|
|
272
|
+
/** @this {any} */ function(_, raw) {
|
|
273
|
+
if (typeof raw === "bigint") return raw.toString();
|
|
274
|
+
if (raw === null || typeof raw !== "object") return raw;
|
|
275
|
+
while (stack.length && stack[stack.length - 1][0] !== this) stack.pop();
|
|
276
|
+
for (let i = 0; i < stack.length; i++) if (stack[i][1] === raw) return "[Circular]";
|
|
277
|
+
const v = types.isMap(raw) ? Object.fromEntries(raw) : types.isSet(raw) ? [...raw] : types.isNativeError(raw) ? _plainError(raw) : raw;
|
|
278
|
+
stack.push([v, raw]);
|
|
279
|
+
return v;
|
|
280
|
+
},
|
|
281
|
+
spaces,
|
|
282
|
+
);
|
|
283
|
+
} catch (error) {
|
|
284
|
+
// A getter or a toJSON() threw: describe the value instead of failing.
|
|
285
|
+
try {
|
|
286
|
+
text = JSON.stringify(require("node:util").inspect(value, { depth: 4, breakLength: Infinity }));
|
|
287
|
+
} catch {
|
|
288
|
+
text = JSON.stringify(`[Unserializable: ${error instanceof Error ? error.message : String(error)}]`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return text ?? String(text);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* The fields of an error that `JSON.stringify` would drop.
|
|
296
|
+
* @param {Error} error
|
|
297
|
+
* @returns {Record<string, unknown>}
|
|
298
|
+
*/
|
|
299
|
+
const _plainError = (error) => {
|
|
300
|
+
/** @type {Record<string, unknown>} */
|
|
301
|
+
const out = { name: error.name, message: error.message };
|
|
302
|
+
for (const key of Object.keys(error)) out[key] = /** @type {any} */ (error)[key];
|
|
303
|
+
if (error.cause !== undefined) out.cause = error.cause;
|
|
304
|
+
if (error.stack) out.stack = error.stack;
|
|
305
|
+
return out;
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* `JSON.parse` with an optional fallback. With a fallback, invalid input
|
|
310
|
+
* returns it instead of throwing. A leading byte order mark, which Windows
|
|
311
|
+
* editors like to add, is ignored.
|
|
312
|
+
*
|
|
313
|
+
* @example
|
|
314
|
+
* nc.JSONParse('{"a":1}'); // { a: 1 }
|
|
315
|
+
* nc.JSONParse("oops", null); // null
|
|
316
|
+
* nc.JSONParse("oops"); // throws SyntaxError
|
|
317
|
+
* nc.JSONParse(text, {}, (key, value) => (key === "at" ? new Date(value) : value));
|
|
318
|
+
*
|
|
319
|
+
* @template [T=any]
|
|
320
|
+
* @param {string} text
|
|
321
|
+
* @param {T} [fallback] Returned when `text` isn't valid JSON.
|
|
322
|
+
* @param {(this: any, key: string, value: any) => any} [reviver] Passed to `JSON.parse`.
|
|
323
|
+
* @returns {T}
|
|
324
|
+
* @throws {SyntaxError} If `text` is invalid and there's no fallback.
|
|
325
|
+
*/
|
|
326
|
+
function JSONParse(text, fallback, reviver) {
|
|
327
|
+
const clean = typeof text === "string" && text.charCodeAt(0) === 0xfeff ? text.slice(1) : text;
|
|
328
|
+
if (arguments.length < 2) return JSON.parse(clean);
|
|
329
|
+
try {
|
|
330
|
+
return JSON.parse(clean, reviver);
|
|
331
|
+
} catch {
|
|
332
|
+
return /** @type {T} */ (fallback);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
module.exports = {
|
|
337
|
+
wait,
|
|
338
|
+
when,
|
|
339
|
+
dontCrash,
|
|
340
|
+
JSONString,
|
|
341
|
+
JSONParse,
|
|
342
|
+
};
|