All files / ulid / _util.ts

100.00% Branches 15/15
100.00% Lines 62/62
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
 
 
 
 
 
 
 
 
x9
x9
x9
x9
x9
x9
 
x35
x35
x35
 
x9
x50
x55
x55
 
x55
x86
x50
x410
x410
x410
x410
x86
x50
 
x9
x29
x29
x29
x349
x349
x29
x29
 
x9
x28
x28
x28
x28
x28
x55
x55
x55
x56
x56
x55
x64
x64
x64
x72
x72
x29
x28
 
 
x9
x20
x20
x20
x43
x56
x56
x56
x53
x53
x53
x20
x20











































































// Copyright 2018-2025 the Deno authors. MIT license.

/** Type for a ULID generator function. */
// deno-lint-ignore deno-style-guide/naming-convention
export type ULID = (seedTime?: number) => string;

// 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;
export const ULID_LEN = TIME_LEN + RANDOM_LEN;

function replaceCharAt(str: string, index: number, char: string) {
  return str.substring(0, index) + char + str.substring(index + 1);
}

export function encodeTime(timestamp: number): string {
  if (!Number.isInteger(timestamp) || timestamp < 0 || timestamp > TIME_MAX) {
    throw new RangeError(
      `Time must be a positive integer less than ${TIME_MAX}`,
    );
  }
  let str = "";
  for (let len = TIME_LEN; len > 0; len--) {
    const mod = timestamp % ENCODING_LEN;
    str = ENCODING[mod] + str;
    timestamp = Math.floor(timestamp / ENCODING_LEN);
  }
  return str;
}

export function encodeRandom(): string {
  let str = "";
  const bytes = crypto.getRandomValues(new Uint8Array(RANDOM_LEN));
  for (const byte of bytes) {
    str += ENCODING[byte % ENCODING_LEN];
  }
  return str;
}

export function incrementBase32(str: string): string {
  let index = str.length;
  let char;
  let charIndex;
  const maxCharIndex = ENCODING_LEN - 1;
  while (--index >= 0) {
    char = str[index]!;
    charIndex = ENCODING.indexOf(char);
    if (charIndex === -1) {
      throw new TypeError("Incorrectly encoded string");
    }
    if (charIndex === maxCharIndex) {
      str = replaceCharAt(str, index, ENCODING[0]!);
      continue;
    }
    return replaceCharAt(str, index, ENCODING[charIndex + 1]!);
  }
  throw new Error("Cannot increment this string");
}

/** Generates a monotonically increasing ULID. */
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));
      return encodeTime(lastTime) + incrementedRandom;
    }
    lastTime = seedTime;
    const newRandom = (lastRandom = encodeRand());
    return encodeTime(seedTime) + newRandom;
  };
}