Skip to content
/ ui7 Public

Generate UUIDv7 identifiers in JavaScript (or TypeScript)

License

Notifications You must be signed in to change notification settings

silverlyra/ui7

Repository files navigation

ui7

npm deno.land CI includes TypeScript types node-current MIT license

A small UUIDv7 generator, targeting a proposed update to RFC 4122. v7 UUID’s are lexically sortable by their included timestamp.

import uuid, { timestamp } from "ui7";

const id = uuid();
// ==> "01836531-a895-7a2d-a70d-504ea62b40e2"

const when = new Date(timestamp(id));
// ==> 2022-09-22T12:34:56.789Z

Installation

ui7 is available on NPM and deno.land:

npm

npm install --save ui7

Node.js versions 12.0 and up are supported, and ui7 includes an ESM build. It has no runtime dependencies.

deno

import uuid from "https://deno.land/x/ui7@v0.2.3/mod.ts";

Usage

Default generator

The default UUIDv7 generator is available as both a default export and a named v7 function:

import uuid from "ui7";

console.log(uuid());
// ==> "01836d65-3ec5-789f-b509-9ebd1d5ac2d3"
import { v7 } from "ui7";

console.log(v7());
// ==> "01836d65-4a33-7aeb-aceb-8bfe42901787"

Monotonic generators

Version 7 UUID’s include a millisecond-precision timestamp part and a random part. By default, ui7 will fully populate the random part with random bits. This means that ID’s generated during the same millisecond will sort in an arbitrary order:

import uuid from "ui7";

const ids = [uuid(), uuid(), uuid()];
// [
//   "01836d99-1b31-75c7-bb83-aa52c651b085",
//   "01836d99-1b31-7b6a-8010-98fe59822b40",
//   "01836d99-1b31-71c4-bed3-6d3111c2de7d"
// ]

[...ids].sort().map(id => ids.indexOf(id))
// [ 2, 0, 1 ]

If you want UUID’s generated in the same process to sort in the order they were generated, you can instead create a monotonic generator:

import { generator } from "ui7";

// `generator` uses a monotonic entropy source by default.
const uuid = generator();

const ids = [uuid(), uuid(), uuid()];
// [
//   "01836da2-8a0d-7429-812d-f23f4d3d14c1",
//   "01836da2-8a0d-742a-89a3-0442ac613d11",
//   "01836da2-8a0d-742b-85e0-476f85ee36d9"
// ]

[...ids].sort().map(id => ids.indexOf(id))
// [ 0, 1, 2 ]

This uses the 12-bit rand_a field of UUIDv7 as a counter. For the first timestamp generated in a particular millisecond, the high bit is set to 0, so that there are always at least 2¹¹ (2,048) sequential counter values available before the field overflows (and wraps back to 0).

Retrieving the timestamp

Use the timestamp function to read the timestamp field of a UUIDv7:

import { timestamp } from "ui7";

const when = timestamp("01836db9-3d80-7df2-9c5e-d66442a3cf21");
// 1663993200000

const date = new Date(when);
// 2022-09-24T04:20:00.000Z

If the input to timestamp is not a UUIDv7, it throws a ParseError exception.

Customizing generation

The default uuid function, the generator factory function, and the functions returned by generator all accept some options to customize their behavior.

Setting the time

You can pass a numeric timestamp, Date object, or custom clock function to a UUID generator or factory:

import uuid from "ui7";

const id1 = uuid(0);
// "00000000-0000-7e2f-898b-a1f288a85661"

const id2 = uuid(new Date("2009-08-23T03:58:16.491Z"));
// "01234567-89ab-772e-9998-11f7395091b3"

const id3 = uuid(() => Date.now() * 2);
// "0306db58-42bc-722f-b2a5-4b13763cc462"

// Pass an object with a `time` property to combine with other options:
const id4 = uuid({ time: 1663993200000 });
// "01836db9-3d80-74a3-8b2a-8bf75bae40d5"

Controlling the format

You can omit dashes from the generated UUID’s, or use upper-case hex characters:

import uuid from "ui7";

const id1 = uuid({ dashes: false });
// "01836db3b12d7f4e88c9fdc29019862e"

const id2 = uuid({ upper: true });
// "01836DB3-E2C7-7C84-8EB7-5CBE9205615B"

const id3 = uuid({ dashes: false, upper: true });
// "01836DB44DEF7836B69838F38A0A2A2A"

Using a different entropy source

By default, ui7 uses crypto.getRandomValues to populate the random part of the UUID (or crypto.randomFillSync on Node.js versions that don't support Web-compatible crypto).

To instead set the "random" bits to either all 0 or all 1, pass a literal 0 or 0xff as the entropy option:

import uuid from "ui7";

const id1 = uuid({ entropy: 0 });
// "018abdfe-4693-7000-8000-000000000000"

const id2 = uuid({ entropy: 0xff });
// "18abfe1-984b-7fff-bfff-ffffffffffff"

Full control is available by providing an entropy function, which must return a Uint8Array with the requested byteLength:

import uuid from "ui7";

const id1 = uuid({ entropy: (n) => new Uint8Array(n) });
// "01836db8-4831-7000-8000-000000000000"

const id2 = uuid({ entropy: (n) => new Uint8Array(n).map((_, i) => i) })
// "01836dbb-bd3d-7001-8203-040506070809"

// The entropy function receives the timestamp of the ID being generated:
const id4 = uuid({
  entropy(n, time) {
    const b = new Uint8Array(n);
    new DataView(b.buffer).setBigUint64(0, BigInt(time));
    return b;
  }
});
// "01836dbd-448c-7000-8183-6dbd448c0000"

Note that the default implementation avoids making a getRandomValues call every time entropy() is called; it makes larger, less infrequent reads to improve performance when many UUID's are being generated.

Creating a preset

In addition to providing options to the default generator, you can pass them to the generator factory function, which will return a generator that uses those options as its defaults:

import { generator } from "ui7";

const uuid = generator({
  time: () => Math.trunc(Date.now() / 1000),
  entropy: 0xff,
  upper: true,
});

const id1 = uuid();
// "0000632F-3ACC-7FFF-BFFF-FFFFFFFFFFFF"

// Options can still be set for each individual ID:
const id2 = uuid({ dashes: false, time: 0 }):
// "0000000000007FFFBFFFFFFFFFFFFFFF"

TypeScript

ui7 is written in TypeScript and ships with type definitions.

Should I use this?

UUID’s are currently defined by RFC 4122 (2005), which specifies three UUID versions in common use: 1, 4, and 5. The “UUIDv7” identifiers generated by this package conform to a proposed update to that standard, but as of this writing (September 2023) it has not yet obsoleted the 2005 edition.

UUIDv7 – a sortable UUID containing a UNIX timestamp – was first proposed in the second draft from April 2021. Originally, UUIDv7 held a variable-precision timestamp, but this was simplified to millisecond precision only in the fourth draft from March 2022. This implementation originally targeted a June 2022 draft.

There are advantages to the UUIDv7 format: they encode a timestamp, while still being collision-resistant with up to 74 bits of entropy per UUID. Their natural sortability by putting the timestamp field first is convenient for display and efficient for insertion into the B-tree structures that underlie many databases. And they're still UUID's, and can be used directly in (e.g.) a Postgres uuid column without conversion.

But you might reasonably decide to say “no” or “not yet” to UUIDv7 until it is specified in an accepted RFC, or to use a different unique identifier format like ULID.

Should I use this implementation?

ui7 is a compact, zero-dependency implementation of UUIDv7, using a cryptographic PRNG provided by its host environment. It is one of the simplest, but neither the fastest nor the slowest implementation; a few others can be found on NPM.