cluster-id
Version:
Database cluster friendly object id with great query isolation.
101 lines (89 loc) • 3.62 kB
JavaScript
/*
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