devlien
Version:
Devlien is a lightweight, zero-dependency Node.js framework with clean MVC structure, built-in ORM, and intuitive routing for rapid backend development.
411 lines (287 loc) • 9.43 kB
JavaScript
import Database from "../Database/Mysql.js";
import Relation from "../Eloquent/Relation.js";
import collect from "devlien/collect";
export default class Model extends Relation {
#table = '';
#_where = '';
#_columns = ['*'];
#_orderBy = '';
#_join = '';
#_skip = '';
#_limit = '';
#_withSoftDete = false;
#successFn = false;
#attributes = {};
#sudo_keys = ['fillable', 'hidden'];
constructor(config={}){
super();
this.#table = config.table ? config.table : this.getTableName();
}
where(condition) {
if(Array.isArray(condition) && condition.length==3)
this.#_where = this.#_where + (this.#_where?' AND ':'') + `\`${condition[0]}\` ${condition[1]} '${condition[2]}'`;
else if(!Array.isArray(condition) && typeof condition == 'object'){
let str = [];
for(const key in condition){
if(key.split('.').length>1){
let _key = key.split('.').map((row)=>`\`${row}\``).join('.');
str.push(str.push(`${_key} = '${condition[key]}'`));
}
else
str.push(`\`${key}\` = '${condition[key]}'`);
}
this.#_where = this.#_where + (this.#_where?' AND ':'') + str.join(' and ');
}
return this;
}
static where(condition){
return (new this()).where(condition);
}
orderBy(entity, type){
this.#_orderBy = `ORDER BY ${entity} ${type}`;
return this;
}
static orderBy(entity, type){
return (new this()).orderBy(entity, type);
}
skip(_num=null){
this.#_skip = _num;
return this;
}
static skip(_num=null){
return (new this()).skip(_num);
}
limit(_num){
this.#_limit = _num;
return this;
}
static limit(_num){
return (new this()).limit(_num);
}
//join('t1', 't2', 't1.id', 't2.id')
join(...fields){
if(fields.length==3){
this.#_join += ` ${this.#_join? 'JOIN' : ''} ${fields[0]} ON ${fields[0]}.${fields[1]} = ${this.#table}.${fields[1]}`;
}
else {
this.#_join += ` ${this.#_join? 'JOIN' : ''} ${fields[0]} ON ${fields[0]}.${fields[2]} = ${fields[1]}.${fields[3]}`;
}
return this;
}
static join(...fields){
return (new this()).join(fields);
}
select(columns) {
this.#_columns = columns;
return this;
}
static select(columns){
return (new this()).select(columns);
}
async first() {
try{
this.#_limit = 1;
const [records] = await Database.instance().query(this.makeQuery('get'));
return collect(this.#geReformattedRecords(records)).first();
}
catch(e){
throw new Error(e);
}
}
static async first(){
return await (new this()).first();
}
async last() {
try{
this.orderBy('id', 'DESC');
this.#_limit = 1;
const [records] = await Database.instance().query(this.makeQuery('get'));
return collect(this.#geReformattedRecords(records)).last();
}
catch(e){
throw new Error(e);
}
}
static async last(){
return await (new this()).last();
}
async get() {
try{
var [records] = await Database.instance().query(this.makeQuery('get'));
return collect(this.#geReformattedRecords(records));
}
catch(e){
throw new Error(e);
}
}
static async get(){
return await (new this()).get();
}
async count(){
try{
let [count] = await Database.instance().query(this.makeQuery('count'));
return count[0].count;
}
catch(e){
throw new Error(e);
}
}
static async count(){
return await (new this()).count();
}
async create(data) {
try{
const columns = Object.keys(data);
const values = Object.values(data);
const [record] = await Database.instance().query(this.makeQuery('insert', columns), values);
let _data = this.where({'id':record.insertId}).first();
if(this.#successFn)
this.#successFn(this.where({'id':record.insertId}), _data);
return _data;
}
catch(e){
throw new Error(e);
}
}
static async create(data){
return await (new this()).create(data);
}
async update(data={}){
try{
await Database.instance().query(this.makeQuery('update', data));
let _data = this.first()
if(this.#successFn)
this.#successFn(this, _data);
return _data;
}
catch(e){
throw new Error(e);
}
}
static async update(data={}){
return await (new this()).update(data);
}
async delete(){
try{
if(this.softdelete){
await this.update({deleted_at:(new Date()).toISOString().split('T')[0]});
}
else
return await Database.instance().query(this.makeQuery('delete'));
}
catch(e){
throw new Error(e);
}
}
static async delete(){
return await (new this()).delete();
}
async truncate(){
try{
return await Database.instance().query(`TRUNCATE TABLE ${this.#table};`);
}
catch(e){
throw new Error(e);
}
}
static async truncate(){
return await (new this()).truncate();
}
withSoftDelete(slug=true) {
this.#_withSoftDete = slug;
return this;
}
static withSoftDelete(slug=true){
return (new this()).withSoftDelete(slug);
}
onlySoftDelete(is=true){
if(is){
this.#_withSoftDete = true;
this.#_where += `${this.#_where?' AND ' : ''} \`${this.#table}\`.deleted_at IS NOT NULL`;
}
return this;
}
static onlySoftDelete(is=true){
return (new this()).onlySoftDelete(is);
}
makeQuery(type='get', columns=[]) {
let query = "";
if(type=='get'){
if(this.softdelete && !this.#_withSoftDete){
this.#_where = this.#_where + `${this.#_where?' AND ':''} \`${this.#table}\`.\`deleted_at\` IS NULL`
}
columns = this.#_columns.join(',');
query = `
SELECT
${columns}
FROM
${this.#table}
${this.#_join ? 'JOIN '+ this.#_join : ''}
${this.#_where?' WHERE '+this.#_where:''}
${this.#_orderBy}
${this.#_limit ? ' LIMIT '+this.#_limit : ''}
${this.#_skip ? ' OFFSET '+this.#_skip : ''}
`;
}
else if(type=='insert')
{
const _columns = columns.map(k => `\`${k}\``).join(', ');
const placeholders = columns.map(() => '?').join(', ');
query = `INSERT INTO ${this.#table} (${_columns}) VALUES (${placeholders}) ${this.#_where?'where '+this.#_where:''} `;
}
else if(type=='update')
{
let _columns = []
for(const key in columns) {
if(key!='created_at' && key!='updated_at'){
_columns.push(`\`${key}\`=${columns[key]=='NULL'?'NULL':`'${columns[key]}'`}`)
}
};
query = `UPDATE ${this.#table} SET ${_columns.join(',')} ${this.#_where ? 'where '+this.#_where:''}`;
}
else if(type == 'delete'){
query = `DELETE FROM ${this.#table} ${this.#_where ? 'where '+this.#_where:''}`;
}
else if(type=='count'){
query = `SELECT COUNT(*) as count FROM ${this.#table} ${this.#_where ? 'where '+this.#_where:''}`;
}
return query.replace(/\s+/g, ' ').trim();
}
onSuccess(Fn=null) {
this.#successFn = Fn;
return this;
}
toObject(){
return Object.assign({}, this);
}
getTableName(){
const constructor = Object.getPrototypeOf(this).constructor;
return this.pluralize(this.toSnakeCase(constructor.name));
}
static instance() {
return new this();
}
getAttributes() {
return this.#attributes;
}
#geReformattedRecords(records) {
return records.map((record) => {
this.#attributes = record;
return this.#getValidAttributes();
});
}
#getValidAttributes() {
let formatted_data = {};
let rm = [...(this.hidden ? this.hidden : []), ...this.#sudo_keys];
for (const key in this.#attributes) {
if (rm.indexOf(key) < 0) {
formatted_data[key] = this.#attributes[key];
}
}
let _this = new this.constructor();
_this.#attributes = formatted_data;
delete _this.fillable;
delete _this.hidden;
return Object.assign(_this, formatted_data);
}
}