ravendb
Version:
RavenDB client for Node.js
227 lines • 8.19 kB
JavaScript
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