diff --git a/.travis.yml b/.travis.yml
index bfb6689..70b7046 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,5 +1,6 @@
language: node_js
node_js:
- - "8.1"
+ - "6"
+ - "8"
script: "npm run test && npm run bench"
cache: yarn
diff --git a/README.md b/README.md
index 7452601..9898baf 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@
[![Compactr](https://img.shields.io/npm/v/compactr.svg)](https://www.npmjs.com/package/compactr)
-[![Node](https://img.shields.io/badge/node->%3D4.0-blue.svg)](https://nodejs.org)
+[![Node](https://img.shields.io/badge/node->%3D6.0-blue.svg)](https://nodejs.org)
[![Build Status](https://travis-ci.org/compactr/compactr.js.svg?branch=master)](https://travis-ci.org/compactr/compactr.js)
[![Gitter](https://img.shields.io/gitter/room/compactr/compactr.svg)](https://gitter.im/compactr/compactr)
@@ -20,7 +20,9 @@
## What is this and why does it matter?
-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 development and reduce repetition by allowing you to build schemas for your data directly in your scripting language. For example, if you have a DB schema for a model, you could use that directly as a schema for Compactr.
+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 the Javascript ecosystem.
+
+[More](docs/ABOUT.md)
## Install
@@ -47,51 +49,38 @@ const userSchema = Compactr.schema({
// Encoding
userSchema.write({ id: 123, name: 'John' });
-// Get the schema header bytes (for partial loads)
+// Get the header bytes
const header = userSchema.headerBytes();
-// Get the partial load bytes
+// Get the content bytes
const partial = userSchema.contentBytes();
-// Get the full header + content bytes
+// Get the full payload (header + content bytes)
const buffer = userSchema.bytes();
-// Decoding (full)
+// Decoding a full payload
const content = userSchema.read(buffer);
-// Decoding (partial)
+// Decoding a partial payload (content)
const content = userSchema.readContent(partial);
```
-## Performances
-
-```
-[Array] JSON x 188 ops/sec ±2.47% (73 runs sampled)
-[Array] Compactr x 248 ops/sec ±3.16% (72 runs sampled)
+## Speed comparison
-[Boolean] JSON x 220 ops/sec ±5.04% (71 runs sampled)
-[Boolean] Compactr x 731 ops/sec ±7.57% (74 runs sampled)
+![Speed](http://res.cloudinary.com/kalm/image/upload/v1507323565/compactr_speed_adhlsk.png)
-[Float] JSON x 159 ops/sec ±3.41% (70 runs sampled)
-[Float] Compactr x 476 ops/sec ±1.58% (85 runs sampled)
+*Measured against plain JSON serialization + convertion to buffer. Compactr serialization is performed with default settings via the partial (content only) load method*
-[Integer] JSON x 264 ops/sec ±1.79% (79 runs sampled)
-[Integer] Compactr x 885 ops/sec ±1.36% (84 runs sampled)
-
-[Object] JSON x 139 ops/sec ±1.89% (76 runs sampled)
-[Object] Compactr x 169 ops/sec ±1.52% (80 runs sampled)
-
-[String] JSON x 107 ops/sec ±6.86% (64 runs sampled)
-[String] Compactr x 167 ops/sec ±4.86% (72 runs sampled)
-```
## Size comparison
+![Size](http://res.cloudinary.com/kalm/image/upload/v1507323565/compactr_bytes_cbjxka.png)
+
JSON: `{"id":123,"name":"John"}`: 24 bytes
Compactr (full): ``: 10 bytes
diff --git a/benchmarks/array.js b/benchmarks/array.js
index 70f6e1f..0063e5b 100644
--- a/benchmarks/array.js
+++ b/benchmarks/array.js
@@ -30,7 +30,7 @@ function arrJSON() {
let packed, unpacked;
for(let i = 0; i> 24, val >> 16, val >> 8, val & 0xff];
function string(encoding, val) {
const chars = [];
for (let i = 0; i < val.length; i++) {
- chars.push.apply(chars, encoding(val.charCodeAt(i)));
+ fastPush.apply(chars, encoding(val.charCodeAt(i)));
}
return chars;
@@ -51,14 +52,14 @@ function array(schema, val) {
const ret = [];
for (let i = 0; i < val.length; i++) {
let encoded = schema.transformIn(val[i]);
- ret.push.apply(ret, schema.getSize(encoded.length));
- ret.push.apply(ret, encoded);
+ fastPush.apply(ret, schema.getSize(encoded.length));
+ fastPush.apply(ret, encoded);
}
return ret;
}
function object(schema, val) {
- return schema.write(val).array();
+ return schema.write(val).buffer();
}
/** Credit to @feross' ieee754 module */
diff --git a/src/reader.js b/src/reader.js
index 4312876..2d59761 100644
--- a/src/reader.js
+++ b/src/reader.js
@@ -20,20 +20,20 @@ function Reader(scope) {
let caret = 1;
const keys = bytes[0];
for (let i = 0; i < keys; i++) {
- caret = readKey(bytes, caret);
+ caret = readKey(bytes, caret, i);
}
scope.contentBegins = caret;
return this;
}
- function readKey(bytes, caret) {
+ function readKey(bytes, caret, index) {
const key = getSchemaDef(bytes[caret]);
- scope.header.push({
+ scope.header[index] = {
key,
- size: key.size || Decoder.unsigned(bytes.slice(caret + 1, caret + key.count + 1))
- });
+ size: key.size || Decoder.unsigned(bytes.subarray(caret + 1, caret + key.count + 1))
+ };
return caret + key.count + 1;
}
@@ -46,8 +46,13 @@ function Reader(scope) {
function readContent(bytes, caret) {
caret = caret || 0;
const ret = {};
+ if (scope.options.keyOrder === true) {
+ for (let i = 0; i < scope.items.length; i++) {
+ ret[scope.items[i]] = undefined;
+ }
+ }
for (let i = 0; i < scope.header.length; i++) {
- ret[scope.header[i].key.name] = scope.header[i].key.transformOut(bytes.slice(caret, caret + scope.header[i].size));
+ ret[scope.header[i].key.name] = scope.header[i].key.transformOut(bytes.subarray(caret, caret + scope.header[i].size));
caret += scope.header[i].size;
}
return ret;
diff --git a/src/schema.js b/src/schema.js
index a056e51..c960448 100644
--- a/src/schema.js
+++ b/src/schema.js
@@ -12,7 +12,7 @@ const Converter = require('./converter');
/* Methods -------------------------------------------------------------------*/
-function Schema(schema) {
+function Schema(schema, options = { keyOrder: false }) {
const sizeRef = {
boolean: 1,
number: 8,
@@ -36,10 +36,11 @@ function Schema(schema) {
schema,
indices: {},
items: Object.keys(schema),
- headerBytes: [],
- contentBytes: [],
+ headerBytes: [0],
+ contentBytes: [0],
header: [],
- contentBegins: 0
+ contentBegins: 0,
+ options
};
scope.indices = preformat(schema);
const writer = Writer(scope);
diff --git a/src/writer.js b/src/writer.js
index 1f9c20d..54a8b8d 100644
--- a/src/writer.js
+++ b/src/writer.js
@@ -2,12 +2,17 @@
'use strict';
+/* Local variables -----------------------------------------------------------*/
+
+const fastPush = Array.prototype.push;
+
/* Methods -------------------------------------------------------------------*/
function Writer(scope) {
function write(data, options) {
- clear();
+ scope.headerBytes = [0];
+ scope.contentBytes = [];
const keys = filterKeys(data);
scope.headerBytes[0] = keys.length;
@@ -29,13 +34,20 @@ function Writer(scope) {
function splitBytes(encoded, key) {
scope.headerBytes.push(scope.indices[key].index);
- scope.headerBytes.push.apply(scope.headerBytes, scope.indices[key].getSize(encoded.length));
- scope.contentBytes.push.apply(scope.contentBytes, encoded);
- }
-
- function clear() {
- scope.headerBytes = [0];
- scope.contentBytes = [];
+ fastPush.apply(scope.headerBytes, scope.indices[key].getSize(encoded.length));
+ let res = encoded;
+ if (scope.indices[key].size !== null) {
+ if (scope.indices[key].size !== encoded.length) {
+ if(scope.indices[key].size > encoded.length) {
+ res = new Array(scope.indices[key].size).fill(0);
+ res.splice(0, encoded.length, ...encoded);
+ }
+ else {
+ res = encoded.slice(0, scope.indices[key].size);
+ }
+ }
+ }
+ fastPush.apply(scope.contentBytes, res);
}
function sizes(data) {
@@ -60,9 +72,10 @@ function Writer(scope) {
}
function concat(header, content) {
+ // return [...header, ...content];
const res = [];
- res.push.apply(res, header);
- res.push.apply(res, content);
+ fastPush.apply(res, header);
+ fastPush.apply(res, content);
return res;
}
@@ -78,19 +91,7 @@ function Writer(scope) {
return Buffer.from(concat(scope.headerBytes, scope.contentBytes));
}
- function headerArray() {
- return scope.headerBytes;
- }
-
- function contentArray() {
- return scope.contentBytes;
- }
-
- function array() {
- return concat(scope.headerBytes, scope.contentBytes);
- }
-
- return { write, headerBuffer, headerArray, contentBuffer, contentArray, buffer, array, sizes };
+ return { write, headerBuffer, contentBuffer, buffer, sizes };
}
/* Exports -------------------------------------------------------------------*/
diff --git a/tests/index.js b/tests/index.js
index e71ed7b..bda2e8d 100644
--- a/tests/index.js
+++ b/tests/index.js
@@ -16,15 +16,15 @@ describe('Data integrity - simple', () => {
const Schema = Compactr.schema({ test: { type: 'boolean' } });
it('should preserve boolean value and type - true', () => {
- expect(Schema.read(Schema.write({ test: true }).array())).to.deep.equal({ test: true });
+ expect(Schema.read(Schema.write({ test: true }).buffer())).to.deep.equal({ test: true });
});
it('should preserve boolean value and type - false', () => {
- expect(Schema.read(Schema.write({ test: false }).array())).to.deep.equal({ test: false });
+ expect(Schema.read(Schema.write({ test: false }).buffer())).to.deep.equal({ test: false });
});
it('should skip null or undefined values', () => {
- expect(Schema.read(Schema.write({ test: null }).array())).to.deep.equal({});
+ expect(Schema.read(Schema.write({ test: null }).buffer())).to.deep.equal({});
});
});
@@ -32,11 +32,11 @@ describe('Data integrity - simple', () => {
const Schema = Compactr.schema({ test: { type: 'number' } });
it('should preserve number value and type', () => {
- expect(Schema.read(Schema.write({ test: 23.23 }).array())).to.deep.equal({ test: 23.23 });
+ expect(Schema.read(Schema.write({ test: 23.23 }).buffer())).to.deep.equal({ test: 23.23 });
});
it('should preserve number value and type for negative values', () => {
- expect(Schema.read(Schema.write({ test: -23.23 }).array())).to.deep.equal({ test: -23.23 });
+ expect(Schema.read(Schema.write({ test: -23.23 }).buffer())).to.deep.equal({ test: -23.23 });
});
});
@@ -44,7 +44,7 @@ describe('Data integrity - simple', () => {
const Schema = Compactr.schema({ test: { type: 'string' } });
it('should preserve string value and type', () => {
- expect(Schema.read(Schema.write({ test: 'hello world' }).array())).to.deep.equal({ test: 'hello world' });
+ expect(Schema.read(Schema.write({ test: 'hello world' }).buffer())).to.deep.equal({ test: 'hello world' });
});
});
@@ -52,7 +52,7 @@ describe('Data integrity - simple', () => {
const Schema = Compactr.schema({ test: { type: 'array', items: { type: 'string' } } });
it('should preserve array values and types', () => {
- expect(Schema.read(Schema.write({ test: ['a', 'b', 'c'] }).array())).to.deep.equal({ test: ['a', 'b', 'c'] });
+ expect(Schema.read(Schema.write({ test: ['a', 'b', 'c'] }).buffer())).to.deep.equal({ test: ['a', 'b', 'c'] });
});
});
@@ -60,7 +60,7 @@ describe('Data integrity - simple', () => {
const Schema = Compactr.schema({ test: { type: 'object', schema: { test: { type: 'number' } } } });
it('should preserve object values and types', () => {
- expect(Schema.read(Schema.write({ test: { test: 23.23 } }).array())).to.deep.equal({ test: { test: 23.23 } });
+ expect(Schema.read(Schema.write({ test: { test: 23.23 } }).buffer())).to.deep.equal({ test: { test: 23.23 } });
});
});
});
@@ -70,11 +70,11 @@ describe('Data integrity - multi simple', () => {
const Schema = Compactr.schema({ test: { type: 'boolean' }, test2: { type: 'boolean' } });
it('should preserve boolean value and type - false', () => {
- expect(Schema.read(Schema.write({ test: false, test2: true }).array())).to.deep.equal({ test: false, test2: true });
+ expect(Schema.read(Schema.write({ test: false, test2: true }).buffer())).to.deep.equal({ test: false, test2: true });
});
it('should skip null or undefined values', () => {
- expect(Schema.read(Schema.write({ test: null, test2: false }).array())).to.deep.equal({ test2: false });
+ expect(Schema.read(Schema.write({ test: null, test2: false }).buffer())).to.deep.equal({ test2: false });
});
});
@@ -82,7 +82,7 @@ describe('Data integrity - multi simple', () => {
const Schema = Compactr.schema({ test: { type: 'number' }, test2: { type: 'number' } });
it('should preserve number value and type', () => {
- expect(Schema.read(Schema.write({ test: 23.23, test2: -97.7 }).array())).to.deep.equal({ test: 23.23, test2: -97.7 });
+ expect(Schema.read(Schema.write({ test: 23.23, test2: -97.7 }).buffer())).to.deep.equal({ test: 23.23, test2: -97.7 });
});
});
@@ -90,7 +90,7 @@ describe('Data integrity - multi simple', () => {
const Schema = Compactr.schema({ test: { type: 'string' }, test2: { type: 'string' } });
it('should preserve string value and type', () => {
- expect(Schema.read(Schema.write({ test: 'hello world', test2: 'écho' }).array())).to.deep.equal({ test: 'hello world', test2: 'écho' });
+ expect(Schema.read(Schema.write({ test: 'hello world', test2: 'écho' }).buffer())).to.deep.equal({ test: 'hello world', test2: 'écho' });
});
});
@@ -98,7 +98,7 @@ describe('Data integrity - multi simple', () => {
const Schema = Compactr.schema({ test: { type: 'array', items: { type: 'string' } }, test2: { type: 'array', items: { type: 'string' } } });
it('should preserve array values and types', () => {
- expect(Schema.read(Schema.write({ test: ['a', 'b', 'c'], test2: ['d', 'e', 'f'] }).array())).to.deep.equal({ test: ['a', 'b', 'c'], test2: ['d', 'e', 'f'] });
+ expect(Schema.read(Schema.write({ test: ['a', 'b', 'c'], test2: ['d', 'e', 'f'] }).buffer())).to.deep.equal({ test: ['a', 'b', 'c'], test2: ['d', 'e', 'f'] });
});
});
@@ -106,7 +106,7 @@ describe('Data integrity - multi simple', () => {
const Schema = Compactr.schema({ test: { type: 'object', schema: { test: { type: 'number' } } }, test2: { type: 'object', schema: { test: { type: 'number' } } } });
it('should preserve object values and types', () => {
- expect(Schema.read(Schema.write({ test: { test: 23.23 }, test2: { test: -97.7 } }).array())).to.deep.equal({ test: { test: 23.23 }, test2: { test: -97.7 } });
+ expect(Schema.read(Schema.write({ test: { test: 23.23 }, test2: { test: -97.7 } }).buffer())).to.deep.equal({ test: { test: 23.23 }, test2: { test: -97.7 } });
});
});
});
@@ -122,7 +122,7 @@ describe('Data integrity - multi mixed', () => {
});
it('should preserve values and types', () => {
- expect(Schema.read(Schema.write({ bool: true, num: 23.23, str: 'hello world', arr: ['a', 'b', 'c'], obj: { sub: 'way' } }).array())).to.deep.equal({ bool: true, num: 23.23, str: 'hello world', arr: ['a', 'b', 'c'], obj: { sub: 'way' } });
+ expect(Schema.read(Schema.write({ bool: true, num: 23.23, str: 'hello world', arr: ['a', 'b', 'c'], obj: { sub: 'way' } }).buffer())).to.deep.equal({ bool: true, num: 23.23, str: 'hello world', arr: ['a', 'b', 'c'], obj: { sub: 'way' } });
});
});
});
@@ -134,15 +134,15 @@ describe('Data integrity - partial - simple', () => {
const Schema = Compactr.schema({ test: { type: 'boolean' } });
it('should preserve boolean value and type - true', () => {
- expect(Schema.readContent(Schema.write({ test: true }).contentArray())).to.deep.equal({ test: true });
+ expect(Schema.readContent(Schema.write({ test: true }).contentBuffer())).to.deep.equal({ test: true });
});
it('should preserve boolean value and type - false', () => {
- expect(Schema.readContent(Schema.write({ test: false }).contentArray())).to.deep.equal({ test: false });
+ expect(Schema.readContent(Schema.write({ test: false }).contentBuffer())).to.deep.equal({ test: false });
});
it('should still send one 0 byte in case of null (coersed)', () => {
- expect(Schema.readContent(Schema.write({ test: null }).contentArray())).to.deep.equal({ test: false });
+ expect(Schema.readContent(Schema.write({ test: null }).contentBuffer())).to.deep.equal({ test: false });
});
});
@@ -150,11 +150,11 @@ describe('Data integrity - partial - simple', () => {
const Schema = Compactr.schema({ test: { type: 'number' } });
it('should preserve number value and type', () => {
- expect(Schema.readContent(Schema.write({ test: 23.23 }).contentArray())).to.deep.equal({ test: 23.23 });
+ expect(Schema.readContent(Schema.write({ test: 23.23 }).contentBuffer())).to.deep.equal({ test: 23.23 });
});
it('should preserve number value and type for negative values', () => {
- expect(Schema.readContent(Schema.write({ test: -23.23 }).contentArray())).to.deep.equal({ test: -23.23 });
+ expect(Schema.readContent(Schema.write({ test: -23.23 }).contentBuffer())).to.deep.equal({ test: -23.23 });
});
});
@@ -162,15 +162,15 @@ describe('Data integrity - partial - simple', () => {
const Schema = Compactr.schema({ test: { type: 'string', size: 22 } });
it('should preserve string value and type', () => {
- expect(Schema.readContent(Schema.write({ test: 'hello world' }).contentArray())).to.deep.equal({ test: 'hello world' });
+ expect(Schema.readContent(Schema.write({ test: 'hello world' }).contentBuffer())).to.deep.equal({ test: 'hello world' });
});
});
describe('Array', () => {
- const Schema = Compactr.schema({ test: { type: 'array', size: 20, items: { type: 'string' } } });
+ const Schema = Compactr.schema({ test: { type: 'array', size: 12, items: { type: 'string' } } });
it('should preserve array values and types', () => {
- expect(Schema.readContent(Schema.write({ test: ['a', 'b', 'c'] }).contentArray())).to.deep.equal({ test: ['a', 'b', 'c'] });
+ expect(Schema.readContent(Schema.write({ test: ['a', 'b', 'c'] }).contentBuffer())).to.deep.equal({ test: ['a', 'b', 'c', '', '', ''] });
});
});
@@ -178,7 +178,7 @@ describe('Data integrity - partial - simple', () => {
const Schema = Compactr.schema({ test: { type: 'object', size: 20, schema: { test: { type: 'number' } } } });
it('should preserve object values and types', () => {
- expect(Schema.readContent(Schema.write({ test: { test: 23.23 } }).contentArray())).to.deep.equal({ test: { test: 23.23 } });
+ expect(Schema.readContent(Schema.write({ test: { test: 23.23 } }).contentBuffer())).to.deep.equal({ test: { test: 23.23 } });
});
});
});
@@ -188,11 +188,11 @@ describe('Data integrity - partial - multi simple', () => {
const Schema = Compactr.schema({ test: { type: 'boolean' }, test2: { type: 'boolean' } });
it('should preserve boolean value and type - false', () => {
- expect(Schema.readContent(Schema.write({ test: false, test2: true }).contentArray())).to.deep.equal({ test: false, test2: true });
+ expect(Schema.readContent(Schema.write({ test: false, test2: true }).contentBuffer())).to.deep.equal({ test: false, test2: true });
});
it('should skip null or undefined values', () => {
- expect(Schema.readContent(Schema.write({ test: null, test2: false }).contentArray())).to.deep.equal({ test: false, test2: false });
+ expect(Schema.readContent(Schema.write({ test: null, test2: false }).contentBuffer())).to.deep.equal({ test: false, test2: false });
});
});
@@ -200,7 +200,7 @@ describe('Data integrity - partial - multi simple', () => {
const Schema = Compactr.schema({ test: { type: 'number' }, test2: { type: 'number' } });
it('should preserve number value and type', () => {
- expect(Schema.readContent(Schema.write({ test: 23.23, test2: -97.7 }).contentArray())).to.deep.equal({ test: 23.23, test2: -97.7 });
+ expect(Schema.readContent(Schema.write({ test: 23.23, test2: -97.7 }).contentBuffer())).to.deep.equal({ test: 23.23, test2: -97.7 });
});
});
@@ -208,7 +208,7 @@ describe('Data integrity - partial - multi simple', () => {
const Schema = Compactr.schema({ test: { type: 'string', size: 22 }, test2: { type: 'string', size: 8 } });
it('should preserve string value and type', () => {
- expect(Schema.readContent(Schema.write({ test: 'hello world', test2: 'écho' }).contentArray())).to.deep.equal({ test: 'hello world', test2: 'écho' });
+ expect(Schema.readContent(Schema.write({ test: 'hello world', test2: 'écho' }).contentBuffer())).to.deep.equal({ test: 'hello world', test2: 'écho' });
});
});
@@ -216,7 +216,7 @@ describe('Data integrity - partial - multi simple', () => {
const Schema = Compactr.schema({ test: { type: 'array', size: 9, items: { type: 'string' } }, test2: { type: 'array', size: 9, items: { type: 'string' } } });
it('should preserve array values and types', () => {
- expect(Schema.readContent(Schema.write({ test: ['a', 'b', 'c'], test2: ['d', 'e', 'f'] }).contentArray())).to.deep.equal({ test: ['a', 'b', 'c'], test2: ['d', 'e', 'f'] });
+ expect(Schema.readContent(Schema.write({ test: ['a', 'b', 'c'], test2: ['d', 'e', 'f'] }).contentBuffer())).to.deep.equal({ test: ['a', 'b', 'c'], test2: ['d', 'e', 'f'] });
});
});
@@ -224,7 +224,7 @@ describe('Data integrity - partial - multi simple', () => {
const Schema = Compactr.schema({ test: { type: 'object', size: 11, schema: { test: { type: 'number' } } }, test2: { type: 'object', size: 11, schema: { test: { type: 'number' } } } });
it('should preserve object values and types', () => {
- expect(Schema.readContent(Schema.write({ test: { test: 23.23 }, test2: { test: -97.7 } }).contentArray())).to.deep.equal({ test: { test: 23.23 }, test2: { test: -97.7 } });
+ expect(Schema.readContent(Schema.write({ test: { test: 23.23 }, test2: { test: -97.7 } }).contentBuffer())).to.deep.equal({ test: { test: 23.23 }, test2: { test: -97.7 } });
});
});
});
@@ -240,7 +240,7 @@ describe('Data integrity - partial - multi mixed', () => {
});
it('should preserve values and types', () => {
- expect(Schema.readContent(Schema.write({ bool: true, num: 23.23, str: 'hello world', arr: ['a', 'b', 'c'], obj: { sub: 'way' } }).contentArray())).to.deep.equal({ bool: true, num: 23.23, str: 'hello world', arr: ['a', 'b', 'c'], obj: { sub: 'way' } });
+ expect(Schema.readContent(Schema.write({ bool: true, num: 23.23, str: 'hello world', arr: ['a', 'b', 'c'], obj: { sub: 'way' } }).contentBuffer())).to.deep.equal({ bool: true, num: 23.23, str: 'hello world', arr: ['a', 'b', 'c'], obj: { sub: 'way' } });
});
});
});
\ No newline at end of file