kuuid
Version:
Time-sortable UUID - roughly time-sortable unique id generator
136 lines (119 loc) • 3.56 kB
JavaScript
// standard Node.js crypto library
import * as crypto from 'node:crypto'
import { ts } from './lib/ts.js'
import { tsms } from './lib/tsms.js'
import { base62Encode } from './lib/base62.js'
// the maximum timestamp achievable (8 digits of base 62)
const maxTS = Math.pow(62, 8) - 1
// calculate an 8-digit prefix for the timestamp 't'
// that is base62 encoded and sorts in time order
export function prefix(opts) {
if (typeof opts === 'string' || typeof opts === 'number') {
opts = {
timestamp: opts,
reverse: false,
random: 4,
millisecond: false
}
} else {
opts = opts || {}
}
// get time stamp for now
let timestamp = opts.millisecond ? tsms(opts.timestamp) : ts(opts.timestamp)
if (opts.reverse) {
timestamp = maxTS - timestamp
}
// turn timestamp into 8-digit, base-62 encoded string
return base62Encode(timestamp).padStart(8, '0')
}
export function rand(n) {
if (!n || n < 1 || n > 4) {
n = 4
}
// we want 128-bits of random data. To do this we
// add 4 batches of 4 random bytes encoded as 6-digit, base-62 encoded strings
let randomStr = ''
for (let i = 0; i < n; i++) {
const rand = crypto.randomBytes(4).toString('hex')
randomStr += base62Encode(parseInt(rand, 16)).padStart(6, '0')
}
return randomStr
}
// generate a kuuid
export function id(opts) {
opts = opts || {}
const ty = typeof opts
if (['string', 'number'].includes(ty)) {
return prefix(opts) + rand()
}
if (ty === 'object') {
opts.timestamp = opts.timestamp ? opts.timestamp : undefined
opts.random = opts.random ? opts.random : 4
opts.reverse = !!opts.reverse
opts.millisecond = !!opts.millisecond
return prefix(opts) + rand(opts.random)
}
throw new Error('invalid parameters')
}
// generate a kuuid with ms
export function idms(t) {
return id({ timestamp: t, millisecond: true })
}
// generate a kuuid (reverse mode)
export function idr(t) {
return id({ timestamp: t, reverse: true })
}
// generate a kuuid (reverse mode)
export function idmsr(t) {
return id({ timestamp: t, reverse: true, millisecond: true })
}
// generate short id
export function ids(t) {
return id({ timestamp: t, random: 2 })
}
// generate short id (reverse)
export function idsr(t) {
return id({ timestamp: t, reverse: true, random: 2 })
}
// prefix milliseconds
export function prefixms(t) {
return prefix({ timestamp: t, millisecond: true })
}
// prefix reverse
export function prefixReverse(t) {
return prefix({ timestamp: t, reverse: true })
}
// prefix milliseconds reverse
export function prefixReverseMs(t) {
return prefix({ timestamp: t, reverse: true, millisecond: true })
}
let counter = 0
let lastT = 0
export function v7s(t) {
t = tsms(t)
if (t !== lastT) {
lastT = t
counter = 0
} else {
counter++
}
const prefix = t.toString(16).padStart(12, '0')
const version = '7'
const counterStr = counter.toString(16).padStart(3, '0')
const fodder = crypto.randomBytes(8).toString('hex').split('')
let variantNibble = parseInt(fodder[0], 16)
// set bits 64 & 65 to 1 and 0 respectively
variantNibble |= 0x8 // set the first bit 1000
variantNibble &= 0xb // unset the second bit: mask 1011
fodder[0] = variantNibble.toString(16)
return prefix + version + counterStr + fodder.join('')
}
export function v7(t) {
const DELIMITER = '-'
const arr = v7s(t).split('')
arr.splice(8, 0, DELIMITER)
arr.splice(13, 0, DELIMITER)
arr.splice(18, 0, DELIMITER)
arr.splice(23, 0, DELIMITER)
return arr.join('')
}