PHORM Annotations 🚀
Annotation library for declarative SQL table and schema definitions in Dart.
phorm_annotations provides the building blocks for defining your database schema using standard Dart classes. It is designed to work with phorm_generator to automate SQL generation and runtime metadata.
Features
- Declarative Schemas: Define tables and columns using
@Schema,@Column, and@ID. - Relationships: Annotations for
HasMany,BelongsTo,HasOne, andManyToMany(with pivot tables). - Type Safety: Database-agnostic logical types mapped to SQLite.
- Constraints: Support for
UNIQUE,CHECK, andDEFAULTvalues. - Paranoid Mode: Built-in support for soft deletes via
deletedAt. - Factories:
Factory<T>interface for generating test/seed data. - Value Converters:
ValueConverter<D, S>for custom Dart ↔ SQL type transformations.
Basic Usage
1. Annotate your model
import 'package:phorm_annotations/phorm_annotations.dart';
part 'user.sql.g.dart';
@Schema(
tableName: 'users',
paranoid: true,
columnNaming: ColumnNamingStrategy.snakeCase,
indexes: [
Index(columns: ['email'], unique: true),
Index(columns: ['first_name', 'last_name']),
],
)
class User extends Model with _$PhormUserMixin {
@ID(autoIncrement: false, unique: true)
@override
final String id;
@Column()
final String firstName;
@Column()
final String lastName;
@Column(unique: true)
final String email;
@Column(
validators: [
ContainsValidator(['M', 'F', 'Other'], constraint: 'gender_check'),
],
)
final String gender;
User({
required this.id,
required this.firstName,
required this.lastName,
required this.email,
required this.gender,
});
factory User.fromJson(Map<String, dynamic> json) => _$PhormUserFromJson(json);
}
2. Run the generator
dart run build_runner build
Annotations Reference
@Schema
Defines table-level configuration.
| Property | Type | Description |
|---|---|---|
tableName |
String? |
Explicit SQL table name. |
indexes |
List<Index> |
List of table indexes. |
paranoid |
bool |
Enables soft deletes (requires deletedAt column). |
columnNaming |
ColumnNamingStrategy |
Strategy for mapping field names to SQL (snakeCase, camelCase, pascalCase). |
dialect |
SqlDialectKind |
Target SQL dialect for DDL generation (sqlite (default), postgres, mysql). |
relationships |
List<Relationship> |
Define HasMany, HasOne, or BelongsTo. |
timestamps |
bool |
Auto-manage createdAt / updatedAt (default true). |
useToJson |
bool |
Generate the toJson() mapper (default true). |
useFromJson |
bool |
Generate the fromJson() factory (default true). |
useCopyWith |
bool |
Generate the copyWith() helper (default true). |
useToString |
bool |
Generate the toString() helper (default true). |
useValidator |
bool |
Generate the validate() method from column CHECK constraints (default true). |
generateFullService |
bool |
Generate the pluralized static service class (e.g. Users) exposing the full CRUD/query API and column constants. Set false to emit only the lightweight schema/table/mappers and skip the large service (default true). |
@Column
Standard column definition. PHORM automatically infers the SQLite type from the Dart field type.
@Column(unique: true, defaultValue: 'active')
final String status;
| Property | Type | Description |
|---|---|---|
columnName |
String? |
Override column name. |
sqlType |
String? |
Explicit SQL type override as a raw string (e.g. 'VARCHAR(255)'). |
type |
SqlType? |
Explicit SQL type as a typed object (e.g. VARCHAR(255), DECIMAL(10, 2), JSONB()). |
unique |
bool |
Enforce UNIQUE constraint. |
nullable |
bool |
Mark column as NULL or NOT NULL. |
defaultValue |
dynamic |
SQL DEFAULT value. |
validators |
List<IValidator>? |
List of validators (triggers SQL CHECK and Dart validation). |
converter |
ValueConverter? |
Custom Dart ↔ SQL value transformer. |
collate |
String? |
Specify string collation (e.g. Collate.noCase). |
SqlType objects are organised by dialect: common_types (VARCHAR, TEXT, INTEGER, BIGINT, BOOLEAN, REAL, DOUBLE, DECIMAL, DATE, TIME, TIMESTAMP, BLOB, JSON), sqlite_types (NUMERIC, Collate), postgres_types (JSONB), mysql_types (scaffolded). Resolution precedence: sqlType → type → converter → inferred Dart type.
Automatic Type Mapping
PHORM maps Dart types to SQLite types automatically:
| Dart Type | SQLite Type | Notes |
|---|---|---|
String |
TEXT |
Default for strings, UUIDs |
int |
INTEGER |
Standard integer |
bool |
INTEGER |
Stored as 1 (true) / 0 (false) |
double |
REAL |
Floating point numbers |
num |
NUMERIC |
Supports both int and double |
DateTime |
TEXT |
Stored as ISO-8601 strings |
Uint8List |
BLOB |
Binary data |
Standard SQL Types
For manual overrides in @Column(sqlType: ...) or when adding columns in migrations, use the SqlTypes class to avoid hardcoding strings:
// In a migration
table.addColumn(name: 'age', type: SqlTypes.integer, version: 2);
// In a model
@Column(collate: Collate.noCase)
final String username;
Available types: SqlTypes.text, SqlTypes.integer, SqlTypes.real, SqlTypes.blob, SqlTypes.numeric.
Referential Actions
When defining relationships, use ReferentialAction for onDelete and onUpdate parameters:
HasMany(
model: Order,
foreignKey: 'user_id',
onDelete: ReferentialAction.cascade,
);
Available actions: cascade, setNull, setDefault, restrict, noAction.
🔗 Relationships
Relationships define how models connect. They are used by phorm for automatic Eager Loading.
HasMany: One-to-Many (e.g., User has many Posts).HasOne: One-to-One (e.g., User has one Profile).BelongsTo: Many-to-One (e.g., Post belongs to User).
@Schema(
tableName: 'users',
relationships: [
HasMany(model: Post, foreignKey: 'user_id'),
ManyToMany(
model: Role,
pivotTable: 'user_roles',
foreignKey: 'user_id',
relatedKey: 'role_id',
),
],
)
class User extends Model with _$PhormUserMixin { ... }
Query Results
phorm provides two read methods with explicit return types:
| Method | Returns | Use when |
|---|---|---|
readAll(...) |
Result<T> |
You only need the list of records |
readAllWithCount(...) |
ResultWithCount<T> |
You need the list + total count (pagination) |
// Simple read — no cast needed
final result = await userService.readAll(limit: 20);
for (final user in result.data) { ... }
// With count — no cast needed
final paged = await userService.readAllWithCount(limit: 20, offset: 40);
print('Page 3: ${paged.data.length} of ${paged.count} total');
Factories
Use the Factory<T> interface to generate model instances for testing or seeding:
class UserFactory extends Factory<User> {
int _i = 0;
@override
User create() {
_i++;
return User(id: 'user_$_i', name: 'User $_i', email: 'user$_i@example.com');
}
}
final users = UserFactory().createMany(50); // List<User>
Status
- Dart SDK:
>=3.0.0 <4.0.0 - License: MIT License