} callback Callback invoked with err and {{code: number, info: Object|Array|null, options: {frozen: Boolean}}}
* @internal
* @throws {Error}
* @ignore
*/
this.parseTypeName = async function (keyspace, typeName, startIndex, length, udtResolver) {
startIndex = startIndex || 0;
if (!length) {
length = typeName.length;
}
let innerTypes;
let frozen = false;
if (typeName.indexOf("'", startIndex) === startIndex) {
//If quoted, this is a custom type.
const info = typeName.substr(startIndex+1, length-2);
return {
code: dataTypes.custom,
info: info,
};
}
if (!length) {
length = typeName.length;
}
if (typeName.indexOf(cqlNames.frozen, startIndex) === startIndex) {
//Remove the frozen token
startIndex += cqlNames.frozen.length + 1;
length -= cqlNames.frozen.length + 2;
frozen = true;
}
if (typeName.indexOf(cqlNames.list, startIndex) === startIndex) {
//move cursor across the name and bypass the angle brackets
startIndex += cqlNames.list.length + 1;
length -= cqlNames.list.length + 2;
innerTypes = parseParams(typeName, startIndex, length, '<', '>');
if (innerTypes.length !== 1) {
throw new TypeError('Not a valid type ' + typeName);
}
const info = await this.parseTypeName(keyspace, innerTypes[0], 0, null, udtResolver);
return {
code: dataTypes.list,
info: info,
options: {
frozen: frozen
}
};
}
if (typeName.indexOf(cqlNames.set, startIndex) === startIndex) {
//move cursor across the name and bypass the angle brackets
startIndex += cqlNames.set.length + 1;
length -= cqlNames.set.length + 2;
innerTypes = parseParams(typeName, startIndex, length, '<', '>');
if (innerTypes.length !== 1) {
throw new TypeError('Not a valid type ' + typeName);
}
const info = await this.parseTypeName(keyspace, innerTypes[0], 0, null, udtResolver);
return {
code: dataTypes.set,
info: info,
options: {
frozen: frozen
}
};
}
if (typeName.indexOf(cqlNames.map, startIndex) === startIndex) {
//move cursor across the name and bypass the angle brackets
startIndex += cqlNames.map.length + 1;
length -= cqlNames.map.length + 2;
innerTypes = parseParams(typeName, startIndex, length, '<', '>');
//It should contain the key and value types
if (innerTypes.length !== 2) {
throw new TypeError('Not a valid type ' + typeName);
}
const info = await this._parseChildTypes(keyspace, innerTypes, udtResolver);
return {
code: dataTypes.map,
info: info,
options: {
frozen: frozen
}
};
}
if (typeName.indexOf(cqlNames.tuple, startIndex) === startIndex) {
//move cursor across the name and bypass the angle brackets
startIndex += cqlNames.tuple.length + 1;
length -= cqlNames.tuple.length + 2;
innerTypes = parseParams(typeName, startIndex, length, '<', '>');
if (innerTypes.length < 1) {
throw new TypeError('Not a valid type ' + typeName);
}
const info = await this._parseChildTypes(keyspace, innerTypes, udtResolver);
return {
code: dataTypes.tuple,
info: info,
options: {frozen: frozen}
};
}
if (typeName.indexOf(cqlNames.vector, startIndex) === startIndex) {
// It's a vector, so record the subtype and dimension.
// parseVectorTypeArgs is not an async function but we are. To keep things simple let's ask the
// function to just return whatever it finds for an arg and we'll eval it after the fact
const params = this.parseVectorTypeArgs.bind(this)(typeName, cqlNames.vector, (arg) => arg );
params["info"][0] = await this.parseTypeName(keyspace, params["info"][0]);
return params;
}
const quoted = typeName.indexOf('"', startIndex) === startIndex;
if (quoted) {
// Remove quotes
startIndex++;
length -= 2;
}
// Quick check if its a single type
if (startIndex > 0) {
typeName = typeName.substr(startIndex, length);
}
// Un-escape double quotes if quoted.
if (quoted) {
typeName = typeName.replace('""', '"');
}
const typeCode = dataTypes[typeName];
if (typeof typeCode === 'number') {
return {code : typeCode, info: null};
}
if (typeName === cqlNames.duration) {
return {code : dataTypes.custom, info : customTypeNames.duration};
}
if (typeName === cqlNames.empty) {
// Set as custom
return {code : dataTypes.custom, info : 'empty'};
}
const udtInfo = await udtResolver(keyspace, typeName);
if (udtInfo) {
return {
code: dataTypes.udt,
info: udtInfo,
options: {
frozen: frozen
}
};
}
throw new TypeError('Not a valid type "' + typeName + '"');
};
/**
* @param {String} keyspace
* @param {Array} typeNames
* @param {Function} udtResolver
* @returns {Promise}
* @private
*/
this._parseChildTypes = function (keyspace, typeNames, udtResolver) {
return Promise.all(typeNames.map(name => this.parseTypeName(keyspace, name.trim(), 0, null, udtResolver)));
};
/**
* Parses a Cassandra fully-qualified class name string into data type information
* @param {String} typeName
* @param {Number} [startIndex]
* @param {Number} [length]
* @throws {TypeError}
* @returns {ColumnInfo}
* @internal
* @ignore
*/
this.parseFqTypeName = function (typeName, startIndex, length) {
let frozen = false;
let reversed = false;
startIndex = startIndex || 0;
let params;
if (!length) {
length = typeName.length;
}
if (length > complexTypeNames.reversed.length && typeName.indexOf(complexTypeNames.reversed) === startIndex) {
//Remove the reversed token
startIndex += complexTypeNames.reversed.length + 1;
length -= complexTypeNames.reversed.length + 2;
reversed = true;
}
if (length > complexTypeNames.frozen.length &&
typeName.indexOf(complexTypeNames.frozen, startIndex) === startIndex) {
//Remove the frozen token
startIndex += complexTypeNames.frozen.length + 1;
length -= complexTypeNames.frozen.length + 2;
frozen = true;
}
const options = {
frozen: frozen,
reversed: reversed
};
if (typeName === complexTypeNames.empty) {
//set as custom
return {
code: dataTypes.custom,
info: 'empty',
options: options
};
}
//Quick check if its a single type
if (length <= singleFqTypeNamesLength) {
if (startIndex > 0) {
typeName = typeName.substr(startIndex, length);
}
const typeCode = singleTypeNames[typeName];
if (typeof typeCode === 'number') {
return {code : typeCode, info: null, options : options};
}
// special handling for duration
if (typeName === customTypeNames.duration) {
return {code: dataTypes.duration, options: options};
}
throw new TypeError('Not a valid type "' + typeName + '"');
}
if (typeName.indexOf(complexTypeNames.list, startIndex) === startIndex) {
//Its a list
//org.apache.cassandra.db.marshal.ListType(innerType)
//move cursor across the name and bypass the parenthesis
startIndex += complexTypeNames.list.length + 1;
length -= complexTypeNames.list.length + 2;
params = parseParams(typeName, startIndex, length);
if (params.length !== 1) {
throw new TypeError('Not a valid type ' + typeName);
}
const info = this.parseFqTypeName(params[0]);
return {
code: dataTypes.list,
info: info,
options: options
};
}
if (typeName.indexOf(complexTypeNames.set, startIndex) === startIndex) {
//Its a set
//org.apache.cassandra.db.marshal.SetType(innerType)
//move cursor across the name and bypass the parenthesis
startIndex += complexTypeNames.set.length + 1;
length -= complexTypeNames.set.length + 2;
params = parseParams(typeName, startIndex, length);
if (params.length !== 1)
{
throw new TypeError('Not a valid type ' + typeName);
}
const info = this.parseFqTypeName(params[0]);
return {
code : dataTypes.set,
info : info,
options: options
};
}
if (typeName.indexOf(complexTypeNames.map, startIndex) === startIndex) {
//org.apache.cassandra.db.marshal.MapType(keyType,valueType)
//move cursor across the name and bypass the parenthesis
startIndex += complexTypeNames.map.length + 1;
length -= complexTypeNames.map.length + 2;
params = parseParams(typeName, startIndex, length);
//It should contain the key and value types
if (params.length !== 2) {
throw new TypeError('Not a valid type ' + typeName);
}
const info1 = this.parseFqTypeName(params[0]);
const info2 = this.parseFqTypeName(params[1]);
return {
code : dataTypes.map,
info : [info1, info2],
options: options
};
}
if (typeName.indexOf(complexTypeNames.udt, startIndex) === startIndex) {
//move cursor across the name and bypass the parenthesis
startIndex += complexTypeNames.udt.length + 1;
length -= complexTypeNames.udt.length + 2;
const udtType = this._parseUdtName(typeName, startIndex, length);
udtType.options = options;
return udtType;
}
if (typeName.indexOf(complexTypeNames.tuple, startIndex) === startIndex) {
//move cursor across the name and bypass the parenthesis
startIndex += complexTypeNames.tuple.length + 1;
length -= complexTypeNames.tuple.length + 2;
params = parseParams(typeName, startIndex, length);
if (params.length < 1) {
throw new TypeError('Not a valid type ' + typeName);
}
const info = params.map(x => this.parseFqTypeName(x));
return {
code : dataTypes.tuple,
info : info,
options: options
};
}
if (typeName.indexOf(customTypeNames.vector, startIndex) === startIndex) {
// It's a vector, so record the subtype and dimension.
const params = this.parseVectorTypeArgs.bind(this)(typeName, customTypeNames.vector, this.parseFqTypeName);
params.options = options;
return params;
}
// Assume custom type if cannot be parsed up to this point.
const info = typeName.substr(startIndex, length);
return {
code: dataTypes.custom,
info: info,
options: options
};
};
/**
* Parses type names with composites
* @param {String} typesString
* @returns {{types: Array, isComposite: Boolean, hasCollections: Boolean}}
* @internal
* @ignore
*/
this.parseKeyTypes = function (typesString) {
let i = 0;
let length = typesString.length;
const isComposite = typesString.indexOf(complexTypeNames.composite) === 0;
if (isComposite) {
i = complexTypeNames.composite.length + 1;
length--;
}
const types = [];
let startIndex = i;
let nested = 0;
let inCollectionType = false;
let hasCollections = false;
//as collection types are not allowed, it is safe to split by ,
while (++i < length) {
switch (typesString[i]) {
case ',':
if (nested > 0) {
break;
}
if (inCollectionType) {
//remove type id
startIndex = typesString.indexOf(':', startIndex) + 1;
}
types.push(typesString.substring(startIndex, i));
startIndex = i + 1;
break;
case '(':
if (nested === 0 && typesString.indexOf(complexTypeNames.collection, startIndex) === startIndex) {
inCollectionType = true;
hasCollections = true;
//skip collection type
i++;
startIndex = i;
break;
}
nested++;
break;
case ')':
if (inCollectionType && nested === 0){
types.push(typesString.substring(typesString.indexOf(':', startIndex) + 1, i));
startIndex = i + 1;
break;
}
nested--;
break;
}
}
if (startIndex < length) {
types.push(typesString.substring(startIndex, length));
}
return {
types: types.map(name => this.parseFqTypeName(name)),
hasCollections: hasCollections,
isComposite: isComposite
};
};
/**
*
* @param {string} typeName
* @param {number} startIndex
* @param {number} length
* @returns {UdtColumnInfo}
*/
this._parseUdtName = function (typeName, startIndex, length) {
const udtParams = parseParams(typeName, startIndex, length);
if (udtParams.length < 2) {
//It should contain at least the keyspace, name of the udt and a type
throw new TypeError('Not a valid type ' + typeName);
}
/**
* @type {{keyspace: String, name: String, fields: Array}}
*/
const udtInfo = {
keyspace: udtParams[0],
name: utils.allocBufferFromString(udtParams[1], 'hex').toString(),
fields: []
};
for (let i = 2; i < udtParams.length; i++) {
const p = udtParams[i];
const separatorIndex = p.indexOf(':');
const fieldType = this.parseFqTypeName(p, separatorIndex + 1, p.length - (separatorIndex + 1));
udtInfo.fields.push({
name: utils.allocBufferFromString(p.substr(0, separatorIndex), 'hex').toString(),
type: fieldType
});
}
return {
code : dataTypes.udt,
info : udtInfo
};
};
}
/**
* Sets the encoder and decoder methods for this instance
* @private
*/
function setEncoders() {
this.decoders = {
[dataTypes.custom]: this.decodeCustom,
[dataTypes.ascii]: this.decodeAsciiString,
[dataTypes.bigint]: this.decodeLong,
[dataTypes.blob]: this.decodeBlob,
[dataTypes.boolean]: this.decodeBoolean,
[dataTypes.counter]: this.decodeLong,
[dataTypes.decimal]: this.decodeDecimal,
[dataTypes.double]: this.decodeDouble,
[dataTypes.float]: this.decodeFloat,
[dataTypes.int]: this.decodeInt,
[dataTypes.text]: this.decodeUtf8String,
[dataTypes.timestamp]: this.decodeTimestamp,
[dataTypes.uuid]: this.decodeUuid,
[dataTypes.varchar]: this.decodeUtf8String,
[dataTypes.varint]: this.decodeVarint,
[dataTypes.timeuuid]: this.decodeTimeUuid,
[dataTypes.inet]: this.decodeInet,
[dataTypes.date]: this.decodeDate,
[dataTypes.time]: this.decodeTime,
[dataTypes.smallint]: this.decodeSmallint,
[dataTypes.tinyint]: this.decodeTinyint,
[dataTypes.duration]: decodeDuration,
[dataTypes.list]: this.decodeList,
[dataTypes.map]: this.decodeMap,
[dataTypes.set]: this.decodeSet,
[dataTypes.udt]: this.decodeUdt,
[dataTypes.tuple]: this.decodeTuple
};
this.encoders = {
[dataTypes.custom]: this.encodeCustom,
[dataTypes.ascii]: this.encodeAsciiString,
[dataTypes.bigint]: this.encodeLong,
[dataTypes.blob]: this.encodeBlob,
[dataTypes.boolean]: this.encodeBoolean,
[dataTypes.counter]: this.encodeLong,
[dataTypes.decimal]: this.encodeDecimal,
[dataTypes.double]: this.encodeDouble,
[dataTypes.float]: this.encodeFloat,
[dataTypes.int]: this.encodeInt,
[dataTypes.text]: this.encodeUtf8String,
[dataTypes.timestamp]: this.encodeTimestamp,
[dataTypes.uuid]: this.encodeUuid,
[dataTypes.varchar]: this.encodeUtf8String,
[dataTypes.varint]: this.encodeVarint,
[dataTypes.timeuuid]: this.encodeUuid,
[dataTypes.inet]: this.encodeInet,
[dataTypes.date]: this.encodeDate,
[dataTypes.time]: this.encodeTime,
[dataTypes.smallint]: this.encodeSmallint,
[dataTypes.tinyint]: this.encodeTinyint,
[dataTypes.duration]: encodeDuration,
[dataTypes.list]: this.encodeList,
[dataTypes.map]: this.encodeMap,
[dataTypes.set]: this.encodeSet,
[dataTypes.udt]: this.encodeUdt,
[dataTypes.tuple]: this.encodeTuple
};
}
/**
* Decodes Cassandra bytes into Javascript values.
*
* This is part of an experimental API, this can be changed future releases.
*
* @param {Buffer} buffer Raw buffer to be decoded.
* @param {ColumnInfo} type
*/
Encoder.prototype.decode = function (buffer, type) {
if (buffer === null || (buffer.length === 0 && !zeroLengthTypesSupported.has(type.code))) {
return null;
}
const decoder = this.decoders[type.code];
if (!decoder) {
throw new Error('Unknown data type: ' + type.code);
}
return decoder.call(this, buffer, type);
};
/**
* Encodes Javascript types into Buffer according to the Cassandra protocol.
*
* This is part of an experimental API, this can be changed future releases.
*
* @param {*} value The value to be converted.
* @param {ColumnInfo | Number | String} typeInfo The type information.
* It can be either a:
*
* - A
String
representing the data type.
* - A
Number
with one of the values of {@link module:types~dataTypes dataTypes}.
* - An
Object
containing the type.code
as one of the values of
* {@link module:types~dataTypes dataTypes} and type.info
.
*
*
* @returns {Buffer}
* @throws {TypeError} When there is an encoding error
*/
Encoder.prototype.encode = function (value, typeInfo) {
if (value === undefined) {
value = this.encodingOptions.useUndefinedAsUnset && this.protocolVersion >= 4 ? types.unset : null;
}
if (value === types.unset) {
if (!types.protocolVersion.supportsUnset(this.protocolVersion)) {
throw new TypeError('Unset value can not be used for this version of Cassandra, protocol version: ' +
this.protocolVersion);
}
return value;
}
if (value === null || value instanceof Buffer) {
return value;
}
/** @type {ColumnInfo | null} */
let type = null;
if (typeInfo) {
if (typeof typeInfo === 'number') {
type = {
code: typeInfo
};
}
else if (typeof typeInfo === 'string') {
type = dataTypes.getByName(typeInfo);
}
else if (typeof typeInfo.code === 'number') {
type = typeInfo;
}
if (type == null || typeof type.code !== 'number') {
throw new TypeError('Type information not valid, only String and Number values are valid hints');
}
}
else {
//Lets guess
type = Encoder.guessDataType(value);
if (!type) {
throw new TypeError('Target data type could not be guessed, you should use prepared statements for accurate type mapping. Value: ' + util.inspect(value));
}
}
const encoder = this.encoders[type.code];
if (!encoder) {
throw new Error('Type not supported ' + type.code);
}
return encoder.call(this, value, type);
};
/**
* Try to guess the Cassandra type to be stored, based on the javascript value type
* @param value
* @returns {ColumnInfo | null}
* @ignore
* @internal
*/
Encoder.guessDataType = function (value) {
const esTypeName = (typeof value);
if (esTypeName === 'number') {
return {code : dataTypes.double};
}
else if (esTypeName === 'string') {
if (value.length === 36 && uuidRegex.test(value)){
return {code : dataTypes.uuid};
}
return {code : dataTypes.text};
}
else if (esTypeName === 'boolean') {
return {code : dataTypes.boolean};
}
else if (value instanceof Buffer) {
return {code : dataTypes.blob};
}
else if (value instanceof Date) {
return {code : dataTypes.timestamp};
}
else if (value instanceof Long) {
return {code : dataTypes.bigint};
}
else if (value instanceof Integer) {
return {code : dataTypes.varint};
}
else if (value instanceof BigDecimal) {
return {code : dataTypes.decimal};
}
else if (value instanceof types.Uuid) {
return {code : dataTypes.uuid};
}
else if (value instanceof types.InetAddress) {
return {code : dataTypes.inet};
}
else if (value instanceof types.Tuple) {
return {code : dataTypes.tuple};
}
else if (value instanceof types.LocalDate) {
return {code : dataTypes.date};
}
else if (value instanceof types.LocalTime) {
return {code : dataTypes.time};
}
else if (value instanceof types.Duration) {
return {code : dataTypes.custom,
info : customTypeNames.duration};
}
// Map JS TypedArrays onto vectors
else if (value instanceof types.Vector) {
if (value && value.length > 0) {
if (value instanceof Float32Array) {
return {
code: dataTypes.custom,
customTypeName: 'vector',
info: [ {code: dataTypes.float}, value.length]
};
}
/** @type {ColumnInfo?} */
let subtypeColumnInfo = null;
// try to fetch the subtype from the Vector, or else guess
if (value.subtype) {
try {
subtypeColumnInfo = dataTypes.getByName(value.subtype);
} catch (TypeError) {
// ignore
}
}
if (subtypeColumnInfo == null){
subtypeColumnInfo = this.guessDataType(value[0]);
}
if (subtypeColumnInfo != null) {
return {
code: dataTypes.custom,
customTypeName: 'vector',
info: [subtypeColumnInfo, value.length]
};
}
throw new TypeError("Cannot guess subtype from element " + value[0]);
} else {
throw new TypeError("Cannot guess subtype of empty vector");
}
}
else if (Array.isArray(value)) {
return {code : dataTypes.list};
}
else if (value instanceof Geometry) {
if (value instanceof LineString) {
return {code : dataTypes.custom,
info : customTypeNames.lineString};
} else if (value instanceof Point) {
return {code : dataTypes.custom,
info : customTypeNames.point};
} else if (value instanceof Polygon) {
return {code : dataTypes.custom,
info : customTypeNames.polygon};
}
}
else if (value instanceof DateRange) {
return {code : dataTypes.custom,
info : customTypeNames.dateRange};
}
return null;
};
/**
* Gets a buffer containing with the bytes (BE) representing the collection length for protocol v2 and below
* @param {Buffer|Number} value
* @returns {Buffer}
* @private
*/
function getLengthBufferV2(value) {
if (!value) {
return buffers.int16Zero;
}
const lengthBuffer = utils.allocBufferUnsafe(2);
if (typeof value === 'number') {
lengthBuffer.writeUInt16BE(value, 0);
}
else {
lengthBuffer.writeUInt16BE(value.length, 0);
}
return lengthBuffer;
}
/**
* Gets a buffer containing with the bytes (BE) representing the collection length for protocol v3 and above
* @param {Buffer|Number} value
* @returns {Buffer}
* @private
*/
function getLengthBufferV3(value) {
if (!value) {
return buffers.int32Zero;
}
const lengthBuffer = utils.allocBufferUnsafe(4);
if (typeof value === 'number') {
lengthBuffer.writeInt32BE(value, 0);
}
else {
lengthBuffer.writeInt32BE(value.length, 0);
}
return lengthBuffer;
}
/**
* @param {Buffer} buffer
* @private
*/
function handleBufferCopy(buffer) {
if (buffer === null) {
return null;
}
return utils.copyBuffer(buffer);
}
/**
* @param {Buffer} buffer
* @private
*/
function handleBufferRef(buffer) {
return buffer;
}
/**
* Decodes collection length for protocol v3 and above
* @param bytes
* @param offset
* @returns {Number}
* @private
*/
function decodeCollectionLengthV3(bytes, offset) {
return bytes.readInt32BE(offset);
}
/**
* Decodes collection length for protocol v2 and below
* @param bytes
* @param offset
* @returns {Number}
* @private
*/
function decodeCollectionLengthV2(bytes, offset) {
return bytes.readUInt16BE(offset);
}
function decodeDuration(bytes) {
return types.Duration.fromBuffer(bytes);
}
function encodeDuration(value) {
if (!(value instanceof types.Duration)) {
throw new TypeError('Not a valid duration, expected Duration/Buffer obtained ' + util.inspect(value));
}
return value.toBuffer();
}
/**
* @private
* @param {Buffer} buffer
*/
function decodeLineString(buffer) {
return LineString.fromBuffer(buffer);
}
/**
* @private
* @param {LineString} value
*/
function encodeLineString(value) {
return value.toBuffer();
}
/**
* @private
* @param {Buffer} buffer
*/
function decodePoint(buffer) {
return Point.fromBuffer(buffer);
}
/**
* @private
* @param {LineString} value
*/
function encodePoint(value) {
return value.toBuffer();
}
/**
* @private
* @param {Buffer} buffer
*/
function decodePolygon(buffer) {
return Polygon.fromBuffer(buffer);
}
/**
* @private
* @param {Polygon} value
*/
function encodePolygon(value) {
return value.toBuffer();
}
function decodeDateRange(buffer) {
return DateRange.fromBuffer(buffer);
}
/**
* @private
* @param {DateRange} value
*/
function encodeDateRange(value) {
return value.toBuffer();
}
/**
* @param {String} value
* @param {Number} startIndex
* @param {Number} length
* @param {String} [open]
* @param {String} [close]
* @returns {Array}
* @private
*/
function parseParams(value, startIndex, length, open, close) {
open = open || '(';
close = close || ')';
const types = [];
let paramStart = startIndex;
let level = 0;
for (let i = startIndex; i < startIndex + length; i++) {
const c = value[i];
if (c === open) {
level++;
}
if (c === close) {
level--;
}
if (level === 0 && c === ',') {
types.push(value.substr(paramStart, i - paramStart));
paramStart = i + 1;
}
}
//Add the last one
types.push(value.substr(paramStart, length - (paramStart - startIndex)));
return types;
}
/**
* @param {Array.} parts
* @param {Number} totalLength
* @returns {Buffer}
* @private
*/
function concatRoutingKey(parts, totalLength) {
if (totalLength === 0) {
return null;
}
if (parts.length === 1) {
return parts[0];
}
const routingKey = utils.allocBufferUnsafe(totalLength);
let offset = 0;
for (let i = 0; i < parts.length; i++) {
const item = parts[i];
routingKey.writeUInt16BE(item.length, offset);
offset += 2;
item.copy(routingKey, offset);
offset += item.length;
routingKey[offset] = 0;
offset++;
}
return routingKey;
}
function invertObject(obj) {
const rv = {};
for(const k in obj){
if (obj.hasOwnProperty(k)) {
rv[obj[k]] = k;
}
}
return rv;
}
Encoder.isTypedArray = function(arg) {
// The TypedArray superclass isn't available directly so to detect an instance of a TypedArray
// subclass we have to access the prototype of a concrete instance. There's nothing magical about
// Uint8Array here; we could just as easily use any of the other TypedArray subclasses.
return (arg instanceof Object.getPrototypeOf(Uint8Array));
};
module.exports = Encoder;