UNPKG

hibernot

Version:

Solution for hibernation using dynamic pings to your free tier backend services

190 lines (189 loc) 8.37 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Hibernot = exports.HibernotError = void 0; // Custom error class for Hibernot-specific errors. // Use this for all errors thrown by the Hibernot library, so consumers can distinguish them from other errors. class HibernotError extends Error { constructor(message) { super(message); this.name = 'HibernotError'; } } exports.HibernotError = HibernotError; /** * Hibernot * * Main class that manages inactivity detection and keep-alive logic. * * Usage: Instantiate with a config object, then use the middleware() in your Express app. * * This class is designed to help keep your service "warm" by calling a keep-alive function * after a period of inactivity, with optional retry logic. */ class Hibernot { /** * Constructor * Validates and sets up configuration, then starts the inactivity timer. * * @param config - HibernotConfig object (see type above) */ constructor(config) { // Tracks the number of times registerActivity() has been called (i.e., API hits). this.activityCount = 1; // Stores the timestamp (ms since epoch) of the last activity. this.lastActivityTimestamp = Date.now(); // Holds the reference to the current inactivity timer (Node.js Timeout object). this.inactivityTimeout = null; // Validate inactivityLimitMs: must be a positive number. if (typeof config.inactivityLimit !== 'number' || config.inactivityLimit <= 0) { throw new HibernotError('inactivityLimit must be a positive number'); } // Validate keepAliveFn: must be a function. if (!config.keepAliveFn || typeof config.keepAliveFn !== 'function') { throw new HibernotError('keepAliveFn must be a valid async function'); } // Validate maxRetryAttempts: if provided, must be a non-negative number. if (config.maxRetryAttempts !== undefined && (typeof config.maxRetryAttempts !== 'number' || config.maxRetryAttempts < 0)) { throw new HibernotError('maxRetryAttempts must be a non-negative number'); } // Set config, providing a default for maxRetryAttempts if not specified. this.config = Object.assign(Object.assign({}, config), { maxRetryAttempts: config.maxRetryAttempts !== undefined ? config.maxRetryAttempts : 3 }); // Start the inactivity timer. this.start(); } /** * Express-style middleware generator. * * Use this in your Express app to automatically register activity on each request. * Example: app.use(hibernotInstance.middleware()); */ middleware() { return (req, res, next) => { this.registerActivity(); next(); }; } /** * Registers an activity (e.g., API hit). * Increments the activity counter, updates the last activity timestamp, and resets the inactivity timer. * Call this manually if not using the middleware. */ registerActivity() { this.activityCount++; this.lastActivityTimestamp = Date.now(); this.resetInactivityTimeout(); } /** * Starts (or restarts) the inactivity timer. * Call this if you want to manually restart the inactivity detection logic. */ start() { this.resetInactivityTimeout(); } /** * Resets the activity counter to zero. * Useful for monitoring or testing purposes. */ resetActivityCount() { this.activityCount = 0; } /** * (Private) Resets the inactivity timer. * If the timer expires, checks if inactivityLimitMs has passed since last activity. * If so, logs a message, calls keepAliveFn (with retries), and registers a new activity. * Always resets the timer at the end (recursively). * * Dev note: If you want to change the inactivity logic, modify this method. */ resetInactivityTimeout() { return __awaiter(this, void 0, void 0, function* () { if (this.inactivityTimeout) { clearTimeout(this.inactivityTimeout); } this.inactivityTimeout = setTimeout(() => __awaiter(this, void 0, void 0, function* () { try { const msSinceLastActivity = Date.now() - this.lastActivityTimestamp; if (msSinceLastActivity >= this.config.inactivityLimit) { console.log(`[${this.config.instanceName || 'Hibernot'}] No activity for ${this.config.inactivityLimit / 1000} seconds. Executing keepAliveFn...`); yield this.executeKeepAliveWithRetries(); // After keepAliveFn, register a new activity to reset the inactivity window. this.registerActivity(); } } catch (err) { // Log error if keepAliveFn fails after all retries. console.error(`[${this.config.instanceName || 'Hibernot'}] Keep-alive function failed after retries:`, err); } finally { // Always reset the timer for the next inactivity window. this.resetInactivityTimeout(); } }), this.config.inactivityLimit); }); } /** * (Private) Executes keepAliveFn, retrying up to maxRetryAttempts times if it fails. * Waits 1 second between retries. * Throws the last error if all retries fail. * * Dev note: To change retry logic or backoff, edit this method. */ executeKeepAliveWithRetries() { return __awaiter(this, void 0, void 0, function* () { var _a; let lastError = null; const maxAttempts = (_a = this.config.maxRetryAttempts) !== null && _a !== void 0 ? _a : 3; for (let attempt = 1; attempt <= maxAttempts; attempt++) { try { yield this.config.keepAliveFn(); return; // Success, exit function. } catch (err) { lastError = err; console.warn(`[${this.config.instanceName || 'Hibernot'}] Keep-alive attempt ${attempt} failed:`, err); // Wait 1 second before next retry, unless this was the last attempt. if (attempt < maxAttempts) { yield new Promise(resolve => setTimeout(resolve, 1000)); } } } // All retries failed, throw the last error. throw lastError || new HibernotError('keepAliveFn failed after all retries'); }); } /** * Returns statistics about the Hibernot instance: * - activityCount: number of registered activities (API hits) * - lastActivityTimestamp: timestamp of last activity * - instanceName: instance name (or 'Unnamed' if not set) * * Dev note: Extend this if you want to expose more metrics. */ getStats() { return { activityCount: this.activityCount, lastActivityTimestamp: this.lastActivityTimestamp, instanceName: this.config.instanceName || 'Unnamed', }; } /** * Stops the inactivity timer, preventing further keepAliveFn calls. * Call this if you want to disable inactivity detection (e.g., during shutdown). */ stop() { if (this.inactivityTimeout) { clearTimeout(this.inactivityTimeout); this.inactivityTimeout = null; } } } exports.Hibernot = Hibernot;