Skip to content
rekkrjsPublic

About

An Eloquent-inspired ORM for Bun and Node.js, supporting SQLite, MySQL, and PostgreSQL.

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

ORM

Runs on Bun and Node.js. Install with:

bun add @rekkr/orm          # Bun 1.4.2+
npm install @rekkr/orm      # Node.js 24.21+

On Node.js, add pg, mysql2 or ioredis for the servers you use β€” see Installation.

An Eloquent-inspired ORM for Bun and Node.js. On Bun it runs on the native bun:sql client with zero runtime dependencies; on Node.js it runs on the built-in node:sqlite and the standard pg, mysql2 and ioredis drivers. It supports SQLite, MySQL, and PostgreSQL with full TypeScript typing, a chainable query builder, schema migrations, model observers, polymorphic relations, and an interactive REPL.


Features

  • πŸ”₯ Bun-native, Node.js-ready β€” bun:sql on Bun; node:sqlite, pg and mysql2 on Node.js, held to the same results by one test suite
  • πŸͺΆ Zero runtime dependencies β€” On Bun, nothing beyond Bun itself; on Node.js, only the drivers for the databases you use
  • πŸ“¦ Multi-database β€” SQLite, MySQL, and PostgreSQL support
  • 🧯 Portable unique errors β€” Duplicate unique and primary keys share one safe public error type across drivers
  • πŸ”· Fully Typed β€” Generate declarations for plain extends Model classes with typed attributes, queries, relations, and eager loads
  • πŸ—οΈ Schema Builder β€” Programmatic table creation, indexes, foreign keys
  • πŸ” Query Builder β€” Chainable joins, unions, relation and pivot shortcuts, SQL diagnostics, and Laravel-style aliases
  • ⚑ Predictable direct JSON β€” rawJson() skips per-row hydration or reports why it cannot
  • πŸ”Ž Search β€” PostgreSQL full-text search and SQLite FTS5 engines, no search service to run
  • 🧠 Tagged Cache β€” Redis-backed cache facade, query remember(), and exact tag invalidation
  • πŸ“£ Events β€” Application-level event dispatcher with function listeners and class handlers
  • πŸ“¨ Queue Jobs β€” Database- and Redis-backed background job queue with named queues, retries, delays, and an orm queue worker
  • πŸ› οΈ Commands β€” Artisan-style CLI commands with a signature DSL, argument/option parsing, and orm run
  • 🧬 Eloquent-style Models β€” Property attributes, casts, scopes, soft deletes, and concurrency-safe find-or-create helpers
  • 🧺 Collections β€” Laravel-style paging, filtering, partitioning, strict matching, and aggregation helpers
  • πŸ”— Relations β€” Standard, many-to-many, polymorphic, through, one-of-many, and relation queries
  • πŸ‘οΈ Observers β€” Lifecycle hooks (creating, created, updating, updated, etc.)
  • πŸš€ Migrations & CLI β€” Create, run, reset, refresh, and inspect migrations from the command line
  • 🌱 Seeders & Factories β€” Root or targeted seeders plus typed factories for bulk data and relationship graphs
  • πŸ’¬ REPL β€” Inspect models and run queries interactively with orm repl
  • ⚑ Streaming β€” chunk, chunkById, cursor, each, eachById, and lazy for memory-efficient large dataset processing
  • 🏒 Multi-tenant β€” Database-per-tenant, schema-per-tenant, and RLS strategies with DB.tenant() and TenantContext

Installation

bun add @rekkr/orm          # Bun
npm install @rekkr/orm      # Node.js 24.21+
npm install pg              # Node.js + PostgreSQL

See Installation for the driver each database needs and what differs between the runtimes.


Quickstart

Extend Model directly. Table names follow conventions (User β†’ users), while fillable controls which attributes may be mass-assigned:

import { Model } from "@rekkr/orm";

class User extends Model {
  static override fillable = ["name", "email"];
  static override softDeletes = true;

  posts() {
    return this.hasMany(Post);
  }
}

class Post extends Model {
  static override fillable = ["user_id", "title"];

  author() {
    return this.belongsTo(User);
  }
}

Create the users and posts tables with default timestamp names and a foreign key between them:

// database/migrations/<timestamp>_create_blog_tables.ts
import { Migration, Schema } from "@rekkr/orm";

export default class CreateBlogTables extends Migration {
  async up() {
    await Schema.create("users", (table) => {
      table.id();
      table.string("name");
      table.string("email").unique();
      table.string("role").default("member");
      table.string("locale", 10).default("en");
      table.timestamp("email_verified_at").nullable();
      table.timestamps();
      table.softDeletes();
    });

    await Schema.create("posts", (table) => {
      table.id();
      table.foreignId("user_id").constrained("users").cascadeOnDelete();
      table.string("title");
      table.text("body").nullable();
      table.boolean("published").default(false);
      table.timestamp("published_at").nullable();
      table.timestamps();
      table.index(["user_id", "created_at"]);
    });
  }

  async down() {
    await Schema.dropIfExists("posts");
    await Schema.dropIfExists("users");
  }
}

See Models: timestamps for custom column names, including a camelCase model and matching schema.

Native getters can be serialized with appends without duplicating them in static accessors:

class User extends Model {
  static override fillable = ["firstName", "lastName"];
  static override appends = ["fullName"];

  get fullName(): string {
    return `${this.getAttribute("firstName")} ${this.getAttribute("lastName")}`.trim();
  }
}

Create the shared configuration used by both the CLI and your application:

// orm.config.ts
import type { OrmConfig } from "@rekkr/orm";

const config: OrmConfig = {
  connection: { url: "sqlite://app.db" },
};

export default config;

Configure ORM once at application startup, then query your models:

import { configureOrm } from "@rekkr/orm";
import config from "./orm.config";

configureOrm(config);

const user = await User.where("email", "alice@example.com").firstOrFail();
const posts = await user.posts().get();

console.log(user.getAttribute("name"), posts.length);

// Three ways to list users:
const users = await User.orderBy("id").get();     // Collection of User models
const usersJson = await User.orderBy("id").json(); // JSON-ready objects
const usersRawJson = await User.orderBy("id").rawJson(); // JSON-ready objects without hydrating models

Or use the DB facade for ad-hoc table access without a model:

import { DB } from "@rekkr/orm";

const rows = await DB.table("audit_logs")
  .where("event", "login")
  .orderBy("created_at", "desc")
  .limit(10)
  .get();

See the Quickstart guide for the full walkthrough.

Use the canonical migration commands for generation, dry runs, and seeded rebuilds:

orm make:migration create_blog_tables
orm migrate --pretend
orm migrate:rollback --step=2 --pretend
orm migrate:refresh --seed
orm migrate:fresh --seed
orm migrate:fresh --seed --seeder=UserSeeder

--landlord, --tenants, and --tenant apply to both phases. In production, mutating migration commands require confirmation or --force; status and pretend runs never prompt, and pretend does not execute SQL. Migration locking is automatic, so there is no --isolated flag. With --json, stdout remains one document.


Documentation

Getting Started

Topic Summary
Installation Add the package to a Bun or Node.js project.
Configuration Connection, tenancy, migrations, seeders, type generation.
Quickstart End-to-end walkthrough: install β†’ config β†’ migration β†’ model β†’ query.

Database

Topic Summary
Schema Builder Tables, columns, indexes, foreign keys.
Migrations Versioned schema changes, rollback, multi-tenant scopes, auto-create database / schema.
Seeders Populate development and test data.
Transactions DB.transaction(), explicit connection transactions, and nested savepoints.

Querying

Topic Summary
Query Builder Chainable where / join / with / aggregates, DB facade, raw queries.
Cache Redis-backed cache API, query remember(), exact tag invalidation.
Search PostgreSQL full-text search and SQLite FTS5.
Collections map, filter, groupBy, and other helpers returned by get().
Models Defining models, casts, accessors, soft deletes, persistence.
Relationships hasMany, belongsTo, belongsToMany, polymorphic, eager loading.
Validation Typed Laravel-style validator with fluent rules and tenant-aware database checks.

TypeScript

Topic Summary
TypeScript Plain model declarations, generated attributes, typed builders, scopes, and accessors.
Type Generation Generate attribute interfaces from your database schema.

Background Processing

Topic Summary
Queue Jobs Dispatch jobs to named queues, run workers with orm queue, retries, delays, failed-job tracking, database and Redis drivers.
Commands Artisan-style CLI commands with signature DSL, argument/option parsing, output helpers, and orm run.

Advanced

Topic Summary
SvelteKit Helper Typed route model binding and action validation helpers for +page.server.ts.
Policies Register model/resource policies, use can / authorize, and enforce access in RouteBuilder.
Observers Lifecycle hooks for creating, updating, deleting, and more.
Events Application-level events with function listeners, class handlers, and temporary subscriptions.
Library Usage Programmatic API via configureOrm().
Testing In-memory SQLite and transactional test isolation.

The full index lives at docs/README.md.


License

MIT

This project is a fork of Bunny.

Performance and verification: benchmark history.

About

An Eloquent-inspired ORM for Bun and Node.js, supporting SQLite, MySQL, and PostgreSQL.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages