cache/client.js

/*
 *  Author: Vlad Seryakov vseryakov@gmail.com
 *  backendjs 2018
 */

const { EventEmitter } = require("events");
const logger = require(__dirname + '/../logger');
const lib = require(__dirname + '/../lib');
const metrics = require(__dirname + '/../metrics');

/**
 * Base class for the cache clients, implements cache protocol in the same class,
 * not supported methods just do nothing without raising any errors
 * @param {object} [options]
 * @memberOf module:cache
 */

class CacheClient extends EventEmitter {
    name = "cache client"
    cacheName = ""
    options = {}

    constructor(options) {
        super();
        this.setMaxListeners(0);
        this.url = String(options?.url || "");
        this.metrics = new metrics.Timer();
        this.applyOptions(options);
        this.on("ready", () => { this.ready = true });
        logger.debug("client:", this.url, this.options);
    }

    /**
     * Close current connection, ports.... not valid after this call
     */
    close() {
        this.url = "";
        this.options = {};
        this.metrics.end();
        this.removeAllListeners();
    }

    /**
     * Prepare options to be used safely, parse the reserved params that start with **bk-** from the url and store in the options
     * @param {object} options
     */
    applyOptions(options) {
        for (const p in options) {
            if (p[0] != "_" && p != "url") this.options[p] = options[p];
        }
        const h = URL.parse(this.url);
        if (!h) return;
        this.port = h.port || 0;
        this.protocol = h.protocol;
        this.hostname = h.hostname || "";
        this.pathname = h.pathname || "";
        for (const [key, val] of h.searchParams) {
            if (!key.startsWith("bk-")) continue;
            this.options[key.substr(3)] = lib.isNumeric(val) ? lib.toNumber(val) : val;
            h.searchParams.delete(key);
        }
        this.url = h.toString();
    }

    /**
     * Handle reserved options
     @param {object} options
     */
    applyReservedOptions(options) {}

    /**
     * Returns the cache statistics to the callback as the forst argument, the object tructure is specific to each implementstion
     * @param {object} [options]
     * @param {function} [callback]
     */
    stats(options, callback) {
        lib.tryCall(callback);
    }

    // CACHE MANAGEMENT

    /**
     * Clear all or only matched keys from the cache
     * @param {string} pattern
     * @param {function} [callback]
     */
    clear(pattern, callback) {
        lib.tryCall(callback);
    }

    /**
     * Returns an item from the cache by a key, callback is required and it acceptes only the item,
     * on any error null or undefined will be returned
     * @param {string} key
     * @param {object} options
     * @param {function} callback
     */
    get(key, options, callback) {
        lib.tryCall(callback);
    }

    /**
     * Store an item in the cache
     * @param {string} key
     * @param {string} val
     * @param {object} options
     * @param {int} [options.ttl] - TTL in milliseconds
     * @param {function} [callback]
     */
    put(key, val, options, callback) {
        lib.tryCall(callback);
    }

    /**
     * Add/substract a number from the an item, returns new number in the callback if provided, in case of an error null/indefined should be returned
     * @param {string} key
     * @param {number} val
     * @param {object} options
     * @param {function} [callback]
     */
    incr(key, val, options, callback) {
        lib.tryCall(callback, null, 0);
    }

    /**
     * Delete an item from the cache
     * @param {string} key
     * @param {object} options
     * @param {function} [callback]
     */
    del(key, options, callback) {
        lib.tryCall(callback);
    }

    // LOCKING MANAGEMENT

    /**
     * Lock by name
     * by default return an error
     * @param {string} name
     * @param {object} options
     * @param {function} [callback]
     */
    lock(name, options, callback) {
        logger.error("lock:", "NOT IMPLEMENTED", this.name, name, options);
        lib.tryCall(callback, { status: 500, message: "not implemented" });
    }

    /**
     * Unlock by name
     * @param {string} name
     * @param {object} options
     * @param {function} [callback]
     */

    unlock(name, options, callback) {
        lib.tryCall(callback);
    }

    // RATE CONTROL

    /**
     * Rate limit check, by default it uses the server LRU cache meaning it works within one physical machine only.
     *
     * @param {string} key
     * @param {number} val
     * @param {object} options - same as for {@link module:metrics.TokenBucket} rate limiter
     * @param {string} options.name - unique id, can be IP address, account id, etc...
     * @param {int} options.rate - rate per interval
     * @param {int} options.interval - in milliseconds
     * @param {int} [options.max]
     * @param {function} [callback] - arguments must be:
     * - 1st arg is a delay to wait till the bucket is ready again,
     * - 2nd arg is an object with the bucket state: { delay:, count:, total:, elapsed: }
     */
    limiter(options, callback) {
        logger.error("limiter:", "NOT IMPLEMENTED", this.name, options);
        lib.tryCall(callback, 60000, {});
    }

}

module.exports = CacheClient;