Vyuh Entity System
A type-safe, configuration-driven framework for managing business entities in Flutter applications. Define entities once with full CRUD, permissions, drafts, versioning, audit trails, and configurable UI layouts.
Why Entity System?
Configuration-Driven Entities
Define metadata, API, layouts, editors, actions, and routing in a single EntityConfiguration descriptor per entity type.
Authorization-Aware by Default
Declarative gating via the Authorize DSL on routes, layouts, actions, and tabs. Backed by a pluggable AuthorizationProvider with permissions, roles, and combinator expressions.
Drafts, Versioning & Audit
EntityCapability mixins (Auditable, Versionable, Draftable) layer in audit fields, version history, and the full draft approval workflow with no boilerplate.
Configurable Layouts & Editors
Table, grid, and custom collection layouts; tabbed detail layouts; per-entity dashboards and analytics. Form-based or multi-part editors with optional e-signature gates.
Getting Started
Core Concepts
Key Components
| Component | Description |
|---|---|
| EntityConfiguration<T> | Central descriptor unifying metadata, API, layouts, editors, actions, fields, and routing |
| EntityBase | Base class for all entities (id, name) with capability mixins for Auditable / Versionable / Draftable fields |
| EntityApi<T> / HttpEntityApi<T> | Abstract CRUD contract with the standard HTTP implementation, EndpointBuilder, and query caching |
| EntityLayouts<T> | Holds list, detail, summary, dashboard, analytics, and item/comparison layouts |
| EntityEditor<T> | Editor configuration: form-based, multi-part, signature-driven, with transformers and lifecycle resolvers |
| EntityRouting<T> | Path patterns (NavigationPathBuilder), the route builder, navigation mode, and per-route Authorize gates |
| EntitySystemPlugin | Vyuh plugin that registers entity configurations, generates GoRouter routes, and owns the AuthorizationProvider |
| EntityExtensionDescriptor | Per-feature descriptor for entities, services, field formatters, and hierarchies |
Learn More
Quick Example
Define a Course entity for a Learning Management System. The usual path is annotation-driven: the generator emits the base class, fields registry, API, layouts, editor, routes, and courseConfig descriptor.
import 'package:fluentui_system_icons/fluentui_system_icons.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:vyuh_entity_annotations/vyuh_entity_annotations.dart';
import 'package:vyuh_entity_system_ui/vyuh_entity_system_ui.dart';
part 'course.g.dart';
part 'course.entity.dart';
@Entity(
schema: 'lms',
identifier: 'courses',
name: 'Course',
pluralName: 'Courses',
description: 'Training courses for participants',
icon: FluentIcons.book_24_regular,
visibility: EntityVisibility.all,
navigation: EntityNavigation(
prefix: '/lms/courses',
authorize: AuthorizeEntity(
list: AuthorizeRule(Authorize.permission('lms.courses.view')),
create: AuthorizeRule(Authorize.permission('lms.courses.create')),
view: AuthorizeRule(Authorize.permission('lms.courses.view')),
edit: AuthorizeRule(Authorize.permission('lms.courses.update')),
),
),
layouts: EntityUILayouts(
table: TableLayout(
columns: [
TableColumn('name', widthFactor: 0.35, minWidth: 200),
TableColumn('level', widthFactor: 0.2),
TableColumn('status', widthFactor: 0.2),
TableColumn('durationMinutes', widthFactor: 0.15),
],
),
),
)
@JsonSerializable(fieldRename: FieldRename.snake, includeIfNull: false)
class Course extends $CourseBase {
@Field(label: 'Description', type: TextFieldType(lines: 3))
final String? description;
@Field(label: 'Level', filterable: true, sortable: true)
final String level; // beginner | intermediate | advanced
@Field(label: 'Status', filterable: true, sortable: true)
final String status; // draft | published | archived
@Field(label: 'Duration (min)', sortable: true)
final int durationMinutes;
Course({
required super.id,
required super.name,
this.description,
this.level = 'beginner',
this.status = 'draft',
this.durationMinutes = 0,
super.versionNumber,
super.isActive,
super.createdAt,
super.updatedAt,
super.createdBy,
super.updatedBy,
});
factory Course.fromJson(Map<String, dynamic> json) => _$CourseFromJson(json);
@override
Map<String, dynamic> toJson() => _$CourseToJson(this);
}
// Register the generated `courseConfig` in an EntityExtensionDescriptor.See the Quick Start for the full step-by-step.