|
|
|
import {Model} from '../Model'
|
|
|
|
import {ModelBuilder} from '../ModelBuilder'
|
|
|
|
import {AbstractBuilder} from '../../builder/AbstractBuilder'
|
|
|
|
import {ResultCollection} from '../../builder/result/ResultCollection'
|
|
|
|
import {Collection, ErrorWithContext, Maybe} from '../../../util'
|
|
|
|
import {QuerySource} from '../../types'
|
|
|
|
import {RelationBuilder} from './RelationBuilder'
|
|
|
|
import {InjectionAware} from '../../../di'
|
|
|
|
|
|
|
|
/** Type alias for possible values of a relation. */
|
|
|
|
export type RelationValue<T2> = Maybe<Collection<T2> | T2>
|
|
|
|
|
|
|
|
/** Error thrown when a relation result is accessed synchronously before it is loaded. */
|
|
|
|
export class RelationNotLoadedError extends ErrorWithContext {
|
|
|
|
constructor(
|
|
|
|
context: {[key: string]: any} = {},
|
|
|
|
) {
|
|
|
|
super('Attempted to get value of relation that has not yet been loaded.', context)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Base class for inter-model relation implementations.
|
|
|
|
*/
|
|
|
|
export abstract class Relation<T extends Model<T>, T2 extends Model<T2>, V extends RelationValue<T2>> extends InjectionAware {
|
|
|
|
protected constructor(
|
|
|
|
/** The model related from. */
|
|
|
|
protected parent: T,
|
|
|
|
|
|
|
|
/** The model related to. */
|
|
|
|
public readonly related: T2,
|
|
|
|
) {
|
|
|
|
super()
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Get the value of the key field from the parent model. */
|
|
|
|
protected abstract get parentValue(): any
|
|
|
|
|
|
|
|
/** Create a new relation builder query for this relation instance. */
|
|
|
|
public abstract query(): RelationBuilder<T2>
|
|
|
|
|
|
|
|
/** Limit the results of the builder to only this relation's rows. */
|
|
|
|
public abstract applyScope(where: AbstractBuilder<T2>): void
|
|
|
|
|
|
|
|
/** Create a relation query that will eager-load the result of this relation for a set of models. */
|
|
|
|
public abstract buildEagerQuery(parentQuery: ModelBuilder<T>, result: Collection<T>): ModelBuilder<T2>
|
|
|
|
|
|
|
|
/** Given a set of possibly-related instances, filter out the ones that are relevant to the parent. */
|
|
|
|
public abstract matchResults(possiblyRelated: Collection<T>): Collection<T>
|
|
|
|
|
|
|
|
/** Set the value of the relation. */
|
|
|
|
public abstract setValue(related: V): void
|
|
|
|
|
|
|
|
/** Get the value of the relation. */
|
|
|
|
public abstract getValue(): V
|
|
|
|
|
|
|
|
/** Returns true if the relation has been loaded. */
|
|
|
|
public abstract isLoaded(): boolean
|
|
|
|
|
|
|
|
/** Get a collection of the results of this relation. */
|
|
|
|
public fetch(): ResultCollection<T2> {
|
|
|
|
return this.query()
|
|
|
|
.where(where => this.applyScope(where))
|
|
|
|
.get()
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Resolve the result of this relation. */
|
|
|
|
public abstract get(): Promise<V>
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Makes the relation "thenable" so relation methods on models can be awaited
|
|
|
|
* to yield the result of the relation.
|
|
|
|
*
|
|
|
|
* @example
|
|
|
|
* ```ts
|
|
|
|
* const rows = await myModelInstance.myHasManyRelation() -- rows is a Collection
|
|
|
|
* ```
|
|
|
|
*
|
|
|
|
* @param resolve
|
|
|
|
* @param reject
|
|
|
|
*/
|
|
|
|
public then(resolve: (result: V) => unknown, reject: (e: Error) => unknown): void {
|
|
|
|
if ( this.isLoaded() ) {
|
|
|
|
resolve(this.getValue())
|
|
|
|
} else {
|
|
|
|
this.get()
|
|
|
|
.then(result => {
|
|
|
|
if ( result instanceof Collection ) {
|
|
|
|
this.setValue(result)
|
|
|
|
}
|
|
|
|
|
|
|
|
resolve(result)
|
|
|
|
})
|
|
|
|
.catch(reject)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Get the value of this relation. */
|
|
|
|
public get value(): V {
|
|
|
|
return this.getValue()
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Get the query source for the related model in this relation. */
|
|
|
|
public get relatedQuerySource(): QuerySource {
|
|
|
|
const related = this.related.constructor as typeof Model
|
|
|
|
return related.querySource()
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Get a new builder instance for this relation. */
|
|
|
|
public builder(): RelationBuilder<T2> {
|
|
|
|
const relatedCtor = this.related.constructor as typeof Model
|
|
|
|
return this.make<RelationBuilder<T2>>(RelationBuilder, this)
|
|
|
|
.connection(relatedCtor.getConnection())
|
|
|
|
.from(relatedCtor.tableName())
|
|
|
|
}
|
|
|
|
}
|