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
x30
x30
x30
x366
x366
x30
x30
 
x9
x27
x27
x27
x27
x27
x54
x54
x54
x55
x55
x54
x64
x64
x64
x70
x70
x28
x27
 
 
x9
x20
x20
x20
x43
x55
x55
x55
x54
x54
x54
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;
  };
}