docs: function ajax(options) {
      var serverUrl = urls.resolveServer(options.url, options);
      var serverObj = server.Server.getInstance(serverUrl);
      if (!serverObj.sessionUserUuid && serverObj.credentials) {
          // not logged in
          var credentials_1 = serverObj.credentials;
          serverObj.credentials = null;
          return login(credentials_1, {
              serverUrl: serverUrl
          }).then(function () {
              diag.debug.assert(function () { return !!serverObj.sessionUserUuid; });
              diag.debug.assert(function () { return serverObj.credentials == credentials_1; });
              return ajax(options); // repeat after login
          });
      }
      // process options
      var currentOptions = serverObj.applyOptions({
          serverUrl: serverUrl,
          agentOptions: options.agentOptions || init.initOptions.agentOptions,
          agentClass: options.agentClass || init.initOptions.agentClass,
          // options taking effect at request time
          application: options.application || init.initOptions.application,
          tenantOrga: options.tenantOrga || init.initOptions.tenantOrga
      });
      // resolve target url
      var url = urls.resolveUrl(options.url, currentOptions);
      diag.debug.debug(options.method + ' ' + url);
      diag.debug.assert(function () { return url.substr(0, serverUrl.length) === serverUrl; });
      var requestCallback = options.requestCallback || _.identity;
      var responseCallback = options.responseCallback || _.identity;
      options = _.clone(options);
      options.agentOptions = currentOptions.agentOptions;
      options.agentClass = currentOptions.agentClass;
      if (currentOptions.clientCertificate) {
          // apply certificate options (must copy options.agentOptions)
          options.agentOptions = _.defaults({}, currentOptions.clientCertificate, options.agentOptions);
      }
      var headers = {};
      if (serverObj.sessionUserUuid) {
          // add X-Gofer-User header so that server may check we are running under correct identity
          headers['X-Gofer-User'] = serverObj.sessionUserUuid;
      }
      if (currentOptions.clientApp) {
          // add X-Relution-ClientApp for server-side analytics
          headers['X-Relution-ClientApp'] = currentOptions.clientApp;
      }
      if (!_.isEmpty(headers)) {
          options.headers = _.defaults(headers, options.headers);
      }
      return Q.Promise(function (resolveResult, rejectResult) {
          var promiseResponse = Q.Promise(function (resolveResponse, rejectResponse) {
              var resp;
              var req;
              try {
                  req = requestWithDefaults(url, options, function (error, response, body) {
                      if (response === void 0) { response = resp; }
                      // node.js assigns status string as body for status codes not having body data
                      if (response && response.statusCode === 202) {
                          diag.debug.assert(body === http.STATUS_CODES[202], body);
                          body = undefined; // resolves promise to undefined below
                      }
                      // error processing
                      if (!error && response && response.statusCode >= 400) {
                          if (_.isError(body)) {
                              // correct but practically impossible
                              error = body;
                          }
                          else if (_.isString(body)) {
                              // use plain-text as Error message
                              error = new Error(body);
                          }
                          else if (_.isObjectLike(body)) {
                              // body is object representation of server-side error or exception,
                              // converting to true Error object here
                              error = new Error(response.statusMessage);
                              diag.debug.assert(function () { return !_.isArray(body); }, 'kicks in for array responses as well, not sure if this is desirable');
                              _.extend(error, body);
                          }
                          else {
                              // handles numbers, booleans, etc. assigning as cause of failure
                              error = new Error(response.statusMessage);
                              if (!_.isUndefined(body)) {
                                  error.cause = body;
                              }
                          }
                      }
                      if (error) {
                          // completes HttpError construction
                          diag.debug.assertIsError(error, 'should operate true Error instances');
                          error.requestUrl = url;
                          if (response) {
                              error.statusCode = response.statusCode;
                              error.statusMessage = response.statusMessage;
                              Object.defineProperty(error, 'rawResponse', {
                                  value: response,
                                  enumerable: false
                              });
                          }
                          Object.defineProperty(error, 'rawRequest', {
                              value: req,
                              enumerable: false
                          });
                      }
                      if (!response) {
                          // network connectivity problem
                          serverObj.serverInfos = null;
                          diag.debug.assertIsError(error);
                          if (promiseResponse) {
                              rejectResponse(error); // will also rejectResult(error)
                          }
                          else {
                              rejectResult(error); // promiseResponse not constructed yet
                          }
                      }
                      else {
                          // server information
                          serverObj.serverInfos = {
                              version: response.headers['x-relution-version'],
                              description: response.headers['x-server']
                          };
                          if (response.statusCode === 503 ||
                              response.statusCode === 500 && error.className === 'java.util.concurrent.TimeoutException') {
                              // 503 (service unavailable) indicates the server is temporarily overloaded, and unable
                              // handling the request. This happens when async delegation timed out on the Java side,
                              // usually after about 2 minutes. In this case retry the request until we are done...
                              diag.debug.info('server overloaded, retrying request.');
                              // here promiseResponse must have been resolved already,
                              // we chain anyways because of error propagation
                              promiseResponse.thenResolve(ajax(options)).done(resolveResult, rejectResult);
                              return; // early exit as result is handled by done call above
                          }
                          else {
                              // logon session processing
                              var sessionUserUuid = response.headers['x-gofer-user'];
                              if (sessionUserUuid) {
                                  serverObj.sessionUserUuid = sessionUserUuid;
                              }
                              else if (response.statusCode === 401) {
                                  // apparently our session is lost!
                                  serverObj.sessionUserUuid = null;
                                  diag.debug.assert(function () { return !!error; });
                                  diag.debug.warn('server session is lost!', error);
                                  var credentials = serverObj.credentials;
                                  if (credentials) {
                                      // recover by attempting login,
                                      // here promiseResponse must have been resolved already,
                                      // we chain anyways because of error propagation
                                      serverObj.credentials = null;
                                      promiseResponse.thenResolve(login(credentials, {
                                          serverUrl: serverUrl
                                      }).then(function () {
                                          diag.debug.assert(function () { return !!serverObj.sessionUserUuid; });
                                          diag.debug.info('server session recovered.');
                                          return ajax(options);
                                      })).done(resolveResult, rejectResult);
                                      return; // early exit as result is handled by done call above
                                  }
                              }
                          }
                      }
                      // completes the chain propagating results, must be skipped when request is retried above
                      if (promiseResponse) {
                          promiseResponse.then(function (responseResult) {
                              diag.debug.assert(function () { return responseResult === resp; }, 'definition of behavior in case ' +
                                  'of proxying the original response is reserved for future extension!');
                              if (error) {
                                  rejectResult(error);
                              }
                              else {
                                  resolveResult(body);
                              }
                          }, function (responseError) {
                              rejectResult(responseError);
                          }).done();
                      }
                  });
              }
              catch (error) {
                  // path taken when request.js throws
                  return rejectResult(error);
              }
              // transport response
              try {
                  Q(requestCallback(req)).then(function (request) {
                      if (request === void 0) { request = req; }
                      request.on('response', function (response) {
                          if (!resp) {
                              resp = response;
                              resolveResponse(responseCallback(resp));
                          }
                      });
                      return request;
                  }).done();
              }
              catch (error) {
                  // path taken when requestCallback throws
                  return rejectResponse(error);
              }
          });
      });
  }