UNPKG

tiny-ulid

Version:
175 lines (174 loc) • 6.9 kB
const Uint8Array_ = Uint8Array, 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") }, isUlidBytes = bytes => 16 == bytes.length, toUlidBytes = bytes => { if (isUlidBytes(bytes)) return bytes throw Error("Uint8Array's length was not 16") }, ulidBufferToBytes = ulidBuffer => new Uint8Array_(ulidBuffer), 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) }, setUlidBytesTime = (ulidBytes, time = Date.now()) => { ulidBytes[0] = time / 2 ** 40 ulidBytes[1] = time / 2 ** 32 ulidBytes[2] = time >> 24 ulidBytes[3] = time >> 16 ulidBytes[4] = time >> 8 ulidBytes[5] = time }, getUlidBufferTime = buffer => { const dataView = new DataView(buffer) return dataView.getUint16(0) * 2 ** 32 + dataView.getUint32(2) }, pool = new Uint8Array_(2560) let poolOffset = 0 const makeUlidBuffer = ({ ulidBuffer = makeEmptyUlidBuffer(), time = Date.now() } = {}) => { setUlidBufferTime(ulidBuffer, time) poolOffset || crypto.getRandomValues(pool) new Uint8Array_(ulidBuffer, 6).set(pool.slice(poolOffset, (poolOffset += 10))) poolOffset %= 2560 return ulidBuffer }, setUlidBytesRandom = ulidBytes => { ;(poolOffset %= 2560) || crypto.getRandomValues(pool) ulidBytes.set(pool.subarray(poolOffset, (poolOffset += 10)), 6) }, makeUlidBytes = () => { const ulidBytes = makeEmptyUlidBytes() setUlidBytesTime(ulidBytes) setUlidBytesRandom(ulidBytes) return ulidBytes }, timeToUlidString = time => { if (time > 0xffffffffffff) throw Error("ULID cannot represent a date more than 48 bits") let result = "" for (let offset = 50; offset;) result += CROCKFORD_BASE32[(time / 2 ** (offset -= 5)) & 31] return result }, ulidBufferRandomPartToUlidString = ulidBuffer => { let result = "" const dataView = new DataView(ulidBuffer) for (let bitOffset = 48; 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)] }, persistedUlidStringBytes = new Uint8Array_(26), textDecoder = new TextDecoder(), CROCKFORD_BASE32_CHAR_CODES = CROCKFORD_BASE32.split("").map(char => char.charCodeAt(0)), ulidBufferToString = ulidBuffer => { const dataView = new DataView(ulidBuffer) persistedUlidStringBytes[0] = CROCKFORD_BASE32_CHAR_CODES[dataView.getUint8(0) >> 5] for (let bitOffset = 3, index = 1; bitOffset < 123; bitOffset += 5) persistedUlidStringBytes[index++] = CROCKFORD_BASE32_CHAR_CODES[ (dataView.getUint16(Math.floor(bitOffset / 8)) >> (11 - (bitOffset % 8))) & 31 ] persistedUlidStringBytes[25] = CROCKFORD_BASE32_CHAR_CODES[31 & dataView.getUint8(15)] return textDecoder.decode(persistedUlidStringBytes) }, makeEmptyUlidBytes = () => new Uint8Array_(16), makeUlidPool = new Uint8Array_(4096) let makeUlidPoolOffset = 0 const makeUlid = () => { const time = Date.now() persistedUlidStringBytes[0] = CROCKFORD_BASE32_CHAR_CODES[(time / 2 ** 45) & 31] persistedUlidStringBytes[1] = CROCKFORD_BASE32_CHAR_CODES[(time / 2 ** 40) & 31] persistedUlidStringBytes[2] = CROCKFORD_BASE32_CHAR_CODES[(time / 2 ** 35) & 31] persistedUlidStringBytes[3] = CROCKFORD_BASE32_CHAR_CODES[(time / 2 ** 30) & 31] for (let index = 4, offset = 30; index < 10;) persistedUlidStringBytes[index++] = CROCKFORD_BASE32_CHAR_CODES[(time >> (offset -= 5)) & 31] ;(makeUlidPoolOffset %= 4096) || crypto.getRandomValues(makeUlidPool) persistedUlidStringBytes.set(makeUlidPool.subarray(makeUlidPoolOffset, (makeUlidPoolOffset += 16)), 10) for (let index = 10; index < 26;) persistedUlidStringBytes[index] = CROCKFORD_BASE32_CHAR_CODES[31 & persistedUlidStringBytes[index++]] return textDecoder.decode(persistedUlidStringBytes) }, 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 bytes = ulidBufferToBytes(ulidBuffer) for (let index = 16; index > 6; index--) if ((bytes[index] = bytes[index] + 1)) return if (throwOnOverflow) throw Error("Overflow when incrementing ULID buffer") }, cloneUlidBuffer = ulidBuffer => { const clone = makeEmptyUlidBytes() clone.set(new Uint8Array_(ulidBuffer)) return clone.buffer }, makeMonotonicallyIncrementingUlidBufferFunction = ({ mitigateOverflow = !0 } = {}) => { const stateUlidBuffer = makeEmptyUlidBuffer(), stateUlidBytes = new Uint8Array_(stateUlidBuffer) return ({ ulidBuffer } = {}) => { if (getUlidBufferTime(stateUlidBuffer) >= Date.now()) incrementUlidBuffer(stateUlidBuffer, { throwOnOverflow: !0 }) else { makeUlidBuffer({ ulidBuffer: stateUlidBuffer }) mitigateOverflow && (stateUlidBytes[6] &= 127) } if (!ulidBuffer) return cloneUlidBuffer(stateUlidBuffer) new Uint8Array_(ulidBuffer).set(stateUlidBytes) return ulidBuffer } } export { cloneUlidBuffer, decodeUlid, getUlidBufferTime, incrementUlidBuffer, isUlid, isUlidBuffer, isUlidBytes, makeEmptyUlidBuffer, makeEmptyUlidBytes, makeMonotonicallyIncrementingUlidBufferFunction, makeUlid, makeUlidBuffer, makeUlidBytes, setUlidBufferTime, setUlidBytesRandom, setUlidBytesTime, timeToUlidString, toUlid, toUlidBuffer, toUlidBytes, ulidBufferRandomPartToUlidString, ulidBufferToBytes, ulidBufferToString }