UNPKG

arangojs

Version:

The official ArangoDB JavaScript driver.

273 lines 10.8 kB
"use strict"; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) if (e.indexOf(p[i]) < 0) t[p[i]] = s[p[i]]; return t; }; Object.defineProperty(exports, "__esModule", { value: true }); const querystring_1 = require("querystring"); const error_1 = require("./error"); const bytelength_1 = require("./util/bytelength"); const request_1 = require("./util/request"); const LinkedList = require("linkedlist/lib/linkedlist"); const MIME_JSON = /\/(json|javascript)(\W|$)/; const LEADER_ENDPOINT_HEADER = "x-arango-endpoint"; function isSystemError(err) { return (Object.getPrototypeOf(err) === Error.prototype && err.hasOwnProperty("code") && err.hasOwnProperty("errno") && err.hasOwnProperty("syscall")); } class Connection { constructor(config = {}) { this._activeTasks = 0; this._arangoVersion = 30000; this._databaseName = "_system"; this._queue = new LinkedList(); this._hosts = []; this._urls = []; if (typeof config === "string") config = { url: config }; else if (Array.isArray(config)) config = { url: config }; if (config.arangoVersion !== undefined) { this._arangoVersion = config.arangoVersion; } if (config.isAbsolute) { this._databaseName = false; } this._agent = config.agent; this._agentOptions = request_1.isBrowser ? Object.assign({}, config.agentOptions) : Object.assign({ maxSockets: 3, keepAlive: true, keepAliveMsecs: 1000 }, config.agentOptions); this._maxTasks = this._agentOptions.maxSockets || 3; if (this._agentOptions.keepAlive) this._maxTasks *= 2; this._headers = Object.assign({}, config.headers); this._loadBalancingStrategy = config.loadBalancingStrategy || "NONE"; this._useFailOver = this._loadBalancingStrategy !== "ROUND_ROBIN"; if (config.maxRetries === false) { this._shouldRetry = false; this._maxRetries = 0; } else { this._shouldRetry = true; this._maxRetries = config.maxRetries || 0; } const urls = config.url ? Array.isArray(config.url) ? config.url : [config.url] : ["http://localhost:8529"]; this.addToHostList(urls); if (this._loadBalancingStrategy === "ONE_RANDOM") { this._activeHost = Math.floor(Math.random() * this._hosts.length); } else { this._activeHost = 0; } } get _databasePath() { return this._databaseName === false ? "" : `/_db/${this._databaseName}`; } _runQueue() { if (!this._queue.length || this._activeTasks >= this._maxTasks) return; const task = this._queue.shift(); let host = this._activeHost; if (task.host !== undefined) { host = task.host; } else if (this._loadBalancingStrategy === "ROUND_ROBIN") { this._activeHost = (this._activeHost + 1) % this._hosts.length; } this._activeTasks += 1; this._hosts[host](task.options, (err, res) => { this._activeTasks -= 1; if (err) { if (this._hosts.length > 1 && this._activeHost === host && this._useFailOver) { this._activeHost = (this._activeHost + 1) % this._hosts.length; } if (!task.host && this._shouldRetry && task.retries < (this._maxRetries || this._hosts.length - 1) && isSystemError(err) && err.syscall === "connect" && err.code === "ECONNREFUSED") { task.retries += 1; this._queue.push(task); } else { task.reject(err); } } else { const response = res; if (response.statusCode === 503 && response.headers[LEADER_ENDPOINT_HEADER]) { const url = response.headers[LEADER_ENDPOINT_HEADER]; const [index] = this.addToHostList(url); task.host = index; if (this._activeHost === host) { this._activeHost = index; } this._queue.push(task); } else { response.host = host; task.resolve(response); } } this._runQueue(); }); } _buildUrl({ absolutePath = false, basePath, path, qs }) { let pathname = ""; let search; if (!absolutePath) { pathname = this._databasePath; if (basePath) pathname += basePath; } if (path) pathname += path; if (qs) { if (typeof qs === "string") search = `?${qs}`; else search = `?${querystring_1.stringify(qs)}`; } return search ? { pathname, search } : { pathname }; } _sanitizeEndpointUrl(url) { if (url.startsWith("tcp:")) return url.replace(/^tcp:/, "http:"); if (url.startsWith("ssl:")) return url.replace(/^ssl:/, "https:"); return url; } addToHostList(urls) { const cleanUrls = (Array.isArray(urls) ? urls : [urls]).map(url => this._sanitizeEndpointUrl(url)); const newUrls = cleanUrls.filter(url => this._urls.indexOf(url) === -1); this._urls.push(...newUrls); this._hosts.push(...newUrls.map((url) => request_1.createRequest(url, this._agentOptions, this._agent))); return cleanUrls.map(url => this._urls.indexOf(url)); } get arangoMajor() { return Math.floor(this._arangoVersion / 10000); } getDatabaseName() { return this._databaseName; } getActiveHost() { return this._activeHost; } setDatabaseName(databaseName) { if (this._databaseName === false) { throw new Error("Can not change database from absolute URL"); } this._databaseName = databaseName; } setHeader(key, value) { this._headers[key] = value; } close() { for (const host of this._hosts) { if (host.close) host.close(); } } request(_a, getter) { var { host, method = "GET", body, expectBinary = false, isBinary = false, isJsonStream = false, headers } = _a, urlInfo = __rest(_a, ["host", "method", "body", "expectBinary", "isBinary", "isJsonStream", "headers"]); return new Promise((resolve, reject) => { let contentType = "text/plain"; if (isBinary) { contentType = "application/octet-stream"; } else if (body) { if (typeof body === "object") { if (isJsonStream) { body = body.map((obj) => JSON.stringify(obj)).join("\r\n") + "\r\n"; contentType = "application/x-ldjson"; } else { body = JSON.stringify(body); contentType = "application/json"; } } else { body = String(body); } } const extraHeaders = Object.assign({}, this._headers, { "content-type": contentType, "x-arango-version": String(this._arangoVersion) }); if (!request_1.isBrowser) { // Node doesn't set content-length but ArangoDB needs it extraHeaders["content-length"] = String(body ? bytelength_1.byteLength(body, "utf-8") : 0); } this._queue.push({ retries: 0, host, options: { url: this._buildUrl(urlInfo), headers: Object.assign({}, extraHeaders, headers), method, expectBinary, body }, reject, resolve: (res) => { const contentType = res.headers["content-type"]; let parsedBody = undefined; if (res.body.length && contentType && contentType.match(MIME_JSON)) { try { parsedBody = res.body; parsedBody = JSON.parse(parsedBody); } catch (e) { if (!expectBinary) { if (typeof parsedBody !== "string") { parsedBody = res.body.toString("utf-8"); } e.response = res; reject(e); return; } } } else if (res.body && !expectBinary) { parsedBody = res.body.toString("utf-8"); } else { parsedBody = res.body; } if (parsedBody && parsedBody.hasOwnProperty("error") && parsedBody.hasOwnProperty("code") && parsedBody.hasOwnProperty("errorMessage") && parsedBody.hasOwnProperty("errorNum")) { res.body = parsedBody; reject(new error_1.ArangoError(res)); } else if (res.statusCode && res.statusCode >= 400) { res.body = parsedBody; reject(new error_1.HttpError(res)); } else { if (!expectBinary) res.body = parsedBody; resolve(getter ? getter(res) : res); } } }); this._runQueue(); }); } } exports.Connection = Connection; //# sourceMappingURL=connection.js.map