- TypeScript 100%
| tests | ||
| .gitignore | ||
| cli.ts | ||
| deno.json | ||
| lurid.ts | ||
| README.md | ||
| word_bytes.ts | ||
lurid
(l)exigraphical (U)TC and (r)andomness (id)entifier = lurid
lexicographically sortable, human-readable ids.
comparison
| format | human readable? | lexicographically sortable? |
|---|---|---|
| uuid | no | no |
| ksuid | no | yes |
| lurid | yes | yes |
uuids are not sortable. ksuids are but you cannot easily say them aloud. lurids are both — every byte maps to a common short word so anyone can read one back, and time is encoded in the first seven words so they sort naturally.
format
a lurid is 10 words separated by dashes, for example:
able-flat-give-call-spot-type-deck-soft-need-last
each word is a byte mapped to one of 256 common four-letter words. 7 bytes of timestamp + 3 bytes of randomness from crypto.getRandomValues().
roughly, they're:
time-time-time-time-time-time-time-rand-rand-rand
examples
generate a handful of lurids back-to-back:
[machine:~/] for run in {1..10}; do lurid; done
able-flat-give-burn-star-more-ring-land-cone-over
able-flat-give-burn-star-near-inch-coat-wand-name
able-flat-give-burn-star-noon-wind-flow-left-gone
able-flat-give-burn-star-over-both-slow-gold-trot
able-flat-give-burn-star-path-made-room-food-have
able-flat-give-burn-star-plod-your-long-soft-that
able-flat-give-burn-star-push-iron-fact-glad-land
able-flat-give-burn-star-real-ring-mark-tidy-tort
able-flat-give-burn-star-road-burn-book-wind-push
able-flat-give-burn-star-rope-gold-limo-neck-page
note they're sorted (lexicographically ordered).
let's parse one:
[machine:~/] lurid able-flat-give-burn-star-more-ring-land-cone-over
{
valid: true,
input: "able-flat-give-burn-star-more-ring-land-cone-over",
words: [
"able", "flat",
"give", "burn",
"star", "more",
"ring", "land",
"cone", "over"
],
time_words: [
"able", "flat",
"give", "burn",
"star", "more",
"ring"
],
time_values: [
0, 63, 72, 20,
191, 129, 160
],
time_as_int: 17812177482260480,
time_as_double: 1781217748226.048,
time_string: "2026-06-11T22:42:28.226Z",
randomness_words: [ "land", "cone", "over" ],
randomness_bytes: [ 104, 30, 142 ],
randomness_value: 9313896
}
use --time-only to just get the utc date string:
[machine:~/] lurid --time-only able-flat-give-burn-star-more-ring-land-cone-over
2026-06-11T22:42:28.226Z
cli usage
install and run:
deno install -gA jsr:@andyburke/lurid/cli
lurid
able-flat-give-burn-with-wife-push-fade-crop-mean
options:
-h, --help show this help message
-v, --version show the version number
-b, --basis set the basis value for time (unix epoch by default)
-s, --separator set the word separator (default: -)
-t, --time-only show only the extracted utc date string when parsing
api
import { default: lurid, parse, get_utc_string } from 'jsr:@andyburke/lurid';
const id = lurid(); // 'able-flat-give-busy-blow-with-room-real-cold-keep'
const parsed = parse( id );
const utc = get_utc_string( id ); // '2026-06-11T22:44:38.197Z'
// custom time basis (unix epoch offset) and separator
const id_with_different_basis = lurid( 778946684800, '_' ); // 'able_cove_rail_race_stem_huge_ball_soft_wish_mark'
parse returns:
{
valid: boolean,
input: string,
words: string[], // all 10 words
time_words: string[], // first 7 words
time_values: number[], // byte values of time words
time_as_int: number, // timestamp as integer (ms * 10000)
time_as_double: number, // timestamp as ms since basis
time_string: string, // ISO 8601 UTC date
randomness_words: string[], // last 3 words
randomness_bytes: number[], // byte values of randomness words
randomness_value: number // combined randomness int
}
word bytes (dictionary)
the dictionary is exported separately for your own encoding needs:
import { convert_to_words, convert_from_words, LURID_WORD_BYTE_DICTIONARY } from '@jsr:@andyburke/lurid/word_bytes';
const words = convert_to_words( [ 0, 62, 35 ] ); // ['able', 'fish', 'cove']
const bytes = convert_from_words( words ); // [ 0, 62, 35 ]
console.dir( {
LURID_WORD_BYTE_DICTIONARY,
words,
bytes
} );
license
MIT