@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.
- package/CHANGELOG.md +190 -0
- package/README.md +136 -549
- package/index.js +80 -35
- package/package.json +160 -33
- package/src/Arr.js +887 -419
- package/src/Async.js +585 -0
- package/src/Cache.js +329 -0
- package/src/Checker.js +971 -539
- 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 -894
- package/src/Func.js +670 -353
- package/src/Http.js +538 -0
- package/src/Id.js +384 -201
- package/src/Logger.js +739 -378
- package/src/Num.js +844 -361
- package/src/Obj.js +855 -372
- package/src/SQLite.js +1308 -1005
- package/src/Schema.js +1586 -0
- package/src/Str.js +983 -436
- package/src/Sys.js +511 -0
- package/src/Time.js +1260 -422
- package/src/Utils.js +321 -325
- package/src/errors.js +292 -0
- package/types/index.d.ts +693 -526
- package/types/src/Arr.d.ts +455 -145
- package/types/src/Arr.d.ts.map +1 -1
- 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 +501 -235
- 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 +686 -238
- package/types/src/FS.d.ts.map +1 -1
- package/types/src/Func.d.ts +380 -135
- package/types/src/Func.d.ts.map +1 -1
- package/types/src/Http.d.ts +374 -0
- package/types/src/Http.d.ts.map +1 -0
- package/types/src/Id.d.ts +149 -68
- package/types/src/Id.d.ts.map +1 -1
- package/types/src/Logger.d.ts +363 -46
- package/types/src/Logger.d.ts.map +1 -1
- package/types/src/Num.d.ts +473 -137
- package/types/src/Num.d.ts.map +1 -1
- package/types/src/Obj.d.ts +390 -104
- package/types/src/Obj.d.ts.map +1 -1
- package/types/src/SQLite.d.ts +646 -309
- 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 +569 -166
- package/types/src/Str.d.ts.map +1 -1
- package/types/src/Sys.d.ts +269 -0
- package/types/src/Sys.d.ts.map +1 -0
- package/types/src/Time.d.ts +663 -204
- package/types/src/Time.d.ts.map +1 -1
- 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/types/index.d.ts.map +0 -1
package/src/Id.js
CHANGED
|
@@ -1,40 +1,81 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
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
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
const
|
|
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
|
-
*
|
|
17
|
-
* @
|
|
18
|
-
* @
|
|
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
|
-
*
|
|
24
|
-
* @
|
|
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 {
|
|
55
|
+
* @returns {string}
|
|
27
56
|
*/
|
|
28
|
-
const
|
|
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
|
-
*
|
|
32
|
-
* @
|
|
33
|
-
* @param {Buffer} buffer - Bytes to encode.
|
|
34
|
-
* @returns {string} The Base58 string.
|
|
74
|
+
* @param {Buffer} buffer
|
|
75
|
+
* @returns {string}
|
|
35
76
|
*/
|
|
36
|
-
const
|
|
37
|
-
if (buffer.length
|
|
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
|
|
52
|
-
for (let i = 0;
|
|
53
|
-
for (let i = digits.length - 1; i >= 0; i--)
|
|
54
|
-
return
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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
|
-
|
|
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
|
-
* @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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
};
|