Comparing version 1.1.1 to 1.2.0
import { Stream } from "stream"; | ||
export default wrap; | ||
export declare function wrap<T extends Stream>(stream: T): T; | ||
export declare function encode<T extends any>(...data: T[]): Buffer | Uint8Array; | ||
export declare function decode<T extends any>(buf: Buffer | Uint8Array): T; | ||
export declare function decode<T extends any>(buf: Buffer | Uint8Array, temp: any[]): IterableIterator<T>; | ||
export declare function decode<T extends any>(buf: Buffer | Uint8Array, temp: any[]): IterableIterator<T>; | ||
export declare class BSP { | ||
constructor(options: { | ||
objectSerializer: (obj: any) => string | Buffer | Uint8Array; | ||
objectDeserializer: (data: string | Buffer | Uint8Array) => any; | ||
/** @default "string" */ | ||
serializationStyle?: "string" | "buffer"; | ||
}); | ||
encode<T extends any>(...data: T[]): Buffer | Uint8Array; | ||
decode<T extends any>(buf: Buffer | Uint8Array): T; | ||
decode<T extends any>(buf: Buffer | Uint8Array, temp: any[]): IterableIterator<T>; | ||
wrap<T extends Stream>(stream: T): T; | ||
} |
271
index.js
@@ -60,73 +60,3 @@ "use strict"; | ||
function encode(...data) { | ||
if (data.length === 0) { | ||
throw new SyntaxError("encode function requires at least one argument"); | ||
} | ||
let buf = TypedArray.from([]); | ||
for (let payload of data) { | ||
let type = NaN; | ||
let _type = typeof payload; | ||
switch (_type) { | ||
case "string": | ||
type = 1; | ||
payload = encodeText(payload); | ||
break; | ||
case "number": | ||
type = 2; | ||
payload = encodeText(payload.toString()); | ||
break; | ||
case "bigint": | ||
type = 3; | ||
payload = encodeText(payload.toString()); | ||
break; | ||
case "boolean": | ||
type = 4; | ||
payload = TypedArray.from([Number(payload)]); | ||
break; | ||
case "object": | ||
case "undefined": | ||
if (null === payload || undefined === payload) { | ||
type = 0; | ||
payload = TypedArray.from([]); | ||
} else if (isBufferLike(payload)) { | ||
type = 6; // raw data | ||
} else { | ||
type = 5; | ||
payload = encodeText(JSON.stringify(payload)); | ||
} | ||
break; | ||
default: | ||
throw new TypeError(`unsupported payload type (${_type})`); | ||
} | ||
let head = [type]; | ||
let len = payload.byteLength; | ||
if (len <= 255) { | ||
head.push(1, len); | ||
} else { | ||
let binLen = len <= 65535 ? 16 : 64; | ||
let bin = sprintf(`%0${binLen}b`, len); | ||
head.push(len <= 65535 ? 2 : 3); | ||
for (let i = 0; i < binLen;) { | ||
head.push(parseInt(bin.slice(i, i += 8), 2)); | ||
} | ||
} | ||
buf = concatBuffers([buf, TypedArray.from(head), payload]); | ||
} | ||
return buf; | ||
} | ||
/** | ||
@@ -208,5 +138,7 @@ * @param {Buffer|Uint8Array} buf | ||
* @param {[number, number, Buffer|Uint8Array]} temp | ||
* @param {Function} deserialize | ||
* @param {"string" | "buffer"} serializationStyle | ||
* @returns {IterableIterator<any>} | ||
*/ | ||
function* decodeSegment(buf, temp) { | ||
function* decodeSegment(buf, temp, deserialize, serializationStyle) { | ||
// put the buffer into the temp | ||
@@ -248,3 +180,6 @@ if (temp.length === 0 || isHeaderTemp(temp)) { | ||
case 5: // object | ||
yield JSON.parse(decodeText(payload)); | ||
if (serializationStyle === "string") | ||
yield deserialize(decodeText(payload)); | ||
else | ||
yield deserialize(payload); | ||
break; | ||
@@ -269,58 +204,162 @@ | ||
/** | ||
* @param {Buffer|Uint8Array} buf | ||
*/ | ||
function decode(buf) { | ||
if (arguments.length === 2 && Array.isArray(arguments[1])) { | ||
return decodeSegment(buf, arguments[1]); | ||
} else { | ||
return decodeSegment(buf, []).next().value; | ||
class BSP { | ||
/** | ||
* @param {{ | ||
objectSerializer: Function, | ||
objectDeserializer: Function, | ||
serializationStyle?: "string" | "buffer" | ||
}} options | ||
*/ | ||
constructor(options) { | ||
this._serialize = options.objectSerializer; | ||
this._deserialize = options.objectDeserializer; | ||
this._serializationStyle = options.serializationStyle || "string"; | ||
} | ||
} | ||
function wrap(stream) { | ||
let _write = stream.write.bind(stream); | ||
let _on = stream.on.bind(stream); | ||
let _once = stream.once.bind(stream); | ||
let _prepend = stream.prependListener.bind(stream); | ||
let _prependOnce = stream.prependOnceListener.bind(stream); | ||
let addListener = (fn, event, listener) => { | ||
if (event === "data") { | ||
let temp = []; | ||
let _listener = (buf) => { | ||
for (let data of decode(buf, temp)) { | ||
listener(data); | ||
encode(...data) { | ||
if (data.length === 0) { | ||
throw new SyntaxError("encode function requires at least one argument"); | ||
} | ||
let buf = TypedArray.from([]); | ||
for (let payload of data) { | ||
let type = NaN; | ||
let _type = typeof payload; | ||
switch (_type) { | ||
case "string": | ||
type = 1; | ||
payload = encodeText(payload); | ||
break; | ||
case "number": | ||
type = 2; | ||
payload = encodeText(payload.toString()); | ||
break; | ||
case "bigint": | ||
type = 3; | ||
payload = encodeText(payload.toString()); | ||
break; | ||
case "boolean": | ||
type = 4; | ||
payload = TypedArray.from([Number(payload)]); | ||
break; | ||
case "object": | ||
case "undefined": | ||
if (null === payload || undefined === payload) { | ||
type = 0; | ||
payload = TypedArray.from([]); | ||
} else if (isBufferLike(payload)) { | ||
type = 6; // raw data | ||
} else { | ||
type = 5; | ||
payload = this._serialize(payload); | ||
if (typeof payload === "string") | ||
payload = encodeText(payload); | ||
} | ||
break; | ||
default: | ||
throw new TypeError(`unsupported payload type (${_type})`); | ||
} | ||
let head = [type]; | ||
let len = payload.byteLength; | ||
if (len <= 255) { | ||
head.push(1, len); | ||
} else { | ||
let binLen = len <= 65535 ? 16 : 64; | ||
let bin = sprintf(`%0${binLen}b`, len); | ||
head.push(len <= 65535 ? 2 : 3); | ||
for (let i = 0; i < binLen;) { | ||
head.push(parseInt(bin.slice(i, i += 8), 2)); | ||
} | ||
}; | ||
return fn("data", _listener); | ||
} | ||
buf = concatBuffers([buf, TypedArray.from(head), payload]); | ||
} | ||
return buf; | ||
} | ||
/** | ||
* @param {Buffer|Uint8Array} buf | ||
*/ | ||
decode(buf) { | ||
if (arguments.length === 2 && Array.isArray(arguments[1])) { | ||
return decodeSegment( | ||
buf, arguments[1], | ||
this._deserialize, | ||
this._serializationStyle | ||
); | ||
} else { | ||
return fn(event, listener); | ||
return decodeSegment( | ||
buf, | ||
[], | ||
this._deserialize, | ||
this._serializationStyle | ||
).next().value; | ||
} | ||
}; | ||
} | ||
stream.write = function write(chunk, encoding, callback) { | ||
return _write(encode(chunk), encoding, callback); | ||
}; | ||
wrap(stream) { | ||
let _write = stream.write.bind(stream); | ||
let _on = stream.on.bind(stream); | ||
let _once = stream.once.bind(stream); | ||
let _prepend = stream.prependListener.bind(stream); | ||
let _prependOnce = stream.prependOnceListener.bind(stream); | ||
let addListener = (fn, event, listener) => { | ||
if (event === "data") { | ||
let temp = []; | ||
let _listener = (buf) => { | ||
for (let data of this.decode(buf, temp)) { | ||
listener(data); | ||
} | ||
}; | ||
return fn("data", _listener); | ||
} else { | ||
return fn(event, listener); | ||
} | ||
}; | ||
stream.on = stream.addListener = function on(event, listener) { | ||
return addListener(_on, event, listener); | ||
}; | ||
stream.write = (chunk, encoding, callback) => { | ||
return _write(this.encode(chunk), encoding, callback); | ||
}; | ||
stream.once = function once(event, listener) { | ||
return addListener(_once, event, listener); | ||
}; | ||
stream.on = stream.addListener = function on(event, listener) { | ||
return addListener(_on, event, listener); | ||
}; | ||
stream.prependListener = function prependListener(event, listener) { | ||
return addListener(_prepend, event, listener); | ||
}; | ||
stream.once = function once(event, listener) { | ||
return addListener(_once, event, listener); | ||
}; | ||
stream.prependOnceListener = function prependOnceListener(event, listener) { | ||
return addListener(_prependOnce, event, listener); | ||
}; | ||
stream.prependListener = function prependListener(event, listener) { | ||
return addListener(_prepend, event, listener); | ||
}; | ||
return stream; | ||
stream.prependOnceListener = function prependOnceListener(event, listener) { | ||
return addListener(_prependOnce, event, listener); | ||
}; | ||
return stream; | ||
} | ||
} | ||
exports.encode = encode; | ||
exports.decode = decode; | ||
exports.wrap = wrap; | ||
const BSPStatic = new BSP({ | ||
objectSerializer: JSON.stringify, | ||
objectDeserializer: JSON.parse | ||
}); | ||
exports.BSP = BSP; | ||
exports.encode = BSPStatic.encode.bind(BSPStatic); | ||
exports.decode = BSPStatic.decode.bind(BSPStatic); | ||
exports.wrap = BSPStatic.wrap.bind(BSPStatic); |
{ | ||
"name": "bsp", | ||
"version": "1.1.1", | ||
"version": "1.2.0", | ||
"description": "Basic Socket Protocol", | ||
@@ -31,2 +31,4 @@ "main": "index.js", | ||
"@types/node": "^10.12.12", | ||
"bson-ext": "^2.0.3", | ||
"fron": "^0.2.2", | ||
"mocha": "^5.2.0" | ||
@@ -37,2 +39,2 @@ }, | ||
} | ||
} | ||
} |
@@ -119,5 +119,7 @@ # Basic Socket Protocol | ||
- `wrap<T extends Stream>(stream: T): T` | ||
- `encode(...data: any[]): Buffer | Uint8Array` | ||
- `decode` | ||
### Static API | ||
- `function wrap<T extends Stream>(stream: T): T` | ||
- `function encode(...data: any[]): Buffer | Uint8Array` | ||
- `function decode()` | ||
- `(buf: Buffer | Uint8Array) => any` | ||
@@ -152,2 +154,62 @@ - `(buf: Buffer | Uint8Array, temp: any[]) => IterableIterator<any>` | ||
argument is not provided, the decode function will only parse and return the | ||
first chunk of the data decoded. | ||
first chunk of the data decoded. | ||
### BSP Class | ||
```ts | ||
declare class BSP { | ||
constructor(options: { | ||
objectSerializer: (obj: any) => string | Uint8Array; | ||
objectDeserializer: (data: string | Uint8Array) => any; | ||
/** @default "string" */ | ||
serializationStyle?: "string" | "buffer"; | ||
}); | ||
encode<T extends any>(...data: T[]): Buffer | Uint8Array; | ||
decode<T extends any>(buf: Buffer | Uint8Array): T; | ||
decode<T extends any>(buf: Buffer | Uint8Array, temp: any[]): IterableIterator<T>; | ||
wrap<T extends Stream>(stream: T): T; | ||
} | ||
``` | ||
This class is used to create a BSP instance using custom object | ||
serializer/deserializer other than JSON. The following example uses BSON in | ||
order to support compound types like RegExp, Date, etc. | ||
```js | ||
const bsp = require("bsp"); | ||
const assert = require("assert"); | ||
const BSON = require("bson-ext"); | ||
let bson = new BSON([ | ||
BSON.Binary, | ||
BSON.Code, | ||
BSON.DBRef, | ||
BSON.Decimal128, | ||
BSON.Double, | ||
BSON.Int32, | ||
BSON.Long, | ||
BSON.Map, | ||
BSON.MaxKey, | ||
BSON.MinKey, | ||
BSON.ObjectId, | ||
BSON.BSONRegExp, | ||
BSON.Symbol, | ||
BSON.Timestamp | ||
]); | ||
let _bsp = new bsp.BSP({ | ||
objectSerializer: bson.serialize.bind(bson), | ||
objectDeserializer: bson.deserialize.bind(bson), | ||
serializationStyle: "buffer" | ||
}); | ||
let data = { | ||
foo: "Hello", | ||
bar: "World", | ||
regexp: /Hello[ ]\S+/, | ||
date: new Date() | ||
}; | ||
let buf = _bsp.encode(data); | ||
let result = _bsp.decode(buf); | ||
assert.deepStrictEqual(result, data); | ||
``` |
66122
349
214
4