import { compareWithOriginal , getPrimaryKeyInfo , getRelationValues , getTableName , getValues , isSaved , mapValueProperties , setSaved , } from "./utils/models.ts" ; import type { Adapter } from "./adapters/adapter.ts" ; import { range } from "./utils/number.ts" ; import { RelationType } from "./model.ts" ; import { ModelQuery } from "./modelquery.ts" ; import { Q } from "./q.ts" ; /**
 * Same as Partial<T> but goes deeper and makes Partial<T> all its properties and sub-properties.
 */ export type DeepPartial < T > = { [ P in keyof T ] ? : T [ P ] extends Array < infer U > ? Array < DeepPartial < U > > : T [ P ] extends ReadonlyArray < infer U > ? ReadonlyArray < DeepPartial < U > > : DeepPartial < T [ P ] > ; } ; /**
 * Query options to find a single record.
 */ export interface FindOneOptions < T > { where ? : DeepPartial < T > ; includes ? : string [ ] ; } /**
 * Query options to find multiple records.
 */ export type FindOptions < T > = FindOneOptions < T > & { limit ? : number ; offset ? : number ; } ; /**
 * Manager allows you to perform queries to your model.
 */ export class Manager { param adapter constructor ( private adapter ) { } param modelClass public query < T extends Object > ( modelClass ) { return new ModelQuery ( modelClass , this . adapter ) ; } param model public async save < T extends Object > ( model ) ; param models public async save < T extends Object > ( models ) ; param model public async save < T extends Object > ( model ) { if ( Array . isArray ( model ) ) { await this . adapter . transaction ( async ( ) => { for ( const [ key , value ] of this . groupModels ( model ) ) { const insert = [ ] ; for ( const data of value ) { ! data . isSaved ? insert . push ( data . model ) : await this . update ( data . model ) ; } await this . bulkInsert ( key , insert ) ; } } ) ; } else { isSaved ( model ) ? await this . update ( model ) : await this . bulkInsert ( model . constructor , [ model ] ) ; } return model ; } param model public async remove < T extends Object > ( model ) ; param model public async remove < T extends Object > ( model ) ; param model public async remove < T extends Object > ( model ) { if ( Array . isArray ( model ) ) { await this . adapter . transaction ( async ( ) => { for ( const [ key , value ] of this . groupModels ( model ) . entries ( ) ) { const tableName = getTableName ( key ) ; const primaryKeyInfo = getPrimaryKeyInfo ( key ) ; const ids = [ ] ; const models = [ ] ; for ( const item of value ) { if ( item . isSaved ) { ids . push ( ( item . model as any ) [ primaryKeyInfo . propertyKey ] ) ; models . push ( item . model ) ; } } await this . adapter . table ( tableName ) . where ( primaryKeyInfo . name , Q . in ( ids ) ) . delete ( ) . execute ( ) ; for ( const item of models ) { delete ( item as any ) [ primaryKeyInfo . propertyKey ] ; setSaved ( item , false ) ; } } } ) ; } else { const tableName = getTableName ( model . constructor ) ; const primaryKeyInfo = getPrimaryKeyInfo ( model . constructor ) ; if ( isSaved ( model ) ) { const id = ( model as any ) [ primaryKeyInfo . propertyKey ] ; await this . adapter . table ( tableName ) . where ( primaryKeyInfo . name , id ) . delete ( ) . execute ( ) ; delete ( model as any ) [ primaryKeyInfo . propertyKey ] ; setSaved ( model , false ) ; } } return model ; } param model private async update < T extends Object > ( model ) { const tableName = getTableName ( model . constructor ) ; const primaryKeyInfo = getPrimaryKeyInfo ( model . constructor ) ; const { isDirty , diff } = compareWithOriginal ( model ) ; if ( isDirty ) { await this . adapter . table ( tableName ) . where ( primaryKeyInfo . name , ( model as any ) [ primaryKeyInfo . propertyKey ] ) . update ( diff ) . execute ( ) ; setSaved ( model , true ) ; } return model ; } /**
     * Group models by its constructor.
     */ private groupModels < T extends Object > ( models ) { return models . reduce ( ( prev , next , index ) => { const previousModels = prev . get ( next . constructor ) ; const data = { model : next , index , isSaved : isSaved ( next ) } ; prev . set ( next . constructor , previousModels ? previousModels . concat ( data ) : [ data ] ) ; return prev ; } , new Map < Function , { model : T ; index : number ; isSaved : boolean ; } [ ] > ( ) ) ; } /**
     * Insert multiple records to the database efficiently
     */ private async bulkInsert < T extends Object > ( modelClass , models ) { const tableName = getTableName ( modelClass ) ; const primaryKeyInfo = getPrimaryKeyInfo ( modelClass ) ; const values = models . map ( ( model ) => { const values = getValues ( model ) ; for ( const relation of getRelationValues ( model ) ) { if ( relation . description . type === RelationType . BelongsTo ) { values [ relation . description . targetColumn ] = relation . value as number ; } } return values ; } ) ; const query = this . adapter . table ( tableName ) . insert ( values ) ; if ( this . adapter . dialect === "postgres" ) { query . returning ( primaryKeyInfo . name ) ; } const result = await query . execute ( ) ; const lastInsertedId = this . adapter . dialect === "postgres" ? result [ result . length - 1 ] [ primaryKeyInfo . name ] as number : this . adapter . lastInsertedId ; const ids = range ( lastInsertedId + 1 - models . length , lastInsertedId ) ; for ( const [ index , model ] of models . entries ( ) ) { const value = mapValueProperties ( model . constructor , values [ index ] , "propertyKey" ) ; value [ primaryKeyInfo . propertyKey ] = ids [ index ] ; Object . assign ( model , value ) ; for ( const relation of getRelationValues ( model ) ) { if ( relation . description . type === RelationType . HasMany ) { const ids = relation . value as number [ ] ; const tableName = getTableName ( relation . description . getModel ( ) ) ; const relationPkInfo = getPrimaryKeyInfo ( relation . description . getModel ( ) ) ; await this . adapter . table ( tableName ) . update ( { [ relation . description . targetColumn ] : ( model as any ) [ primaryKeyInfo . propertyKey ] } ) . where ( relationPkInfo . name , Q . in ( ids ) ) . execute ( ) ; for ( let i = 0 ; i < ids . length ; i ++ ) { ( model as any ) [ relation . description . propertyKey ] [ i ] [ relation . description . targetColumn ] = ( model as any ) [ primaryKeyInfo . propertyKey ] ; } } } setSaved ( model , true ) ; } return models ; } }