tiny-ulid
Version:
Make and decode ULIDs.
96 lines (95 loc) • 3.7 kB
JavaScript
const CROCKFORD_BASE32 = "0123456789ABCDEFGHJKMNPQRSTVWXYZ",
ULID_REGEX = /^[0-7][\dA-HJKMNP-TV-Z]{25}$/,
isUlidBuffer = arrayBuffer => 16 == arrayBuffer.byteLength,
toUlidBuffer = arrayBuffer => {
if (isUlidBuffer(arrayBuffer)) return arrayBuffer
throw Error("ArrayBuffer's length was not 16")
},
isUlid = string => ULID_REGEX.test(string),
toUlid = string => {
if (isUlid(string)) return string
throw Error("Invalid ULID.")
},
makeEmptyUlidBuffer = () => new ArrayBuffer(16),
setUlidBufferTime = (buffer, time = Date.now()) => {
const dataView = new DataView(buffer)
dataView.setUint16(0, Math.floor(time / 2 ** 32))
dataView.setUint32(2, time)
},
getUlidBufferTime = buffer => {
const dataView = new DataView(buffer)
return dataView.getUint16(0) * 2 ** 32 + dataView.getUint32(2)
},
makeUlidBuffer = ({ ulidBuffer = makeEmptyUlidBuffer(), time = Date.now() } = {}) => {
setUlidBufferTime(ulidBuffer, time)
crypto.getRandomValues(new Uint8Array(ulidBuffer, 6))
return ulidBuffer
},
makeUlid = ({ time = Date.now(), ulidBuffer = makeUlidBuffer({ time }) } = {}) => {
const dataView = new DataView(ulidBuffer)
let result = CROCKFORD_BASE32[dataView.getUint8(0) >> 5]
for (let bitOffset = 3; bitOffset < 123; bitOffset += 5) {
const byteOffset = Math.floor(bitOffset / 8)
result += CROCKFORD_BASE32[(dataView.getUint16(byteOffset) >> (11 - (bitOffset % 8))) & 31]
}
return result + CROCKFORD_BASE32[31 & dataView.getUint8(15)]
},
decodeUlid = ulid => {
const ulidBuffer = makeUlidBuffer(),
bytes = new Uint8Array(ulidBuffer)
for (let bytesIndex = 0, ulidIndex = 0; bytesIndex < 15; bytesIndex++) {
bytes[bytesIndex] =
(CROCKFORD_BASE32.indexOf(ulid[ulidIndex]) << 5) | CROCKFORD_BASE32.indexOf(ulid[++ulidIndex])
bytes[++bytesIndex] =
(CROCKFORD_BASE32.indexOf(ulid[++ulidIndex]) << 3) | (CROCKFORD_BASE32.indexOf(ulid[++ulidIndex]) >> 2)
bytes[++bytesIndex] =
(CROCKFORD_BASE32.indexOf(ulid[ulidIndex]) << 6) |
(CROCKFORD_BASE32.indexOf(ulid[++ulidIndex]) << 1) |
(CROCKFORD_BASE32.indexOf(ulid[++ulidIndex]) >> 4)
bytes[++bytesIndex] =
(CROCKFORD_BASE32.indexOf(ulid[ulidIndex]) << 4) | (CROCKFORD_BASE32.indexOf(ulid[++ulidIndex]) >> 1)
bytes[++bytesIndex] =
(CROCKFORD_BASE32.indexOf(ulid[ulidIndex]) << 7) |
(CROCKFORD_BASE32.indexOf(ulid[++ulidIndex]) << 2) |
(CROCKFORD_BASE32.indexOf(ulid[++ulidIndex]) >> 3)
}
bytes[15] = (CROCKFORD_BASE32.indexOf(ulid[24]) << 5) | CROCKFORD_BASE32.indexOf(ulid[25])
return ulidBuffer
},
incrementUlidBuffer = (ulidBuffer, { throwOnOverflow = !1 } = {}) => {
const dataView = new DataView(ulidBuffer)
dataView.setBigUint64(8, dataView.getBigUint64(8) + 1n)
if (!dataView.getBigUint64(8)) {
dataView.setUint16(6, dataView.getUint16(6) + 1)
if (throwOnOverflow && !dataView.getUint16(6)) throw Error("Overflow when incrementing ULID buffer")
}
},
cloneUlidBuffer = ulidBuffer => ulidBuffer.slice(),
makeMonotonicallyIncrementingUlidBufferFunction = ({ mitigateOverflow = !0 } = {}) => {
let ulidBuffer
return () => {
if (ulidBuffer && getUlidBufferTime(ulidBuffer) >= Date.now()) {
incrementUlidBuffer(ulidBuffer, { throwOnOverflow: !0 })
return cloneUlidBuffer(ulidBuffer)
}
ulidBuffer = makeUlidBuffer()
setUlidBufferTime(ulidBuffer)
mitigateOverflow && (new Uint8Array(ulidBuffer)[6] &= 127)
return cloneUlidBuffer(ulidBuffer)
}
}
export {
cloneUlidBuffer,
decodeUlid,
getUlidBufferTime,
incrementUlidBuffer,
isUlid,
isUlidBuffer,
makeEmptyUlidBuffer,
makeMonotonicallyIncrementingUlidBufferFunction,
makeUlid,
makeUlidBuffer,
setUlidBufferTime,
toUlid,
toUlidBuffer
}