UNPKG

neo4j-driver-core

Version:
641 lines (640 loc) 27.8 kB
"use strict"; /** * Copyright (c) "Neo4j" * Neo4j Sweden AB [https://neo4j.com] * * 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 __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while (g && (g = 0, op[0] && (_ = 0)), _) try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [op[0] & 2, t.value]; switch (op[0]) { case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [0]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; var _a; Object.defineProperty(exports, "__esModule", { value: true }); /* eslint-disable @typescript-eslint/promise-function-async */ var result_summary_1 = __importDefault(require("./result-summary")); var internal_1 = require("./internal"); var error_1 = require("./error"); var EMPTY_CONNECTION_HOLDER = internal_1.connectionHolder.EMPTY_CONNECTION_HOLDER; /** * @private * @param {Error} error The error * @returns {void} */ var DEFAULT_ON_ERROR = function (error) { // eslint-disable-next-line @typescript-eslint/restrict-plus-operands, @typescript-eslint/no-base-to-string console.log('Uncaught error when processing result: ' + error); }; /** * @private * @param {ResultSummary} summary * @returns {void} */ var DEFAULT_ON_COMPLETED = function (summary) { }; /** * @private * @param {string[]} keys List of keys of the record in the result * @return {void} */ var DEFAULT_ON_KEYS = function (keys) { }; /** * A stream of {@link Record} representing the result of a query. * Can be consumed eagerly as {@link Promise} resolved with array of records and {@link ResultSummary} * summary, or rejected with error that contains {@link string} code and {@link string} message. * Alternatively can be consumed lazily using {@link Result#subscribe} function. * @access public */ var Result = /** @class */ (function () { /** * Inject the observer to be used. * @constructor * @access private * @param {Promise<observer.ResultStreamObserver>} streamObserverPromise * @param {mixed} query - Cypher query to execute * @param {Object} parameters - Map with parameters to use in query * @param {ConnectionHolder} connectionHolder - to be notified when result is either fully consumed or error happened. */ function Result(streamObserverPromise, query, parameters, connectionHolder, watermarks) { if (watermarks === void 0) { watermarks = { high: Number.MAX_VALUE, low: Number.MAX_VALUE }; } /** * Called when finally the result is done * * *Should not be combined with {@link Result#subscribe} function.* * @param {function()|null} onfinally - function when the promise finished * @return {Promise} promise. */ this[_a] = 'Result'; this._stack = captureStacktrace(); this._streamObserverPromise = streamObserverPromise; this._p = null; this._query = query; this._parameters = parameters !== null && parameters !== void 0 ? parameters : {}; this._connectionHolder = connectionHolder !== null && connectionHolder !== void 0 ? connectionHolder : EMPTY_CONNECTION_HOLDER; this._keys = null; this._summary = null; this._error = null; this._watermarks = watermarks; } /** * Returns a promise for the field keys. * * *Should not be combined with {@link Result#subscribe} function.* * * @public * @returns {Promise<string[]>} - Field keys, in the order they will appear in records. } */ Result.prototype.keys = function () { var _this = this; if (this._keys !== null) { return Promise.resolve(this._keys); } else if (this._error !== null) { return Promise.reject(this._error); } return new Promise(function (resolve, reject) { _this._streamObserverPromise .then(function (observer) { return observer.subscribe(_this._decorateObserver({ onKeys: function (keys) { return resolve(keys); }, onError: function (err) { return reject(err); } })); }) .catch(reject); }); }; /** * Returns a promise for the result summary. * * *Should not be combined with {@link Result#subscribe} function.* * * @public * @returns {Promise<ResultSummary>} - Result summary. * */ Result.prototype.summary = function () { var _this = this; if (this._summary !== null) { return Promise.resolve(this._summary); } else if (this._error !== null) { return Promise.reject(this._error); } return new Promise(function (resolve, reject) { _this._streamObserverPromise .then(function (o) { o.cancel(); o.subscribe(_this._decorateObserver({ onCompleted: function (summary) { return resolve(summary); }, onError: function (err) { return reject(err); } })); }) .catch(reject); }); }; /** * Create and return new Promise * * @private * @return {Promise} new Promise. */ Result.prototype._getOrCreatePromise = function () { var _this = this; if (this._p == null) { this._p = new Promise(function (resolve, reject) { var records = []; var observer = { onNext: function (record) { records.push(record); }, onCompleted: function (summary) { resolve({ records: records, summary: summary }); }, onError: function (error) { reject(error); } }; _this.subscribe(observer); }); } return this._p; }; /** * Provides a async iterator over the records in the result. * * *Should not be combined with {@link Result#subscribe} or ${@link Result#then} functions.* * * @public * @returns {PeekableAsyncIterator<Record<R>, ResultSummary>} The async iterator for the Results */ Result.prototype[Symbol.asyncIterator] = function () { var _this = this; if (!this.isOpen()) { var error_2 = (0, error_1.newError)('Result is already consumed'); return { next: function () { return Promise.reject(error_2); }, peek: function () { return Promise.reject(error_2); } }; } var state = { paused: true, firstRun: true, finished: false }; var controlFlow = function () { var _b, _c; if (state.streaming == null) { return; } var size = (_c = (_b = state.queuedObserver) === null || _b === void 0 ? void 0 : _b.size) !== null && _c !== void 0 ? _c : 0; var queueSizeIsOverHighOrEqualWatermark = size >= _this._watermarks.high; var queueSizeIsBellowOrEqualLowWatermark = size <= _this._watermarks.low; if (queueSizeIsOverHighOrEqualWatermark && !state.paused) { state.paused = true; state.streaming.pause(); } else if ((queueSizeIsBellowOrEqualLowWatermark && state.paused) || (state.firstRun && !queueSizeIsOverHighOrEqualWatermark)) { state.firstRun = false; state.paused = false; state.streaming.resume(); } }; var initializeObserver = function () { return __awaiter(_this, void 0, void 0, function () { var _b; return __generator(this, function (_c) { switch (_c.label) { case 0: if (!(state.queuedObserver === undefined)) return [3 /*break*/, 2]; state.queuedObserver = this._createQueuedResultObserver(controlFlow); _b = state; return [4 /*yield*/, this._subscribe(state.queuedObserver, true).catch(function () { return undefined; })]; case 1: _b.streaming = _c.sent(); controlFlow(); _c.label = 2; case 2: return [2 /*return*/, state.queuedObserver]; } }); }); }; var assertSummary = function (summary) { if (summary === undefined) { throw (0, error_1.newError)('InvalidState: Result stream finished without Summary', error_1.PROTOCOL_ERROR); } return true; }; return { next: function () { return __awaiter(_this, void 0, void 0, function () { var queuedObserver, next; return __generator(this, function (_b) { switch (_b.label) { case 0: if (state.finished) { if (assertSummary(state.summary)) { return [2 /*return*/, { done: true, value: state.summary }]; } } return [4 /*yield*/, initializeObserver()]; case 1: queuedObserver = _b.sent(); return [4 /*yield*/, queuedObserver.dequeue()]; case 2: next = _b.sent(); if (next.done === true) { state.finished = next.done; state.summary = next.value; } return [2 /*return*/, next]; } }); }); }, return: function (value) { return __awaiter(_this, void 0, void 0, function () { var queuedObserver, last; var _b; return __generator(this, function (_c) { switch (_c.label) { case 0: if (state.finished) { if (assertSummary(state.summary)) { return [2 /*return*/, { done: true, value: value !== null && value !== void 0 ? value : state.summary }]; } } (_b = state.streaming) === null || _b === void 0 ? void 0 : _b.cancel(); return [4 /*yield*/, initializeObserver()]; case 1: queuedObserver = _c.sent(); return [4 /*yield*/, queuedObserver.dequeueUntilDone()]; case 2: last = _c.sent(); state.finished = true; last.value = value !== null && value !== void 0 ? value : last.value; state.summary = last.value; return [2 /*return*/, last]; } }); }); }, peek: function () { return __awaiter(_this, void 0, void 0, function () { var queuedObserver; return __generator(this, function (_b) { switch (_b.label) { case 0: if (state.finished) { if (assertSummary(state.summary)) { return [2 /*return*/, { done: true, value: state.summary }]; } } return [4 /*yield*/, initializeObserver()]; case 1: queuedObserver = _b.sent(); return [4 /*yield*/, queuedObserver.head()]; case 2: return [2 /*return*/, _b.sent()]; } }); }); } }; }; /** * Waits for all results and calls the passed in function with the results. * * *Should not be combined with {@link Result#subscribe} function.* * * @param {function(result: {records:Array<Record>, summary: ResultSummary})} onFulfilled - function to be called * when finished. * @param {function(error: {message:string, code:string})} onRejected - function to be called upon errors. * @return {Promise} promise. */ Result.prototype.then = function (onFulfilled, onRejected) { return this._getOrCreatePromise().then(onFulfilled, onRejected); }; /** * Catch errors when using promises. * * *Should not be combined with {@link Result#subscribe} function.* * * @param {function(error: Neo4jError)} onRejected - Function to be called upon errors. * @return {Promise} promise. */ Result.prototype.catch = function (onRejected) { return this._getOrCreatePromise().catch(onRejected); }; Result.prototype.finally = function (onfinally) { return this._getOrCreatePromise().finally(onfinally); }; /** * Stream records to observer as they come in, this is a more efficient method * of handling the results, and allows you to handle arbitrarily large results. * * @param {Object} observer - Observer object * @param {function(keys: string[])} observer.onKeys - handle stream head, the field keys. * @param {function(record: Record)} observer.onNext - handle records, one by one. * @param {function(summary: ResultSummary)} observer.onCompleted - handle stream tail, the result summary. * @param {function(error: {message:string, code:string})} observer.onError - handle errors. * @return {void} */ Result.prototype.subscribe = function (observer) { this._subscribe(observer) .catch(function () { }); }; /** * Check if this result is active, i.e., neither a summary nor an error has been received by the result. * @return {boolean} `true` when neither a summary or nor an error has been received by the result. */ Result.prototype.isOpen = function () { return this._summary === null && this._error === null; }; /** * Stream records to observer as they come in, this is a more efficient method * of handling the results, and allows you to handle arbitrarily large results. * * @access private * @param {ResultObserver} observer The observer to send records to. * @param {boolean} paused The flag to indicate if the stream should be started paused * @returns {Promise<observer.ResultStreamObserver>} The result stream observer. */ Result.prototype._subscribe = function (observer, paused) { if (paused === void 0) { paused = false; } var _observer = this._decorateObserver(observer); return this._streamObserverPromise .then(function (o) { if (paused) { o.pause(); } o.subscribe(_observer); return o; }) .catch(function (error) { if (_observer.onError != null) { _observer.onError(error); } return Promise.reject(error); }); }; /** * Decorates the ResultObserver with the necessary methods. * * @access private * @param {ResultObserver} observer The ResultObserver to decorate. * @returns The decorated result observer */ Result.prototype._decorateObserver = function (observer) { var _this = this; var _b, _c, _d; var onCompletedOriginal = (_b = observer.onCompleted) !== null && _b !== void 0 ? _b : DEFAULT_ON_COMPLETED; var onErrorOriginal = (_c = observer.onError) !== null && _c !== void 0 ? _c : DEFAULT_ON_ERROR; var onKeysOriginal = (_d = observer.onKeys) !== null && _d !== void 0 ? _d : DEFAULT_ON_KEYS; var onCompletedWrapper = function (metadata) { _this._releaseConnectionAndGetSummary(metadata).then(function (summary) { if (_this._summary !== null) { return onCompletedOriginal.call(observer, _this._summary); } _this._summary = summary; return onCompletedOriginal.call(observer, summary); }).catch(onErrorOriginal); }; var onErrorWrapper = function (error) { // notify connection holder that the used connection is not needed any more because error happened // and result can't bee consumed any further; call the original onError callback after that _this._connectionHolder.releaseConnection().then(function () { replaceStacktrace(error, _this._stack); _this._error = error; onErrorOriginal.call(observer, error); }).catch(onErrorOriginal); }; var onKeysWrapper = function (keys) { _this._keys = keys; return onKeysOriginal.call(observer, keys); }; return { onNext: (observer.onNext != null) ? observer.onNext.bind(observer) : undefined, onKeys: onKeysWrapper, onCompleted: onCompletedWrapper, onError: onErrorWrapper }; }; /** * Signals the stream observer that the future records should be discarded on the server. * * @protected * @since 4.0.0 * @returns {void} */ Result.prototype._cancel = function () { if (this._summary === null && this._error === null) { this._streamObserverPromise.then(function (o) { return o.cancel(); }) .catch(function () { }); } }; /** * @access private * @param metadata * @returns */ Result.prototype._releaseConnectionAndGetSummary = function (metadata) { var _b = internal_1.util.validateQueryAndParameters(this._query, this._parameters, { skipAsserts: true }), query = _b.validatedQuery, parameters = _b.params; var connectionHolder = this._connectionHolder; return connectionHolder .getConnection() .then( // onFulfilled: function (connection) { return connectionHolder .releaseConnection() .then(function () { return connection === null || connection === void 0 ? void 0 : connection.getProtocolVersion(); }); }, // onRejected: function (_) { return undefined; }) .then(function (protocolVersion) { return new result_summary_1.default(query, parameters, metadata, protocolVersion); }); }; /** * @access private */ Result.prototype._createQueuedResultObserver = function (onQueueSizeChanged) { var _this = this; function createResolvablePromise() { var resolvablePromise = {}; resolvablePromise.promise = new Promise(function (resolve, reject) { resolvablePromise.resolve = resolve; resolvablePromise.reject = reject; }); return resolvablePromise; } function isError(elementOrError) { return elementOrError instanceof Error; } function dequeue() { var _b; return __awaiter(this, void 0, void 0, function () { var element; return __generator(this, function (_c) { switch (_c.label) { case 0: if (buffer.length > 0) { element = (_b = buffer.shift()) !== null && _b !== void 0 ? _b : (0, error_1.newError)('Unexpected empty buffer', error_1.PROTOCOL_ERROR); onQueueSizeChanged(); if (isError(element)) { throw element; } return [2 /*return*/, element]; } promiseHolder.resolvable = createResolvablePromise(); return [4 /*yield*/, promiseHolder.resolvable.promise]; case 1: return [2 /*return*/, _c.sent()]; } }); }); } var buffer = []; var promiseHolder = { resolvable: null }; var observer = { onNext: function (record) { observer._push({ done: false, value: record }); }, onCompleted: function (summary) { observer._push({ done: true, value: summary }); }, onError: function (error) { observer._push(error); }, _push: function (element) { if (promiseHolder.resolvable !== null) { var resolvable = promiseHolder.resolvable; promiseHolder.resolvable = null; if (isError(element)) { resolvable.reject(element); } else { resolvable.resolve(element); } } else { buffer.push(element); onQueueSizeChanged(); } }, dequeue: dequeue, dequeueUntilDone: function () { return __awaiter(_this, void 0, void 0, function () { var element; return __generator(this, function (_b) { switch (_b.label) { case 0: if (!true) return [3 /*break*/, 2]; return [4 /*yield*/, dequeue()]; case 1: element = _b.sent(); if (element.done === true) { return [2 /*return*/, element]; } return [3 /*break*/, 0]; case 2: return [2 /*return*/]; } }); }); }, head: function () { return __awaiter(_this, void 0, void 0, function () { var element, element, error_3; return __generator(this, function (_b) { switch (_b.label) { case 0: if (buffer.length > 0) { element = buffer[0]; if (isError(element)) { throw element; } return [2 /*return*/, element]; } promiseHolder.resolvable = createResolvablePromise(); _b.label = 1; case 1: _b.trys.push([1, 3, 4, 5]); return [4 /*yield*/, promiseHolder.resolvable.promise]; case 2: element = _b.sent(); buffer.unshift(element); return [2 /*return*/, element]; case 3: error_3 = _b.sent(); buffer.unshift(error_3); throw error_3; case 4: onQueueSizeChanged(); return [7 /*endfinally*/]; case 5: return [2 /*return*/]; } }); }); }, get size() { return buffer.length; } }; return observer; }; return Result; }()); _a = Symbol.toStringTag; function captureStacktrace() { var error = new Error(''); if (error.stack != null) { return error.stack.replace(/^Error(\n\r)*/, ''); // we don't need the 'Error\n' part, if only it exists } return null; } /** * @private * @param {Error} error The error * @param {string| null} newStack The newStack * @returns {void} */ function replaceStacktrace(error, newStack) { if (newStack != null) { // Error.prototype.toString() concatenates error.name and error.message nicely // then we add the rest of the stack trace // eslint-disable-next-line @typescript-eslint/no-base-to-string error.stack = error.toString() + '\n' + newStack; } } exports.default = Result;