Model
ActiveRecord-style base class backed by the configured Knex or db0 connection. Extend for each table.
import { Model } from 'mevn-orm'
class User extends Model {
declare name: string
declare email: string
declare password: string
override fillable = ['name', 'email', 'password']
override hidden = ['password']
}Declare column types on the subclass so the language server types user.name as string and types create / where / update payloads. See Typing attributes in the models guide.
Attribute Helper Types
Inferred from declared instance fields (or a merged interface). Untyped models fall back to Record<string, unknown>.
| Type | Use |
|---|---|
ModelAttributes<T> | Declared columns including id |
CreateAttributes<T> | create / createMany payload (id omitted) |
WhereAttributes<T> | where / firstOrCreate lookup object |
UpdateAttributes<T> | Instance and static update payload |
AttributeColumn<T> | Declared column name (orderBy) |
Instance properties
fillable: string[]
Attributes allowed through instance save(). Default [].
hidden: string[]
Attributes excluded from toArray() and stripped after many read/create paths. Default [].
table: string
Database table. Defaults to plural snake_case of the class name. Override when inference is wrong:
class PasswordReset extends Model {
override table = 'password_reset_tokens'
}modelName: string
Snake_case singular form of the class name (e.g. PasswordResetToken → password_reset_token). Used for default foreign key names on relations.
id?: number
Primary key after insert or load.
Static properties & helpers
static currentTable: string
Resolved table name for the class (honours override table).
static resolveTable(): string
Same resolution as currentTable.
static ensureCurrentQuery()
Returns a new independent ModelQuery for the model table. currentQuery is deprecated and always undefined.
Instance methods
constructor(properties?: Record<string, unknown>)
Assigns properties, initialises fillable / hidden, and sets modelName / table from the class name.
save(): Promise<this>
Inserts using only fillable fields, reloads the row, sets id, and returns this.
const user = new User()
user.name = 'Jane'
user.email = 'jane@example.com'
user.password = hashed
await user.save()update(properties): Promise<this>
Updates the row by id, reloads, returns a new instance of the same class. Throws if id is missing. properties is UpdateAttributes<this> when columns are declared.
const updated = await user.update({ name: 'Jane Updated' })delete(): Promise<void>
Deletes by id. Throws if id is missing.
toArray(): Record<string, unknown>
Plain object for API responses. Omits fillable, hidden, modelName, table, function values, and all hidden attributes.
On a collection, toArray() is an array of those objects. On a model, it is one object.
toJson(): Record<string, unknown>
One record as a plain object with the same payload as instance toArray().
Call this helper explicitly. It returns an object rather than a JSON string and is distinct from JavaScript's automatic toJSON() hook. Use JSON.stringify(model.toJson()) to encode the filtered object as a string.
stripColumns(model, keepInternalState?): T
Removes private / hidden keys from a model object in place. Used internally after loads.
Relationship helpers
Documented under Relationships:
hasOne(Related, localKey?, foreignKey?)hasMany(Related, localKey?, foreignKey?)belongsTo(Related, foreignKey?, ownerKey?)
Static CRUD
All static methods that return models preserve the derived type (User.find → User | null).
static find(id, columns?): Promise<InstanceType<T> | null>
Find by primary key. Returns null when missing.
const user = await User.find(1)
const partial = await User.find(1, ['id', 'email'])static findOrFail(id, columns?): Promise<InstanceType<T>>
Like find, but throws Error: ${Name} with id "${id}" not found.
static create(properties): Promise<InstanceType<T>>
Insert one row and return a model instance (hidden fields stripped). properties is CreateAttributes<Instance> when the model declares columns.
const user = await User.create({ name, email, password: hashed })static createMany(properties[]): Promise<InstanceType<T>[]>
Insert many rows sequentially. Empty array returns [].
static firstOrCreate(attributes, values?): Promise<InstanceType<T>>
Return the first row matching attributes, or create with { ...attributes, ...values }.
const user = await User.firstOrCreate(
{ email: 'jane@example.com' },
{ name: 'Jane', password: hashed }
)static update(properties): Promise<number>
Bulk update across the whole table. Use User.where(...).update(...) for a scoped update.
static destroy(): Promise<number>
Bulk delete across the whole table. Use User.where(...).destroy() for a scoped delete.
Static query builder
Chain scopes on an independent ModelQuery, then call a terminal method. The query may be reused; other requests cannot change its scope.
Scopes
| Method | Signature | Notes |
|---|---|---|
where | where(conditions?: WhereAttributes): ModelQuery<T> | Equality object |
orderBy | orderBy(column, direction?: 'asc' | 'desc') | Default 'asc' |
limit | limit(count: number) | |
offset | offset(count: number) | |
clone | clone(): ModelQuery<T> | Independent copy of the current scope |
User.where({ active: true }).orderBy('name').limit(10).all()The static where method retains its equality-object signature. On a returned ModelQuery, where also accepts (column, operator, value) and a grouped callback. Query objects provide whereIn, whereNotIn, whereBetween, whereNull, whereNotNull, whereLike, and whereILike. Group callbacks support matching orWhere variants, including nested groups. See Queries for NULL, wildcard, and Date semantics.
Terminals
| Method | Returns |
|---|---|
first(columns?) | InstanceType<T> | null |
firstOrFail(columns?) | InstanceType<T>; throws when missing |
all(columns?) | ModelCollection<InstanceType<T>> |
exists() | boolean; respects limit and offset |
value(column) | Column value or undefined when missing |
pluck(column) | Array of column values |
count(column?) | number (default column *) |
paginate(perPage?, page?, columns?) | PaginatedResult<InstanceType<T>> |
update / destroy | row counts |
Default perPage for paginate is 15; default page is 1.
value() distinguishes a missing row (undefined) from a SQL NULL (null). pluck() returns [] for no matches. Scalar helpers select raw column values without model hydration, so explicitly selecting a hidden column returns its value. Terminal reads do not mutate reusable queries. exists() returns false for limit(0) and respects offset(); count() ignores both.
PaginatedResult<T>
interface PaginatedResult<T extends Model> {
data: ModelCollection<T>
total: number
per_page: number
current_page: number
next_page: number | null
prev_page: number | null
last_page: number
}ModelCollection
class ModelCollection<T extends Model> extends Array<T> {
toArray(): Record<string, unknown>[]
}Returned by all() and paginate().data. Supports normal array methods plus collection-level serialisation.