Permalink
Browse files

First push

  • Loading branch information...
0 parents commit 8c7f957dab0d272f870eaac3c37f76bcdb8f71e2 @dylang committed Dec 19, 2011
Showing with 244 additions and 0 deletions.
  1. +3 −0 .travis.yml
  2. +1 −0 index.js
  3. +148 −0 lib/shortid.js
  4. +22 −0 package.json
  5. +19 −0 readme.md
  6. +51 −0 test/test.shortid.js
3 .travis.yml
@@ -0,0 +1,3 @@
+language: node_js
+node_js:
+ - 0.6
1 index.js
@@ -0,0 +1 @@
+module.exports = require('./lib/shortid');
148 lib/shortid.js
@@ -0,0 +1,148 @@
+/*
+ * Short Id
+ * by Dylan Greene
+ *
+ */
+var randomBytes = require('crypto').randomBytes
+
+
+// remove from Date.now all the milliseconds before this thing was created
+var LESS_TIME = 1324151035201;
+
+// Use a shuffled alphabet based on a seed so that predicting future numbers is more difficult
+var ALPHABET_ORIGINAL = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
+var ALPHABET_SHUFFLED;
+
+var options = {
+ version: 0, //don't change unless we change the algos or seeds
+ seed: 1, // change to any positive int if you don't want people to figure out your id scheme
+ worker: 0 // if you are using cluster or multiple servers use this to make sure
+ // two machines don't create the same id. VERY unlikely but this is just in case.
+};
+
+// Counter always starts at zero
+var counter = -1;
+var previousSeconds;
+
+/**
+ * Generate the id
+ * Returns a unique short id.
+ */
+function generate() {
+
+ var seconds = Date.now() - LESS_TIME >> 10; //divide by 10 to get seconds
+
+ counter = seconds == previousSeconds ? counter + 1 : 0;
+
+ var buffer = [];
+
+ function encode(number) {
+ var loopCounter = 0;
+ var done = false;
+ while (!done) {
+ buffer.push( ( (number >> (4 * loopCounter)) & 0x0f ) | (randomBytes(1)[0] & 0x30) );
+ done = number < (Math.pow(16, loopCounter + 1 ) );
+ loopCounter++;
+ }
+ }
+
+ encode(options.version);
+ encode(options.worker);
+ if (counter > 0) {
+ encode(counter);
+ }
+ encode(seconds); //seconds since this module was created. >> 10 removes the milliseconds more or less.
+
+ previousSeconds = seconds;
+
+ var str = '';
+ var i;
+ for(i = 0; i < buffer.length; i++) {
+ str = str + ALPHABET_SHUFFLED[buffer[i]];
+ }
+ return str;
+}
+
+/**
+ * Set the seed.
+ * Highly recommended if you don't want people to try to figure out your id schema.
+ * exposed as ShortId.seed(int)
+ * @param seed Integer value to seed the random alphabet. ALWAYS USE THE SAME SEED or you might get overlaps.
+ */
+function setSeed(seed) {
+ options.seed = seed;
+
+ // Found online somewhere
+ function random(seed) {
+ seed = seed || 0;
+ seed = (seed * 9301 + 49297) % 233280;
+ return seed/(233280.0);
+ }
+
+ var sourceArray = ALPHABET_ORIGINAL.split('');
+ var targetArray = [];
+ var r = random(seed);
+ var characterIndex;
+ while (sourceArray.length > 0) {
+ r = random(r * 1000);
+ characterIndex = Math.floor(r * sourceArray.length);
+ targetArray.push(sourceArray.splice(characterIndex, 1)[0]);
+ }
+ ALPHABET_SHUFFLED = targetArray.join('') + '-_';
+
+ return module.exports;
+}
+
+/**
+ * Set the cluster worker or machine id
+ * exposed as ShortId.worker(int)
+ * @param worker worker must be positive integer. Number less than 16 is recommended.
+ * returns ShortId module so it can be chained.
+ */
+function setWorker(worker) {
+ options.worker = worker;
+ return module.exports;
+}
+
+/**
+ * Set the version.
+ * exposed as ShortId.version(int)
+ * @param version version must be positive integer. Number less than 16 is recommended.
+ * returns ShortId module so it can be chained.
+ */
+function setVersion(version) {
+ options.version = version;
+ return module.exports;
+}
+
+/**
+ * returns the shuffled alphabet
+ */
+function alphabet() {
+ return ALPHABET_SHUFFLED;
+}
+
+/**
+ * Decode the id to get the version and worker
+ * Mainly for debugging and testing.
+ * @param id - the ShortId-generated id.
+ */
+function decode(id) {
+ return {
+ version: ALPHABET_SHUFFLED.indexOf(id.substr(0, 1)) & 0x0f,
+ worker: ALPHABET_SHUFFLED.indexOf(id.substr(1, 1)) & 0x0f
+ };
+}
+
+// use defaults
+setSeed(options.seed);
+setVersion(options.version);
+setWorker(process.env.CLUSTER_WORKER ? parseInt(process.env.CLUSTER_WORKER, 10) : options.worker);
+
+module.exports = generate;
+module.exports.generate = generate;
+module.exports.seed = setSeed;
+module.exports.version = setVersion;
+module.exports.worker = setWorker;
+module.exports.alphabet = alphabet;
+module.exports.decode = decode;
22 package.json
@@ -0,0 +1,22 @@
+{
+ "name":"shortid",
+ "version":"1.0.0",
+ "description":"Short unique id generator. Url-friendly. Non-predictable. Cluster-compatible.",
+ "keywords": ["short", "tiny", "id", "uuid", "bitly", "shorten", "mongoid"],
+ "homepage":"http://doodleordie.com",
+ "author":"Dylan Greene <dylang@gmail.com>",
+ "dependencies":{
+ },
+ "devDependencies":{
+ "logging":"*",
+ "mocha":"*"
+ },
+ "main":"index",
+ "engines":{
+ "node":">=0.6"
+ },
+ "scripts":{
+ "start":"node cluster",
+ "test": "nodeunit test"
+ }
+}
19 readme.md
@@ -0,0 +1,19 @@
+# ShortId [![Build Status](https://secure.travis-ci.org/dylang/Shortid.png)](http://travis-ci.org/dylang/Shortid)
+
+ShortId is a tiny id generator good for creating guarenteed unique ids that are easier to use in urls and for sharing than UUID's.
+
+* 8-12 characters
+* Non-sequential so they are not predictable.
+* Random alphabet based on a seed you provide so others can't decrypt them.
+* Includes version in case you want to change how you encode your id.
+* Includes cluster worker id so you can use this on multi-processor server instances.
+* Includes tests that run on Mocha.
+
+## ShortId.generate() returns id
+
+Other functions (docs coming soon)
+
+* ShortId.version(int) sets version, returns ShortId
+* ShortId.worker(int) sets cluster worker, returns ShortId
+
+See the tests for more examples.
51 test/test.shortid.js
@@ -0,0 +1,51 @@
+var log = require('logging').from(__filename);
+var ShortId = require('../index');
+
+var Assert = require('assert');
+
+describe('testing shortid', function(done) {
+
+ it('should create a unique random alphabet with each seed', function(done) {
+ Assert.equal(ShortId.seed(1).alphabet(), 'efOCXpDj8xs60SaFV59UGyd7WvLo2YQrTMimKgwb4Rct1ZI3PJqEBuAHhzknNl-_', 'Seeded with 1');
+ Assert.equal(ShortId.seed(1).alphabet(), 'efOCXpDj8xs60SaFV59UGyd7WvLo2YQrTMimKgwb4Rct1ZI3PJqEBuAHhzknNl-_', 'Got the same alphabet');
+ Assert.equal(ShortId.seed(1234).alphabet(), 'CXEje6P0mYBauThDkFiOUwLGKZboHMzRINWptx74gs935nd2qA1yfJrVSl8Qvc-_', 'Got new alphabet');
+ done();
+ });
+
+ it('should run a bunch and never get duplicates', function(done) {
+ ShortId.seed(1);
+ var ids = {};
+ var id;
+
+ var i=1000;
+ while(i--) {
+ id = ShortId.generate();
+ Assert.ok(!ids[id], 'no repeat ids');
+ Assert.ok(id.length < 17, 'length less than 10 characters ' + id);
+ ids[id] = 1;
+ }
+ done();
+ });
+
+ it('should decode worker', function(done){
+ ShortId.worker(0);
+ Assert.equal(ShortId.decode(ShortId.generate()).worker, 0, 'worker value not correct');
+
+ ShortId.worker(15);
+ Assert.equal(ShortId.decode(ShortId.generate()).worker, 15, 'worker value not correct');
+
+ done();
+ });
+
+ it('should decode version', function(done){
+ ShortId.version(0);
+ Assert.equal(ShortId.decode(ShortId.generate()).version, 0, 'version value not correct');
+
+ ShortId.version(15);
+ Assert.equal(ShortId.decode(ShortId.generate()).version, 15, 'version value not correct');
+
+ done();
+ });
+
+
+});

0 comments on commit 8c7f957

Please sign in to comment.