2024-01-01 21:11:32 +00:00
|
|
|
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
|
2023-09-12 11:31:03 +00:00
|
|
|
|
2023-12-01 09:55:52 +00:00
|
|
|
/** Type for a ULID generator function. */
|
|
|
|
export type ULID = (seedTime?: number) => string;
|
2023-09-12 11:31:03 +00:00
|
|
|
|
|
|
|
// These values should NEVER change. If
|
|
|
|
// they do, we're no longer making ulids!
|
|
|
|
export const ENCODING = "0123456789ABCDEFGHJKMNPQRSTVWXYZ"; // Crockford's Base32
|
|
|
|
export const ENCODING_LEN = ENCODING.length;
|
|
|
|
export const TIME_MAX = Math.pow(2, 48) - 1;
|
|
|
|
export const TIME_LEN = 10;
|
|
|
|
export const RANDOM_LEN = 16;
|
2024-06-11 08:12:36 +00:00
|
|
|
export const ULID_LEN = TIME_LEN + RANDOM_LEN;
|
2023-09-12 11:31:03 +00:00
|
|
|
|
|
|
|
function replaceCharAt(str: string, index: number, char: string) {
|
|
|
|
return str.substring(0, index) + char + str.substring(index + 1);
|
|
|
|
}
|
|
|
|
|
2024-06-17 03:11:11 +00:00
|
|
|
export function encodeTime(timestamp: number): string {
|
|
|
|
if (!Number.isInteger(timestamp) || timestamp < 0 || timestamp > TIME_MAX) {
|
2024-06-11 08:12:36 +00:00
|
|
|
throw new RangeError(
|
2024-06-17 03:11:11 +00:00
|
|
|
`Time must be a positive integer less than ${TIME_MAX}`,
|
2024-06-11 08:12:36 +00:00
|
|
|
);
|
2023-09-12 11:31:03 +00:00
|
|
|
}
|
|
|
|
let str = "";
|
2024-06-17 03:11:11 +00:00
|
|
|
for (let len = TIME_LEN; len > 0; len--) {
|
|
|
|
const mod = timestamp % ENCODING_LEN;
|
2023-09-12 11:31:03 +00:00
|
|
|
str = ENCODING[mod] + str;
|
2024-06-17 03:11:11 +00:00
|
|
|
timestamp = Math.floor(timestamp / ENCODING_LEN);
|
2023-09-12 11:31:03 +00:00
|
|
|
}
|
|
|
|
return str;
|
|
|
|
}
|
|
|
|
|
2024-06-17 03:11:11 +00:00
|
|
|
export function encodeRandom(): string {
|
2023-09-12 11:31:03 +00:00
|
|
|
let str = "";
|
2024-06-17 03:11:11 +00:00
|
|
|
const bytes = crypto.getRandomValues(new Uint8Array(RANDOM_LEN));
|
|
|
|
for (const byte of bytes) {
|
|
|
|
str += ENCODING[byte % ENCODING_LEN];
|
2023-09-12 11:31:03 +00:00
|
|
|
}
|
|
|
|
return str;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function incrementBase32(str: string): string {
|
|
|
|
let index = str.length;
|
|
|
|
let char;
|
|
|
|
let charIndex;
|
|
|
|
const maxCharIndex = ENCODING_LEN - 1;
|
|
|
|
while (--index >= 0) {
|
2024-01-10 20:26:34 +00:00
|
|
|
char = str[index]!;
|
2023-09-12 11:31:03 +00:00
|
|
|
charIndex = ENCODING.indexOf(char);
|
|
|
|
if (charIndex === -1) {
|
2024-06-11 08:12:36 +00:00
|
|
|
throw new TypeError("Incorrectly encoded string");
|
2023-09-12 11:31:03 +00:00
|
|
|
}
|
|
|
|
if (charIndex === maxCharIndex) {
|
2024-01-10 20:26:34 +00:00
|
|
|
str = replaceCharAt(str, index, ENCODING[0]!);
|
2023-09-12 11:31:03 +00:00
|
|
|
continue;
|
|
|
|
}
|
2024-01-10 20:26:34 +00:00
|
|
|
return replaceCharAt(str, index, ENCODING[charIndex + 1]!);
|
2023-09-12 11:31:03 +00:00
|
|
|
}
|
2024-06-11 08:12:36 +00:00
|
|
|
throw new Error("Cannot increment this string");
|
2023-09-12 11:31:03 +00:00
|
|
|
}
|
|
|
|
|
2023-11-30 15:08:19 +00:00
|
|
|
/** Generates a monotonically increasing ULID. */
|
2023-09-12 11:31:03 +00:00
|
|
|
export function monotonicFactory(encodeRand = encodeRandom): ULID {
|
|
|
|
let lastTime = 0;
|
|
|
|
let lastRandom: string;
|
|
|
|
return function ulid(seedTime: number = Date.now()): string {
|
|
|
|
if (seedTime <= lastTime) {
|
|
|
|
const incrementedRandom = (lastRandom = incrementBase32(lastRandom));
|
2024-06-17 03:11:11 +00:00
|
|
|
return encodeTime(lastTime) + incrementedRandom;
|
2023-09-12 11:31:03 +00:00
|
|
|
}
|
|
|
|
lastTime = seedTime;
|
2024-06-17 03:11:11 +00:00
|
|
|
const newRandom = (lastRandom = encodeRand());
|
|
|
|
return encodeTime(seedTime) + newRandom;
|
2023-09-12 11:31:03 +00:00
|
|
|
};
|
|
|
|
}
|