UNPKG

scuid

Version:

Collision-resistant IDs optimized for horizontal scaling and performance

193 lines (161 loc) 4.42 kB
var os = require( 'os' ) /** * Pad a string to length `n` with `chr` * @param {String} str * @param {String} chr * @param {Number} n * @return {String} */ function pad( str, chr, n ) { return (chr.repeat( Math.max( n - str.length, 0 ) ) + str).substr(-n) } /** * Scuid * @constructor * @param {Object} [options] * @return {Scuid} */ function Scuid( options ) { if( !(this instanceof Scuid) ) return new Scuid( options ) options = options != null ? options : {} /** @type {String} Generated ID's prefix */ this.prefix = options.prefix || Scuid.defaults.prefix /** @type {Number} Radix used in .toString() calls (2-36) */ this.base = options.base || Scuid.defaults.base /** @type {Number} Block size to pad and trim to */ this.blockSize = options.blockSize || Scuid.defaults.blockSize /** @type {Number} Maximum of discrete counter values */ this.discreteValues = Math.pow( this.base, this.blockSize ) /** @type {Number} Process ID */ this.pid = options.pid || Scuid.defaults.pid /** @type {String} Host name */ this.hostname = options.hostname || Scuid.defaults.hostname /** @type {String} Block padding character */ this.fill = options.fill || Scuid.defaults.fill /** @type {Object} Random number generator */ this.rng = options.rng || Scuid.defaults.rng /** @type {String} Generated process fingerprint */ this._fingerprint = Scuid.fingerprint( this.pid, this.hostname, this.base ) /** @type {Number} Internal counter for collision prevention */ this._counter = 0 } /** * Generate a 2 character host ID * @param {String} hostname * @return {String} hostid */ Scuid.hostId = function( hostname ) { var result = hostname.length + 36 for( var i = 0; i < hostname.length; i++ ) { result += hostname.charCodeAt( i ) } return pad( result.toString( 36 ), '0', 2 ) } /** * Generate a 4 character process fingerprint * @param {Number} [pid] * @param {String} [hostname] * @return {String} fingerprint */ Scuid.fingerprint = function( pid, hostname, base ) { base = base || 36 pid = pid == null ? process.pid : pid hostname = !hostname ? os.hostname() : hostname return pad( pid.toString( base ), '0', 2 ) + Scuid.hostId( hostname ) } /** * Default options * @type {Object} */ Scuid.defaults = { prefix: 'c', base: 36, blockSize: 4, fill: '0', pid: process.pid, hostname: os.hostname, rng: Math, } /** * Scuid prototype * @type {Object} * @ignore */ Scuid.prototype = { constructor: Scuid, /** * Get & advance the internal counter * @return {Number} */ count: function() { this._counter = this._counter < this.discreteValues ? this._counter : 0 return this._counter++ }, /** * Get the blocksize padded counter * @return {String} */ counter: function() { return pad( this.count().toString( this.base ), this.fill, this.blockSize ) }, /** * Generate a block of `blockSize` random characters * @return {String} */ randomBlock: function() { var block = this.rng.random() * this.discreteValues << 0 return pad( block.toString( this.base ), this.fill, this.blockSize ) }, /** * Get the padded timestamp * @return {String} */ timestamp: function() { return pad( Date.now().toString( this.base ), this.fill, 8 ) }, /** * Get the used process fingerprint * @return {String} */ fingerprint: function() { return this._fingerprint }, /** * Generate a (s)cuid * @return {String} id */ id: function() { return this.prefix + this.timestamp() + this.counter() + this._fingerprint + this.randomBlock() + this.randomBlock() }, /** * Generate a slug * @return {String} slug */ slug: function() { return this.timestamp().substr( -2 ) + this.count().toString( this.base ).substr( -4 ) + this._fingerprint[0] + this._fingerprint[ this._fingerprint.length - 1 ] + this.randomBlock().substr( -2 ) }, } var instance = new Scuid() // Exports module.exports = function scuid() { return instance.id() } module.exports.slug = function slug() { return instance.slug() } module.exports.fingerprint = function() { return instance.fingerprint() } module.exports.createFingerprint = function( pid, hostname ) { return Scuid.fingerprint( pid, hostname ) } module.exports.constructor = Scuid module.exports.create = Scuid