@lionrockjs/central
Version:
Node.js MVC framework inspire from PHP Kohana Framework
275 lines (245 loc) • 8.14 kB
JavaScript
/**
* Copyright (c) 2023 Kojin Nakana
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
import Central from './Central.mjs';
import Model from './Model.mjs';
/**
* ORM option.
* @typedef {object} ORMOption
* @property {Database} [database] - The database to use.
* @property {DatabaseAdapter} [adapter] - Database adapter for SQLite, MariaDB, postgresql, etc.
* @property {string} [insertID] - The ID of the record will be inserted.
* @property {number} [limit] - The limit of the record to read.
* @property {number} [offset] - The offset of the record to read.
* @property {string} [orderBy] - The order of the record to read.
* @property {boolean} [asArray] - Return the result as an array.
* @property {string[]} [columns] - The columns to read.
*/
export default class ORM {
static classPrefix = 'model/';
/**
* @param {typeof Model} MClass
* @param {...ORMOption} options
* @returns {Model}
*/
static create(MClass, options = {}) {
return new MClass(null, options);
}
/**
* Create and read data from database
* @param {typeof Model} MClass
* @param id
* @param {...ORMOption} options
* @returns {Promise< Model | null >}
*/
static async factory(MClass, id, options = {}) {
const m = new MClass(id, options);
await m.read(options.columns);
return m;
}
static #collection(MClass, options = {}) {
const m = ORM.create(MClass, options);
return m.getCollection();
}
static async #readResult(result, m, creator, asArray){
if (asArray) return result.map(creator);
if (result.length === 0) return null;
if (result.length === 1) return Object.assign(m, result[0]);
return result.map(creator);
}
// Collection methods
/**
* read all records from the model
* @param {typeof Model} MClass
* @param {...ORMOption} options
* @returns {Promise< Model[] | Model | null >}
*/
static async readAll(MClass, options = {}) {
const m = ORM.create(MClass, options);
const result = await m.getCollection().readAll(options.columns);
return this.#readResult(result, m, x => Object.assign(ORM.create(MClass, options), x), options.asArray);
}
/**
*
* @param {typeof Model} MClass
* @param {string} key
* @param {string[] | number[]} values
* @param {...ORMOption} options
* @returns {Promise< Model[] | Model | null >}
*/
static async readBy(MClass, key, values, options = {}) {
const m = ORM.create(MClass, options);
const result = await m.getCollection().readBy(key, values, options.columns) || [];
return this.#readResult(result, m, x => Object.assign(ORM.create(MClass, options), x), options.asArray);
}
/**
* Given criterias [['', 'id', SQL.EQUAL, 11], [SQL.AND, 'name', SQL.EQUAL, 'peter']]
* @param {typeof Model} MClass
* @param {[string[]]} criteria
* @param {...ORMOption} options
* @returns {Promise< Model[] | Model | null >}
*/
static async readWith(MClass, criteria = [], options = {}) {
if (criteria.length === 0) return [];
const m = ORM.create(MClass, options);
const result = await m.getCollection().readWith(criteria, options.columns) || [];
return this.#readResult(result, m, x => Object.assign(ORM.create(MClass, options), x), options.asArray);
}
/**
*
* @param {typeof Model} MClass
* @param {...ORMOption} options
* @returns {Promise<number>}
*/
static async countAll(MClass, options = {}) {
return await this.#collection(MClass, options).countAll();
}
/**
*
* @param {typeof Model} MClass
* @param {string} key
* @param {string[]} values
* @param {...ORMOption} options
* @returns {Promise<number>}
*/
static async countBy(MClass, key, values, options = {}) {
return await this.#collection(MClass, options).countBy(key, values);
}
/**
* Given criterias [['', 'id', SQL.EQUAL, 11], [SQL.AND, 'name', SQL.EQUAL, 'peter']]
* @param {typeof Model} MClass
* @param {[string[]]} criteria
* @param {...ORMOption} options
* @returns {Promise<number>}
*/
static async countWith(MClass, criteria, options = {}) {
if (!criteria || criteria.length === 0) throw new Error(`${MClass.constructor.name} count with no criteria`);
return await this.#collection(MClass, options).countWith(criteria);
}
/**
*
* @param {typeof Model} MClass
* @param {...ORMOption} options
* @returns {Promise<void>}
*/
static async deleteAll(MClass, options = {}) {
await this.#collection(MClass, options).deleteAll(options.kv);
}
/**
*
* @param {typeof Model} MClass
* @param {string} key
* @param {string|number[]} values
* @param {...ORMOption} options
* @returns {Promise<void>}
*/
static async deleteBy(MClass, key, values, options = {}) {
await this.#collection(MClass, options).deleteBy(key, values);
}
/**
* Given criterias [['', 'id', SQL.EQUAL, 11], [SQL.AND, 'name', SQL.EQUAL, 'peter']]
* @param {typeof Model} MClass
* @param {[string[]]}criteria
* @param {...ORMOption} options
* @returns {Promise<void>}
*/
static async deleteWith(MClass, criteria, options = {}) {
if (!criteria || criteria.length === 0) throw new Error(`${MClass.name} delete with no criteria`);
const m = ORM.create(MClass, options);
return m.getCollection().deleteWith(criteria);
}
/**
* @param {typeof Model} MClass
* @param {Map} kv
* @param {Map} columnValues
* @param {...ORMOption} options
*/
static async updateAll(MClass, kv, columnValues, options = {}) {
const m = ORM.create(MClass, options);
await m.getCollection().updateAll(kv, columnValues);
}
/**
*
* @param {typeof Model} MClass
* @param {...ORMOption} options
* @param {string} key
* @param {[]} values
* @param {Map} columnValues
* @returns {Promise<void>}
*/
static async updateBy(MClass, key, values, columnValues, options = {}) {
const m = ORM.create(MClass, options);
return m.getCollection().updateBy(key, values, columnValues);
}
/**
* Given criterias [['', 'id', SQL.EQUAL, 11], [SQL.AND, 'name', SQL.EQUAL, 'peter']]
* @param {typeof Model} MClass
* @param {...ORMOption} options
* @param {[[string]]}criteria
* @param {Map} columnValues
* @returns {Promise<*>}
*/
static async updateWith(MClass, criteria, columnValues, options = {}) {
if (!criteria || criteria.length === 0) throw new Error(`${MClass.name} update with no criteria`);
if (!columnValues || columnValues.size === 0) throw new Error(`${MClass.name} update without values`);
await this.#collection(MClass, options).updateWith(criteria, columnValues);
}
/**
*
* @param {typeof Model} MClass
* @param {...ORMOption} options
* @param {string[]} columns
* @param {[String[]]} values
* @returns {Promise<void>}
*/
static async insertAll(MClass, columns, values, options = {}) {
// verify columns
columns.forEach(x => {
if (x === 'id') return;
if (!MClass.fields.has(x) && !MClass.belongsTo.has(x)) throw new Error(`${MClass.name} insert invalid columns ${x}`);
});
await this.#collection(MClass, options).insertAll(columns, values);
}
/**
*
* @param modelName
* @param defaultMClass
* @returns {Promise<typeof Model>}
*/
static async import(modelName, defaultMClass=Model){
try{
return await Central.import(ORM.classPrefix + modelName);
}catch(e){
if(defaultMClass === Model)throw e;
return defaultMClass;
}
}
/**
*
* @param {Model[]} orms
* @param {Object} eagerLoadOptions
* @returns {Promise<ORM[]>}
*/
static async eagerLoad(orms, eagerLoadOptions){
if(orms.length < 1)return [];
if(!eagerLoadOptions.with)return [];
await Promise.all(
orms.map(async it => {
await it.eagerLoad(eagerLoadOptions);
})
);
}
/**
*
* @param {Model} model
* @returns {Promise<Model>}
*/
static async write(model){
return model.write();
}
}
Object.freeze(ORM.prototype);