This page says how Seed makes ids, why every one is a UUID version 7, and what a secret uses instead.
You need:
- Nothing installed. Read it before you add a table, a job or anything else with an id.
Every id is one, whatever it names, including an id other people see. Make it with uuidV7()
from @spy4x/platform/universal/uuid.
An e2e spec imports the same package as @jsr/spy4x__platform/universal/uuid, its npm name.
Playwright's loader finds packages only in node_modules.
A version 7 id starts with the time it was made. New rows sit together in the index and sort by age. A version 4 id is random and scatters over the index.
| What | Examples |
|---|---|
| Records | Groups, notes, tags, invitations, API token rows, a group's change log rows |
| Jobs and outbox rows | The ones Seed queues, and the ones @spy4x/server writes |
| Requests | Idempotency keys and request ids |
| Devices | A browser registered for push |
| Fake billing ids | cs_fake_… and bps_fake_… of the development billing provider |
| Test fixtures | Throwaway schemas and e-mail addresses |
A name cut from an id takes its end, uuidV7().slice(-8). Its start is the clock and repeats for
about a minute. For the same reason, the profile page labels a device by the last eight characters
of its id.
The browser makes the id of a note, a tag and a group, online and offline. A retry or a queued change then names the same record.
SQL never makes an id: the application passes it in, and no uuid column has a default. Postgres
18 has uuidv7(), so SQL may make one where no caller needs to know it first. Nothing here does
yet.
A secret is a value that alone lets its holder in: a session token, a private share link, an API
key, a cookie secret. A version 7 id shows when it was made and is easier to guess. So a secret
may need randomBase64Url() from @spy4x/platform/tokens instead.
That choice is the owner's. If you find a secret made as a UUID, leave the call as it is and ask. Name the value and what a correct guess would give.
Two values still wait on that decision. Each is a version 4 UUID from Web Crypto:
| File | Value |
|---|---|
apps/worker/demo/seed.ts |
The cookie secret of the seed script's own session manager |
libs/server/billing/setup.ts |
The webhook secret of the billing client in a process with no webhooks |
Code that already makes a value that is no id stays as it is. That covers a password hash, a
salt, a nonce, a key, a code and a randomBase64Url() token such as an invitation link's.
The idempotency claim token comes from @spy4x/server as a version 7 UUID, from 1.64.1 on. So
idempotency_keys.claim_token stays uuid, with no default.
Rows and offline changes made before this rule carry version 4 ids. They are never rewritten.
Every validator accepts a lower-case UUID of any version: use isUuid and UUID_PATTERN from
@spy4x/platform/universal/uuid.
Do not write a pattern that names a version. A test that needs an old id writes it as a literal string.
tests/no-uuid-v4.test.ts runs in deno task check. It reads every text file git tracks, whatever
its extension, and fails on any of these:
- a call that makes a version 4 id, in TypeScript or SQL;
- a pattern or helper that accepts only version 4.
Two old migrations are allowed by their exact names, because applied migrations are history. A new migration is checked. The two secrets above are allowed by their exact line, so a second call in either file fails.