@ix-xs/node-comfort 1.2.0 → 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 (75) hide show
  1. package/CHANGELOG.md +190 -0
  2. package/README.md +136 -549
  3. package/index.js +80 -35
  4. package/package.json +160 -33
  5. package/src/Arr.js +887 -419
  6. package/src/Async.js +585 -0
  7. package/src/Cache.js +329 -0
  8. package/src/Checker.js +971 -539
  9. package/src/Cli.js +1034 -0
  10. package/src/Color.js +415 -0
  11. package/src/Crypto.js +707 -0
  12. package/src/Emitter.js +289 -0
  13. package/src/Env.js +555 -0
  14. package/src/FS.js +1483 -894
  15. package/src/Func.js +670 -353
  16. package/src/Http.js +538 -0
  17. package/src/Id.js +384 -201
  18. package/src/Logger.js +739 -378
  19. package/src/Num.js +844 -361
  20. package/src/Obj.js +855 -372
  21. package/src/SQLite.js +1308 -1005
  22. package/src/Schema.js +1586 -0
  23. package/src/Str.js +983 -436
  24. package/src/Sys.js +511 -0
  25. package/src/Time.js +1260 -422
  26. package/src/Utils.js +321 -325
  27. package/src/errors.js +292 -0
  28. package/types/index.d.ts +693 -526
  29. package/types/src/Arr.d.ts +455 -145
  30. package/types/src/Arr.d.ts.map +1 -1
  31. package/types/src/Async.d.ts +321 -0
  32. package/types/src/Async.d.ts.map +1 -0
  33. package/types/src/Cache.d.ts +195 -0
  34. package/types/src/Cache.d.ts.map +1 -0
  35. package/types/src/Checker.d.ts +501 -235
  36. package/types/src/Checker.d.ts.map +1 -1
  37. package/types/src/Cli.d.ts +582 -0
  38. package/types/src/Cli.d.ts.map +1 -0
  39. package/types/src/Color.d.ts +294 -0
  40. package/types/src/Color.d.ts.map +1 -0
  41. package/types/src/Crypto.d.ts +534 -0
  42. package/types/src/Crypto.d.ts.map +1 -0
  43. package/types/src/Emitter.d.ts +170 -0
  44. package/types/src/Emitter.d.ts.map +1 -0
  45. package/types/src/Env.d.ts +382 -0
  46. package/types/src/Env.d.ts.map +1 -0
  47. package/types/src/FS.d.ts +686 -238
  48. package/types/src/FS.d.ts.map +1 -1
  49. package/types/src/Func.d.ts +380 -135
  50. package/types/src/Func.d.ts.map +1 -1
  51. package/types/src/Http.d.ts +374 -0
  52. package/types/src/Http.d.ts.map +1 -0
  53. package/types/src/Id.d.ts +149 -68
  54. package/types/src/Id.d.ts.map +1 -1
  55. package/types/src/Logger.d.ts +363 -46
  56. package/types/src/Logger.d.ts.map +1 -1
  57. package/types/src/Num.d.ts +473 -137
  58. package/types/src/Num.d.ts.map +1 -1
  59. package/types/src/Obj.d.ts +390 -104
  60. package/types/src/Obj.d.ts.map +1 -1
  61. package/types/src/SQLite.d.ts +646 -309
  62. package/types/src/SQLite.d.ts.map +1 -1
  63. package/types/src/Schema.d.ts +940 -0
  64. package/types/src/Schema.d.ts.map +1 -0
  65. package/types/src/Str.d.ts +569 -166
  66. package/types/src/Str.d.ts.map +1 -1
  67. package/types/src/Sys.d.ts +269 -0
  68. package/types/src/Sys.d.ts.map +1 -0
  69. package/types/src/Time.d.ts +663 -204
  70. package/types/src/Time.d.ts.map +1 -1
  71. package/types/src/Utils.d.ts +154 -102
  72. package/types/src/Utils.d.ts.map +1 -1
  73. package/types/src/errors.d.ts +292 -0
  74. package/types/src/errors.d.ts.map +1 -0
  75. package/types/index.d.ts.map +0 -1
package/src/Id.js CHANGED
@@ -1,40 +1,81 @@
1
- const crypto = require("node:crypto");
1
+ "use strict";
2
2
 
3
3
  /**
4
- * ID, token & hashing utilities.
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.
5
7
  *
6
- * Cryptographically-secure identifiers (UUID v4, ULID, nanoid-style tokens),
7
- * hashing helpers and constant-time comparison • all built on Node's native
8
- * `node:crypto`, with zero external dependencies.
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
9
13
  */
10
14
 
11
- const NANOID_ALPHABET = "useandom-26T198340PX75pxJACKVERYMINDBUSHWOLF_GQZbfghjklqvwyzrict";
12
- const BASE58_ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
13
- const ULID_ALPHABET = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"; // Crockford base32
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
+ */
14
24
 
15
25
  /**
16
- * Per-prefix counters for {@link module:Id.seq}.
17
- * @private
18
- * @type {Record<string, number>}
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`.
19
31
  */
20
- const _seqCounters = {};
21
32
 
22
33
  /**
23
- * Fills a buffer with cryptographically secure random bytes.
24
- * @private
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
25
54
  * @param {number} size
26
- * @returns {Buffer}
55
+ * @returns {string}
27
56
  */
28
- const _randomBytes = (size) => crypto.randomBytes(size);
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
+ };
29
72
 
30
73
  /**
31
- * Encodes a buffer as Base58 using the Bitcoin alphabet.
32
- * @private
33
- * @param {Buffer} buffer - Bytes to encode.
34
- * @returns {string} The Base58 string.
74
+ * @param {Buffer} buffer
75
+ * @returns {string}
35
76
  */
36
- const _toBase58 = (buffer) => {
37
- if (buffer.length === 0) return "";
77
+ const _base58 = (buffer) => {
78
+ if (!buffer.length) return "";
38
79
  const digits = [0];
39
80
  for (const byte of buffer) {
40
81
  let carry = byte;
@@ -48,188 +89,330 @@ const _toBase58 = (buffer) => {
48
89
  carry = Math.floor(carry / 58);
49
90
  }
50
91
  }
51
- let str = "";
52
- for (let i = 0; buffer[i] === 0 && i < buffer.length - 1; i++) str += BASE58_ALPHABET[0];
53
- for (let i = digits.length - 1; i >= 0; i--) str += BASE58_ALPHABET[digits[i]];
54
- return str;
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;
55
96
  };
56
97
 
57
- module.exports = {
58
- /**
59
- * Generates a random UUID v4.
60
- *
61
- * @example
62
- * nodeComfort.id.uuid(); // "3f2504e0-4f89-41d3-9a0c-0305e82c3301"
63
- *
64
- * @returns {string} A UUID v4 string.
65
- */
66
- uuid() {
67
- return crypto.randomUUID();
68
- },
69
-
70
- /**
71
- * Generates a URL-safe, collision-resistant ID (nanoid-compatible alphabet).
72
- *
73
- * Uses secure randomness with unbiased rejection sampling.
74
- *
75
- * @example
76
- * nodeComfort.id.nano(); // "V1StGXR8_Z5jdHi6B-myT"
77
- * nodeComfort.id.nano(10); // "IRFa-VaY2b"
78
- *
79
- * @param {number} [size=21] - Desired length.
80
- * @param {string} [alphabet] - Custom alphabet.
81
- * @returns {string} The generated ID.
82
- */
83
- nano(size = 21, alphabet = NANOID_ALPHABET) {
84
- const len = alphabet.length;
85
- // Bitmask for unbiased sampling.
86
- const mask = (2 << Math.floor(Math.log2(len - 1))) - 1;
87
- const step = Math.ceil((1.6 * mask * size) / len);
88
- let id = "";
89
- while (id.length < size) {
90
- const bytes = _randomBytes(step);
91
- for (let i = 0; i < step && id.length < size; i++) {
92
- const index = bytes[i] & mask;
93
- if (index < len) id += alphabet[index];
94
- }
95
- }
96
- return id;
97
- },
98
-
99
- /**
100
- * Generates a ULID • a lexicographically-sortable, timestamp-prefixed ID.
101
- *
102
- * ULIDs sort by creation time and are a great primary-key alternative to UUIDs.
103
- *
104
- * @example
105
- * nodeComfort.id.ulid(); // "01ARZ3NDEKTSV4RRFFQ69G5FAV"
106
- *
107
- * @param {number} [time=Date.now()] - Timestamp in ms to encode.
108
- * @returns {string} A 26-character ULID.
109
- */
110
- ulid(time = Date.now()) {
111
- let timeChars = "";
112
- let t = time;
113
- for (let i = 0; i < 10; i++) {
114
- timeChars = ULID_ALPHABET[t % 32] + timeChars;
115
- t = Math.floor(t / 32);
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;
116
112
  }
113
+ }
114
+ if (bits > 0) out += CROCKFORD[(value << (5 - bits)) & 31];
115
+ return out;
116
+ };
117
117
 
118
- const bytes = _randomBytes(16);
119
- let randChars = "";
120
- for (let i = 0; i < 16; i++) {
121
- randChars += ULID_ALPHABET[bytes[i] % 32];
122
- }
123
- return timeChars + randChars;
124
- },
125
-
126
- /**
127
- * Generates a cryptographically-secure random token.
128
- *
129
- * @example
130
- * nodeComfort.id.token(); // 32-byte hex string
131
- * nodeComfort.id.token(16, "base64url"); // 16 bytes, URL-safe base64
132
- *
133
- * @param {number} [bytes=32] - Number of random bytes.
134
- * @param {"hex"|"base64"|"base64url"|"base58"} [encoding="hex"] - Output encoding.
135
- * @returns {string} The token.
136
- */
137
- token(bytes = 32, encoding = "hex") {
138
- const buffer = _randomBytes(bytes);
139
- if (encoding === "base58") return _toBase58(buffer);
140
- return buffer.toString(encoding);
141
- },
142
-
143
- /**
144
- * Generates a short, human-friendly random code (uppercase + digits by default).
145
- *
146
- * Ambiguous characters (`0/O`, `1/I`) are excluded, making it suitable for
147
- * coupons, OTP-like codes and invite links.
148
- *
149
- * @example
150
- * nodeComfort.id.code(); // "K7QF9X"
151
- * nodeComfort.id.code(4, "1234"); // "3142"
152
- *
153
- * @param {number} [length=6] - Number of characters.
154
- * @param {string} [alphabet="ABCDEFGHJKLMNPQRSTUVWXYZ23456789"] - Characters to pick from.
155
- * @returns {string} The random code.
156
- */
157
- code(length = 6, alphabet = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789") {
158
- const bytes = _randomBytes(length);
159
- let out = "";
160
- for (let i = 0; i < length; i++) {
161
- out += alphabet[bytes[i] % alphabet.length];
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++;
162
152
  }
163
- return out;
164
- },
165
-
166
- /**
167
- * Hashes a value with a chosen algorithm.
168
- *
169
- * @example
170
- * nodeComfort.id.hash("hello"); // sha256 hex
171
- * nodeComfort.id.hash("hello", { algorithm: "md5" });
172
- *
173
- * @param {string|Buffer} value - Data to hash.
174
- * @param {{ algorithm?: string, encoding?: "hex"|"base64"|"base64url" }} [options] - Hash options.
175
- * @returns {string} The digest.
176
- */
177
- hash(value, options = {}) {
178
- const algorithm = options.algorithm ?? "sha256";
179
- const encoding = options.encoding ?? "hex";
180
- return crypto.createHash(algorithm).update(value).digest(encoding);
181
- },
182
-
183
- /**
184
- * Computes an HMAC signature for a value using a secret key.
185
- *
186
- * @example
187
- * nodeComfort.id.hmac("payload", "secret"); // sha256 HMAC hex
188
- *
189
- * @param {string|Buffer} value - Data to sign.
190
- * @param {string|Buffer} secret - Secret key.
191
- * @param {{ algorithm?: string, encoding?: "hex"|"base64"|"base64url" }} [options] - HMAC options.
192
- * @returns {string} The signature.
193
- */
194
- hmac(value, secret, options = {}) {
195
- const algorithm = options.algorithm ?? "sha256";
196
- const encoding = options.encoding ?? "hex";
197
- return crypto.createHmac(algorithm, secret).update(value).digest(encoding);
198
- },
199
-
200
- /**
201
- * Compares two strings/buffers in constant time to prevent timing attacks.
202
- *
203
- * Use this when comparing secrets, tokens, or signatures.
204
- *
205
- * @example
206
- * if (nodeComfort.id.safeEqual(provided, expected)) { ... }
207
- *
208
- * @param {string|Buffer} a - First value.
209
- * @param {string|Buffer} b - Second value.
210
- * @returns {boolean} True if equal.
211
- */
212
- safeEqual(a, b) {
213
- const bufA = Buffer.isBuffer(a) ? a : Buffer.from(String(a));
214
- const bufB = Buffer.isBuffer(b) ? b : Buffer.from(String(b));
215
- if (bufA.length !== bufB.length) return false;
216
- return crypto.timingSafeEqual(bufA, bufB);
217
- },
218
-
219
- /**
220
- * Returns a monotonically-increasing counter ID prefixed with an optional label.
221
- *
222
- * Useful for readable, unique keys within a single process run.
223
- *
224
- * @example
225
- * nodeComfort.id.seq("user"); // "user-1"
226
- * nodeComfort.id.seq("user"); // "user-2"
227
- *
228
- * @param {string} [prefix=""] - Optional prefix.
229
- * @returns {string} The sequential ID.
230
- */
231
- seq(prefix = "") {
232
- _seqCounters[prefix] = (_seqCounters[prefix] ?? 0) + 1;
233
- return prefix ? `${prefix}-${_seqCounters[prefix]}` : String(_seqCounters[prefix]);
234
- },
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,
235
418
  };