npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@avelonjs/orm

v0.8.1

Published

Model layer and QueryIR builder for Avelon applications.

Readme

@avelonjs/orm

@avelonjs/orm is Scrivener, Avelon's model layer. It builds serializable QueryIR and never imports a database vendor. Reach for this package when application code needs models, fluent queries, ward injection, relation loads, casts, timestamps, soft deletes, or lifecycle hooks.

Installation

bun add @avelonjs/orm

Basic Usage

import { Model } from '@avelonjs/orm'
import { defineConfig } from '@avelonjs/core'
import type { DatabaseDriver } from '@avelonjs/core'

export function boot(database: DatabaseDriver) {
  defineConfig({
    name: 'app',
    drivers: { database },
  })
}

export class Post extends Model {
  static override table = 'posts'
  static override fillable = ['title', 'body', 'user_id']
  static override casts = { published_at: 'datetime' } as const
  static override timestamps = true
  static override softDeletes = true

  declare id: string
  declare title: string
  declare body: string
  declare user_id: string
  declare published_at: Date | null

  author() {
    return this.belongsTo(User, 'user_id')
  }

  static published() {
    return this.query().whereNotNull('published_at')
  }
}

export class User extends Model {
  static override table = 'users'
  static override fillable = ['email', 'name']
}

export async function latestPublished() {
  return Post.published().with('author').latest('published_at').paginate(15)
}

Query Builder

The builder accumulates predicates, ordering, limits, relations, and wards, then emits QueryIR for the configured database driver. where('age', 20) equals where('age', '=', 20). orWhere wraps the accumulated predicates in an or node. paginate uses count mode for the total.

Models

Models declare a table, fillable attributes, optional casts, timestamps, and soft deletes. Use declare for attributes so hydration is not overwritten by emitted class fields. findOrFail raises NotFound. forActor injects the actor's ward. Scrivener.unwarded(Post).query() skips ward injection and is restricted by Bailiff to errands and seeds.

Lifecycle Hooks

Writes dispatch ModelLifecycle on the same event bus as application events. A creating listener that returns false (or calls stopPropagation) aborts the insert.

import { Events, defineListener } from '@avelonjs/core'
import { ModelLifecycle } from '@avelonjs/orm'

Events.listen(
  ModelLifecycle,
  defineListener({
    handle: (event) => {
      if (event.hook === 'creating' && event.modelName === 'Post') return false
    },
  }),
)

Method Reference

| Method | Signature | Description | | ----------------------------- | ---------------------------------------------------------------------------------- | -------------------------------------------------- | | query | (table: string, driver?: DatabaseDriver) => QueryBuilder | Starts a fluent builder for a table. | | QueryBuilder.select | (...columns: string[]) => this | Sets the projection. | | QueryBuilder.where | (column: string, opOrValue: CompareOp \| unknown, value?: unknown) => this | Adds a comparison predicate. | | QueryBuilder.wherePredicate | (predicate: Predicate) => this | Adds an arbitrary predicate. | | QueryBuilder.whereNull | (column: string) => this | Constrains a column to be null. | | QueryBuilder.whereNotNull | (column: string) => this | Constrains a column to be non-null. | | QueryBuilder.whereIn | (column: string, values: readonly unknown[]) => this | Constrains a column to a list. | | QueryBuilder.orWhere | (column: string, opOrValue: CompareOp \| unknown, value?: unknown) => this | ORs a comparison with accumulated predicates. | | QueryBuilder.orderBy | (column: string, direction?: 'asc' \| 'desc', nulls?: 'first' \| 'last') => this | Orders results. | | QueryBuilder.latest | (column?: string) => this | Orders descending by a timestamp column. | | QueryBuilder.limit | (count: number) => this | Limits rows. | | QueryBuilder.offset | (count: number) => this | Offsets rows. | | QueryBuilder.with | (relation: string \| RelationLoad) => this | Eager-loads a relation method or descriptor. | | QueryBuilder.ward | (predicate: WardInput) => this | Injects a ward predicate. | | QueryBuilder.unwarded | () => this | Skips registered ward injection. | | QueryBuilder.withTrashed | () => this | Includes soft-deleted rows. | | QueryBuilder.toIR | (mode?: QueryIR['mode']) => QueryIR | Builds serializable IR. | | QueryBuilder.get | () => Promise<readonly TModel[]> | Executes a select and hydrates models. | | QueryBuilder.first | () => Promise<TModel \| null> | Returns the first hydrated model. | | QueryBuilder.count | () => Promise<number> | Executes a count. | | QueryBuilder.paginate | (perPage: number, page?: number) => Promise<Page<TModel>> | Counts and selects one page. | | QueryBuilder.insert | (values: Row \| Row[], returning?: string[] \| '*') => Promise<QueryResult> | Inserts rows. | | QueryBuilder.update | (values: Row, returning?: string[] \| '*') => Promise<QueryResult> | Updates matching rows. | | QueryBuilder.delete | (returning?: string[] \| '*') => Promise<QueryResult> | Deletes or soft-deletes matching rows. | | Model.query | (driver?) => QueryBuilder | Starts a warded query for the model. | | Model.find | (id: string \| number) => Promise<TModel \| null> | Finds by primary key. | | Model.findOrFail | (id: string \| number) => Promise<TModel> | Finds by primary key or throws NotFound. | | Model.create | (values: Row) => Promise<TModel> | Mass-assigns fillable attributes and saves. | | Model.forActor | (actor: GateActor) => QueryBuilder | Starts a query warded for an actor. | | Model.withTrashed | () => QueryBuilder | Starts a query that includes soft-deleted rows. | | Model.fill | (values: Row, raw?: boolean) => this | Copies fillable attributes onto the instance. | | Model.save | (driver?) => Promise<this> | Persists the instance and fires lifecycle hooks. | | Model.update | (values: Row) => Promise<this> | Fills and saves. | | Model.delete | (driver?) => Promise<void> | Deletes or soft-deletes the instance. | | Model.restore | (driver?) => Promise<this> | Clears deleted_at on a soft-deleted instance. | | Model.belongsTo | (related, foreignKey, ownerKey?) => RelationLoad | Builds a belongsTo descriptor. | | Model.hasOne | (related, foreignKey, localKey?) => RelationLoad | Builds a hasOne descriptor. | | Model.hasMany | (related, foreignKey, localKey?) => RelationLoad | Builds a hasMany descriptor. | | Scrivener.unwarded | (model) => { query: () => QueryBuilder } | Starts an unwarded query. | | ModelLifecycle | class ModelLifecycle extends Event | Hook name, model name, and instance for observers. | | ModelHook | type | 'creating' \| 'created' \| 'updating' \| 'updated' \| 'saving' \| 'saved' \| 'deleting' \| 'deleted' \| 'restored' | | Page | interface Page<TModel> | data, total, perPage, page, and lastPage from paginate. |

Testing

Point defineConfig at FakeDatabase from @avelonjs/conformance and assert on toIR() output or hydrated models.

import { defineConfig } from '@avelonjs/core'
import { FakeDatabase } from '@avelonjs/conformance'
import { Model } from '@avelonjs/orm'

defineConfig({ name: 'test', drivers: { database: new FakeDatabase() } })

export class User extends Model {
  static override table = 'assay_users'
  static override fillable = ['id', 'email', 'name', 'age', 'nickname']
}

await User.create({
  id: 'u1',
  email: '[email protected]',
  name: 'One',
  age: 20,
  nickname: null,
})