src/core.js
const CustomError = require('./util/error');
const Database = require('better-sqlite3');
const path = require('path');
const { DataTypeDefaults } = require('./util/constants');
const Util = require('./util/util');
const Table = require('./table');
/**
* The base provider
*/
class Core {
/** @param {string} filepath The filepath to the DB */
constructor(filepath) {
/**
* The resolved path to the file
* @type {string}
*/
if (filepath !== null && typeof filepath !== 'undefined' && filepath.length > 0) this.filepath = path.resolve(path.dirname(require.main.filename), filepath);
else this.filepath = ':memory:';
/**
* The raw database
* @type {external:Database}
* @private
*/
this._db = null;
/**
* The names of the tables
* @type {Map<string, Table>}
*/
this.tables = new Map();
/**
* A map of prepared statements to use internally
* @type {Map<string, external:Statement>}
* @private
*/
this.prepared = new Map();
}
/**
* The raw database
* @type {external:Database}
* @readonly
*/
get db() {
return this._db;
}
/**
* Initializes the wrapper, and pulls all the table names into memory
* @returns {Core}
*/
init() {
this._db = new Database(this.filepath);
this.prepared.set('getTable', this.db.prepare('SELECT * FROM sqlite_master WHERE name=? AND type=\'table\''));
const tables = this.db.prepare('SELECT * FROM sqlite_master WHERE type=\'table\'').all();
for (const table of tables) {
this.tables.set(table.name, new Table({ core: this, name: table.name })._init());
}
return this;
}
/**
* Creates a new table
* @param {string} tableName The name of the table
* @param {TableOptions} options Options for the table
* @returns {Table}
*/
create(tableName, options) {
Util.checkTableName(tableName);
const parsedOptions = this._parseOptions(options);
this.db.prepare(`CREATE TABLE IF NOT EXISTS ${tableName}${parsedOptions}`).run();
const tableInfo = this.prepared.get('getTable').get(tableName);
this.tables.set(tableInfo.name, new Table({
core: this,
name: tableInfo.name
}));
return this.get(tableName);
}
/**
* Parses options for creating a table
* @param {TableOptions} options Options to parse
* @returns {ParsedTableOptions}
* @private
*/
_parseOptions(options) {
if (typeof options !== 'object' && !(options instanceof Array)) throw new CustomError(`The table options must be an array, found type "${typeof options}"`, 'INVALID_OPTIONS');
if (!options.length) throw new CustomError('Options must be provided', 'INVALID_OPTIONS');
const optArr = [];
for (let keyOptions of options) {
const keyArr = [];
keyOptions = Util.mergeDefault(DataTypeDefaults, keyOptions);
if (!keyOptions.name) throw new CustomError('A key must have a name', 'INVALID_KEY_NAME');
if (typeof keyOptions.name !== 'string') throw new CustomError(`A name must be of type string, found type "${typeof keyOptions.name}"`, 'INVALID_KEY_NAME');
keyArr.push(keyOptions.name);
keyArr.push(keyOptions.type);
if (keyOptions.primary) keyArr.push('PRIMARY KEY');
if (keyOptions.unique) keyArr.push('UNIQUE');
if (keyOptions.nullable != null) { // eslint-disable-line eqeqeq
keyArr.push(keyOptions.nullable ? 'NULL' : 'NOT NULL');
}
optArr.push(keyArr.join(' '));
}
let optStr = optArr.join(', ');
optStr = `(${optStr})`;
return optStr;
}
/**
* Gets a table
* @param {string} tableName The name of the table
* @returns {Table}
*/
get(tableName) {
Util.checkTableName(tableName);
const table = this.tables.get(tableName);
if (!table) throw new CustomError(`The table ${tableName} doesn't exists`, 'INVALID_TABLE');
return table;
}
/**
* Deletes a table, and drops it from the database
* @param {string} tableName The name of the table
* @param {boolean} [drop=false] Whether to drop the table from the database
* @returns {null}
*/
delete(tableName, drop = false) {
Util.checkTableName(tableName);
const table = this.tables.get(tableName);
if (!table) throw new CustomError(`The table ${tableName} doesn't exists`, 'INVALID_TABLE');
this.db.prepare(`DELETE FROM ${tableName}`).run();
if (drop) this.db.prepare(`DROP TABLE ${tableName}`).run();
this.tables.delete(tableName);
return null;
}
/**
* When concatenated with a string, this returns the raw databases name
* @returns {string}
*/
toString() {
return this.db.name;
}
/**
* Defines the JSON.stringify() behavior
* @private
*/
toJSON() {
return Util.flatten(this);
}
valueOf() {
return this.db.name;
}
}
module.exports = Core;
/**
* @external Database
* @see {@link https://github.com/JoshuaWise/better-sqlite3/blob/master/docs/api.md#class-database}
*/
/**
* The parsed table options
* @private
* @typedef {string} ParsedTableOptions
*/
/**
* Options when creating a table
* @typedef {Array<KeyOptions>} TableOptions
*/
/**
* @external Statement
* @see {@link https://github.com/JoshuaWise/better-sqlite3/blob/master/docs/api.md#class-statement}
*/