text: import * as tslib_1 from "tslib";
  // Copyright (c) Microsoft Corporation.
  // Licensed under the MIT license.
  import uuid from "uuid/v4";
  import { Constants, HTTPMethod, OperationType, ResourceType } from "./common/constants";
  import { getIdFromLink, getPathFromLink, parseLink } from "./common/helper";
  import { logger } from "./common/logger";
  import { StatusCodes, SubStatusCodes } from "./common/statusCodes";
  import { ConsistencyLevel, DatabaseAccount } from "./documents";
  import { executePlugins, PluginOn } from "./plugins/Plugin";
  import { QueryIterator } from "./queryIterator";
  import { getHeaders } from "./request/request";
  import { request as executeRequest } from "./request/RequestHandler";
  import { SessionContainer } from "./session/sessionContainer";
  /** @hidden */
  const log = logger("ClientContext");
  /**
   * @hidden
   * @ignore
   */
  export class ClientContext {
      constructor(cosmosClientOptions, globalEndpointManager) {
          this.cosmosClientOptions = cosmosClientOptions;
          this.globalEndpointManager = globalEndpointManager;
          this.connectionPolicy = cosmosClientOptions.connectionPolicy;
          this.sessionContainer = new SessionContainer();
          this.partitionKeyDefinitionCache = {};
      }
      /** @ignore */
      read({ path, resourceType, resourceId, options = {}, partitionKey }) {
          return tslib_1.__awaiter(this, void 0, void 0, function* () {
              try {
                  const request = {
                      globalEndpointManager: this.globalEndpointManager,
                      requestAgent: this.cosmosClientOptions.agent,
                      connectionPolicy: this.connectionPolicy,
                      method: HTTPMethod.get,
                      path,
                      operationType: OperationType.Read,
                      client: this,
                      resourceId,
                      options,
                      resourceType,
                      plugins: this.cosmosClientOptions.plugins,
                      partitionKey
                  };
                  request.headers = yield this.buildHeaders(request);
                  this.applySessionToken(request);
                  // read will use ReadEndpoint since it uses GET operation
                  request.endpoint = yield this.globalEndpointManager.resolveServiceEndpoint(request);
                  const response = yield executePlugins(request, executeRequest, PluginOn.operation);
                  this.captureSessionToken(undefined, path, OperationType.Read, response.headers);
                  return response;
              }
              catch (err) {
                  this.captureSessionToken(err, path, OperationType.Upsert, err.headers);
                  throw err;
              }
          });
      }
      queryFeed({ path, resourceType, resourceId, resultFn, query, options, partitionKeyRangeId, partitionKey }) {
          return tslib_1.__awaiter(this, void 0, void 0, function* () {
              // Query operations will use ReadEndpoint even though it uses
              // GET(for queryFeed) and POST(for regular query operations)
              const request = {
                  globalEndpointManager: this.globalEndpointManager,
                  requestAgent: this.cosmosClientOptions.agent,
                  connectionPolicy: this.connectionPolicy,
                  method: HTTPMethod.get,
                  path,
                  operationType: OperationType.Query,
                  client: this,
                  partitionKeyRangeId,
                  resourceId,
                  resourceType,
                  options,
                  body: query,
                  plugins: this.cosmosClientOptions.plugins,
                  partitionKey
              };
              const requestId = uuid();
              if (query !== undefined) {
                  request.method = HTTPMethod.post;
              }
              request.endpoint = yield this.globalEndpointManager.resolveServiceEndpoint(request);
              request.headers = yield this.buildHeaders(request);
              if (query !== undefined) {
                  request.headers[Constants.HttpHeaders.IsQuery] = "true";
                  request.headers[Constants.HttpHeaders.ContentType] = Constants.MediaTypes.QueryJson;
                  if (typeof query === "string") {
                      request.body = { query }; // Converts query text to query object.
                  }
              }
              this.applySessionToken(request);
              log.info("query " +
                  requestId +
                  " started" +
                  (request.partitionKeyRangeId ? " pkrid: " + request.partitionKeyRangeId : ""));
              log.silly(request);
              const start = Date.now();
              const response = yield executeRequest(request);
              log.info("query " + requestId + " finished - " + (Date.now() - start) + "ms");
              this.captureSessionToken(undefined, path, OperationType.Query, response.headers);
              return this.processQueryFeedResponse(response, !!query, resultFn);
          });
      }
      getQueryPlan(path, resourceType, resourceId, query, options = {}) {
          return tslib_1.__awaiter(this, void 0, void 0, function* () {
              const request = {
                  globalEndpointManager: this.globalEndpointManager,
                  requestAgent: this.cosmosClientOptions.agent,
                  connectionPolicy: this.connectionPolicy,
                  method: HTTPMethod.post,
                  path,
                  operationType: OperationType.Read,
                  client: this,
                  resourceId,
                  resourceType,
                  options,
                  body: query,
                  plugins: this.cosmosClientOptions.plugins
              };
              request.endpoint = yield this.globalEndpointManager.resolveServiceEndpoint(request);
              request.headers = yield this.buildHeaders(request);
              request.headers[Constants.HttpHeaders.IsQueryPlan] = "True";
              request.headers[Constants.HttpHeaders.QueryVersion] = "1.4";
              request.headers[Constants.HttpHeaders.SupportedQueryFeatures] =
                  "Aggregate, Distinct, MultipleOrderBy, OffsetAndLimit, OrderBy, Top, CompositeAggregate";
              request.headers[Constants.HttpHeaders.ContentType] = Constants.MediaTypes.QueryJson;
              if (typeof query === "string") {
                  request.body = { query }; // Converts query text to query object.
              }
              this.applySessionToken(request);
              const response = yield executeRequest(request);
              this.captureSessionToken(undefined, path, OperationType.Query, response.headers);
              return response;
          });
      }
      queryPartitionKeyRanges(collectionLink, query, options) {
          const path = getPathFromLink(collectionLink, ResourceType.pkranges);
          const id = getIdFromLink(collectionLink);
          const cb = (innerOptions) => {
              return this.queryFeed({
                  path,
                  resourceType: ResourceType.pkranges,
                  resourceId: id,
                  resultFn: (result) => result.PartitionKeyRanges,
                  query,
                  options: innerOptions
              });
          };
          return new QueryIterator(this, query, options, cb);
      }
      delete({ path, resourceType, resourceId, options = {}, partitionKey }) {
          return tslib_1.__awaiter(this, void 0, void 0, function* () {
              try {
                  const request = {
                      globalEndpointManager: this.globalEndpointManager,
                      requestAgent: this.cosmosClientOptions.agent,
                      connectionPolicy: this.connectionPolicy,
                      method: HTTPMethod.delete,
                      client: this,
                      operationType: OperationType.Delete,
                      path,
                      resourceType,
                      options,
                      resourceId,
                      plugins: this.cosmosClientOptions.plugins,
                      partitionKey
                  };
                  request.headers = yield this.buildHeaders(request);
                  this.applySessionToken(request);
                  // deleteResource will use WriteEndpoint since it uses DELETE operation
                  request.endpoint = yield this.globalEndpointManager.resolveServiceEndpoint(request);
                  const response = yield executePlugins(request, executeRequest, PluginOn.operation);
                  if (parseLink(path).type !== "colls") {
                      this.captureSessionToken(undefined, path, OperationType.Delete, response.headers);
                  }
                  else {
                      this.clearSessionToken(path);
                  }
                  return response;
              }
              catch (err) {
                  this.captureSessionToken(err, path, OperationType.Upsert, err.headers);
                  throw err;
              }
          });
      }
      create({ body, path, resourceType, resourceId, options = {}, partitionKey }) {
          return tslib_1.__awaiter(this, void 0, void 0, function* () {
              try {
                  const request = {
                      globalEndpointManager: this.globalEndpointManager,
                      requestAgent: this.cosmosClientOptions.agent,
                      connectionPolicy: this.connectionPolicy,
                      method: HTTPMethod.post,
                      client: this,
                      operationType: OperationType.Create,
                      path,
                      resourceType,
                      resourceId,
                      body,
                      options,
                      plugins: this.cosmosClientOptions.plugins,
                      partitionKey
                  };
                  request.headers = yield this.buildHeaders(request);
                  // create will use WriteEndpoint since it uses POST operation
                  this.applySessionToken(request);
                  request.endpoint = yield this.globalEndpointManager.resolveServiceEndpoint(request);
                  const response = yield executePlugins(request, executeRequest, PluginOn.operation);
                  this.captureSessionToken(undefined, path, OperationType.Create, response.headers);
                  return response;
              }
              catch (err) {
                  this.captureSessionToken(err, path, OperationType.Upsert, err.headers);
                  throw err;
              }
          });
      }
      processQueryFeedResponse(res, isQuery, resultFn) {
          if (isQuery) {
              return { result: resultFn(res.result), headers: res.headers, code: res.code };
          }
          else {
              const newResult = resultFn(res.result).map((body) => body);
              return { result: newResult, headers: res.headers, code: res.code };
          }
      }
      applySessionToken(requestContext) {
          const request = this.getSessionParams(requestContext.path);
          if (requestContext.headers && requestContext.headers[Constants.HttpHeaders.SessionToken]) {
              return;
          }
          const sessionConsistency = requestContext.headers[Constants.HttpHeaders.ConsistencyLevel];
          if (!sessionConsistency) {
              return;
          }
          if (sessionConsistency !== ConsistencyLevel.Session) {
              return;
          }
          if (request.resourceAddress) {
              const sessionToken = this.sessionContainer.get(request);
              if (sessionToken) {
                  requestContext.headers[Constants.HttpHeaders.SessionToken] = sessionToken;
              }
          }
      }
      replace({ body, path, resourceType, resourceId, options = {}, partitionKey }) {
          return tslib_1.__awaiter(this, void 0, void 0, function* () {
              try {
                  const request = {
                      globalEndpointManager: this.globalEndpointManager,
                      requestAgent: this.cosmosClientOptions.agent,
                      connectionPolicy: this.connectionPolicy,
                      method: HTTPMethod.put,
                      client: this,
                      operationType: OperationType.Replace,
                      path,
                      resourceType,
                      body,
                      resourceId,
                      options,
                      plugins: this.cosmosClientOptions.plugins,
                      partitionKey
                  };
                  request.headers = yield this.buildHeaders(request);
                  this.applySessionToken(request);
                  // replace will use WriteEndpoint since it uses PUT operation
                  request.endpoint = yield this.globalEndpointManager.resolveServiceEndpoint(request);
                  const response = yield executePlugins(request, executeRequest, PluginOn.operation);
                  this.captureSessionToken(undefined, path, OperationType.Replace, response.headers);
                  return response;
              }
              catch (err) {
                  this.captureSessionToken(err, path, OperationType.Upsert, err.headers);
                  throw err;
              }
          });
      }
      upsert({ body, path, resourceType, resourceId, options = {}, partitionKey }) {
          return tslib_1.__awaiter(this, void 0, void 0, function* () {
              try {
                  const request = {
                      globalEndpointManager: this.globalEndpointManager,
                      requestAgent: this.cosmosClientOptions.agent,
                      connectionPolicy: this.connectionPolicy,
                      method: HTTPMethod.post,
                      client: this,
                      operationType: OperationType.Upsert,
                      path,
                      resourceType,
                      body,
                      resourceId,
                      options,
                      plugins: this.cosmosClientOptions.plugins,
                      partitionKey
                  };
                  request.headers = yield this.buildHeaders(request);
                  request.headers[Constants.HttpHeaders.IsUpsert] = true;
                  this.applySessionToken(request);
                  // upsert will use WriteEndpoint since it uses POST operation
                  request.endpoint = yield this.globalEndpointManager.resolveServiceEndpoint(request);
                  const response = yield executePlugins(request, executeRequest, PluginOn.operation);
                  this.captureSessionToken(undefined, path, OperationType.Upsert, response.headers);
                  return response;
              }
              catch (err) {
                  this.captureSessionToken(err, path, OperationType.Upsert, err.headers);
                  throw err;
              }
          });
      }
      execute({ sprocLink, params, options = {}, partitionKey }) {
          return tslib_1.__awaiter(this, void 0, void 0, function* () {
              // Accept a single parameter or an array of parameters.
              // Didn't add type annotation for this because we should legacy this behavior
              if (params !== null && params !== undefined && !Array.isArray(params)) {
                  params = [params];
              }
              const path = getPathFromLink(sprocLink);
              const id = getIdFromLink(sprocLink);
              const request = {
                  globalEndpointManager: this.globalEndpointManager,
                  requestAgent: this.cosmosClientOptions.agent,
                  connectionPolicy: this.connectionPolicy,
                  method: HTTPMethod.post,
                  client: this,
                  operationType: OperationType.Execute,
                  path,
                  resourceType: ResourceType.sproc,
                  options,
                  resourceId: id,
                  body: params,
                  plugins: this.cosmosClientOptions.plugins,
                  partitionKey
              };
              request.headers = yield this.buildHeaders(request);
              // executeStoredProcedure will use WriteEndpoint since it uses POST operation
              request.endpoint = yield this.globalEndpointManager.resolveServiceEndpoint(request);
              return executePlugins(request, executeRequest, PluginOn.operation);
          });
      }
      /**
       * Gets the Database account information.
       * @param {string} [options.urlConnection]   - The endpoint url whose database account needs to be retrieved. \
       * If not present, current client's url will be used.
       */
      getDatabaseAccount(options = {}) {
          return tslib_1.__awaiter(this, void 0, void 0, function* () {
              const endpoint = options.urlConnection || this.cosmosClientOptions.endpoint;
              const request = {
                  endpoint,
                  globalEndpointManager: this.globalEndpointManager,
                  requestAgent: this.cosmosClientOptions.agent,
                  connectionPolicy: this.connectionPolicy,
                  method: HTTPMethod.get,
                  client: this,
                  operationType: OperationType.Read,
                  path: "",
                  resourceType: ResourceType.none,
                  options,
                  plugins: this.cosmosClientOptions.plugins
              };
              request.headers = yield this.buildHeaders(request);
              // await options.beforeOperation({ endpoint, request, headers: requestHeaders });
              const { result, headers } = yield executePlugins(request, executeRequest, PluginOn.operation);
              const databaseAccount = new DatabaseAccount(result, headers);
              return { result: databaseAccount, headers };
          });
      }
      getWriteEndpoint() {
          return this.globalEndpointManager.getWriteEndpoint();
      }
      getReadEndpoint() {
          return this.globalEndpointManager.getReadEndpoint();
      }
      captureSessionToken(err, path, operationType, resHeaders) {
          const request = this.getSessionParams(path);
          request.operationType = operationType;
          if (!err ||
              (!this.isMasterResource(request.resourceType) &&
                  (err.code === StatusCodes.PreconditionFailed ||
                      err.code === StatusCodes.Conflict ||
                      (err.code === StatusCodes.NotFound &&
                          err.substatus !== SubStatusCodes.ReadSessionNotAvailable)))) {
              this.sessionContainer.set(request, resHeaders);
          }
      }
      clearSessionToken(path) {
          const request = this.getSessionParams(path);
          this.sessionContainer.remove(request);
      }
      getSessionParams(resourceLink) {
          const resourceId = null;
          let resourceAddress = null;
          const parserOutput = parseLink(resourceLink);
          resourceAddress = parserOutput.objectBody.self;
          const resourceType = parserOutput.type;
          return {
              resourceId,
              resourceAddress,
              resourceType,
              isNameBased: true
          };
      }
      isMasterResource(resourceType) {
          if (resourceType === Constants.Path.OffersPathSegment ||
              resourceType === Constants.Path.DatabasesPathSegment ||
              resourceType === Constants.Path.UsersPathSegment ||
              resourceType === Constants.Path.PermissionsPathSegment ||
              resourceType === Constants.Path.TopologyPathSegment ||
              resourceType === Constants.Path.DatabaseAccountPathSegment ||
              resourceType === Constants.Path.PartitionKeyRangesPathSegment ||
              resourceType === Constants.Path.CollectionsPathSegment) {
              return true;
          }
          return false;
      }
      buildHeaders(requestContext) {
          return getHeaders({
              clientOptions: this.cosmosClientOptions,
              defaultHeaders: Object.assign({}, this.cosmosClientOptions.defaultHeaders, requestContext.options.initialHeaders),
              verb: requestContext.method,
              path: requestContext.path,
              resourceId: requestContext.resourceId,
              resourceType: requestContext.resourceType,
              options: requestContext.options,
              partitionKeyRangeId: requestContext.partitionKeyRangeId,
              useMultipleWriteLocations: this.connectionPolicy.useMultipleWriteLocations,
              partitionKey: requestContext.partitionKey
          });
      }
  }
  //# sourceMappingURL=ClientContext.js.map