http://git-wip-us.apache.org/repos/asf/ignite/blob/c56d16fb/modules/platforms/nodejs/lib/internal/BinaryUtils.js
----------------------------------------------------------------------
diff --git a/modules/platforms/nodejs/lib/internal/BinaryUtils.js 
b/modules/platforms/nodejs/lib/internal/BinaryUtils.js
new file mode 100644
index 0000000..ab2d40c
--- /dev/null
+++ b/modules/platforms/nodejs/lib/internal/BinaryUtils.js
@@ -0,0 +1,598 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+const Decimal = require('decimal.js');
+const ObjectType = require('../ObjectType').ObjectType;
+const CompositeType = require('../ObjectType').CompositeType;
+const MapObjectType = require('../ObjectType').MapObjectType;
+const CollectionObjectType = require('../ObjectType').CollectionObjectType;
+const ComplexObjectType = require('../ObjectType').ComplexObjectType;
+const ObjectArrayType = require('../ObjectType').ObjectArrayType;
+const Timestamp = require('../Timestamp');
+const EnumItem = require('../EnumItem');
+const Errors = require('../Errors');
+const ArgumentChecker = require('./ArgumentChecker');
+
+// Operation codes
+const OPERATION = Object.freeze({
+    // Key-Value Queries
+    CACHE_GET : 1000,
+    CACHE_PUT : 1001,
+    CACHE_PUT_IF_ABSENT : 1002,
+    CACHE_GET_ALL : 1003,
+    CACHE_PUT_ALL : 1004,
+    CACHE_GET_AND_PUT : 1005,
+    CACHE_GET_AND_REPLACE : 1006,
+    CACHE_GET_AND_REMOVE : 1007,
+    CACHE_GET_AND_PUT_IF_ABSENT : 1008,
+    CACHE_REPLACE : 1009,
+    CACHE_REPLACE_IF_EQUALS : 1010,
+    CACHE_CONTAINS_KEY : 1011,
+    CACHE_CONTAINS_KEYS : 1012,
+    CACHE_CLEAR : 1013,
+    CACHE_CLEAR_KEY : 1014,
+    CACHE_CLEAR_KEYS : 1015,
+    CACHE_REMOVE_KEY : 1016,
+    CACHE_REMOVE_IF_EQUALS : 1017,
+    CACHE_REMOVE_KEYS : 1018,
+    CACHE_REMOVE_ALL : 1019,
+    CACHE_GET_SIZE : 1020,
+    // Cache Configuration
+    CACHE_GET_NAMES : 1050,
+    CACHE_CREATE_WITH_NAME : 1051,
+    CACHE_GET_OR_CREATE_WITH_NAME : 1052,
+    CACHE_CREATE_WITH_CONFIGURATION : 1053,
+    CACHE_GET_OR_CREATE_WITH_CONFIGURATION : 1054,
+    CACHE_GET_CONFIGURATION : 1055,
+    CACHE_DESTROY : 1056,
+    // SQL and Scan Queries
+    QUERY_SCAN : 2000,
+    QUERY_SCAN_CURSOR_GET_PAGE : 2001,
+    QUERY_SQL : 2002,
+    QUERY_SQL_CURSOR_GET_PAGE : 2003,
+    QUERY_SQL_FIELDS : 2004,
+    QUERY_SQL_FIELDS_CURSOR_GET_PAGE : 2005,
+    RESOURCE_CLOSE : 0,
+    // Binary Types
+    GET_BINARY_TYPE : 3002,
+    PUT_BINARY_TYPE : 3003
+});
+
+const TYPE_CODE = Object.assign({
+        BINARY_OBJECT : 27,
+        BINARY_ENUM : 38
+    },
+    ObjectType.PRIMITIVE_TYPE,
+    ObjectType.COMPOSITE_TYPE);
+
+
+const TYPE_INFO = Object.freeze({
+    [TYPE_CODE.BYTE] : {
+        NAME : 'byte',
+        SIZE : 1
+    },
+    [TYPE_CODE.SHORT] : {
+        NAME : 'short',
+        SIZE : 2
+    },
+    [TYPE_CODE.INTEGER] : {
+        NAME : 'integer',
+        SIZE : 4
+    },
+    [TYPE_CODE.LONG] : {
+        NAME : 'long',
+        SIZE : 8
+    },
+    [TYPE_CODE.FLOAT] : {
+        NAME : 'float',
+        SIZE : 4
+    },
+    [TYPE_CODE.DOUBLE] : {
+        NAME : 'double',
+        SIZE : 8
+    },
+    [TYPE_CODE.CHAR] : {
+        NAME : 'char',
+        SIZE : 2
+    },
+    [TYPE_CODE.BOOLEAN] : {
+        NAME : 'boolean',
+        SIZE : 1
+    },
+    [TYPE_CODE.STRING] : {
+        NAME : 'string',
+        NULLABLE : true
+    },
+    [TYPE_CODE.UUID] : {
+        NAME : 'UUID',
+        SIZE : 16,
+        NULLABLE : true
+    },
+    [TYPE_CODE.DATE] : {
+        NAME : 'date',
+        SIZE : 8,
+        NULLABLE : true
+    },
+    [TYPE_CODE.BYTE_ARRAY] : {
+        NAME : 'byte array',
+        ELEMENT_TYPE : TYPE_CODE.BYTE,
+        NULLABLE : true
+    },
+    [TYPE_CODE.SHORT_ARRAY] : {
+        NAME : 'short array',
+        ELEMENT_TYPE : TYPE_CODE.SHORT,
+        NULLABLE : true
+    },
+    [TYPE_CODE.INTEGER_ARRAY] : {
+        NAME : 'integer array',
+        ELEMENT_TYPE : TYPE_CODE.INTEGER,
+        NULLABLE : true
+    },
+    [TYPE_CODE.LONG_ARRAY] : {
+        NAME : 'long array',
+        ELEMENT_TYPE : TYPE_CODE.LONG,
+        NULLABLE : true
+    },
+    [TYPE_CODE.FLOAT_ARRAY] : {
+        NAME : 'float array',
+        ELEMENT_TYPE : TYPE_CODE.FLOAT,
+        NULLABLE : true
+    },
+    [TYPE_CODE.DOUBLE_ARRAY] : {
+        NAME : 'double array',
+        ELEMENT_TYPE : TYPE_CODE.DOUBLE,
+        NULLABLE : true
+    },
+    [TYPE_CODE.CHAR_ARRAY] :  {
+        NAME : 'char array',
+        ELEMENT_TYPE : TYPE_CODE.CHAR,
+        NULLABLE : true
+    },
+    [TYPE_CODE.BOOLEAN_ARRAY] :  {
+        NAME : 'boolean array',
+        ELEMENT_TYPE : TYPE_CODE.BOOLEAN,
+        NULLABLE : true
+    },
+    [TYPE_CODE.STRING_ARRAY] :  {
+        NAME : 'string array',
+        ELEMENT_TYPE : TYPE_CODE.STRING,
+        KEEP_ELEMENT_TYPE : true,
+        NULLABLE : true
+    },
+    [TYPE_CODE.DATE_ARRAY] :  {
+        NAME : 'date array',
+        ELEMENT_TYPE : TYPE_CODE.DATE,
+        KEEP_ELEMENT_TYPE : true,
+        NULLABLE : true
+    },
+    [TYPE_CODE.UUID_ARRAY] :  {
+        NAME : 'UUID array',
+        ELEMENT_TYPE : TYPE_CODE.UUID,
+        KEEP_ELEMENT_TYPE : true,
+        NULLABLE : true
+    },
+    [TYPE_CODE.OBJECT_ARRAY] : {
+        NAME : 'object array',
+        ELEMENT_TYPE : TYPE_CODE.COMPLEX_OBJECT,
+        KEEP_ELEMENT_TYPE : true,
+        NULLABLE : true
+    },
+    [TYPE_CODE.COLLECTION] : {
+        NAME : 'collection',
+        NULLABLE : true
+    },
+    [TYPE_CODE.MAP] : {
+        NAME : 'map',
+        NULLABLE : true
+    },
+    [TYPE_CODE.BINARY_OBJECT] : {
+        NAME : 'BinaryObject',
+        NULLABLE : true
+    },
+    [TYPE_CODE.ENUM] : {
+        NAME : 'enum',
+        NULLABLE : true
+    },
+    [TYPE_CODE.ENUM_ARRAY] :  {
+        NAME : 'enum array',
+        ELEMENT_TYPE : TYPE_CODE.ENUM,
+        KEEP_ELEMENT_TYPE : true,
+        NULLABLE : true
+    },
+    [TYPE_CODE.DECIMAL] : {
+        NAME : 'decimal',
+        NULLABLE : true
+    },
+    [TYPE_CODE.DECIMAL_ARRAY] :  {
+        NAME : 'decimal array',
+        ELEMENT_TYPE : TYPE_CODE.DECIMAL,
+        KEEP_ELEMENT_TYPE : true,
+        NULLABLE : true
+    },
+    [TYPE_CODE.TIMESTAMP] : {
+        NAME : 'timestamp',
+        NULLABLE : true
+    },
+    [TYPE_CODE.TIMESTAMP_ARRAY] :  {
+        NAME : 'timestamp array',
+        ELEMENT_TYPE : TYPE_CODE.TIMESTAMP,
+        KEEP_ELEMENT_TYPE : true,
+        NULLABLE : true
+    },
+    [TYPE_CODE.TIME] : {
+        NAME : 'time',
+        NULLABLE : true
+    },
+    [TYPE_CODE.TIME_ARRAY] :  {
+        NAME : 'time array',
+        ELEMENT_TYPE : TYPE_CODE.TIME,
+        KEEP_ELEMENT_TYPE : true,
+        NULLABLE : true
+    },
+    [TYPE_CODE.NULL] : {
+        NAME : 'null',
+        NULLABLE : true
+    },
+    [TYPE_CODE.COMPLEX_OBJECT] : {
+        NAME : 'complex object',
+        NULLABLE : true
+    }
+});
+
+const UTF8_ENCODING = 'utf8';
+
+class BinaryUtils {
+    static get OPERATION() {
+        return OPERATION;
+    }
+
+    static get TYPE_CODE() {
+        return TYPE_CODE;
+    }
+
+    static get TYPE_INFO() {
+        return TYPE_INFO;
+    }
+
+    static getSize(typeCode) {
+        const size = TYPE_INFO[typeCode].SIZE;
+        return size ? size : 0;
+    }
+
+    static get ENCODING() {
+        return UTF8_ENCODING;
+    }
+
+    static getTypeName(type) {
+        if (typeof type === 'string') {
+            return type;
+        }
+        const typeCode = BinaryUtils.getTypeCode(type);
+        return TYPE_INFO[typeCode] ? TYPE_INFO[typeCode].NAME : 'type code ' + 
typeCode;
+    }
+
+    static isNullable(type) {
+        return TYPE_INFO[BinaryUtils.getTypeCode(type)].NULLABLE === true;
+    }
+
+    static getTypeCode(type) {
+        return type instanceof CompositeType ? type._typeCode : type;
+    }
+
+    static checkObjectType(type, argName) {
+        if (type === null || type instanceof CompositeType) {
+            return;
+        }
+        ArgumentChecker.hasValueFrom(type, argName, false, 
ObjectType.PRIMITIVE_TYPE);
+    }
+
+    static calcObjectType(object) {
+        const BinaryObject = require('../BinaryObject');
+        const objectType = typeof object;
+        if (object === null) {
+            return BinaryUtils.TYPE_CODE.NULL;
+        }
+        else if (objectType === 'number') {
+            return BinaryUtils.TYPE_CODE.DOUBLE;
+        }
+        else if (objectType === 'string') {
+            return BinaryUtils.TYPE_CODE.STRING;
+        }
+        else if (objectType === 'boolean') {
+            return BinaryUtils.TYPE_CODE.BOOLEAN;
+        }
+        else if (object instanceof Timestamp) {
+            return BinaryUtils.TYPE_CODE.TIMESTAMP;
+        }
+        else if (object instanceof Date) {
+            return BinaryUtils.TYPE_CODE.DATE;
+        }
+        else if (object instanceof EnumItem) {
+            return BinaryUtils.TYPE_CODE.ENUM;
+        }
+        else if (object instanceof Decimal) {
+            return BinaryUtils.TYPE_CODE.DECIMAL;
+        }
+        else if (object instanceof Array) {
+            if (object.length > 0 && object[0] !== null) {
+                return 
BinaryUtils.getArrayType(BinaryUtils.calcObjectType(object[0]));
+            }
+        }
+        else if (object instanceof Set) {
+            return new 
CollectionObjectType(CollectionObjectType.COLLECTION_SUBTYPE.HASH_SET);
+        }
+        else if (object instanceof Map) {
+            return new MapObjectType();
+        }
+        else if (object instanceof BinaryObject) {
+            return BinaryUtils.TYPE_CODE.BINARY_OBJECT;
+        }
+        else if (objectType === 'object') {
+            return new ComplexObjectType(object);
+        }
+        throw Errors.IgniteClientError.unsupportedTypeError(objectType);
+    }
+
+    static checkCompatibility(value, type) {
+        if (!type) {
+            return;
+        }
+        const typeCode = BinaryUtils.getTypeCode(type);
+        if (value === null) {
+            if (!BinaryUtils.isNullable(typeCode)) {
+                throw 
Errors.IgniteClientError.typeCastError(BinaryUtils.TYPE_CODE.NULL, typeCode);
+            }
+            return;
+        }
+        else if (BinaryUtils.isStandardType(typeCode)) {
+            BinaryUtils.checkStandardTypeCompatibility(value, typeCode, type);
+            return;
+        }
+        const valueTypeCode = 
BinaryUtils.getTypeCode(BinaryUtils.calcObjectType(value));
+        if (typeCode !== valueTypeCode) {
+            throw Errors.IgniteClientError.typeCastError(valueTypeCode, 
typeCode);
+        }
+    }
+
+    static isStandardType(typeCode) {
+        return typeCode !== BinaryUtils.TYPE_CODE.BINARY_OBJECT &&
+            typeCode !== BinaryUtils.TYPE_CODE.COMPLEX_OBJECT;
+    }
+
+    static checkStandardTypeCompatibility(value, typeCode, type = null) {
+        const valueType = typeof value;
+        switch (typeCode) {
+            case BinaryUtils.TYPE_CODE.BYTE:
+            case BinaryUtils.TYPE_CODE.SHORT:
+            case BinaryUtils.TYPE_CODE.INTEGER:
+            case BinaryUtils.TYPE_CODE.LONG:
+                if (!Number.isInteger(value)) {
+                    throw Errors.IgniteClientError.valueCastError(value, 
typeCode);
+                }
+                return;
+            case BinaryUtils.TYPE_CODE.FLOAT:
+            case BinaryUtils.TYPE_CODE.DOUBLE:
+                if (valueType !== 'number') {
+                    throw Errors.IgniteClientError.valueCastError(value, 
typeCode);
+                }
+                return;
+            case BinaryUtils.TYPE_CODE.CHAR:
+                if (valueType !== 'string' || value.length !== 1) {
+                    throw Errors.IgniteClientError.valueCastError(value, 
typeCode);
+                }
+                return;
+            case BinaryUtils.TYPE_CODE.BOOLEAN:
+                if (valueType !== 'boolean') {
+                    throw Errors.IgniteClientError.valueCastError(value, 
typeCode);
+                }
+                return;
+            case BinaryUtils.TYPE_CODE.STRING:
+                if (valueType !== 'string') {
+                    throw Errors.IgniteClientError.valueCastError(value, 
typeCode);
+                }
+                return;
+            case BinaryUtils.TYPE_CODE.UUID:
+                if (!value instanceof Array ||
+                    value.length !== 
BinaryUtils.getSize(BinaryUtils.TYPE_CODE.UUID)) {
+                    throw Errors.IgniteClientError.valueCastError(value, 
typeCode);
+                }
+                value.forEach(element =>
+                    BinaryUtils.checkStandardTypeCompatibility(element, 
BinaryUtils.TYPE_CODE.BYTE));
+                return;
+            case BinaryUtils.TYPE_CODE.DATE:
+                if (!value instanceof Date) {
+                    throw Errors.IgniteClientError.valueCastError(value, 
typeCode);
+                }
+                return;
+            case BinaryUtils.TYPE_CODE.ENUM:
+                if (!value instanceof EnumItem) {
+                    throw Errors.IgniteClientError.valueCastError(value, 
typeCode);
+                }
+                return;
+            case BinaryUtils.TYPE_CODE.DECIMAL:
+                if (!value instanceof Decimal) {
+                    throw Errors.IgniteClientError.valueCastError(value, 
typeCode);
+                }
+                return;
+            case BinaryUtils.TYPE_CODE.TIMESTAMP:
+                if (!value instanceof Timestamp) {
+                    throw Errors.IgniteClientError.valueCastError(value, 
typeCode);
+                }
+                return;
+            case BinaryUtils.TYPE_CODE.TIME:
+                if (!value instanceof Date) {
+                    throw Errors.IgniteClientError.valueCastError(value, 
typeCode);
+                }
+                return;
+            case BinaryUtils.TYPE_CODE.BYTE_ARRAY:
+            case BinaryUtils.TYPE_CODE.SHORT_ARRAY:
+            case BinaryUtils.TYPE_CODE.INTEGER_ARRAY:
+            case BinaryUtils.TYPE_CODE.LONG_ARRAY:
+            case BinaryUtils.TYPE_CODE.FLOAT_ARRAY:
+            case BinaryUtils.TYPE_CODE.DOUBLE_ARRAY:
+            case BinaryUtils.TYPE_CODE.CHAR_ARRAY:
+            case BinaryUtils.TYPE_CODE.BOOLEAN_ARRAY:
+            case BinaryUtils.TYPE_CODE.STRING_ARRAY:
+            case BinaryUtils.TYPE_CODE.UUID_ARRAY:
+            case BinaryUtils.TYPE_CODE.DATE_ARRAY:
+            case BinaryUtils.TYPE_CODE.OBJECT_ARRAY:
+            case BinaryUtils.TYPE_CODE.ENUM_ARRAY:
+            case BinaryUtils.TYPE_CODE.DECIMAL_ARRAY:
+            case BinaryUtils.TYPE_CODE.TIMESTAMP_ARRAY:
+            case BinaryUtils.TYPE_CODE.TIME_ARRAY:
+                if (!value instanceof Array) {
+                    throw Errors.IgniteClientError.typeCastError(valueType, 
typeCode);
+                }
+                return;
+            case BinaryUtils.TYPE_CODE.MAP:
+                if (!value instanceof Map) {
+                    throw Errors.IgniteClientError.typeCastError(valueType, 
typeCode);
+                }
+                return;
+            case BinaryUtils.TYPE_CODE.COLLECTION:
+                if (!(type && type._isSet() && value instanceof Set || value 
instanceof Array)) {
+                    throw Errors.IgniteClientError.typeCastError(valueType, 
type && type._isSet() ? 'set' : typeCode);
+                }
+                return;
+            case BinaryUtils.TYPE_CODE.NULL:
+                if (value !== null) {
+                    throw Errors.IgniteClientError.typeCastError('not null', 
typeCode);
+                }
+                return;
+            default:
+                const valueTypeCode = 
BinaryUtils.getTypeCode(BinaryUtils.calcObjectType(value));
+                if (valueTypeCode === BinaryUtils.TYPE_CODE.BINARY_OBJECT) {
+                    throw 
Errors.IgniteClientError.typeCastError(valueTypeCode, typeCode);
+                }
+                return;
+        }
+    }
+
+    static checkTypesComatibility(expectedType, actualTypeCode) {
+        if (expectedType === null) {
+            return;
+        }
+        const expectedTypeCode = BinaryUtils.getTypeCode(expectedType);
+        if (actualTypeCode === BinaryUtils.TYPE_CODE.NULL) {
+            return;
+        }
+        else if (expectedTypeCode === BinaryUtils.TYPE_CODE.BINARY_OBJECT ||
+            actualTypeCode === BinaryUtils.TYPE_CODE.BINARY_OBJECT &&
+            expectedTypeCode === BinaryUtils.TYPE_CODE.COMPLEX_OBJECT) {
+            return;
+        }
+        else if (actualTypeCode !== expectedTypeCode) {
+            throw Errors.IgniteClientError.typeCastError(actualTypeCode, 
expectedTypeCode);
+        }
+    }
+
+    static getArrayElementType(arrayType) {
+        if (arrayType instanceof ObjectArrayType) {
+            return arrayType._elementType;
+        }
+        else if (arrayType === BinaryUtils.TYPE_CODE.OBJECT_ARRAY) {
+            return null;
+        }
+        const elementTypeCode = TYPE_INFO[arrayType].ELEMENT_TYPE;
+        if (!elementTypeCode) {
+            throw Errors.IgniteClientError.internalError();
+        }
+        return elementTypeCode;
+    }
+
+    static getArrayType(elementType) {
+        switch (BinaryUtils.getTypeCode(elementType)) {
+            case BinaryUtils.TYPE_CODE.BYTE:
+                return BinaryUtils.TYPE_CODE.BYTE_ARRAY;
+            case BinaryUtils.TYPE_CODE.SHORT:
+                return BinaryUtils.TYPE_CODE.SHORT_ARRAY;
+            case BinaryUtils.TYPE_CODE.INTEGER:
+                return BinaryUtils.TYPE_CODE.INTEGER_ARRAY;
+            case BinaryUtils.TYPE_CODE.LONG:
+                return BinaryUtils.TYPE_CODE.LONG_ARRAY;
+            case BinaryUtils.TYPE_CODE.FLOAT:
+                return BinaryUtils.TYPE_CODE.FLOAT_ARRAY;
+            case BinaryUtils.TYPE_CODE.DOUBLE:
+                return BinaryUtils.TYPE_CODE.DOUBLE_ARRAY;
+            case BinaryUtils.TYPE_CODE.CHAR:
+                return BinaryUtils.TYPE_CODE.CHAR_ARRAY;
+            case BinaryUtils.TYPE_CODE.BOOLEAN:
+                return BinaryUtils.TYPE_CODE.BOOLEAN_ARRAY;
+            case BinaryUtils.TYPE_CODE.STRING:
+                return BinaryUtils.TYPE_CODE.STRING_ARRAY;
+            case BinaryUtils.TYPE_CODE.UUID:
+                return BinaryUtils.TYPE_CODE.UUID_ARRAY;
+            case BinaryUtils.TYPE_CODE.DATE:
+                return BinaryUtils.TYPE_CODE.DATE_ARRAY;
+            case BinaryUtils.TYPE_CODE.ENUM:
+                return BinaryUtils.TYPE_CODE.ENUM_ARRAY;
+            case BinaryUtils.TYPE_CODE.DECIMAL:
+                return BinaryUtils.TYPE_CODE.DECIMAL_ARRAY;
+            case BinaryUtils.TYPE_CODE.TIMESTAMP:
+                return BinaryUtils.TYPE_CODE.TIMESTAMP_ARRAY;
+            case BinaryUtils.TYPE_CODE.TIME:
+                return BinaryUtils.TYPE_CODE.TIME_ARRAY;
+            case BinaryUtils.TYPE_CODE.BINARY_OBJECT:
+                return new ObjectArrayType();
+            default:
+                return new ObjectArrayType(elementType);
+        }
+    }
+
+    static keepArrayElementType(arrayTypeCode) {
+        return TYPE_INFO[arrayTypeCode].KEEP_ELEMENT_TYPE === true;
+    }
+
+    static getJsObjectFieldNames(jsObject) {
+        var fields = new Array();
+        for (let field in jsObject) {
+            if (typeof jsObject[field] !== 'function') {
+                fields.push(field);
+            }
+        }
+        return fields;
+    }
+
+    static hashCode(str) {
+        let hash = 0, char;
+        if (str && str.length > 0) {
+            for (let i = 0; i < str.length; i++) {
+                char = str.charCodeAt(i);
+                hash = ((hash << 5) - hash) + char;
+                hash |= 0; // Convert to 32bit integer
+            }
+        }
+        return hash;
+    }
+
+    static hashCodeLowerCase(str) {
+        return BinaryUtils.hashCode(str ? str.toLowerCase() : str);
+    }
+
+    static contentHashCode(buffer, startPos, endPos) {
+        let hash = 1;
+        for (let i = startPos; i <= endPos; i++) {
+            hash = 31 * hash + buffer._buffer[i];
+            hash |= 0; // Convert to 32bit integer
+        }
+        return hash;
+    }
+}
+
+module.exports = BinaryUtils;

http://git-wip-us.apache.org/repos/asf/ignite/blob/c56d16fb/modules/platforms/nodejs/lib/internal/BinaryWriter.js
----------------------------------------------------------------------
diff --git a/modules/platforms/nodejs/lib/internal/BinaryWriter.js 
b/modules/platforms/nodejs/lib/internal/BinaryWriter.js
new file mode 100644
index 0000000..3686bb4
--- /dev/null
+++ b/modules/platforms/nodejs/lib/internal/BinaryWriter.js
@@ -0,0 +1,210 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+const Decimal = require('decimal.js');
+const Errors = require('../Errors');
+const ComplexObjectType = require('../ObjectType').ComplexObjectType;
+const BinaryUtils = require('./BinaryUtils');
+
+class BinaryWriter {
+
+    static async writeString(buffer, value) {
+        await BinaryWriter.writeObject(buffer, value, 
BinaryUtils.TYPE_CODE.STRING);
+    }
+
+    static async writeObject(buffer, object, objectType = null, 
writeObjectType = true) {
+        BinaryUtils.checkCompatibility(object, objectType);
+        if (object === null) {
+            buffer.writeByte(BinaryUtils.TYPE_CODE.NULL);
+            return;
+        }
+
+        objectType =  objectType ? objectType : 
BinaryUtils.calcObjectType(object);
+        const objectTypeCode = BinaryUtils.getTypeCode(objectType);
+
+        if (writeObjectType) {
+            buffer.writeByte(objectTypeCode);
+        }
+        switch (objectTypeCode) {
+            case BinaryUtils.TYPE_CODE.BYTE:
+            case BinaryUtils.TYPE_CODE.SHORT:
+            case BinaryUtils.TYPE_CODE.INTEGER:
+            case BinaryUtils.TYPE_CODE.FLOAT:
+            case BinaryUtils.TYPE_CODE.DOUBLE:
+                buffer.writeNumber(object, objectTypeCode);
+                break;
+            case BinaryUtils.TYPE_CODE.LONG:
+                buffer.writeLong(object);
+                break;
+            case BinaryUtils.TYPE_CODE.CHAR:
+                buffer.writeChar(object);
+                break;
+            case BinaryUtils.TYPE_CODE.BOOLEAN:
+                buffer.writeBoolean(object);
+                break;
+            case BinaryUtils.TYPE_CODE.STRING:
+                buffer.writeString(object);
+                break;
+            case BinaryUtils.TYPE_CODE.UUID:
+                BinaryWriter._writeUUID(buffer, object);
+                break;
+            case BinaryUtils.TYPE_CODE.DATE:
+                buffer.writeDate(object);
+                break;
+            case BinaryUtils.TYPE_CODE.ENUM:
+                await BinaryWriter._writeEnum(buffer, object);
+                break;
+            case BinaryUtils.TYPE_CODE.DECIMAL:
+                BinaryWriter._writeDecimal(buffer, object);
+                break;
+            case BinaryUtils.TYPE_CODE.TIMESTAMP:
+                BinaryWriter._writeTimestamp(buffer, object);
+                break;
+            case BinaryUtils.TYPE_CODE.TIME:
+                BinaryWriter._writeTime(buffer, object);
+                break;
+            case BinaryUtils.TYPE_CODE.BYTE_ARRAY:
+            case BinaryUtils.TYPE_CODE.SHORT_ARRAY:
+            case BinaryUtils.TYPE_CODE.INTEGER_ARRAY:
+            case BinaryUtils.TYPE_CODE.LONG_ARRAY:
+            case BinaryUtils.TYPE_CODE.FLOAT_ARRAY:
+            case BinaryUtils.TYPE_CODE.DOUBLE_ARRAY:
+            case BinaryUtils.TYPE_CODE.CHAR_ARRAY:
+            case BinaryUtils.TYPE_CODE.BOOLEAN_ARRAY:
+            case BinaryUtils.TYPE_CODE.STRING_ARRAY:
+            case BinaryUtils.TYPE_CODE.UUID_ARRAY:
+            case BinaryUtils.TYPE_CODE.DATE_ARRAY:
+            case BinaryUtils.TYPE_CODE.OBJECT_ARRAY:
+            case BinaryUtils.TYPE_CODE.ENUM_ARRAY:
+            case BinaryUtils.TYPE_CODE.DECIMAL_ARRAY:
+            case BinaryUtils.TYPE_CODE.TIMESTAMP_ARRAY:
+            case BinaryUtils.TYPE_CODE.TIME_ARRAY:
+                await BinaryWriter._writeArray(buffer, object, objectType, 
objectTypeCode);
+                break;
+            case BinaryUtils.TYPE_CODE.COLLECTION:
+                await BinaryWriter._writeCollection(buffer, object, 
objectType);
+                break;
+            case BinaryUtils.TYPE_CODE.MAP:
+                await BinaryWriter._writeMap(buffer, object, objectType);
+                break;
+            case BinaryUtils.TYPE_CODE.BINARY_OBJECT:
+                await BinaryWriter._writeBinaryObject(buffer, object, 
objectType);
+                break;
+            case BinaryUtils.TYPE_CODE.COMPLEX_OBJECT:
+                await BinaryWriter._writeComplexObject(buffer, object, 
objectType);
+                break;
+            default:
+                throw 
Errors.IgniteClientError.unsupportedTypeError(objectType);
+        }
+    }
+
+    static _writeUUID(buffer, value) {
+        buffer.writeBuffer(Buffer.from(value));
+    }
+
+    static async _writeEnum(buffer, enumValue) {
+        await enumValue._write(buffer);
+    }
+
+    static _writeDecimal(buffer, decimal) {
+        let strValue = decimal.toExponential();
+        let expIndex = strValue.indexOf('e');
+        if (expIndex < 0) {
+            expIndex = strValue.indexOf('E');
+        }
+        let scale = 0;
+        if (expIndex >= 0) {
+            scale = parseInt(strValue.substring(expIndex + 1));
+            strValue = strValue.substring(0, expIndex);
+        }
+        const isNegative = strValue.startsWith('-');
+        if (isNegative) {
+            strValue = strValue.substring(1);
+        }
+        const dotIndex = strValue.indexOf('.');
+        if (dotIndex >= 0) {
+            scale -= strValue.length - dotIndex - 1;
+            strValue = strValue.substring(0, dotIndex) + 
strValue.substring(dotIndex + 1);
+        }
+        scale = -scale;
+        let hexValue = new Decimal(strValue).toHexadecimal().substring(2);
+        hexValue = ((hexValue.length % 2 !== 0) ? '000' : '00') + hexValue;
+        const valueBuffer = Buffer.from(hexValue, 'hex');
+        if (isNegative) {
+            valueBuffer[0] |= 0x80;
+        }
+        buffer.writeInteger(scale);
+        buffer.writeInteger(valueBuffer.length);
+        buffer.writeBuffer(valueBuffer);
+    }
+
+    static _writeTimestamp(buffer, timestamp) {
+        buffer.writeDate(timestamp);
+        buffer.writeInteger(timestamp.getNanos());
+    }
+
+    static _writeTime(buffer, time) {
+        const midnight = new Date(time);
+        midnight.setHours(0, 0, 0, 0);
+        buffer.writeLong(time.getTime() - midnight.getTime());
+    }
+
+    static async _writeArray(buffer, array, arrayType, arrayTypeCode) {
+        const BinaryType = require('./BinaryType');
+        const elementType = BinaryUtils.getArrayElementType(arrayType);
+        const keepElementType = 
BinaryUtils.keepArrayElementType(arrayTypeCode);
+        if (arrayTypeCode === BinaryUtils.TYPE_CODE.OBJECT_ARRAY) {
+            buffer.writeInteger(elementType instanceof ComplexObjectType ?
+                BinaryType._calculateId(elementType._typeName) : -1);
+        }
+        buffer.writeInteger(array.length);
+        for (let elem of array) {
+            await BinaryWriter.writeObject(buffer, elem, elementType, 
keepElementType);
+        }
+    }
+
+    static async _writeCollection(buffer, collection, collectionType) {
+        buffer.writeInteger(collection instanceof Set ? collection.size : 
collection.length);
+        buffer.writeByte(collectionType._subType);
+        for (let element of collection) {
+            await BinaryWriter.writeObject(buffer, element, 
collectionType._elementType);
+        }
+    }
+
+    static async _writeMap(buffer, map, mapType) {
+        buffer.writeInteger(map.size);
+        buffer.writeByte(mapType._subType);
+        for (let [key, value] of map.entries()) {
+            await BinaryWriter.writeObject(buffer, key, mapType._keyType);
+            await BinaryWriter.writeObject(buffer, value, mapType._valueType);
+        }
+    }
+
+    static async _writeBinaryObject(buffer, binaryObject) {
+        buffer.position = buffer.position - 1;
+        await binaryObject._write(buffer);
+    }
+
+    static async _writeComplexObject(buffer, object, objectType) {
+        const BinaryObject = require('../BinaryObject');
+        await BinaryWriter._writeBinaryObject(buffer, await 
BinaryObject.fromObject(object, objectType));
+    }
+}
+
+module.exports = BinaryWriter;

http://git-wip-us.apache.org/repos/asf/ignite/blob/c56d16fb/modules/platforms/nodejs/lib/internal/ClientFailoverSocket.js
----------------------------------------------------------------------
diff --git a/modules/platforms/nodejs/lib/internal/ClientFailoverSocket.js 
b/modules/platforms/nodejs/lib/internal/ClientFailoverSocket.js
new file mode 100644
index 0000000..3ef9c76
--- /dev/null
+++ b/modules/platforms/nodejs/lib/internal/ClientFailoverSocket.js
@@ -0,0 +1,134 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+const Util = require('util');
+const Errors = require('../Errors');
+const IgniteClient = require('../IgniteClient');
+const ClientSocket = require('./ClientSocket');
+const Logger = require('./Logger');
+
+/** Socket wrapper with failover functionality: reconnects on failure. */
+class ClientFailoverSocket {
+
+    constructor(onStateChanged) {
+        this._socket = null;
+        this._state = IgniteClient.STATE.DISCONNECTED;
+        this._onStateChanged = onStateChanged;
+    }
+
+    async connect(config) {
+        if (this._state !== IgniteClient.STATE.DISCONNECTED) {
+            throw new Errors.IllegalStateError();
+        }
+        this._config = config;
+        this._endpointsNumber = this._config._endpoints.length;
+        this._endpointIndex = this._getRandomInt(this._endpointsNumber - 1);
+        await this._connect();
+    }
+
+    async send(opCode, payloadWriter, payloadReader = null) {
+        if (this._state !== IgniteClient.STATE.CONNECTED) {
+            throw new Errors.IllegalStateError();
+        }
+        await this._socket.sendRequest(opCode, payloadWriter, payloadReader);
+    }
+
+    disconnect() {
+        if (this._state !== IgniteClient.STATE.DISCONNECTED) {
+            this._changeState(IgniteClient.STATE.DISCONNECTED);
+            if (this._socket) {
+                this._socket.disconnect();
+                this._socket = null;
+            }
+        }
+    }
+
+    async _onSocketDisconnect(error = null) {
+        this._changeState(IgniteClient.STATE.CONNECTING, null, error);        
+        this._socket = null;
+        this._endpointIndex++;
+        try {
+            await this._connect();
+        }
+        catch (err) {
+        }
+    }
+
+    async _connect() {
+        const errors = new Array();
+        let index, endpoint;
+        for (let i = 0; i < this._endpointsNumber; i++) {
+            index = (this._endpointIndex + i) % this._endpointsNumber;
+            endpoint = this._config._endpoints[index];
+            try {
+                this._changeState(IgniteClient.STATE.CONNECTING, endpoint);
+                this._socket = new ClientSocket(
+                    endpoint, this._config, 
this._onSocketDisconnect.bind(this));
+                await this._socket.connect();
+                this._changeState(IgniteClient.STATE.CONNECTED, endpoint);
+                return;
+            }
+            catch (err) {
+                errors.push(Util.format('[%s] %s', endpoint, err.message));
+            }
+        }
+        const error = errors.join('; ');
+        this._changeState(IgniteClient.STATE.DISCONNECTED, endpoint, error);
+        this._socket = null;
+        throw new Errors.IgniteClientError(error);
+    }
+
+    _changeState(state, endpoint = null, reason = null) {
+        if (Logger.debug) {
+            Logger.logDebug(Util.format('Socket %s: %s -> %s'),
+                endpoint ? endpoint : this._socket ? this._socket._endpoint : 
'',
+                this._getState(this._state),
+                this._getState(state));
+        }
+        if (this._state !== state) {
+            this._state = state;
+            if (this._onStateChanged) {
+                this._onStateChanged(state, reason);
+            }
+        }
+    }
+
+    _getState(state) {
+        switch (state) {
+            case IgniteClient.STATE.DISCONNECTED:
+                return 'DISCONNECTED';
+            case IgniteClient.STATE.CONNECTING:
+                return 'CONNECTING';
+            case IgniteClient.STATE.CONNECTED:
+                return 'CONNECTED';
+            default:
+                return 'UNKNOWN';
+        }
+    }
+
+    // returns a random integer between 0 and max
+    _getRandomInt(max) {
+        if (max === 0) {
+            return 0;
+        }
+        return Math.floor(Math.random() * (max + 1));
+    }
+}
+
+module.exports = ClientFailoverSocket;

http://git-wip-us.apache.org/repos/asf/ignite/blob/c56d16fb/modules/platforms/nodejs/lib/internal/ClientSocket.js
----------------------------------------------------------------------
diff --git a/modules/platforms/nodejs/lib/internal/ClientSocket.js 
b/modules/platforms/nodejs/lib/internal/ClientSocket.js
new file mode 100644
index 0000000..1f12040
--- /dev/null
+++ b/modules/platforms/nodejs/lib/internal/ClientSocket.js
@@ -0,0 +1,434 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+const net = require('net');
+const tls = require('tls');
+const URL = require('url');
+const Long = require('long');
+const Util = require('util');
+const Errors = require('../Errors');
+const IgniteClientConfiguration = require('../IgniteClientConfiguration');
+const MessageBuffer = require('./MessageBuffer');
+const BinaryUtils = require('./BinaryUtils');
+const BinaryReader = require('./BinaryReader');
+const BinaryWriter = require('./BinaryWriter');
+const ArgumentChecker = require('./ArgumentChecker');
+const Logger = require('./Logger');
+
+const HANDSHAKE_SUCCESS_STATUS_CODE = 1;
+const REQUEST_SUCCESS_STATUS_CODE = 0;
+const PORT_DEFAULT = 10800;
+
+class ProtocolVersion {
+
+    constructor(major = null, minor = null, patch = null) {
+        this._major = major;
+        this._minor = minor;
+        this._patch = patch;
+    }
+
+    compareTo(other) {
+        let diff = this._major - other._major;
+        if (diff !== 0) {
+            return diff;
+        }
+        diff = this._minor - other._minor;
+        if (diff !== 0) {
+            return diff;
+        }
+        return this._patch - other._patch;
+    }
+
+    equals(other) {
+        return this.compareTo(other) === 0;
+    }
+
+    toString() {
+        return Util.format('%d.%d.%d', this._major, this._minor, this._patch);
+    }
+
+    read(buffer) {
+        this._major = buffer.readShort();
+        this._minor = buffer.readShort();
+        this._patch = buffer.readShort();
+    }
+
+    write(buffer) {
+        buffer.writeShort(this._major);
+        buffer.writeShort(this._minor);
+        buffer.writeShort(this._patch);
+    }
+}
+
+const PROTOCOL_VERSION_1_0_0 = new ProtocolVersion(1, 0, 0);
+const PROTOCOL_VERSION_1_1_0 = new ProtocolVersion(1, 1, 0);
+
+const SUPPORTED_VERSIONS = [
+    // PROTOCOL_VERSION_1_0_0, // Support for QueryField precision/scale 
fields breaks 1.0.0 compatibility
+    PROTOCOL_VERSION_1_1_0
+];
+
+const STATE = Object.freeze({
+    INITIAL : 0,
+    HANDSHAKE : 1,
+    CONNECTED : 2,
+    DISCONNECTED : 3
+});
+
+class ClientSocket {
+
+    constructor(endpoint, config, onSocketDisconnect) {
+        ArgumentChecker.notEmpty(endpoint, 'endpoints');
+        this._endpoint = endpoint;
+        this._parseEndpoint(endpoint);
+        this._config = config;
+        this._state = STATE.INITIAL;
+        this._socket = null;
+        this._requestId = Long.ZERO;
+        this._handshakeRequestId = null;
+        this._protocolVersion = null;
+        this._requests = new Map();
+        this._onSocketDisconnect = onSocketDisconnect;
+        this._error = null;
+        this._wasConnected = false;
+    }
+
+    async connect() {
+        return new Promise((resolve, reject) => {
+            this._connectSocket(
+                this._getHandshake(PROTOCOL_VERSION_1_1_0, resolve, reject));
+        });
+    }
+
+    disconnect() {
+        this._disconnect(true, false);
+    }
+
+    get requestId() {
+        const id = this._requestId;
+        this._requestId = this._requestId.add(1);
+        return id;
+    }
+
+    async sendRequest(opCode, payloadWriter, payloadReader = null) {
+        if (this._state === STATE.CONNECTED) {
+            return new Promise(async (resolve, reject) => {
+                const request = new Request(this.requestId, opCode, 
payloadWriter, payloadReader, resolve, reject);
+                this._addRequest(request);
+                await this._sendRequest(request);
+            });
+        }
+        else {
+            throw new Errors.IllegalStateError();
+        }
+    }
+
+    _connectSocket(handshakeRequest) {
+        const onConnected = async () => {
+            this._state = STATE.HANDSHAKE;
+            // send handshake
+            await this._sendRequest(handshakeRequest);
+        };
+
+        const options = Object.assign({},
+            this._config._options,
+            { host : this._host, port : this._port, version : this._version });
+        if (this._config._useTLS) {
+            this._socket = tls.connect(options, onConnected);
+        }
+        else {
+            this._socket = net.createConnection(options, onConnected);
+        }
+
+        this._socket.on('data', async (data) => {
+            try {
+                await this._processResponse(data);
+            }
+            catch (err) {
+                this._error = err.message;
+                this._disconnect();
+            }
+        });
+        this._socket.on('close', () => {
+            this._disconnect(false);
+        });
+        this._socket.on('error', (error) => {
+            this._error = this._state === STATE.INITIAL ?
+                'Connection failed: ' + error : error;
+            this._disconnect();
+        });
+    }
+
+    _addRequest(request) {
+        this._requests.set(request.id.toString(), request);
+    }
+
+    async _sendRequest(request) {
+        try {
+            const message = await request.getMessage();
+            this._logMessage(request.id.toString(), true, message);
+            this._socket.write(message);
+        }
+        catch (err) {
+            this._requests.delete(request.id);
+            request.reject(err);
+        }
+    }
+
+    async _processResponse(message) {
+        if (this._state === STATE.DISCONNECTED) {
+            return;
+        }
+        let offset = 0;
+        while (offset < message.length) {
+            let buffer = MessageBuffer.from(message, offset);
+            // Response length
+            const length = buffer.readInteger();
+            offset += length + 
BinaryUtils.getSize(BinaryUtils.TYPE_CODE.INTEGER);
+            let requestId, isSuccess;
+            const isHandshake = this._state === STATE.HANDSHAKE;
+
+            if (isHandshake) {
+                // Handshake status
+                isSuccess = (buffer.readByte() === 
HANDSHAKE_SUCCESS_STATUS_CODE)
+                requestId = this._handshakeRequestId.toString();
+            }
+            else {
+                // Request id
+                requestId = buffer.readLong().toString();
+                // Status code
+                isSuccess = (buffer.readInteger() === 
REQUEST_SUCCESS_STATUS_CODE);
+            }
+
+            this._logMessage(requestId, false, buffer.data);
+
+            if (this._requests.has(requestId)) {
+                const request = this._requests.get(requestId);
+                this._requests.delete(requestId);
+                if (isHandshake) {
+                    await this._finalizeHandshake(buffer, request, isSuccess);
+                }
+                else {
+                    await this._finalizeResponse(buffer, request, isSuccess);
+                }
+            }
+            else {
+                throw Errors.IgniteClientError.internalError('Invalid response 
id: ' + requestId);
+            }
+        }
+    }
+
+    async _finalizeHandshake(buffer, request, isSuccess) {
+        if (!isSuccess) {
+            // Server protocol version
+            const serverVersion = new ProtocolVersion();
+            serverVersion.read(buffer);
+            // Error message
+            const errMessage = await BinaryReader.readObject(buffer);
+
+            if (!this._protocolVersion.equals(serverVersion)) {
+                if (!this._isSupportedVersion(serverVersion) ||
+                    serverVersion.compareTo(PROTOCOL_VERSION_1_1_0) < 0 && 
this._config._userName) {
+                    request.reject(new Errors.OperationError(
+                        Util.format('Protocol version mismatch: client %s / 
server %s. Server details: %s',
+                            this._protocolVersion.toString(), 
serverVersion.toString(), errMessage)));
+                    this._disconnect();
+                }
+                else {
+                    // retry handshake with server version
+                    const handshakeRequest = this._getHandshake(serverVersion, 
request.resolve, request.reject);
+                    await this._sendRequest(handshakeRequest);
+                }
+            }
+            else {
+                request.reject(new Errors.OperationError(errMessage));
+                this._disconnect();
+            }
+        }
+        else {
+            this._state = STATE.CONNECTED;
+            this._wasConnected = true;
+            request.resolve();
+        }
+    }
+
+    async _finalizeResponse(buffer, request, isSuccess) {
+        if (!isSuccess) {
+            // Error message
+            const errMessage = await BinaryReader.readObject(buffer);
+            request.reject(new Errors.OperationError(errMessage));
+        }
+        else {
+            try {
+                if (request.payloadReader) {
+                    await request.payloadReader(buffer);
+                }
+                request.resolve();
+            }
+            catch (err) {
+                request.reject(err);
+            }
+        }
+    }
+
+    async _handshakePayloadWriter(payload) {
+        // Handshake code
+        payload.writeByte(1);
+        // Protocol version
+        this._protocolVersion.write(payload);
+        // Client code
+        payload.writeByte(2);
+        if (this._config._userName) {
+            await BinaryWriter.writeString(payload, this._config._userName);
+            await BinaryWriter.writeString(payload, this._config._password);
+        }
+    }
+
+    _getHandshake(version, resolve, reject) {
+        this._protocolVersion = version;
+        const handshakeRequest = new Request(
+            this.requestId, null, this._handshakePayloadWriter.bind(this), 
null, resolve, reject);
+        this._addRequest(handshakeRequest);
+        this._handshakeRequestId = handshakeRequest.id;
+        return handshakeRequest;
+    }
+
+    _isSupportedVersion(protocolVersion) {
+        for (let version of SUPPORTED_VERSIONS) {
+            if (version.equals(protocolVersion)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    _disconnect(close = true, callOnDisconnect = true) {
+        this._state = STATE.DISCONNECTED;
+        this._requests.forEach((request, id) => {
+            request.reject(new Errors.LostConnectionError(this._error));
+            this._requests.delete(id);
+        });
+        if (this._wasConnected && callOnDisconnect && 
this._onSocketDisconnect) {
+            this._onSocketDisconnect(this._error);
+        }
+        if (close) {
+            this._onSocketDisconnect = null;
+            this._socket.end();
+        }
+    }
+
+    _parseEndpoint(endpoint) {
+        endpoint = endpoint.trim();
+        this._host = endpoint;
+        this._port = null;
+        const colonCnt = endpoint.split(':').length - 1;
+        if (colonCnt > 1) {
+            // IPv6 address
+            this._version = 6;
+            const index = endpoint.lastIndexOf(']:');
+            if (index >= 0) {
+                this._host = endpoint.substring(0, index + 1);
+                this._port = endpoint.substring(index + 2);
+            }
+            if (this._host.startsWith('[') || this._host.endsWith(']')) {
+                if (this._host.startsWith('[') && this._host.endsWith(']')) {
+                    this._host = this._host.substring(1, this._host.length - 
1);
+                }
+                else {
+                    throw 
Errors.IgniteClientError.illegalArgumentError('Incorrect endpoint format: ' + 
endpoint);
+                }
+            }
+        }
+        else {
+            // IPv4 address
+            this._version = 4;
+            const index = endpoint.lastIndexOf(':');
+            if (index >= 0) {
+                this._host = endpoint.substring(0, index);
+                this._port = endpoint.substring(index + 1);
+            }
+        }
+        if (!this._port) {
+            this._port = PORT_DEFAULT;
+        }
+        else {
+            this._port = parseInt(this._port);
+            if (isNaN(this._port)) {
+                throw Errors.IgniteClientError.illegalArgumentError('Incorrect 
endpoint format: ' + endpoint);
+            }
+        }
+    }
+
+    _logMessage(requestId, isRequest, message) {
+        if (Logger.debug) {
+            Logger.logDebug((isRequest ? 'Request: ' : 'Response: ') + 
requestId);
+            Logger.logDebug('[' + [...message] + ']');
+        }
+    }
+}
+
+class Request {
+    constructor(id, opCode, payloadWriter, payloadReader, resolve, reject) {
+        this._id = id;
+        this._opCode = opCode;
+        this._payloadWriter = payloadWriter;
+        this._payloadReader = payloadReader;
+        this._resolve = resolve;
+        this._reject = reject;
+    }
+
+    get id() {
+        return this._id;
+    }
+
+    get payloadReader() {
+        return this._payloadReader;
+    }
+
+    get resolve() {
+        return this._resolve;
+    }
+
+    get reject() {
+        return this._reject;
+    }
+
+    async getMessage() {
+        const message = new MessageBuffer();
+        // Skip message length
+        const messageStartPos = 
BinaryUtils.getSize(BinaryUtils.TYPE_CODE.INTEGER);
+        message.position = messageStartPos;
+        if (this._opCode !== null) {
+            // Op code
+            message.writeShort(this._opCode);
+            // Request id
+            message.writeLong(this._id);
+        }
+        if (this._payloadWriter) {
+            // Payload
+            await this._payloadWriter(message);
+        }
+        // Message length
+        message.position = 0;
+        message.writeInteger(message.length - messageStartPos);
+        return message.data;
+    }
+}
+
+module.exports = ClientSocket;
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/c56d16fb/modules/platforms/nodejs/lib/internal/Logger.js
----------------------------------------------------------------------
diff --git a/modules/platforms/nodejs/lib/internal/Logger.js 
b/modules/platforms/nodejs/lib/internal/Logger.js
new file mode 100644
index 0000000..628c70e
--- /dev/null
+++ b/modules/platforms/nodejs/lib/internal/Logger.js
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+/** Utility class for logging errors and debug messages. */
+class Logger {
+    static get debug() {
+        return Logger._debug;
+    }
+
+    static set debug(value) {
+        Logger._debug = value;
+    }
+
+    static logDebug(data, ...args) {
+        if (Logger._debug) {
+            console.log(data, ...args);
+        }
+    }
+
+    static logError(data, ...args) {
+        if (Logger._debug) {
+            console.log('ERROR: ' + data, ...args);
+        }
+    }
+}
+
+Logger._debug = false;
+
+module.exports = Logger;

http://git-wip-us.apache.org/repos/asf/ignite/blob/c56d16fb/modules/platforms/nodejs/lib/internal/MessageBuffer.js
----------------------------------------------------------------------
diff --git a/modules/platforms/nodejs/lib/internal/MessageBuffer.js 
b/modules/platforms/nodejs/lib/internal/MessageBuffer.js
new file mode 100644
index 0000000..f1407bf
--- /dev/null
+++ b/modules/platforms/nodejs/lib/internal/MessageBuffer.js
@@ -0,0 +1,272 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+const Long = require('long');
+const BinaryUtils = require('./BinaryUtils');
+const Errors = require('../Errors');
+
+const BUFFER_CAPACITY_DEFAULT = 256;
+const BYTE_ZERO = 0;
+const BYTE_ONE = 1;
+
+class MessageBuffer {
+    constructor(capacity = BUFFER_CAPACITY_DEFAULT) {
+        this._buffer = Buffer.allocUnsafe(capacity);
+        this._capacity = capacity;
+        this._length = 0;
+        this._position = 0;
+    }
+
+    static from(source, position) {
+        const buf = new MessageBuffer();
+        buf._buffer = Buffer.from(source);
+        buf._position = position;
+        buf._length = buf._buffer.length;
+        buf._capacity = buf._length;
+        return buf;
+    }
+
+    get position() {
+        return this._position;
+    }
+
+    set position(position) {
+        this._position = position;
+    }
+
+    get length() {
+        return this._length;
+    }
+
+    get data() {
+        return this.getSlice(0, this.length);
+    }
+
+    get buffer() {
+        return this._buffer;
+    }
+
+    getSlice(start, end) {
+        return this._buffer.slice(start, end);
+    }
+
+    writeByte(value) {
+        this.writeNumber(value, BinaryUtils.TYPE_CODE.BYTE);
+    }
+
+    writeShort(value) {
+        this.writeNumber(value, BinaryUtils.TYPE_CODE.SHORT);
+    }
+
+    writeInteger(value) {
+        this.writeNumber(value, BinaryUtils.TYPE_CODE.INTEGER);
+    }
+
+    writeLong(value) {
+        try {
+            if (!Long.isLong(value)) {
+                value = Long.fromValue(value);
+            }
+        }
+        catch (err) {
+            throw Errors.IgniteClientError.valueCastError(value, 
BinaryUtils.TYPE_CODE.LONG);
+        }
+        const buffer = Buffer.from(value.toBytesLE());
+        this.writeBuffer(buffer);
+    }
+
+    writeFloat(value) {
+        this.writeNumber(value, BinaryUtils.TYPE_CODE.FLOAT);
+    }
+
+    writeDouble(value) {
+        this.writeNumber(value, BinaryUtils.TYPE_CODE.DOUBLE);
+    }
+
+    writeNumber(value, type) {
+        const size = BinaryUtils.getSize(type);
+        this._ensureCapacity(size);
+        try {
+            switch (type) {
+                case BinaryUtils.TYPE_CODE.BYTE:
+                    this._buffer.writeInt8(value, this._position);
+                    break;
+                case BinaryUtils.TYPE_CODE.SHORT:
+                    this._buffer.writeInt16LE(value, this._position);
+                    break;
+                case BinaryUtils.TYPE_CODE.INTEGER:
+                    this._buffer.writeInt32LE(value, this._position);
+                    break;
+                case BinaryUtils.TYPE_CODE.FLOAT:
+                    this._buffer.writeFloatLE(value, this._position);
+                    break;
+                case BinaryUtils.TYPE_CODE.DOUBLE:
+                    this._buffer.writeDoubleLE(value, this._position);
+                    break;
+                default:
+                    throw Errors.IgniteClientError.internalError();
+            }
+        }
+        catch (err) {
+            throw Errors.IgniteClientError.valueCastError(value, type);
+        }
+        this._position += size;
+    }
+
+    writeBoolean(value) {
+        this.writeByte(value ? BYTE_ONE : BYTE_ZERO);
+    }
+
+    writeChar(value) {
+        this.writeShort(value.charCodeAt(0));
+    }
+
+    writeString(value) {
+        const buffer = Buffer.from(value, BinaryUtils.ENCODING);
+        const length = buffer.length;
+        this.writeInteger(length);
+        if (length > 0) {
+            this.writeBuffer(buffer);
+        }
+    }
+
+    writeDate(value) {
+        this.writeLong(value.getTime());
+    }
+
+    readByte() {
+        return this.readNumber(BinaryUtils.TYPE_CODE.BYTE);
+    }
+
+    readShort() {
+        return this.readNumber(BinaryUtils.TYPE_CODE.SHORT);
+    }
+
+    readInteger() {
+        return this.readNumber(BinaryUtils.TYPE_CODE.INTEGER);
+    }
+
+    readLong() {
+        const size = BinaryUtils.getSize(BinaryUtils.TYPE_CODE.LONG)
+        this._ensureSize(size);
+        const value = Long.fromBytesLE([...this._buffer.slice(this._position, 
this._position + size)]);
+        this._position += size;
+        return value;
+    }
+
+    readFloat() {
+        return this.readNumber(BinaryUtils.TYPE_CODE.FLOAT);
+    }
+
+    readDouble() {
+        return this.readNumber(BinaryUtils.TYPE_CODE.DOUBLE);
+    }
+
+    readNumber(type) {
+        const size = BinaryUtils.getSize(type);
+        this._ensureSize(size);
+        let value;
+        switch (type) {
+            case BinaryUtils.TYPE_CODE.BYTE:
+                value = this._buffer.readInt8(this._position);
+                break;
+            case BinaryUtils.TYPE_CODE.SHORT:
+                value = this._buffer.readInt16LE(this._position);
+                break;
+            case BinaryUtils.TYPE_CODE.INTEGER:
+                value = this._buffer.readInt32LE(this._position);
+                break;
+            case BinaryUtils.TYPE_CODE.FLOAT:
+                value = this._buffer.readFloatLE(this._position);
+                break;
+            case BinaryUtils.TYPE_CODE.DOUBLE:
+                value = this._buffer.readDoubleLE(this._position);
+                break;
+            default:
+                throw Errors.IgniteClientError.internalError();
+        }
+        this._position += size;
+        return value;
+    }
+
+    readBoolean() {
+        return this.readByte() === BYTE_ONE;
+    }
+
+    readChar() {
+        return String.fromCharCode(this.readShort());
+    }
+
+    readString() {
+        const bytesCount = this.readInteger();
+        this._ensureSize(bytesCount);
+        const result = this._buffer.toString(BinaryUtils.ENCODING, 
this._position, this._position + bytesCount);
+        this._position += bytesCount;
+        return result;
+    }
+
+    readBuffer(length) {
+        this._ensureSize(length);
+        const result = this._buffer.slice(this._position, this._position + 
length);
+        this._position += length;
+        return result;
+    }
+
+    readDate() {
+        return new Date(this.readLong().toNumber());
+    }
+
+    writeBuffer(buffer, start = undefined, end = undefined) {
+        if (start === undefined) {
+            start = 0;
+        }
+        if (end === undefined) {
+            end = buffer.length;
+        }
+        const size = end - start; 
+        this._ensureCapacity(size);
+        buffer.copy(this._buffer, this._position, start, end);
+        this._position += size;
+    }
+
+    _ensureSize(size) {
+        if (this._position + size > this._length) {
+            throw Errors.IgniteClientError.internalError('Unexpected format of 
response');
+        }
+    }
+
+    _ensureCapacity(valueSize) {
+        if (valueSize <= 0) {
+            throw Errors.IgniteClientError.internalError();
+        }
+        let newCapacity = this._capacity;
+        while (this._position + valueSize > newCapacity) {
+            newCapacity = newCapacity * 2;
+        }
+        if (this._capacity < newCapacity) {
+            this._buffer = Buffer.concat([this._buffer, 
Buffer.allocUnsafe(newCapacity - this._capacity)], newCapacity);
+            this._capacity = newCapacity;
+        }
+        if (this._position + valueSize > this._length) {
+            this._length = this._position + valueSize;
+        }
+    }
+}
+
+module.exports = MessageBuffer;

http://git-wip-us.apache.org/repos/asf/ignite/blob/c56d16fb/modules/platforms/nodejs/package-lock.json
----------------------------------------------------------------------
diff --git a/modules/platforms/nodejs/package-lock.json 
b/modules/platforms/nodejs/package-lock.json
new file mode 100644
index 0000000..e62bdbb
--- /dev/null
+++ b/modules/platforms/nodejs/package-lock.json
@@ -0,0 +1,168 @@
+{
+  "name": "apache-ignite-client",
+  "version": "1.0.0",
+  "lockfileVersion": 1,
+  "requires": true,
+  "dependencies": {
+    "add-matchers": {
+      "version": "0.5.0",
+      "resolved": 
"https://registry.npmjs.org/add-matchers/-/add-matchers-0.5.0.tgz";,
+      "integrity": "sha1-UCGQ5HUM1XIWGDkyaLYaFXNm52U=",
+      "dev": true
+    },
+    "balanced-match": {
+      "version": "1.0.0",
+      "resolved": 
"https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz";,
+      "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
+      "dev": true
+    },
+    "brace-expansion": {
+      "version": "1.1.11",
+      "resolved": 
"https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz";,
+      "integrity": 
"sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+      "dev": true,
+      "requires": {
+        "balanced-match": "1.0.0",
+        "concat-map": "0.0.1"
+      }
+    },
+    "concat-map": {
+      "version": "0.0.1",
+      "resolved": 
"https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz";,
+      "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+      "dev": true
+    },
+    "decimal.js": {
+      "version": "10.0.0",
+      "resolved": 
"https://registry.npmjs.org/decimal.js/-/decimal.js-10.0.0.tgz";,
+      "integrity": 
"sha512-oTJ6FdBXq28TSDB/e0CfuXWxZE1+Jj3vV9+6eL73d+YpryRqD/+R4O5kAmZjNHiL2zw9lXrnZR8NhJtualzufg=="
+    },
+    "fs.realpath": {
+      "version": "1.0.0",
+      "resolved": 
"https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz";,
+      "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+      "dev": true
+    },
+    "glob": {
+      "version": "7.1.2",
+      "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz";,
+      "integrity": 
"sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==",
+      "dev": true,
+      "requires": {
+        "fs.realpath": "1.0.0",
+        "inflight": "1.0.6",
+        "inherits": "2.0.3",
+        "minimatch": "3.0.4",
+        "once": "1.4.0",
+        "path-is-absolute": "1.0.1"
+      }
+    },
+    "inflight": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz";,
+      "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+      "dev": true,
+      "requires": {
+        "once": "1.4.0",
+        "wrappy": "1.0.2"
+      }
+    },
+    "inherits": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz";,
+      "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=",
+      "dev": true
+    },
+    "jasmine": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/jasmine/-/jasmine-3.1.0.tgz";,
+      "integrity": "sha1-K9Wf1+xuwOistk4J9Fpo7SrRlSo=",
+      "dev": true,
+      "requires": {
+        "glob": "7.1.2",
+        "jasmine-core": "3.1.0"
+      }
+    },
+    "jasmine-core": {
+      "version": "3.1.0",
+      "resolved": 
"https://registry.npmjs.org/jasmine-core/-/jasmine-core-3.1.0.tgz";,
+      "integrity": "sha1-pHheE11d9lAk38kiSVPfWFvSdmw=",
+      "dev": true
+    },
+    "jasmine-expect": {
+      "version": "3.8.3",
+      "resolved": 
"https://registry.npmjs.org/jasmine-expect/-/jasmine-expect-3.8.3.tgz";,
+      "integrity": 
"sha512-VZu4V0O/YWBfUa2dgb9cz/WcQdWRFtdM4SV0Iy+abORLEkqP6vUrMvoFtxP9TJ+Rtor+z6857Vz5ySOaTFEt2A==",
+      "dev": true,
+      "requires": {
+        "add-matchers": "0.5.0"
+      }
+    },
+    "jasmine-reporters": {
+      "version": "2.3.1",
+      "resolved": 
"https://registry.npmjs.org/jasmine-reporters/-/jasmine-reporters-2.3.1.tgz";,
+      "integrity": "sha1-9C1XjplmlhY0MdkRwxZ5cZ+0Ozs=",
+      "dev": true,
+      "requires": {
+        "mkdirp": "0.5.1",
+        "xmldom": "0.1.27"
+      }
+    },
+    "long": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz";,
+      "integrity": 
"sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="
+    },
+    "minimatch": {
+      "version": "3.0.4",
+      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz";,
+      "integrity": 
"sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+      "dev": true,
+      "requires": {
+        "brace-expansion": "1.1.11"
+      }
+    },
+    "minimist": {
+      "version": "0.0.8",
+      "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz";,
+      "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
+      "dev": true
+    },
+    "mkdirp": {
+      "version": "0.5.1",
+      "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz";,
+      "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
+      "dev": true,
+      "requires": {
+        "minimist": "0.0.8"
+      }
+    },
+    "once": {
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz";,
+      "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+      "dev": true,
+      "requires": {
+        "wrappy": "1.0.2"
+      }
+    },
+    "path-is-absolute": {
+      "version": "1.0.1",
+      "resolved": 
"https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz";,
+      "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+      "dev": true
+    },
+    "wrappy": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz";,
+      "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+      "dev": true
+    },
+    "xmldom": {
+      "version": "0.1.27",
+      "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.27.tgz";,
+      "integrity": "sha1-1QH5ezvbQDr4757MIFcxh6rawOk=",
+      "dev": true
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/c56d16fb/modules/platforms/nodejs/package.json
----------------------------------------------------------------------
diff --git a/modules/platforms/nodejs/package.json 
b/modules/platforms/nodejs/package.json
new file mode 100644
index 0000000..4ffb809
--- /dev/null
+++ b/modules/platforms/nodejs/package.json
@@ -0,0 +1,25 @@
+{
+  "name": "apache-ignite-client",
+  "version": "1.0.0",
+  "description": "",
+  "main": "index.js",
+  "author": "",
+  "license": "Apache-2.0",
+  "engines": {
+    "node": ">=8.0.0"
+  },
+  "dependencies": {
+    "long": "latest",
+    "decimal.js": "latest"
+  },
+  "scripts": {
+    "test": "jasmine",
+    "test:examples": "node ./spec/ExamplesExecutor.js Examples",
+    "test:auth_example": "node ./spec/ExamplesExecutor.js AuthExample"
+  },
+  "devDependencies": {
+    "jasmine": "latest",
+    "jasmine-expect": "latest",
+    "jasmine-reporters": "latest"
+  }
+}

http://git-wip-us.apache.org/repos/asf/ignite/blob/c56d16fb/modules/platforms/nodejs/spec/ExamplesExecutor.js
----------------------------------------------------------------------
diff --git a/modules/platforms/nodejs/spec/ExamplesExecutor.js 
b/modules/platforms/nodejs/spec/ExamplesExecutor.js
new file mode 100644
index 0000000..7f263d9
--- /dev/null
+++ b/modules/platforms/nodejs/spec/ExamplesExecutor.js
@@ -0,0 +1,11 @@
+const Jasmine = require('jasmine');
+
+const jasmine = new Jasmine();
+jasmine.loadConfig({
+    'spec_dir': 'spec',
+    'spec_files': [
+        `examples/${process.argv[2]}.spec.js`
+    ],
+    "random": false
+});
+jasmine.execute();
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ignite/blob/c56d16fb/modules/platforms/nodejs/spec/README.md
----------------------------------------------------------------------
diff --git a/modules/platforms/nodejs/spec/README.md 
b/modules/platforms/nodejs/spec/README.md
new file mode 100644
index 0000000..4b947c4
--- /dev/null
+++ b/modules/platforms/nodejs/spec/README.md
@@ -0,0 +1,42 @@
+# Tests #
+
+NodeJS Client for Apache Ignite contains 
[Jasmine](https://www.npmjs.com/package/jasmine) tests to check the behavior of 
the client. the tests include:
+- functional tests which cover all API methods of the client
+- examples executors which run all examples except AuthTlsExample
+- AuthTlsExample executor
+
+## Tests Installation ##
+
+(temporary, while the NPM module is not released on 
[npmjs](https://www.npmjs.com))
+
+Tests are installed along with the client.
+Follow the [instructions in the main readme](../README.md#installation).
+
+## Tests Running ##
+
+1. Run Apache Ignite server locally or remotely with default configuration.
+2. Set the environment variable:
+    - **APACHE_IGNITE_CLIENT_ENDPOINTS** - comma separated list of Ignite node 
endpoints.
+    - **APACHE_IGNITE_CLIENT_DEBUG** - (optional) if *true*, tests will 
display additional output (default: *false*).
+3. Alternatively, instead of the environment variables setting, you can 
directly specify the values of the corresponding variables in 
[local_ignite_path/modules/platforms/nodejspec/config.js](./config.js) file.
+4. Run the tests:
+
+### Run Functional Tests ###
+
+Call `npm test` command from `local_ignite_path/modules/platforms/nodejs` 
folder.
+
+### Run Examples Executors ###
+
+Call `npm run test:examples` command from 
`local_ignite_path/modules/platforms/nodejs` folder.
+
+### Run AuthTlsExample Executor ###
+
+It requires running Apache Ignite server with non-default configuration 
(authentication and TLS switched on).
+
+If the server runs locally:
+- setup the server to accept TLS. During the setup use `keystore.jks` and 
`truststore.jks` certificates from 
`local_ignite_path/modules/platforms/nodejs/examples/certs/` folder. Password 
for the files: `123456`
+- switch on the authentication on the server. Use the default 
username/password.
+
+If the server runs remotely, and/or other certificates are required, and/or 
non-default username/password is required - see this 
[instruction](../examples/README.md#additional-setup-for-authtlsexample).
+
+Call `npm run test:auth_example` command from 
`local_ignite_path/modules/platforms/nodejs` folder.

http://git-wip-us.apache.org/repos/asf/ignite/blob/c56d16fb/modules/platforms/nodejs/spec/TestingHelper.js
----------------------------------------------------------------------
diff --git a/modules/platforms/nodejs/spec/TestingHelper.js 
b/modules/platforms/nodejs/spec/TestingHelper.js
new file mode 100644
index 0000000..79a5336
--- /dev/null
+++ b/modules/platforms/nodejs/spec/TestingHelper.js
@@ -0,0 +1,384 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+require('jasmine-expect');
+const JasmineReporters = require('jasmine-reporters');
+
+const Util = require('util');
+const exec = require('child_process').exec;
+const config = require('./config');
+const IgniteClient = require('apache-ignite-client');
+const IgniteClientConfiguration = IgniteClient.IgniteClientConfiguration;
+const Errors = IgniteClient.Errors;
+const EnumItem = IgniteClient.EnumItem;
+const Timestamp = IgniteClient.Timestamp;
+const Decimal = IgniteClient.Decimal;
+const BinaryObject = IgniteClient.BinaryObject;
+const ObjectType = IgniteClient.ObjectType;
+
+const TIMEOUT_MS = 60000;
+
+jasmine.getEnv().addReporter(new JasmineReporters.TeamCityReporter());
+
+const dateComparator = (date1, date2) => { return !date1 && !date2 || 
date1.value === date2.value; };
+const floatComparator = (date1, date2) => { return Math.abs(date1 - date2) < 
0.00001; };
+const defaultComparator = (value1, value2) => { return value1 === value2; };
+const enumComparator = (value1, value2) => {
+    return value1.getTypeId() === value2.getTypeId() &&
+        value1.getOrdinal() === value2.getOrdinal(); };
+const decimalComparator = (value1, value2) => {
+    return value1 === null && value2 === null ||
+        value1.equals(value2);
+};
+const timestampComparator = (value1, value2) => {
+    return value1 === null && value2 === null ||
+        dateComparator(value1.getTime(), value2.getTime()) &&
+        value1.getNanos() === value2.getNanos(); };
+
+const numericValueModificator = (data) => { return data > 0 ? data - 10 : data 
+ 10; };
+const charValueModificator = (data) => { return 
String.fromCharCode(data.charCodeAt(0) + 5); };
+const booleanValueModificator = (data) => { return !data; };
+const stringValueModificator = (data) => { return data + 'xxx'; };
+const dateValueModificator = (data) => { return new Date(data.getTime() + 
12345); };
+const UUIDValueModificator = (data) => { return data.reverse(); };
+const enumValueModificator = (data) => { return new EnumItem(data.getTypeId(), 
data.getOrdinal() + 1); };
+const decimalValueModificator = (data) => { return data.add(12345); };
+const timestampValueModificator = (data) => { return new Timestamp(new 
Date(data.getTime() + 12345), data.getNanos() + 123); };
+
+const primitiveValues = {
+    [ObjectType.PRIMITIVE_TYPE.BYTE] : { 
+        values : [-128, 0, 127],
+        isMapKey : true,
+        modificator : numericValueModificator
+    },
+    [ObjectType.PRIMITIVE_TYPE.SHORT] : {
+        values : [-32768, 0, 32767],
+        isMapKey : true,
+        modificator : numericValueModificator
+    },
+    [ObjectType.PRIMITIVE_TYPE.INTEGER] : {
+        values : [12345, 0, -54321],
+        isMapKey : true,
+        modificator : numericValueModificator
+    },
+    [ObjectType.PRIMITIVE_TYPE.LONG] : {
+        values : [12345678912345, 0, -98765432112345],
+        isMapKey : true,
+        modificator : numericValueModificator
+    },
+    [ObjectType.PRIMITIVE_TYPE.FLOAT] : {
+        values : [-1.155, 0, 123e-5],
+        isMapKey : false,
+        modificator : numericValueModificator
+    },
+    [ObjectType.PRIMITIVE_TYPE.DOUBLE] : {
+        values : [-123e5, 0, 0.0001],
+        typeOptional : true,
+        isMapKey : false,
+        modificator : numericValueModificator
+    },
+    [ObjectType.PRIMITIVE_TYPE.CHAR] : {
+        values : ['a', String.fromCharCode(0x1234)],
+        isMapKey : true,
+        modificator : charValueModificator
+    },
+    [ObjectType.PRIMITIVE_TYPE.BOOLEAN] : {
+        values : [true, false],
+        isMapKey : true,
+        typeOptional : true,
+        modificator : booleanValueModificator
+    },
+    [ObjectType.PRIMITIVE_TYPE.STRING] : {
+        values : ['abc', ''],
+        isMapKey : true,
+        typeOptional : true,
+        modificator : stringValueModificator
+    },
+    [ObjectType.PRIMITIVE_TYPE.UUID] : {
+        values : [
+            [ 18, 70, 2, 119, 154, 254, 198, 254, 195, 146, 33, 60, 116, 230, 
0, 146 ],
+            [ 141, 77, 31, 194, 127, 36, 184, 255, 192, 4, 118, 57, 253, 209, 
111, 147 ]
+        ],
+        isMapKey : false,
+        modificator : UUIDValueModificator
+    },
+    [ObjectType.PRIMITIVE_TYPE.DATE] : {
+        values : [new Date(), new Date('1995-12-17T03:24:00'), new Date(0)],
+        typeOptional : true,
+        isMapKey : false,
+        modificator : dateValueModificator
+    },
+    // [ObjectType.PRIMITIVE_TYPE.ENUM] : {
+    //     values : [new EnumItem(12345, 7), new EnumItem(0, 0)],
+    //     typeOptional : true,
+    //     isMapKey : false,
+    //     modificator : enumValueModificator
+    // },
+    [ObjectType.PRIMITIVE_TYPE.DECIMAL] : {
+        values : [new Decimal('123456789.6789345'), new Decimal(0), new 
Decimal('-98765.4321e15')],
+        typeOptional : true,
+        isMapKey : false,
+        modificator : decimalValueModificator
+    },
+    [ObjectType.PRIMITIVE_TYPE.TIMESTAMP] : {
+        values : [new Timestamp(new Date().getTime(), 12345), new 
Timestamp(new Date('1995-12-17T03:24:00').getTime(), 543), new Timestamp(0, 0)],
+        typeOptional : true,
+        isMapKey : false,
+        modificator : timestampValueModificator
+    },
+    [ObjectType.PRIMITIVE_TYPE.TIME] : {
+        values : [new Date(), new Date('1995-12-17T03:24:00'), new Date(123)],
+        isMapKey : false,
+        modificator : dateValueModificator
+    }
+};
+
+const arrayValues = {
+    [ObjectType.PRIMITIVE_TYPE.BYTE_ARRAY] : { elemType : 
ObjectType.PRIMITIVE_TYPE.BYTE },
+    [ObjectType.PRIMITIVE_TYPE.SHORT_ARRAY] : { elemType : 
ObjectType.PRIMITIVE_TYPE.SHORT },
+    [ObjectType.PRIMITIVE_TYPE.INTEGER_ARRAY] : { elemType : 
ObjectType.PRIMITIVE_TYPE.INTEGER },
+    [ObjectType.PRIMITIVE_TYPE.LONG_ARRAY] : { elemType : 
ObjectType.PRIMITIVE_TYPE.LONG },
+    [ObjectType.PRIMITIVE_TYPE.FLOAT_ARRAY] : { elemType : 
ObjectType.PRIMITIVE_TYPE.FLOAT },
+    [ObjectType.PRIMITIVE_TYPE.DOUBLE_ARRAY] : { elemType : 
ObjectType.PRIMITIVE_TYPE.DOUBLE, typeOptional : true },
+    [ObjectType.PRIMITIVE_TYPE.CHAR_ARRAY] : { elemType : 
ObjectType.PRIMITIVE_TYPE.CHAR },
+    [ObjectType.PRIMITIVE_TYPE.BOOLEAN_ARRAY] : { elemType : 
ObjectType.PRIMITIVE_TYPE.BOOLEAN, typeOptional : true },
+    [ObjectType.PRIMITIVE_TYPE.STRING_ARRAY] : { elemType : 
ObjectType.PRIMITIVE_TYPE.STRING, typeOptional : true },
+    [ObjectType.PRIMITIVE_TYPE.UUID_ARRAY] : { elemType : 
ObjectType.PRIMITIVE_TYPE.UUID },
+    [ObjectType.PRIMITIVE_TYPE.DATE_ARRAY] : { elemType : 
ObjectType.PRIMITIVE_TYPE.DATE, typeOptional : true },
+    //[ObjectType.PRIMITIVE_TYPE.ENUM_ARRAY] : { elemType : 
ObjectType.PRIMITIVE_TYPE.ENUM, typeOptional : true },
+    [ObjectType.PRIMITIVE_TYPE.DECIMAL_ARRAY] : { elemType : 
ObjectType.PRIMITIVE_TYPE.DECIMAL, typeOptional : true },
+    [ObjectType.PRIMITIVE_TYPE.TIMESTAMP_ARRAY] : { elemType : 
ObjectType.PRIMITIVE_TYPE.TIMESTAMP, typeOptional : true },
+    [ObjectType.PRIMITIVE_TYPE.TIME_ARRAY] : { elemType : 
ObjectType.PRIMITIVE_TYPE.TIME }
+};
+
+// Helper class for testing apache-ignite-client library.
+// Contains common methods for testing environment initialization and cleanup.
+class TestingHelper {
+    static get TIMEOUT() {
+        return TIMEOUT_MS;
+    }
+
+    static get primitiveValues() {
+        return primitiveValues;
+    }
+
+    static get arrayValues() {
+        return arrayValues;
+    }
+
+    // Initializes testing environment: creates and starts the library client, 
sets default jasmine test timeout.
+    // Should be called from any test suite beforeAll method.
+    static async init() {
+        jasmine.DEFAULT_TIMEOUT_INTERVAL = TIMEOUT_MS;
+
+        TestingHelper._igniteClient = new IgniteClient();
+        TestingHelper._igniteClient.setDebug(config.debug);
+        await TestingHelper._igniteClient.connect(new 
IgniteClientConfiguration(...config.endpoints));
+    }
+
+    // Cleans up testing environment.
+    // Should be called from any test suite afterAll method.
+    static async cleanUp() {
+        await TestingHelper.igniteClient.disconnect();
+    }
+
+    static get igniteClient() {
+        return TestingHelper._igniteClient;
+    }
+
+    static async destroyCache(cacheName, done) {
+        try {
+            await TestingHelper.igniteClient.destroyCache(cacheName);
+        }
+        catch (err) {
+            TestingHelper.checkOperationError(err, done);
+        }
+    }
+
+    static executeExample(name, outputChecker) {
+        return new Promise((resolve, reject) => {
+                exec('node ' + name, (error, stdout, stderr) => {
+                    TestingHelper.logDebug(stdout);
+                    resolve(stdout);
+                })
+            }).
+            then(output => {
+                expect(output).not.toMatch('ERROR:');
+                expect(output).toMatch('Client is started');
+            });
+    }
+
+    static checkOperationError(error, done) {
+        TestingHelper.checkError(error, Errors.OperationError, done)
+    }
+
+    static checkIllegalArgumentError(error, done) {
+        TestingHelper.checkError(error, Errors.IgniteClientError, done)
+    }
+
+    static checkError(error, errorType, done) {
+        if (!(error instanceof errorType)) {
+            done.fail('unexpected error: ' + error);
+        }
+    }
+
+    static logDebug(message) {
+        if (config.debug) {
+            console.log(message);
+        }
+    }
+
+    static printValue(value) {
+        const val = Util.inspect(value, false, null);
+        const length = 500;
+        return val.length > length ? val.substr(0, length) + '...' : val;
+    }
+
+    static async compare(value1, value2) {
+        TestingHelper.logDebug(Util.format('compare: %s and %s', 
TestingHelper.printValue(value1), TestingHelper.printValue(value2)));
+        if (value1 === undefined || value2 === undefined) {
+            TestingHelper.logDebug(Util.format('compare: unexpected 
"undefined" value'));
+            return false;
+        }
+        if (value1 === null && value2 === null) {
+            return true;
+        }
+        if (value1 === null && value2 !== null || value1 !== null && value2 
=== null) {
+            return false;
+        }
+        if (typeof value1 !== typeof value2) {
+            TestingHelper.logDebug(Util.format('compare: value types are 
different: %s and %s',
+                typeof value1, typeof value2));
+            return false;
+        }
+        if (typeof value1 === 'number') {
+            return floatComparator(value1, value2);
+        }
+        else if (typeof value1 !== 'object') {
+            return defaultComparator(value1, value2);
+        }
+        else if (value1.constructor.name !== value2.constructor.name && 
!value2 instanceof BinaryObject) {
+            TestingHelper.logDebug(Util.format('compare: value types are 
different: %s and %s',
+                value1.constructor.name, value2.constructor.name));
+            return false;
+        }
+        else if (value1 instanceof Date && value2 instanceof Date) {
+            return dateComparator(value1, value2);
+        }
+        else if (value1 instanceof EnumItem && value2 instanceof EnumItem) {
+            return enumComparator(value1, value2);
+        }
+        else if (value1 instanceof Decimal && value2 instanceof Decimal) {
+            return decimalComparator(value1, value2);
+        }
+        else if (value1 instanceof Timestamp && value2 instanceof Timestamp) {
+            return timestampComparator(value1, value2);
+        }
+        else if (value1 instanceof Array && value2 instanceof Array) {
+            if (value1.length !== value2.length) {
+                TestingHelper.logDebug(Util.format('compare: array lengths are 
different'));
+                return false;
+            }
+            for (var i = 0; i < value1.length; i++) {
+                if (!await TestingHelper.compare(value1[i], value2[i])) {
+                    TestingHelper.logDebug(Util.format('compare: array 
elements are different: %s, %s',
+                        TestingHelper.printValue(value1[i]), 
TestingHelper.printValue(value2[i])));
+                    return false;
+                }
+            }
+            return true;
+        }
+        else if (value1 instanceof Map && value2 instanceof Map) {
+            if (value1.size !== value2.size) {
+                TestingHelper.logDebug(Util.format('compare: map sizes are 
different'));
+                return false;
+            }
+            for (var [key, val] of value1) {
+                if (!value2.has(key)) {
+                    TestingHelper.logDebug(Util.format('compare: maps are 
different: %s key is absent', TestingHelper.printValue(key)));
+                    return false;
+                }
+                if (!(await TestingHelper.compare(val, value2.get(key)))) {
+                    TestingHelper.logDebug(Util.format('compare: map values 
are different: %s, %s',
+                        TestingHelper.printValue(val), 
TestingHelper.printValue(value2.get(key))));
+                    return false;
+                }
+            }
+            return true;
+        }
+        else if (value1 instanceof Set && value2 instanceof Set) {
+            if (value1.size !== value2.size) {
+                TestingHelper.logDebug(Util.format('compare: set sizes are 
different'));
+                return false;
+            }
+            const value1Arr = [...value1].sort();
+            const value2Arr = [...value2].sort();
+            if (!await TestingHelper.compare(value1Arr, value2Arr)) {
+                TestingHelper.logDebug(Util.format('compare: sets are 
different: %s and %s',
+                    TestingHelper.printValue(value1Arr), 
TestingHelper.printValue(value2Arr)));
+                return false;
+            }
+            return true;
+        }
+        else if (value2 instanceof BinaryObject) {
+            if (value1 instanceof BinaryObject) {
+                if (value1.getTypeName() !== value2.getTypeName()) {
+                    TestingHelper.logDebug(Util.format('compare: binary object 
type names are different'));
+                    return false;
+                }
+                if (!await TestingHelper.compare(value1.getFieldNames(), 
value2.getFieldNames())) {
+                    TestingHelper.logDebug(Util.format('compare: binary object 
field names are different'));
+                    return false;
+                }
+                for (let fieldName of value1.getFieldNames()) {
+                    if (!value1.hasField(fieldName) || 
!value2.hasField(fieldName) ||
+                        !await TestingHelper.compare(await 
value1.getField(fieldName), await value1.getField(fieldName))) {
+                        TestingHelper.logDebug(Util.format('compare: binary 
objects field "%s" values are different', fieldName));
+                        return false;
+                    }
+                }
+                return true;
+            }
+            else {
+                let value;
+                for (let key of Object.keys(value1)) {
+                    value = await value2.getField(key);
+                    if (!(await TestingHelper.compare(value1[key], value))) {
+                        TestingHelper.logDebug(Util.format('compare: binary 
object values for key %s are different: %s and %s',
+                            TestingHelper.printValue(key), 
TestingHelper.printValue(value1[key]), TestingHelper.printValue(value)));
+                        return false;
+                    }
+                }
+                return true;
+            }
+        }
+        else {
+            for (let key of Object.keys(value1)) {
+                if (!(await TestingHelper.compare(value1[key], value2[key]))) {
+                    TestingHelper.logDebug(Util.format('compare: object values 
for key %s are different: %s and %s',
+                        TestingHelper.printValue(key), 
TestingHelper.printValue(value1[key]), TestingHelper.printValue(value2[key])));
+                    return false;
+                }
+            }
+            return true;
+        }
+    }    
+}
+
+module.exports = TestingHelper;

Reply via email to