UNPKG

ravendb

Version:
227 lines 8.19 kB
import { StatusCodes } from "./StatusCode.js"; import { Stream, Readable, PassThrough } from "node:stream"; import { getLogger } from "../Utility/LogUtil.js"; import { throwError } from "../Exceptions/index.js"; import { getEtagHeader, HeadersBuilder, closeHttpResponse } from "../Utility/HttpUtil.js"; import { JsonSerializer } from "../Mapping/Json/Serializer.js"; import { RavenCommandResponsePipeline } from "./RavenCommandResponsePipeline.js"; import { ObjectUtil } from "../Utility/ObjectUtil.js"; import { HEADERS } from "../Constants.js"; import { DefaultCommandResponseBehavior } from "./Behaviors/DefaultCommandResponseBehavior.js"; const log = getLogger({ module: "RavenCommand" }); export class RavenCommand { // protected final Class<TResult> resultClass; result; statusCode; failedNodes; _responseType; timeout; _canCache; _canCacheAggressively; _canReadFromCache = true; _selectedNodeTag; _selectedShardNumber; _numberOfAttempts; failoverTopologyEtag = -2; _etag; get responseBehavior() { return DefaultCommandResponseBehavior.INSTANCE; } get responseType() { return this._responseType; } set etag(value) { this._etag = value; } get canCache() { return this._canCache; } get canCacheAggressively() { return this._canCacheAggressively; } get selectedNodeTag() { return this._selectedNodeTag; } set selectedNodeTag(nodeTag) { this._selectedNodeTag = nodeTag; } get selectedShardNumber() { return this._selectedShardNumber; } set selectedShardNumber(value) { this._selectedShardNumber = value; } get numberOfAttempts() { return this._numberOfAttempts; } set numberOfAttempts(value) { this._numberOfAttempts = value; } constructor(copy) { if (copy instanceof RavenCommand) { this._canCache = copy._canCache; this._canReadFromCache = copy._canReadFromCache; this._canCacheAggressively = copy._canCacheAggressively; this._selectedNodeTag = copy._selectedNodeTag; this._selectedShardNumber = copy.selectedShardNumber; this._responseType = copy._responseType; } else { this._responseType = "Object"; this._canCache = true; this._canCacheAggressively = true; } } get _serializer() { return JsonSerializer.getDefaultForCommandPayload(); } async setResponseFromCache(cachedValue) { if (!cachedValue) { this.result = null; return; } const readable = new Readable(); readable.push(cachedValue); readable.push(null); await this.setResponseAsync(readable, true); } _defaultPipeline(bodyCallback) { return this._pipeline() .parseJsonSync() .collectBody(bodyCallback) .objectKeysTransform(ObjectUtil.camel); } async setResponseAsync(bodyStream, fromCache) { if (this._responseType === "Empty" || this._responseType === "Raw") { this._throwInvalidResponse(); } return throwError("NotSupportedException", this.constructor.name + " command must override the setResponseAsync()" + " method which expects response with the following type: " + this._responseType); } async send(agent, requestOptions) { const { body, uri, fetcher, ...restOptions } = requestOptions; log.info(`Send command ${this.constructor.name} to ${uri}${body ? " with body " + body : ""}.`); if (requestOptions.dispatcher) { // support for fiddler agent = requestOptions.dispatcher; } const bodyToUse = fetcher ? RavenCommand.maybeWrapBody(body) : body; const optionsToUse = { body: bodyToUse, ...restOptions, dispatcher: agent }; const passthrough = new PassThrough(); passthrough.pause(); const fetchFn = fetcher ?? fetch; // support for custom fetcher const response = await fetchFn(uri, optionsToUse); const effectiveStream = response.body ? Readable.fromWeb(response.body) : new Stream(); effectiveStream .pipe(passthrough); return { response, bodyStream: passthrough }; } static maybeWrapBody(body) { if (body instanceof Readable) { throw new Error("Requests using stream.Readable as payload are not yet supported!"); } return body; } setResponseRaw(response, body) { throwError("NotSupportedException", "When _responseType is set to RAW then please override this method to handle the response."); } _urlEncode(value) { return encodeURIComponent(value); } static ensureIsNotNullOrEmpty(value, name) { if (!value) { throwError("InvalidArgumentException", name + " cannot be null or empty"); } } isFailedWithNode(node) { return this.failedNodes && !!this.failedNodes.get(node); } async processResponse(cache, response, bodyStream, url) { if (!response) { return "Automatic"; } if (this._responseType === "Empty" || response.status === StatusCodes.NoContent) { return "Automatic"; } try { if (this._responseType === "Object") { const contentLength = Number.parseInt(response.headers.get("content-length"), 10); if (contentLength === 0) { closeHttpResponse(response); return "Automatic"; } const bodyPromise = this.setResponseAsync(bodyStream, false); bodyStream.resume(); const body = await bodyPromise; if (cache) { this._cacheResponse(cache, url, response, body); } return "Automatic"; } else { const bodyPromise = this.setResponseAsync(bodyStream, false); bodyStream.resume(); await bodyPromise; } return "Automatic"; } catch (err) { log.error(err, `Error processing command ${this.constructor.name} response.`); throwError("RavenException", `Error processing command ${this.constructor.name} response: ${err.stack}`, err); } finally { closeHttpResponse(response); // response.destroy(); // since we're calling same hosts and port a lot, we might not want to destroy sockets explicitly // they're going to get back to Agent's pool and reused } return "Automatic"; } _cacheResponse(cache, url, response, responseJson) { if (!this.canCache) { return; } const changeVector = getEtagHeader(response); if (!changeVector) { return; } cache.set(url, changeVector, responseJson); } _addChangeVectorIfNotNull(changeVector, req) { if (changeVector) { req.headers[HEADERS.IF_MATCH] = `"${changeVector}"`; } } _reviveResultTypes(raw, conventions, typeInfo, knownTypes) { return conventions.objectMapper.fromObjectLiteral(raw, typeInfo, knownTypes); } async _parseResponseDefaultAsync(bodyStream) { let body = null; this.result = await this._defaultPipeline(_ => body = _).process(bodyStream); return body; } _headers() { return HeadersBuilder.create(); } _throwInvalidResponse() { throwError("InvalidOperationException", "Response is invalid"); } static _throwInvalidResponse(cause) { throwError("InvalidOperationException", "Response is invalid: " + cause, cause.message, cause); } onResponseFailure(response) { // empty } _pipeline() { return RavenCommandResponsePipeline.create(); } } //# sourceMappingURL=RavenCommand.js.map