UNPKG

tiny-ulid

Version:
96 lines (95 loc) 3.7 kB
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 }