UNPKG

bson-rpc

Version:
363 lines (317 loc) 10.3 kB
/** * Machine id. * * Create a random 3-byte value (i.e. unique for this * process). Other drivers use a md5 of the machine id here, but * that would mean an asyc call to gethostname, so we don't bother. * @ignore */ var MACHINE_ID = parseInt(Math.random() * 0xFFFFFF, 10); // Regular expression that checks for hex value var checkForHexRegExp = new RegExp("^[0-9a-fA-F]{24}$"); var hasBufferType = false; // Check if buffer exists try { if(Buffer && Buffer.from) hasBufferType = true; } catch(err) {}; /** * Create a new ObjectID instance * * @class * @param {(string|number)} id Can be a 24 byte hex string, 12 byte binary string or a Number. * @property {number} generationTime The generation time of this ObjectId instance * @return {ObjectID} instance of ObjectID. */ var ObjectID = function ObjectID(id) { // Duck-typing to support ObjectId from different npm packages if(id instanceof ObjectID) return id; if(!(this instanceof ObjectID)) return new ObjectID(id); this._bsontype = 'ObjectID'; // The most common usecase (blank id, new objectId instance) if(id == null || typeof id == 'number') { // Generate a new id this.id = this.generate(id); // If we are caching the hex string if(ObjectID.cacheHexString) this.__id = this.toString('hex'); // Return the object return; } // Check if the passed in id is valid var valid = ObjectID.isValid(id); // Throw an error if it's not a valid setup if(!valid && id != null){ throw new Error("Argument passed in must be a single String of 12 bytes or a string of 24 hex characters"); } else if(valid && typeof id == 'string' && id.length == 24 && hasBufferType) { return new ObjectID(new Buffer(id, 'hex')); } else if(valid && typeof id == 'string' && id.length == 24) { return ObjectID.createFromHexString(id); } else if(id != null && id.length === 12) { // assume 12 byte string this.id = id; } else if(id != null && id.toHexString) { // Duck-typing to support ObjectId from different npm packages return id; } else { throw new Error("Argument passed in must be a single String of 12 bytes or a string of 24 hex characters"); } if(ObjectID.cacheHexString) this.__id = this.toString('hex'); }; // Allow usage of ObjectId as well as ObjectID var ObjectId = ObjectID; // Precomputed hex table enables speedy hex string conversion var hexTable = []; for (var i = 0; i < 256; i++) { hexTable[i] = (i <= 15 ? '0' : '') + i.toString(16); } /** * Return the ObjectID id as a 24 byte hex string representation * * @method * @return {string} return the 24 byte hex string representation. */ ObjectID.prototype.toHexString = function() { if(ObjectID.cacheHexString && this.__id) return this.__id; var hexString = ''; if(!this.id || !this.id.length) { throw new Error('invalid ObjectId, ObjectId.id must be either a string or a Buffer, but is [' + JSON.stringify(this.id) + ']'); } if(this.id instanceof _Buffer) { hexString = convertToHex(this.id); if(ObjectID.cacheHexString) this.__id = hexString; return hexString; } for (var i = 0; i < this.id.length; i++) { hexString += hexTable[this.id.charCodeAt(i)]; } if(ObjectID.cacheHexString) this.__id = hexString; return hexString; }; /** * Update the ObjectID index used in generating new ObjectID's on the driver * * @method * @return {number} returns next index value. * @ignore */ ObjectID.prototype.get_inc = function() { return ObjectID.index = (ObjectID.index + 1) % 0xFFFFFF; }; /** * Update the ObjectID index used in generating new ObjectID's on the driver * * @method * @return {number} returns next index value. * @ignore */ ObjectID.prototype.getInc = function() { return this.get_inc(); }; /** * Generate a 12 byte id buffer used in ObjectID's * * @method * @param {number} [time] optional parameter allowing to pass in a second based timestamp. * @return {Buffer} return the 12 byte id buffer string. */ ObjectID.prototype.generate = function(time) { if ('number' != typeof time) { time = ~~(Date.now()/1000); } // Use pid var pid = (typeof process === 'undefined' ? Math.floor(Math.random() * 100000) : process.pid) % 0xFFFF; var inc = this.get_inc(); // Buffer used var buffer = new Buffer(12); // Encode time buffer[3] = time & 0xff; buffer[2] = (time >> 8) & 0xff; buffer[1] = (time >> 16) & 0xff; buffer[0] = (time >> 24) & 0xff; // Encode machine buffer[6] = MACHINE_ID & 0xff; buffer[5] = (MACHINE_ID >> 8) & 0xff; buffer[4] = (MACHINE_ID >> 16) & 0xff; // Encode pid buffer[8] = pid & 0xff; buffer[7] = (pid >> 8) & 0xff; // Encode index buffer[11] = inc & 0xff; buffer[10] = (inc >> 8) & 0xff; buffer[9] = (inc >> 16) & 0xff; // Return the buffer return buffer; }; /** * Converts the id into a 24 byte hex string for printing * * @param {String} format The Buffer toString format parameter. * @return {String} return the 24 byte hex string representation. * @ignore */ ObjectID.prototype.toString = function(format) { // Is the id a buffer then use the buffer toString method to return the format if(this.id && this.id.copy) { return this.id.toString(typeof format === 'string' ? format : 'hex'); } // if(this.buffer ) return this.toHexString(); }; /** * Converts to a string representation of this Id. * * @return {String} return the 24 byte hex string representation. * @ignore */ ObjectID.prototype.inspect = ObjectID.prototype.toString; /** * Converts to its JSON representation. * * @return {String} return the 24 byte hex string representation. * @ignore */ ObjectID.prototype.toJSON = function() { return this.toHexString(); }; /** * Compares the equality of this ObjectID with `otherID`. * * @method * @param {object} otherID ObjectID instance to compare against. * @return {boolean} the result of comparing two ObjectID's */ ObjectID.prototype.equals = function equals (otherId) { var id; if(otherId instanceof ObjectID) { return this.toString() == otherId.toString(); } else if(typeof otherId == 'string' && ObjectID.isValid(otherId) && otherId.length == 12 && this.id instanceof _Buffer) { return otherId === this.id.toString('binary'); } else if(typeof otherId == 'string' && ObjectID.isValid(otherId) && otherId.length == 24) { return otherId.toLowerCase() === this.toHexString(); } else if(typeof otherId == 'string' && ObjectID.isValid(otherId) && otherId.length == 12) { return otherId === this.id; } else if(otherId != null && (otherId instanceof ObjectID || otherId.toHexString)) { return otherId.toHexString() === this.toHexString(); } else { return false; } } /** * Returns the generation date (accurate up to the second) that this ID was generated. * * @method * @return {date} the generation date */ ObjectID.prototype.getTimestamp = function() { var timestamp = new Date(); var time = this.id[3] | this.id[2] << 8 | this.id[1] << 16 | this.id[0] << 24; timestamp.setTime(Math.floor(time) * 1000); return timestamp; } /** * @ignore */ ObjectID.index = ~~(Math.random() * 0xFFFFFF); /** * @ignore */ ObjectID.createPk = function createPk () { return new ObjectID(); }; /** * Creates an ObjectID from a second based number, with the rest of the ObjectID zeroed out. Used for comparisons or sorting the ObjectID. * * @method * @param {number} time an integer number representing a number of seconds. * @return {ObjectID} return the created ObjectID */ ObjectID.createFromTime = function createFromTime (time) { var buffer = new Buffer([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]); // Encode time into first 4 bytes buffer[3] = time & 0xff; buffer[2] = (time >> 8) & 0xff; buffer[1] = (time >> 16) & 0xff; buffer[0] = (time >> 24) & 0xff; // Return the new objectId return new ObjectID(buffer); }; // Lookup tables var encodeLookup = '0123456789abcdef'.split('') var decodeLookup = [] var i = 0 while (i < 10) decodeLookup[0x30 + i] = i++ while (i < 16) decodeLookup[0x41 - 10 + i] = decodeLookup[0x61 - 10 + i] = i++ var _Buffer = Buffer; var convertToHex = function(bytes) { return bytes.toString('hex'); } /** * Creates an ObjectID from a hex string representation of an ObjectID. * * @method * @param {string} hexString create a ObjectID from a passed in 24 byte hexstring. * @return {ObjectID} return the created ObjectID */ ObjectID.createFromHexString = function createFromHexString (string) { // Throw an error if it's not a valid setup if(typeof string === 'undefined' || string != null && string.length != 24) { throw new Error("Argument passed in must be a single String of 12 bytes or a string of 24 hex characters"); } // Use Buffer.from method if available if(hasBufferType) return new ObjectID(new Buffer(string, 'hex')); // Calculate lengths var array = new _Buffer(12); var n = 0; var i = 0; while (i < 24) { array[n++] = decodeLookup[string.charCodeAt(i++)] << 4 | decodeLookup[string.charCodeAt(i++)] } return new ObjectID(array); }; /** * Checks if a value is a valid bson ObjectId * * @method * @return {boolean} return true if the value is a valid bson ObjectId, return false otherwise. */ ObjectID.isValid = function isValid(id) { if(id == null) return false; if(typeof id == 'number') { return true; } if(typeof id == 'string') { return id.length == 12 || (id.length == 24 && checkForHexRegExp.test(id)); } if(id instanceof ObjectID) { return true; } if(id instanceof _Buffer) { return true; } // Duck-Typing detection of ObjectId like objects if(id.toHexString) { return id.id.length == 12 || (id.id.length == 24 && checkForHexRegExp.test(id.id)); } return false; }; /** * @ignore */ Object.defineProperty(ObjectID.prototype, "generationTime", { enumerable: true , get: function () { return this.id[3] | this.id[2] << 8 | this.id[1] << 16 | this.id[0] << 24; } , set: function (value) { // Encode time into first 4 bytes this.id[3] = value & 0xff; this.id[2] = (value >> 8) & 0xff; this.id[1] = (value >> 16) & 0xff; this.id[0] = (value >> 24) & 0xff; } }); /** * Expose. */ module.exports = ObjectID; module.exports.ObjectID = ObjectID; module.exports.ObjectId = ObjectID;