@senx/warp10
Version:
Warp 10 NodeJS library
373 lines (372 loc) • 14.9 kB
JavaScript
/*
* Copyright 2020-2023 SenX S.A.S.
*
* Licensed 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.
*/
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Warp10 = exports.TimeUnits = void 0;
const dayjs_1 = __importDefault(require("dayjs"));
const utc_1 = __importDefault(require("dayjs/plugin/utc"));
const url_1 = require("url");
const https_1 = __importDefault(require("https"));
const http_1 = __importDefault(require("http"));
const logger_1 = require("./logger");
dayjs_1.default.extend(utc_1.default);
var TimeUnits;
(function (TimeUnits) {
TimeUnits[TimeUnits["US"] = 1000] = "US";
TimeUnits[TimeUnits["MS"] = 1] = "MS";
TimeUnits[TimeUnits["NS"] = 1000000] = "NS";
})(TimeUnits || (exports.TimeUnits = TimeUnits = {}));
/**
*
*/
class Warp10 {
/**
* Create new Warp 10 connector.
*
* @param params - \{ endpoint: string; debug?: boolean; silent?: boolean, timeUnit: TimeUnits \}
* endpoint - Warp 10 endpoint, without <code>/api/v0</code> at the end.
* debug - Enable debug
* silent - Do not produce logs
* timeUnit - Platform timeUnit @see TimeUnits
* headers - custom HTTP headers
* timeout - http Timeout
*
* @example
* ```
* // standard constructor
* const w10 = new Warp10({endpoint: 'https://sandbox.senx.io'});
* // builder pattern
* const w10 = new Warp10().endpoint('https://sandbox.senx.io').timeUnit(TimeUnits.US);
* ```
*/
constructor(params) {
var _a, _b;
this._headers = {};
this._timeout = 0;
this.LOG = new logger_1.Logger(Warp10, params === null || params === void 0 ? void 0 : params.debug, params === null || params === void 0 ? void 0 : params.silent);
if ((params === null || params === void 0 ? void 0 : params.endpoint) != null)
this.endpoint(params.endpoint);
this.timeUnit((_a = params === null || params === void 0 ? void 0 : params.timeUnit) !== null && _a !== void 0 ? _a : TimeUnits.US);
this.headers((_b = params === null || params === void 0 ? void 0 : params.headers) !== null && _b !== void 0 ? _b : {});
if ((params === null || params === void 0 ? void 0 : params.timeout) != null) {
this.timeout(params === null || params === void 0 ? void 0 : params.timeout);
}
this.LOG.debug(['constructor'], params);
}
endpoint(endpoint) {
if (endpoint == null)
throw new Error('Endpoint is mandatory');
// remove trailing slash if any
this.url = (endpoint !== null && endpoint !== void 0 ? endpoint : '').replace(/\/+$/, '');
this.client = this.url.startsWith('https') ? https_1.default : http_1.default;
this._endpoint = new url_1.URL(endpoint !== null && endpoint !== void 0 ? endpoint : '');
return this;
}
headers(headers) {
this._headers = headers !== null && headers !== void 0 ? headers : {};
return this;
}
debug(debug) {
this.LOG.isDebug = debug;
return this;
}
silent(silent) {
this.LOG.silent = silent;
return this;
}
timeUnit(timeUnit) {
this._timeUnit = timeUnit !== null && timeUnit !== void 0 ? timeUnit : TimeUnits.US;
return this;
}
timeout(to) {
if (to) {
this._timeout = to;
}
return this;
}
async send(options, data) {
this.LOG.debug(['send'], { options, data });
let body = '';
if (!this.client)
throw new Error('Warp10Lib is misconfigured, probably a wrong ort missing endpoint value.');
return new Promise((resolve, reject) => {
const req = this.client.request(options, (res) => {
res.on("data", (chunk) => body += chunk);
res.on("error", (err) => reject(err));
res.on("end", () => {
try {
this.LOG.debug(['send', 'result'], { body, headers: res.headers });
resolve({ body, headers: res.headers });
}
catch (err) {
reject(err);
}
});
});
req.on("error", (err) => {
this.LOG.error(['send', 'error'], err);
reject(err);
});
req.on('timeout', (err) => {
this.LOG.warn(['send', 'timeout'], err);
req.abort();
reject(err);
});
req.on('uncaughtException', (err) => {
this.LOG.error(['send', 'uncaughtException'], err);
req.abort();
reject(err);
});
if (data) {
req.write(data);
}
req.end(() => {
// end the request to prevent ECONNRESET and socket hung errors
});
});
}
/**
* Build got request options from defined options
*
* @param path - request path
* @param method - request method
* @param warpToken - the X-Warp10-Token, if any
*/
getOptions(path, method = 'GET', warpToken) {
if (!this._endpoint)
throw new Error('Missing endpoint');
const opts = {
hostname: this._endpoint.hostname,
port: this._endpoint.port,
path,
method,
headers: {
'Content-Type': 'text/plain; charset=UTF-8',
'X-Warp10-Token': warpToken || '',
...this._headers
}
};
if (this._timeout > 0)
opts.bodyTimeout = this._timeout;
return opts;
}
/**
* Execute a WarpScript against a Warp 10 instance
*
* @param warpScript - WarpScript to execute
*
* @example
* ```
* // Prints "[4]":
* console.log(await w10.exec('2 2 +'))
* ```
*/
async exec(warpScript) {
var _a, _b, _c;
const { headers, body } = await this.send(this.getOptions(`/api/v0/exec`, 'POST'), warpScript);
return {
result: JSON.parse(body),
meta: {
elapsed: parseInt(((_a = headers['x-warp10-elapsed']) !== null && _a !== void 0 ? _a : ['0'])[0], 10),
ops: parseInt(((_b = headers['x-warp10-ops']) !== null && _b !== void 0 ? _b : ['0'])[0], 10),
fetched: parseInt(((_c = headers['x-warp10-fetched']) !== null && _c !== void 0 ? _c : ['0'])[0], 10)
}
};
}
/**
* Fetch data against a Warp 10 instance
*
* @param readToken - Read token
* @param className - ClassName, could be a regexp starting with '\~' (ie: '~io.warp10.*' )
* @param labels - Labels key value map. Could be a regexp starting with '\~' (ie: \{ 'myLabel': '~sensor_.*' \} )
* @param start - ISO8601 UTC Date
* @param stop - ISO8601 UTC Date if 'start' is a ISO8601 date. Timespan (in platform timeunit format) if 'start' is a timestamp
* @param format - Output format: text' | 'fulltext' | 'json' | 'tsv' | 'fulltsv' | 'pack' | 'raw' | 'formatted', default is 'formatted'
* @param dedup - Deduplicates data (default is true)
*
* @example
* ```
* // fetch raw data between 2 dates
* console.log(await w10.fetch(readToken, '~io.warp10.*', {}, '2019-11-11T12:34:43.388409Z', '2019-11-21T12:34:43.388409Z', 'json'));
*
* // Fetch data with a time span
* console.log(await w10.fetch(readToken, '~.*', {}, '2019-11-21T12:34:43.388409Z', 86400000000 * 5));
* ```
*/
async fetch(readToken, className, labels, start, stop, format = 'formatted', dedup = true) {
var _a, _b, _c, _d;
const params = new url_1.URLSearchParams([]);
params.set('selector', encodeURIComponent(className) + this.formatLabels(labels));
params.set('format', format === 'formatted' ? 'json' : format);
params.set('dedup', '' + dedup);
if (typeof stop === 'string') {
params.set('start', start);
params.set('stop', stop);
}
if (typeof stop === 'number') {
params.set('now', start);
params.set('timespan', '' + stop);
}
const { headers, body } = await this.send(this.getOptions(`/api/v0/fetch?${params.toString()}`, 'GET', readToken));
let result;
switch (format) {
case "json":
result = JSON.parse(body);
break;
case "formatted":
result = this.formatGTS((_a = JSON.parse(body)) !== null && _a !== void 0 ? _a : []);
break;
default:
result = body.split('\n');
}
return {
result,
meta: {
elapsed: parseInt(((_b = headers['x-warp10-elapsed']) !== null && _b !== void 0 ? _b : ['0'])[0], 10),
ops: parseInt(((_c = headers['x-warp10-ops']) !== null && _c !== void 0 ? _c : ['0'])[0], 10),
fetched: parseInt(((_d = headers['x-warp10-fetched']) !== null && _d !== void 0 ? _d : ['0'])[0], 10)
}
};
}
/**
* Update datapoints
*
* @param writeToken - Write token
* @param dataPoints
*
* @example
* ```
* console.log(await w10.update(writeToken, [
* {timestamp: moment.utc().valueOf() * 1000, className: 'io.warp10.test', labels: {key: 'value'}, value: 54},
* '1380475081000000// io.warp10.test{key=value} T',
* '1566893344654882/48.81:-4.147/124 io.warp10.test{key=value} [8.2 151 152 1568189745655509/40.6:-74/14 ]',
* ]));
* ```
*/
async update(writeToken, dataPoints) {
const payload = dataPoints.map(d => {
var _a, _b, _c;
let pos = '';
if (typeof d === 'string') {
return d;
}
else {
if (d.lat != null && d.lng != null) {
pos = `${d.lat}:${d.lng}`;
}
return `${(_a = d.timestamp) !== null && _a !== void 0 ? _a : dayjs_1.default.utc().valueOf() * ((_b = this._timeUnit) !== null && _b !== void 0 ? _b : TimeUnits.US).valueOf()}/${pos}/${(_c = d.elev) !== null && _c !== void 0 ? _c : ''} ${d.className}${this.formatLabels(d.labels)} ${Warp10.formatValues(d.value)}`;
}
});
const opts = this.getOptions(`/api/v0/update`, 'POST', writeToken);
const { body } = await this.send(opts, payload.join('\n'));
return { response: body, count: payload.length };
}
/**
*
* @param deleteToken - Delete token
* @param className - ClassName, could be a regexp starting with '\~' (ie: '~io.warp10.*' )
* @param labels - Labels key value map. Could be a regexp starting with '\~' (ie: \{ 'myLabel': '~sensor_.*' \} )
* @param start - ISO8601 UTC Date
* @param end - ISO8601 UTC Date
* @param deleteAll - Default is 'false'
*
* @example
* ```
* // delete data between 2 dates
* console.log(await w10.delete(deleteToken, '~io.warp10.test*', {key: 'value'}, '2019-11-11T12:34:43.388409Z', '2019-11-21T12:34:43.388409Z'));
*
* // delete all
* console.log(await w10.delete(deleteToken, '~io.warp10.test*', {key: 'value'}, '', '', true));
* ```
*
*/
async delete(deleteToken, className, labels, start, end, deleteAll = false) {
const params = new url_1.URLSearchParams([]);
params.set('selector', encodeURIComponent(className) + this.formatLabels(labels));
if (deleteAll) {
params.set('deleteall', '' + true);
}
else {
let startM = dayjs_1.default.utc(start);
let endM = dayjs_1.default.utc(end);
if (startM.isAfter(endM)) {
startM = dayjs_1.default.utc(end);
endM = dayjs_1.default.utc(start);
}
params.set('start', (startM.valueOf() * 1000) + '');
params.set('end', (endM.valueOf() * 1000) + '');
}
const { body } = await this.send(this.getOptions(`/api/v0/delete?${params.toString()}`, 'GET', deleteToken));
return { result: body };
}
/**
* Update Meta
* @param writeToken - Write token
* @param meta - Metadata key value map to update
*
* @example
* ```
* // write meta
* console.log(await w10.meta(writeToken, [{
* className: 'io.warp10.test',
* labels: {key: 'value'},
* attributes: {attr: 'value'}
* }]));
* ```
*/
async meta(writeToken, meta) {
const payload = meta.map(m => encodeURIComponent(m.className) + this.formatLabels(m.labels) + this.formatLabels(m.attributes));
const { body } = await this.send(this.getOptions(`/api/v0/meta`, 'POST', writeToken), payload.join('\n'));
return { response: body, count: payload.length };
}
formatLabels(labels) {
return `{${Object.keys(labels !== null && labels !== void 0 ? labels : {}).map(k => `${k}=${encodeURIComponent(`${labels[k]}`)}`)}}`;
}
static formatValues(value) {
return (typeof value === 'string') ? `'${encodeURIComponent(value)}'` : value;
}
formatGTS(gtsList) {
const res = [];
const size = (gtsList !== null && gtsList !== void 0 ? gtsList : []).length;
for (let i = 0; i < size; i++) {
const gts = gtsList[i];
const data = [];
const vSize = gts.v.length;
for (let j = 0; j < vSize; j++) {
const dp = {
ts: gts.v[j][0],
value: gts.v[j][gts.v[j].length - 1]
};
if (gts.v[j].length > 3) {
dp.loc = {
lat: gts.v[j][1],
long: gts.v[j][2]
};
}
if (gts.v[j].length > 4) {
dp.elev = gts.v[j][3];
}
data.push(dp);
}
res.push({ name: gts.c, labels: gts.l, attributes: gts.a, data });
}
return res;
}
}
exports.Warp10 = Warp10;