@ultipa-graph/ultipa-driver
Version:
NodeJS SDK for ultipa-server 5.2
530 lines • 21.6 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ConnectionBase = exports.RAFT_GLOBAL = void 0;
const md5_1 = __importDefault(require("md5"));
const grpc = __importStar(require("@grpc/grpc-js"));
const ultipa_pb_1 = require("../../proto/ultipa_pb");
const utils_1 = require("../../utils");
const network_manager_1 = require("../network.manager");
const types_1 = require("../../types/types");
const types_extra_1 = require("../../types/types.extra");
const moment_1 = __importDefault(require("moment"));
const chalk_1 = __importDefault(require("chalk"));
let { CommandList } = utils_1.UQLMAKER;
function rpc_timezone(timezone, timezoneOffset) {
if (!!timezone) {
return {
tz: timezone
};
}
if (!!timezoneOffset) {
return {
tz_offset: timezoneOffset
};
}
return {
tz_offset: 0
};
}
/**
* GrpcClientInfo Class
* Used to manage basic information and metadata required for gRPC client connections
*
* Main responsibilities:
* 1. Store connection information (host address, username, password, certificate)
* 2. Generate metadata required for gRPC calls
* 3. Get/cache gRPC client instances
*/
class GrpcClientInfo {
host;
username;
password;
crt;
constructor(host, username, password, crt) {
this.host = host.replace("https://", "").replace("http://", "");
this.username = username;
this.password = password;
this.crt = crt;
}
getMetadata(graphSetName, timezone, timezoneOffset) {
let metadata = new grpc.Metadata();
metadata.add("user", this.username);
metadata.add("password", this.password);
metadata.add("graph_name", graphSetName);
let tzObj = rpc_timezone(timezone, timezoneOffset);
for (const key in tzObj) {
metadata.add(key, tzObj[key]);
}
return metadata;
}
getClient() {
return network_manager_1.grpcNetworkManager.getUltipaRpcsClient(this.host, this.crt);
}
}
exports.RAFT_GLOBAL = "global";
/**
* ConnectionBase Class
*
* Base connection class that provides core functionality for communicating with Ultipa database
*
* Main features:
* - Manages gRPC client connections
* - Contains query base api uql() & gql()
* - Provides connection testing capabilities
*/
class ConnectionBase {
grpcClintInfo;
host;
username;
password;
crt;
defaultConfig;
constructor(host, username, password, crt, defaultConfig, isMd5) {
if (!isMd5) {
password = (0, md5_1.default)(password).toUpperCase();
}
this.host = host;
this.username = username;
this.password = password;
this.crt = crt;
this.setDefaultConfig(defaultConfig);
this.grpcClintInfo = new GrpcClientInfo(host, this.username, this.password, this.crt);
}
setDefaultConfig(config) {
if (!this.defaultConfig) {
this.defaultConfig = {
hosts: [this.host],
defaultGraph: "default",
timeout: 2147483647,
username: this.username,
password: this.password,
};
}
if (config?.defaultGraph) {
this.defaultConfig.defaultGraph = config?.defaultGraph;
}
if (config?.timeout !== undefined) {
this.defaultConfig.timeout = config?.timeout;
}
}
getGraphSetName(currentGraphName, uql, isGlobal) {
if (isGlobal) {
return exports.RAFT_GLOBAL;
}
// if (uql) {
// let uqlParse = new EasyUqlParse(uql)
// if (uqlParse.isGlobal()) {
// return RAFT_GLOBAL;
// }
// // if (
// // [
// // CommandList.truncate, CommandList.mountGraph,
// // CommandList.unmountGraph,
// // ].includes(`${uqlParse.firstCommandName()}().${uqlParse.secondCommandName()}`)
// // ) {
// // let graphName = uqlParse.getCommand(1)?.safelyGetFirstParams()
// // if (!!graphName) {
// // return graphName
// // }
// // }
// }
return currentGraphName || this.defaultConfig.defaultGraph;
}
getTimeout(timeout) {
return timeout || this.defaultConfig.timeout;
}
async getClientInfo(params) {
let { graphSetName, isGlobal, timezone, timezoneOffset, useHost } = params;
let goGraphName = this.getGraphSetName(graphSetName, params.uql, isGlobal);
let metadata = this.grpcClintInfo.getMetadata(goGraphName, timezone, timezoneOffset);
let clientToUse = this.grpcClintInfo;
if (useHost) {
clientToUse = new GrpcClientInfo(useHost, this.username, this.password, this.crt);
}
return {
client: clientToUse.getClient(),
host: useHost || this.grpcClintInfo.host,
metadata,
goGraphName,
};
}
async auth(authType, queryText, config) {
return new Promise(async (resolve, reject) => {
let authReq = new ultipa_pb_1.AuthenticateRequest();
authReq.setType(authType);
if (queryText) {
authReq.setQueryText(queryText);
}
let clientInfo = await this.getClientInfo({
graphSetName: config?.graph,
timezone: config?.timezone,
timezoneOffset: config?.timezoneOffset,
});
clientInfo.client.controlsClient.authenticate(authReq, clientInfo.metadata, (err, resp) => {
let response = new types_1.Response();
if (err) {
response = utils_1.FormatResponse.unknownError(err, response);
resolve(response);
return;
}
response.status = utils_1.FormatType.status(resp.getStatus());
resolve(response);
});
});
}
/**
* Login
*/
async login(config) {
network_manager_1.grpcNetworkManager.removeUltipaRpcsClient(this.grpcClintInfo.host, this.crt);
return new Promise(async (resolve, reject) => {
let v = await this.test_has_message(config);
if (v.connect) {
resolve(true);
}
else {
reject(v.msg);
}
});
}
/**
* Test connect
*/
async test() {
let v = await this.test_has_message();
return v.connect;
}
/**
* Test message
*/
async test_has_message(config) {
let createResult = (connect, msg) => {
return {
connect,
msg,
};
};
if (config?.host.includes("UNAVAILABLE")) {
await (0, utils_1.sleepSeconds)(3);
return createResult(false, `14 UNAVAILABLE: Name resolution failed for target dns: ${config?.host}`);
}
return new Promise(async (resolve, reject) => {
let clientInfo = await this.getClientInfo({
isGlobal: true,
useHost: config?.host,
timezone: config?.timezone,
timezoneOffset: config?.timezoneOffset,
});
let name = "test";
let req = new ultipa_pb_1.HelloUltipaRequest();
req.setName(name);
clientInfo.client.controlsClient.sayHello(req, clientInfo.metadata, (err, res) => {
if (err) {
resolve(createResult(false, err.message || err.details));
return;
}
let msg = res.getMessage();
if (msg != `${name} Welcome To Ultipa!`) {
resolve(createResult(false, res.getStatus().getMsg()));
return;
}
resolve(createResult(true, "ok"));
});
});
}
/**
* Executes a UQL query on the current graphset or the database and returns the result.
* @param uql
* @param config
* @returns Response
*/
async uql(uql, config) {
try {
return await this._query_without_catch(uql, types_1.QueryType.UQL, config);
}
catch (error) {
return utils_1.FormatResponse.catchUltipaUqlError(error);
}
}
/**
* Executes a UQL query on the current graphset or the database and returns the result incrementally, allowing handling of large datasets without loading everything into memory at once.
* @param uql
* @param cb QueryResponseListener
* @param config
*/
async uqlStream(uql, cb, config) {
await new Promise(async (resolve, reject) => {
const originalCb = cb;
const wrappedCb = {
onData: async (data) => {
try {
await originalCb.onData(data);
}
catch (error) {
reject(error);
}
},
onEnd: () => {
originalCb.onEnd();
resolve();
},
onClose: () => {
resolve();
originalCb.onClose?.();
},
onError: (err) => {
reject(err);
originalCb.onError?.(err);
},
onPause: () => {
originalCb.onPause?.();
},
onReadable: () => {
originalCb.onReadable?.();
},
onResume: () => {
originalCb.onResume?.();
},
onStart: () => {
originalCb.onStart?.();
},
};
await this._query_without_catch(uql, types_1.QueryType.UQL, config, wrappedCb);
});
}
/**
* Executes a GQL query on the current graphset or the database and returns the result.
* @param gql
* @param config
* @returns Response
*/
async gql(gql, config) {
try {
return await this._query_without_catch(gql, types_1.QueryType.GQL, config);
}
catch (error) {
return utils_1.FormatResponse.catchUltipaUqlError(error);
}
}
/**
* Executes a GQL query on the current graphset or the database and returns the result incrementally, allowing handling of large datasets without loading everything into memory at once.
* @param gql
* @param stream
* @param config
*/
async gqlStream(gql, cb, config) {
await new Promise(async (resolve, reject) => {
const originalCb = cb;
const wrappedCb = {
onData: async (data) => {
try {
await originalCb.onData(data);
}
catch (error) {
reject(error);
}
},
onEnd: () => {
originalCb.onEnd();
resolve();
},
onClose: () => {
resolve();
originalCb.onClose?.();
},
onError: (err) => {
reject(err);
originalCb.onError?.(err);
},
onPause: () => {
originalCb.onPause?.();
},
onReadable: () => {
originalCb.onReadable?.();
},
onResume: () => {
originalCb.onResume?.();
},
onStart: () => {
originalCb.onStart?.();
},
};
await this._query_without_catch(gql, types_1.QueryType.GQL, config, wrappedCb);
});
}
/**
* Execute UQL
*/
async _query_without_catch(queryText, ql, config, cb, forceRefresh) {
if (this.constructor.name === 'UltipaDriver' && this.connectionPool?.getActive) {
const activeConnection = await this.connectionPool.getActive();
if (activeConnection.constructor.name !== 'UltipaDriver') {
return await activeConnection._query_without_catch(queryText, ql, config, cb, forceRefresh);
}
}
return new Promise(async (resolve, reject) => {
try {
let _TIME_MAP = {
main: `${queryText.substr(0, 20) + (queryText.length > 20 ? "..." : "")}`,
getClientInfo: "Get client info",
queryText: "Execute UQL",
};
let timeRecordManager = new utils_1.TimeRecordManager(_TIME_MAP.main);
let response = new types_1.Response();
let onErrorCache_go_on_exec_onEnd = null;
let request = new ultipa_pb_1.QueryRequest();
request.setQueryText(queryText);
if (config?.logQueryText) {
console.log('qyeryText: ' + queryText);
}
request.setGraphName(config?.graph || this.defaultConfig.defaultGraph);
request.setQueryType(ql);
request.setTimeout(this.getTimeout(config?.timeout));
request.setThreadNum(config?.thread || 0);
if (config?.timezoneOffset !== undefined) {
const offsetSeconds = utils_1.UltipaDatetime.parseTimezoneOffsetToSeconds(String(config.timezoneOffset));
request.setTzOffset(String(offsetSeconds));
}
else {
request.setTz(config?.timezone || moment_1.default.tz.guess());
}
timeRecordManager.start(_TIME_MAP.getClientInfo);
let clientInfo = await this.getClientInfo({
useHost: config?.host ? config?.host : this.defaultConfig.hosts[0],
graphSetName: request?.getGraphName(),
uql: request.getQueryText(),
timezone: request?.getTz(),
timezoneOffset: request?.getTzOffset(),
});
timeRecordManager.stop(_TIME_MAP.getClientInfo);
timeRecordManager.start(_TIME_MAP.queryText);
let { goGraphName, metadata, host } = clientInfo;
let getDateString = () => {
return `${(0, moment_1.default)().format("YYYY-DD-MM HH:mm:ss.SSS")} <${chalk_1.default.green(this.username)}>`;
};
if (config?.logQueryText) {
console.log(`${getDateString()} SDK_LOG: [${host}-${goGraphName}] ${chalk_1.default.green(queryText)} START! ${forceRefresh ? "Force Refresh!" : ""}`);
}
let uqlParse = new utils_1.EasyUqlParse(queryText);
let isUseUqlExtra = uqlParse.isExtra();
let stream = isUseUqlExtra
? clientInfo.client.controlsClient.queryEx(request, metadata)
: clientInfo.client.rpcsClient.query(request, metadata);
let dataCustomDeal = utils_1.streamHelper.isUseStream(cb);
let addReq = () => {
if (config?.withRequestInfo) {
response.requestInfo = {
graphName: goGraphName,
uql: queryText,
host,
isUseUqlExtra,
timeRecord: timeRecordManager.toString()
};
}
};
let dealReq = dataCustomDeal
? cb
: {
package_limit: cb?.package_limit || 0,
onData: (_res) => {
if (!response.status) {
response = _res;
}
else {
// If current response is error, or there was an error response before, maintain error status
if (_res.status.code !== types_1.ErrorCode.SUCCESS || response.status?.code !== types_1.ErrorCode.SUCCESS) {
if (response.status?.code === types_1.ErrorCode.SUCCESS) {
response = _res; // If previous status was success, replace with new error response
}
}
else {
response = (0, utils_1.mergeQueryResponse)(response, _res);
}
}
},
onEnd: async () => {
if (onErrorCache_go_on_exec_onEnd) {
return;
}
timeRecordManager.stop(_TIME_MAP.queryText);
timeRecordManager.stopTotal();
addReq();
// response.status = FormatResponse.successStatus();
if (config?.logQueryText) {
console.log(`${getDateString()} SDK_LOG: [${host}-${goGraphName}] ${chalk_1.default.green(queryText)} END! ${(response.status?.code === types_1.ErrorCode.SUCCESS ? "✅" : "❌")} ${JSON.stringify(response.status)}`);
}
resolve(response);
},
onError: async (err) => {
addReq();
// resolve here,and onData should be stop
onErrorCache_go_on_exec_onEnd = err;
stream.cancel();
let e = err;
if (e.code === 14 && forceRefresh) {
await (0, utils_1.sleepSeconds)(3);
let res = await this._query_without_catch(queryText, ql, config, cb, true);
resolve(res);
return;
}
response = utils_1.FormatResponse.unknownError(err, response);
resolve(response);
},
};
utils_1.streamHelper.commonDeal(stream, dealReq, (_res) => {
let res = _res;
return utils_1.FormatType.queryResponse(res, { timezone: config?.timezone, timezoneOffset: config?.timezoneOffset });
});
if (dataCustomDeal) {
resolve(null);
}
}
catch (error) {
reject(error);
}
});
}
async uqlSingle(uqlMaker) {
let res = await this.uql(uqlMaker.toString(), uqlMaker.commonParams);
return res;
}
async uqlResponse(uqlMaker, config) {
return await this.uql(uqlMaker.toString(), config);
}
async uqlJobResponse(uqlMaker, config) {
let res = await this.uql(uqlMaker.toString(), config);
return {
status: utils_1.FormatResponse.successStatus(),
statisics: res.statistics,
jobId: res.items[types_extra_1.ResponseTableName.JOB_RESULT]?.asTable()?.toKV()?.pop().new_job_id
};
}
}
exports.ConnectionBase = ConnectionBase;
//# sourceMappingURL=connection.base.js.map