UNPKG

flake-idgen

Version:

Flake ID generator yields k-ordered, conflict-free ids in a distributed environment

125 lines (110 loc) 4.62 kB
/** * Flake ID generator yields k-ordered, conflict-free ids in a distributed environment. */ (function () { "use strict"; /** * Represents an ID generator. * @exports FlakeId * @constructor * @param {object=} options - Generator options * @param {Number} options.id - Generator identifier. It can have values from 0 to 1023. It can be provided instead of <tt>datacenter</tt> and <tt>worker</tt> identifiers. * @param {Number} options.datacenter - Datacenter identifier. It can have values from 0 to 31. * @param {Number} options.worker - Worker identifier. It can have values from 0 to 31. * @param {Number} options.epoch - Number used to reduce value of a generated timestamp. * @param {Number} options.seqMask */ var FlakeId = module.exports = function (options) { this.options = options || {}; // Set generator id from 'id' option or combination of 'datacenter' and 'worker' if (typeof this.options.id !== 'undefined') { /*jslint bitwise: true */ this.id = this.options.id & 0x3FF; } else { this.datacenter = (this.options.datacenter || 0) & 0x1F; this.worker = (this.options.worker || 0) & 0x1F; this.id = (this.datacenter) << 5 | this.worker; } this.genId = this.id; this.genId <<= 12; // id generator identifier - will not change while generating ids this.epoch = +this.options.epoch || 0; this.seq = 0; this.lastTime = 0; this.overflow = false; this.seqMask = this.options.seqMask || 0xFFF; }; FlakeId.POW10 = Math.pow(2, 10); // 2 ^ 10 FlakeId.POW26 = Math.pow(2, 26); // 2 ^ 26 FlakeId.prototype = { /** * Generates conflice-free id * @param {cb=} callback The callback that handles the response. * @returns {Buffer} Generated id if callback is not provided * @exception if a sequence exceeded its maximum value and a callback function is not provided */ next: function (cb) { /** * This callback receives generated id * @callback callback * @param {Error} error - Error occurred during id generation * @param {Buffer} id - Generated id */ var id; if (Buffer.alloc) { id = Buffer.alloc(8); } else { id = new Buffer(8); id.fill(0); } var time = Date.now() - this.epoch; // Generates id in the same millisecond as the previous id if (time < this.lastTime) { if (cb) { setTimeout(self.next.bind(self, cb), this.lastTime - time); return; } throw new Error(`Clock moved backwards. Refusing to generate id for ${this.lastTime - time} milliseconds`); } if (time === this.lastTime) { // If all sequence values (4096 unique values including 0) have been used // to generate ids in the current millisecond (overflow is true) wait till next millisecond if (this.overflow) { overflowCond(this, cb); return; } // Increase sequence counter /*jslint bitwise: true */ this.seq = (this.seq + 1) & this.seqMask; // sequence counter exceeded its max value (4095) // - set overflow flag and wait till next millisecond if (this.seq === 0) { this.overflow = true; overflowCond(this, cb); return; } } else { this.overflow = false; this.seq = 0; } this.lastTime = time; id.writeUInt32BE(((time & 0x3) << 22) | this.genId | this.seq, 4); id.writeUInt8(Math.floor(time / 4) & 0xFF, 4); id.writeUInt16BE(Math.floor(time / FlakeId.POW10) & 0xFFFF, 2); id.writeUInt16BE(Math.floor(time / FlakeId.POW26) & 0xFFFF, 0); if (cb) { process.nextTick(cb.bind(null, null, id)); } else { return id; } } }; function overflowCond(self, cb) { if (cb) { setTimeout(self.next.bind(self, cb), 1); } else { throw new Error('Sequence exceeded its maximum value. Provide callback function to handle sequence overflow'); } } }());