UNPKG

tspace-mysql

Version:

Tspace MySQL is a promise-based ORM for Node.js, designed with modern TypeScript and providing type safety for schema databases.

1,442 lines 203 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Model = void 0; const pluralize_1 = __importDefault(require("pluralize")); const DB_1 = require("./DB"); const Schema_1 = require("./Schema"); const AbstractModel_1 = require("./Abstracts/AbstractModel"); const Proxy_1 = require("./Handlers/Proxy"); const Relation_1 = require("./Handlers/Relation"); const Blueprint_1 = require("./Blueprint"); const State_1 = require("./Handlers/State"); const Cache_1 = require("./Cache"); const JoinModel_1 = require("./JoinModel"); const constants_1 = require("../constants"); let globalSettings = { softDelete: false, debug: false, uuid: false, timestamp: false, pattern: false, logger: { selected: false, inserted: false, updated: false, deleted: false, }, }; /** * * 'Model' class is a representation of a database table * @generic {Type} TS * @generic {Type} TR * @example * import { Model, Blueprint , TSchema , TRelation } from 'tspace-mysql' * * const schema = { * id : new Blueprint().int().primary().autoIncrement(), * uuid : new Blueprint().varchar(50).null(), * email : new Blueprint().varchar(50).null(), * name : new Blueprint().varchar(255).null(), * } * * type TS = TSchema<typeof schema> * type TR = TRelation<{}> * * class User extends Model<TS,TR> { * ...........configration * } * * const users = await new User().findMany() * console.log(users) */ class Model extends AbstractModel_1.AbstractModel { $cache; constructor($cache = Cache_1.Cache) { super(); this.$cache = $cache; /** * * @Get initialize for model */ this._initialModel(); /** * * @define Setup for model */ this.define(); this.boot(); return new Proxy(this, Proxy_1.proxyHandler); } /** * The 'global' method is used setting global variables in models. * @static * @param {GlobalSetting} settings * @example * Model.global({ * softDelete : true, * uuid : true, * timestamp : true, * debug : true * logger : { * selected : true, * inserted : true, * updated : true, * deleted : true * }, * }) * @returns {void} void */ static global(settings) { globalSettings = Object.assign({}, globalSettings, settings); return; } /** * The 'column' method is used keyof column in schema. * @param {string} column * @example * import { User } from '../User' * Model.column<User>('id') * @returns {string} column */ static column(column, { table = false } = {}) { if (table) { return new this().bindColumn(String(column)); } return column; } /** * The 'table' method is used get table name. * @static * @returns {string} name of the table */ static get table() { return new this().getTableName(); } /** * The 'formatPattern' method is used to change the format of the pattern. * @param {object} data { data , pattern } * @property {Record | string} data * @property {string} parttern * @returns {Record | string} T */ static formatPattern({ data, pattern, }) { const utils = new this().$utils; if (pattern === "snake_case") { if (typeof data === "string") { return data.replace(/([A-Z])/g, (str) => `_${str.toLocaleLowerCase()}`); } return utils.snakeCase({ ...data }); } if (pattern === "camelCase") { if (typeof data === "string") { return data.replace(/(.(_|-|\s)+.)/g, (str) => str[0] + str[str.length - 1].toUpperCase()); } return utils.camelCase({ ...data }); } return data; } /** * The 'instance' method is used get instance. * @override * @static * @returns {Model} instance of the Model */ static get instance() { return new this(); } /** * The 'cache' method is used get the functions from the Cache * @returns {TCache} cache */ static get cache() { return Cache_1.Cache; } useMiddleware(fun) { if (typeof fun !== "function") throw new Error(`this '${fun}' is not a function`); this.$state.set("MIDDLEWARES", [...this.$state.get("MIDDLEWARES"), fun]); return this; } /** * The 'globalScope' method is a feature that allows you to apply query constraints to all queries for a given model. * * @example * class User extends Model { * constructor() { * super() * this.globalScope(query => { * return query.where('id' , '>' , 10) * }) * } * } * @returns {void} void */ globalScope(callback) { const model = new Model().table(this.getTableName()); const repository = callback(model); if (repository instanceof Promise) throw new Error('"whereQuery" is not supported a Promise'); if (!(repository instanceof Model)) throw new Error(`Unknown callback query: '${repository}'`); this.$state.set("GLOBAL_SCOPE_QUERY", () => { const where = repository?.$state.get("WHERE") || []; const select = repository?.$state.get("SELECT") || []; const orderBy = repository?.$state.get("ORDER_BY") || []; const limit = repository?.$state.get("LIMIT") || null; if (where.length) { this.$state.set("WHERE", [ ...this.$state.get("WHERE"), [ this.$state.get("WHERE").length ? `${this.$constants("AND")}` : "", ...where, ].join(" "), ]); } if (select.length) { this.$state.set("SELECT", [...this.$state.get("SELECT"), ...select]); } if (orderBy.length) { this.$state.set("ORDER_BY", [ ...this.$state.get("ORDER_BY"), ...orderBy, ]); } if (limit != null) { this.$state.set("LIMIT", limit); } }); return this; } /** * The 'define' method is a special method that you can define within a model. * @example * class User extends Model { * define() { * this.useUUID() * this.usePrimaryKey('id') * this.useTimestamp() * this.useSoftDelete() * } * } * @returns {void} void */ define() { } /** * The 'boot' method is a special method that you can define within a model. * @example * class User extends Model { * boot() { * this.useUUID() * this.usePrimaryKey('id') * this.useTimestamp() * this.useSoftDelete() * } * } * @returns {void} void */ boot() { } /** * The "useObserve" method is used to pattern refers to a way of handling model events using observer classes. * Model events are triggered when certain actions occur on models, * such as creating, updating, deleting, or saving a record. * * Observers are used to encapsulate the event-handling logic for these events, * keeping the logic separate from the model itself and promoting cleaner, more maintainable code. * @param {Function} observer * @returns this * @example * * class UserObserve { * public selected(results : unknown) { * console.log({ results , selected : true }) * } * * public created(results : unknown) { * console.log({ results , created : true }) * } * * public updated(results : unknown) { * console.log({ results ,updated : true }) * } * * public deleted(results : unknown) { * console.log({ results ,deleted : true }) * } * } * * class User extends Model { * constructor() { * super() * this.useObserver(UserObserve) * } * } */ useObserver(observer) { this.$state.set("OBSERVER", observer); return this; } /** * The "useLogger" method is used to keeping query data and changed in models. * * @type {object} options * @property {boolean} options.selected - default is false * @property {boolean} options.inserted - default is true * @property {boolean} options.updated - default is true * @property {boolean} options.deleted - default is true * @example * class User extends Model { * constructor() { * super() * this.useLogger({ * selected : true, * inserted : true, * updated : true, * deleted : true, * }) * } * } * @returns {this} this */ useLogger({ selected = false, inserted = true, updated = true, deleted = true, } = {}) { this.$state.set("LOGGER", true); this.$state.set("LOGGER_OPTIONS", { selected, inserted, updated, deleted, }); return this; } /** * The "useSchema" method is used to define the schema. * * It's automatically create, called when not exists table or columns. * @param {object} schema using Blueprint for schema * @example * import { Blueprint } from 'tspace-mysql'; * class User extends Model { * constructor() { * super() * this.useSchema ({ * id : new Blueprint().int().notNull().primary().autoIncrement(), * uuid : new Blueprint().varchar(50).null(), * email : new Blueprint().varchar(50).null(), * name : new Blueprint().varchar(255).null(), * created_at : new Blueprint().timestamp().null(), * updated_at : new Blueprint().timestamp().null() * }) * } * } * @returns {this} this */ useSchema(schema) { this.$state.set("SCHEMA_TABLE", schema); return this; } /** * * The "useRegistry" method is used to define Function to results. * * It's automatically given Function to results. * @returns {this} this * @example * class User extends Model { * constructor() { * super() * this.useRegistry() * } * } */ useRegistry() { this.$state.set("REGISTRY", { ...this.$state.get("REGISTRY"), $save: this._save.bind(this), $attach: this._attach.bind(this), $detach: this._detach.bind(this), }); return this; } /** * The "useLoadRelationsInRegistry" method is used automatically called relations in your registry Model. * @returns {this} this * @example * class User extends Model { * constructor() { * super() * this.useLoadRelationInRegistry() * } * } */ useLoadRelationsInRegistry() { const relations = this.$state .get("RELATION") .map((r) => String(r.name)); if (relations.length) this.relations(...Array.from(new Set(relations))); return this; } /** * The "useBuiltInRelationFunctions" method is used to define the function. * * It's automatically given built-in relation functions to a results. * @returns {this} this * @example * class User extends Model { * constructor() { * super() * this.useBuiltInRelationsFunction() * } * } */ useBuiltInRelationFunctions() { this.$state.set("FUNCTION_RELATION", true); return this; } /** * The "usePrimaryKey" method is add primary keys for database tables. * * @param {string} primary * @returns {this} this * @example * class User extends Model { * constructor() { * super() * this.usePrimaryKey() * } * } */ usePrimaryKey(primary) { this.$state.set("PRIMARY_KEY", primary); return this; } /** * The "useUUID" method is a concept of using UUIDs (Universally Unique Identifiers) as column 'uuid' in table. * * It's automatically genarate when created a result. * @param {string?} column [column=uuid] make new name column for custom column replace uuid with this * @returns {this} this * @example * class User extends Model { * constructor() { * super() * this.useUUID() * } * } */ useUUID(column) { this.$state.set("UUID", true); if (column) this.$state.set("UUID_FORMAT", column); return this; } /** * The "useDebug" method is viewer raw-sql logs when excute the results. * @returns {this} this */ useDebug() { this.$state.set("DEBUG", true); return this; } /** * The "usePattern" method is used to assign pattern [snake_case , camelCase]. * @param {string} pattern * @returns {this} this * @example * class User extends Model { * constructor() { * super() * this.usePattern('camelCase') * } * } */ usePattern(pattern) { const allowPattern = [ this.$constants("PATTERN").snake_case, this.$constants("PATTERN").camelCase, ]; if (!allowPattern.includes(pattern)) { throw this._assertError(`The 'tspace-mysql' support only pattern '${this.$constants("PATTERN").snake_case}', '${this.$constants("PATTERN").camelCase}'`); } this.$state.set("PATTERN", pattern); this._makeTableName(); return this; } /** * The "useCamelCase" method is used to assign pattern camelCase. * @returns {this} this * @example * class User extends Model { * constructor() { * super() * this.useCamelCase() * } * } */ useCamelCase() { this.$state.set("PATTERN", this.$constants("PATTERN").camelCase); this._makeTableName(); return this; } /** * The "SnakeCase" method is used to assign pattern snake_case. * @returns {this} this * @example * class User extends Model { * constructor() { * super() * this.SnakeCase() * } * } */ useSnakeCase() { this.$state.set("PATTERN", this.$constants("PATTERN").snake_case); this._makeTableName(); return this; } /** * The "useSoftDelete" refer to a feature that allows you to "soft delete" records from a database table instead of permanently deleting them. * * Soft deleting means that the records are not physically removed from the database but are instead marked as deleted by setting a timestamp in a dedicated column. * * This feature is particularly useful when you want to retain a record of deleted data and potentially recover it later, * or when you want to maintain referential integrity in your database * @param {string?} column default deleted_at * @returns {this} this * @example * class User extends Model { * constructor() { * this.useSoftDelete('deletedAt') * } * } */ useSoftDelete(column) { this.$state.set("SOFT_DELETE", true); if (column) this.$state.set("SOFT_DELETE_FORMAT", column); return this; } /** * The "useTimestamp" method is used to assign a timestamp when creating a new record, * or updating a record. * @param {object} timestampFormat * @property {string} timestampFormat.createdAt - change new name column replace by default [created at] * @property {string} timestampFormat.updatedAt - change new name column replace by default updated at * @returns {this} this * @example * class User extends Model { * constructor() { * this.useTimestamp({ * createdAt : 'createdAt', * updatedAt : 'updatedAt' * }) * } * } */ useTimestamp(timestampFormat) { this.$state.set("TIMESTAMP", true); if (timestampFormat) { this.$state.set("TIMESTAMP_FORMAT", { CREATED_AT: timestampFormat.createdAt, UPDATED_AT: timestampFormat.updatedAt, }); } return this; } /** * This "useTable" method is used to assign the name of the table. * @param {string} table table name in database * @returns {this} this * @example * class User extends Model { * constructor() { * this.useTable('setTableNameIsUser') // => 'setTableNameIsUser' * } * } */ useTable(table) { this.$state.set("TABLE_NAME", `\`${table}\``); return this; } /** * This "useTableSingular" method is used to assign the name of the table with signgular pattern. * @returns {this} this * @example * class User extends Model { * constructor() { * this.useTableSingular() // => 'user' * } * } */ useTableSingular() { const table = this._classToTableName(this.constructor?.name, { singular: true, }); this.$state.set("TABLE_NAME", `\`${this._valuePattern(table)}\``); return this; } /** * This "useTablePlural " method is used to assign the name of the table with pluarl pattern * @returns {this} this * @example * class User extends Model { * constructor() { * this.useTablePlural() // => 'users' * } * } */ useTablePlural() { const table = this._classToTableName(this.constructor?.name); this.$state.set("TABLE_NAME", `\`${this._valuePattern(table)}\``); return this; } /** * This 'useValidationSchema' method is used to validate the schema when have some action create or update. * @param {Object<ValidateSchema>} schema types (String Number and Date) * @returns {this} this * @example * class User extends Model { * constructor() { * this.useValidationSchema({ * id : Number, * uuid : Number, * name : { * type : String, * require : true * // json : true, * // enum : ["1","2","3"] * }, * email : { * type : String, * require : true, * length : 199, * match: /^[a-zA-Z0-9._]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/, * unique : true, * fn : async (email : string) => /^[a-zA-Z0-9._]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(email) * }, * createdAt : Date, * updatedAt : Date, * deletedAt : Date * }) * } * } */ useValidationSchema(schema) { this.$state.set("VALIDATE_SCHEMA", true); this.$state.set("VALIDATE_SCHEMA_DEFINED", schema); return this; } /** * This 'useValidateSchema' method is used to validate the schema when have some action create or update. * @param {Object<ValidateSchema>} schema types (String Number and Date) * @returns {this} this * @example * class User extends Model { * constructor() { * this.useValidationSchema({ * id : Number, * uuid : string, * name : { * type : String, * require : true * }, * email : { * type : String, * require : true, * length : 199, * // json : true, * // enum : ["1","2","3"] * match: /^[a-zA-Z0-9._]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/, * unique : true, * fn : async (email : string) => /^[a-zA-Z0-9._]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(email) * }, * createdAt : Date, * updatedAt : Date, * deletedAt : Date * }) * } * } */ useValidateSchema(schema) { return this.useValidationSchema(schema); } /** * The "useHooks" method is used to assign hook function when execute returned results to callback function. * @param {Function[]} arrayFunctions functions for callback result * @returns {this} this * @example * class User extends Model { * constructor() { * this.useHook([(results) => console.log(results)]) * } * } */ useHooks(arrayFunctions) { for (const func of arrayFunctions) { if (typeof func !== "function") throw this._assertError(`this 'function' is not a function`); this.$state.set("HOOKS", [...this.$state.get("HOOKS"), func]); } return this; } /** * The "beforeCreatingTable" method is used exection function when creating the table. * @param {Function} fn functions for executing before creating the table * @returns {this} this * @example * class User extends Model { * constructor() { * this.beforeCreatingTable(async () => { * await new User() * .create({ * ...columns * }) * .save() * }) * } * } */ beforeCreatingTable(fn) { if (!(fn instanceof Function)) throw this._assertError(`This '${fn}' is not a function.`); this.$state.set("BEFORE_CREATING_TABLE", fn); return this; } /** * The "whenCreatingTable" method is used exection function when creating the table. * @param {Function} fn functions for executing when creating the table * @returns {this} this * @example * class User extends Model { * constructor() { * this.whenCreatingTable(async () => { * await new User() * .create({ * ...columns * }) * .save() * }) * } * } */ whenCreatingTable(fn) { if (!(fn instanceof Function)) throw this._assertError(`This '${fn}' is not a function.`); this.$state.set("BEFORE_CREATING_TABLE", fn); return this; } /** * exceptColumns for method except * @override * @returns {promise<string>} string */ async exceptColumns() { const excepts = this.$state.get("EXCEPTS"); const hasDot = excepts.some((except) => /\./.test(except)); const names = excepts .map((except) => { if (/\./.test(except)) return except.split(".")[0]; return null; }) .filter((d) => d != null); const tableNames = names.length ? [...new Set(names)] : [this.$state.get("TABLE_NAME")]; const removeExcepts = []; const schemaColumns = this.getSchemaModel(); for (const tableName of tableNames) { const isHasSchema = [ schemaColumns != null, tableName.replace(/`/g, "") === this.$state.get("TABLE_NAME").replace(/`/g, ""), ].every((d) => d); if (isHasSchema) { const columns = Object.keys(schemaColumns); const removeExcept = columns.filter((column) => { return excepts.every((except) => { if (/\./.test(except)) { const [table, _] = except.split("."); return except !== `${table}.${column}`; } return except !== column; }); }); removeExcepts.push(hasDot ? removeExcept.map((r) => `${tableName}.${r}`) : removeExcept); continue; } const columns = await this.getColumns(); const removeExcept = columns.filter((column) => { return excepts.every((except) => { if (/\./.test(except)) { const [table, _] = except.split("."); return except !== `${table}.${column}`; } return except !== column; }); }); removeExcepts.push(hasDot ? removeExcept.map((r) => `\`${tableName}\`.${r}`) : removeExcept); } return removeExcepts.flat(); } /** * Build method for relation in model * @param {string} name name relation registry in your model * @param {Function} callback query callback * @returns {this} this */ buildMethodRelation(name, callback) { this.relations(name); const relation = this.$state .get("RELATIONS") .find((v) => v.name === name); if (relation == null) { throw this._assertError(`This Relation '${String(name)}' not be register in Model '${this.constructor?.name}'.`); } const relationHasExists = Object.values(this.$constants("RELATIONSHIP"))?.includes(relation.relation); if (!relationHasExists) { throw this._assertError(`Unknown relationship in '${this.$constants("RELATIONSHIP")}'.`); } if (callback == null) { relation.query = new relation.model(); return this; } relation.query = callback(new relation.model()); return this; } meta(meta) { this.$state.set("META", meta); return this; } /** * The 'typeOfSchema' method is used get type of schema. * @returns {TS} type of schema */ typeOfSchema() { return {}; } /** * The 'typeOfRelation' method is used get type of relation. * @returns {TR} type of Relation */ typeOfRelation() { return {}; } /** * The 'cache' method is used get data from cache. * @param {Object} object * @property {string} key key of cache * @property {number} expires ms * @returns {this} this */ cache({ key, expires }) { this.$state.set("CACHE", { key, expires, }); return this; } /** * * @override * @param {string[]} ...columns * @returns {this} this */ select(...columns) { if (!columns.length) { this.$state.set("SELECT", ["*"]); return this; } let select = columns.map((c) => { const column = String(c); if (column.includes(this.$constants("RAW"))) { return column?.replace(this.$constants("RAW"), "").replace(/'/g, ""); } const blueprintMapQuery = this._getBlueprintByKey(column, { mapQuery: true, }); if (blueprintMapQuery) { const sql = blueprintMapQuery.sql?.select; if (sql == null) return this.bindColumn(column); if (sql.toLowerCase().includes(" as ")) { return sql; } return `${sql} ${this.$constants("AS")} ${column}`; } return this.bindColumn(column); }); select = [...this.$state.get("SELECT"), ...select]; if (this.$state.get("DISTINCT") && select.length) { select[0] = String(select[0]).includes(this.$constants("DISTINCT")) ? select[0] : `${this.$constants("DISTINCT")} ${select[0]}`; } this.$state.set("SELECT", select); return this; } /** * * @override * @param {...string} columns * @returns {this} this */ hidden(...columns) { this.$state.set("HIDDEN", columns); return this; } /** * * @override * @param {...string} columns * @returns {this} this */ except(...columns) { if (!columns.length) return this; const exceptColumns = this.$state.get("EXCEPTS"); this.$state.set("EXCEPTS", [...columns, ...exceptColumns]); return this; } /** * * @override * @returns {this} this */ exceptTimestamp() { let excepts = []; if (this.$state.get("SOFT_DELETE")) { const deletedAt = this._valuePattern(this.$state.get("SOFT_DELETE_FORMAT")); excepts = [...excepts, deletedAt]; } const updatedAt = this._valuePattern(this.$state.get("TIMESTAMP_FORMAT").UPDATED_AT); const createdAt = this._valuePattern(this.$state.get("TIMESTAMP_FORMAT").CREATED_AT); excepts = [...excepts, createdAt, updatedAt]; const exceptColumns = this.$state.get("EXCEPTS"); this.$state.set("EXCEPTS", [...excepts, ...exceptColumns]); return this; } /** * * @override * @param {string} column * @param {string?} order by default order = 'asc' but you can used 'asc' or 'desc' * @returns {this} */ orderBy(column, order = "ASC") { const blueprintMapQuery = this._getBlueprintByKey(String(column), { mapQuery: true, }); if (blueprintMapQuery) { const sql = blueprintMapQuery.sql?.where; if (sql) { this.$state.set("ORDER_BY", [ ...this.$state.get("ORDER_BY"), `${sql} ${order.toUpperCase()}`, ]); return this; } } const orderBy = [column] .map((c) => { if (/\./.test(c)) return this.bindColumn(c.replace(/'/g, "")); if (c.includes(this.$constants("RAW"))) return c?.replace(this.$constants("RAW"), ""); return this.bindColumn(c); }) .join(", "); this.$state.set("ORDER_BY", [ ...this.$state.get("ORDER_BY"), `${orderBy} ${order.toUpperCase()}`, ]); return this; } /** * * @override * @param {string?} columns [column=id] * @returns {this} */ latest(...columns) { let orderBy = "`id`"; if (columns?.length) { orderBy = columns .map((c) => { if (/\./.test(c)) return this.bindColumn(c.replace(/'/g, "")); if (c.includes(this.$constants("RAW"))) return c?.replace(this.$constants("RAW"), ""); const blueprintMapQuery = this._getBlueprintByKey(c, { mapQuery: true, }); if (blueprintMapQuery) { const sql = blueprintMapQuery.sql?.orderBy; if (sql) return sql; } return this.bindColumn(c); }) .join(", "); } this.$state.set("ORDER_BY", [ ...this.$state.get("ORDER_BY"), `${orderBy} ${this.$constants("DESC")}`, ]); return this; } /** * * @override * @param {string?} columns [column=id] * @returns {this} */ oldest(...columns) { let orderBy = "`id`"; if (columns?.length) { orderBy = columns .map((c) => { if (/\./.test(c)) return this.bindColumn(c.replace(/'/g, "")); if (c.includes(this.$constants("RAW"))) return c?.replace(this.$constants("RAW"), ""); const blueprintMapQuery = this._getBlueprintByKey(c, { mapQuery: true, }); if (blueprintMapQuery) { const sql = blueprintMapQuery.sql?.orderBy; if (sql) return sql; } return this.bindColumn(c); }) .join(", "); } this.$state.set("ORDER_BY", [ ...this.$state.get("ORDER_BY"), `${orderBy} ${this.$constants("ASC")}`, ]); return this; } /** * * @override * @param {string?} columns [column=id] * @returns {this} */ groupBy(...columns) { let groupBy = "id"; if (columns?.length) { groupBy = columns .map((c) => { if (/\./.test(c)) return this.bindColumn(c.replace(/'/g, "")); if (c.includes(this.$constants("RAW"))) return c?.replace(this.$constants("RAW"), ""); const blueprintMapQuery = this._getBlueprintByKey(c, { mapQuery: true, }); if (blueprintMapQuery) { const sql = blueprintMapQuery.sql?.groupBy; if (sql) return sql; } return this.bindColumn(c); }) .join(", "); } this.$state.set("GROUP_BY", [...this.$state.get("GROUP_BY"), `${groupBy}`]); return this; } /** * @override * @param {string} column * @returns {string} return table.column */ bindColumn(column, pattern = true) { if (!/\./.test(column)) { if (column === "*") return "*"; const c = pattern ? this._valuePattern(column) : column; const alias = this.$state.get("ALIAS"); return [ alias == null || alias === "" ? `\`${this.getTableName().replace(/`/g, "")}\`` : `\`${alias.replace(/`/g, "")}\``, ".", `\`${c.replace(/`/g, "")}\``, ].join(""); } let [table, c] = column.split("."); c = pattern ? this._valuePattern(c) : c; if (c === "*") { return `\`${table.replace(/`/g, "")}\`.*`; } return `\`${table.replace(/`/g, "")}\`.\`${c.replace(/`/g, "")}\``; } /** * * @override * The 'makeSelectStatement' method is used to make select statement. * @returns {Promise<string>} string */ async makeSelectStatement() { const schemaModel = this.getSchemaModel(); const makeStatement = (columns) => { return [ `${this.$constants("SELECT")}`, `${columns.join(", ")}`, `${this.$constants("FROM")}`, `\`${this.getTableName()}\``, ].join(" "); }; if (schemaModel == null) { const schemaTable = await this.getSchema(); const columns = schemaTable.map((column) => `\`${this.getTableName()}\`.\`${column.Field}\``); return makeStatement(columns); } const columns = Object.keys(schemaModel).map((column) => `\`${this.getTableName()}\`.\`${column}\``); return makeStatement(columns); } /** * * @override * The 'makeInsertStatement' method is used to make insert table statement. * @returns {Promise<string>} string */ async makeInsertStatement() { const schemaModel = this.getSchemaModel(); const makeStatement = (columns) => { return [ `${this.$constants("INSERT")}`, `\`${this.getTableName()}\``, `(${columns.join(", ")})`, `${this.$constants("VALUES")}`, `(${Array(columns.length).fill("`?`").join(" , ")})`, ].join(" "); }; if (schemaModel == null) { const schemaTable = await this.getSchema(); const columns = schemaTable.map((column) => this.bindColumn(column.Field)); return makeStatement(columns); } const columns = Object.keys(schemaModel).map((column) => this.bindColumn(column)); return makeStatement(columns); } /** * * @override * The 'makeUpdateStatement' method is used to make update table statement. * @returns {Promise<string>} string */ async makeUpdateStatement() { const schemaModel = this.getSchemaModel(); const makeStatement = (columns) => { return [ `${this.$constants("UPDATE")}`, `\`${this.getTableName()}\``, `${this.$constants("SET")}`, `(${columns.join(", ")})`, `${this.$constants("WHERE")}`, `${this.bindColumn("id")} = '?'`, ].join(" "); }; if (schemaModel == null) { const schemaTable = await this.getSchema(); const columns = schemaTable.map((column) => `${this.bindColumn(column.Field)} = '?'`); return makeStatement(columns); } const columns = Object.keys(schemaModel).map((column) => `${this.bindColumn(column)} = '?'`); return makeStatement(columns); } /** * * @override * The 'makeDeleteStatement' method is used to make delete statement. * @returns {Promise<string>} string */ async makeDeleteStatement() { const makeStatement = () => { return [ `${this.$constants("DELETE")}`, `${this.$constants("FROM")}`, `\`${this.getTableName()}\``, `${this.$constants("WHERE")}`, `${this.bindColumn("id")} = \`?\``, ].join(" "); }; return makeStatement(); } /** * * @override * The 'makeCreateTableStatement' method is used to make create table statement. * @returns {Promise<string>} string */ async makeCreateTableStatement() { const schemaModel = this.getSchemaModel(); const makeStatement = (columns) => { return [ `${this.$constants("CREATE_TABLE_NOT_EXISTS")}`, `\`${this.getTableName()}\``, `(`, `\n ${columns?.join(",\n ")}`, `\n)`, `${this.$constants("ENGINE")}`, ].join(" "); }; if (schemaModel == null) { const columns = await this.showSchema(); return makeStatement(columns); } let columns = []; for (const key in schemaModel) { const data = schemaModel[key]; const { type, attributes } = Schema_1.Schema.detectSchema(data); if (type == null || attributes == null) continue; columns = [...columns, `${key} ${type} ${attributes?.join(" ")}`]; } return makeStatement(columns); } /** * * Clone instance of model * @param {Model} instance instance of model * @returns {this} this */ clone(instance) { const copy = Object.fromEntries(instance.$state.get()); this.$state.clone(copy); return this; } /** * * Copy an instance of model * @param {Model} instance instance of model * @param {Object} options keep data * @returns {Model} Model */ copyModel(instance, options) { if (!(instance instanceof Model)) { throw this._assertError("This instance is not an instanceof Model."); } const copy = Object.fromEntries(instance.$state.get()); const newInstance = new Model(); newInstance.$state.clone(copy); newInstance.$state.set("SAVE", ""); newInstance.$state.set("DEBUG", false); newInstance.$state.set("LOGGER", false); if (options?.relations == null || !options.relations) newInstance.$state.set("RELATIONS", []); if (options?.insert == null || !options.insert) newInstance.$state.set("INSERT", ""); if (options?.update == null || !options.update) newInstance.$state.set("UPDATE", ""); if (options?.delete == null || !options.delete) newInstance.$state.set("DELETE", ""); if (options?.select == null || !options.select) newInstance.$state.set("SELECT", []); if (options?.join == null || !options.join) newInstance.$state.set("JOIN", []); if (options?.where == null || !options.where) newInstance.$state.set("WHERE", []); if (options?.groupBy == null || !options.groupBy) newInstance.$state.set("GROUP_BY", []); if (options?.having == null || !options.having) newInstance.$state.set("HAVING", ""); if (options?.orderBy == null || !options.orderBy) newInstance.$state.set("ORDER_BY", []); if (options?.limit == null || !options.limit) newInstance.$state.set("LIMIT", ""); if (options?.offset == null || !options.offset) newInstance.$state.set("OFFSET", ""); return newInstance; } /** * * execute the query using raw sql syntax * @override * @param {string} sql * @returns {this} this */ async _queryStatement(sql, { retry = false } = {}) { try { const logger = async (results) => { const selectRegex = /^SELECT\b/i; const loggerOptions = this.$state.get("LOGGER_OPTIONS"); if (!(selectRegex.test(sql) && loggerOptions.selected)) return; await this._checkTableLoggerIsExists().catch((_) => null); await new DB_1.DB(this.$state.get("TABLE_LOGGER")) .create({ uuid: DB_1.DB.generateUUID(), model: this.$state.get("MODEL_NAME"), query: sql, action: "SELECT", data: results.length ? JSON.stringify(results.length === 1 ? results[0] : results) : null, changed: null, createdAt: this.$utils.timestamp(), updatedAt: this.$utils.timestamp(), }) .void() .save() .catch((_) => null); }; if (this.$state.get("DEBUG")) { this.$utils.consoleDebug(sql, retry); this.$state.set("QUERIES", [...this.$state.get("QUERIES"), sql]); const startTime = +new Date(); const result = await this.$pool.query(sql); const endTime = +new Date(); this.$utils.consoleExec(startTime, endTime); if (this.$state.get("LOGGER")) await logger(result); return result; } const result = await this.$pool.query(sql); if (this.$state.get("LOGGER")) await logger(result); return result; } catch (error) { if (this.$state.get("JOIN")?.length) throw error; const retry = Number(this.$state.get("RETRY")); await this._checkSchemaOrNextError(error, retry, error); this.$state.set("RETRY", retry + 1); return await this._queryStatement(sql, { retry: true }); } } /** * * execute the query using raw sql syntax actions for insert update and delete * @override * @param {Object} actions * @property {Function} actions.sqlresult * @property {Function} actions.returnId * @returns {this} this */ async _actionStatement({ sql, returnId = false, }) { try { const getResults = async (sql) => { if (this.$state.get("DEBUG")) { this.$utils.consoleDebug(sql); const startTime = +new Date(); const results = await this.$pool.query(sql); const endTime = +new Date(); this.$utils.consoleExec(startTime, endTime); this.$state.set("QUERIES", [...this.$state.get("QUERIES"), sql]); return results; } return await this.$pool.query(sql); }; if (this.$state.get("LOGGER")) { const updateRegex = /^UPDATE\b/i; const insertRegex = /^INSERT\b/i; const deleteRegex = /^DELETE\b/i; const loggerOptions = this.$state.get("LOGGER_OPTIONS"); if (insertRegex.test(sql) && loggerOptions.inserted) { await this._checkTableLoggerIsExists().catch((_) => null); const result = await getResults(sql); const changed = await new Model() .copyModel(this, { where: false, orderBy: true, limit: true, }) .where("id", result.insertId) .disableVoid() .get(); await new DB_1.DB(this.$state.get("TABLE_LOGGER")) .create({ uuid: DB_1.DB.generateUUID(), model: this.$state.get("MODEL_NAME"), query: sql, action: "INSERTD", data: changed.length ? JSON.stringify(changed.length === 1 ? changed[0] : changed) : null, changed: null, createdAt: this.$utils.timestamp(), updatedAt: this.$utils.timestamp(), }) .void() .save() .catch((_) => null); if (returnId) return [result.affectedRows, result.insertId]; return result.affectedRows; } if (updateRegex.test(sql) && loggerOptions.updated) { await this._checkTableLoggerIsExists().catch((err) => null); const createdAt = this.$utils.timestamp(); const data = await new Model() .copyModel(this, { where: true, orderBy: true, limit: true, }) .disableVoid() .get(); const result = await getResults(sql); const changed = await new Model() .copyModel(this, { where: true, orderBy: true, limit: true, }) .disableSoftDelete() .disableVoid() .get(); const updatedAt = this.$utils.timestamp(); await new DB_1.DB(this.$state.get("TABLE_LOGGER")) .create({ uuid: DB_1.DB.generateUUID(), model: this.$state.get("MODEL_NAME"), query: sql, action: "UPDATED", data: data.length ? JSON.stringify(data.length === 1 ? data[0] : data) : null, changed: changed.length ? JSON.stringify(changed.length === 1 ? changed[0] : changed) : null, createdAt, updatedAt, }) .void() .save() .catch((_) => null); if (returnId) return [result.affectedRows, result.insertId]; return result.affectedRows; } if (deleteRegex.test(sql) && loggerOptions.deleted) { await this._checkTableLoggerIsExists().catch((err) => null); const data = await new Model() .copyModel(this, { where: true, orderBy: true, limit: true, }) .disableVoid() .get(); const result = await getResults(sql); await new DB_1.DB(this.$state.get("TABLE_LOGGER")) .create({ uuid: DB_1.DB.generateUUID(), model: this.$state.get("MODEL_NAME"), query: sql, action: "DELETED", data: data.length ? JSON.stringify(data.length === 1 ? data[0] : data) : null, changed: null, createdAt: this.$ut