UNPKG

cluster-id

Version:

Database cluster friendly object id with great query isolation.

101 lines (89 loc) 3.62 kB
/* Generator of cluster friendly ids that can be converted to 64-bit integers for compact storing in Redis structures. */ // TODO: make variable or padding encoding for both counter & sequence because no separator // TODO: replace swap bits with 2^5 array length // TODO: compare distribution of values (even without html5) shortid and CompactId // make tests // default start date for the id generator var ref = require('./base64'); var encode = ref.encode; var maxNum = ref.maxNum; var BASE = ref.BASE; var ref$1 = require('./parse'); var parseScope = ref$1.parseScope; var createCounter = require('./counter') var ref$2 = require('./config'); var SEGMENT_LEN = ref$2.SEGMENT_LEN; var META_LEN = ref$2.META_LEN; var TIME_LEN = ref$2.TIME_LEN; var MIN_INSTANCE_LEN = 2 var MAX_INSTANCE_LEN = 4 var MIN_COUNTER_LEN = 2 var DEFAULT_EPOCH = 0 // 1970-01-01 function assert (condition, message) { if (!condition) { throw new Error(message || 'Assert failed') } } function getSegmentStr (segment, scopeIdStr) { if (typeof segment === 'number') { assert(segment >= 0 && segment < BASE, ("Invalid segment: " + segment)) return encode(segment, SEGMENT_LEN) } else if (scopeIdStr) { // use segment of scope return scopeIdStr.slice(0, SEGMENT_LEN) } else if (!segment) { return encode(0, SEGMENT_LEN) } else { throw new Error(("Invalid segment: " + segment)) } } function _createGenerator (epoch, instance, instanceLen, counterLen) { var ref = createCounter(counterLen); var updateCounter = ref.updateCounter; var encodeCounter = ref.encodeCounter; return function id (ref) { if ( ref === void 0 ) ref = {}; var segment = ref.segment; var scope = ref.scope; var time = ref.time; var scopeIdStr = scope ? parseScope(scope) : null var segmentStr = getSegmentStr(segment, scopeIdStr) if (time === undefined) { time = Date.now() } assert(time > epoch, ("Invalid time: " + time)) var seconds = Math.floor((time - epoch) / 1000) var timeStr = encode(seconds, TIME_LEN) var counter = updateCounter(seconds, time) var metaStr = encodeCounter(counter) + encode(instance, instanceLen) assert(metaStr.length === META_LEN, ("Invalid meta: " + counter + ", " + instance)) assert(timeStr.length === TIME_LEN, ("Too far away: " + seconds)) // console.log('time', timeStr, timeStr.length, TIME_LEN) if (scope) { // if we have scope use it for sharding and use time for sorting inside shard return scopeIdStr + segmentStr + timeStr + metaStr // console.log('parseScope', parseScope(scopeId), timeStr, metaStr) } else { return segmentStr + metaStr + timeStr } } } function generator (ref) { if ( ref === void 0 ) ref = {}; var epoch = ref.epoch; if ( epoch === void 0 ) epoch = DEFAULT_EPOCH; var instance = ref.instance; if ( instance === void 0 ) instance = 0; var instanceLen = ref.instanceLen; if ( instanceLen === void 0 ) instanceLen = MIN_INSTANCE_LEN; if (typeof epoch !== 'number' || epoch < 0 || epoch > Date.now()) { throw new Error('Invalid epoch ' + epoch) } assert(instanceLen >= MIN_INSTANCE_LEN && instanceLen <= MAX_INSTANCE_LEN, ("Invalid instance length: " + instanceLen)) var maxInstance = maxNum(instanceLen) assert(instance >= 0 && instance <= maxInstance, ("Invalid instance: " + instance)) var counterLen = META_LEN - instanceLen if (counterLen < MIN_COUNTER_LEN) { throw new Error(("Invalid counter length: " + counterLen)) } return _createGenerator(epoch, instance, instanceLen, counterLen) } module.exports = generator