ormed 0.1.0-dev+3
ormed: ^0.1.0-dev+3 copied to clipboard
Strongly typed ORM core primitives (annotations, metadata, codecs, and generator) for the routed ecosystem.
ormed #
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,@OrmEventto 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:
TestDatabaseManagerwith 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_clitodev_dependenciesensures version parity with your project but will include all database drivers and their dependencies in yourpubspec.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.
User→usersUserProfile→user_profiles
- Columns: Inferred from field names using snake_case.
emailAddress→email_addresscreatedAt→created_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) #
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) #
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) #
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.
Related Packages #
| 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.