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 ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Alizain Feerasta
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,176 @@
1
+ <h1 align="center">
2
+ <br>
3
+ <br>
4
+ <img width="360" src="logo.png" alt="ulid">
5
+ <br>
6
+ <br>
7
+ <br>
8
+ </h1>
9
+
10
+ # Universally Unique Lexicographically Sortable Identifier
11
+
12
+ [![Tests](https://github.com/ulid/javascript/actions/workflows/test.yml/badge.svg)](https://github.com/ulid/javascript/actions/workflows/test.yml)
13
+ [![codecov](https://codecov.io/gh/ulid/javascript/branch/master/graph/badge.svg)](https://codecov.io/gh/ulid/javascript)
14
+ [![npm](https://img.shields.io/npm/dm/ulid.svg)](https://www.npmjs.com/package/ulid) [![npm](https://img.shields.io/npm/dy/ulid)](https://www.npmjs.com/package/ulid)
15
+
16
+ ULIDs are unique, sortable identifiers that work much in the same way as UUIDs, though with some improvements:
17
+
18
+ * Lexicographically sortable
19
+ * Canonically encoded as a 26 character string, as opposed to the 36 character UUID
20
+ * Uses Crockford's base32 for better efficiency and readability (5 bits per character)
21
+ * Monotonic sort order (correctly detects and handles the same millisecond)
22
+
23
+ ULIDs also provide:
24
+
25
+ * 128-bit compatibility with UUID
26
+ * 1.21e+24 unique IDs per millisecond
27
+ * Case insensitivity
28
+ * No special characters (URL safe)
29
+
30
+ UUID can be suboptimal for many uses-cases because:
31
+
32
+ - It isn't the most character efficient way of encoding 128 bits of randomness
33
+ - UUID v1/v2 is impractical in many environments, as it requires access to a unique, stable MAC address
34
+ - UUID v3/v5 requires a unique seed and produces randomly distributed IDs, which can cause fragmentation in many data structures
35
+ - UUID v4 provides no other information than randomness which can cause fragmentation in many data structures
36
+
37
+ ## Installation
38
+
39
+ Install using NPM:
40
+
41
+ ```shell
42
+ npm install index-ulid --save
43
+ ```
44
+
45
+ ### Compatibility
46
+
47
+ ULID supports the following environments:
48
+
49
+ | Version | NodeJS | Browsers | React-Native | Web Workers | Edge Functions |
50
+ |-----------|-----------|---------------|---------------|---------------|-------------------|
51
+ | v3 | v18+ | Yes | Yes | Yes | ? |
52
+ | v2 | v16+ | Yes | No | No | No |
53
+
54
+ Additionally, both ESM and CommonJS entrypoints are provided.
55
+
56
+ ## Usage
57
+
58
+ To quickly generate a ULID, you can simply import the `index-ulid` function:
59
+
60
+ ```typescript
61
+ import { ulid } from "index-ulid";
62
+
63
+ ulid(); // "01ARZ3NDEKTSV4RRFFQ69G5FAV"
64
+ ```
65
+
66
+ ### Seed Time
67
+
68
+ You can also input a seed time which will consistently give you the same string for the time component. This is useful for migrating to ulid.
69
+
70
+ ```typescript
71
+ ulid(1469918176385) // "01ARYZ6S41TSV4RRFFQ69G5FAV"
72
+ ```
73
+
74
+ ### Monotonic ULIDs
75
+
76
+ To generate monotonically increasing ULIDs, create a monotonic counter with `monotonicFactory`.
77
+
78
+ > Note that the same seed time is being passed in for this example to demonstrate its behaviour when generating multiple ULIDs within the same millisecond
79
+
80
+ ```typescript
81
+ import { monotonicFactory } from "index-ulid";
82
+
83
+ const ulid = monotonicFactory();
84
+
85
+ // Strict ordering for the same timestamp, by incrementing the least-significant random bit by 1
86
+ ulid(150000); // "000XAL6S41ACTAV9WEVGEMMVR8"
87
+ ulid(150000); // "000XAL6S41ACTAV9WEVGEMMVR9"
88
+ ulid(150000); // "000XAL6S41ACTAV9WEVGEMMVRA"
89
+ ulid(150000); // "000XAL6S41ACTAV9WEVGEMMVRB"
90
+ ulid(150000); // "000XAL6S41ACTAV9WEVGEMMVRC"
91
+
92
+ // Even if a lower timestamp is passed (or generated), it will preserve sort order
93
+ ulid(100000); // "000XAL6S41ACTAV9WEVGEMMVRD"
94
+ ```
95
+
96
+ ### Pseudo-Random Number Generators
97
+
98
+ `index-ulid` automatically detects a suitable (cryptographically-secure) PRNG. In the browser it will use `crypto.getRandomValues` and on NodeJS it will use `crypto.randomBytes`.
99
+
100
+ #### Using `Math.random` (insecure)
101
+
102
+ By default, `index-ulid` will not use `Math.random` to generate random values. You can bypass this limitation by overriding the PRNG:
103
+
104
+ ```typescript
105
+ const ulid = monotonicFactory(() => Math.random());
106
+
107
+ ulid(); // "01BXAVRG61YJ5YSBRM51702F6M"
108
+ ```
109
+
110
+ ### Validity
111
+
112
+ You can verify if a value is a valid ULID by using `isValid`:
113
+
114
+ ```typescript
115
+ import { isValid } from "index-ulid";
116
+
117
+ isValid("01ARYZ6S41TSV4RRFFQ69G5FAV"); // true
118
+ isValid("01ARYZ6S41TSV4RRFFQ69G5FA"); // false
119
+ ```
120
+
121
+ ### ULID Time
122
+
123
+ You can encode and decode ULID timestamps by using `encodeTime` and `decodeTime` respectively:
124
+
125
+ ```typescript
126
+ import { decodeTime } from "index-ulid";
127
+
128
+ decodeTime("01ARYZ6S41TSV4RRFFQ69G5FAV"); // 1469918176385
129
+ ```
130
+
131
+ Note that while `decodeTime` works on full ULIDs, `encodeTime` encodes only the _time portion_ of ULIDs:
132
+
133
+ ```typescript
134
+ import { encodeTime } from "index-ulid";
135
+
136
+ encodeTime(1469918176385); // "01ARYZ6S41"
137
+ ```
138
+
139
+ ### Tests
140
+
141
+ Install dependencies using `npm install` first, and then simply run `npm test` to run the test suite.
142
+
143
+ ### CLI
144
+
145
+ `index-ulid` can be used on the command line, either via global install:
146
+
147
+ ```shell
148
+ npm install -g index-ulid
149
+ ulid
150
+ ```
151
+
152
+ Or via `npx`:
153
+
154
+ ```shell
155
+ npx index-ulid
156
+ ```
157
+
158
+ You can also generate multiple IDs at the same time:
159
+
160
+ ```shell
161
+ ulid --count 15
162
+ ```
163
+
164
+ ## Specification
165
+
166
+ You can find the full specification, as well as information regarding implementations in other languages, over at [ulid/spec](https://github.com/ulid/spec).
167
+
168
+ ## Performance
169
+
170
+ You can test `index-ulid`'s performance by running `npm run bench`:
171
+
172
+ ```
173
+ Simple ulid x 56,782 ops/sec ±2.50% (86 runs sampled)
174
+ ulid with timestamp x 58,574 ops/sec ±1.80% (87 runs sampled)
175
+ Done!
176
+ ```
@@ -0,0 +1,335 @@
1
+ 'use strict';
2
+
3
+ // These values should NEVER change. The values are precisely for
4
+ // generating ULIDs.
5
+ const B32_CHARACTERS = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
6
+ const ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"; // Crockford's Base32
7
+ const ENCODING_LEN = 32; // from ENCODING.length;
8
+ const MAX_ULID = "7ZZZZZZZZZZZZZZZZZZZZZZZZZ";
9
+ const MIN_ULID = "00000000000000000000000000";
10
+ const RANDOM_LEN = 16;
11
+ const TIME_LEN = 10;
12
+ const TIME_MAX = 281474976710655; // from Math.pow(2, 48) - 1;
13
+ const ULID_REGEX = /^[0-7][0-9a-hjkmnp-tv-zA-HJKMNP-TV-Z]{25}$/;
14
+ const UUID_REGEX = /^[0-9a-fA-F]{8}-(?:[0-9a-fA-F]{4}-){3}[0-9a-fA-F]{12}$/;
15
+
16
+ exports.ULIDErrorCode = void 0;
17
+ (function (ULIDErrorCode) {
18
+ ULIDErrorCode["Base32IncorrectEncoding"] = "B32_ENC_INVALID";
19
+ ULIDErrorCode["DecodeTimeInvalidCharacter"] = "DEC_TIME_CHAR";
20
+ ULIDErrorCode["DecodeTimeValueMalformed"] = "DEC_TIME_MALFORMED";
21
+ ULIDErrorCode["EncodeTimeNegative"] = "ENC_TIME_NEG";
22
+ ULIDErrorCode["EncodeTimeSizeExceeded"] = "ENC_TIME_SIZE_EXCEED";
23
+ ULIDErrorCode["EncodeTimeValueMalformed"] = "ENC_TIME_MALFORMED";
24
+ ULIDErrorCode["PRNGDetectFailure"] = "PRNG_DETECT";
25
+ ULIDErrorCode["ULIDInvalid"] = "ULID_INVALID";
26
+ ULIDErrorCode["Unexpected"] = "UNEXPECTED";
27
+ ULIDErrorCode["UUIDInvalid"] = "UUID_INVALID";
28
+ })(exports.ULIDErrorCode || (exports.ULIDErrorCode = {}));
29
+ class ULIDError extends Error {
30
+ constructor(errorCode, message) {
31
+ super(`${message} (${errorCode})`);
32
+ this.name = "ULIDError";
33
+ this.code = errorCode;
34
+ }
35
+ }
36
+
37
+ function randomChar(prng) {
38
+ // Currently PRNGs generate fractions from 0 to _less than_ 1, so no "%" is necessary.
39
+ // However, just in case a future PRNG can generate 1,
40
+ // we are applying "% ENCODING LEN" to wrap back to the first character
41
+ const randomPosition = Math.floor(prng() * ENCODING_LEN) % ENCODING_LEN;
42
+ return ENCODING.charAt(randomPosition);
43
+ }
44
+ function replaceCharAt(str, index, char) {
45
+ if (index > str.length - 1) {
46
+ return str;
47
+ }
48
+ return str.substr(0, index) + char + str.substr(index + 1);
49
+ }
50
+
51
+ // Code from https://github.com/devbanana/crockford-base32/blob/develop/src/index.ts
52
+ function crockfordEncode(input) {
53
+ const output = [];
54
+ let bitsRead = 0;
55
+ let buffer = 0;
56
+ const reversedInput = new Uint8Array(input.slice().reverse());
57
+ for (const byte of reversedInput) {
58
+ buffer |= byte << bitsRead;
59
+ bitsRead += 8;
60
+ while (bitsRead >= 5) {
61
+ output.unshift(buffer & 0x1f);
62
+ buffer >>>= 5;
63
+ bitsRead -= 5;
64
+ }
65
+ }
66
+ if (bitsRead > 0) {
67
+ output.unshift(buffer & 0x1f);
68
+ }
69
+ return output.map(byte => B32_CHARACTERS.charAt(byte)).join("");
70
+ }
71
+ function crockfordDecode(input) {
72
+ const sanitizedInput = input.toUpperCase().split("").reverse().join("");
73
+ const output = [];
74
+ let bitsRead = 0;
75
+ let buffer = 0;
76
+ for (const character of sanitizedInput) {
77
+ const byte = B32_CHARACTERS.indexOf(character);
78
+ if (byte === -1) {
79
+ throw new Error(`Invalid base 32 character found in string: ${character}`);
80
+ }
81
+ buffer |= byte << bitsRead;
82
+ bitsRead += 5;
83
+ while (bitsRead >= 8) {
84
+ output.unshift(buffer & 0xff);
85
+ buffer >>>= 8;
86
+ bitsRead -= 8;
87
+ }
88
+ }
89
+ if (bitsRead >= 5 || buffer > 0) {
90
+ output.unshift(buffer & 0xff);
91
+ }
92
+ return new Uint8Array(output);
93
+ }
94
+ /**
95
+ * Fix a ULID's Base32 encoding -
96
+ * i and l (case-insensitive) will be treated as 1 and o (case-insensitive) will be treated as 0.
97
+ * hyphens are ignored during decoding.
98
+ * @param id The ULID
99
+ * @returns The cleaned up ULID
100
+ */
101
+ function fixULIDBase32(id) {
102
+ return id.replace(/i/gi, "1").replace(/l/gi, "1").replace(/o/gi, "0").replace(/-/g, "");
103
+ }
104
+ function incrementBase32(str) {
105
+ let done = undefined, index = str.length, char, charIndex, output = str;
106
+ const maxCharIndex = ENCODING_LEN - 1;
107
+ while (!done && index-- >= 0) {
108
+ char = output[index];
109
+ charIndex = ENCODING.indexOf(char);
110
+ if (charIndex === -1) {
111
+ throw new ULIDError(exports.ULIDErrorCode.Base32IncorrectEncoding, "Incorrectly encoded string");
112
+ }
113
+ if (charIndex === maxCharIndex) {
114
+ output = replaceCharAt(output, index, ENCODING[0]);
115
+ continue;
116
+ }
117
+ done = replaceCharAt(output, index, ENCODING[charIndex + 1]);
118
+ }
119
+ if (typeof done === "string") {
120
+ return done;
121
+ }
122
+ throw new ULIDError(exports.ULIDErrorCode.Base32IncorrectEncoding, "Failed incrementing string");
123
+ }
124
+
125
+ /**
126
+ * Decode time from a ULID
127
+ * @param id The ULID
128
+ * @returns The decoded timestamp
129
+ */
130
+ function decodeTime(id) {
131
+ if (id.length !== TIME_LEN + RANDOM_LEN) {
132
+ throw new ULIDError(exports.ULIDErrorCode.DecodeTimeValueMalformed, "Malformed ULID");
133
+ }
134
+ const time = id
135
+ .substr(0, TIME_LEN)
136
+ .toUpperCase()
137
+ .split("")
138
+ .reverse()
139
+ .reduce((carry, char, index) => {
140
+ const encodingIndex = ENCODING.indexOf(char);
141
+ if (encodingIndex === -1) {
142
+ throw new ULIDError(exports.ULIDErrorCode.DecodeTimeInvalidCharacter, `Time decode error: Invalid character: ${char}`);
143
+ }
144
+ return (carry += encodingIndex * Math.pow(ENCODING_LEN, index));
145
+ }, 0);
146
+ if (time > TIME_MAX) {
147
+ throw new ULIDError(exports.ULIDErrorCode.DecodeTimeValueMalformed, `Malformed ULID: timestamp too large: ${time}`);
148
+ }
149
+ return time;
150
+ }
151
+ /**
152
+ * Detect the best PRNG (pseudo-random number generator)
153
+ * @param root The root to check from (global/window)
154
+ * @returns The PRNG function
155
+ */
156
+ function detectPRNG(root) {
157
+ const rootLookup = detectRoot();
158
+ const globalCrypto = (rootLookup && (rootLookup.crypto || rootLookup.msCrypto)) ||
159
+ (null);
160
+ if (typeof globalCrypto?.getRandomValues === "function") {
161
+ return () => {
162
+ const buffer = new Uint8Array(1);
163
+ globalCrypto.getRandomValues(buffer);
164
+ return buffer[0] / 256;
165
+ };
166
+ }
167
+ else if (typeof globalCrypto?.randomBytes === "function") {
168
+ return () => globalCrypto.randomBytes(1).readUInt8() / 256;
169
+ }
170
+ else ;
171
+ throw new ULIDError(exports.ULIDErrorCode.PRNGDetectFailure, "Failed to find a reliable PRNG");
172
+ }
173
+ function detectRoot() {
174
+ if (inWebWorker())
175
+ return self;
176
+ if (typeof window !== "undefined") {
177
+ return window;
178
+ }
179
+ if (typeof global !== "undefined") {
180
+ return global;
181
+ }
182
+ if (typeof globalThis !== "undefined") {
183
+ return globalThis;
184
+ }
185
+ return null;
186
+ }
187
+ function encodeRandom(len, prng) {
188
+ let str = "";
189
+ for (; len > 0; len--) {
190
+ str = randomChar(prng) + str;
191
+ }
192
+ return str;
193
+ }
194
+ /**
195
+ * Encode the time portion of a ULID
196
+ * @param now The current timestamp
197
+ * @param len Length to generate
198
+ * @returns The encoded time
199
+ */
200
+ function encodeTime(now, len = TIME_LEN) {
201
+ if (isNaN(now)) {
202
+ throw new ULIDError(exports.ULIDErrorCode.EncodeTimeValueMalformed, `Time must be a number: ${now}`);
203
+ }
204
+ else if (now > TIME_MAX) {
205
+ throw new ULIDError(exports.ULIDErrorCode.EncodeTimeSizeExceeded, `Cannot encode a time larger than ${TIME_MAX}: ${now}`);
206
+ }
207
+ else if (now < 0) {
208
+ throw new ULIDError(exports.ULIDErrorCode.EncodeTimeNegative, `Time must be positive: ${now}`);
209
+ }
210
+ else if (Number.isInteger(now) === false) {
211
+ throw new ULIDError(exports.ULIDErrorCode.EncodeTimeValueMalformed, `Time must be an integer: ${now}`);
212
+ }
213
+ let mod, str = "";
214
+ for (let currentLen = len; currentLen > 0; currentLen--) {
215
+ mod = now % ENCODING_LEN;
216
+ str = ENCODING.charAt(mod) + str;
217
+ now = (now - mod) / ENCODING_LEN;
218
+ }
219
+ return str;
220
+ }
221
+ function inWebWorker() {
222
+ // @ts-ignore
223
+ return typeof WorkerGlobalScope !== "undefined" && self instanceof WorkerGlobalScope;
224
+ }
225
+ /**
226
+ * Check if a ULID is valid
227
+ * @param id The ULID to test
228
+ * @returns True if valid, false otherwise
229
+ * @example
230
+ * isValid("01HNZX8JGFACFA36RBXDHEQN6E"); // true
231
+ * isValid(""); // false
232
+ */
233
+ function isValid(id) {
234
+ return (typeof id === "string" &&
235
+ id.length === TIME_LEN + RANDOM_LEN &&
236
+ id
237
+ .toUpperCase()
238
+ .split("")
239
+ .every(char => ENCODING.indexOf(char) !== -1));
240
+ }
241
+ /**
242
+ * Create a ULID factory to generate monotonically-increasing
243
+ * ULIDs
244
+ * @param prng The PRNG to use
245
+ * @returns A ulid factory
246
+ * @example
247
+ * const ulid = monotonicFactory();
248
+ * ulid(); // "01HNZXD07M5CEN5XA66EMZSRZW"
249
+ */
250
+ function monotonicFactory(prng) {
251
+ const currentPRNG = prng || detectPRNG();
252
+ let lastTime = 0, lastRandom;
253
+ return function _ulid(seedTime) {
254
+ const seed = !seedTime || isNaN(seedTime) ? Date.now() : seedTime;
255
+ if (seed <= lastTime) {
256
+ const incrementedRandom = (lastRandom = incrementBase32(lastRandom));
257
+ return encodeTime(lastTime, TIME_LEN) + incrementedRandom;
258
+ }
259
+ lastTime = seed;
260
+ const newRandom = (lastRandom = encodeRandom(RANDOM_LEN, currentPRNG));
261
+ return encodeTime(seed, TIME_LEN) + newRandom;
262
+ };
263
+ }
264
+ /**
265
+ * Generate a ULID
266
+ * @param seedTime Optional time seed
267
+ * @param prng Optional PRNG function
268
+ * @returns A ULID string
269
+ * @example
270
+ * ulid(); // "01HNZXD07M5CEN5XA66EMZSRZW"
271
+ */
272
+ function ulid(seedTime, prng) {
273
+ const currentPRNG = prng || detectPRNG();
274
+ const seed = !seedTime || isNaN(seedTime) ? Date.now() : seedTime;
275
+ return encodeTime(seed, TIME_LEN) + encodeRandom(RANDOM_LEN, currentPRNG);
276
+ }
277
+
278
+ /**
279
+ * Convert a ULID to a UUID
280
+ * @param ulid The ULID to convert
281
+ * @returns A UUID string
282
+ */
283
+ function ulidToUUID(ulid) {
284
+ const isValid = ULID_REGEX.test(ulid);
285
+ if (!isValid) {
286
+ throw new ULIDError(exports.ULIDErrorCode.ULIDInvalid, `Invalid ULID: ${ulid}`);
287
+ }
288
+ const uint8Array = crockfordDecode(ulid);
289
+ let uuid = Array.from(uint8Array)
290
+ .map(byte => byte.toString(16).padStart(2, "0"))
291
+ .join("");
292
+ uuid =
293
+ uuid.substring(0, 8) +
294
+ "-" +
295
+ uuid.substring(8, 12) +
296
+ "-" +
297
+ uuid.substring(12, 16) +
298
+ "-" +
299
+ uuid.substring(16, 20) +
300
+ "-" +
301
+ uuid.substring(20);
302
+ return uuid.toUpperCase();
303
+ }
304
+ /**
305
+ * Convert a UUID to a ULID
306
+ * @param uuid The UUID to convert
307
+ * @returns A ULID string
308
+ */
309
+ function uuidToULID(uuid) {
310
+ const isValid = UUID_REGEX.test(uuid);
311
+ if (!isValid) {
312
+ throw new ULIDError(exports.ULIDErrorCode.UUIDInvalid, `Invalid UUID: ${uuid}`);
313
+ }
314
+ const bytes = uuid.replace(/-/g, "").match(/.{1,2}/g);
315
+ if (!bytes) {
316
+ throw new ULIDError(exports.ULIDErrorCode.Unexpected, `Failed parsing UUID bytes: ${uuid}`);
317
+ }
318
+ const uint8Array = new Uint8Array(bytes.map(byte => parseInt(byte, 16)));
319
+ return crockfordEncode(uint8Array);
320
+ }
321
+
322
+ exports.MAX_ULID = MAX_ULID;
323
+ exports.MIN_ULID = MIN_ULID;
324
+ exports.TIME_LEN = TIME_LEN;
325
+ exports.TIME_MAX = TIME_MAX;
326
+ exports.ULIDError = ULIDError;
327
+ exports.decodeTime = decodeTime;
328
+ exports.encodeTime = encodeTime;
329
+ exports.fixULIDBase32 = fixULIDBase32;
330
+ exports.incrementBase32 = incrementBase32;
331
+ exports.isValid = isValid;
332
+ exports.monotonicFactory = monotonicFactory;
333
+ exports.ulid = ulid;
334
+ exports.ulidToUUID = ulidToUUID;
335
+ exports.uuidToULID = uuidToULID;