@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.
Files changed (77) hide show
  1. package/CHANGELOG.md +190 -0
  2. package/LICENSE +21 -0
  3. package/README.md +210 -748
  4. package/index.js +84 -13
  5. package/package.json +201 -21
  6. package/src/Arr.js +901 -0
  7. package/src/Async.js +585 -0
  8. package/src/Cache.js +329 -0
  9. package/src/Checker.js +979 -188
  10. package/src/Cli.js +1034 -0
  11. package/src/Color.js +415 -0
  12. package/src/Crypto.js +707 -0
  13. package/src/Emitter.js +289 -0
  14. package/src/Env.js +555 -0
  15. package/src/FS.js +1483 -768
  16. package/src/Func.js +692 -0
  17. package/src/Http.js +538 -0
  18. package/src/Id.js +418 -0
  19. package/src/Logger.js +739 -307
  20. package/src/Num.js +849 -0
  21. package/src/Obj.js +904 -0
  22. package/src/SQLite.js +1308 -994
  23. package/src/Schema.js +1586 -0
  24. package/src/Str.js +999 -0
  25. package/src/Sys.js +511 -0
  26. package/src/Time.js +1323 -0
  27. package/src/Utils.js +321 -325
  28. package/src/errors.js +292 -0
  29. package/types/index.d.ts +693 -120
  30. package/types/src/Arr.d.ts +562 -0
  31. package/types/src/Arr.d.ts.map +1 -0
  32. package/types/src/Async.d.ts +321 -0
  33. package/types/src/Async.d.ts.map +1 -0
  34. package/types/src/Cache.d.ts +195 -0
  35. package/types/src/Cache.d.ts.map +1 -0
  36. package/types/src/Checker.d.ts +609 -81
  37. package/types/src/Checker.d.ts.map +1 -1
  38. package/types/src/Cli.d.ts +582 -0
  39. package/types/src/Cli.d.ts.map +1 -0
  40. package/types/src/Color.d.ts +294 -0
  41. package/types/src/Color.d.ts.map +1 -0
  42. package/types/src/Crypto.d.ts +534 -0
  43. package/types/src/Crypto.d.ts.map +1 -0
  44. package/types/src/Emitter.d.ts +170 -0
  45. package/types/src/Emitter.d.ts.map +1 -0
  46. package/types/src/Env.d.ts +382 -0
  47. package/types/src/Env.d.ts.map +1 -0
  48. package/types/src/FS.d.ts +709 -201
  49. package/types/src/FS.d.ts.map +1 -1
  50. package/types/src/Func.d.ts +455 -0
  51. package/types/src/Func.d.ts.map +1 -0
  52. package/types/src/Http.d.ts +374 -0
  53. package/types/src/Http.d.ts.map +1 -0
  54. package/types/src/Id.d.ts +200 -0
  55. package/types/src/Id.d.ts.map +1 -0
  56. package/types/src/Logger.d.ts +374 -24
  57. package/types/src/Logger.d.ts.map +1 -1
  58. package/types/src/Num.d.ts +578 -0
  59. package/types/src/Num.d.ts.map +1 -0
  60. package/types/src/Obj.d.ts +475 -0
  61. package/types/src/Obj.d.ts.map +1 -0
  62. package/types/src/SQLite.d.ts +647 -305
  63. package/types/src/SQLite.d.ts.map +1 -1
  64. package/types/src/Schema.d.ts +940 -0
  65. package/types/src/Schema.d.ts.map +1 -0
  66. package/types/src/Str.d.ts +696 -0
  67. package/types/src/Str.d.ts.map +1 -0
  68. package/types/src/Sys.d.ts +269 -0
  69. package/types/src/Sys.d.ts.map +1 -0
  70. package/types/src/Time.d.ts +664 -0
  71. package/types/src/Time.d.ts.map +1 -0
  72. package/types/src/Utils.d.ts +154 -102
  73. package/types/src/Utils.d.ts.map +1 -1
  74. package/types/src/errors.d.ts +292 -0
  75. package/types/src/errors.d.ts.map +1 -0
  76. package/tsconfig.json +0 -18
  77. package/types/index.d.ts.map +0 -1
package/src/Id.js ADDED
@@ -0,0 +1,418 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Unique ids: UUID v4 and v7, ULID, nanoid-style ids, Snowflakes, tokens
5
+ * and short codes. All of them use the secure random generator of
6
+ * `node:crypto`, with no bias.
7
+ *
8
+ * @example
9
+ * id.uuidv7(); // "0190a5c4-7c1e-7b2a-9f3e-1c2d3e4f5a6b", sorts by creation time
10
+ * id.nano(); // "V1StGXR8_Z5jdHi6B-myT"
11
+ * id.code(); // "K7QF9X"
12
+ * id.token(32); // for API keys and reset links
13
+ */
14
+
15
+ /** @type {typeof import("node:crypto") | undefined} */
16
+ let _nodeCrypto;
17
+ const _crypto = () => (_nodeCrypto ??= require("node:crypto"));
18
+ const nodeCrypto = require("./Crypto.js");
19
+
20
+ /**
21
+ * Encodings for `token()`.
22
+ * @typedef {"hex" | "base64" | "base64url" | "base58" | "base32"} TokenEncoding
23
+ */
24
+
25
+ /**
26
+ * Options for `snowflake()` and `parseSnowflake()`.
27
+ * @typedef {object} SnowflakeOptions
28
+ * @property {number} [epoch] Start of time, in ms. Defaults to Discord's (2015-01-01). Twitter uses `1288834974657`.
29
+ * @property {number} [workerId] 0 to 31. Defaults to `0`.
30
+ * @property {number} [processId] 0 to 31. Defaults to `process.pid % 32`.
31
+ */
32
+
33
+ /**
34
+ * A decoded Snowflake.
35
+ * @typedef {object} ParsedSnowflake
36
+ * @property {Date} date When it was created.
37
+ * @property {number} timestamp When it was created, in ms.
38
+ * @property {number} workerId
39
+ * @property {number} processId
40
+ * @property {number} increment Sequence number within the millisecond, 0 to 4095.
41
+ */
42
+
43
+ const NANO_ALPHABET = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
44
+ const BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
45
+ const CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
46
+ const DISCORD_EPOCH = 1420070400000;
47
+
48
+ /** @type {Record<string, number>} */
49
+ const _counters = {};
50
+
51
+ /**
52
+ * Secure random characters, with rejection sampling so there's no bias.
53
+ * @param {string} alphabet
54
+ * @param {number} size
55
+ * @returns {string}
56
+ */
57
+ const _random = (alphabet, size) => {
58
+ const len = alphabet.length;
59
+ if (len < 2 || len > 256) throw new RangeError("The alphabet must contain between 2 and 256 characters");
60
+ const mask = (2 << Math.floor(Math.log2(len - 1))) - 1;
61
+ const step = Math.ceil((1.6 * mask * size) / len);
62
+ let out = "";
63
+ while (out.length < size) {
64
+ const bytes = _crypto().randomBytes(step);
65
+ for (let i = 0; i < step && out.length < size; i++) {
66
+ const index = bytes[i] & mask;
67
+ if (index < len) out += alphabet[index];
68
+ }
69
+ }
70
+ return out;
71
+ };
72
+
73
+ /**
74
+ * @param {Buffer} buffer
75
+ * @returns {string}
76
+ */
77
+ const _base58 = (buffer) => {
78
+ if (!buffer.length) return "";
79
+ const digits = [0];
80
+ for (const byte of buffer) {
81
+ let carry = byte;
82
+ for (let i = 0; i < digits.length; i++) {
83
+ carry += digits[i] << 8;
84
+ digits[i] = carry % 58;
85
+ carry = Math.floor(carry / 58);
86
+ }
87
+ while (carry > 0) {
88
+ digits.push(carry % 58);
89
+ carry = Math.floor(carry / 58);
90
+ }
91
+ }
92
+ let out = "";
93
+ for (let i = 0; i < buffer.length - 1 && buffer[i] === 0; i++) out += BASE58_ALPHABET[0];
94
+ for (let i = digits.length - 1; i >= 0; i--) out += BASE58_ALPHABET[digits[i]];
95
+ return out;
96
+ };
97
+
98
+ /**
99
+ * @param {Buffer} buffer
100
+ * @returns {string}
101
+ */
102
+ const _base32 = (buffer) => {
103
+ let bits = 0;
104
+ let value = 0;
105
+ let out = "";
106
+ for (const byte of buffer) {
107
+ value = (value << 8) | byte;
108
+ bits += 8;
109
+ while (bits >= 5) {
110
+ out += CROCKFORD[(value >>> (bits - 5)) & 31];
111
+ bits -= 5;
112
+ }
113
+ }
114
+ if (bits > 0) out += CROCKFORD[(value << (5 - bits)) & 31];
115
+ return out;
116
+ };
117
+
118
+ /**
119
+ * A random UUID (version 4). For ids that sort by date, see `uuidv7()`.
120
+ *
121
+ * @example
122
+ * id.uuid(); // "3f2504e0-4f89-41d3-9a0c-0305e82c3301"
123
+ *
124
+ * @returns {string}
125
+ */
126
+ function uuid() {
127
+ return _crypto().randomUUID();
128
+ }
129
+
130
+ let _v7LastMs = 0;
131
+ let _v7Counter = 0;
132
+
133
+ /**
134
+ * A UUID v7: it starts with a timestamp, so ids sort by creation time.
135
+ * That keeps database indexes compact, which makes them great primary
136
+ * keys. Ids from the same process always increase, even within a
137
+ * millisecond.
138
+ *
139
+ * @example
140
+ * id.uuidv7(); // "0190a5c4-7c1e-7b2a-9f3e-1c2d3e4f5a6b"
141
+ * id.timestamp(id.uuidv7()); // when it was created
142
+ *
143
+ * @returns {string}
144
+ */
145
+ function uuidv7() {
146
+ let now = Date.now();
147
+ if (now <= _v7LastMs) {
148
+ _v7Counter++;
149
+ if (_v7Counter > 0xfff) {
150
+ _v7Counter = 0;
151
+ _v7LastMs++;
152
+ }
153
+ now = _v7LastMs;
154
+ } else {
155
+ _v7LastMs = now;
156
+ _v7Counter = _crypto().randomInt(0, 0x800);
157
+ }
158
+ const bytes = _crypto().randomBytes(16);
159
+ bytes.writeUIntBE(now, 0, 6);
160
+ bytes[6] = 0x70 | ((_v7Counter >> 8) & 0x0f);
161
+ bytes[7] = _v7Counter & 0xff;
162
+ bytes[8] = 0x80 | (bytes[8] & 0x3f);
163
+ const hex = bytes.toString("hex");
164
+ return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
165
+ }
166
+
167
+ let _ulidLastMs = -1;
168
+ /** @type {number[]} */
169
+ let _ulidLastRandom = [];
170
+
171
+ /**
172
+ * A ULID: 26 URL-safe characters that sort by creation time. Ids made in
173
+ * the same millisecond still increase.
174
+ *
175
+ * @example
176
+ * id.ulid(); // "01HQ3Z8K9V3N5W7Y2XJ4T6R8PB"
177
+ * id.ulid(Date.UTC(2024, 0, 1)); // for a given time
178
+ *
179
+ * @param {number} [time] Timestamp to encode, in ms. Defaults to now.
180
+ * @returns {string}
181
+ */
182
+ function ulid(time) {
183
+ const now = time ?? Date.now();
184
+ /** @type {number[]} */
185
+ let random;
186
+ if (time === undefined && now === _ulidLastMs) {
187
+ random = [..._ulidLastRandom];
188
+ let i = random.length - 1;
189
+ while (i >= 0 && random[i] === 31) random[i--] = 0;
190
+ if (i < 0) throw new RangeError("ULID overflow: too many ids in the same millisecond");
191
+ random[i]++;
192
+ } else {
193
+ random = [..._crypto().randomBytes(16)].map((b) => b & 31);
194
+ }
195
+ if (time === undefined) {
196
+ _ulidLastMs = now;
197
+ _ulidLastRandom = random;
198
+ }
199
+ let t = now;
200
+ let head = "";
201
+ for (let i = 0; i < 10; i++) {
202
+ head = CROCKFORD[t % 32] + head;
203
+ t = Math.floor(t / 32);
204
+ }
205
+ return head + random.map((v) => CROCKFORD[v]).join("");
206
+ }
207
+
208
+ /**
209
+ * A short, URL-safe random id, like `nanoid`. At 21 characters, collisions
210
+ * are as unlikely as with a UUID.
211
+ *
212
+ * @example
213
+ * id.nano(); // "V1StGXR8_Z5jdHi6B-myT"
214
+ * id.nano(10); // "IRFa-VaY2b"
215
+ * id.nano(8, "0123456789abcdef"); // "4f90d13a"
216
+ *
217
+ * @param {number} [size=21]
218
+ * @param {string} [alphabet] 2 to 256 characters. Defaults to letters, digits, `_` and `-`.
219
+ * @returns {string}
220
+ */
221
+ function nano(size = 21, alphabet = NANO_ALPHABET) {
222
+ return _random(alphabet, size);
223
+ }
224
+
225
+ /**
226
+ * Makes an id generator with your own alphabet and length.
227
+ *
228
+ * @example
229
+ * const orderId = id.customAlphabet("0123456789ABCDEF", 12);
230
+ * orderId(); // "4F1A09C2BB7E"
231
+ *
232
+ * @param {string} alphabet
233
+ * @param {number} [size=21]
234
+ * @returns {(size?: number) => string}
235
+ */
236
+ function customAlphabet(alphabet, size = 21) {
237
+ _random(alphabet, 1);
238
+ return (length = size) => _random(alphabet, length);
239
+ }
240
+
241
+ /**
242
+ * A random token for API keys, session ids, reset links...
243
+ *
244
+ * @example
245
+ * id.token(); // 64 hex characters
246
+ * id.token(16, "base64url"); // 22 URL-safe characters
247
+ * id.token(16, "base58"); // no look-alike characters
248
+ *
249
+ * @param {number} [bytes=32] How much randomness, in bytes.
250
+ * @param {TokenEncoding} [encoding="hex"]
251
+ * @returns {string}
252
+ */
253
+ function token(bytes = 32, encoding = "hex") {
254
+ const buffer = _crypto().randomBytes(bytes);
255
+ if (encoding === "base58") return _base58(buffer);
256
+ if (encoding === "base32") return _base32(buffer);
257
+ return buffer.toString(encoding);
258
+ }
259
+
260
+ /**
261
+ * A short code that's easy to read aloud and type: no `0/O` or `1/I/L`
262
+ * mix-ups. For invites, coupons and verification codes.
263
+ *
264
+ * @example
265
+ * id.code(); // "K7QF9X"
266
+ * id.code(4, "0123456789"); // "3920"
267
+ *
268
+ * @param {number} [length=6]
269
+ * @param {string} [alphabet="ABCDEFGHJKMNPQRSTUVWXYZ23456789"]
270
+ * @returns {string}
271
+ */
272
+ function code(length = 6, alphabet = "ABCDEFGHJKMNPQRSTUVWXYZ23456789") {
273
+ return _random(alphabet, length);
274
+ }
275
+
276
+ let _snowflakeLastMs = 0;
277
+ let _snowflakeIncrement = 0;
278
+
279
+ /**
280
+ * A Snowflake id, the 64-bit format used by Discord and Twitter. Returned
281
+ * as a string because it doesn't fit in a JavaScript number.
282
+ *
283
+ * @example
284
+ * id.snowflake(); // "1215361932810932224"
285
+ *
286
+ * @param {SnowflakeOptions} [options]
287
+ * @returns {string}
288
+ */
289
+ function snowflake(options = {}) {
290
+ const epoch = options.epoch ?? DISCORD_EPOCH;
291
+ let now = Date.now();
292
+ if (now <= _snowflakeLastMs) {
293
+ _snowflakeIncrement = (_snowflakeIncrement + 1) & 0xfff;
294
+ if (_snowflakeIncrement === 0) _snowflakeLastMs++;
295
+ now = _snowflakeLastMs;
296
+ } else {
297
+ _snowflakeLastMs = now;
298
+ _snowflakeIncrement = 0;
299
+ }
300
+ const worker = BigInt((options.workerId ?? 0) & 31);
301
+ const proc = BigInt((options.processId ?? process.pid) & 31);
302
+ return ((BigInt(now - epoch) << 22n) | (worker << 17n) | (proc << 12n) | BigInt(_snowflakeIncrement)).toString();
303
+ }
304
+
305
+ /**
306
+ * Decodes a Snowflake. With the default epoch, it reads Discord ids.
307
+ *
308
+ * @example
309
+ * id.parseSnowflake("175928847299117063").date; // 2016-04-30T11:18:25.796Z
310
+ *
311
+ * @param {string | bigint} id
312
+ * @param {SnowflakeOptions} [options] Use the epoch it was created with.
313
+ * @returns {ParsedSnowflake}
314
+ */
315
+ function parseSnowflake(id, options = {}) {
316
+ const value = BigInt(id);
317
+ const timestamp = Number(value >> 22n) + (options.epoch ?? DISCORD_EPOCH);
318
+ return {
319
+ date: new Date(timestamp),
320
+ timestamp,
321
+ workerId: Number((value >> 17n) & 31n),
322
+ processId: Number((value >> 12n) & 31n),
323
+ increment: Number(value & 0xfffn),
324
+ };
325
+ }
326
+
327
+ /**
328
+ * When a ULID or a UUID v7 was created.
329
+ *
330
+ * @example
331
+ * id.timestamp(id.ulid()); // now
332
+ * id.timestamp(id.uuid()); // undefined, v4 has no time in it
333
+ *
334
+ * @param {string} value
335
+ * @returns {Date | undefined}
336
+ */
337
+ function timestamp(value) {
338
+ const s = String(value);
339
+ if (/^[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(s)) {
340
+ return new Date(parseInt(s.replace(/-/g, "").slice(0, 12), 16));
341
+ }
342
+ if (/^[0-7][0-9A-HJKMNP-TV-Z]{25}$/i.test(s)) {
343
+ let ms = 0;
344
+ for (const char of s.slice(0, 10).toUpperCase()) ms = ms * 32 + CROCKFORD.indexOf(char);
345
+ return new Date(ms);
346
+ }
347
+ return undefined;
348
+ }
349
+
350
+ /**
351
+ * Readable sequential ids like `"user-1"`, `"user-2"`, counted per prefix.
352
+ * Only unique within the current process; handy in logs and tests.
353
+ *
354
+ * @example
355
+ * id.seq("user"); // "user-1"
356
+ * id.seq("user"); // "user-2"
357
+ *
358
+ * @param {string} [prefix=""]
359
+ * @returns {string}
360
+ */
361
+ function seq(prefix = "") {
362
+ _counters[prefix] = (_counters[prefix] ?? 0) + 1;
363
+ return prefix ? `${prefix}-${_counters[prefix]}` : String(_counters[prefix]);
364
+ }
365
+
366
+ /**
367
+ * Hashes a value.
368
+ *
369
+ * @deprecated Use `nc.crypto.hash()`. This alias goes away in 3.0.
370
+ * @param {string | Buffer | Uint8Array} value
371
+ * @param {import("./Crypto").HashOptions} [options]
372
+ * @returns {string}
373
+ */
374
+ function hash(value, options) {
375
+ return nodeCrypto.hash(value, options);
376
+ }
377
+
378
+ /**
379
+ * Signs a value with HMAC.
380
+ *
381
+ * @deprecated Use `nc.crypto.hmac()`. This alias goes away in 3.0.
382
+ * @param {string | Buffer | Uint8Array} value
383
+ * @param {string | Buffer | Uint8Array} secret
384
+ * @param {import("./Crypto").HashOptions} [options]
385
+ * @returns {string}
386
+ */
387
+ function hmac(value, secret, options) {
388
+ return nodeCrypto.hmac(value, secret, options);
389
+ }
390
+
391
+ /**
392
+ * Compares two secrets in constant time.
393
+ *
394
+ * @deprecated Use `nc.crypto.safeEqual()`. This alias goes away in 3.0.
395
+ * @param {string | Buffer | Uint8Array} a
396
+ * @param {string | Buffer | Uint8Array} b
397
+ * @returns {boolean}
398
+ */
399
+ function safeEqual(a, b) {
400
+ return nodeCrypto.safeEqual(a, b);
401
+ }
402
+
403
+ module.exports = {
404
+ uuid,
405
+ uuidv7,
406
+ ulid,
407
+ nano,
408
+ customAlphabet,
409
+ token,
410
+ code,
411
+ snowflake,
412
+ parseSnowflake,
413
+ timestamp,
414
+ seq,
415
+ hash,
416
+ hmac,
417
+ safeEqual,
418
+ };