supernova-ai
v0.1.0
Published
Short, sortable, prefixed IDs (org_0slcSpOdnti) backed by the Web Crypto CSPRNG. Zero dependencies.
Maintainers
Readme
supernova-ai
Short, sortable, prefixed identifiers — the ID scheme we use across Supernova. Zero dependencies.
import { generateId } from "supernova-ai";
generateId("org"); // → "org_0slcSpOdnti"
generateId("app"); // → "app_0slcSsdWhkY"Each ID is a prefix, a fixed-width base62 timestamp (milliseconds since 2025-01-01), and a few random characters. That gives you three things a UUID doesn't:
- Readable. The prefix tells you what the ID points at, in a log line or a URL, without looking it up.
- Sortable. The timestamp leads and is fixed-width over an ASCII-ordered
alphabet, so plain lexicographic order —
sort,order by, a B-tree — is creation order to the millisecond. Inserts stay at the right edge of the index instead of scattering through it. - Short. 15 characters for a 3-character prefix, against 36 for a UUID.
API
generateId(prefix, options?)
randomness— number of random characters appended after the timestamp. Default4.separator— default"_".
The return type is templated, so generateId("org") is typed as
`org_${string}` and survives being passed around as a branded ID.
randomString(length, alphabet?)
A cryptographically secure random string, rejection-sampled to avoid modulo
bias. Suitable for tokens and secrets — a length-n string over the default
62-character alphabet carries n * log2(62) ≈ 5.95n bits of entropy.
Choosing randomness
IDs are unique by timestamp plus randomness. They are not coordinated across
processes, so uniqueness is a birthday problem within a single millisecond:
k IDs minted in the same millisecond over a space of 62^randomness collide
with probability about k² / (2 · 62^randomness).
The default of 4 characters (14.8M) is sized for the case where a millisecond holds a handful of IDs, which is what request-driven code actually does. Minting in a tight loop is where it runs out — measured on one core:
| randomness | space | collisions per 200k minted at ~200 ids/ms |
| --- | --- | --- |
| 4 (default) | 1.5e7 | a few |
| 6 | 5.7e10 | none observed |
| 8 | 2.2e14 | none observed |
So: leave it at 4 for records created by requests, jobs, and users. Raise it to 6 or 8 when a loop mints IDs continuously, or when many machines mint against the same prefix at once. It costs one character each.
Notes
Randomness comes from Web Crypto crypto.getRandomValues, a global in Node 18+,
Deno, Bun, Cloudflare Workers, and browsers. Math.random() is never used.
The 7-character timestamp is fixed-width deliberately: a variable-width encoding stops sorting the moment it gains a character. 7 base62 characters carry the clock to the year 2136.
License
MIT
