databases/json.js
const {Connection, Database, Table, Key} = require('./generic');
const fs = require('fs');
const Promise = require('bluebird');
const debug = require('debug')('database:json');
/**
* Options for a {@link JSONConnection}.
* @typedef {Object} JSONConnectionOptions
* @property {String} filePath The file path to a JSON file. Encoding must be UTF-8.
*/
/**
* Options for a {@link JSONDatabase}.
* @typedef {Object} JSONDatabaseOptions
* @property {JSONTable[]} tables The tables in this database.
*/
/**
* Options for a {@link JSONTable}.
* @typedef {Object} JSONTableOptions
* @property {String} name The name of the table.
* @property {JSONKey[]} keys The keys in this table.
*/
/**
* Options for a {@link JSONKey}.
* @typedef {Object} JSONKeyOptions
* @property {String} name The name of the key.
* @property {Any} value The value of the key.
*/
/**
* A connection to a JSON file.
* @extends Connection
*/
module.exports.JSONConnection = class JSONConnection extends Connection {
/**
* Creates a new {@link JSONConnection}.
* @param {JSONConnectionOptions} options The options for this connection.
*/
constructor(options) {
super(options);
debug('debugging is enabled');
}
/**
* Loads the JSON file.
* @returns {Promise<JSONDatabase>} A promise that, when resolves, returns the JSONDatabase object. Rejects on error with an error object.
*/
connect() {
return new Promise((resolve, reject) => {
fs.readFile(this.options.filePath, {encoding: 'utf-8'}, (err, data) => {
if (err) {
debug('error occured while loading database, rejecting promise');
reject(err);
return;
}
// load json
debug('loading JSON');
let json = JSON.parse(data);
// create temp arrays
let tempTables = [];
let tempKeys = [];
// read the JSON and get tables
// this does a similar thing to JSONDatabase#toJSON, but goes from JSON to the classes.
debug(json);
Object.keys(json).forEach(table => {
debug('loading table ' + table);
// get all keys in this table
Object.keys(json[table]).forEach(key => {
// push to a temp array for later
debug('loading key ' + key);
tempKeys.push(new exports.JSONKey({
name: key,
value: json[table][key]
}));
});
// use tempKeys to build a JSONTable object and push that to the tempTables array
debug('adding table to the final table array with keys');
tempTables.push(new exports.JSONTable({
name: table,
keys: tempKeys
}));
// rinse and repeat
});
// create database
debug('creating database');
this.options.database = new exports.JSONDatabase({
tables: tempTables
});
// resolve!
debug('finished');
resolve(this.options.database);
});
});
}
/**
* Saves the data.
* @returns {Promise} A promise that resolves if the data saved successfully. Rejects if there was an error.
*/
save() {
return new Promise((resolve, reject) => {
debug('saving data');
fs.writeFile(this.options.filePath, this.options.database.toJSON(), err => {
if (err) reject(err);
debug('saved data');
resolve();
});
});
}
}
/**
* A database stored in a JSON file.
* @extends Database
*/
module.exports.JSONDatabase = class JSONDatabase extends Database {
/**
* Creates a new {@link JSONDatabase}.
* @param {JSONDatabaseOptions} options The options for this database.
*/
constructor(options) {
super(options);
}
/**
* Gets the tables in this database. See {@link JSONTable}
*/
get tables() {
return this.options.tables;
}
/**
* Gets a table in this database.
* @param {String} name The name of the table.
* @returns {JSONTable|null} The table, if found. Otherwise, null.
*/
table(name) {
for (let i = 0; i <= this.options.tables.length; i++) {
if (this.options.tables[i].name === name) {
return this.options.tables[i];
}
}
return null;
}
/**
* Add a table to the database.
* @param {JSONTable} table The table to add to this database.
*/
add(table) {
this.options.tables.push(table);
}
/**
* Gets the JSON string for this database.
* @returns {String} The JSON string.
*/
toJSON() {
// create a blank array to store the table data in
var base = {};
debug('converting database to JSON');
this.tables.forEach(table => {
debug('loading table ' + table.name);
// create a blank object to store each key in
let tableData = {};
table.keys.forEach(key => {
debug('loading key ' + key.name);
// add a key to the data
tableData[key.name] = key.value;
});
// push the table data to the array
debug('table data for table ' + table.name + ' is ' + require('util').inspect(tableData));
debug('writing table');
base[table.name] = tableData;
// rinse and repeat
});
return JSON.stringify(base);
}
}
/**
* A table.
* @extends Table
*/
module.exports.JSONTable = class JSONTable extends Table {
/**
* Creates a new {@link JSONTable}.
* @param {JSONTableOptions} options The options for this table.
*/
constructor(options) {
super(options);
}
/**
* Gets the name of the table.
*/
get name() {
return this.options.name;
}
/**
* Gets the keys in this table. See {@link JSONKey}.
*/
get keys() {
return this.options.keys;
}
/**
* Gets a key in this table.
* @param {String} name The name of the key.
* @returns {JSONKey|null} The key, if found. Otherwise, null.
*/
key(name) {
for (let i = 0; i <= this.options.keys.length; i++) {
if (this.options.keys[i].name === name) {
return this.options.keys[i];
}
}
return null;
}
/**
* Add a key to the table.
* @param {JSONKey} key The key to add to this table.
*/
add(key) {
this.options.keys.push(key);
}
}
/**
* A key.
* @extends Key
*/
module.exports.JSONKey = class JSONKey extends Key {
/**
* Creates a new {@link JSONKey}.
* @param {JSONKeyOptions} options The options for this key.
*/
constructor(options) {
super(options);
}
/**
* Gets the name of the key.
*/
get name() {
return this.options.name;
}
}