ormed 0.1.0-dev+3 copy "ormed: ^0.1.0-dev+3" to clipboard
ormed: ^0.1.0-dev+3 copied to clipboard

Strongly typed ORM core primitives (annotations, metadata, codecs, and generator) for the routed ecosystem.

ormed #

License: MIT Documentation Buy Me A Coffee

A strongly-typed ORM (Object-Relational Mapping) core for Dart, inspired by Eloquent (Laravel), GORM, SQLAlchemy, and ActiveRecord. Combines compile-time code generation with runtime flexibility for type-safe database operations.

Features #

  • Annotations: @OrmModel, @OrmField, @OrmRelation, @OrmScope, @OrmEvent to describe tables, columns, relationships, and behaviors
  • Code Generation: Emits ModelDefinition, FieldDefinition, ModelCodec, DTOs, and tracked model classes
  • Query Builder: Fluent, type-safe queries with filtering, ordering, pagination, aggregates, and eager loading
  • Relationships: HasOne, HasMany, BelongsTo, ManyToMany, and polymorphic relations (MorphOne, MorphMany)
  • Migrations: Schema builder with Laravel-style table blueprints and driver-specific overrides
  • Value Codecs: Custom type serialization with driver-scoped codec registries
  • Soft Deletes: Built-in support with withTrashed(), onlyTrashed() scopes
  • Event System: Model lifecycle events (Creating, Created, Updating, Updated, Deleting, Deleted)
  • Testing: TestDatabaseManager with database isolation strategies

Installation #

dependencies:
  ormed: any
  ormed_sqlite: any # Or ormed_postgres, ormed_mysql

dev_dependencies:
  ormed_cli: any
  build_runner: ^2.4.0

Getting Started #

The recommended way to use Ormed is via the ormed CLI, which manages migrations, seeders, and project scaffolding.

1. Install the CLI #

You can install the CLI globally (recommended for smaller dependency trees) or add it to your dev_dependencies:

# Global installation
dart pub global activate ormed_cli

Note: Adding ormed_cli to dev_dependencies ensures version parity with your project but will include all database drivers and their dependencies in your pubspec.lock.

2. Initialize the Project #

Scaffold the configuration and directory structure:

ormed init

(Or use dart run ormed_cli:ormed init if not installed globally).

This creates ormed.yaml and the lib/src/database directory.

3. Define a Model #

Create a model file (e.g., lib/src/models/user.dart):

import 'package:ormed/ormed.dart';

part 'user.orm.dart';

@OrmModel()
class User extends Model<User> {
  final String name;
  final String email;

  User({required this.name, required this.email});
}

3. Generate ORM Code #

Run build_runner to generate model definitions and tracked classes:

dart run build_runner build

4. Create and Run Migrations #

Generate a migration for your model:

dart run ormed_cli:ormed make --name create_users_table --create --table users

Edit the generated migration in lib/src/database/migrations/ to add columns:

void up(SchemaBuilder schema) {
  schema.create('users', (table) {
    table.id();
    table.string('name');
    table.string('email').unique();
    table.timestamps();
  });
}

Apply the migration:

dart run ormed_cli:ormed migrate

5. Seed the Database #

Create a seeder:

dart run ormed_cli:ormed make --name UserSeeder --seeder

Add data in lib/src/database/seeders/user_seeder.dart:

Future<void> run() async {
  await seed<User>([
    {'name': 'John Doe', 'email': 'john@example.com'},
  ]);
}

Run the seeders:

dart run ormed_cli:ormed seed

6. Bootstrap and Use #

In your application code, use bootstrapOrm() to register all generated models and metadata automatically:

import 'package:ormed/ormed.dart';
import 'package:ormed_sqlite/ormed_sqlite.dart';
import 'orm_registry.g.dart'; // Generated by ormed

void main() async {
  // 1. Bootstrap the registry
  final registry = bootstrapOrm();

  // 2. Initialize DataSource
  final ds = DataSource(DataSourceOptions(
    driver: SqliteDriverAdapter.file('database.sqlite'),
    registry: registry,
  ));
  await ds.init();

  // 3. Query your models
  final users = await ds.query<User>().get();
  print('Found ${users.length} users');
}

Migrations #

Ormed supports two migration formats, which can be mixed in the same project. The migration runner is format-agnostic, allowing you to use the best tool for each change.

Dart Migrations (Default) #

Type-safe migrations using a fluent SchemaBuilder. Best for most use cases.

dart run ormed_cli:ormed make --name add_bio_to_users

SQL Migrations #

Raw .sql files for complex schema changes or when porting existing SQL scripts.

dart run ormed_cli:ormed make --name add_bio_to_users --format sql

This creates a directory with up.sql and down.sql files.

Simultaneous Support #

You can have Dart and SQL migrations in the same project. The runner executes them in chronological order based on their timestamps, regardless of format. This flexibility allows you to use Dart for standard schema changes and SQL for database-specific features or complex optimizations.

Annotations #

@OrmModel #

By default, Ormed infers the table name by pluralizing and snake_casing the class name (e.g., User -> users, UserRole -> user_roles).

@OrmModel(
  table: 'users',
  schema: 'public',           // Optional schema/namespace
  softDeletes: true,          // Enable soft deletes
  primaryKey: 'id',           // Alternative to @OrmField(isPrimaryKey: true)
  fillable: ['email', 'name'], // Mass assignment whitelist
  guarded: ['role'],          // Mass assignment blacklist
  hidden: ['password'],       // Hidden from serialization
  connection: 'analytics',    // Connection name override
)

@OrmField #

By default, Ormed infers the column name by snake_casing the field name (e.g., emailAddress -> email_address).

@OrmField(
  columnName: 'user_email',   // Custom column name
  isPrimaryKey: true,
  autoIncrement: true,
  isNullable: true,
  isUnique: true,
  isIndexed: true,
  codec: JsonMapCodec,        // Custom value codec
  columnType: ColumnType.text, // Override column type
  defaultValueSql: 'NOW()',   // SQL default expression
  driverOverrides: {...},     // Per-driver customizations
)

@OrmRelation #

// One-to-One (foreign key on related table)
@OrmRelation.hasOne(related: Profile, foreignKey: 'user_id')
final Profile? profile;

// One-to-Many
@OrmRelation.hasMany(related: Post, foreignKey: 'author_id')
final List<Post> posts;

// Inverse relation
@OrmRelation.belongsTo(related: User, foreignKey: 'user_id')
final User author;

// Many-to-Many
@OrmRelation.manyToMany(
  related: Tag,
  pivot: 'post_tags',
  foreignPivotKey: 'post_id',
  relatedPivotKey: 'tag_id',
)
final List<Tag> tags;

// Polymorphic
@OrmRelation.morphMany(related: Comment, morphName: 'commentable')
final List<Comment> comments;

Query Builder #

final posts = await ds.query<$Post>()
    // Filtering
    .whereEquals('status', 'published')
    .whereIn('category_id', [1, 2, 3])
    .whereNull('deleted_at')
    .whereBetween('views', 100, 1000)
    .whereHas('comments', (q) => q.whereEquals('approved', true))
    
    // Eager loading
    .with_(['author', 'tags'])
    .withCount('comments')
    
    // Ordering & Pagination
    .orderByDesc('created_at')
    .limit(20)
    .offset(40)
    
    // Execute
    .get();

// Aggregates
final count = await ds.query<$Post>().count();
final avgViews = await ds.query<$Post>().avg('views');

// Pagination
final page = await ds.query<$Post>().paginate(page: 2, perPage: 15);

Migrations #

class CreateUsersTable extends Migration {
  @override
  void up(SchemaBuilder schema) {
    schema.create('users', (table) {
      table.id();                              // bigIncrements primary key
      table.string('email').unique();
      table.string('name').nullable();
      table.boolean('active').defaultValue(true);
      table.timestamps();                      // created_at, updated_at
      table.softDeletes();                     // deleted_at
    });
  }

  @override
  void down(SchemaBuilder schema) {
    schema.drop('users');
  }
}

Column Types #

Method Description
id() Auto-incrementing big integer primary key
string(name, length?) VARCHAR column
text(name) TEXT column
integer(name) / bigInteger(name) Integer columns
decimal(name, precision?, scale?) Decimal column
boolean(name) Boolean column
dateTime(name) / timestamp(name) DateTime columns
json(name) / jsonb(name) JSON columns
binary(name) Binary/BLOB column
enum_(name, values) Enum column

Column Modifiers #

table.string('locale')
    .nullable()
    .unique()
    .defaultValue('en')
    .comment('User locale')
    .driverType('postgres', ColumnType.jsonb())  // Per-driver type
    .driverDefault('postgres', "'{}'::jsonb");   // Per-driver default

Value Codecs #

Custom type serialization for database ↔ Dart conversion:

class UuidCodec extends ValueCodec<UuidValue> {
  const UuidCodec();

  @override
  UuidValue? decode(Object? value) =>
      value == null ? null : UuidValue.fromString(value as String);

  @override
  Object? encode(UuidValue? value) => value?.uuid;
}

// Register globally
ValueCodecRegistry.instance.registerCodecFor(UuidCodec, const UuidCodec());

// Or per-driver
ValueCodecRegistry.instance.forDriver('postgres')
    .registerCodec(key: 'UuidValue', codec: const PostgresUuidCodec());

Repository Operations #

final repo = ds.repo<$User>();

// Create
await repo.insert($UserInsertDto(email: 'new@example.com'));
await repo.insertMany([dto1, dto2, dto3]);

// Read
final user = await repo.find(1);
final all = await repo.all();
final first = await repo.first();

// Update (accepts DTOs, models, or maps)
await repo.update(dto, where: {'id': 1});
await repo.update(dto, where: (q) => q.whereEquals('email', 'old@example.com'));

// Delete
await repo.delete(model);
await repo.deleteByIds([1, 2, 3]);

// Upsert
await repo.upsert(dto, uniqueBy: ['email']);

Transactions #

await ds.transaction(() async {
  await ds.repo<$User>().insert(userDto);
  await ds.repo<$Profile>().insert(profileDto);
  // Automatically rolls back on exception
});

Naming Conventions #

ormed follows a "convention over configuration" approach for database names:

  • Tables: Inferred from the model class name using plural snake_case.
    • Userusers
    • UserProfileuser_profiles
  • Columns: Inferred from field names using snake_case.
    • emailAddressemail_address
    • createdAtcreated_at

You can override these using the @OrmModel and @OrmField annotations:

@OrmModel(tableName: 'legacy_users')
class User extends Model<User> {
  @OrmField(columnName: 'user_email')
  final String email;
}

Database Drivers #

Ormed requires a database driver to connect to your database. Choose the driver that matches your database:

SQLite (ormed_sqlite) #

pub package

Best for: Local development, mobile apps, embedded databases, testing.

dependencies:
  ormed_sqlite: any
import 'package:ormed_sqlite/ormed_sqlite.dart';

// In-memory (great for testing)
final adapter = SqliteDriverAdapter.inMemory();

// File-based
final adapter = SqliteDriverAdapter.file('app.sqlite');

Features: In-memory & file databases, JSON1 extension, FTS5 full-text search, window functions, R*Tree spatial indexes.

PostgreSQL (ormed_postgres) #

pub package

Best for: Production applications, complex queries, advanced data types.

dependencies:
  ormed_postgres: any
import 'package:ormed_postgres/ormed_postgres.dart';

final adapter = PostgresDriverAdapter.fromUrl(
  'postgres://user:pass@localhost:5432/mydb',
);

Features: UUID, JSONB, arrays, geometric types, full-text search (tsvector), range types, RETURNING clause, connection pooling.

MySQL/MariaDB (ormed_mysql) #

pub package

Best for: Existing MySQL infrastructure, WordPress/Laravel migrations, web hosting.

dependencies:
  ormed_mysql: any
import 'package:ormed_mysql/ormed_mysql.dart';

final adapter = MySqlDriverAdapter.fromUrl(
  'mysql://user:pass@localhost:3306/mydb',
);

Features: MySQL 8.0+, MariaDB 10.5+, JSON columns, ENUM/SET types, spatial types, Laravel-compatible configuration.

Package Description
ormed_sqlite SQLite driver adapter
ormed_postgres PostgreSQL driver adapter
ormed_mysql MySQL/MariaDB driver adapter
ormed_cli CLI tool for migrations and scaffolding

Examples #

See the example/ directory and packages/orm_playground for comprehensive usage examples.

3
likes
140
points
--
downloads

Publisher

verified publisherglenfordwilliams.com

Strongly typed ORM core primitives (annotations, metadata, codecs, and generator) for the routed ecosystem.

Repository (GitHub)
View/report issues

Topics

#orm #database #sql

Documentation

Documentation
API reference

Funding

Consider supporting this project:

www.buymeacoffee.com

License

MIT (license)

Dependencies

analyzer, build, carbonized, collection, contextual, crypto, dotenv, glob, inflector_dart, meta, path, source_gen, test, yaml

More

Packages that depend on ormed