@neumatter/bvon
Version:
Serialize data into buffer. Browser or NodeJS.
193 lines (153 loc) • 4.13 kB
JavaScript
import randomBytes from '@neumatter/random-bytes'
import ByteView from 'byteview'
let PROCESS_UNIQUE = null
const BASE32_UOID = '23456789ABCDEFGHJKLMNPQRSTUVWXYZ'
const BASE32_UTID_REGEX = /^[2-9A-HJ-NP-Z]{28}$/
function encodeBase32Hex (byteView, padding = false) {
if (!ByteView.isView(byteView)) {
throw new Error('[Base32[encode]] did not recieve valid type')
}
const { length } = byteView
let index = -1
let bits = 0
let value = 0
let response = ''
while (++index < length) {
const byte = byteView[index]
value = (value << 0x8) | byte
bits += 8
while (bits >= 5) {
response += BASE32_UOID[(value >>> (bits - 5)) & 0x1F]
bits -= 5
}
}
if (bits > 0) {
response += BASE32_UOID[(value << (5 - bits)) & 0x1F]
}
if (padding) {
while (response.length % 8 !== 0) {
response += '='
}
}
return response
}
function base32ByteLength (string) {
const { length } = string
let validLength = string.indexOf('=')
if (validLength === -1) validLength = length
const placeHoldersLength = validLength === length
? 0
: length - validLength
return (((length - placeHoldersLength) * 5) / 8) | 0
}
function decodeBase32Hex (string) {
const { length } = string
let bits = 0
let value = 0
let index = 0
const bytes = new ByteView(base32ByteLength(string))
let i = -1
while (++i < length) {
if (string[i] === '=') continue
value = (value << 5) | BASE32_UOID.indexOf(string[i])
bits += 5
if (bits >= 8) {
bytes[index++] = (value >>> (bits - 8)) & 0xFF
bits -= 8
}
}
return bytes.buffer
}
function randomUint24 () {
const bytes = randomBytes(3)
return (
(bytes[0] << 16) |
(bytes[1] << 8) |
bytes[2]
)
}
export default class UOID {
static
static
static cacheHexString
static
return (this.
}
static generate (time) {
if (typeof time !== 'number') {
time = Date.now() / 1000 | 0
}
const inc = this.
const bytes = new ByteView(17)
bytes.setUint32(0, time)
// set PROCESS_UNIQUE if yet not initialized
if (PROCESS_UNIQUE === null) {
PROCESS_UNIQUE = randomBytes(6)
}
bytes[4] = PROCESS_UNIQUE[0]
bytes[5] = PROCESS_UNIQUE[1]
bytes[6] = PROCESS_UNIQUE[2]
bytes[7] = PROCESS_UNIQUE[3]
bytes[8] = PROCESS_UNIQUE[4]
bytes[9] = PROCESS_UNIQUE[5]
const rand = randomBytes(3)
bytes[10] = rand[0]
bytes[11] = rand[1]
bytes[12] = rand[2]
// 3-byte counter
bytes[15] = inc & 0xff
bytes[14] = (inc >> 8) & 0xff
bytes[13] = (inc >> 16) & 0xff
bytes[16] = this.
return bytes
}
constructor (inputId) {
let workingId
if (ByteView.isView(inputId) && inputId.byteLength === 16) {
workingId = encodeBase32Hex(inputId)
} else {
workingId = inputId
}
if (typeof workingId === 'undefined' || workingId === null) {
// Generate a new id
this.
} else if (typeof workingId === 'string') {
if (workingId.length === 28 && BASE32_UTID_REGEX.test(workingId)) {
this.
this.
} else {
throw new Error(
'Argument passed in must be a string of 12 bytes or a string of 24 hex characters or an integer'
)
}
} else {
throw new Error('Argument passed in does not match the accepted types')
}
}
get version () {
return this.
}
getTimestamp () {
return new Date(this.
}
toString () {
if (!this.
this.
}
return this.
}
inspect () {
return `UOID('${this.toString()}')`
}
[] () {
return `UOID(\x1b[32m'${this.toString()}'\x1b[0m)`
}
[] () {
return this.toString()
}
toJSON () {
return this.toString()
}
}