All files / uuid / v7.ts

100.00% Branches 6/6
100.00% Functions 3/3
100.00% Lines 27/27
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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
x16
 
x16
x16
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
x16
x10014
x10014
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
x16
x10010
x10010
 
x10010
x3
x3
 
x3
x10007
x10007
 
x10007
 
x10007
x10007
x10010
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
x16
x7
x2
x2
 
x2
x5
x5
x7










































































































// Copyright 2018-2026 the Deno authors. MIT license.
// This module is browser compatible.

/**
 * Functions for working with UUID Version 7 strings.
 *
 * UUID Version 7 is defined in {@link https://www.rfc-editor.org/rfc/rfc9562.html#section-5.7 | RFC 9562}.
 *
 * ```ts
 * import { generate, validate, extractTimestamp } from "@std/uuid/v7";
 * import { assert, assertEquals } from "@std/assert";
 *
 * const uuid = generate();
 * assert(validate(uuid));
 * assertEquals(extractTimestamp("017f22e2-79b0-7cc3-98c4-dc0c0c07398f"), 1645557742000);
 * ```
 *
 * @module
 */

import { bytesToUuid } from "./_common.ts";

const UUID_REGEXP =
  /^[0-9a-f]{8}-[0-9a-f]{4}-[7][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
/**
 * Determines whether a string is a valid
 * {@link https://www.rfc-editor.org/rfc/rfc9562.html#section-5.7 | UUIDv7}.
 *
 * @param id UUID value.
 *
 * @returns `true` if the string is a valid UUIDv7, otherwise `false`.
 *
 * @example Usage
 * ```ts
 * import { validate } from "@std/uuid/v7";
 * import { assert, assertFalse } from "@std/assert";
 *
 * assert(validate("017f22e2-79b0-7cc3-98c4-dc0c0c07398f"));
 * assertFalse(validate("fac8c1e0-ad1a-4204-a0d0-8126ae84495d"));
 * ```
 */
export function validate(id: string): boolean {
  return UUID_REGEXP.test(id);
}

/**
 * Generates a {@link https://www.rfc-editor.org/rfc/rfc9562.html#section-5.7 | UUIDv7}.
 *
 * @throws {RangeError} If the timestamp is not a non-negative integer.
 *
 * @param timestamp Unix Epoch timestamp in milliseconds.
 *
 * @returns Returns a UUIDv7 string
 *
 * @example Usage
 * ```ts
 * import { generate, validate } from "@std/uuid/v7";
 * import { assert } from "@std/assert";
 *
 * const uuid = generate();
 * assert(validate(uuid));
 * ```
 */
export function generate(timestamp: number = Date.now()): string {
  const bytes = new Uint8Array(16);
  const view = new DataView(bytes.buffer);
  // Unix timestamp in milliseconds (truncated to 48 bits)
  if (!Number.isInteger(timestamp) || timestamp < 0) {
    throw new RangeError(
      `Cannot generate UUID as timestamp must be a non-negative integer: timestamp ${timestamp}`,
    );
  }
  view.setBigUint64(0, BigInt(timestamp) << 16n);
  crypto.getRandomValues(bytes.subarray(6));
  // Version (4 bits) Occupies bits 48 through 51 of octet 6.
  view.setUint8(6, (view.getUint8(6) & 0b00001111) | 0b01110000);
  // Variant (2 bits) Occupies bits 64 through 65 of octet 8.
  view.setUint8(8, (view.getUint8(8) & 0b00111111) | 0b10000000);
  return bytesToUuid(bytes);
}

/**
 * Extracts the timestamp from a UUIDv7.
 *
 * @param uuid UUIDv7 string to extract the timestamp from.
 * @returns Returns the timestamp in milliseconds.
 *
 * @throws {TypeError} If the UUID is not a valid UUIDv7.
 *
 * @example Usage
 * ```ts
 * import { extractTimestamp } from "@std/uuid/v7";
 * import { assertEquals } from "@std/assert";
 *
 * const uuid = "017f22e2-79b0-7cc3-98c4-dc0c0c07398f";
 * const timestamp = extractTimestamp(uuid);
 * assertEquals(timestamp, 1645557742000);
 * ```
 */
export function extractTimestamp(uuid: string): number {
  if (!validate(uuid)) {
    throw new TypeError(
      `Cannot extract timestamp because the UUID is not a valid UUIDv7: uuid is "${uuid}"`,
    );
  }
  const timestampHex = uuid.slice(0, 8) + uuid.slice(9, 13);
  return parseInt(timestampHex, 16);
}