Skip to content

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.

ts
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.

PropertyTypeDefaultDescription
tablestringrequiredD1 table name
primaryKeystring'id'Primary key column name
softDeletesbooleanfalseEnable soft-delete via deleted_at column
timestampsbooleantrueAuto-manage created_at / updated_at
timestampMode'auto' | 'manual' | 'none''auto'Timestamp write behavior
revisionsRevisionConfigundefinedEnable immutable audit log
revisionRedactstring[][]Fields excluded from revision data
revisionOnlystring[][]Only these fields captured in revision data
eagerLoadersRecord<string, (model: T) => QueryBuilder<any>>{}Eager-load relation definitions for .with()

timestampMode values

ValueBehavior
'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

ts
interface RevisionConfig {
  enabled: boolean
  mode: 'diff' | 'snapshot' | 'diff+after' | 'before+after'
  includeRequestId?: boolean
}
ModeWhat 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

ts
interface RevisionContext {
  actorId?: string
  requestId?: string
  reason?: string
}

Pass this as opts.revision to save() and delete().

Static Methods

create(db, attrs)

ts
static create(db: D1Database, attrs: TAttrs): Promise<TModel>

Inserts a new row and returns the hydrated model instance.

ts
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)

ts
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.

ts
const post = await Post.find(env.DB, postId)
if (post) {
  console.log(post.attrs.title)
}

query()

ts
static query(): QueryBuilder<TModel>

Returns a QueryBuilder for constructing a SELECT query. All filter, shape, and terminal methods are available on the returned builder.

ts
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)

ts
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.

ts
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)

ts
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.

ts
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)

ts
set(attrs: Partial<TAttrs>): this

Merges attrs into the in-memory model state. Returns this for chaining. Changes are not persisted until you call .save().

ts
post.set({ title: 'Updated title' }).set({ user_id: newUserId })
await post.save(env.DB)

save(db, opts?)

ts
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.

ts
post.set({ title: 'New title' })
await post.save(env.DB, {
  revision: { actorId: userId, reason: 'user edit' },
})

delete(db, opts?)

ts
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.

ts
await post.delete(env.DB, {
  revision: { actorId: userId, reason: 'content removed' },
})

restore(db)

ts
restore(db: D1Database): Promise<void>

Clears deleted_at, making the record visible to normal queries again. Only meaningful when softDeletes = true.

ts
const post = await Post.query().onlyTrashed().whereEq('id', postId).first(env.DB)
if (post) {
  await post.restore(env.DB)
}

Instance Properties

attrs

ts
get attrs(): TAttrs

Returns the current attribute state of the model as a plain object. Access field values here.

ts
console.log(post.attrs.title)
console.log(post.attrs.created_at)

Released under the MIT License.