@plinthjs/orm
v0.1.0
Published
Mason ORM (Eloquent equivalent). Tier-2 flagship: async relationship layer (ADR-0003) running over @plinthjs/database.
Maintainers
Readme
@plinthjs/orm
The Eloquent-equivalent ORM — Mason's Tier-2 flagship. Depends on
@plinthjs/support.
Current scope: the async relationship-layer prototype. Per ADR-0003, the relationship API is settled before the rest of the ORM is built. This package validates that API and the batched eager-loading algorithm over a pluggable
DataSource— the Tier-2 build replaces the in-memory source with the TypeORM-backed query builder under this exact surface. See ADR-0007 for the API shape.
The four access patterns (no transparent lazy loading)
import { Model, ArrayDataSource } from '@plinthjs/orm'
class User extends Model {
static table = 'users'
static relations = {
posts: (m) => m.hasMany(Post, 'user_id', 'id'),
profile: (m) => m.hasOne(Profile, 'user_id', 'id'),
}
}
class Post extends Model {
static table = 'posts'
static relations = { author: (m) => m.belongsTo(User, 'user_id', 'id') }
}
// 1. Eager — one query for users, one for posts (no N+1):
const users = await User.query().with('posts').get()
users[0].getRelation('posts') // Post[]
// 2. Lazy-eager — load onto an existing model:
const user = await User.query().first()
await user.load('posts', 'profile')
// 3. Relation query — constrained and chainable:
const published = await user.related('posts').where('published', true).get()
// 4. Access — populated only after loading; throws RelationNotLoadedError otherwise:
user.getRelation('posts')Relations supported in the prototype: hasMany, belongsTo, hasOne, with nested
eager loading (with('posts.comments')). belongsToMany (pivot) and the morph relations land
in the Tier-2 build.
What the prototype proves
- The explicit
with/load/relatedtriad is ergonomic and type-safe. - Batched eager loading avoids N+1 — verified by the
ArrayDataSourcequery log: loading posts for N users issues 2 queries, andposts.commentsissues 3 (one per level), regardless of row counts. user.postsas a loaded-data property (throwing when unloaded) is achievable in JS via a generated prototype getter, with relation definitions instatic relations.
Architecture
Model ──query()──▶ QueryBuilder ──select()──▶ DataSource (ArrayDataSource | TypeORM in Tier 2)
│ │
│ related()/load() └─ eagerLoad(): one whereIn query per relation level, then match()
▼
Relation (HasMany | HasOne | BelongsTo): toQuery / eagerQuery / match / getResults