UNPKG

@senx/warp10

Version:

Warp 10 NodeJS library

373 lines (372 loc) 14.9 kB
/* * 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. */ 'use strict'; 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;