Comparing version 0.0.1 to 1.0.1
{ | ||
"name": "compactr", | ||
"version": "0.0.1", | ||
"version": "1.0.1", | ||
"description": "A compression library for the modern web", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -14,3 +14,3 @@ # Compactr | ||
Compactr is a library to compress and decompress Javascript objects before sending them over the web. It's immencely usefull for web applications that use sockets a lot. Smaller payloads equals better, faster throughput and less bandwidth costs. | ||
Compactr is a library to compress and decompress Javascript objects before sending them over the web. It's immensely useful for web applications that use sockets a lot. Smaller payloads equals better, faster throughput and less bandwidth costs. | ||
@@ -25,3 +25,3 @@ | ||
Why yes, Protocol Buffer is by far the better performing protocol out there, but there's a few things about it I don't like - as a Node developper. | ||
Why yes, Protocol Buffer is by far the better performing protocol out there, but there's a few things about it I don't like - as a Node developer. | ||
@@ -32,6 +32,8 @@ The first thing that comes to mind is the painful management of `.proto` files. | ||
Furthermore, Compactr has **NO** dependencies or compiled modules. It's the lightest module you've ever seen! | ||
## So what's your solution? | ||
Protocol Buffers are awesome. Having schemas to deflate and inflate data while maintaining some kind of validation is a great concept. Compactr's goal is to build on that to better suite Node server developement and reduce noise by allowing you to re-use your current Model schemas. | ||
Protocol Buffers are awesome. Having schemas to deflate and inflate data while maintaining some kind of validation is a great concept. Compactr's goal is to build on that to better suit Node server development and reduce noise by allowing you to re-use your current Model schemas. | ||
@@ -43,20 +45,9 @@ | ||
``` | ||
/* Waterline Schema (User) */ | ||
| **Waterline** | **Mongoose** | | ||
| --- | --- | --- | | ||
| `{` <br> ` id: {` <br> ` type: 'integer',` <br> ` required: true` <br> ` },` <br> ` name: 'string'` <br> `}` | `{` <br> ` id: {` <br> ` type: Number,` <br> ` required: true` <br> ` },` <br> ` name: String` <br> `}` | | ||
{ | ||
id: { | ||
type: 'integer', | ||
required: true | ||
}, | ||
name: { | ||
type: 'string', | ||
defaultsTo: 'John' | ||
} | ||
} | ||
``` | ||
``` | ||
/* User compessing in controller */ | ||
/* User compessing in a controller */ | ||
@@ -69,3 +60,3 @@ const Compactr = require('compactr'); | ||
``` | ||
``` | ||
@@ -78,2 +69,3 @@ ``` | ||
``` | ||
No need to create additional models for serialization! | ||
@@ -97,4 +89,5 @@ | ||
See this chart: | ||
**TODO** | ||
I'm still working on graphs and proper test scenarios, but I can say that it performs as fast, and sometimes faster than JSON encoding/decoding and outputs a buffer that is more or less half the size! | ||
@@ -105,7 +98,6 @@ ## Alright, what about features? | ||
- [ ] Use Waterline schemas | ||
- [ ] Synchronously encode/decode | ||
- [ ] Asynchronously encode/decode (Promise-based) | ||
- [ ] Stream encoding-decoding | ||
- [ ] Nested objects | ||
- [x] Use Waterline schemas | ||
- [x] Use Mongoose schemas | ||
- [x] Synchronously encode/decode | ||
- [ ] Nested objects/ Arrays | ||
@@ -115,3 +107,3 @@ ## Alright, I'm convinced! How can I help? | ||
Just open an issue, identifying it as a feature that you want to tackle. | ||
Ex: `STORY - [...]` | ||
And we'll take the discussion there. | ||
Ex: `STORY - [...]` | ||
And we'll take the discussion there. |
@@ -9,39 +9,160 @@ /** | ||
const Workbench = require('./Workbench'); | ||
const Types = require('./Types'); | ||
/* Local variables -----------------------------------------------------------*/ | ||
const SEP_CODE = 255; | ||
const READ_SKIP = 2; | ||
const INT8_SIZE = 1; | ||
const INT16_SIZE = 2; | ||
const INT32_SIZE = 4; | ||
const DOUBLE_SIZE = 6; | ||
const CPR = Object.create(null); | ||
let doubleBuffer = Buffer.alloc(8); | ||
/* Methods -------------------------------------------------------------------*/ | ||
/** | ||
* Decodes a Compactr Buffer using a Schema | ||
* @param {object} schema The Schema to use to decode the buffer | ||
* @param {Buffer} data The buffer to decode | ||
* @returns {object} The decoded buffer | ||
*/ | ||
function Decode(schema, data) { | ||
let result = {}; | ||
let keys = Object.keys(schema); | ||
let bytes = new Workbench(data); | ||
const keys = Object.keys(schema); | ||
const len = data.length; | ||
let _caret = len; | ||
let _propName; | ||
let _propType; | ||
let _caret = 0; | ||
for (let i = len - 1; i >= 0; i--) { | ||
if (data[i] === SEP_CODE) { | ||
let _propName = keys[read_index(data, i)]; | ||
if (_propName !== undefined && schema[_propName] !== undefined) { | ||
let _propType = Types.resolve(schema[_propName].type || schema[_propName]); | ||
result[_propName] = read(data, _propType, i + READ_SKIP, _caret); | ||
if (i === 0) break; | ||
_caret = i; | ||
i -= READ_SKIP; | ||
} | ||
} | ||
} | ||
data.forEach((byte, index) => { | ||
// SEP | ||
if (byte === Workbench.SEP_CODE) { | ||
if (index > 0) { | ||
_propName = keys[bytes.read(Types.NUMBER, _caret + 1, _caret + 2)]; | ||
_propType = Types.resolve(schema[_propName].type || schema[_propName]); | ||
result[_propName] = bytes.read(_propType, _caret + 2, index); | ||
} | ||
_caret = index; | ||
} | ||
if (index === data.length - 1) { | ||
_propName = keys[bytes.read(Types.NUMBER, _caret + 1, _caret + 2)]; | ||
_propType = Types.resolve(schema[_propName].type || schema[_propName]); | ||
result[_propName] = bytes.read(_propType, _caret + 2, index + 1); | ||
} | ||
}); | ||
return result; | ||
} | ||
/** | ||
* Returns a Schema key index - 1 byte - (0<->255) | ||
* @param {Buffer} buffer The buffer to read from | ||
* @param {integer} index The buffer index to read at | ||
* @returns {integer} The Schema index key | ||
*/ | ||
function read_index(buffer, index) { | ||
return buffer[index + 1]; | ||
} | ||
/** | ||
* Returns a Boolean value - 1 byte - (0<->1) | ||
* @param {Buffer} buffer The buffer to read from | ||
* @param {integer} index The buffer index to read at | ||
* @returns {integer} The Boolean value | ||
*/ | ||
function read_boolean(buffer, index) { | ||
return buffer[index] === 1; | ||
} | ||
/** | ||
* Returns a signed INT8 value - 1 byte - (-128<->127) | ||
* @param {Buffer} buffer The buffer to read from | ||
* @param {integer} from The buffer index to read from | ||
* @param {integer} to The buffer index to read to | ||
* @returns {integer} The INT8 value | ||
*/ | ||
function read_int8(buffer, from, to) { | ||
return buffer.readInt8(from, to); | ||
} | ||
/** | ||
* Returns a signed INT16 value - 2 bytes - (-32768<->32767) | ||
* @param {Buffer} buffer The buffer to read from | ||
* @param {integer} from The buffer index to read from | ||
* @param {integer} to The buffer index to read to | ||
* @returns {integer} The INT16 value | ||
*/ | ||
function read_int16(buffer, from, to) { | ||
return buffer.readInt16BE(from, to); | ||
} | ||
/** | ||
* Returns a signed INT32 value - 4 bytes - (...) | ||
* @param {Buffer} buffer The buffer to read from | ||
* @param {integer} from The buffer index to read from | ||
* @param {integer} to The buffer index to read to | ||
* @returns {integer} The INT32 value | ||
*/ | ||
function read_int32(buffer, from, to) { | ||
return buffer.readInt32BE(from, to); | ||
} | ||
/** | ||
* Returns a String value | ||
* @param {Buffer} buffer The buffer to read from | ||
* @param {integer} from The buffer index to read from | ||
* @param {integer} to The buffer index to read to | ||
* @returns {integer} The String value | ||
*/ | ||
function read_string(buffer, from, to) { | ||
let acc = []; | ||
for (let i = from; i < to; i++) { | ||
acc.push(buffer[i]); | ||
} | ||
return String.fromCodePoint.apply(CPR, acc); | ||
} | ||
/** | ||
* Returns a double value - 6 bytes | ||
* !Doubles are in fact 8 bytes long, but only the first 6 are encoded! | ||
* @param {Buffer} buffer The buffer to read from | ||
* @param {integer} from The buffer index to read from | ||
* @param {integer} to The buffer index to read to | ||
* @returns {integer} The double value | ||
*/ | ||
function read_double(buffer, from, to) { | ||
buffer.copy( | ||
doubleBuffer, | ||
0, | ||
from, | ||
from + DOUBLE_SIZE | ||
); | ||
return doubleBuffer.readDoubleBE(); | ||
} | ||
/** | ||
* Returns the decoded value for a property | ||
* @param {integer} type The expected variable type | ||
* @param {Buffer} buffer The buffer to read from | ||
* @param {integer} from The buffer index to read from | ||
* @param {integer} to The buffer index to read to | ||
* @returns {?} The decoded value | ||
*/ | ||
function read(buffer, type, from, to) { | ||
let res; | ||
if (type === Types.BOOLEAN) res = read_boolean(buffer, from); | ||
else if (type === Types.NUMBER) { | ||
if (to - from === INT8_SIZE) res = read_int8(buffer, from, to); | ||
else if (to - from === INT16_SIZE) res = read_int16(buffer, from, to); | ||
else if (to - from === INT32_SIZE) res = read_int32(buffer, from, to); | ||
else res = read_double(buffer, from, to); | ||
} | ||
else if (type === Types.STRING) res = read_string(buffer, from, to); | ||
return res; | ||
} | ||
/* Exports -------------------------------------------------------------------*/ | ||
module.exports = Decode; |
@@ -9,25 +9,167 @@ /** | ||
const Workbench = require('./Workbench'); | ||
const Types = require('./Types'); | ||
/* Local variables -----------------------------------------------------------*/ | ||
// One frame - all overheads | ||
const MAX_SIZE = 1400; | ||
// Number ranges and byte sizes | ||
const MIN_INT8 = -128; | ||
const MAX_INT8 = 127; | ||
const MIN_INT16 = -32768; | ||
const MAX_INT16 = 32767; | ||
const INT8_SIZE = 1; | ||
const INT16_SIZE = 2; | ||
const INT32_SIZE = 4; | ||
const DOUBLE_SIZE = 6; | ||
const SEP_CODE = 255; | ||
const allowed_types = ['number', 'boolean', 'string']; | ||
let work_buffer = Buffer.allocUnsafe(MAX_SIZE); | ||
/* Methods -------------------------------------------------------------------*/ | ||
function Encode(schema, obj) { | ||
let result = new Workbench(); | ||
/** | ||
* Encodes a JS object into a Buffer using a Schema | ||
* @param {object} schema The Schema to use for encoding | ||
* @param {object} payload The payload to encode | ||
* @returns {Buffer} The encoded Buffer | ||
*/ | ||
function Encode(schema, payload) { | ||
let result = work_buffer; | ||
const keys = Object.keys(schema); | ||
const len = keys.length; | ||
Object.keys(schema).forEach((key, index) => { | ||
if (obj[key]) { | ||
let _type = schema[key].type || schema[key]; | ||
result.append(Types.SEP); | ||
result.append(Types.NUMBER, index); | ||
result.append(Types.resolve(_type), obj[key]); | ||
result.caret = 0; | ||
for (let i = len - 1; i >= 0; i--) { | ||
let key = keys[i]; | ||
if (is_valid(key, payload)) { | ||
let type = Types.resolve(schema[key].type || schema[key]); | ||
append_index(result, i); | ||
if (type === Types.BOOLEAN) append_boolean(result, payload[key]); | ||
else if (type === Types.NUMBER) append_number(result, payload[key]); | ||
else if (type === Types.STRING) append_string(result, payload[key]); | ||
} | ||
}); | ||
return result.read(Types.BUFFER); | ||
} | ||
return result.slice(0, result.caret); | ||
} | ||
/** | ||
* Returns wether a payload property is valid for encoding | ||
* If not, it will be skipped | ||
* @param {string} key The property key to validate | ||
* @param {object} payload The payload to encode | ||
* @returns {boolean} Wether the property is valid for encoding | ||
*/ | ||
function is_valid(key, payload) { | ||
if (key in payload) { | ||
let _type = typeof payload[key]; | ||
return (allowed_types.includes(_type)); | ||
} | ||
return false; | ||
} | ||
/** | ||
* Appends a Number type value to the Buffer | ||
* @param {Buffer} buffer The Buffer to append to | ||
* @param {number} data The data to append | ||
*/ | ||
function append_number(buffer, data) { | ||
if (Number.isInteger(data)) { | ||
if (data <= MAX_INT8 && data >= MIN_INT8) { | ||
append_int8(buffer, data); | ||
} | ||
else if (data <= MAX_INT16 && data >= MIN_INT16) { | ||
append_int16(buffer, data); | ||
} | ||
else append_int32(buffer, data); | ||
} | ||
else append_double(buffer, data); | ||
} | ||
/** | ||
* Appends a Boolean type value to the Buffer | ||
* @param {Buffer} buffer The Buffer to append to | ||
* @param {number} data The data to append | ||
*/ | ||
function append_boolean(buffer, data) { | ||
buffer[buffer.caret] = data ? 1 : 0; | ||
buffer.caret += INT8_SIZE; | ||
} | ||
/** | ||
* Appends a signed INT8 type value to the Buffer | ||
* @param {Buffer} buffer The Buffer to append to | ||
* @param {number} data The data to append | ||
*/ | ||
function append_int8(buffer, data) { | ||
buffer.writeInt8(data, buffer.caret); | ||
buffer.caret += INT8_SIZE; | ||
} | ||
/** | ||
* Appends a signed INT16 type value to the Buffer | ||
* @param {Buffer} buffer The Buffer to append to | ||
* @param {number} data The data to append | ||
*/ | ||
function append_int16(buffer, data) { | ||
buffer.writeInt16BE(data, buffer.caret); | ||
buffer.caret += INT16_SIZE; | ||
} | ||
/** | ||
* Appends a signed INT32 type value to the Buffer | ||
* @param {Buffer} buffer The Buffer to append to | ||
* @param {number} data The data to append | ||
*/ | ||
function append_int32(buffer, data) { | ||
buffer.writeInt32BE(data, buffer.caret); | ||
buffer.caret += INT32_SIZE; | ||
} | ||
/** | ||
* Appends a double type value to the Buffer | ||
* @param {Buffer} buffer The Buffer to append to | ||
* @param {number} data The data to append | ||
*/ | ||
function append_double(buffer, data) { | ||
// Ommit last 2 digits of the double | ||
buffer.writeDoubleBE(data, buffer.caret); | ||
buffer.caret += DOUBLE_SIZE; | ||
} | ||
/** | ||
* Appends a String type value to the Buffer | ||
* @param {Buffer} buffer The Buffer to append to | ||
* @param {number} data The data to append | ||
*/ | ||
function append_string(buffer, data) { | ||
let len = data.length; | ||
for (let i = 0; i < len; i++) { | ||
buffer[buffer.caret + i] = data.codePointAt(i); | ||
} | ||
buffer.caret += len; | ||
} | ||
/** | ||
* Appends an index type value to the Buffer [255, x] | ||
* @param {Buffer} buffer The Buffer to append to | ||
* @param {number} data The data to append | ||
*/ | ||
function append_index(buffer, data) { | ||
buffer[buffer.caret] = SEP_CODE; | ||
// Unsigned Int | ||
buffer[buffer.caret + 1] = data; | ||
buffer.caret += INT16_SIZE; | ||
} | ||
/* Exports -------------------------------------------------------------------*/ | ||
module.exports = Encode; |
/** | ||
* Main module for Kompactor | ||
* Main module for Compactr | ||
*/ | ||
@@ -4,0 +4,0 @@ |
@@ -9,21 +9,34 @@ /** | ||
const ARRAY = 0; | ||
const BOOLEAN = 1; | ||
const BUFFER = 2; | ||
const NUMBER = 3; | ||
const STRING = 4; | ||
const SEP = 5; | ||
const BOOLEAN = 0; | ||
const BUFFER = 1; | ||
const NUMBER = 2; | ||
const STRING = 3; | ||
const INDEX = 4; | ||
const BOOLEAN_STR = 'boolean'; | ||
const BUFFER_STR = 'buffer'; | ||
const NUMBER_STR = 'number'; | ||
const STRING_STR = 'string'; | ||
/* Methods -------------------------------------------------------------------*/ | ||
/** | ||
* Returns the matching id for a data type | ||
* @param {string|function} type Either a type constructor or name | ||
* @returns {integer} The matching index | ||
*/ | ||
function resolve(type) { | ||
type = type.toLowerCase(); | ||
if (type === 'array' || type === Array) return ARRAY; | ||
if (type === 'boolean' || type === Boolean) return BOOLEAN; | ||
if (type === 'buffer' || type === Buffer) return BUFFER; | ||
if (type === 'number' || type === Number) return NUMBER; | ||
if (type === 'string' || type === String) return STRING; | ||
throw new Error('Unrecognized type ' + type); | ||
let res = BOOLEAN; | ||
if (typeof type === STRING_STR) { | ||
if (type === BUFFER_STR) res = BUFFER; | ||
else if (type === NUMBER_STR) res = NUMBER; | ||
else if (type === STRING_STR) res = STRING; | ||
} | ||
else { | ||
if (type === Buffer) res = BUFFER; | ||
else if (type === Number) res = NUMBER; | ||
else if (type === String) res = STRING; | ||
} | ||
return res; | ||
} | ||
@@ -33,2 +46,2 @@ | ||
module.exports = { ARRAY, BOOLEAN, BUFFER, NUMBER, STRING, resolve }; | ||
module.exports = { BOOLEAN, BUFFER, NUMBER, STRING, INDEX, resolve }; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
6468173
12
337
0
101
1