UNPKG

@ultipa-graph/ultipa-driver

Version:

NodeJS SDK for ultipa-server 5.2

530 lines 21.6 kB
"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