sequential-ids
Version:
centralized generation of sequential, human-readable ids
208 lines (189 loc) • 5.86 kB
JavaScript
var http = require("http");
function incrementLetter(letter) {
if (letter === "Z") {
return null;
} else {
var charCode = letter.charCodeAt(0);
charCode++;
return String.fromCharCode(charCode);
}
}
function incrementLetters(letters) {
letters = letters.split("");
var idx = letters.length - 1;
var nextLetter;
while(1) {
if (idx < 0) {
letters.unshift("A");
break;
}
nextLetter = incrementLetter(letters[idx]);
if (! nextLetter) {
letters[idx] = "A";
idx--;
} else {
letters[idx] = nextLetter;
break;
}
}
return letters.join("");
}
function fillLetters(letters, numLetters) {
numLetters = numLetters - letters.length;
while (numLetters > 0) {
letters = "A" + letters;
numLetters--;
}
return letters;
}
function incrementNumber(num, _max) {
return num === _max ? null : ++num;
}
function fillZeros(num, numDigits) {
var number = num.toString();
numDigits = numDigits - number.length;
while(numDigits > 0) {
number = "0" + number;
numDigits--;
}
return number;
}
function parseId(id) {
if (! id) return null;
id = id.trim();
// NOTE: using \w in Regexp matches both digits and letters
var ltrs = id.match(/^([A-z]*)\s*\-*/);
var nums = id.match(/\-*\s*(\d*)$/);
var result = {};
if (ltrs || nums) {
result.numbers = nums[1] || "";
result.letters = ltrs[1] ? ltrs[1].toUpperCase() : "";
return result;
} else {
return null;
}
}
function generateId(letters, numLetters, numbers, numNumbers) {
if (numLetters > 0 && numNumbers <= 0) {
var nextLetters = incrementLetters(letters);
var id = fillLetters(nextLetters, numLetters);
return {id: id, letters: nextLetters, numbers: null};
} else if (numLetters <= 0 && numNumbers > 0) {
var nextNumber = incrementNumber(numbers);
var id = fillZeros(nextNumber, numNumbers);
return {id: id, letters: null, numbers: nextNumber};
} else {
var maxNumber = Math.pow(10, numNumbers) - 1;
var nextNumber = incrementNumber(numbers, maxNumber);
var nextLetters = letters;
if (nextNumber === null) {
nextNumber = 0;
nextLetters = incrementLetters(letters);
}
var id = fillLetters(nextLetters, numLetters)
+ " - " + fillZeros(nextNumber, numNumbers);
return {id: id, letters: nextLetters, numbers: nextNumber};
}
}
function int(_var, _default) {
if (typeof(_var) === "undefined") return _default;
var _int = parseInt(_var);
return isNaN(_int) ? _default : _int;
}
var Generator = (function() {
function Generator(options) {
options = options || {};
this.options = {};
this.keys = {};
this.options.port = int(options.port, 9876);
this.options.autoAddKeys = options.autoAddKeys ? true : false;
this.server;
this._started;
this._stopped;
this.add('__default',options);
}
Generator.prototype.add = function(key, options){
options = options || {};
if(this.keys[key]){ return false; }
this.keys[key] = {};
this.keys[key].options = {};
this.keys[key].options.digits = int(options.digits, 6);
this.keys[key].options.letters = int(options.letters, 3);
this.keys[key].options.store = typeof(options.store) === "function"
? options.store : function() {}
this.keys[key].options.store_freq = int(options.store_freq, 1);
this.keys[key].options.restore = options.restore || null;
this.keys[key].numbers = -1;
this.keys[key].letters = "A";
// workaround to get A's generated as first ids when
// options.digits is 0
if (options.digits === 0) {
this.keys[key].letters = "@";
}
if (options.restore) {
var result = parseId(options.restore);
if (result) {
this.keys[key].numbers = parseInt(result.numbers);
this.keys[key].options.digits = result.numbers.length;
this.keys[key].letters = result.letters;
this.keys[key].options.letters = result.letters.length;
}
}
this.keys[key].generatedIds = [];
this.keys[key].unsavedIds = [];
return true;
};
Generator.prototype.generate = function(key) {
if(!key){ key = '__default'; }
if(!this.keys[key]){
if(!this.options.autoAddKeys){
return null;
}
this.add(key);
}
var _new = generateId(this.keys[key].letters, this.keys[key].options.letters,
this.keys[key].numbers, this.keys[key].options.digits);
this.keys[key].letters = _new.letters;
this.keys[key].numbers = _new.numbers;
this.keys[key].generatedIds.push(_new.id);
this.keys[key].unsavedIds.push(_new.id);
if (this.keys[key].options.store_freq === this.keys[key].unsavedIds.length) {
this.keys[key].options.store(this.keys[key].unsavedIds);
this.keys[key].unsavedIds = [];
}
return _new.id;
};
Generator.prototype.start = function(done) {
done = done || function() {};
if (this._online) return;
this.server = http.Server(function(req, res) {
if(req.url.match(/(^\/\w+)(?:\/(\w+))?/)){
var action = RegExp.$1, key = RegExp.$2 || '__default';
switch(action) {
case "/next":
res.end(this.generate(key));
break;
case "/ping":
res.end("pong");
break;
}
}
}.bind(this));
this.server.listen(this.options.port, done);
this._online = true;
};
Generator.prototype.store = function(key) {
if(!key){ key = '__default'; }
if (this.keys[key].unsavedIds.length > 0) this.keys[key].options.store(this.keys[key].unsavedIds);
};
Generator.prototype.stop = function() {
if (! this._online) return;
for(var key in this.keys){
this.store(key);
}
this.server.close();
this._online = false;
};
return Generator;
})();
exports = module.exports = Generator;