diff --git a/README.md b/README.md index 9898baf..39b7c6b 100644 --- a/README.md +++ b/README.md @@ -50,13 +50,13 @@ const userSchema = Compactr.schema({ userSchema.write({ id: 123, name: 'John' }); // Get the header bytes -const header = userSchema.headerBytes(); +const header = userSchema.headerBuffer(); // Get the content bytes -const partial = userSchema.contentBytes(); +const partial = userSchema.contentBuffer(); // Get the full payload (header + content bytes) -const buffer = userSchema.bytes(); +const buffer = userSchema.buffer(); @@ -90,7 +90,30 @@ Compactr (partial): ``: 5 bytes ## Protocol details -[Compactr Protocol](https://github.com/compactr/protocol) +### Data types + +Type | Count bytes | Byte size +--- | --- | --- +boolean | 0 | 1 +number | 0 | 8 +int8 | 0 | 1 +int16 | 0 | 2 +int32 | 0 | 4 +double | 0 | 8 +string | 1 | 2/char +char8 | 1 | 1/char +char16 | 1 | 2/char +char32 | 1 | 4/char +array | 1 | (x)/entry +object | 1 | (x) +unsigned | 0 | 8 +unsigned8 | 0 | 1 +unsigned16 | 0 | 2 +unsigned32 | 0 | 4 + +* Count bytes range can be specified per-item in the schema* + +See the full [Compactr protocol](https://github.com/compactr/protocol) ## Want to help ? diff --git a/package.json b/package.json index 39f3762..1a95533 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "compactr", - "version": "2.2.0", + "version": "2.2.1", "description": "Schema based serialization made easy", "main": "index.js", "scripts": { diff --git a/src/converter.js b/src/converter.js index 9a7e771..c0777fa 100644 --- a/src/converter.js +++ b/src/converter.js @@ -4,35 +4,43 @@ /* Methods -------------------------------------------------------------------*/ +/** @private */ function int8(value) { return Number(value) & 0xff; } +/** @private */ function int16(value) { return Number(value) & 0xffff; } +/** @private */ function int32(value) { return Number(value) & 0xffffffff; } +/** @private */ function double(value) { const ret = Number(value); return (Number.isFinite(ret)) ? ret : 0; } +/** @private */ function string(value) { return '' + value; } +/** @private */ function boolean(value) { return !!value; } +/** @private */ function object(value) { return (value.constructor === Object) ? value : {}; } +/** @private */ function array(value) { return (value.concat !== undefined) ? value : [value]; } diff --git a/src/decoder.js b/src/decoder.js index 05e0d5a..e8fe39a 100644 --- a/src/decoder.js +++ b/src/decoder.js @@ -9,10 +9,12 @@ const fromChar = String.fromCharCode; /* Methods -------------------------------------------------------------------*/ +/** @private */ function boolean(bytes) { return !!bytes[0]; } +/** @private */ function number(bytes) { if (bytes.length === 1) return (!(bytes[0] & 0x80))?bytes[0]:((0xff - bytes[0] + 1) * -1); if (bytes.length === 2) { @@ -22,12 +24,14 @@ function number(bytes) { return (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | (bytes[3]); } +/** @private */ function unsigned(bytes) { if (bytes.length === 1) return bytes[0]; if (bytes.length === 2) return bytes[0] << 8 | bytes[1]; return (bytes[0] << 24) | (bytes[1] << 16) | (bytes[2] << 8) | (bytes[3]); } +/** @private */ function string(bytes) { let res = []; for (let i = 0; i < bytes.length; i += 2) { @@ -36,6 +40,7 @@ function string(bytes) { return fromChar.apply(null, res); } +/** @private */ function char8(bytes) { let res = []; for (let i = 0; i < bytes.length; i += 1) { @@ -44,6 +49,7 @@ function char8(bytes) { return fromChar.apply(null, res); } +/** @private */ function char32(bytes) { let res = []; for (let i = 0; i < bytes.length; i += 4) { @@ -52,6 +58,7 @@ function char32(bytes) { return fromChar.apply(null, res); } +/** @private */ function array(schema, bytes) { const ret = []; for (let i = 0; i < bytes.length;) { @@ -64,11 +71,15 @@ function array(schema, bytes) { return ret; } +/** @private */ function object(schema, bytes) { return schema.read(bytes); } -/** Credit to @feross' ieee754 module */ +/** + * Credit to @feross' ieee754 module + * @private + */ function double(bytes) { let s = bytes[0]; let e = (s & 127); diff --git a/src/encoder.js b/src/encoder.js index 073e21e..42a511e 100644 --- a/src/encoder.js +++ b/src/encoder.js @@ -17,28 +17,44 @@ const fastPush = Array.prototype.push; /* Methods -------------------------------------------------------------------*/ +/** @private */ function boolean(val) { return [val ? 1 : 0]; } +/** @private */ function int8(val) { return [(val < 0) ? 256 + val : val]; } +/** @private */ function int16(val) { if (val < 0) val = 0xffff + val + 1; return [val >> 8, val & 0xff]; } +/** @private */ function int32(val) { if (val < 0) val = 0xffffffff + val + 1; return [val >> 24, val >> 16, val >> 8, val & 0xff]; } -function unsigned8(val) { return [val & 0xff]; } -function unsigned16(val) { return [val >> 8, val & 0xff]; } -function unsigned32(val) { return [val >> 24, val >> 16, val >> 8, val & 0xff]; } +/** @private */ +function unsigned8(val) { + return [val & 0xff]; +} + +/** @private */ +function unsigned16(val) { + return [val >> 8, val & 0xff]; +} + +/** @private */ +function unsigned32(val) { + return [val >> 24, val >> 16, val >> 8, val & 0xff]; +} +/** @private */ function string(encoding, val) { const chars = []; for (let i = 0; i < val.length; i++) { @@ -48,6 +64,7 @@ function string(encoding, val) { return chars; } +/** @private */ function array(schema, val) { const ret = []; for (let i = 0; i < val.length; i++) { @@ -58,11 +75,15 @@ function array(schema, val) { return ret; } +/** @private */ function object(schema, val) { return schema.write(val).buffer(); } -/** Credit to @feross' ieee754 module */ +/** + * Credit to @feross' ieee754 module + * @private + */ function double(val) { var buffer = []; var e, m, c; @@ -120,6 +141,7 @@ function double(val) { return buffer; } +/** @private */ function getSize(count, byteLength) { return intMap[count](byteLength); } diff --git a/src/reader.js b/src/reader.js index 2d59761..258efdd 100644 --- a/src/reader.js +++ b/src/reader.js @@ -10,11 +10,20 @@ const Decoder = require('./decoder'); function Reader(scope) { + /** + * Decodes an encoded buffer. Requires header bytes. + * @param {Buffer} bytes + * @returns {Object} The decoded buffer + */ function read(bytes) { readHeader(bytes); return readContent(bytes, scope.contentBegins); } + /** + * Reads only the header of an encoded buffer + * @param {*} bytes + */ function readHeader(bytes) { scope.header = []; let caret = 1; @@ -27,6 +36,7 @@ function Reader(scope) { return this; } + /** @private */ function readKey(bytes, caret, index) { const key = getSchemaDef(bytes[caret]); @@ -37,12 +47,19 @@ function Reader(scope) { return caret + key.count + 1; } + /** @private */ function getSchemaDef(index) { for (let i = 0; i < scope.items.length; i++) { if (scope.indices[scope.items[i]].index === index) return scope.indices[scope.items[i]]; } } + /** + * Reads only a content buffer and returns an object with the decoded values + * @param {Buffer} bytes The content buffer + * @param {Integer} caret The content bytes offset, if the bytes also include an header + * @returns {Object} An object with the decoded values + */ function readContent(bytes, caret) { caret = caret || 0; const ret = {}; diff --git a/src/schema.js b/src/schema.js index c960448..1fa74a8 100644 --- a/src/schema.js +++ b/src/schema.js @@ -12,6 +12,11 @@ const Converter = require('./converter'); /* Methods -------------------------------------------------------------------*/ +/** + * Creates a new schema definition, with a reader and writer attached + * @param {*} schema The schema to use + * @param {Object (keyOrder: {boolean})} options The options for the schema + */ function Schema(schema, options = { keyOrder: false }) { const sizeRef = { boolean: 1, @@ -48,6 +53,7 @@ function Schema(schema, options = { keyOrder: false }) { applyBlank(); // Pre-load header for easy streaming + /** @private */ function preformat(schema) { const ret = {}; Object.keys(schema) @@ -74,6 +80,7 @@ function Schema(schema, options = { keyOrder: false }) { return ret; } + /** @private */ function applyBlank() { for (let key in scope.schema) { scope.header.push({ @@ -83,6 +90,7 @@ function Schema(schema, options = { keyOrder: false }) { } } + /** @private */ function computeNested(schema, key) { const keyType = schema[key].type; const isObject = (keyType === 'object'); diff --git a/src/writer.js b/src/writer.js index 54a8b8d..d35717c 100644 --- a/src/writer.js +++ b/src/writer.js @@ -10,6 +10,12 @@ const fastPush = Array.prototype.push; function Writer(scope) { + /** + * Start writing some data against a schema + * @param {*} data The data to be encoded + * @param {Object (coerce: {boolean}, validate: {boolean})} options The options for the encoding + * @returns {Writer} Self reference + */ function write(data, options) { scope.headerBytes = [0]; scope.contentBytes = []; @@ -32,6 +38,7 @@ function Writer(scope) { return this; } + /** @private */ function splitBytes(encoded, key) { scope.headerBytes.push(scope.indices[key].index); fastPush.apply(scope.headerBytes, scope.indices[key].getSize(encoded.length)); @@ -50,6 +57,11 @@ function Writer(scope) { fastPush.apply(scope.contentBytes, res); } + /** + * Returns the byte sizes of a data object, for insight or troubleshooting + * @param {*} data The data to extract size information of + * @returns {Object} The detailed sizes information + */ function sizes(data) { const s = {}; for (let key in data) { @@ -63,6 +75,7 @@ function Writer(scope) { return s; } + /** @private */ function filterKeys(data) { const res = []; for (let key in data) { @@ -71,22 +84,35 @@ function Writer(scope) { return res; } + /** @private */ function concat(header, content) { - // return [...header, ...content]; const res = []; fastPush.apply(res, header); fastPush.apply(res, content); return res; } + /** + * Returns the bytes from the header of the encoded data buffer. + * A fresh schema with no written data will return a blank, usable for partial encodings. + * @returns {Buffer} The header buffer + */ function headerBuffer() { return Buffer.from(scope.headerBytes); } + /** + * Returns the bytes from the content of the encoded data buffer. + * @returns {Buffer} The content buffer + */ function contentBuffer() { return Buffer.from(scope.contentBytes); } + /** + * Returns the bytes from the header AND content of the encoded data buffer. + * @returns {Buffer} The data buffer + */ function buffer() { return Buffer.from(concat(scope.headerBytes, scope.contentBytes)); }