protosphere
Version:
protocol buffers made easy
627 lines (583 loc) • 18.8 kB
JavaScript
const pbf = require('pbf');
const mapKeys = require('lodash/fp/mapKeys');
const toPairs = require('lodash/fp/toPairs');
const sortBy = require('lodash/fp/sortBy');
const fromPairs = require('lodash/fp/fromPairs');
const sortObject = (object) => fromPairs(sortBy(0)(toPairs(object)));
// schema = _(schema).toPairs().sortBy(0).fromPairs().value();
const classify = (subject) => {
switch (typeof subject) {
case 'string':
return 'string';
break;
case 'number':
if (isNaN(subject) === true) return 'nan';
if (isFinite(subject) === false) return 'infinity';
if (Number.isInteger(subject) === false) return 'double';
return 'integer';
break;
case 'undefined':
return 'undefined';
break;
case 'boolean':
return 'boolean';
break;
case 'function':
return 'function';
break;
case 'object':
if (subject === null) return 'null';
switch (subject.__proto__) {
case undefined:
return undefined;
break;
case Object.prototype:
return 'object';
break;
case Array.prototype:
return 'array';
break;
case Error.prototype:
return 'error';
break;
case Promise.prototype:
return 'promise';
break;
case Date.prototype:
return 'date';
break;
case RegExp.prototype:
return 'regexp';
break;
case Map.prototype:
return 'map';
break;
case Set.prototype:
return 'set';
break;
case WeakMap.prototype:
return 'weakmap';
break;
case WeakSet.prototype:
return 'weakset';
break;
case ArrayBuffer.prototype:
return 'arraybuffer';
break;
case DataView.prototype:
return 'dataview';
break;
// -128 to 127; 1 byte; byte; int8_t
// 8-bit two's complement signed integer
case Int8Array.prototype:
return 'int8array';
break;
// 0 to 255; 1 byte; octet; uint8_t
// 8-bit unsigned integer
case Uint8Array.prototype:
return 'uint8array';
break;
// 0 to 255; 1 byte; octet; uint8_t
// 8-bit unsigned integer (clamped)
case Uint8ClampedArray.prototype:
return 'uint8clampedarray';
break;
// -32768 to 32767; 2 bytes; short; int16_t
// 16-bit two's complement signed integer
case Int16Array.prototype:
return 'int16array';
break;
// 0 to 65535; 2 bytes; unsigned short; uint16_t
// 16-bit unsigned integer
case Uint16Array.prototype:
return 'uint16array';
break;
// -2147483648 to 2147483647; 4 bytes; long; int32_t
// 32-bit two's complement signed integer
case Int32Array.prototype:
return 'int32array';
break;
// 0 to 4294967295; 4 bytes; unsigned long; uint32_t
// 32-bit unsigned integer
case Uint32Array.prototype:
return 'uint32array';
break;
// 1.2x10-38 to 3.4x1038; 4 bytes; unrestricted float; float
// 32-bit IEEE floating point number ( 7 significant digits e.g. 1.1234567)
case Float32Array.prototype:
return 'float32array';
break;
// 5.0x10-324 to 1.8x10308; 8 bytes; unrestricted double; double
// 64-bit IEEE floating point number (16 significant digits e.g. 1.123...15)
case Float64Array.prototype:
return 'float64array';
break;
default:
return undefined;
break;
}
break;
default:
return undefined;
break;
}
};
const concat = (...args) => {
let str = '';
args.map((arg, index) => str = str.concat(String(arg)));
return str;
};
const concats = (...args) => {
let str = '';
args.map((arg, index) => {
str = index === 0 ? String(arg) : str.concat(' ', String(arg));
});
return str;
};
const stringify = (arg) => JSON.stringify(arg, null, 2);
const fillArray = (withThis, length) => {
var arr = new Array(length);
for (var i = 0; i < length; i++) {
arr[i] = withThis;
}
return arr;
}
let inspect = () => {};
class BooleanSchema {
constructor () {
this.type = 'boolean';
}
}
class StringSchema {
constructor () {
this.type = 'string';
}
}
class IntegerSchema {
constructor () {
this.type = 'integer';
}
}
class DoubleSchema {
constructor () {
this.type = 'double';
}
}
class ObjectSchema {
constructor (contents) {
this.type = 'object';
this.contents = sortObject(contents);
}
}
class ArraySchema {
constructor (schema) {
this.type = 'array';
this.schema = schema;
}
}
class Protosphere {
constructor (schema) {
schema = sortObject(schema);
this.schema = schema;
}
transform (values) {
const schema = this.schema;
return new Promise((resolve, reject) => {
try {
// inspect(schema);
// on transform, we push() to arrays,
// on hydrate, we shift() from arrays
const arrays = [];
const strings = [];
const booleans = [];
const integers = [];
const doubles = [];
const nulls = [];
const undefineds = [];
const nans = [];
const infinitys = [];
let inputs = 0;
const traverse = (schema, values) => {
inspect(schema);
mapKeys((key) => {
inputs++;
let s = schema[key];
let v = values[key];
/*
if (s.type !== classify(v)) {
} */
switch (s.type) {
case 'boolean':
switch (classify(v)) {
case 'boolean':
booleans.push(v);
break;
case 'null':
nulls.push(inputs);
break;
case 'undefined':
undefineds.push(inputs);
break;
default:
throw new TypeError(
concats('Unexpected', classify(v), 'on', s.type, 'field @', key, 'of', stringify(values))
);
break;
}
break;
case 'string':
switch (classify(v)) {
case 'string':
strings.push(v);
break;
case 'null':
nulls.push(inputs);
break;
case 'undefined':
undefineds.push(inputs);
break;
default:
throw new TypeError(
concats('Unexpected', classify(v), 'on', s.type, 'field @', key, 'of', stringify(values))
);
break;
}
break;
case 'integer':
switch (classify(v)) {
case 'integer':
integers.push(v);
break;
case 'null':
nulls.push(inputs);
break;
case 'undefined':
undefineds.push(inputs);
break;
default:
throw new TypeError(
concats('Unexpected', classify(v), 'on', s.type, 'field @', key, 'of', stringify(values))
);
break;
}
break;
case 'double':
switch (classify(v)) {
case 'double':
doubles.push(v);
break;
case 'null':
nulls.push(inputs);
break;
case 'undefined':
undefineds.push(inputs);
break;
default:
throw new TypeError(
concats('Unexpected', classify(v), 'on', s.type, 'field @', key, 'of', stringify(values))
);
break;
}
break;
case 'array':
switch (classify(v)) {
case 'array':
if (s.schema) {
arrays.push(v.length);
traverse(fillArray(s.schema, v.length), v);
}
break;
case 'null':
nulls.push(inputs);
break;
case 'undefined':
undefineds.push(inputs);
break;
default:
throw new TypeError(
concats('Unexpected', classify(v), 'on', s.type, 'field @', key, 'of', stringify(values))
);
break;
}
break;
case 'object':
switch (classify(v)) {
case 'object':
traverse(s.contents, v);
break;
case 'null':
nulls.push(inputs);
break;
case 'undefined':
undefineds.push(inputs);
break;
default:
throw new TypeError(
concats('Unexpected', classify(v), 'on', s.type, 'field @', key, 'of', stringify(values))
);
break;
}
break;
}
})(schema);
};
traverse(schema, values);
let genesis = concat(
arrays.length ? 1 : 0,
booleans.length ? 1 : 0,
integers.length ? 1 : 0,
doubles.length ? 1 : 0,
nulls.length ? 1 : 0,
undefineds.length ? 1 : 0,
nans.length ? 1 : 0,
infinitys.length ? 1 : 0,
strings.length ? 1 : 0,
' ', strings.length
);
let protobuf = new pbf();
let next = 0;
next++;
protobuf.writeStringField(next, genesis)
if (arrays.length) {
next++;
protobuf.writePackedVarint(next, arrays);
};
if (booleans.length) {
next++;
protobuf.writePackedBoolean(next, booleans);
};
if (integers.length) {
next++;
protobuf.writePackedSVarint(next, integers);
};
if (doubles.length) {
next++;
protobuf.writePackedDouble(next, doubles);
};
if (nulls.length) {
next++;
protobuf.writePackedVarint(next, nulls);
};
if (undefineds.length) {
next++;
protobuf.writePackedVarint(next, undefineds);
};
if (nans.length) {
next++;
protobuf.writePackedVarint(next, nans);
};
if (infinitys.length) {
next++;
protobuf.writePackedVarint(next, infinitys);
};
if (strings.length) {
for (var i = 0; i <= strings.length - 1; i++) {
next++
protobuf.writeStringField(next, strings[i]);
}
};
let buffer = protobuf.finish();
resolve(buffer);
} catch (e) {
reject(e);
}
});
}
hydrate (buffer) {
const schema = this.schema;
return new Promise((resolve, reject) => {
try {
if (classify(buffer) !== 'uint8array') {
buffer = new Uint8Array(buffer);
}
let genesis, switches, overhead,
arrays, booleans, integers, doubles,
nulls, undefineds, nans, infinitys,
strings, stringCount, hasStrings;
let handlers = [];
const reader = (tag, data, pbf) => {
if (tag === 1) {
genesis = pbf.readString().split(' ');
switches = genesis[0].split('').map((x) => parseInt(x));
stringCount = parseInt(genesis[1]);
if (stringCount > 0) {
strings = [];
}
overhead = 0;
if (switches[0]) {
overhead++;
handlers.push((pbf) => {
arrays = pbf.readPackedVarint();
});
}
if (switches[1]) {
overhead++;
handlers.push((pbf) => {
booleans = pbf.readPackedBoolean();
});
}
if (switches[2]) {
overhead++;
handlers.push((pbf) => {
integers = pbf.readPackedSVarint();
});
}
if (switches[3]) {
overhead++;
handlers.push((pbf) => {
doubles = pbf.readPackedDouble();
});
}
if (switches[4]) {
overhead++;
handlers.push((pbf) => {
nulls = pbf.readPackedVarint();
});
}
if (switches[5]) {
overhead++;
handlers.push((pbf) => {
undefineds = pbf.readPackedVarint();
});
}
if (switches[6]) {
overhead++;
handlers.push((pbf) => {
nans = pbf.readPackedVarint();
});
}
if (switches[7]) {
overhead++;
infinitys.push((pbf) => {
infinitys = pbf.readPackedVarint();
});
}
hasStrings = switches[8] ? true : false;
} else {
if (tag <= (1 + overhead)) {
var handler = handlers.shift();
handler(pbf);
return;
} else {
if (tag <= (1 + overhead + stringCount)) {
strings.push(pbf.readString());
}
}
}
}
new pbf(buffer).readFields(reader);
let outputs = 0;
const reverse = (schema, object) => {
mapKeys((key) => {
outputs++;
let s = schema[key];
switch (s.type) {
case 'boolean':
if (typeof undefineds !== 'undefined' && undefineds.includes(outputs)) {
object[key] = undefined;
} else if (typeof nulls !== 'undefined' && nulls.includes(outputs)) {
object[key] = null;
} else {
object[key] = booleans.shift();
}
break;
case 'string':
if (typeof undefineds !== 'undefined' && undefineds.includes(outputs)) {
object[key] = undefined;
} else if (typeof nulls !== 'undefined' && nulls.includes(outputs)) {
object[key] = null;
} else {
object[key] = strings.shift();
}
break;
case 'integer':
if (typeof undefineds !== 'undefined' && undefineds.includes(outputs)) {
object[key] = undefined;
} else if (typeof nulls !== 'undefined' && nulls.includes(outputs)) {
object[key] = null;
} else {
object[key] = integers.shift();
}
break;
case 'double':
if (typeof undefineds !== 'undefined' && undefineds.includes(outputs)) {
object[key] = undefined;
} else if (typeof nulls !== 'undefined' && nulls.includes(outputs)) {
object[key] = null;
} else {
object[key] = doubles.shift();
}
break;
case 'array':
if (typeof undefineds !== 'undefined' && undefineds.includes(outputs)) {
object[key] = undefined;
} else if (typeof nulls !== 'undefined' && nulls.includes(outputs)) {
object[key] = null;
} else {
let arrayLength = arrays.shift();
object[key] = new Array(arrayLength);
reverse(fillArray(s.schema, arrayLength), object[key]);
}
break;
case 'object':
if (typeof undefineds !== 'undefined' && undefineds.includes(outputs)) {
object[key] = undefined;
} else if (typeof nulls !== 'undefined' && nulls.includes(outputs)) {
object[key] = null;
} else {
object[key] = {};
reverse(s.contents, object[key]);
}
break;
}
})(schema);
return object;
};
let reversed = reverse(schema, {});
inspect(reversed);
resolve(reversed);
} catch (e) {
reject(e);
}
});
}
static Boolean () {
return new BooleanSchema();
}
static String () {
return new StringSchema();
}
static Integer () {
return new IntegerSchema();
}
static Double () {
return new DoubleSchema();
}
static Object (contents) {
return new ObjectSchema(contents);
}
static Array (schema) {
return new ArraySchema(schema);
}
static enableInspect () {
inspect = (...parameters) => parameters.map((parameter) => {
console.dir(parameter, { depth:null, colors: true })
});
}
}
if (typeof window !== 'undefined') {
window.Protosphere = Protosphere;
} else if (typeof module !== 'undefined') {
module.exports = Protosphere;
}
// (debug)\(.*\;
/*
const debug = (...parameters) => {
if (parameters.length >= 2) {
console.log.apply(null, parameters);
} else {
parameters.map((parameter) => {
console.dir(parameter, {depth:null, colors: true});
});
}
}; */