Skip to content

Schema-first design

In gisila_orm the source of truth for your tables is a YAML file, not Dart classes and not the live database. You describe models, columns, relations, indexes and constraints in *.gisila.yaml; the generator derives everything else from it:

blog.gisila.yaml ──▶ Dart models, <Model>Table columns, relations
──▶ full CREATE / DROP SQL
──▶ incremental migrations (diff against the last snapshot)

Because the Dart code and the SQL come from the same file, they can’t drift apart: a column that exists in PostTable exists in the migration that creates the table, with a matching type and nullability.

  • A service that owns its PostgreSQL database and evolves its schema with the code.
  • You want compile-time checked column names and value types in queries (PostTable.title.eq(42) doesn’t compile).
  • You want migrations written for you from schema edits, and refused when they would drop data.
  • Several packages or apps share one data model: the YAML is easy to review in pull requests.
  • The database is owned by someone else (a legacy system, a reporting replica). Generating migrations makes no sense there. Write TableMeta by hand for the tables you read, or use raw SQL.
  • Heavy use of features the schema can’t describe: partial or expression indexes, composite primary keys, triggers, views, partitioning, decimal precision and scale, PostGIS. You can still use gisila_orm, but put that DDL in hand-written migrations next to the generated ones.
  • A database other than PostgreSQL. Only PostgreSQL is supported.
  1. Parse and validate. All schema files merge into one SchemaDefinition. The validator reports every mistake at once, with file, line and a “did you mean” hint, before anything is written.
  2. Apply conventions. Table names default to plural snake_case (BlogPost → blog_posts), a missing primary key becomes id BIGSERIAL, a foreign key author is stored as author_id and typed like its target’s primary key, and relations get an inverse when you name it with reverse_name.
  3. Emit. Dart models and typed columns, a full-schema SQL script, and an incremental migration from the diff with the last committed snapshot.

The schema describes shape; it doesn’t describe behaviour. Business rules beyond NOT NULL, UNIQUE, CHECK and foreign keys live in your Dart code. allow_blank: false is the one exception: it generates a validate() check in Dart, not a database constraint.

You get You give up
One reviewed file for the whole data model Some Postgres features need hand-written SQL
Typed queries that track the schema Regenerating after every schema edit
Safe, generated migrations Control over migration SQL unless you edit or add files
Models free of annotations and runtime reflection Custom methods on models (generated files are overwritten; use extensions)

To add behaviour to a generated model, write an extension in your own file:

import 'models/blog.gisila.g.dart';
extension PostX on Post {
bool get isPublished => publishedAt != null;
}
Author:
columns:
name: { type: varchar, is_null: false }
email: { type: varchar, is_null: false, is_unique: true }
Post:
columns:
title: { type: varchar, is_null: false, max_length: 200 }
published_at: { type: timestamp }
author:
type: Author
is_null: false
on_delete: CASCADE
reverse_name: posts

From this you get Author, Post, AuthorTable, PostTable, Post.author, Author.posts, authors(), posts(), the CREATE TABLE statements with a foreign key posts.author_id → authors.id ON DELETE CASCADE, and a migration.