UNPKG

@ayanaware/bento

Version:

Modular runtime framework designed to solve complex tasks

177 lines 6.13 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.VariableFileLoader = void 0; const fs = require("fs"); const path = require("path"); const util_1 = require("util"); const errors_1 = require("@ayanaware/errors"); const logger_api_1 = require("@ayanaware/logger-api"); const VariableLoader_1 = require("./VariableLoader"); const log = logger_api_1.Logger.get(); const accessAsync = (0, util_1.promisify)(fs.access); const readFileAsync = (0, util_1.promisify)(fs.readFile); /** * Allows you to load Bento Variables and set their defaults from files * If you have a custom file format or prefer to use something other than JSON * You can provide a custom `parseFileContents` function. Just return Key/Value pairs * derived from the file Buffer. * * Keep in mind that if you have key `HELLO_WORLD` in a file or defaults file and `HELLO_WORLD` in the enviorment * the value from the enviorment will take priority */ class VariableFileLoader extends VariableLoader_1.VariableLoader { /** * @param watching Enable file watching? (Automatic hot-loading of variables in files) */ constructor(watching = true) { super(); this.name = 'VariableFileLoader'; /** * Registered files and Variables they loaded */ this.files = new Map(); /** * Registered fs Watchers */ this.watchers = new Map(); this.watching = watching; } /** * Add Multiple Variable Files * @param files Array of File Locations * @param defaults Defaults Mode * * @returns Array<Path> */ async addFiles(files, defaults) { const results = []; for (const file of files) { const location = await this.addFile(file, defaults); results.push(location); } return results; } /** * Add Variables File * @param location File Location * @param defaults Defaults Mode * * @throws ProcessingError If `fs.access` check fails and `defaults` is true * @returns Path */ async addFile(location, defaults = false) { const abs = path.resolve(...location); try { await accessAsync(abs, fs.constants.F_OK); } catch (e) { // defaults files are "required", so bubble error if (defaults) throw new errors_1.ProcessingError(`addFile(): Defaults file "${abs}" access check failed`).setCause(e); log.warn(`addFile(): Ignoring File "${abs}", access check failed`); return; } await this.processFile(abs, defaults); // start watcher if (this.watching) this.addWatcher(abs, defaults); return abs; } /** * Remove Variables file * @param location File Location * @param purge Purge Variables that this file Added */ removeFile(location, purge) { const abs = path.resolve(...location); if (!this.files.has(abs)) return; // purge requested, unload all variables we contributed if (purge) { const variables = this.files.get(abs); for (const variable of variables) this.unloadVariable(variable); } // close watcher if exist const watcher = this.watchers.get(abs); if (watcher) { watcher.close(); this.watchers.delete(abs); } this.files.delete(abs); } addWatcher(location, defaults) { try { const watcher = fs.watch(location, (event, filename) => { if (event !== 'change') return; if (!filename) return; this.processFile(location, defaults).then(() => { log.info(`Watcher "${location}": Successfully reprocessed file`); }).catch(e => { log.error(`Watcher "${location}" Error: ${e}`); }); }); this.watchers.set(location, watcher); } catch (e) { throw new Error(`Watcher "${location}" Failed to register`); } } /** * Read and return file contents * @param location File Location * * @returns File Buffer */ async getFileContents(location) { try { await accessAsync(location, fs.constants.F_OK); return readFileAsync(location); } catch (e) { throw new errors_1.ProcessingError(`getFileContents(): Failed for "${location}"`).setCause(e); } } /** * Convert File Buffer into Key/Value pairings * @param data File Buffer * * @returns Key/Value Pairings Object */ parseFileContents(data) { try { return JSON.parse(data.toString()); } catch (e) { throw new errors_1.ProcessingError('Failed to parse JSON').setCause(e); } } async processFile(location, defaults) { const data = await this.getFileContents(location); const pairings = this.parseFileContents(data); // file/variable ownership if (!this.files.has(location)) this.files.set(location, new Set()); for (const [key, fileValue] of Object.entries(pairings)) { // register variable this.variables.add(key); this.files.get(location).add(key); // add default if eligable if (defaults && fileValue !== undefined) this.defaults.set(key, fileValue); // process value let value = fileValue; // env override & defaults const override = this.getVariableValue(key); if (override !== undefined) value = override; if (value === undefined && this.defaults.has(key)) value = this.defaults.get(key); this.loadVariable(key, value); } } } exports.VariableFileLoader = VariableFileLoader; //# sourceMappingURL=VariableFileLoader.js.map