
# UUID

> uuidv4 for identifiers that reveal nothing, uuidv7 for identifiers that sort by creation time, and a generator when ordering has to be strict.

::note
**ELI5.** A UUID is a 128-bit identifier written as 36 characters, made so that two machines can each invent one and never collide. Version 4 is entirely random. Version 7 puts a millisecond timestamp in the front and randomness behind it, so sorting the strings sorts the records by when they were created — which is what databases want from a primary key. Reach for v7 by default. Reach for v4 when the creation time is itself something you would rather not publish.
::

```ts
import { uuidv4, uuidv7, secureUUID } from "unsecure/uuid";

uuidv4(); // "ec4fba09-1ab4-4917-a29d-6fe7ab0e0f70"
uuidv7(); // "01a076ba-1e53-7411-8518-84905f353e3d"
secureUUID(); // the same function as uuidv7, under a version-agnostic name
```

All of it is backed by `crypto.getRandomValues` and RFC 9562.

## Which one to use

| What you need                                           | Use                             |
| ------------------------------------------------------- | ------------------------------- |
| A random identifier, version not part of the contract   | `secureUUID()`                  |
| A sortable database key, scattered calls                | `uuidv7()`                      |
| A sortable database key with strictly increasing output | `createUUIDv7Generator()`       |
| To backfill historical events out of order              | `uuidv7(date)` or the generator |
| An identifier that leaks nothing, not even a timestamp  | `uuidv4()`                      |
| The creation time of an existing v7                     | `uuidv7Timestamp()`             |
| To check a string really is a canonical v4 or v7        | `isUUIDv4()` / `isUUIDv7()`     |

## Signatures

```ts
function uuidv4(): string;
function uuidv7(timestamp?: Date | number): string;
const secureUUID: typeof uuidv7;

interface UUIDv7Generator {
  next(timestamp?: Date | number): string;
}
function createUUIDv7Generator(): UUIDv7Generator;

function uuidv7Timestamp(uuid: string): number;
function isUUIDv4(value: unknown): value is string;
function isUUIDv7(value: unknown): value is string;
```

## Embedding a timestamp

`uuidv7()` and `gen.next()` take an optional `Date` or Unix-millisecond `number` in place of `Date.now()`. Useful for tests, backfills and replays.

```ts
uuidv7(new Date("2020-01-01")); // "016f5e66-e800-778d-b4bc-ddd693ea9f20"
uuidv7(1_609_459_200_000); // "0176bb3e-7000-7831-a29f-5be8dd047233"
```

Fractional milliseconds are floored. The value must be finite, non-negative and within `[0, 2^48 - 1]`, or it is `OUT_OF_RANGE`. Anything that is not a `Date` or a `number` is `INVALID_TYPE`.

## Reading the timestamp back

```ts
import { isUUIDv7, uuidv7Timestamp } from "unsecure/uuid";

const id = uuidv7(new Date("2020-01-01T00:00:00Z"));
uuidv7Timestamp(id); // 1577836800000
new Date(uuidv7Timestamp(id)).toISOString(); // "2020-01-01T00:00:00.000Z"
```

Only a v7 carries a timestamp. A v4's first 48 bits are random, so `uuidv7Timestamp` throws `MALFORMED` for one. Gate the call:

```ts
if (isUUIDv7(input)) {
  const created = new Date(uuidv7Timestamp(input));
}
```

The guards are case-insensitive and check the format, the version nibble and the variant bits.

## The monotonic generator

`uuidv7()` fills its random fields with pure random bits, so two UUIDs made in the same millisecond may sort in either order. When you need the stronger guarantee — sequential inserts landing on the same index page — use the generator.

```ts
import { createUUIDv7Generator } from "unsecure/uuid";

const gen = createUUIDv7Generator();
gen.next(); // "01a076ba-1e53-77f8-9627-b0e8f4e20d0a"
gen.next(); // "01a076ba-1e53-77f9-89f0-1b72b99c26a5"
gen.next(); // "01a076ba-1e53-77fa-9109-7dea2727cf00"
```

With no argument every emitted UUID sorts after the previous one, through same-millisecond bursts, counter overflow and clock regressions.

### How it holds

The generator uses a dual clock. The 12-bit `rand_a` field becomes a monotonic counter (RFC 9562 §6.2 Method 3), and both the counter and its reference timestamp are driven **only** by `Date.now()`. A caller-supplied `timestamp` controls the embedded timestamp field of the output and nothing else.

- **Seed.** On each new wall-clock millisecond the counter is reseeded to a random value in `[0, 0x7ff]`, the lower half of its range. That leaves at least 2048 increments of headroom.
- **Overflow.** Past 4095 UUIDs in one millisecond, the internal reference advances by 1 ms and the counter reseeds. Uniqueness holds.
- **Clock regression.** If `Date.now()` goes backwards — an NTP adjustment, a VM pause — the reference is held and the counter keeps incrementing.
- **Caller timestamps do not perturb state.** `gen.next(pastTs)` embeds `pastTs` verbatim while the counter still advances from the wall clock, which is what makes out-of-order backfills safe.
- **Scope is one process.** Two generators, or two processes, share the counter field but stay distinguishable through the 62 random bits of `rand_b`.

A `next()` call that throws does not mutate the generator's state.

## The v7 layout

```
 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                           unix_ts_ms                          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|          unix_ts_ms           |  ver  |       rand_a          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|var|                        rand_b                             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                            rand_b                             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
```

- 48-bit `unix_ts_ms`, big-endian, milliseconds since the epoch. Good through roughly the year 10,895.
- A 4-bit version nibble, `0111`.
- 12-bit `rand_a`: random in `uuidv7()`, the counter in the generator.
- A 2-bit variant, `10`.
- 62-bit `rand_b`, always random.

## Errors

| Cause                                                    | `code`         |
| -------------------------------------------------------- | -------------- |
| A `timestamp` that is not a `Date` or a `number`         | `INVALID_TYPE` |
| A `timestamp` outside `[0, 2^48 - 1]`, or not finite     | `OUT_OF_RANGE` |
| `uuidv7Timestamp` on a string that is not a canonical v7 | `MALFORMED`    |
| `uuidv7Timestamp` on a value that is not a string        | `INVALID_TYPE` |

## Pitfall: assuming `uuidv7()` is strictly monotonic

Two UUIDs from the same millisecond may appear in either order. Across different milliseconds the ordering is guaranteed. Use the generator when you need more than that.

## Pitfall: mixing caller timestamps and call order

The embedded timestamp dominates lexical order, so mixing `next()` and `next(pastTs)` gives UUIDs that sort by their embedded time, not by when you called.

```ts
const a = gen.next(); // embeds Date.now()
const b = gen.next(new Date("2020-01-01")); // b sorts before a
```

That is usually what you want for a database key. If you need "latest inserted sorts last", feed ascending timestamps or omit the argument. Two UUIDs carrying the same embedded timestamp sort by their counter only when both calls fall in the same wall-clock millisecond of this process; a pair straddling a boundary sorts in either order. They are still unique.

## Pitfall: reading sub-millisecond precision

The embedded timestamp is millisecond precision by construction. `uuidv7Timestamp()` cannot tell you anything finer. If ordering within a millisecond matters, use the generator — its counter encodes order, not time.

## Note on `uuidv4()`

This is not `crypto.randomUUID()`. The randomness source is the same, but the version and variant bits are set explicitly so the implementation is self-contained and does not shift if a runtime changes its shortcut.
