@vulcan-sql/extension-driver-ksqldb
Version:
ksqlDB driver for VulcanSQL
173 lines • 6.88 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.RestfulClient = void 0;
const tslib_1 = require("tslib");
const http2 = require("http2");
const RESTFUL_API = {
INFO: '/info',
KSQL: '/ksql',
QUERY: '/query',
};
const DEFAULT_OPTIONS = {
host: 'http://localhost:8088',
timeout: 25000,
};
class RestfulClient {
constructor(options) {
this.connected = false;
this.startSession = () => null;
this.options = options;
this.connect();
}
/**
* The connect method will create a promise "startSession" method, not really to connect http2 immediately.
* To let users establish a "startSession" promise request only when they need to query or exec a statement.
*/
connect() {
this.startSession = () => new Promise((resolve, reject) => {
this.client = http2.connect(this.options.host || DEFAULT_OPTIONS.host, {
timeout: this.options.timeout || DEFAULT_OPTIONS.timeout,
});
this.client.setTimeout(this.options.timeout || DEFAULT_OPTIONS.timeout, () => {
var _a;
if (this.connected === false) {
const timeoutError = new Error("Connection timeout.");
reject(timeoutError);
(_a = this.client) === null || _a === void 0 ? void 0 : _a.destroy(timeoutError);
}
});
this.client.on('connect', () => {
this.connected = true;
resolve();
});
this.client.on('error', (error) => {
reject(error);
});
});
}
closeSession() {
return new Promise((resolve, reject) => {
if (this.client) {
!this.client.destroyed && this.client.destroy();
this.connected = false;
resolve();
}
else {
reject(new Error('Client is not initialized'));
}
});
}
checkConnection() {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
try {
const res = yield this.request(RESTFUL_API.INFO, 'GET');
return res.KsqlServerInfo['serverStatus'];
}
catch (e) {
if (e.error_code) {
throw new Error(JSON.stringify(e));
}
else {
throw new Error('KsqlDb server is not ready');
}
}
});
}
checkConnectionRunning() {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
const status = yield this.checkConnection();
const isRunning = status === 'RUNNING';
return isRunning;
});
}
/**
* According to ksqldb restful API: https://docs.ksqldb.io/en/latest/developer-guide/ksqldb-rest-api/query-endpoint
* To run a SELECT statement and stream back the results.
* SELECT statement: https://docs.ksqldb.io/en/latest/developer-guide/ksqldb-reference/select-pull-query
*/
query({ query, query_params = {}, }) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
// bind query parameters
const ksql = this.bindParams(query, query_params);
const buffer = Buffer.from(JSON.stringify({ ksql }));
const res = yield this.request(RESTFUL_API.QUERY, 'POST', buffer);
return res;
});
}
/**
* According to ksqldb restful API: https://docs.ksqldb.io/en/latest/developer-guide/ksqldb-rest-api/ksql-endpoint
* All statements, except those starting with SELECT and PRINT, can be run on this exec method.
* To run SELECT and PRINT statements use the "query" method instead.
*/
exec({ query, query_params = {}, }) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
// bind query parameters
const ksql = this.bindParams(query, query_params);
const buffer = Buffer.from(JSON.stringify({ ksql }));
const res = yield this.request(RESTFUL_API.KSQL, 'POST', buffer);
return res;
});
}
bindParams(query, query_params) {
const values = Object.values(query_params);
// replace the parameterized placeholder to values
const ksql = query.replace(/\$(\d+)/g, (_, index) => {
const valueIndex = parseInt(index) - 1;
const paramValue = values[valueIndex];
// Because the ksqldb queries are expressed using a strict subset of ANSI SQL.
// It didn't support the string auto conversion, so we need to add the single quote for string value.
return typeof paramValue === 'string' ? `'${paramValue}'` : paramValue;
});
return ksql;
}
request(path, method, buffer) {
return tslib_1.__awaiter(this, void 0, void 0, function* () {
this.startSession && (yield this.startSession());
return new Promise((resolve, reject) => {
const config = {
'content-type': 'application/vnd.ksql.v1+json',
':method': method,
':path': path,
};
// add authorization if username and password is provided
if (this.options.username && this.options.password) {
config['authorization'] = `Basic ${Buffer.from(`${this.options.username}:${this.options.password}`).toString('base64')}`;
}
const req = this.client.request(config);
req.setEncoding('utf-8');
let status = null;
req.on('response', (headers) => {
status = headers[':status'];
});
let data = '';
req.on('data', (chunk) => {
data += chunk;
});
req.on('end', () => {
let responseData = data;
try {
responseData = JSON.parse(data);
}
catch (e) {
responseData = data;
}
if (status === 200) {
resolve(responseData);
}
else {
reject(responseData);
}
this.closeSession();
});
req.on('error', (error) => {
reject(error);
this.closeSession();
});
buffer && req.write(buffer);
req.end();
});
});
}
}
exports.RestfulClient = RestfulClient;
//# sourceMappingURL=restfulClient.js.map