@ayanaware/bento
Version:
Modular runtime framework designed to solve complex tasks
177 lines • 6.13 kB
JavaScript
"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