Add BinaryPacker from kv-redis connector

Add a helper for encoding JavaScript values into binary Buffers.

The implemenetation is based on msgpack5 format and preserves JavaScript
objects like Buffers and Dates, as opposed to (binary)JSON.
This commit is contained in:
Miroslav Bajtoš 2016-10-19 13:14:55 +02:00
parent 2cbc1143c1
commit 6fd3ac7285
4 changed files with 154 additions and 0 deletions

View File

@ -16,3 +16,4 @@ exports.createPromiseCallback = require('./lib/utils').createPromiseCallback;
// KeyValue helpers
exports.ModelKeyComposer = require('./lib/model-key-composer');
exports.BinaryPacker = require('./lib/binary-packer');

80
lib/binary-packer.js Normal file
View File

@ -0,0 +1,80 @@
// Copyright IBM Corp. 2014,2016. All Rights Reserved.
// Node module: loopback-connector
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
var createPromiseCallback = require('./utils').createPromiseCallback;
var msgpack = require('msgpack5');
module.exports = BinaryPacker;
/**
* Create a new Packer instance that can be used to convert between JavaScript
* objects and a binary representation in a Buffer.
*
* Compared to JSON, this encoding preserves the following JavaScript types:
* - Date
*/
function BinaryPacker() {
this._packer = msgpack({ forceFloat64: true });
this._packer.register(1, Date, encodeDate, decodeDate);
}
/**
* Encode the provided value to a `Buffer`.
*
* @param {*} value Any value (string, number, object)
* @callback {Function} cb The callback to receive the parsed result.
* @param {Error} err
* @param {Buffer} data The encoded value
* @promise
*/
BinaryPacker.prototype.encode = function(value, cb) {
cb = cb || createPromiseCallback();
try {
// msgpack5 returns https://www.npmjs.com/package/bl instead of Buffer
// use .slice() to convert to a Buffer
var data = this._packer.encode(value).slice();
setImmediate(function() {
cb(null, data);
});
} catch (err) {
setImmediate(function() {
cb(err);
});
}
return cb.promise;
};
/**
* Decode the binary value back to a JavaScript value.
* @param {Buffer} binary The binary input.
* @callback {Function} cb The callback to receive the composed value.
* @param {Error} err
* @param {*} value Decoded value.
* @promise
*/
BinaryPacker.prototype.decode = function(binary, cb) {
cb = cb || createPromiseCallback();
try {
var value = this._packer.decode(binary);
setImmediate(function() {
cb(null, value);
});
} catch (err) {
setImmediate(function() {
cb(err);
});
}
return cb.promise;
};
function encodeDate(obj) {
return new Buffer(obj.toISOString(), 'utf8');
}
function decodeDate(buf) {
return new Date(buf.toString('utf8'));
}

View File

@ -22,6 +22,7 @@
"async": "^1.0.0",
"bluebird": "^3.4.6",
"debug": "^2.2.0",
"msgpack5": "^3.4.1",
"strong-globalize": "^2.5.8"
},
"devDependencies": {

View File

@ -0,0 +1,72 @@
// Copyright IBM Corp. 2016. All Rights Reserved.
// Node module: loopback-connector
// This file is licensed under the MIT License.
// License text available at https://opensource.org/licenses/MIT
'use strict';
var BinaryPacker = require('../lib/binary-packer');
var expect = require('chai').expect;
describe('BinaryPacker', function() {
var packer;
beforeEach(function createPacker() {
packer = new BinaryPacker();
});
describe('encode()', function() {
it('supports invocation with a callback', function(done) {
packer.encode('a-value', done);
});
});
describe('decode()', function() {
it('supports invocation with a callback', function(done) {
packer.encode('a-value', function(err, binary) {
if (err) return done(err);
packer.decode(binary, function(err, result) {
if (err) return done(err);
expect(result).to.eql('a-value');
done();
});
});
});
});
describe('roundtrip', function() {
var TEST_CASES = {
String: 'a-value',
Object: { a: 1, b: 2 },
Buffer: new Buffer([1, 2, 3]),
Date: new Date('2016-08-03T11:53:03.470Z'),
Integer: 12345,
Float: 12.345,
Boolean: false,
};
Object.keys(TEST_CASES).forEach(function(tc) {
it('works for ' + tc + ' values', function() {
var value = TEST_CASES[tc];
return encodeAndDecode(value)
.then(function(result) {
expect(result).to.eql(value);
});
});
});
it('works for nested properties', function() {
return encodeAndDecode(TEST_CASES)
.then(function(result) {
expect(result).to.eql(TEST_CASES);
});
});
function encodeAndDecode(value) {
return packer.encode(value)
.then(function(binary) {
return packer.decode(binary);
});
}
});
});