ULID
Universally Unique Lexicographically Sortable Identifier
ulid.mjs
js
const ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"; // Crockford's Base32
const TIME_LEN = 10;
const RANDOM_LEN = 16;
/**
* Convert a number to base32 encoded string
*
* @param {number} value
* @param {number} length
* @returns {string}
*/
function encodeBase32(value, length) {
let encoded = "";
while (length--) {
encoded = ENCODING[value % 32] + encoded;
value = Math.floor(value / 32);
}
return encoded;
}
/**
* Get current time in milliseconds
*
* @returns {string} ULID time part
*/
function getTimePart() {
const time = Date.now();
return encodeBase32(time, TIME_LEN);
}
/**
* Generate randomness
*
* @returns {string} ULID random part
*/
function getRandomPart() {
let randomPart = "";
for (let i = 0; i < RANDOM_LEN; i++) {
randomPart += ENCODING[Math.floor(Math.random() * 32)];
}
return randomPart;
}
/**
* Combine time part and random part
*
* @returns {string} ULID
*/
export const ulid = () => getTimePart() + getRandomPart();
/**
* Convert base32 encoded string to a number
*
* @param {string} encoded
* @returns {number}
*/
function decodeBase32(encoded) {
let value = 0;
for (let i = 0; i < encoded.length; i++) {
value = value * 32 + ENCODING.indexOf(encoded[i]);
}
return value;
}
/**
* Convert the time part back to milliseconds
*
* @param {string} ulid
* @returns {number}
*/
export const getTimeFromUlid = ulid => {
// Extract the time part...
const timePart = ulid.substring(0, TIME_LEN);
return decodeBase32(timePart);
};
ulid.spec.mjs
js
import { test, expect } from "vitest";
import { ulid, getTimeFromUlid } from "./index.mjs";
test("it should combine time part and random part", () => {
const id = ulid();
expect(id.substring(0, 2)).toEqual("01");
expect(id).toHaveLength(26);
});
test("it should convert the time part back to milliseconds", () => {
const time = new Date(getTimeFromUlid("01J9H6RFH20B3MSN0CE487E5EG"));
expect(time.getDate()).toEqual(6);
expect(time.getMonth()).toEqual(9);
expect(time.getFullYear()).toEqual(2024);
});