UNPKG

imago-sql-memory-cache

Version:

Caches entire SQL tables in the memory of the Node.js process.

127 lines (105 loc) 4.58 kB
'use strict'; const Sql = require('imago-sql'); /** * Caches the entire SQL table in memory, under global.tables, and periodically * refreshes it. Use it for small, but frequently accessed tables to improve * performance and reduce SQL server load. */ class ImagoSqlMemoryCache { constructor() { this.configs = {}; /** Processed copies of SQL data */ global.sqlCache = global.sqlCache || { }; /** Unprocessed copies of SQL data */ global.tables = global.tables || {}; /** SQL connections */ global.sql = global.sql || {}; /** Promises that help other code know that it needs to await before the * SQL data is fetched. */ global.promises = global.promises || {}; /** Global counter of SQL connection errors. */ global.sqlErrors = global.sqlErrors || {}; } /** * Adds a new SQL table to the list of auto-refreshed tables. * @param {object} options - The parameters of the table to be added to the autorefresh list. * @param {string} options.connString - The SQL connection string in the mssql:// format. * @param {string} options.table - The name of the SQL table. * @param {number} options.refreshEvery - Refresh period in ms. * @param {function} options.callback - The callback to run on the data. */ add(options) { // Cache the settings in case someone wants to refresh them. this.configs[options.table] = options; this.refresh(options); console.log(`We will be refreshing the table ${options.table} every ${Math.round(options.refreshEvery / 1000)} seconds.`); } /** * Updates the cached data in an SQL table. * @param {object} options * @param {string} options.connString - Connection string in mssql:// format * @param {string} options.table - Name of SQL table * @param {number} options.refreshEvery - How often to refresh the data from * @param {function} options.callback - Callback to run once data is refreshed. * SQL, in ms. * @param {boolean} once - Only refresh once. Useful after you've updated * the tables and want to refresh it immediately. */ async refresh(options, once = false) { let resolveFunction; try { // Initialize DB connection and cache it globally: if (!global.sql[options.connString]) { global.sql[options.connString] = new Sql(options.connString); global.sqlErrors[options.connString] = 0; console.log('Initialized connection to ', options.connString); } // If the app service is just starting, delay DB queries by creating // a Promise to delay the processing of the first message until // we finish creating the data. Other code needs to await the promise from // global.promises before proceeding. if (!global.tables[options.table]) { global.promises[options.table] = new Promise(resolve => { resolveFunction = resolve; // Just in case we never resolve for some reason, use a timeout here: setTimeout(resolve, 30 * 1000); }); } // Fetch data from SQL: let data = await global.sql[options.connString].selectAll(options.table); global.tables[options.table] = data; // console.log(`Refreshed SQL table ${options.table}`); if (typeof options.callback === 'function') { await options.callback(options.table); } // Once data is preloaded, resolve the Promise holding the first // request to the endpoint: if (resolveFunction) { resolveFunction(); } } catch (error) { console.log(`Error when refreshing SQL table ${options.table}: `, error); // After a few errors, the connection will be reinitialized by another // piece of code: global.sqlErrors[options.connString]++; if (resolveFunction) { resolveFunction(); } } // No matter the outcome, requeue the same operation again, // because we will keep refreshing/retrying forever - unless a flag is set // that says otherwise. if (!once) { setTimeout(() => this.refresh(options), options.refreshEvery); } } /** * Refreshes the given table once - useful if we just updated it. * @param {string} tableName - The SQL table name. */ async refreshOnce(tableName) { console.log(`Refreshing SQL table once: ${tableName}.`); await this.refresh(this.configs[tableName], true); } } module.exports = ImagoSqlMemoryCache;