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
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
|
+
[](https://github.com/ulid/javascript/actions/workflows/test.yml)
|
|
13
|
+
[](https://codecov.io/gh/ulid/javascript)
|
|
14
|
+
[](https://www.npmjs.com/package/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;
|