Relationships API
Relation helpers live on Model instances. They return Promise-like wrappers you can await or chain.
Model Methods
hasOne(Related, localKey?, foreignKey?): HasOneRelation
One-to-one from this model to Related.
| Parameter | Default |
|---|---|
localKey | parent primary key (id) |
foreignKey | {this.modelName}_id |
class Farmer extends Model {
profile() {
return this.hasOne(Profile)
// profiles.farmer_id = this.id
}
}
const profile = await farmer.profile() // Profile | nullhasMany(Related, localKey?, foreignKey?): HasManyRelation
One-to-many. Same key defaults as hasOne.
class Farmer extends Model {
farms() {
return this.hasMany(Farm)
}
}
const farms = await farmer.farms() // Farm[]
const active = await farmer.farms().where({ active: true }).get()belongsTo(Related, foreignKey?, ownerKey?): BelongsToRelation
Inverse relation.
| Parameter | Default |
|---|---|
foreignKey | Related model name in snake_case plus _id (for Farmer, farmer_id) |
ownerKey | id on the related table |
class Farm extends Model {
farmer() {
return this.belongsTo(Farmer)
}
}
const owner = await farm.farmer() // Farmer | nullPass foreignKey when your column does not follow the default naming convention, such as this.belongsTo(Farmer, 'owner_id').
Relation Classes
All extend abstract Relation and implement PromiseLike.
Shared API
abstract class Relation<TResult, TRelated> implements PromiseLike<TResult> {
where(conditions: WhereAttributes<TRelated>): this
where(column: AttributeColumn<TRelated>, operator: ComparisonOperator, value: unknown): this
where(group: (query: FilterGroup<TRelated>) => void): this
whereIn(column, values): this
whereNotIn(column, values): this
whereBetween(column, bounds): this
whereNull(column): this
whereNotNull(column): this
whereLike(column, pattern): this
whereILike(column, pattern): this
orderBy(column: AttributeColumn<TRelated>, direction?: 'asc' | 'desc'): this
limit(count: number): this
offset(count: number): this
first(columns?: string | string[]): Promise<TRelated | null>
get(columns?: string | string[]): Promise<TRelated[]>
count(column?: string): Promise<number>
paginate(perPage?: number, page?: number, columns?: string | string[]): Promise<RelationPaginatedResult<TRelated>>
then(...) // enables await relation
}where(conditions)
Object form is typed from the related model's declared columns.
Adds equality conditions to the relation query:
relation.where({ active: true })
relation.where({ region: 'west' })Comparisons, membership, ranges, NULL checks, and LIKE filters use the same typed methods as model queries. For the callback form of .where(), the ORM supplies a temporary filter builder as the callback argument. Its conditions form one parenthesized group, so OR methods remain constrained by the relation's parent key:
const matching = await farmer.farms()
.where((filters) => {
filters.whereLike('name', '%orchard%')
filters.orWhereLike('name', '%meadow%')
})
.get()The callback may contain nested groups. See Queries for bound-value, Date, empty-list, and wildcard rules.
first(columns?)
Executes and returns one related model or null.
get(columns?)
Executes and returns an array of related models (empty if none).
orderBy(column, direction?), limit(count), offset(count)
Apply ordering and bounds to the relation query while keeping the parent key filter. Direct await relation still uses the relation's original return shape.
count(column?), paginate(perPage?, page?, columns?)
count() counts all filtered related rows, ignoring order, limit, and offset. paginate() defaults to 15 rows on page 1 and returns page metadata matching model pagination, with data: TRelated[]. It uses a cloned data query, so the relation remains reusable. Hidden fields are stripped from the returned models.
The result has this shape:
interface RelationPaginatedResult<TRelated> {
data: TRelated[]
total: number
per_page: number
current_page: number
next_page: number | null
prev_page: number | null
last_page: number
}await relation
Calls the subclass resolve():
| Class | resolve() |
|---|---|
HasOneRelation | first() → T | null |
HasManyRelation | get() → T[] |
BelongsToRelation | first() → T | null |
Null Query Guards
If the parent lacks a key needed to build the query (e.g. no id), the internal query may be null. Then:
first()→nullget()→[]count()→0paginate()→ an empty page withtotal: 0,current_page: 1, andlast_page: 1
Exports
import {
Relation,
HasOneRelation,
HasManyRelation,
BelongsToRelation
} from 'mevn-orm'These are useful for typing; day-to-day code usually only uses the Model helpers.
Guide
See Relationships for end-to-end examples.