zuraffa 3.13.0 copy "zuraffa: ^3.13.0" to clipboard
zuraffa: ^3.13.0 copied to clipboard

AI first Flutter Clean Architecture Framework and CLI with Result types, UseCase patterns, Dependency Injection and MCP server for building type-safe, scalable apps with AI agents.

πŸ¦’ Zuraffa #

Pub Version License: MIT Documentation

A comprehensive Clean Architecture framework for Flutter applications with Result-based error handling, type-safe failures, and minimal boilerplate.

πŸ“š Documentation #

What is Zuraffa? #

πŸ¦’ Zuraffa (ZΓΌrafa means Giraffe in TΓΌrkΓ§e) is a modern Flutter package that implements Clean Architecture principles with a focus on developer experience and type safety. It provides a robust set of tools for building scalable, testable, and maintainable Flutter applications.

Key Features #

  • βœ… Clean Architecture Enforced: Entity-based, Single (Responsibility) Repository, Orchestrator, and Polymorphic patterns
  • βœ… UseCase Pattern: Single-shot, streaming, and background operations
  • βœ… State Management Included: Simple state management with automatic cleanup
  • βœ… ZFA CLI Tool: Generate boilerplate code with zfa command
  • βœ… MCP Server: AI/IDE integration via Model Context Protocol
  • βœ… GraphQL Generation: Auto-generate queries, mutations, and subscriptions
  • βœ… Cancellation: Cooperative cancellation with CancelToken
  • βœ… Fine-grained Rebuilds: Optimize performance with selective widget updates
  • βœ… Caching: Built-in dual datasource pattern with flexible cache policies
  • βœ… Result Type: Type-safe error handling with Result<T, AppFailure>
  • βœ… Sealed Failures: Exhaustive pattern matching for error cases

Installation #

Add this to your package's pubspec.yaml file:

dependencies:
  zuraffa: ^2.7.0

Then run:

flutter pub get

Quick Start #

1. Configure Your Project #

First, set up ZFA configuration for your project:

# Activate the CLI
dart pub global activate zuraffa

# Create configuration with defaults
zfa config init

# Optionally customize defaults
zfa config set jsonByDefault false

Configuration Options:

  • useZorphyByDefault - Use Zorphy for entities (default: true)
  • jsonByDefault - Default JSON serialization (default: true)
  • compareByDefault - Default compareTo generation (default: true)
  • defaultEntityOutput - Entity output (default: lib/src/domain/entities)

Note: Entity generation requires zorphy_annotation in your project. ZFA will prompt you to add it when creating your first entity, or you can add it now:

dart pub add zorphy_annotation

2. Create Your Entities #

Create entities first, then generate Clean Architecture around them:

# Create an enum
zfa entity enum -n OrderStatus --value pending,processing,shipped,delivered

# Create entities with fields
zfa entity create -n User --field name:String --field email:String?
zfa entity create -n Order --field customer:\$User --field status:OrderStatus --field items:List<\$OrderItem>
zfa entity create -n OrderItem --field product:\$Product --field quantity:int --field price:double

# List all entities
zfa entity list

3. Generate Clean Architecture #

Now generate complete features around your entities:

# Generate a complete feature for your entity
zfa generate Order --methods=get,getList,create,update,delete --data --vpc --state --test

# Or generate multiple entities at once
zfa generate User --methods=get,create --data
zfa generate Product --methods=get,getList,watch,watchList --data --vpc --state

That's it! One command generates:

  • βœ… Domain layer (UseCases + Repository interface)
  • βœ… Data layer (DataRepository + DataSource)
  • βœ… Presentation layer (View, Presenter, Controller, State)
  • βœ… Unit tests for all UseCases

4. Run Code Generation #

After creating entities, run the build:

# Run Zorphy + json_serializable code generation
zfa build

# Or watch for changes
zfa build --watch

This generates:

  • Entity implementations with copyWith, ==, hashCode, toString
  • JSON serialization (toJson/fromJson)
  • Typed patch classes for updates (when using --zorphy)

5. Use the Generated Code #

class ProductView extends CleanView {
  final ProductRepository productRepository;

  const ProductView({super.key, required this.productRepository});

  @override
  State<ProductView> createState() => _ProductViewState(
    ProductController(
      ProductPresenter(productRepository: productRepository),
    ),
  );
}

class _ProductViewState extends CleanViewState<ProductView, ProductController> {
  _ProductViewState(super.controller);

  @override
  void onInitState() {
    super.onInitState();
    controller.getProductList();
  }

  @override
  Widget get view {
    return Scaffold(
      key: globalKey,
      appBar: AppBar(title: const Text('Products')),
      body: ControlledWidgetBuilder<ProductController>(
        builder: (context, controller) {
          if (controller.viewState.isLoading) {
            return const CircularProgressIndicator();
          }
          return ListView.builder(
            itemCount: controller.viewState.productList.length,
            itemBuilder: (context, index) {
              final product = controller.viewState.productList[index];
              return ListTile(title: Text(product.name));
            },
          );
        },
      ),
    );
  }
}

Generated Output Example #

βœ… Created 2 entities

  ⟳ lib/src/domain/entities/order/order.dart
  ⟳ lib/src/domain/entities/order/order.zorphy.dart
  ⟳ lib/src/domain/entities/order/order.g.dart
  ⟳ lib/src/domain/entities/order_item/order_item.dart
  ⟳ lib/src/domain/entities/order_item/order_item.zorphy.dart
  ⟳ lib/src/domain/entities/order_item/order_item.g.dart

βœ… Generated 21 files for Order

  ⟳ lib/src/domain/repositories/order_repository.dart
  ⟳ lib/src/domain/usecases/order/get_order_usecase.dart
  ⟳ lib/src/domain/usecases/order/watch_order_usecase.dart
  ⟳ lib/src/domain/usecases/order/create_order_usecase.dart
  ⟳ lib/src/domain/usecases/order/update_order_usecase.dart
  ⟳ lib/src/domain/usecases/order/delete_order_usecase.dart
  ⟳ lib/src/domain/usecases/order/get_order_list_usecase.dart
  ⟳ lib/src/domain/usecases/order/watch_order_list_usecase.dart
  ⟳ lib/src/presentation/pages/order/order_presenter.dart
  ⟳ lib/src/presentation/pages/order/order_controller.dart
  ⟳ lib/src/presentation/pages/order/order_view.dart
  ⟳ lib/src/presentation/pages/order/order_state.dart
  ⟳ lib/src/data/data_sources/order/order_data_source.dart
  ⟳ lib/src/data/repositories/data_order_repository.dart
  βœ“ test/domain/usecases/order/get_order_usecase_test.dart
  βœ“ test/domain/usecases/order/watch_order_usecase_test.dart
  βœ“ test/domain/usecases/order/create_order_usecase_test.dart
  βœ“ test/domain/usecases/order/update_order_usecase_test.dart
  βœ“ test/domain/usecases/order/delete_order_usecase_test.dart
  βœ“ test/domain/usecases/order/get_order_list_usecase_test.dart
  βœ“ test/domain/usecases/order/watch_order_list_usecase_test.dart

πŸ“ Next steps:
   β€’ Create a DataSource that implements ProductDataSource in data layer
   β€’ Register repositories with DI container
   β€’ Run tests: flutter test 

Core Concepts #

Result Type #

All operations return Result<T, AppFailure> for type-safe error handling:

final result = await getProductUseCase('product-123');

// Pattern matching with fold
result.fold(
  (product) => showProduct(product),
  (failure) => showError(failure),
);

// Or use switch for exhaustive handling
switch (failure) {
  case NotFoundFailure():
    showNotFound();
  case NetworkFailure():
    showOfflineMessage();
  case UnauthorizedFailure():
    navigateToLogin();
  default:
    showGenericError();
}

AppFailure Hierarchy #

Zuraffa provides a sealed class hierarchy for comprehensive error handling:

sealed class AppFailure implements Exception {
  final String message;
  final StackTrace? stackTrace;
  final Object? cause;
}

// Specific failure types
final class ServerFailure extends AppFailure { ... }
final class NetworkFailure extends AppFailure { ... }
final class ValidationFailure extends AppFailure { ... }
final class NotFoundFailure extends AppFailure { ... }
final class UnauthorizedFailure extends AppFailure { ... }
final class ForbiddenFailure extends AppFailure { ... }
final class TimeoutFailure extends AppFailure { ... }
final class CacheFailure extends AppFailure { ... }
final class ConflictFailure extends AppFailure { ... }
final class CancellationFailure extends AppFailure { ... }
final class UnknownFailure extends AppFailure { ... }

Data Updates #

Zuraffa supports two strategies for updating entities:

1. Flexible Partial Updates (Default)

Uses Partial<T> (a Map<String, dynamic>) to send only changed fields. The generator automatically adds validation to ensure only valid fields are updated.

// Generated UpdateUseCase
// params.validate(['id', 'name', 'price']); <-- Auto-generated from Entity
await updateProduct(id: '123', data: {'name': 'New Product Name'});

2. Typed Updates with Zorphy (--zorphy)

If you use Zorphy or similar tools, you can use typed Patch objects for full type safety.

zfa generate Product --methods=update --zorphy
// Generated with --zorphy
await updateProduct(id: '123', data: ProductPatch(name: 'New Product Name'));

UseCase Types #

Single-shot UseCase

For operations that return once:

class GetProductUseCase extends UseCase<Product, String> {
  final ProductRepository _repository;

  GetProductUseCase(this._repository);

  @override
  Future<Product> execute(String productId, CancelToken? cancelToken) async {
    return _repository.getProduct(productId);
  }
}

StreamUseCase

For reactive operations that emit multiple values:

class WatchProductsUseCase extends StreamUseCase<List<Product>, NoParams> {
  final ProductRepository _repository;

  WatchProductsUseCase(this._repository);

  @override
  Stream<List<Product>> execute(NoParams params, CancelToken? cancelToken) {
    return _repository.watchProducts();
  }
}

BackgroundUseCase

For CPU-intensive operations on isolates:

class ProcessImageUseCase extends BackgroundUseCase<ProcessedImage, ImageParams> {
  @override
  BackgroundTask<ImageParams> buildTask() => _processImage;

  static void _processImage(BackgroundTaskContext<ImageParams> context) {
    final result = applyFilters(context.params.image);
    context.sendData(result);
    context.sendDone();
  }
}

CompletableUseCase

For operations that don't return a value (like delete, logout, or clear cache):

class DeleteProductUseCase extends CompletableUseCase<String> {
  final ProductRepository _repository;

  DeleteProductUseCase(this._repository);

  @override
  Future<void> execute(String productId, CancelToken? cancelToken) async {
    cancelToken?.throwIfCancelled();
    await _repository.delete(productId);
  }
}

// Usage - returns Result<void, AppFailure>
final result = await deleteProductUseCase('product-123');
result.fold(
  (_) => showSuccess('Product deleted'),
  (failure) => showError(failure),
);

CompletableUseCase is useful when you only care about whether an operation succeeded or failed, without needing any returned data. Common use cases include:

  • Delete operations
  • Logout/sign out
  • Clear cache
  • Send analytics events
  • Fire-and-forget notifications

SyncUseCase

For synchronous operations that don't require async processing (validation, calculations, transformations):

class ValidateEmailUseCase extends SyncUseCase<bool, String> {
  @override
  bool execute(String email) {
    return RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(email);
  }
}

// Usage - returns Result<bool, AppFailure>
final validateEmail = ValidateEmailUseCase();
final result = validateEmail('user@example.com');
result.fold(
  (isValid) => print('Email is valid: $isValid'),
  (failure) => print('Validation failed: $failure'),
);

SyncUseCase is useful for operations that complete immediately without async I/O:

  • Validation logic - Email, phone number, format checks
  • Data transformations - Mapping, filtering, aggregations
  • Calculations - Totals, averages, conversions
  • Business rules - Eligibility, permissions, checks

Controller with State #

Controllers use StatefulController<T> with immutable state objects:

class ProductController extends Controller with StatefulController<ProductState> {
  final ProductPresenter _presenter;

  ProductController(this._presenter) : super();

  @override
  ProductState createInitialState() => const ProductState();

  Future<void> getProductList() async {
    updateState(viewState.copyWith(isGettingList: true));
    final result = await _presenter.getProductList();

    result.fold(
      (list) => updateState(viewState.copyWith(
        isGettingList: false,
        productList: list,
      )),
      (failure) => updateState(viewState.copyWith(
        isGettingList: false,
        error: failure,
      )),
    );
  }

  Future<void> createProduct(Product product) async {
    updateState(viewState.copyWith(isCreating: true));
    final result = await _presenter.createProduct(product);

    result.fold(
      (created) => updateState(viewState.copyWith(
        isCreating: false,
        productList: [...viewState.productList, created],
      )),
      (failure) => updateState(viewState.copyWith(
        isCreating: false,
        error: failure,
      )),
    );
  }
}

State #

Immutable state classes are auto-generated with the --state flag:

class ProductState {
  final AppFailure? error;
  final List<Product> productList;
  final Product? product;
  final bool isGetting;
  final bool isCreating;
  final bool isUpdating;
  final bool isDeleting;
  final bool isGettingList;

  const ProductState({
    this.error,
    this.productList = const [],
    this.product,
    this.isGetting = false,
    this.isCreating = false,
    this.isUpdating = false,
    this.isDeleting = false,
    this.isGettingList = false,
  });

  ProductState copyWith({...}) => ...;

  bool get isLoading => isGetting || isCreating || isUpdating || isDeleting || isGettingList;
  bool get hasError => error != null;
}

CleanView #

Base class for views with automatic lifecycle management. Views are pure UI and delegate all business logic to the Controller:

class ProductView extends CleanView {
  final ProductRepository productRepository;

  const ProductView({super.key, required this.productRepository});

  @override
  State<ProductView> createState() => _ProductViewState(
    ProductController(
      ProductPresenter(productRepository: productRepository),
    ),
  );
}

class _ProductViewState extends CleanViewState<ProductView, ProductController> {
  _ProductViewState(super.controller);

  @override
  void onInitState() {
    super.onInitState();
    controller.getProductList();
  }

  @override
  Widget get view {
    return Scaffold(
      key: globalKey, // Important: use globalKey on root widget
      appBar: AppBar(title: const Text('Products')),
      body: ControlledWidgetBuilder<ProductController>(
        builder: (context, controller) {
          if (controller.viewState.isLoading) {
            return const CircularProgressIndicator();
          }
          return ListView.builder(
            itemCount: controller.viewState.productList.length,
            itemBuilder: (context, index) {
              final product = controller.viewState.productList[index];
              return ListTile(title: Text(product.name));
            },
          );
        },
      ),
    );
  }
}

Dependency Injection Generation #

Zuraffa can automatically generate dependency injection setup using get_it:

# Generate DI files alongside your code
zfa generate Product --methods=get,getList,create --data --vpc --di

# Use mock datasource in DI (for development/testing)
zfa generate Product --methods=get,getList --data --mock --di --use-mock

# With caching enabled
zfa generate Product --methods=get,getList --data --cache --di

Generated DI Structure #

lib/src/di/
β”œβ”€β”€ index.dart                    # Main entry with setupDependencies()
β”œβ”€β”€ datasources/
β”‚   β”œβ”€β”€ index.dart               # Auto-generated
β”‚   └── product_remote_data_source_di.dart
β”œβ”€β”€ repositories/
β”‚   β”œβ”€β”€ index.dart               # Auto-generated
β”‚   └── product_repository_di.dart
β”œβ”€β”€ usecases/
β”‚   β”œβ”€β”€ index.dart               # Auto-generated
β”‚   β”œβ”€β”€ get_product_usecase_di.dart
β”‚   └── get_product_list_usecase_di.dart
β”œβ”€β”€ presenters/
β”‚   β”œβ”€β”€ index.dart               # Auto-generated
β”‚   └── product_presenter_di.dart
└── controllers/
    β”œβ”€β”€ index.dart               # Auto-generated
β”‚   └── product_controller_di.dart

Usage #

import 'package:get_it/get_it.dart';
import 'src/di/index.dart';

void main() {
  final getIt = GetIt.instance;
  setupDependencies(getIt);
  
  runApp(MyApp());
}

// Access registered dependencies
final productRepository = getIt<ProductRepository>();
final productController = getIt<ProductController>();

Features #

  • βœ… One file per component: No merge conflicts
  • βœ… Auto-generated indexes: Directory scanning regenerates imports
  • βœ… Cache support: Registers remote + local datasources when --cache used
  • βœ… Mock support: Use --use-mock to register mock datasources
  • βœ… Fail-safe: Regenerate anytime without manual merging

Cache Initialization (Hive) #

When using --cache with --di, Zuraffa automatically generates cache initialization files:

# Generate with cache and DI
zfa generate Product --methods=get,getList --data --cache --cache-policy=ttl --ttl=30 --di

Generated Cache Structure #

lib/src/cache/
β”œβ”€β”€ hive_registrar.dart              # @GenerateAdapters for all entities
β”œβ”€β”€ hive_manual_additions.txt        # Template for nested entities/enums
β”œβ”€β”€ product_cache.dart               # Opens Product box
β”œβ”€β”€ timestamp_cache.dart             # Opens timestamps box
β”œβ”€β”€ ttl_30_minutes_cache_policy.dart # Cache policy implementation
└── index.dart                       # initAllCaches() + exports

Adding Nested Entities and Enums #

The generator creates hive_manual_additions.txt for entities that aren't directly cached but need adapters (nested entities, enums, etc.):

# Hive Manual Additions
# Format: import_path|EntityName

../domain/entities/enums/index.dart|ParserType
../domain/entities/enums/index.dart|HttpClientType
../domain/entities/range/range.dart|Range
../domain/entities/filter_parameter/filter_parameter.dart|FilterParameter

After adding entries, regenerate:

zfa generate Product --methods=get --data --cache --di --force

The registrar will include all manual additions:

@GenerateAdapters([
  AdapterSpec<ParserType>(),
  AdapterSpec<HttpClientType>(),
  AdapterSpec<Range>(),
  AdapterSpec<FilterParameter>(),
  AdapterSpec<Product>()
])

Generated Files #

hive_registrar.dart - Automatic adapter registration:

@GenerateAdapters([AdapterSpec<Product>(), AdapterSpec<User>()])
part 'hive_registrar.g.dart';

extension HiveRegistrar on HiveInterface {
  void registerAdapters() {
    registerAdapter(ProductAdapter());
    registerAdapter(UserAdapter());
  }
}

Cache policy - Fully implemented with Hive:

CachePolicy createTtl30MinutesCachePolicy() {
  final timestampBox = Hive.box<int>('cache_timestamps');
  return TtlCachePolicy(
    ttl: const Duration(minutes: 30),
    getTimestamps: () async => Map<String, int>.from(timestampBox.toMap()),
    setTimestamp: (key, timestamp) async => await timestampBox.put(key, timestamp),
    removeTimestamp: (key) async => await timestampBox.delete(key),
    clearAll: () async => await timestampBox.clear(),
  );
}

Usage #

import 'package:hive_ce_flutter/hive_ce_flutter.dart';
import 'src/cache/index.dart';
import 'src/di/index.dart';

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  
  await Hive.initFlutter();
  await initAllCaches();  // Registers adapters + opens boxes
  
  setupDependencies(GetIt.instance);
  
  runApp(MyApp());
}

Workflow #

  1. Generate code with --cache and --di
  2. Run dart run build_runner build (generates hive_registrar.g.dart)
  3. Call initAllCaches() before DI setup

Features #

  • βœ… Automatic adapter registration: No manual Hive.registerAdapter() calls
  • βœ… Separate policy files: daily_cache_policy.dart, ttl_<N>_minutes_cache_policy.dart
  • βœ… Custom TTL: Use --ttl=<minutes> for custom durations
  • βœ… Type-safe: Abstract DataSource type allows easy mock/remote switching

Mock Data Generation #

Zuraffa can generate realistic mock data for your entities, perfect for testing, UI previews, and development:

# Generate mock data alongside other layers
zfa generate Product --methods=get,getList,create --vpc --mock

# Generate only mock data files
zfa generate Product --mock-data-only

Generated Mock Data #

Mock data files provide realistic test data with proper type safety:

// Generated: lib/src/data/mock/product_mock_data.dart
class ProductMockData {
  static final List<Product> products = [
    Product(
      id: 'id 1',
      name: 'name 1', 
      description: 'description 1',
      price: 10.5,
      category: 'category 1',
      isActive: true,
      createdAt: DateTime.now().subtract(Duration(days: 30)),
      updatedAt: DateTime.now().subtract(Duration(days: 30)),
    ),
    Product(
      id: 'id 2',
      name: 'name 2',
      description: 'description 2', 
      price: 21.0,
      category: 'category 2',
      isActive: false,
      createdAt: DateTime.now().subtract(Duration(days: 60)),
      updatedAt: DateTime.now().subtract(Duration(days: 60)),
    ),
    // ... more items
  ];

  static Product get sampleProduct => products.first;
  static List<Product> get sampleList => products;
  static List<Product> get emptyList => [];
  
  // Large dataset for performance testing
  static List<Product> get largeProductList => List.generate(100, 
    (index) => _createProduct(index + 1000));
}

Features #

  • βœ… Realistic data: Type-appropriate values for all field types
  • βœ… Nested entities: Automatic detection and cross-references
  • βœ… Complex types: Support for List<T>, Map<K,V>, nullable types
  • βœ… Enum handling: Smart imports only when needed
  • βœ… Large datasets: Generated methods for performance testing
  • βœ… Null safety: Proper handling of optional fields

Usage in Tests #

// Use in unit tests
test('should process product list', () {
  final products = ProductMockData.sampleList;
  final result = processProducts(products);
  expect(result.length, equals(3));
});

// Use in widget tests  
testWidgets('should display product', (tester) async {
  await tester.pumpWidget(ProductView(
    product: ProductMockData.sampleProduct,
  ));
  expect(find.text('name 1'), findsOneWidget);
});

CLI Tool #

Zuraffa includes a powerful CLI tool (zfa) for generating boilerplate code.

Installation #

# Global activation
dart pub global activate zuraffa

# Or run directly
dart run zuraffa:zfa

Entity Commands (NEW!) #

Zuraffa now includes full Zorphy entity generation - create type-safe entities, enums, and manage data models:

# Create an entity with fields
zfa entity create -n User --field name:String --field email:String? --field age:int

# Create an enum
zfa entity enum -n Status --value active,inactive,pending

# Quick-create a simple entity
zfa entity new -n Product

# Add fields to existing entity
zfa entity add-field -n User --field phone:String?

# Create entity from JSON file
zfa entity from-json user_data.json

# List all entities
zfa entity list

# Build generated code
zfa build
zfa build --watch  # Watch for changes
zfa build --clean  # Clean and rebuild

Full Entity Generation Features:

  • βœ… Type-safe entities with null safety
  • βœ… JSON serialization (built-in)
  • βœ… Sealed classes for polymorphism
  • βœ… Multiple inheritance support
  • βœ… Generic types (List<T>, Map<K,V>)
  • βœ… Nested entities with auto-imports
  • βœ… Enum integration
  • βœ… Self-referencing types (trees)
  • βœ… compareTo, copyWith, patch` methods

πŸ“– For complete entity generation documentation, see ENTITY_GUIDE.md

Generate Clean Architecture #

One command generates your entire feature:

# Generate everything at once - Domain, Data, and Presentation layers
zfa generate Product --methods=get,getList,create,update,delete --data --vpc --state

# Generate with mock data for testing and UI previews
zfa generate Product --methods=get,getList,create,update,delete --data --vpc --state --mock

# Generate only mock data files
zfa generate Product --mock-data-only

# Or generate incrementally:

# Generate UseCases + Repository interface
zfa generate Product --methods=get,getList,create,update,delete

# Add presentation layer (View, Presenter, Controller, State)
zfa generate Product --methods=get,getList,create,update,delete --vpc --state

# Add data layer (DataRepository + DataSource)
zfa generate Product --methods=get,getList,create,update,delete --data

# Use typed patches for updates (Zorphy support)
zfa generate Product --methods=update --zorphy

# Enable caching with dual datasources
zfa generate Config --methods=get,getList --data --cache --cache-policy=daily

# Preview what would be generated without writing files
zfa generate Product --methods=get,getList --dry-run

# Generate with unit tests for each UseCase
zfa generate Product --methods=get,create,update,delete --test

# Custom UseCase with repository
zfa generate SearchProduct --domain=search --repo=Product --params=Query --returns=List<Product>

# Custom UseCase with service (alternative to repository)
zfa generate SendEmail --domain=notifications --service=Email --params=EmailMessage --returns=void

# Orchestrator pattern (compose UseCases)
zfa generate ProcessCheckout --domain=checkout --usecases=ValidateCart,ProcessPayment --params=CheckoutRequest --returns=OrderResult

# Background UseCase for CPU-intensive operations (runs on isolate)
zfa generate CalculatePrimeNumbers --type=background --params=int --returns=int

Services vs Repositories

Zuraffa supports two patterns for dependency injection in custom UseCases:

Repositories (--repo) - Use for data access and entity operations

zfa generate GetProduct --domain=product --repo=Product --params=String --returns=Product

Services (--service) - Use for business logic, external APIs, or non-entity operations

# Generate a service interface
zfa generate SendEmail --domain=notifications --service=Email --params=EmailMessage --returns=void

# Generated service interface
abstract class EmailService {
  Future<void> sendEmail(EmailMessage params);
}

When to use Services:

  • External API integrations (payment gateways, email services, SMS)
  • Business logic that doesn't involve entities
  • Utility operations (file processing, calculations, transformations)
  • Third-party SDK integrations (analytics, crash reporting, authentication)

When to use Repositories:

  • CRUD operations on entities
  • Data access (local or remote)
  • Caching and offline support
  • Data synchronization

GraphQL Generation

Generate GraphQL query/mutation/subscription files for your entities and UseCases with the --gql flag.

Entity-Based GraphQL Generation:

# Basic GraphQL with auto-generated queries
zfa generate Product --methods=get,getList --gql

# With custom return fields
zfa generate Product --methods=get,getList,create --gql --gql-returns="id,name,price,category,isActive,createdAt"

# With custom operation types
zfa generate Product --methods=watch,watchList --gql --gql-type=subscription

# Complete GraphQL setup
zfa generate Product --methods=get,getList,create,update,delete --data --gql --gql-returns="id,name,description,price,category,isActive,createdAt,updatedAt"

Generated files are placed in:

lib/src/data/data_sources/{entity}/graphql/
β”œβ”€β”€ get_product_query.dart
β”œβ”€β”€ get_product_list_query.dart
β”œβ”€β”€ create_product_mutation.dart
└── update_product_mutation.dart

Custom UseCase with GraphQL:

# Custom UseCase with GraphQL query
zfa generate SearchProducts \
  --service=Search \
  --domain=products \
  --params=SearchQuery \
  --returns=List<Product> \
  --gql \
  --gql-type=query \
  --gql-name=searchProducts \
  --gql-input-type=SearchInput \
  --gql-returns="id,name,price,category"

# Custom UseCase with GraphQL mutation
zfa generate UploadFile \
  --service=Storage \
  --domain=storage \
  --params=FileData \
  --returns=String \
  --gql \
  --gql-type=mutation \
  --gql-name=uploadFile \
  --gql-input-type=FileInput

# Custom UseCase with GraphQL subscription
zfa generate WatchUserLocation \
  --service=Location \
  --domain=realtime \
  --params=UserId \
  --returns=Location \
  --gql \
  --gql-type=subscription

GraphQL Options:

Flag Description
--gql Enable GraphQL generation
--gql-type Operation type: query, mutation, subscription (auto-detected for entity methods)
--gql-returns Return fields as comma-separated string
--gql-input-type Input type name for mutation/subscription
--gql-input-name Input variable name (default: input)
--gql-name Custom operation name (default: auto-generated)

Auto-Detection for Entity Methods:

  • get, getList β†’ query
  • create, update, delete β†’ mutation
  • watch, watchList β†’ subscription

GraphQL Generation #

Zuraffa provides powerful GraphQL generation capabilities, creating type-safe GraphQL queries, mutations, and subscriptions directly from your entity and UseCase definitions.

Generated GraphQL Code #

When you use the --gql flag, Zuraffa generates GraphQL operation files with string constants that you can use directly with any GraphQL client (like graphql_flutter or ferry).

Example: Entity-Based Generation

zfa generate Product --methods=get,getList,create --gql --gql-returns="id,name,price,category,isActive,createdAt"

Generated File: lib/src/data/data_sources/product/graphql/get_product_query.dart

// Generated GraphQL query for GetProduct
const String getProductsQuery = r'''
  query GetProduct($id: String!) {
    getProduct(id: $id) {
      id
      name
      price
      category
      isActive
      createdAt
    }
  }''';

Generated File: lib/src/data/data_sources/product/graphql/create_product_mutation.dart

// Generated GraphQL mutation for CreateProduct
const String createProductMutation = r'''
  mutation CreateProduct($input: CreateProductInput!) {
    createProduct(input: $input) {
      id
      name
      price
      category
      isActive
      createdAt
    }
  }''';

Example: Custom UseCase with GraphQL

zfa generate SearchProducts \
  --service=Search \
  --domain=products \
  --params=SearchQuery \
  --returns=List<Product> \
  --gql \
  --gql-type=query \
  --gql-name=searchProducts \
  --gql-input-type=SearchInput \
  --gql-returns="id,name,price,category,rating"

Generated File: lib/src/data/data_sources/products/graphql/search_products_query.dart

// Generated GraphQL query for SearchProducts
const String searchProductsQuery = r'''
  query SearchProducts($input: SearchInput!) {
    searchProducts(input: $input) {
      id
      name
      price
      category
      rating
    }
  }''';

Using Generated GraphQL #

Here's how to use the generated GraphQL strings with a GraphQL client:

import 'package:graphql_flutter/graphql_flutter.dart';
import 'package:zuraffa_example/data/data_sources/product/graphql/get_product_query.dart';
import 'package:zuraffa_example/data/data_sources/product/graphql/create_product_mutation.dart';

class ProductGraphQLDataSource {
  final GraphQLClient _client;

  ProductGraphQLDataSource(this._client);

  Future<Product> getProduct(String id) async {
    final result = await _client.query(
      QueryOptions(
        document: gql(getProductQuery),
        variables: {'id': id},
      ),
    );

    if (result.hasException) {
      throw Exception('Failed to fetch product: ${result.exception}');
    }

    return Product.fromJson(result.data!['getProduct']);
  }

  Future<Product> createProduct(CreateProductInput input) async {
    final result = await _client.mutate(
      MutationOptions(
        document: gql(createProductMutation),
        variables: {'input': input.toJson()},
      ),
    );

    if (result.hasException) {
      throw Exception('Failed to create product: ${result.exception}');
    }

    return Product.fromJson(result.data!['createProduct']);
  }
}

Subscriptions #

Generate GraphQL subscriptions for real-time updates:

zfa generate Product --methods=watchList --gql --gql-type=subscription

Generated Subscription:

// Generated GraphQL subscription for WatchProductList
const String watchProductListSubscription = r'''
  subscription WatchProductList {
    watchProductList {
      id
      name
      price
      category
      isActive
      updatedAt
    }
  }''';

Usage:

ObservableQuery<List<Product>> watchProductList() {
  return _client.subscribe(
    SubscriptionOptions(
      document: gql(watchProductListSubscription),
    ),
  );
}

Custom Fields and Types #

You have full control over the generated GraphQL operations:

# Specify nested fields
zfa generate Order --methods=get --gql --gql-returns="id,createdAt,customer{id,name,email},items{id,quantity,price,product{id,name}}"

# Custom input types
zfa generate CreateOrder \
  --service=Order \
  --domain=orders \
  --params=OrderInput \
  --returns=Order \
  --gql --gql-type=mutation \
  --gql-input-type=OrderCreateInput

Coming Soon #

🚧 Schema-First Generation (Coming Soon)

We're working on even more powerful GraphQL features:

  • Auto-generate entities from GraphQL schema: Import your GraphQL schema and Zuraffa will automatically create all entity definitions
  • Auto-generate UseCases from queries/mutations: Parse your .graphql files and generate corresponding UseCases with proper types
  • Type safety from schema to code: Ensure complete type alignment between your GraphQL server and Flutter app
  • Introspection support: Fetch live schema from GraphQL server to generate up-to-date types
  • Federation support: Generate code for federated GraphQL schemas

These features will make building GraphQL-powered Flutter apps even more seamless, eliminating manual type mapping and reducing boilerplate to near-zero.

Quick Reference #

Flag Description Example
--gql Enable GraphQL generation --gql
--gql-type Operation type: query, mutation, subscription --gql-type=query
--gql-returns Return fields (comma-separated) --gql-returns="id,name,price"
--gql-input-type Input type name --gql-input-type=ProductInput
--gql-input-name Input variable name --gql-input-name=input
--gql-name Custom operation name --gql-name=getProductById

Custom UseCase Types

The --type flag supports three variants for custom UseCases:

Type Description Use When
custom (default) Standard UseCase with repository/service dependencies CRUD operations, business logic
background Runs on a separate isolate CPU-intensive work (calculations, image processing)
stream Emits multiple values over time Real-time data, WebSocket, Firebase listeners

Defining Parameter and Return Types

Use --params and --returns to specify custom types for your UseCase:

# Define custom parameter and return types
zfa generate CalculatePrimeNumbers --type=background --params=int --returns=int

# Orchestrator with multiple UseCases
zfa generate ProcessCheckout --domain=checkout --usecases=ValidateCart,ProcessPayment --params=CheckoutRequest --returns=OrderConfirmation
Flag Description Example
--params Input parameter type for the UseCase --params=int, --params=ProductFilter
--returns Return type from the UseCase --returns=bool, --returns=List<Product>

Available Methods #

Method UseCase Type Description
get UseCase Get single entity by ID
getList UseCase Get all entities
create UseCase Create new entity
update UseCase Update existing entity
delete CompletableUseCase Delete entity by ID
watch StreamUseCase Watch single entity
watchList StreamUseCase Watch all entities

CLI Flags #

Flag Description
--data Generate DataRepository and DataSource (always includes remote datasource)
--vpc Generate View, Presenter, and Controller
--vpcs Generate View, Presenter, Controller, and State
--pc Generate Presenter and Controller only (preserve View)
--pcs Generate Presenter, Controller, and State (preserve View)
--repo Repository to inject (for custom UseCases)
--service Service interface to inject (alternative to --repo)
--domain Domain folder (required for custom UseCases)
--append Append to existing repository/datasources
--usecases Orchestrator: compose UseCases (comma-separated)
--variants Polymorphic: generate variants (comma-separated)
--state Generate immutable State class
--mock Generate mock data files alongside other layers
--mock-data-only Generate only mock data files (no other layers)
--use-mock Use mock datasource in DI (default: remote datasource)
--di Generate dependency injection files (get_it)
--zorphy Use typed Patch objects for updates
--cache Enable caching with dual datasources (remote + local)
--cache-policy Cache expiration: daily, restart, ttl (default: daily)
--cache-storage Local storage hint: hive, sqlite, shared_preferences (default: hive)
--ttl TTL duration in minutes (default: 1440 = 24 hours)
--subfolder Organize under a subfolder (e.g., --subfolder=auth)
--init Add initialize method & isInitialized stream to repos
--force Overwrite existing files
--dry-run Preview what would be generated without writing files
--test Generate unit tests for each UseCase
--format=json Output JSON for AI/IDE integration
--gql Generate GraphQL query/mutation/subscription files
--gql-type GraphQL operation type: query, mutation, subscription
--gql-returns GraphQL return fields (comma-separated)
--gql-input-type GraphQL input type name
--gql-input-name GraphQL input variable name (default: input)
--gql-name Custom GraphQL operation name

AI/JSON Integration #

# JSON output for parsing
zfa generate Product --methods=get,getList --format=json

# Read from stdin
echo '{"name":"Product","methods":["get","getList"]}' | zfa generate Product --from-stdin

# Get JSON schema for validation
zfa schema

# Dry run (preview without writing)
zfa generate Product --methods=get --dry-run --format=json

For complete CLI documentation, see CLI_GUIDE.md.

MCP Server #

Zuraffa includes an MCP (Model Context Protocol) server for seamless integration with AI-powered development environments like Claude Desktop, Cursor, and VS Code.

Installation #

Option 1: From pub.dev (Recommended)

dart pub global activate zuraffa
# MCP server is immediately available: zuraffa_mcp_server

Option 2: Pre-compiled Binary (Fastest)

Download from GitHub Releases:

  • macOS ARM64 / x64
  • Linux x64
  • Windows x64
# macOS/Linux
chmod +x zuraffa_mcp_server-macos-arm64
sudo mv zuraffa_mcp_server-macos-arm64 /usr/local/bin/zuraffa_mcp_server

Option 3: Compile from Source

dart compile exe bin/zuraffa_mcp_server.dart -o zuraffa_mcp_server

MCP Tools #

  • zuraffa_generate - Generate Clean Architecture code
  • zuraffa_schema - Get JSON schema for config validation
  • zuraffa_validate - Validate a generation config

For complete MCP documentation, see MCP_SERVER.md.

Project Structure #

Recommended folder structure for Clean Architecture (auto-generated by zfa):

lib/
β”œβ”€β”€ main.dart
└── src/
    β”œβ”€β”€ core/                    # Shared utilities
    β”‚   β”œβ”€β”€ error/               # Custom failures if needed
    β”‚   β”œβ”€β”€ network/             # HTTP client, interceptors
    β”‚   └── utils/               # Helpers, extensions
    β”‚
    β”œβ”€β”€ data/                    # Data layer
    β”‚   β”œβ”€β”€ data_sources/        # Remote and local data sources
    β”‚   β”‚   └── product/
    β”‚   β”‚       └── product_data_source.dart
    β”‚   └── repositories/        # Repository implementations
    β”‚       └── data_product_repository.dart
    β”‚
    β”œβ”€β”€ domain/                  # Domain layer (pure Dart)
    β”‚   β”œβ”€β”€ entities/            # Business objects
    β”‚   β”‚   └── product/
    β”‚   β”‚       └── product.dart
    β”‚   β”œβ”€β”€ repositories/        # Repository interfaces
    β”‚   β”‚   └── product_repository.dart
    β”‚   β”œβ”€β”€ services/            # Service interfaces (alternative to repositories)
    β”‚   β”‚   └── email_service.dart
    β”‚   └── usecases/            # Business logic
    β”‚       └── product/
    β”‚           β”œβ”€β”€ get_product_usecase.dart
    β”‚           β”œβ”€β”€ create_product_usecase.dart
    β”‚           └── ...
    β”‚
    └── presentation/            # Presentation layer
        └── pages/               # Full-screen views
            └── product/
                β”œβ”€β”€ product_view.dart
                β”œβ”€β”€ product_presenter.dart
                β”œβ”€β”€ product_controller.dart
                └── product_state.dart

All of this is generated with a single command:

zfa generate Product --methods=get,getList,create,update,delete --data --vpc --state

Advanced Features #

CancelToken #

Cooperative cancellation for long-running operations:

// Create a token
final cancelToken = CancelToken();

// Use with a use case
final result = await getProductUseCase(productId, cancelToken: cancelToken);

// Cancel when needed
cancelToken.cancel('Product page closed');

// Create with timeout
final timeoutToken = CancelToken.timeout(const Duration(seconds: 30));

// In Controllers, use createCancelToken() for automatic cleanup
class MyController extends Controller {
  Future<void> loadData() async {
    // Token automatically cancelled when controller disposes
    final result = await execute(myUseCase, params);
  }
}

ControlledWidgetSelector #

For fine-grained rebuilds when only specific values change:

// Only rebuilds when product.name changes
ControlledWidgetSelector<ProductController, String?>(
  selector: (controller) => controller.viewState.product?.name,
  builder: (context, productName) {
    return Text(productName ?? 'Unknown');
  },
)

Global Configuration #

void main() {
  // Enable debug logging
  Zuraffa.enableLogging();

  runApp(MyApp());
}

// Access controllers from child widgets
class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final controller = Zuraffa.getController<MyController>(context);
    return ElevatedButton(
      onPressed: () => controller.doSomething(),
      child: Text('Action'),
    );
  }
}

Example #

See the example directory for a complete working application demonstrating:

  • βœ… UseCase for CRUD operations
  • βœ… StreamUseCase for real-time updates
  • βœ… BackgroundUseCase for CPU-intensive calculations
  • βœ… Controller with immutable state
  • βœ… CleanView with ControlledWidgetBuilder
  • βœ… CancelToken for cancellation
  • βœ… Error handling with AppFailure

Run the example:

cd example
flutter pub get
flutter run

Documentation #

License #

MIT License - see LICENSE for details.

Authors #


Made with ⚑️ for the Flutter community

2
likes
50
points
1.89k
downloads

Publisher

verified publisherzuzu.dev

Weekly Downloads

AI first Flutter Clean Architecture Framework and CLI with Result types, UseCase patterns, Dependency Injection and MCP server for building type-safe, scalable apps with AI agents.

Homepage
Repository (GitHub)
View/report issues
Contributing

Topics

#clean-architecture #state-management #dependency-injection #architecture #code-generation

Documentation

Documentation

License

MIT (license)

Dependencies

analyzer, args, code_builder, dart_style, flutter, get_it, go_router, http, json_annotation, json_serializable, logging, meta, path, provider, responsive_builder, uuid, zorphy, zorphy_annotation

More

Packages that depend on zuraffa