start relations (has one or many; has one; has many)
This commit is contained in:
parent
2fd3c6c22b
commit
81906b02bc
1
TODO.txt
1
TODO.txt
@ -1,6 +1,5 @@
|
|||||||
static assets
|
static assets
|
||||||
middleware
|
middleware
|
||||||
view engine
|
|
||||||
internationalization
|
internationalization
|
||||||
uploads & universal path
|
uploads & universal path
|
||||||
CLI - view routes, template generation, start server, directives, output, args
|
CLI - view routes, template generation, start server, directives, output, args
|
||||||
|
14
app/index.ts
14
app/index.ts
@ -18,3 +18,17 @@ await scaffolding.up()
|
|||||||
*/
|
*/
|
||||||
const app = make(Application, units)
|
const app = make(Application, units)
|
||||||
await app.run()
|
await app.run()
|
||||||
|
|
||||||
|
import UserModel from './models/User.model.ts'
|
||||||
|
import ObjectResultOperator from "../orm/src/builder/type/result/ObjectResultOperator.ts";
|
||||||
|
// const users = await UserModel.find_one({ username: 'garrettmills' })
|
||||||
|
|
||||||
|
const sel = UserModel.select('*').with('login_attempts').where('username', '=', 'garrettmills')
|
||||||
|
console.log(sel)
|
||||||
|
console.log(sel.clone())
|
||||||
|
const gm = await sel.results().first()
|
||||||
|
|
||||||
|
console.log(gm)
|
||||||
|
console.log(await gm.login_attempts())
|
||||||
|
|
||||||
|
// console.log(await las.fetchRelated())
|
||||||
|
20
app/models/LoginAttempt.model.ts
Normal file
20
app/models/LoginAttempt.model.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import {Model} from '../../orm/src/model/Model.ts'
|
||||||
|
import {Field} from '../../orm/src/model/Field.ts'
|
||||||
|
import {Type} from '../../orm/src/db/types.ts'
|
||||||
|
|
||||||
|
export default class LoginAttemptModel extends Model<LoginAttemptModel> {
|
||||||
|
protected static table = 'daton_login_attempts'
|
||||||
|
protected static key = 'daton_login_attempt_id'
|
||||||
|
|
||||||
|
protected static readonly CREATED_AT = 'attempt_date'
|
||||||
|
protected static readonly UPDATED_AT = null
|
||||||
|
|
||||||
|
@Field(Type.int)
|
||||||
|
protected daton_login_attempt_id?: number
|
||||||
|
|
||||||
|
@Field(Type.int)
|
||||||
|
protected user_id!: number
|
||||||
|
|
||||||
|
@Field(Type.timestamp)
|
||||||
|
protected attempt_date!: Date
|
||||||
|
}
|
30
app/models/User.model.ts
Normal file
30
app/models/User.model.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import {Model} from '../../orm/src/model/Model.ts'
|
||||||
|
import {Type} from '../../orm/src/db/types.ts'
|
||||||
|
import {Field} from '../../orm/src/model/Field.ts'
|
||||||
|
import LoginAttemptModel from './LoginAttempt.model.ts'
|
||||||
|
import {Relation} from '../../orm/src/model/relation/decorators.ts'
|
||||||
|
|
||||||
|
export default class UserModel extends Model<UserModel> {
|
||||||
|
protected static table = 'daton_users'
|
||||||
|
protected static key = 'user_id'
|
||||||
|
|
||||||
|
protected static readonly CREATED_AT = 'created_at'
|
||||||
|
protected static readonly UPDATED_AT = 'updated_at'
|
||||||
|
|
||||||
|
@Field(Type.int)
|
||||||
|
protected user_id?: number
|
||||||
|
|
||||||
|
@Field(Type.varchar)
|
||||||
|
protected first_name!: String
|
||||||
|
|
||||||
|
@Field(Type.varchar)
|
||||||
|
protected last_name!: String
|
||||||
|
|
||||||
|
@Field(Type.bool)
|
||||||
|
protected active!: Boolean
|
||||||
|
|
||||||
|
@Relation()
|
||||||
|
public login_attempts() {
|
||||||
|
return this.has_many(LoginAttemptModel)
|
||||||
|
}
|
||||||
|
}
|
@ -23,5 +23,5 @@ export default [
|
|||||||
ViewEngineUnit,
|
ViewEngineUnit,
|
||||||
RoutesUnit,
|
RoutesUnit,
|
||||||
RoutingUnit,
|
RoutingUnit,
|
||||||
HttpServerUnit,
|
// HttpServerUnit,
|
||||||
]
|
]
|
||||||
|
@ -6,7 +6,7 @@ import {Connection} from '../../db/Connection.ts'
|
|||||||
import {ResultCollection} from './result/ResultCollection.ts'
|
import {ResultCollection} from './result/ResultCollection.ts'
|
||||||
import {ResultIterable} from './result/ResultIterable.ts'
|
import {ResultIterable} from './result/ResultIterable.ts'
|
||||||
import ResultOperator from './result/ResultOperator.ts'
|
import ResultOperator from './result/ResultOperator.ts'
|
||||||
import {Collection} from '../../../../lib/src/collection/Collection.ts'
|
import {collect, Collection} from '../../../../lib/src/collection/Collection.ts'
|
||||||
import NoTargetOperatorError from '../../error/NoTargetOperatorError.ts'
|
import NoTargetOperatorError from '../../error/NoTargetOperatorError.ts'
|
||||||
|
|
||||||
export default abstract class ConnectionExecutable<T> {
|
export default abstract class ConnectionExecutable<T> {
|
||||||
@ -21,12 +21,17 @@ export default abstract class ConnectionExecutable<T> {
|
|||||||
throw new Error('Unable to execute database item: no target connection.')
|
throw new Error('Unable to execute database item: no target connection.')
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( !this.__target_operator ) throw new NoTargetOperatorError()
|
|
||||||
|
|
||||||
const query = `SELECT * FROM (${this.sql(0)}) AS target_query OFFSET ${i} LIMIT 1`
|
const query = `SELECT * FROM (${this.sql(0)}) AS target_query OFFSET ${i} LIMIT 1`
|
||||||
const result = await this.__target_connection.query(query)
|
const result = await this.__target_connection.query(query)
|
||||||
const row = result.rows.first()
|
const row = result.rows.first()
|
||||||
if ( row ) return this.__target_operator.inflate_row(row)
|
if ( row ) {
|
||||||
|
if ( !this.__target_operator ) throw new NoTargetOperatorError()
|
||||||
|
|
||||||
|
const inflated = this.__target_operator.inflate_row(row)
|
||||||
|
await this.__target_operator.process_eager_loads(this, collect([inflated]))
|
||||||
|
|
||||||
|
return inflated
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async get_range(start: number, end: number): Promise<Collection<T>> {
|
async get_range(start: number, end: number): Promise<Collection<T>> {
|
||||||
@ -36,10 +41,15 @@ export default abstract class ConnectionExecutable<T> {
|
|||||||
|
|
||||||
const query = `SELECT * FROM (${this.sql(0)}) AS target_query OFFSET ${start} LIMIT ${(end - start) + 1}`
|
const query = `SELECT * FROM (${this.sql(0)}) AS target_query OFFSET ${start} LIMIT ${(end - start) + 1}`
|
||||||
const result = await this.__target_connection.query(query)
|
const result = await this.__target_connection.query(query)
|
||||||
return result.rows.map(row => {
|
const inflated = result.rows.map(row => {
|
||||||
if ( !this.__target_operator ) throw new NoTargetOperatorError()
|
if ( !this.__target_operator ) throw new NoTargetOperatorError()
|
||||||
return this.__target_operator.inflate_row(row)
|
return this.__target_operator.inflate_row(row)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if ( !this.__target_operator ) throw new NoTargetOperatorError()
|
||||||
|
await this.__target_operator.process_eager_loads(this, inflated)
|
||||||
|
|
||||||
|
return inflated
|
||||||
}
|
}
|
||||||
|
|
||||||
iterator(): ResultIterable<T> {
|
iterator(): ResultIterable<T> {
|
||||||
|
@ -20,6 +20,7 @@ import {FullOuterJoin} from './join/FullOuterJoin.ts'
|
|||||||
import {HavingBuilder} from './HavingBuilder.ts'
|
import {HavingBuilder} from './HavingBuilder.ts'
|
||||||
import ConnectionExecutable from './ConnectionExecutable.ts'
|
import ConnectionExecutable from './ConnectionExecutable.ts'
|
||||||
import {Scope} from '../Scope.ts'
|
import {Scope} from '../Scope.ts'
|
||||||
|
import {isInstantiable} from "../../../../di/src/type/Instantiable.ts";
|
||||||
|
|
||||||
export type WhereBuilderFunction = (group: WhereBuilder) => any
|
export type WhereBuilderFunction = (group: WhereBuilder) => any
|
||||||
export type HavingBuilderFunction = (group: HavingBuilder) => any
|
export type HavingBuilderFunction = (group: HavingBuilder) => any
|
||||||
@ -78,6 +79,37 @@ export class Select<T> extends ConnectionExecutable<T> {
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clear_fields() {
|
||||||
|
this._fields = []
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
clone(): Select<T> {
|
||||||
|
const constructor = this.constructor as typeof Select
|
||||||
|
if ( !isInstantiable<Select<T>>(constructor) ) {
|
||||||
|
throw new TypeError(`Parent constructor is not instantiable.`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const select = new constructor()
|
||||||
|
|
||||||
|
select._fields = this._fields
|
||||||
|
select._source = this._source
|
||||||
|
select._wheres = this._wheres
|
||||||
|
select._scopes = this._scopes
|
||||||
|
select._havings = this._havings
|
||||||
|
select._limit = this._limit
|
||||||
|
select._offset = this._offset
|
||||||
|
select._joins = this._joins
|
||||||
|
select._distinct = this._distinct
|
||||||
|
select._group_by = this._group_by
|
||||||
|
select._order = this._order
|
||||||
|
|
||||||
|
select.__target_connection = this.__target_connection
|
||||||
|
select.__target_operator = this.__target_operator
|
||||||
|
|
||||||
|
return select
|
||||||
|
}
|
||||||
|
|
||||||
group_by(...groupings: string[]) {
|
group_by(...groupings: string[]) {
|
||||||
this._group_by = groupings
|
this._group_by = groupings
|
||||||
return this
|
return this
|
||||||
|
@ -1,8 +1,11 @@
|
|||||||
import {QueryRow} from '../../../db/types.ts'
|
import {QueryRow} from '../../../db/types.ts'
|
||||||
|
import ConnectionExecutable from '../ConnectionExecutable.ts'
|
||||||
|
import {Model} from '../../../model/Model.ts'
|
||||||
|
import {Collection} from '../../../../../lib/src/collection/Collection.ts'
|
||||||
|
|
||||||
export default abstract class ResultOperator<T> {
|
export default abstract class ResultOperator<T> {
|
||||||
|
public async process_eager_loads<T2>(query: ConnectionExecutable<T2>, results: Collection<T>): Promise<void> { }
|
||||||
|
|
||||||
abstract inflate_row(row: QueryRow): T
|
abstract inflate_row(row: QueryRow): T
|
||||||
abstract deflate_row(item: T): QueryRow
|
abstract deflate_row(item: T): QueryRow
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -11,10 +11,12 @@ import {logger} from '../../../lib/src/service/logging/global.ts'
|
|||||||
import {QueryFilter} from './filter.ts'
|
import {QueryFilter} from './filter.ts'
|
||||||
import {BehaviorSubject} from '../../../lib/src/support/BehaviorSubject.ts'
|
import {BehaviorSubject} from '../../../lib/src/support/BehaviorSubject.ts'
|
||||||
import {Scope} from '../builder/Scope.ts'
|
import {Scope} from '../builder/Scope.ts'
|
||||||
import {JSONState, Rehydratable} from '../../../lib/src/support/Rehydratable.ts'
|
import {Rehydratable} from '../../../lib/src/support/Rehydratable.ts'
|
||||||
import {HasOne} from './relation/HasOne.ts'
|
import {HasOne} from './relation/HasOne.ts'
|
||||||
import Instantiable from '../../../di/src/type/Instantiable.ts'
|
import Instantiable from '../../../di/src/type/Instantiable.ts'
|
||||||
import {HasMany} from './relation/HasMany.ts'
|
import {HasMany} from './relation/HasMany.ts'
|
||||||
|
import {ModelSelect} from "./query/ModelSelect.ts";
|
||||||
|
import {Relation} from "./relation/Relation.ts";
|
||||||
|
|
||||||
// TODO separate read/write connections
|
// TODO separate read/write connections
|
||||||
// TODO manual dirty flags
|
// TODO manual dirty flags
|
||||||
@ -246,9 +248,13 @@ abstract class Model<T extends Model<T>> extends Builder<T> implements Rehydrata
|
|||||||
* @param {...FieldSet} fields
|
* @param {...FieldSet} fields
|
||||||
*/
|
*/
|
||||||
public select(...fields: FieldSet[]) {
|
public select(...fields: FieldSet[]) {
|
||||||
|
if ( fields.length < 1 ) fields.push(this.qualify_column('*'))
|
||||||
|
|
||||||
const constructor = (this.constructor as typeof Model)
|
const constructor = (this.constructor as typeof Model)
|
||||||
return super.select(...fields)
|
const select = new ModelSelect<T>()
|
||||||
.from(constructor.table_name())
|
|
||||||
|
return select.fields(...fields)
|
||||||
|
.from(constructor.query_source())
|
||||||
.with_scopes(constructor.global_scopes)
|
.with_scopes(constructor.global_scopes)
|
||||||
.target_connection(constructor.get_connection())
|
.target_connection(constructor.get_connection())
|
||||||
.target_operator(make(ModelResultOperator, constructor))
|
.target_operator(make(ModelResultOperator, constructor))
|
||||||
@ -972,6 +978,17 @@ abstract class Model<T extends Model<T>> extends Builder<T> implements Rehydrata
|
|||||||
public has_many<T>(related: Instantiable<T>, foreign_key?: string, local_key?: string) {
|
public has_many<T>(related: Instantiable<T>, foreign_key?: string, local_key?: string) {
|
||||||
return new HasMany(this as any, new related() as any, foreign_key, local_key)
|
return new HasMany(this as any, new related() as any, foreign_key, local_key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get_relation<T2 extends Model<T2>>(name: string): Relation<T, T2> {
|
||||||
|
// @ts-ignore
|
||||||
|
const rel: any = this[name]()
|
||||||
|
|
||||||
|
if ( rel instanceof Relation ) {
|
||||||
|
return rel
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new TypeError(`Cannot get relation of name: ${name}. Method does not return relation.`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { Model }
|
export { Model }
|
||||||
|
@ -4,6 +4,9 @@ import {Injectable} from '../../../di/src/decorator/Injection.ts'
|
|||||||
import {QueryRow} from '../db/types.ts'
|
import {QueryRow} from '../db/types.ts'
|
||||||
import Instantiable from '../../../di/src/type/Instantiable.ts'
|
import Instantiable from '../../../di/src/type/Instantiable.ts'
|
||||||
import {make} from '../../../di/src/global.ts'
|
import {make} from '../../../di/src/global.ts'
|
||||||
|
import ConnectionExecutable from '../builder/type/ConnectionExecutable.ts'
|
||||||
|
import {Collection} from '../../../lib/src/collection/Collection.ts'
|
||||||
|
import {ModelSelect} from './query/ModelSelect.ts'
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export default class ModelResultOperator<T extends Model<T>> extends ResultOperator<T> {
|
export default class ModelResultOperator<T extends Model<T>> extends ResultOperator<T> {
|
||||||
@ -21,4 +24,21 @@ export default class ModelResultOperator<T extends Model<T>> extends ResultOpera
|
|||||||
return item.to_row()
|
return item.to_row()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public async process_eager_loads<T2>(query: ConnectionExecutable<T2>, results: Collection<T>) {
|
||||||
|
if ( query instanceof ModelSelect ) {
|
||||||
|
const eagers = query.eager_relations
|
||||||
|
const model = new this.ModelClass()
|
||||||
|
|
||||||
|
for ( const rel_name of eagers ) {
|
||||||
|
const relation = model.get_relation(rel_name)
|
||||||
|
const select = await relation.build_eager_query(query, results)
|
||||||
|
const all_related = await select.results()
|
||||||
|
results.each(result => {
|
||||||
|
const result_relation = result.get_relation(rel_name)
|
||||||
|
const result_related = result_relation.match_results(all_related as any)
|
||||||
|
result_relation.set_value(result_related as any)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
20
orm/src/model/query/ModelSelect.ts
Normal file
20
orm/src/model/query/ModelSelect.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import {Select} from '../../builder/type/Select.ts'
|
||||||
|
|
||||||
|
export class ModelSelect<T> extends Select<T> {
|
||||||
|
protected _withs: string[] = []
|
||||||
|
|
||||||
|
public with(related: string) {
|
||||||
|
this._withs.push(related)
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
public get eager_relations(): string[] {
|
||||||
|
return [...this._withs]
|
||||||
|
}
|
||||||
|
|
||||||
|
clone(): ModelSelect<T> {
|
||||||
|
const select = super.clone() as ModelSelect<T>
|
||||||
|
select._withs = this._withs
|
||||||
|
return select
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,23 @@ import {HasOneOrMany} from './HasOneOrMany.ts'
|
|||||||
import {Collection} from '../../../../lib/src/collection/Collection.ts'
|
import {Collection} from '../../../../lib/src/collection/Collection.ts'
|
||||||
|
|
||||||
export class HasMany<T extends Model<T>, T2 extends Model<T2>> extends HasOneOrMany<T, T2> {
|
export class HasMany<T extends Model<T>, T2 extends Model<T2>> extends HasOneOrMany<T, T2> {
|
||||||
|
protected _value?: Collection<T2>
|
||||||
|
protected _loaded = false
|
||||||
|
|
||||||
public async get(): Promise<Collection<T2>> {
|
public async get(): Promise<Collection<T2>> {
|
||||||
return this.fetch().collect()
|
return this.fetch().collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public set_value(related: Collection<T2>) {
|
||||||
|
this._loaded = true
|
||||||
|
this._value = related.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
public get_value(): Collection<T2> | undefined {
|
||||||
|
return this._value
|
||||||
|
}
|
||||||
|
|
||||||
|
public is_loaded(): boolean {
|
||||||
|
return this._loaded
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,30 @@
|
|||||||
import {Model} from '../Model.ts'
|
import {Model} from '../Model.ts'
|
||||||
import {HasOneOrMany} from './HasOneOrMany.ts'
|
import {HasOneOrMany} from './HasOneOrMany.ts'
|
||||||
|
import {Collection} from "../../../../lib/src/collection/Collection.ts";
|
||||||
|
import {Logging} from "../../../../lib/src/service/logging/Logging.ts";
|
||||||
|
|
||||||
export class HasOne<T extends Model<T>, T2 extends Model<T2>> extends HasOneOrMany<T, T2> {
|
export class HasOne<T extends Model<T>, T2 extends Model<T2>> extends HasOneOrMany<T, T2> {
|
||||||
|
protected _value?: T2
|
||||||
|
protected _loaded = false
|
||||||
|
|
||||||
public async get(): Promise<T2 | undefined> {
|
public async get(): Promise<T2 | undefined> {
|
||||||
return this.fetch().first()
|
return this.fetch().first()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public set_value(related: Collection<T2>) {
|
||||||
|
this._value = related.first()
|
||||||
|
this._loaded = true
|
||||||
|
|
||||||
|
if ( related.length > 1 ) {
|
||||||
|
this.make(Logging).warn(`HasOne relation result contained more than one value for ${this.qualified_local_key} -> ${this.qualified_local_key}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get_value(): T2 | undefined {
|
||||||
|
return this._value
|
||||||
|
}
|
||||||
|
|
||||||
|
public is_loaded(): boolean {
|
||||||
|
return this._loaded
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
import {Model} from "../Model.ts";
|
import {Model} from '../Model.ts'
|
||||||
import {Relation} from "./Relation.ts";
|
import {Relation} from './Relation.ts'
|
||||||
import ConnectionExecutable from "../../builder/type/ConnectionExecutable.ts";
|
import ConnectionExecutable from '../../builder/type/ConnectionExecutable.ts'
|
||||||
import {WhereBuilder} from "../../builder/type/WhereBuilder.ts";
|
import {WhereBuilder} from '../../builder/type/WhereBuilder.ts'
|
||||||
|
import {ModelSelect} from '../query/ModelSelect.ts'
|
||||||
|
import {Collection} from '../../../../lib/src/collection/Collection.ts'
|
||||||
|
|
||||||
export abstract class HasOneOrMany<T extends Model<T>, T2 extends Model<T2>> extends Relation<T, T2> {
|
export abstract class HasOneOrMany<T extends Model<T>, T2 extends Model<T2>> extends Relation<T, T2> {
|
||||||
constructor(
|
constructor(
|
||||||
@ -42,4 +44,13 @@ export abstract class HasOneOrMany<T extends Model<T>, T2 extends Model<T2>> ext
|
|||||||
.whereRaw(this.qualified_foreign_key, 'IS NOT', 'NULL')
|
.whereRaw(this.qualified_foreign_key, 'IS NOT', 'NULL')
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public build_eager_query(parent_query: ModelSelect<T>, result: Collection<T>): ModelSelect<T2> {
|
||||||
|
const keys = result.pluck(this.local_key).toArray()
|
||||||
|
return this.related.select().whereIn(this.foreign_key, keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
public match_results(possibly_related: Collection<T>) {
|
||||||
|
return possibly_related.where(this.foreign_key, '=', this.parent_value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,10 +6,12 @@ import {AsyncCollection} from '../../../../lib/src/collection/AsyncCollection.ts
|
|||||||
import {Collection} from '../../../../lib/src/collection/Collection.ts'
|
import {Collection} from '../../../../lib/src/collection/Collection.ts'
|
||||||
import {WhereBuilder} from '../../builder/type/WhereBuilder.ts'
|
import {WhereBuilder} from '../../builder/type/WhereBuilder.ts'
|
||||||
import {RelationBuilder} from './RelationBuilder.ts'
|
import {RelationBuilder} from './RelationBuilder.ts'
|
||||||
import {Select} from "../../builder/type/Select.ts";
|
import {Select} from '../../builder/type/Select.ts'
|
||||||
import {FieldSet} from "../../builder/types.ts";
|
import {FieldSet} from '../../builder/types.ts'
|
||||||
import {Update} from "../../builder/type/Update.ts";
|
import {Update} from '../../builder/type/Update.ts'
|
||||||
import {Delete} from "../../builder/type/Delete.ts";
|
import {Delete} from '../../builder/type/Delete.ts'
|
||||||
|
import ModelResultOperator from '../ModelResultOperator.ts'
|
||||||
|
import {ModelSelect} from '../query/ModelSelect.ts'
|
||||||
|
|
||||||
export type RelationResult<T> = T | Collection<T> | undefined
|
export type RelationResult<T> = T | Collection<T> | undefined
|
||||||
|
|
||||||
@ -25,13 +27,21 @@ export abstract class Relation<T extends Model<T>, T2 extends Model<T2>> extends
|
|||||||
|
|
||||||
public get_operator() {
|
public get_operator() {
|
||||||
const related_class = this.related.constructor as typeof Model
|
const related_class = this.related.constructor as typeof Model
|
||||||
return this.make(RelationResultOperator, related_class, this)
|
return this.make(ModelResultOperator, related_class)
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract query(): ConnectionExecutable<T2>
|
public abstract query(): ConnectionExecutable<T2>
|
||||||
|
|
||||||
public abstract scope_query(where: WhereBuilder): void
|
public abstract scope_query(where: WhereBuilder): void
|
||||||
|
|
||||||
|
public abstract build_eager_query(parent_query: ModelSelect<T>, result: Collection<T>): ModelSelect<T2>
|
||||||
|
|
||||||
|
public abstract match_results(possibly_related: Collection<T>): Collection<T>
|
||||||
|
|
||||||
|
public abstract set_value(related: Collection<T2>): void
|
||||||
|
public abstract get_value(): Collection<T2> | T2 | undefined
|
||||||
|
public abstract is_loaded(): boolean
|
||||||
|
|
||||||
public fetch(): AsyncCollection<T2> {
|
public fetch(): AsyncCollection<T2> {
|
||||||
return this.query().results()
|
return this.query().results()
|
||||||
}
|
}
|
||||||
@ -39,7 +49,21 @@ export abstract class Relation<T extends Model<T>, T2 extends Model<T2>> extends
|
|||||||
public abstract get(): Promise<RelationResult<T2>>
|
public abstract get(): Promise<RelationResult<T2>>
|
||||||
|
|
||||||
public then(callback: (result: RelationResult<T2>) => any) {
|
public then(callback: (result: RelationResult<T2>) => any) {
|
||||||
this.get().then(callback)
|
if ( this.is_loaded() ) {
|
||||||
|
callback(this.get_value() as RelationResult<T2>)
|
||||||
|
} else {
|
||||||
|
this.get().then(result => {
|
||||||
|
if ( result instanceof Collection ) {
|
||||||
|
this.set_value(result as any)
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get value(): RelationResult<T2> {
|
||||||
|
return this.get_value()
|
||||||
}
|
}
|
||||||
|
|
||||||
public get related_query_source() {
|
public get related_query_source() {
|
||||||
|
Loading…
Reference in New Issue
Block a user