"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);

// source/index.ts
var source_exports = {};
__export(source_exports, {
  MemoryStore: () => MemoryStore,
  default: () => lib_default,
  rateLimit: () => lib_default
});
module.exports = __toCommonJS(source_exports);

// source/memory-store.ts
var calculateNextResetTime = (windowMs) => {
  const resetTime = new Date();
  resetTime.setMilliseconds(resetTime.getMilliseconds() + windowMs);
  return resetTime;
};
var MemoryStore = class {
  init(options) {
    this.windowMs = options.windowMs;
    this.resetTime = calculateNextResetTime(this.windowMs);
    this.hits = {};
    this.interval = setInterval(async () => {
      await this.resetAll();
    }, this.windowMs);
    if (this.interval.unref)
      this.interval.unref();
  }
  async increment(key) {
    var _a;
    const totalHits = ((_a = this.hits[key]) != null ? _a : 0) + 1;
    this.hits[key] = totalHits;
    return {
      totalHits,
      resetTime: this.resetTime
    };
  }
  async decrement(key) {
    const current = this.hits[key];
    if (current)
      this.hits[key] = current - 1;
  }
  async resetKey(key) {
    delete this.hits[key];
  }
  async resetAll() {
    this.hits = {};
    this.resetTime = calculateNextResetTime(this.windowMs);
  }
  shutdown() {
    clearInterval(this.interval);
  }
};

// source/lib.ts
var isLegacyStore = (store) => typeof store.incr === "function" && typeof store.increment !== "function";
var promisifyStore = (passedStore) => {
  if (!isLegacyStore(passedStore)) {
    return passedStore;
  }
  const legacyStore = passedStore;
  class PromisifiedStore {
    async increment(key) {
      return new Promise((resolve, reject) => {
        legacyStore.incr(
          key,
          (error, totalHits, resetTime) => {
            if (error)
              reject(error);
            resolve({ totalHits, resetTime });
          }
        );
      });
    }
    async decrement(key) {
      return legacyStore.decrement(key);
    }
    async resetKey(key) {
      return legacyStore.resetKey(key);
    }
    async resetAll() {
      if (typeof legacyStore.resetAll === "function")
        return legacyStore.resetAll();
    }
  }
  return new PromisifiedStore();
};
var parseOptions = (passedOptions) => {
  var _a, _b, _c;
  const notUndefinedOptions = omitUndefinedOptions(passedOptions);
  const config = {
    windowMs: 60 * 1e3,
    max: 5,
    message: "Too many requests, please try again later.",
    statusCode: 429,
    legacyHeaders: (_a = passedOptions.headers) != null ? _a : true,
    standardHeaders: (_b = passedOptions.draft_polli_ratelimit_headers) != null ? _b : false,
    requestPropertyName: "rateLimit",
    skipFailedRequests: false,
    skipSuccessfulRequests: false,
    requestWasSuccessful: (_request, response) => response.statusCode < 400,
    skip: (_request, _response) => false,
    keyGenerator(request, _response) {
      if (!request.ip) {
        console.error(
          "WARN | `express-rate-limit` | `request.ip` is undefined. You can avoid this by providing a custom `keyGenerator` function, but it may be indicative of a larger issue."
        );
      }
      return request.ip;
    },
    async handler(request, response, _next, _optionsUsed) {
      response.status(config.statusCode);
      const message = typeof config.message === "function" ? await config.message(
        request,
        response
      ) : config.message;
      if (!response.writableEnded) {
        response.send(message != null ? message : "Too many requests, please try again later.");
      }
    },
    onLimitReached(_request, _response, _optionsUsed) {
    },
    ...notUndefinedOptions,
    store: promisifyStore((_c = notUndefinedOptions.store) != null ? _c : new MemoryStore())
  };
  if (typeof config.store.increment !== "function" || typeof config.store.decrement !== "function" || typeof config.store.resetKey !== "function" || typeof config.store.resetAll !== "undefined" && typeof config.store.resetAll !== "function" || typeof config.store.init !== "undefined" && typeof config.store.init !== "function") {
    throw new TypeError(
      "An invalid store was passed. Please ensure that the store is a class that implements the `Store` interface."
    );
  }
  return config;
};
var handleAsyncErrors = (fn) => async (request, response, next) => {
  try {
    await Promise.resolve(fn(request, response, next)).catch(next);
  } catch (error) {
    next(error);
  }
};
var rateLimit = (passedOptions) => {
  const options = parseOptions(passedOptions != null ? passedOptions : {});
  if (typeof options.store.init === "function")
    options.store.init(options);
  const middleware = handleAsyncErrors(
    async (request, response, next) => {
      const skip = await options.skip(request, response);
      if (skip) {
        next();
        return;
      }
      const augmentedRequest = request;
      const key = await options.keyGenerator(request, response);
      const { totalHits, resetTime } = await options.store.increment(key);
      const retrieveQuota = typeof options.max === "function" ? options.max(request, response) : options.max;
      const maxHits = await retrieveQuota;
      augmentedRequest[options.requestPropertyName] = {
        limit: maxHits,
        current: totalHits,
        remaining: Math.max(maxHits - totalHits, 0),
        resetTime
      };
      if (options.legacyHeaders && !response.headersSent) {
        response.setHeader("X-RateLimit-Limit", maxHits);
        response.setHeader(
          "X-RateLimit-Remaining",
          augmentedRequest[options.requestPropertyName].remaining
        );
        if (resetTime instanceof Date) {
          response.setHeader("Date", new Date().toUTCString());
          response.setHeader(
            "X-RateLimit-Reset",
            Math.ceil(resetTime.getTime() / 1e3)
          );
        }
      }
      if (options.standardHeaders && !response.headersSent) {
        response.setHeader("RateLimit-Limit", maxHits);
        response.setHeader(
          "RateLimit-Remaining",
          augmentedRequest[options.requestPropertyName].remaining
        );
        if (resetTime) {
          const deltaSeconds = Math.ceil(
            (resetTime.getTime() - Date.now()) / 1e3
          );
          response.setHeader("RateLimit-Reset", Math.max(0, deltaSeconds));
        }
      }
      if (options.skipFailedRequests || options.skipSuccessfulRequests) {
        let decremented = false;
        const decrementKey = async () => {
          if (!decremented) {
            await options.store.decrement(key);
            decremented = true;
          }
        };
        if (options.skipFailedRequests) {
          response.on("finish", async () => {
            if (!options.requestWasSuccessful(request, response))
              await decrementKey();
          });
          response.on("close", async () => {
            if (!response.writableEnded)
              await decrementKey();
          });
          response.on("error", async () => {
            await decrementKey();
          });
        }
        if (options.skipSuccessfulRequests) {
          response.on("finish", async () => {
            if (options.requestWasSuccessful(request, response))
              await decrementKey();
          });
        }
      }
      if (maxHits && totalHits === maxHits + 1) {
        options.onLimitReached(request, response, options);
      }
      if (maxHits && totalHits > maxHits) {
        if ((options.legacyHeaders || options.standardHeaders) && !response.headersSent) {
          response.setHeader("Retry-After", Math.ceil(options.windowMs / 1e3));
        }
        options.handler(request, response, next, options);
        return;
      }
      next();
    }
  );
  middleware.resetKey = options.store.resetKey.bind(options.store);
  return middleware;
};
var omitUndefinedOptions = (passedOptions) => {
  const omittedOptions = {};
  for (const k of Object.keys(passedOptions)) {
    const key = k;
    if (passedOptions[key] !== void 0) {
      omittedOptions[key] = passedOptions[key];
    }
  }
  return omittedOptions;
};
var lib_default = rateLimit;
module.exports = rateLimit; module.exports.default = rateLimit; module.exports.rateLimit = rateLimit; module.exports.MemoryStore = MemoryStore;
