check-ulid 3.0.2
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/LICENSE +21 -0
- package/README.md +176 -0
- package/dist/browser/index.cjs +335 -0
- package/dist/browser/index.js +320 -0
- package/dist/cli.d.cts +2 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +195 -0
- package/dist/constants.d.cts +10 -0
- package/dist/constants.d.ts +10 -0
- package/dist/crockford.d.cts +11 -0
- package/dist/crockford.d.ts +11 -0
- package/dist/error.d.cts +16 -0
- package/dist/error.d.ts +16 -0
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/node/index.cjs +339 -0
- package/dist/node/index.js +324 -0
- package/dist/node/payload.js +25695 -0
- package/dist/node/utils.js +234 -0
- package/dist/stub.d.cts +1 -0
- package/dist/stub.d.ts +1 -0
- package/dist/types.d.cts +4 -0
- package/dist/types.d.ts +4 -0
- package/dist/ulid.d.cts +49 -0
- package/dist/ulid.d.ts +49 -0
- package/dist/utils.d.cts +3 -0
- package/dist/utils.d.ts +3 -0
- package/dist/uuid.d.cts +14 -0
- package/dist/uuid.d.ts +14 -0
- package/package.json +83 -0
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
// These values should NEVER change. The values are precisely for
|
|
2
|
+
// generating ULIDs.
|
|
3
|
+
const B32_CHARACTERS = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
4
|
+
const ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"; // Crockford's Base32
|
|
5
|
+
const ENCODING_LEN = 32; // from ENCODING.length;
|
|
6
|
+
const MAX_ULID = "7ZZZZZZZZZZZZZZZZZZZZZZZZZ";
|
|
7
|
+
const MIN_ULID = "00000000000000000000000000";
|
|
8
|
+
const RANDOM_LEN = 16;
|
|
9
|
+
const TIME_LEN = 10;
|
|
10
|
+
const TIME_MAX = 281474976710655; // from Math.pow(2, 48) - 1;
|
|
11
|
+
const ULID_REGEX = /^[0-7][0-9a-hjkmnp-tv-zA-HJKMNP-TV-Z]{25}$/;
|
|
12
|
+
const UUID_REGEX = /^[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$/;
|
|
13
|
+
|
|
14
|
+
var ULIDErrorCode;
|
|
15
|
+
(function (ULIDErrorCode) {
|
|
16
|
+
ULIDErrorCode["Base32IncorrectEncoding"] = "B32_ENC_INVALID";
|
|
17
|
+
ULIDErrorCode["DecodeTimeInvalidCharacter"] = "DEC_TIME_CHAR";
|
|
18
|
+
ULIDErrorCode["DecodeTimeValueMalformed"] = "DEC_TIME_MALFORMED";
|
|
19
|
+
ULIDErrorCode["EncodeTimeNegative"] = "ENC_TIME_NEG";
|
|
20
|
+
ULIDErrorCode["EncodeTimeSizeExceeded"] = "ENC_TIME_SIZE_EXCEED";
|
|
21
|
+
ULIDErrorCode["EncodeTimeValueMalformed"] = "ENC_TIME_MALFORMED";
|
|
22
|
+
ULIDErrorCode["PRNGDetectFailure"] = "PRNG_DETECT";
|
|
23
|
+
ULIDErrorCode["ULIDInvalid"] = "ULID_INVALID";
|
|
24
|
+
ULIDErrorCode["Unexpected"] = "UNEXPECTED";
|
|
25
|
+
ULIDErrorCode["UUIDInvalid"] = "UUID_INVALID";
|
|
26
|
+
})(ULIDErrorCode || (ULIDErrorCode = {}));
|
|
27
|
+
class ULIDError extends Error {
|
|
28
|
+
constructor(errorCode, message) {
|
|
29
|
+
super(`${message} (${errorCode})`);
|
|
30
|
+
this.name = "ULIDError";
|
|
31
|
+
this.code = errorCode;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function randomChar(prng) {
|
|
36
|
+
// Currently PRNGs generate fractions from 0 to _less than_ 1, so no "%" is necessary.
|
|
37
|
+
// However, just in case a future PRNG can generate 1,
|
|
38
|
+
// we are applying "% ENCODING LEN" to wrap back to the first character
|
|
39
|
+
const randomPosition = Math.floor(prng() * ENCODING_LEN) % ENCODING_LEN;
|
|
40
|
+
return ENCODING.charAt(randomPosition);
|
|
41
|
+
}
|
|
42
|
+
function replaceCharAt(str, index, char) {
|
|
43
|
+
if (index > str.length - 1) {
|
|
44
|
+
return str;
|
|
45
|
+
}
|
|
46
|
+
return str.substr(0, index) + char + str.substr(index + 1);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Code from https://github.com/devbanana/crockford-base32/blob/develop/src/index.ts
|
|
50
|
+
function crockfordEncode(input) {
|
|
51
|
+
const output = [];
|
|
52
|
+
let bitsRead = 0;
|
|
53
|
+
let buffer = 0;
|
|
54
|
+
const reversedInput = new Uint8Array(input.slice().reverse());
|
|
55
|
+
for (const byte of reversedInput) {
|
|
56
|
+
buffer |= byte << bitsRead;
|
|
57
|
+
bitsRead += 8;
|
|
58
|
+
while (bitsRead >= 5) {
|
|
59
|
+
output.unshift(buffer & 0x1f);
|
|
60
|
+
buffer >>>= 5;
|
|
61
|
+
bitsRead -= 5;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (bitsRead > 0) {
|
|
65
|
+
output.unshift(buffer & 0x1f);
|
|
66
|
+
}
|
|
67
|
+
return output.map(byte => B32_CHARACTERS.charAt(byte)).join("");
|
|
68
|
+
}
|
|
69
|
+
function crockfordDecode(input) {
|
|
70
|
+
const sanitizedInput = input.toUpperCase().split("").reverse().join("");
|
|
71
|
+
const output = [];
|
|
72
|
+
let bitsRead = 0;
|
|
73
|
+
let buffer = 0;
|
|
74
|
+
for (const character of sanitizedInput) {
|
|
75
|
+
const byte = B32_CHARACTERS.indexOf(character);
|
|
76
|
+
if (byte === -1) {
|
|
77
|
+
throw new Error(`Invalid base 32 character found in string: ${character}`);
|
|
78
|
+
}
|
|
79
|
+
buffer |= byte << bitsRead;
|
|
80
|
+
bitsRead += 5;
|
|
81
|
+
while (bitsRead >= 8) {
|
|
82
|
+
output.unshift(buffer & 0xff);
|
|
83
|
+
buffer >>>= 8;
|
|
84
|
+
bitsRead -= 8;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (bitsRead >= 5 || buffer > 0) {
|
|
88
|
+
output.unshift(buffer & 0xff);
|
|
89
|
+
}
|
|
90
|
+
return new Uint8Array(output);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Fix a ULID's Base32 encoding -
|
|
94
|
+
* i and l (case-insensitive) will be treated as 1 and o (case-insensitive) will be treated as 0.
|
|
95
|
+
* hyphens are ignored during decoding.
|
|
96
|
+
* @param id The ULID
|
|
97
|
+
* @returns The cleaned up ULID
|
|
98
|
+
*/
|
|
99
|
+
function fixULIDBase32(id) {
|
|
100
|
+
return id.replace(/i/gi, "1").replace(/l/gi, "1").replace(/o/gi, "0").replace(/-/g, "");
|
|
101
|
+
}
|
|
102
|
+
function incrementBase32(str) {
|
|
103
|
+
let done = undefined, index = str.length, char, charIndex, output = str;
|
|
104
|
+
const maxCharIndex = ENCODING_LEN - 1;
|
|
105
|
+
while (!done && index-- >= 0) {
|
|
106
|
+
char = output[index];
|
|
107
|
+
charIndex = ENCODING.indexOf(char);
|
|
108
|
+
if (charIndex === -1) {
|
|
109
|
+
throw new ULIDError(ULIDErrorCode.Base32IncorrectEncoding, "Incorrectly encoded string");
|
|
110
|
+
}
|
|
111
|
+
if (charIndex === maxCharIndex) {
|
|
112
|
+
output = replaceCharAt(output, index, ENCODING[0]);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
done = replaceCharAt(output, index, ENCODING[charIndex + 1]);
|
|
116
|
+
}
|
|
117
|
+
if (typeof done === "string") {
|
|
118
|
+
return done;
|
|
119
|
+
}
|
|
120
|
+
throw new ULIDError(ULIDErrorCode.Base32IncorrectEncoding, "Failed incrementing string");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Decode time from a ULID
|
|
125
|
+
* @param id The ULID
|
|
126
|
+
* @returns The decoded timestamp
|
|
127
|
+
*/
|
|
128
|
+
function decodeTime(id) {
|
|
129
|
+
if (id.length !== TIME_LEN + RANDOM_LEN) {
|
|
130
|
+
throw new ULIDError(ULIDErrorCode.DecodeTimeValueMalformed, "Malformed ULID");
|
|
131
|
+
}
|
|
132
|
+
const time = id
|
|
133
|
+
.substr(0, TIME_LEN)
|
|
134
|
+
.toUpperCase()
|
|
135
|
+
.split("")
|
|
136
|
+
.reverse()
|
|
137
|
+
.reduce((carry, char, index) => {
|
|
138
|
+
const encodingIndex = ENCODING.indexOf(char);
|
|
139
|
+
if (encodingIndex === -1) {
|
|
140
|
+
throw new ULIDError(ULIDErrorCode.DecodeTimeInvalidCharacter, `Time decode error: Invalid character: ${char}`);
|
|
141
|
+
}
|
|
142
|
+
return (carry += encodingIndex * Math.pow(ENCODING_LEN, index));
|
|
143
|
+
}, 0);
|
|
144
|
+
if (time > TIME_MAX) {
|
|
145
|
+
throw new ULIDError(ULIDErrorCode.DecodeTimeValueMalformed, `Malformed ULID: timestamp too large: ${time}`);
|
|
146
|
+
}
|
|
147
|
+
return time;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Detect the best PRNG (pseudo-random number generator)
|
|
151
|
+
* @param root The root to check from (global/window)
|
|
152
|
+
* @returns The PRNG function
|
|
153
|
+
*/
|
|
154
|
+
function detectPRNG(root) {
|
|
155
|
+
const rootLookup = detectRoot();
|
|
156
|
+
const globalCrypto = (rootLookup && (rootLookup.crypto || rootLookup.msCrypto)) ||
|
|
157
|
+
(null);
|
|
158
|
+
if (typeof globalCrypto?.getRandomValues === "function") {
|
|
159
|
+
return () => {
|
|
160
|
+
const buffer = new Uint8Array(1);
|
|
161
|
+
globalCrypto.getRandomValues(buffer);
|
|
162
|
+
return buffer[0] / 256;
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
else if (typeof globalCrypto?.randomBytes === "function") {
|
|
166
|
+
return () => globalCrypto.randomBytes(1).readUInt8() / 256;
|
|
167
|
+
}
|
|
168
|
+
else ;
|
|
169
|
+
throw new ULIDError(ULIDErrorCode.PRNGDetectFailure, "Failed to find a reliable PRNG");
|
|
170
|
+
}
|
|
171
|
+
function detectRoot() {
|
|
172
|
+
if (inWebWorker())
|
|
173
|
+
return self;
|
|
174
|
+
if (typeof window !== "undefined") {
|
|
175
|
+
return window;
|
|
176
|
+
}
|
|
177
|
+
if (typeof global !== "undefined") {
|
|
178
|
+
return global;
|
|
179
|
+
}
|
|
180
|
+
if (typeof globalThis !== "undefined") {
|
|
181
|
+
return globalThis;
|
|
182
|
+
}
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
function encodeRandom(len, prng) {
|
|
186
|
+
let str = "";
|
|
187
|
+
for (; len > 0; len--) {
|
|
188
|
+
str = randomChar(prng) + str;
|
|
189
|
+
}
|
|
190
|
+
return str;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Encode the time portion of a ULID
|
|
194
|
+
* @param now The current timestamp
|
|
195
|
+
* @param len Length to generate
|
|
196
|
+
* @returns The encoded time
|
|
197
|
+
*/
|
|
198
|
+
function encodeTime(now, len = TIME_LEN) {
|
|
199
|
+
if (isNaN(now)) {
|
|
200
|
+
throw new ULIDError(ULIDErrorCode.EncodeTimeValueMalformed, `Time must be a number: ${now}`);
|
|
201
|
+
}
|
|
202
|
+
else if (now > TIME_MAX) {
|
|
203
|
+
throw new ULIDError(ULIDErrorCode.EncodeTimeSizeExceeded, `Cannot encode a time larger than ${TIME_MAX}: ${now}`);
|
|
204
|
+
}
|
|
205
|
+
else if (now < 0) {
|
|
206
|
+
throw new ULIDError(ULIDErrorCode.EncodeTimeNegative, `Time must be positive: ${now}`);
|
|
207
|
+
}
|
|
208
|
+
else if (Number.isInteger(now) === false) {
|
|
209
|
+
throw new ULIDError(ULIDErrorCode.EncodeTimeValueMalformed, `Time must be an integer: ${now}`);
|
|
210
|
+
}
|
|
211
|
+
let mod, str = "";
|
|
212
|
+
for (let currentLen = len; currentLen > 0; currentLen--) {
|
|
213
|
+
mod = now % ENCODING_LEN;
|
|
214
|
+
str = ENCODING.charAt(mod) + str;
|
|
215
|
+
now = (now - mod) / ENCODING_LEN;
|
|
216
|
+
}
|
|
217
|
+
return str;
|
|
218
|
+
}
|
|
219
|
+
function inWebWorker() {
|
|
220
|
+
// @ts-ignore
|
|
221
|
+
return typeof WorkerGlobalScope !== "undefined" && self instanceof WorkerGlobalScope;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Check if a ULID is valid
|
|
225
|
+
* @param id The ULID to test
|
|
226
|
+
* @returns True if valid, false otherwise
|
|
227
|
+
* @example
|
|
228
|
+
* isValid("01HNZX8JGFACFA36RBXDHEQN6E"); // true
|
|
229
|
+
* isValid(""); // false
|
|
230
|
+
*/
|
|
231
|
+
function isValid(id) {
|
|
232
|
+
return (typeof id === "string" &&
|
|
233
|
+
id.length === TIME_LEN + RANDOM_LEN &&
|
|
234
|
+
id
|
|
235
|
+
.toUpperCase()
|
|
236
|
+
.split("")
|
|
237
|
+
.every(char => ENCODING.indexOf(char) !== -1));
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Create a ULID factory to generate monotonically-increasing
|
|
241
|
+
* ULIDs
|
|
242
|
+
* @param prng The PRNG to use
|
|
243
|
+
* @returns A ulid factory
|
|
244
|
+
* @example
|
|
245
|
+
* const ulid = monotonicFactory();
|
|
246
|
+
* ulid(); // "01HNZXD07M5CEN5XA66EMZSRZW"
|
|
247
|
+
*/
|
|
248
|
+
function monotonicFactory(prng) {
|
|
249
|
+
const currentPRNG = prng || detectPRNG();
|
|
250
|
+
let lastTime = 0, lastRandom;
|
|
251
|
+
return function _ulid(seedTime) {
|
|
252
|
+
const seed = !seedTime || isNaN(seedTime) ? Date.now() : seedTime;
|
|
253
|
+
if (seed <= lastTime) {
|
|
254
|
+
const incrementedRandom = (lastRandom = incrementBase32(lastRandom));
|
|
255
|
+
return encodeTime(lastTime, TIME_LEN) + incrementedRandom;
|
|
256
|
+
}
|
|
257
|
+
lastTime = seed;
|
|
258
|
+
const newRandom = (lastRandom = encodeRandom(RANDOM_LEN, currentPRNG));
|
|
259
|
+
return encodeTime(seed, TIME_LEN) + newRandom;
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Generate a ULID
|
|
264
|
+
* @param seedTime Optional time seed
|
|
265
|
+
* @param prng Optional PRNG function
|
|
266
|
+
* @returns A ULID string
|
|
267
|
+
* @example
|
|
268
|
+
* ulid(); // "01HNZXD07M5CEN5XA66EMZSRZW"
|
|
269
|
+
*/
|
|
270
|
+
function ulid(seedTime, prng) {
|
|
271
|
+
const currentPRNG = prng || detectPRNG();
|
|
272
|
+
const seed = !seedTime || isNaN(seedTime) ? Date.now() : seedTime;
|
|
273
|
+
return encodeTime(seed, TIME_LEN) + encodeRandom(RANDOM_LEN, currentPRNG);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Convert a ULID to a UUID
|
|
278
|
+
* @param ulid The ULID to convert
|
|
279
|
+
* @returns A UUID string
|
|
280
|
+
*/
|
|
281
|
+
function ulidToUUID(ulid) {
|
|
282
|
+
const isValid = ULID_REGEX.test(ulid);
|
|
283
|
+
if (!isValid) {
|
|
284
|
+
throw new ULIDError(ULIDErrorCode.ULIDInvalid, `Invalid ULID: ${ulid}`);
|
|
285
|
+
}
|
|
286
|
+
const uint8Array = crockfordDecode(ulid);
|
|
287
|
+
let uuid = Array.from(uint8Array)
|
|
288
|
+
.map(byte => byte.toString(16).padStart(2, "0"))
|
|
289
|
+
.join("");
|
|
290
|
+
uuid =
|
|
291
|
+
uuid.substring(0, 8) +
|
|
292
|
+
"-" +
|
|
293
|
+
uuid.substring(8, 12) +
|
|
294
|
+
"-" +
|
|
295
|
+
uuid.substring(12, 16) +
|
|
296
|
+
"-" +
|
|
297
|
+
uuid.substring(16, 20) +
|
|
298
|
+
"-" +
|
|
299
|
+
uuid.substring(20);
|
|
300
|
+
return uuid.toUpperCase();
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Convert a UUID to a ULID
|
|
304
|
+
* @param uuid The UUID to convert
|
|
305
|
+
* @returns A ULID string
|
|
306
|
+
*/
|
|
307
|
+
function uuidToULID(uuid) {
|
|
308
|
+
const isValid = UUID_REGEX.test(uuid);
|
|
309
|
+
if (!isValid) {
|
|
310
|
+
throw new ULIDError(ULIDErrorCode.UUIDInvalid, `Invalid UUID: ${uuid}`);
|
|
311
|
+
}
|
|
312
|
+
const bytes = uuid.replace(/-/g, "").match(/.{1,2}/g);
|
|
313
|
+
if (!bytes) {
|
|
314
|
+
throw new ULIDError(ULIDErrorCode.Unexpected, `Failed parsing UUID bytes: ${uuid}`);
|
|
315
|
+
}
|
|
316
|
+
const uint8Array = new Uint8Array(bytes.map(byte => parseInt(byte, 16)));
|
|
317
|
+
return crockfordEncode(uint8Array);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export { MAX_ULID, MIN_ULID, TIME_LEN, TIME_MAX, ULIDError, ULIDErrorCode, decodeTime, encodeTime, fixULIDBase32, incrementBase32, isValid, monotonicFactory, ulid, ulidToUUID, uuidToULID };
|
package/dist/cli.d.cts
ADDED
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// These values should NEVER change. The values are precisely for
|
|
3
|
+
// generating ULIDs.
|
|
4
|
+
const ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"; // Crockford's Base32
|
|
5
|
+
const ENCODING_LEN = 32; // from ENCODING.length;
|
|
6
|
+
const RANDOM_LEN = 16;
|
|
7
|
+
const TIME_LEN = 10;
|
|
8
|
+
const TIME_MAX = 281474976710655; // from Math.pow(2, 48) - 1;
|
|
9
|
+
|
|
10
|
+
var ULIDErrorCode;
|
|
11
|
+
(function (ULIDErrorCode) {
|
|
12
|
+
ULIDErrorCode["Base32IncorrectEncoding"] = "B32_ENC_INVALID";
|
|
13
|
+
ULIDErrorCode["DecodeTimeInvalidCharacter"] = "DEC_TIME_CHAR";
|
|
14
|
+
ULIDErrorCode["DecodeTimeValueMalformed"] = "DEC_TIME_MALFORMED";
|
|
15
|
+
ULIDErrorCode["EncodeTimeNegative"] = "ENC_TIME_NEG";
|
|
16
|
+
ULIDErrorCode["EncodeTimeSizeExceeded"] = "ENC_TIME_SIZE_EXCEED";
|
|
17
|
+
ULIDErrorCode["EncodeTimeValueMalformed"] = "ENC_TIME_MALFORMED";
|
|
18
|
+
ULIDErrorCode["PRNGDetectFailure"] = "PRNG_DETECT";
|
|
19
|
+
ULIDErrorCode["ULIDInvalid"] = "ULID_INVALID";
|
|
20
|
+
ULIDErrorCode["Unexpected"] = "UNEXPECTED";
|
|
21
|
+
ULIDErrorCode["UUIDInvalid"] = "UUID_INVALID";
|
|
22
|
+
})(ULIDErrorCode || (ULIDErrorCode = {}));
|
|
23
|
+
class ULIDError extends Error {
|
|
24
|
+
constructor(errorCode, message) {
|
|
25
|
+
super(`${message} (${errorCode})`);
|
|
26
|
+
this.name = "ULIDError";
|
|
27
|
+
this.code = errorCode;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function randomChar(prng) {
|
|
32
|
+
// Currently PRNGs generate fractions from 0 to _less than_ 1, so no "%" is necessary.
|
|
33
|
+
// However, just in case a future PRNG can generate 1,
|
|
34
|
+
// we are applying "% ENCODING LEN" to wrap back to the first character
|
|
35
|
+
const randomPosition = Math.floor(prng() * ENCODING_LEN) % ENCODING_LEN;
|
|
36
|
+
return ENCODING.charAt(randomPosition);
|
|
37
|
+
}
|
|
38
|
+
function replaceCharAt(str, index, char) {
|
|
39
|
+
if (index > str.length - 1) {
|
|
40
|
+
return str;
|
|
41
|
+
}
|
|
42
|
+
return str.substr(0, index) + char + str.substr(index + 1);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Code from https://github.com/devbanana/crockford-base32/blob/develop/src/index.ts
|
|
46
|
+
function incrementBase32(str) {
|
|
47
|
+
let done = undefined, index = str.length, char, charIndex, output = str;
|
|
48
|
+
const maxCharIndex = ENCODING_LEN - 1;
|
|
49
|
+
while (!done && index-- >= 0) {
|
|
50
|
+
char = output[index];
|
|
51
|
+
charIndex = ENCODING.indexOf(char);
|
|
52
|
+
if (charIndex === -1) {
|
|
53
|
+
throw new ULIDError(ULIDErrorCode.Base32IncorrectEncoding, "Incorrectly encoded string");
|
|
54
|
+
}
|
|
55
|
+
if (charIndex === maxCharIndex) {
|
|
56
|
+
output = replaceCharAt(output, index, ENCODING[0]);
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
done = replaceCharAt(output, index, ENCODING[charIndex + 1]);
|
|
60
|
+
}
|
|
61
|
+
if (typeof done === "string") {
|
|
62
|
+
return done;
|
|
63
|
+
}
|
|
64
|
+
throw new ULIDError(ULIDErrorCode.Base32IncorrectEncoding, "Failed incrementing string");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Detect the best PRNG (pseudo-random number generator)
|
|
69
|
+
* @param root The root to check from (global/window)
|
|
70
|
+
* @returns The PRNG function
|
|
71
|
+
*/
|
|
72
|
+
function detectPRNG(root) {
|
|
73
|
+
const rootLookup = detectRoot();
|
|
74
|
+
const globalCrypto = (rootLookup && (rootLookup.crypto || rootLookup.msCrypto)) ||
|
|
75
|
+
(null);
|
|
76
|
+
if (typeof globalCrypto?.getRandomValues === "function") {
|
|
77
|
+
return () => {
|
|
78
|
+
const buffer = new Uint8Array(1);
|
|
79
|
+
globalCrypto.getRandomValues(buffer);
|
|
80
|
+
return buffer[0] / 256;
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
else if (typeof globalCrypto?.randomBytes === "function") {
|
|
84
|
+
return () => globalCrypto.randomBytes(1).readUInt8() / 256;
|
|
85
|
+
}
|
|
86
|
+
else ;
|
|
87
|
+
throw new ULIDError(ULIDErrorCode.PRNGDetectFailure, "Failed to find a reliable PRNG");
|
|
88
|
+
}
|
|
89
|
+
function detectRoot() {
|
|
90
|
+
if (inWebWorker())
|
|
91
|
+
return self;
|
|
92
|
+
if (typeof window !== "undefined") {
|
|
93
|
+
return window;
|
|
94
|
+
}
|
|
95
|
+
if (typeof global !== "undefined") {
|
|
96
|
+
return global;
|
|
97
|
+
}
|
|
98
|
+
if (typeof globalThis !== "undefined") {
|
|
99
|
+
return globalThis;
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
function encodeRandom(len, prng) {
|
|
104
|
+
let str = "";
|
|
105
|
+
for (; len > 0; len--) {
|
|
106
|
+
str = randomChar(prng) + str;
|
|
107
|
+
}
|
|
108
|
+
return str;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Encode the time portion of a ULID
|
|
112
|
+
* @param now The current timestamp
|
|
113
|
+
* @param len Length to generate
|
|
114
|
+
* @returns The encoded time
|
|
115
|
+
*/
|
|
116
|
+
function encodeTime(now, len = TIME_LEN) {
|
|
117
|
+
if (isNaN(now)) {
|
|
118
|
+
throw new ULIDError(ULIDErrorCode.EncodeTimeValueMalformed, `Time must be a number: ${now}`);
|
|
119
|
+
}
|
|
120
|
+
else if (now > TIME_MAX) {
|
|
121
|
+
throw new ULIDError(ULIDErrorCode.EncodeTimeSizeExceeded, `Cannot encode a time larger than ${TIME_MAX}: ${now}`);
|
|
122
|
+
}
|
|
123
|
+
else if (now < 0) {
|
|
124
|
+
throw new ULIDError(ULIDErrorCode.EncodeTimeNegative, `Time must be positive: ${now}`);
|
|
125
|
+
}
|
|
126
|
+
else if (Number.isInteger(now) === false) {
|
|
127
|
+
throw new ULIDError(ULIDErrorCode.EncodeTimeValueMalformed, `Time must be an integer: ${now}`);
|
|
128
|
+
}
|
|
129
|
+
let mod, str = "";
|
|
130
|
+
for (let currentLen = len; currentLen > 0; currentLen--) {
|
|
131
|
+
mod = now % ENCODING_LEN;
|
|
132
|
+
str = ENCODING.charAt(mod) + str;
|
|
133
|
+
now = (now - mod) / ENCODING_LEN;
|
|
134
|
+
}
|
|
135
|
+
return str;
|
|
136
|
+
}
|
|
137
|
+
function inWebWorker() {
|
|
138
|
+
// @ts-ignore
|
|
139
|
+
return typeof WorkerGlobalScope !== "undefined" && self instanceof WorkerGlobalScope;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Create a ULID factory to generate monotonically-increasing
|
|
143
|
+
* ULIDs
|
|
144
|
+
* @param prng The PRNG to use
|
|
145
|
+
* @returns A ulid factory
|
|
146
|
+
* @example
|
|
147
|
+
* const ulid = monotonicFactory();
|
|
148
|
+
* ulid(); // "01HNZXD07M5CEN5XA66EMZSRZW"
|
|
149
|
+
*/
|
|
150
|
+
function monotonicFactory(prng) {
|
|
151
|
+
const currentPRNG = detectPRNG();
|
|
152
|
+
let lastTime = 0, lastRandom;
|
|
153
|
+
return function _ulid(seedTime) {
|
|
154
|
+
const seed = !seedTime || isNaN(seedTime) ? Date.now() : seedTime;
|
|
155
|
+
if (seed <= lastTime) {
|
|
156
|
+
const incrementedRandom = (lastRandom = incrementBase32(lastRandom));
|
|
157
|
+
return encodeTime(lastTime, TIME_LEN) + incrementedRandom;
|
|
158
|
+
}
|
|
159
|
+
lastTime = seed;
|
|
160
|
+
const newRandom = (lastRandom = encodeRandom(RANDOM_LEN, currentPRNG));
|
|
161
|
+
return encodeTime(seed, TIME_LEN) + newRandom;
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function parseArgs(args) {
|
|
166
|
+
const output = {};
|
|
167
|
+
while (args.length > 0) {
|
|
168
|
+
const arg = args.shift();
|
|
169
|
+
if (/^\-\-/.test(arg)) {
|
|
170
|
+
if (/=/.test(arg)) {
|
|
171
|
+
const [key, value] = arg.split("=");
|
|
172
|
+
output[key.substring(2)] = value;
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
const value = args.shift();
|
|
176
|
+
if (/^-/.test(value)) {
|
|
177
|
+
args.unshift(value);
|
|
178
|
+
}
|
|
179
|
+
else if (!value) {
|
|
180
|
+
output[arg.substring(2)] = true;
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
output[arg.substring(2)] = value;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return output;
|
|
189
|
+
}
|
|
190
|
+
const argv = parseArgs(process.argv.slice(2));
|
|
191
|
+
const count = /^\d+/.test(argv["count"]) ? parseInt(argv["count"], 10) : 1;
|
|
192
|
+
const factory = monotonicFactory();
|
|
193
|
+
for (let i = 0; i < count; i += 1) {
|
|
194
|
+
console.log(factory());
|
|
195
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const B32_CHARACTERS = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
2
|
+
export declare const ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
3
|
+
export declare const ENCODING_LEN = 32;
|
|
4
|
+
export declare const MAX_ULID = "7ZZZZZZZZZZZZZZZZZZZZZZZZZ";
|
|
5
|
+
export declare const MIN_ULID = "00000000000000000000000000";
|
|
6
|
+
export declare const RANDOM_LEN = 16;
|
|
7
|
+
export declare const TIME_LEN = 10;
|
|
8
|
+
export declare const TIME_MAX = 281474976710655;
|
|
9
|
+
export declare const ULID_REGEX: RegExp;
|
|
10
|
+
export declare const UUID_REGEX: RegExp;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const B32_CHARACTERS = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
2
|
+
export declare const ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
3
|
+
export declare const ENCODING_LEN = 32;
|
|
4
|
+
export declare const MAX_ULID = "7ZZZZZZZZZZZZZZZZZZZZZZZZZ";
|
|
5
|
+
export declare const MIN_ULID = "00000000000000000000000000";
|
|
6
|
+
export declare const RANDOM_LEN = 16;
|
|
7
|
+
export declare const TIME_LEN = 10;
|
|
8
|
+
export declare const TIME_MAX = 281474976710655;
|
|
9
|
+
export declare const ULID_REGEX: RegExp;
|
|
10
|
+
export declare const UUID_REGEX: RegExp;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare function crockfordEncode(input: Uint8Array): string;
|
|
2
|
+
export declare function crockfordDecode(input: string): Uint8Array;
|
|
3
|
+
/**
|
|
4
|
+
* Fix a ULID's Base32 encoding -
|
|
5
|
+
* i and l (case-insensitive) will be treated as 1 and o (case-insensitive) will be treated as 0.
|
|
6
|
+
* hyphens are ignored during decoding.
|
|
7
|
+
* @param id The ULID
|
|
8
|
+
* @returns The cleaned up ULID
|
|
9
|
+
*/
|
|
10
|
+
export declare function fixULIDBase32(id: string): string;
|
|
11
|
+
export declare function incrementBase32(str: string): string;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare function crockfordEncode(input: Uint8Array): string;
|
|
2
|
+
export declare function crockfordDecode(input: string): Uint8Array;
|
|
3
|
+
/**
|
|
4
|
+
* Fix a ULID's Base32 encoding -
|
|
5
|
+
* i and l (case-insensitive) will be treated as 1 and o (case-insensitive) will be treated as 0.
|
|
6
|
+
* hyphens are ignored during decoding.
|
|
7
|
+
* @param id The ULID
|
|
8
|
+
* @returns The cleaned up ULID
|
|
9
|
+
*/
|
|
10
|
+
export declare function fixULIDBase32(id: string): string;
|
|
11
|
+
export declare function incrementBase32(str: string): string;
|
package/dist/error.d.cts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare enum ULIDErrorCode {
|
|
2
|
+
Base32IncorrectEncoding = "B32_ENC_INVALID",
|
|
3
|
+
DecodeTimeInvalidCharacter = "DEC_TIME_CHAR",
|
|
4
|
+
DecodeTimeValueMalformed = "DEC_TIME_MALFORMED",
|
|
5
|
+
EncodeTimeNegative = "ENC_TIME_NEG",
|
|
6
|
+
EncodeTimeSizeExceeded = "ENC_TIME_SIZE_EXCEED",
|
|
7
|
+
EncodeTimeValueMalformed = "ENC_TIME_MALFORMED",
|
|
8
|
+
PRNGDetectFailure = "PRNG_DETECT",
|
|
9
|
+
ULIDInvalid = "ULID_INVALID",
|
|
10
|
+
Unexpected = "UNEXPECTED",
|
|
11
|
+
UUIDInvalid = "UUID_INVALID"
|
|
12
|
+
}
|
|
13
|
+
export declare class ULIDError extends Error {
|
|
14
|
+
code: ULIDErrorCode;
|
|
15
|
+
constructor(errorCode: ULIDErrorCode, message: string);
|
|
16
|
+
}
|
package/dist/error.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare enum ULIDErrorCode {
|
|
2
|
+
Base32IncorrectEncoding = "B32_ENC_INVALID",
|
|
3
|
+
DecodeTimeInvalidCharacter = "DEC_TIME_CHAR",
|
|
4
|
+
DecodeTimeValueMalformed = "DEC_TIME_MALFORMED",
|
|
5
|
+
EncodeTimeNegative = "ENC_TIME_NEG",
|
|
6
|
+
EncodeTimeSizeExceeded = "ENC_TIME_SIZE_EXCEED",
|
|
7
|
+
EncodeTimeValueMalformed = "ENC_TIME_MALFORMED",
|
|
8
|
+
PRNGDetectFailure = "PRNG_DETECT",
|
|
9
|
+
ULIDInvalid = "ULID_INVALID",
|
|
10
|
+
Unexpected = "UNEXPECTED",
|
|
11
|
+
UUIDInvalid = "UUID_INVALID"
|
|
12
|
+
}
|
|
13
|
+
export declare class ULIDError extends Error {
|
|
14
|
+
code: ULIDErrorCode;
|
|
15
|
+
constructor(errorCode: ULIDErrorCode, message: string);
|
|
16
|
+
}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { decodeTime, encodeTime, isValid, monotonicFactory, ulid } from "./ulid.js";
|
|
2
|
+
export { ulidToUUID, uuidToULID } from "./uuid.js";
|
|
3
|
+
export { fixULIDBase32, incrementBase32 } from "./crockford.js";
|
|
4
|
+
export { ULIDError, ULIDErrorCode } from "./error.js";
|
|
5
|
+
export { MAX_ULID, MIN_ULID, TIME_LEN, TIME_MAX } from "./constants.js";
|
|
6
|
+
export * from "./types.js";
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { decodeTime, encodeTime, isValid, monotonicFactory, ulid } from "./ulid.js";
|
|
2
|
+
export { ulidToUUID, uuidToULID } from "./uuid.js";
|
|
3
|
+
export { fixULIDBase32, incrementBase32 } from "./crockford.js";
|
|
4
|
+
export { ULIDError, ULIDErrorCode } from "./error.js";
|
|
5
|
+
export { MAX_ULID, MIN_ULID, TIME_LEN, TIME_MAX } from "./constants.js";
|
|
6
|
+
export * from "./types.js";
|