import { assert , Connection , Join , Order , Query , Where } from "../deps.ts" ; import { dso } from "./dso.ts" ; import { Defaults , FieldOptions , FieldType } from "./field.ts" ; import { Index , IndexType } from "./index.ts" ; import { CharsetType } from "./charset.ts" ; export interface QueryOptions { fields ? : string [ ] ; where ? : Where ; order ? : Order [ ] ; group ? : string [ ] ; join ? : Join [ ] ; limit ? : [ number , number ] ; having ? : Where ; } /** Model Decorator */ export function Model < T extends BaseModel > ( name ) { return ( target ) => { Reflect . defineMetadata ( "model:name" , name , target . prototype ) ; } ; } /** Model Fields list */ export type ModelFields < T > = Partial < Omit < T , keyof BaseModel > > & { created_at ? : Date ; updated_at ? : Date ; } ; /** Model base class */ export class BaseModel { created_at ? : Date ; updated_at ? : Date ; charset : CharsetType = CharsetType . utf8 ; constructor ( public connection ? ) { } /** get model name */ get modelName ( ) : string { return Reflect . getMetadata ( "model:name" , this ) ; } /** get primary key */ get primaryKey ( ) : FieldOptions | undefined { return this . modelFields . find ( ( field ) => field . primary ) ; } /** Returns array of all available without primary key */ get columnIndexes ( ) : { [ key ] : Array < FieldOptions > ; } { return { [ IndexType . INDEX ] : this . modelFields . filter ( ( field ) => field . index ) || [ ] , [ IndexType . UNIQUE ] : this . modelFields . filter ( ( field ) => field . unique ) || [ ] , [ IndexType . SPATIAL ] : this . modelFields . filter ( ( field ) => field . spatial ) || [ ] , [ IndexType . FULLTEXT ] : this . modelFields . filter ( ( field ) => field . fullText ) || [ ] } ; } /** get defined fields list */ get modelFields ( ) : FieldOptions [ ] { return ( Reflect . getMetadata ( "model:fields" , this ) || [ { type : FieldType . DATE , default : Defaults . CURRENT_TIMESTAMP , autoUpdate : true , name : "updated_at" , property : "updated_at" } , { type : FieldType . DATE , default : Defaults . CURRENT_TIMESTAMP , name : "created_at" , property : "created_at" } , ] ) ; } /** get defined index list */ get indexes ( ) : Index [ ] { return Reflect . getMetadata ( "model:indexes" , this ) || [ ] ; } /** return a new Query instance with table name */ builder ( ) { const builder = new Query ( ) ; return builder . table ( this . modelName ) ; } param data private convertModel ( data ) { if ( ! data ) return ; const model = { } ; const fieldsMapping = { } ; this . modelFields . map ( ( field ) => ( fieldsMapping [ field . name ] = field . property ) ) ; this . indexes . map ( ( index ) => { if ( index . property ) model [ index . property ] = index ; } ) ; Object . keys ( data ) . forEach ( ( key ) => { const propertyName = fieldsMapping [ key ] ; model [ propertyName || key ] = data [ key ] ; } ) ; return model ; } param model private convertObject ( model ) { const data = { } ; const fieldsMapping = { } ; this . modelFields . map ( ( field ) => ( fieldsMapping [ field . property ! ] = field . name ) ) ; Object . keys ( model ) . forEach ( ( key ) => { const name = fieldsMapping [ key ] ; data [ name || key ] = model [ key as keyof ModelFields < this > ] ; } ) ; return data ; } private optionsToQuery ( options ) { const query = this . builder ( ) ; if ( options . fields ) { query . select ( ... options . fields ) ; } else { query . select ( `${ this . modelName }.*` ) ; } if ( options . where ) query . where ( options . where ) ; if ( options . group ) query . groupBy ( ... options . group ) ; if ( options . having ) query . having ( options . having ) ; if ( options . join ) { options . join . forEach ( ( join ) => query . join ( join ) ) ; } if ( options . limit ) query . limit ( ... options . limit ) ; if ( options . order ) options . order . forEach ( ( order ) => query . order ( order ) ) ; return query ; } param where async findOne ( options ) { if ( options instanceof Where ) { options = { where : options } ; } const result = await this . query ( this . optionsToQuery ( options ) . limit ( 0 , 1 ) ) ; return this . convertModel ( result [ 0 ] ) ; } param where async delete ( where ) { const result = await this . execute ( this . builder ( ) . delete ( ) . where ( where ) ) ; return result . affectedRows ?? 0 ; } /** find all records by given conditions */ async findAll ( options ) { if ( options instanceof Where ) { options = { where : options } ; } const result = await this . query ( this . optionsToQuery ( options ) ) ; return result . map ( ( record ) => this . convertModel ( record ) ! ) ; } /** find one record by primary key */ async findById ( id ) { assert ( ! ! this . primaryKey ) ; return await this . findOne ( Where . field ( this . primaryKey . name ) . eq ( id ) ) ; } /** insert record */ async insert ( fields ) { const query = this . builder ( ) . insert ( this . convertObject ( fields ) ) ; const result = await this . execute ( query ) ; return result . lastInsertId ; } /** update records by given conditions */ async update ( data , where ? ) { if ( ! where && this . primaryKey && data [ this . primaryKey . property as keyof this ] ) { where = Where . field ( this . primaryKey . name ) . eq ( data [ this . primaryKey . property as keyof this ] ) ; } const query = this . builder ( ) . update ( this . convertObject ( data ) ) . where ( where ?? "" ) ; const result = await this . execute ( query ) ; return result . affectedRows ; } param query async query ( query ) { const sql = query . build ( ) ; dso . showQueryLog && console . log ( `\n[ DSO:QUERY ]\nSQL:\t ${ sql }\n` ) ; const result = this . connection ? await this . connection . query ( sql ) : await dso . client . query ( sql ) ; dso . showQueryLog && console . log ( `RESULT:\t` , result , `\n` ) ; return result ; } param query async execute ( query ) { const sql = query . build ( ) ; dso . showQueryLog && console . log ( `\n[ DSO:EXECUTE ]\nSQL:\t ${ sql }\n` ) ; const result = this . connection ? await this . connection . execute ( sql ) : await dso . client . execute ( sql ) ; dso . showQueryLog && console . log ( `RESULT:\t` , result , `\n` ) ; return result ; } }