@ix-xs/node-comfort 1.1.31 → 2.0.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 +190 -0
- package/LICENSE +21 -0
- package/README.md +210 -748
- package/index.js +84 -13
- package/package.json +201 -21
- package/src/Arr.js +901 -0
- package/src/Async.js +585 -0
- package/src/Cache.js +329 -0
- package/src/Checker.js +979 -188
- package/src/Cli.js +1034 -0
- package/src/Color.js +415 -0
- package/src/Crypto.js +707 -0
- package/src/Emitter.js +289 -0
- package/src/Env.js +555 -0
- package/src/FS.js +1483 -768
- package/src/Func.js +692 -0
- package/src/Http.js +538 -0
- package/src/Id.js +418 -0
- package/src/Logger.js +739 -307
- package/src/Num.js +849 -0
- package/src/Obj.js +904 -0
- package/src/SQLite.js +1308 -994
- package/src/Schema.js +1586 -0
- package/src/Str.js +999 -0
- package/src/Sys.js +511 -0
- package/src/Time.js +1323 -0
- package/src/Utils.js +321 -325
- package/src/errors.js +292 -0
- package/types/index.d.ts +693 -120
- package/types/src/Arr.d.ts +562 -0
- package/types/src/Arr.d.ts.map +1 -0
- package/types/src/Async.d.ts +321 -0
- package/types/src/Async.d.ts.map +1 -0
- package/types/src/Cache.d.ts +195 -0
- package/types/src/Cache.d.ts.map +1 -0
- package/types/src/Checker.d.ts +609 -81
- package/types/src/Checker.d.ts.map +1 -1
- package/types/src/Cli.d.ts +582 -0
- package/types/src/Cli.d.ts.map +1 -0
- package/types/src/Color.d.ts +294 -0
- package/types/src/Color.d.ts.map +1 -0
- package/types/src/Crypto.d.ts +534 -0
- package/types/src/Crypto.d.ts.map +1 -0
- package/types/src/Emitter.d.ts +170 -0
- package/types/src/Emitter.d.ts.map +1 -0
- package/types/src/Env.d.ts +382 -0
- package/types/src/Env.d.ts.map +1 -0
- package/types/src/FS.d.ts +709 -201
- package/types/src/FS.d.ts.map +1 -1
- package/types/src/Func.d.ts +455 -0
- package/types/src/Func.d.ts.map +1 -0
- package/types/src/Http.d.ts +374 -0
- package/types/src/Http.d.ts.map +1 -0
- package/types/src/Id.d.ts +200 -0
- package/types/src/Id.d.ts.map +1 -0
- package/types/src/Logger.d.ts +374 -24
- package/types/src/Logger.d.ts.map +1 -1
- package/types/src/Num.d.ts +578 -0
- package/types/src/Num.d.ts.map +1 -0
- package/types/src/Obj.d.ts +475 -0
- package/types/src/Obj.d.ts.map +1 -0
- package/types/src/SQLite.d.ts +647 -305
- package/types/src/SQLite.d.ts.map +1 -1
- package/types/src/Schema.d.ts +940 -0
- package/types/src/Schema.d.ts.map +1 -0
- package/types/src/Str.d.ts +696 -0
- package/types/src/Str.d.ts.map +1 -0
- package/types/src/Sys.d.ts +269 -0
- package/types/src/Sys.d.ts.map +1 -0
- package/types/src/Time.d.ts +664 -0
- package/types/src/Time.d.ts.map +1 -0
- package/types/src/Utils.d.ts +154 -102
- package/types/src/Utils.d.ts.map +1 -1
- package/types/src/errors.d.ts +292 -0
- package/types/src/errors.d.ts.map +1 -0
- package/tsconfig.json +0 -18
- package/types/index.d.ts.map +0 -1
package/src/Emitter.js
ADDED
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A small typed event emitter. Declare your events once, and your editor
|
|
5
|
+
* checks every event name and listener argument. `on()` returns a function
|
|
6
|
+
* that removes the listener, and you can `await` an event or loop over
|
|
7
|
+
* events with `for await`.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* const chat = new nc.Emitter<{ message: [text: string, from: string]; close: [] }>();
|
|
11
|
+
* const off = chat.on("message", (text, from) => console.log(`${from}: ${text}`));
|
|
12
|
+
* chat.emit("message", "hello", "ada");
|
|
13
|
+
* off();
|
|
14
|
+
* const [text] = await chat.waitFor("message", { timeout: "5s" });
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const { TimeoutError, AbortError } = require("./errors.js");
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Event names mapped to their arguments, like
|
|
21
|
+
* `{ ready: [], data: [chunk: Buffer], error: [error: Error] }`.
|
|
22
|
+
* @typedef {Record<string, any[]>} EventMap
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Options for `waitFor()`.
|
|
27
|
+
* @template {any[]} A
|
|
28
|
+
* @typedef {object} WaitForOptions
|
|
29
|
+
* @property {number | string} [timeout] Reject with a `TimeoutError` after this long.
|
|
30
|
+
* @property {AbortSignal} [signal] Reject with an `AbortError` when aborted.
|
|
31
|
+
* @property {(...args: A) => boolean} [filter] Only resolve for events that pass this test.
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* A typed event emitter. In JavaScript, give the variable a JSDoc type such
|
|
36
|
+
* as `import("@ix-xs/node-comfort").Emitter<{ ready: [port: number] }>`.
|
|
37
|
+
*
|
|
38
|
+
* @template {EventMap} [Events=EventMap]
|
|
39
|
+
*/
|
|
40
|
+
class Emitter {
|
|
41
|
+
/** @type {Map<keyof Events, Set<(...args: any[]) => unknown>>} */
|
|
42
|
+
#listeners = new Map();
|
|
43
|
+
/** @type {Set<(event: keyof Events, ...args: any[]) => unknown>} */
|
|
44
|
+
#any = new Set();
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Listens to an event.
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* const off = emitter.on("data", (chunk) => chunks.push(chunk));
|
|
51
|
+
* off(); // stop listening
|
|
52
|
+
*
|
|
53
|
+
* @template {keyof Events & string} K
|
|
54
|
+
* @param {K} event
|
|
55
|
+
* @param {(...args: Events[K]) => unknown} listener
|
|
56
|
+
* @returns {() => void} Removes the listener.
|
|
57
|
+
*/
|
|
58
|
+
on(event, listener) {
|
|
59
|
+
let set = this.#listeners.get(event);
|
|
60
|
+
if (!set) this.#listeners.set(event, (set = new Set()));
|
|
61
|
+
set.add(listener);
|
|
62
|
+
return () => this.off(event, listener);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Listens to an event once.
|
|
67
|
+
*
|
|
68
|
+
* @template {keyof Events & string} K
|
|
69
|
+
* @param {K} event
|
|
70
|
+
* @param {(...args: Events[K]) => unknown} listener
|
|
71
|
+
* @returns {() => void} Removes the listener if it hasn't run yet.
|
|
72
|
+
*/
|
|
73
|
+
once(event, listener) {
|
|
74
|
+
/** @type {(...args: Events[K]) => unknown} */
|
|
75
|
+
const wrapper = (...args) => {
|
|
76
|
+
off();
|
|
77
|
+
return listener(...args);
|
|
78
|
+
};
|
|
79
|
+
const off = this.on(event, wrapper);
|
|
80
|
+
return off;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Listens to every event; the listener gets the event name first. Handy for
|
|
85
|
+
* logging.
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* emitter.onAny((event, ...args) => nc.debug(event, args));
|
|
89
|
+
*
|
|
90
|
+
* @param {(event: keyof Events & string, ...args: any[]) => unknown} listener
|
|
91
|
+
* @returns {() => void} Removes the listener.
|
|
92
|
+
*/
|
|
93
|
+
onAny(listener) {
|
|
94
|
+
this.#any.add(/** @type {any} */ (listener));
|
|
95
|
+
return () => void this.#any.delete(/** @type {any} */ (listener));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Removes a listener, or all listeners of the event if you don't pass one.
|
|
100
|
+
*
|
|
101
|
+
* @template {keyof Events & string} K
|
|
102
|
+
* @param {K} event
|
|
103
|
+
* @param {(...args: Events[K]) => unknown} [listener]
|
|
104
|
+
* @returns {this}
|
|
105
|
+
*/
|
|
106
|
+
off(event, listener) {
|
|
107
|
+
if (!listener) this.#listeners.delete(event);
|
|
108
|
+
else {
|
|
109
|
+
const set = this.#listeners.get(event);
|
|
110
|
+
set?.delete(listener);
|
|
111
|
+
if (set && !set.size) this.#listeners.delete(event);
|
|
112
|
+
}
|
|
113
|
+
return this;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Calls the event's listeners right away, in the order they were added.
|
|
118
|
+
* If one throws, the error propagates, as with Node's `EventEmitter`.
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* emitter.emit("message", "hello", "ada");
|
|
122
|
+
*
|
|
123
|
+
* @template {keyof Events & string} K
|
|
124
|
+
* @param {K} event
|
|
125
|
+
* @param {Events[K]} args
|
|
126
|
+
* @returns {boolean} `true` if anyone was listening.
|
|
127
|
+
*/
|
|
128
|
+
emit(event, ...args) {
|
|
129
|
+
const listeners = [...(this.#listeners.get(event) ?? [])];
|
|
130
|
+
for (const listener of listeners) listener(...args);
|
|
131
|
+
for (const listener of [...this.#any]) listener(event, ...args);
|
|
132
|
+
return listeners.length > 0 || this.#any.size > 0;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Calls the listeners one at a time, waiting for each, and resolves with
|
|
137
|
+
* what they returned. Good for hooks that may be async.
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* await hooks.emitAsync("beforeSave", record);
|
|
141
|
+
*
|
|
142
|
+
* @template {keyof Events & string} K
|
|
143
|
+
* @param {K} event
|
|
144
|
+
* @param {Events[K]} args
|
|
145
|
+
* @returns {Promise<unknown[]>}
|
|
146
|
+
*/
|
|
147
|
+
async emitAsync(event, ...args) {
|
|
148
|
+
const results = [];
|
|
149
|
+
for (const listener of [...(this.#listeners.get(event) ?? [])]) results.push(await listener(...args));
|
|
150
|
+
for (const listener of [...this.#any]) await listener(event, ...args);
|
|
151
|
+
return results;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Waits for the next time the event fires and resolves with its arguments.
|
|
156
|
+
*
|
|
157
|
+
* @example
|
|
158
|
+
* const [code] = await child.waitFor("exit", { timeout: "30s" });
|
|
159
|
+
* const [text] = await chat.waitFor("message", { filter: (text, from) => from === "ada" });
|
|
160
|
+
*
|
|
161
|
+
* @template {keyof Events & string} K
|
|
162
|
+
* @param {K} event
|
|
163
|
+
* @param {WaitForOptions<Events[K]>} [options]
|
|
164
|
+
* @returns {Promise<Events[K]>}
|
|
165
|
+
* @throws {TimeoutError}
|
|
166
|
+
* @throws {AbortError}
|
|
167
|
+
*/
|
|
168
|
+
waitFor(event, options = {}) {
|
|
169
|
+
return new Promise((resolve, reject) => {
|
|
170
|
+
/** @type {NodeJS.Timeout | undefined} */
|
|
171
|
+
let timer;
|
|
172
|
+
const cleanup = () => {
|
|
173
|
+
clearTimeout(timer);
|
|
174
|
+
off();
|
|
175
|
+
options.signal?.removeEventListener("abort", onAbort);
|
|
176
|
+
};
|
|
177
|
+
const onAbort = () => {
|
|
178
|
+
cleanup();
|
|
179
|
+
reject(new AbortError(`Stopped waiting for "${event}"`, { cause: options.signal?.reason }));
|
|
180
|
+
};
|
|
181
|
+
const off = this.on(event, /** @type {any} */ ((/** @type {Events[K]} */ ...args) => {
|
|
182
|
+
if (options.filter && !options.filter(...args)) return;
|
|
183
|
+
cleanup();
|
|
184
|
+
resolve(args);
|
|
185
|
+
}));
|
|
186
|
+
if (options.signal?.aborted) return onAbort();
|
|
187
|
+
options.signal?.addEventListener("abort", onAbort, { once: true });
|
|
188
|
+
if (options.timeout !== undefined) {
|
|
189
|
+
const ms = typeof options.timeout === "number" ? options.timeout : require("./Time.js").parseDuration(options.timeout) ?? 0;
|
|
190
|
+
timer = setTimeout(() => {
|
|
191
|
+
cleanup();
|
|
192
|
+
reject(new TimeoutError(`Timed out waiting for "${event}" after ${ms}ms`, { timeout: ms }));
|
|
193
|
+
}, ms);
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Loops over an event with `for await`. Events that fire while your loop
|
|
200
|
+
* body runs are queued, and leaving the loop removes the listener.
|
|
201
|
+
*
|
|
202
|
+
* @example
|
|
203
|
+
* for await (const [job] of queue.iterate("job")) {
|
|
204
|
+
* await process(job);
|
|
205
|
+
* }
|
|
206
|
+
*
|
|
207
|
+
* @template {keyof Events & string} K
|
|
208
|
+
* @param {K} event
|
|
209
|
+
* @param {{ signal?: AbortSignal }} [options] Ends the loop when aborted.
|
|
210
|
+
* @returns {AsyncIterableIterator<Events[K]>}
|
|
211
|
+
*/
|
|
212
|
+
iterate(event, options = {}) {
|
|
213
|
+
/** @type {Array<Events[K]>} */
|
|
214
|
+
const buffer = [];
|
|
215
|
+
/** @type {Array<(result: IteratorResult<Events[K]>) => void>} */
|
|
216
|
+
const waiting = [];
|
|
217
|
+
let done = false;
|
|
218
|
+
const finish = () => {
|
|
219
|
+
if (done) return;
|
|
220
|
+
done = true;
|
|
221
|
+
off();
|
|
222
|
+
options.signal?.removeEventListener("abort", finish);
|
|
223
|
+
for (const resolve of waiting.splice(0)) resolve({ value: undefined, done: true });
|
|
224
|
+
};
|
|
225
|
+
const off = this.on(event, /** @type {any} */ ((/** @type {Events[K]} */ ...args) => {
|
|
226
|
+
const next = waiting.shift();
|
|
227
|
+
if (next) next({ value: args, done: false });
|
|
228
|
+
else buffer.push(args);
|
|
229
|
+
}));
|
|
230
|
+
if (options.signal?.aborted) finish();
|
|
231
|
+
options.signal?.addEventListener("abort", finish, { once: true });
|
|
232
|
+
/** @type {AsyncIterableIterator<Events[K]>} */
|
|
233
|
+
const iterator = {
|
|
234
|
+
next: () => {
|
|
235
|
+
if (buffer.length) return Promise.resolve({ value: /** @type {Events[K]} */ (buffer.shift()), done: false });
|
|
236
|
+
if (done) return Promise.resolve({ value: undefined, done: true });
|
|
237
|
+
return new Promise((resolve) => waiting.push(resolve));
|
|
238
|
+
},
|
|
239
|
+
return: () => {
|
|
240
|
+
finish();
|
|
241
|
+
return Promise.resolve({ value: undefined, done: true });
|
|
242
|
+
},
|
|
243
|
+
[Symbol.asyncIterator]() {
|
|
244
|
+
return iterator;
|
|
245
|
+
},
|
|
246
|
+
};
|
|
247
|
+
return iterator;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* How many listeners an event has, or all events together. `onAny`
|
|
252
|
+
* listeners aren't counted.
|
|
253
|
+
*
|
|
254
|
+
* @param {keyof Events & string} [event]
|
|
255
|
+
* @returns {number}
|
|
256
|
+
*/
|
|
257
|
+
listenerCount(event) {
|
|
258
|
+
if (event !== undefined) return this.#listeners.get(event)?.size ?? 0;
|
|
259
|
+
let total = 0;
|
|
260
|
+
for (const set of this.#listeners.values()) total += set.size;
|
|
261
|
+
return total;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* The events that have listeners.
|
|
266
|
+
*
|
|
267
|
+
* @returns {Array<keyof Events & string>}
|
|
268
|
+
*/
|
|
269
|
+
eventNames() {
|
|
270
|
+
return /** @type {Array<keyof Events & string>} */ ([...this.#listeners.keys()]);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Removes all listeners of an event, or everything if you don't name one.
|
|
275
|
+
*
|
|
276
|
+
* @param {keyof Events & string} [event]
|
|
277
|
+
* @returns {this}
|
|
278
|
+
*/
|
|
279
|
+
clear(event) {
|
|
280
|
+
if (event !== undefined) this.#listeners.delete(event);
|
|
281
|
+
else {
|
|
282
|
+
this.#listeners.clear();
|
|
283
|
+
this.#any.clear();
|
|
284
|
+
}
|
|
285
|
+
return this;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
module.exports = Emitter;
|