BaseModel API Reference
BaseModel<TAttrs> is the base class for all ORM models. Extend it with a typed attributes interface that mirrors your database columns.
import { BaseModel } from '@orphnet/d1-eloquent'
interface PostAttrs {
id: string
title: string
user_id: string
created_at?: string
updated_at?: string
deleted_at?: string
}
class Post extends BaseModel<PostAttrs> {
static table = 'posts'
}Model Configuration
Static properties that configure ORM behavior. Set them directly on the class body.
| Property | Type | Default | Description |
|---|---|---|---|
table | string | required | D1 table name |
primaryKey | string | 'id' | Primary key column name |
softDeletes | boolean | false | Enable soft-delete via deleted_at column |
timestamps | boolean | true | Auto-manage created_at / updated_at |
timestampMode | 'auto' | 'manual' | 'none' | 'auto' | Timestamp write behavior |
revisions | RevisionConfig | undefined | Enable immutable audit log |
revisionRedact | string[] | [] | Fields excluded from revision data |
revisionOnly | string[] | [] | Only these fields captured in revision data |
eagerLoaders | Record<string, (model: T) => QueryBuilder<any>> | {} | Eager-load relation definitions for .with() |
timestampMode values
| Value | Behavior |
|---|---|
'auto' | ORM sets created_at on create and updated_at on every save |
'manual' | Timestamps written only when you include them in attrs |
'none' | ORM never touches timestamp columns |
RevisionConfig Type
interface RevisionConfig {
enabled: boolean
mode: 'diff' | 'snapshot' | 'diff+after' | 'before+after'
includeRequestId?: boolean
}| Mode | What is stored |
|---|---|
'diff' | Changed fields only |
'snapshot' | Full record after change |
'diff+after' | Changed fields + full record after (recommended) |
'before+after' | Full record before + full record after |
RevisionContext Type
interface RevisionContext {
actorId?: string
requestId?: string
reason?: string
}Pass this as opts.revision to save() and delete().
Static Methods
create(db, attrs)
static create(db: D1Database, attrs: TAttrs): Promise<TModel>Inserts a new row and returns the hydrated model instance.
const post = await Post.create(env.DB, {
id: crypto.randomUUID(),
title: 'Hello world',
user_id: userId,
})TIP
Text primary keys (UUIDs) are the convention. Generate the id value with crypto.randomUUID() before calling create().
find(db, id)
static find(db: D1Database, id: string): Promise<TModel | null>Fetches a single row by primary key. Returns null if not found. When softDeletes is enabled, soft-deleted rows are excluded.
const post = await Post.find(env.DB, postId)
if (post) {
console.log(post.attrs.title)
}query()
static query(): QueryBuilder<TModel>Returns a QueryBuilder for constructing a SELECT query. All filter, shape, and terminal methods are available on the returned builder.
const posts = await Post.query()
.whereEq('user_id', userId)
.orderBy('created_at', 'DESC')
.limit(10)
.get(env.DB)See QueryBuilder reference for all chainable methods.
asOf(db, id, isoTimestamp)
static asOf(
db: D1Database,
id: string,
isoTimestamp: string
): Promise<TModel | null>Reconstructs the model state as it existed at the given ISO 8601 timestamp. Returns null if the record did not exist at that point.
Requires revisions.enabled = true and revisions.mode of 'diff+after' or 'before+after'. Modes 'diff' and 'snapshot' do not support reliable reconstruction.
const post = await Post.asOf(env.DB, postId, '2026-01-15T12:00:00Z')
if (post) {
console.log(post.attrs.title) // value at that timestamp
}revertTo(db, revision)
static revertTo(
db: D1Database,
revision: ModelRevisionRecord
): Promise<void>Restores a model to the state captured in a revision record. Obtain ModelRevisionRecord objects by querying the ModelRevision model directly.
import { ModelRevision } from '@orphnet/d1-eloquent'
const revisions = await ModelRevision.query()
.whereEq('model_type', 'Post')
.whereEq('model_id', postId)
.orderBy('created_at', 'DESC')
.get(env.DB)
await Post.revertTo(env.DB, revisions[0])Instance Methods
set(attrs)
set(attrs: Partial<TAttrs>): thisMerges attrs into the in-memory model state. Returns this for chaining. Changes are not persisted until you call .save().
post.set({ title: 'Updated title' }).set({ user_id: newUserId })
await post.save(env.DB)save(db, opts?)
save(
db: D1Database,
opts?: {
revision?: RevisionContext
cache?: CacheAdapter
}
): Promise<void>Persists the current in-memory state. Issues an UPDATE for only the changed fields (dirty tracking). If the model has not been persisted yet, issues an INSERT.
post.set({ title: 'New title' })
await post.save(env.DB, {
revision: { actorId: userId, reason: 'user edit' },
})delete(db, opts?)
delete(
db: D1Database,
opts?: {
revision?: RevisionContext
cache?: CacheAdapter
}
): Promise<void>When softDeletes = false (default): issues DELETE FROM table WHERE id = ?.
When softDeletes = true: sets deleted_at to the current UTC timestamp and issues an UPDATE. The row remains in the database and can be restored.
await post.delete(env.DB, {
revision: { actorId: userId, reason: 'content removed' },
})restore(db)
restore(db: D1Database): Promise<void>Clears deleted_at, making the record visible to normal queries again. Only meaningful when softDeletes = true.
const post = await Post.query().onlyTrashed().whereEq('id', postId).first(env.DB)
if (post) {
await post.restore(env.DB)
}Instance Properties
attrs
get attrs(): TAttrsReturns the current attribute state of the model as a plain object. Access field values here.
console.log(post.attrs.title)
console.log(post.attrs.created_at)