Skip to main content

Development Workflow

Using the Hypermodern CLI

The Hypermodern CLI is your primary development tool, providing project scaffolding, code generation, development servers, and deployment utilities. Mastering the CLI significantly improves your development velocity.

CLI Command Reference

# Project Management
hypermodern create <project_name>              # Create new project
hypermodern create module <module_name>        # Create new module
hypermodern init                               # Initialize existing directory

# Code Generation
hypermodern generate                           # Generate all code from schemas
hypermodern generate models                    # Generate only models
hypermodern generate client                    # Generate only client code
hypermodern generate server                    # Generate only server code

# Development Server
hypermodern serve                              # Start development server
hypermodern serve --http-port 8080            # Custom HTTP port
hypermodern serve --ws-port 8082              # Custom WebSocket port
hypermodern serve --tcp-port 8081             # Custom TCP port
hypermodern serve --no-watch                  # Disable hot reload

# Module Management
hypermodern module install <path_or_name>      # Install module
hypermodern module uninstall <name>           # Uninstall module
hypermodern module list                        # List installed modules
hypermodern module validate <path>            # Validate module

# Schema Management
hypermodern schema validate                    # Validate schemas
hypermodern schema merge <files...>           # Merge schema files
hypermodern schema diff <old> <new>           # Compare schemas

# Database Operations
hypermodern db migrate                         # Run pending migrations
hypermodern db rollback <migration_id>        # Rollback migration
hypermodern db status                          # Show migration status
hypermodern db seed                           # Run database seeders

# Production Build
hypermodern build                              # Build for production
hypermodern build --output dist               # Custom output directory
hypermodern build --docker                    # Generate Docker files

# Testing
hypermodern test                               # Run all tests
hypermodern test --unit                       # Run unit tests only
hypermodern test --integration                # Run integration tests only
hypermodern test --coverage                   # Generate coverage report

# Utilities
hypermodern lint                               # Lint code
hypermodern format                             # Format code
hypermodern analyze                            # Analyze code quality
hypermodern docs generate                      # Generate documentation

Advanced CLI Configuration

Create a hypermodern.yaml file in your project root for advanced configuration:

# hypermodern.yaml
project:
  name: "my_hypermodern_app"
  version: "1.0.0"
  description: "My awesome Hypermodern application"

generation:
  output_directory: "lib/generated"
  separate_files: true
  null_safety: true
  immutable_models: true
  generate_json_serialization: true
  generate_binary_serialization: true
  generate_equality: true
  generate_copy_with: true
  generate_to_string: true

server:
  http_port: 8080
  ws_port: 8082
  tcp_port: 8081
  bind_address: "0.0.0.0"
  hot_reload: true
  auto_restart: true
  watch_patterns:
    - "schemas/**/*.json"
    - "lib/**/*.dart"
    - "config/**/*.yaml"

database:
  url: "postgresql://localhost:5432/myapp"
  migrations_directory: "migrations"
  auto_migrate: true
  seed_directory: "seeds"

modules:
  directory: "modules"
  auto_install_dependencies: true
  validate_on_install: true

build:
  output_directory: "build"
  minify: true
  tree_shake: true
  generate_source_maps: false
  docker:
    base_image: "dart:3.0-sdk"
    expose_ports: [8080, 8082, 8081]

testing:
  test_directory: "test"
  coverage_directory: "coverage"
  integration_test_timeout: "30s"
  parallel_tests: true

linting:
  rules_file: "analysis_options.yaml"
  exclude_patterns:
    - "lib/generated/**"
    - "build/**"

documentation:
  output_directory: "docs"
  include_private: false
  generate_examples: true

Custom CLI Commands

Extend the CLI with custom commands for your project:

// tools/custom_commands.dart
import 'package:hypermodern_cli/hypermodern_cli.dart';

class CustomCommands extends CommandGroup {
  @override
  String get name => 'custom';
  
  @override
  String get description => 'Custom project commands';
  
  @override
  List<Command> get commands => [
    SetupDevEnvironmentCommand(),
    GenerateTestDataCommand(),
    DeployToStagingCommand(),
  ];
}

class SetupDevEnvironmentCommand extends Command {
  @override
  String get name => 'setup-dev';
  
  @override
  String get description => 'Set up development environment';
  
  @override
  Future<void> run() async {
    print('Setting up development environment...');
    
    // Create database
    await _createDatabase();
    
    // Run migrations
    await _runMigrations();
    
    // Seed test data
    await _seedTestData();
    
    // Install dependencies
    await _installDependencies();
    
    print('โœ… Development environment ready!');
  }
  
  Future<void> _createDatabase() async {
    print('Creating database...');
    final result = await Process.run('createdb', ['myapp_dev']);
    if (result.exitCode != 0) {
      throw Exception('Failed to create database: ${result.stderr}');
    }
  }
  
  Future<void> _runMigrations() async {
    print('Running migrations...');
    await HypermodernCLI.runCommand(['db', 'migrate']);
  }
  
  Future<void> _seedTestData() async {
    print('Seeding test data...');
    await HypermodernCLI.runCommand(['db', 'seed']);
  }
  
  Future<void> _installDependencies() async {
    print('Installing dependencies...');
    await Process.run('dart', ['pub', 'get']);
  }
}

class GenerateTestDataCommand extends Command {
  @override
  String get name => 'generate-test-data';
  
  @override
  String get description => 'Generate test data for development';
  
  @override
  Future<void> run() async {
    final count = int.tryParse(argResults?['count'] ?? '100') ?? 100;
    
    print('Generating $count test records...');
    
    final generator = TestDataGenerator();
    await generator.generateUsers(count);
    await generator.generatePosts(count * 3);
    await generator.generateComments(count * 10);
    
    print('โœ… Test data generated successfully!');
  }
  
  @override
  void defineOptions(ArgParser parser) {
    parser.addOption('count', abbr: 'c', help: 'Number of records to generate');
  }
}

Hot Reload and Development Server

The development server provides instant feedback through hot reload, automatic code generation, and multi-protocol testing capabilities.

Development Server Architecture

class DevelopmentServer {
  final HypermodernServer _server;
  final FileWatcher _fileWatcher;
  final CodeGenerator _codeGenerator;
  final MigrationRunner _migrationRunner;
  final TestRunner _testRunner;
  
  bool _isReloading = false;
  final Set<String> _changedFiles = {};
  Timer? _reloadTimer;
  
  DevelopmentServer({
    required HypermodernServer server,
    required FileWatcher fileWatcher,
    required CodeGenerator codeGenerator,
    required MigrationRunner migrationRunner,
    required TestRunner testRunner,
  }) : _server = server,
       _fileWatcher = fileWatcher,
       _codeGenerator = codeGenerator,
       _migrationRunner = migrationRunner,
       _testRunner = testRunner;
  
  Future<void> start() async {
    print('๐Ÿš€ Starting Hypermodern development server...');
    
    // Start the main server
    await _server.listen();
    
    // Set up file watching
    await _setupFileWatching();
    
    // Start additional development services
    await _startDevelopmentServices();
    
    print('โœ… Development server ready!');
    print('   HTTP:      http://localhost:${_server.httpPort}');
    print('   WebSocket: ws://localhost:${_server.wsPort}');
    print('   TCP:       localhost:${_server.tcpPort}');
    print('   Admin UI:  http://localhost:${_server.httpPort}/admin');
    print('');
    print('๐Ÿ‘€ Watching for changes...');
  }
  
  Future<void> _setupFileWatching() async {
    // Watch schema files
    _fileWatcher.watch('schemas/**/*.json', (event) {
      _handleSchemaChange(event);
    });
    
    // Watch Dart source files
    _fileWatcher.watch('lib/**/*.dart', (event) {
      _handleSourceChange(event);
    });
    
    // Watch configuration files
    _fileWatcher.watch('hypermodern.yaml', (event) {
      _handleConfigChange(event);
    });
    
    // Watch migration files
    _fileWatcher.watch('migrations/**/*.dart', (event) {
      _handleMigrationChange(event);
    });
  }
  
  void _handleSchemaChange(FileChangeEvent event) {
    print('๐Ÿ“ Schema changed: ${event.path}');
    _changedFiles.add(event.path);
    _scheduleReload(ReloadType.codeGeneration);
  }
  
  void _handleSourceChange(FileChangeEvent event) {
    // Skip generated files
    if (event.path.contains('/generated/')) {
      return;
    }
    
    print('๐Ÿ”„ Source changed: ${event.path}');
    _changedFiles.add(event.path);
    _scheduleReload(ReloadType.hotReload);
  }
  
  void _handleConfigChange(FileChangeEvent event) {
    print('โš™๏ธ Configuration changed: ${event.path}');
    _scheduleReload(ReloadType.fullRestart);
  }
  
  void _handleMigrationChange(FileChangeEvent event) {
    print('๐Ÿ—„๏ธ Migration changed: ${event.path}');
    _scheduleReload(ReloadType.migration);
  }
  
  void _scheduleReload(ReloadType type) {
    // Debounce rapid file changes
    _reloadTimer?.cancel();
    _reloadTimer = Timer(Duration(milliseconds: 500), () {
      _performReload(type);
    });
  }
  
  Future<void> _performReload(ReloadType type) async {
    if (_isReloading) return;
    
    _isReloading = true;
    final stopwatch = Stopwatch()..start();
    
    try {
      switch (type) {
        case ReloadType.codeGeneration:
          await _performCodeGeneration();
          break;
        case ReloadType.hotReload:
          await _performHotReload();
          break;
        case ReloadType.migration:
          await _performMigration();
          break;
        case ReloadType.fullRestart:
          await _performFullRestart();
          break;
      }
      
      stopwatch.stop();
      print('โœ… Reload completed in ${stopwatch.elapsedMilliseconds}ms');
      
    } catch (e) {
      print('โŒ Reload failed: $e');
    } finally {
      _isReloading = false;
      _changedFiles.clear();
    }
  }
  
  Future<void> _performCodeGeneration() async {
    print('๐Ÿ”ง Regenerating code...');
    
    // Validate schemas first
    final validation = await _codeGenerator.validateSchemas();
    if (!validation.isValid) {
      print('โŒ Schema validation failed:');
      for (final error in validation.errors) {
        print('   ${error.file}: ${error.message}');
      }
      return;
    }
    
    // Generate code
    await _codeGenerator.generateAll();
    
    // Hot reload the server
    await _performHotReload();
  }
  
  Future<void> _performHotReload() async {
    print('๐Ÿ”ฅ Hot reloading...');
    
    // Reload server modules
    await _server.reloadModules();
    
    // Notify connected clients
    await _notifyClientsOfReload();
    
    // Run quick tests if enabled
    if (_shouldRunTestsOnReload()) {
      await _runQuickTests();
    }
  }
  
  Future<void> _performMigration() async {
    print('๐Ÿ—„๏ธ Running migrations...');
    
    await _migrationRunner.runPendingMigrations();
    
    // Reload server to pick up schema changes
    await _performHotReload();
  }
  
  Future<void> _performFullRestart() async {
    print('๐Ÿ”„ Performing full restart...');
    
    // Stop current server
    await _server.stop();
    
    // Reload configuration
    await _server.reloadConfiguration();
    
    // Restart server
    await _server.listen();
  }
  
  Future<void> _notifyClientsOfReload() async {
    // Notify WebSocket clients
    await _server.broadcastToWebSocketClients({
      'type': 'hot_reload',
      'timestamp': DateTime.now().toIso8601String(),
      'changed_files': _changedFiles.toList(),
    });
  }
  
  bool _shouldRunTestsOnReload() {
    // Run tests if configuration enables it and changes are in source files
    return _server.config.runTestsOnReload && 
           _changedFiles.any((file) => file.endsWith('.dart'));
  }
  
  Future<void> _runQuickTests() async {
    print('๐Ÿงช Running quick tests...');
    
    final result = await _testRunner.runQuickTests();
    
    if (result.success) {
      print('โœ… All tests passed');
    } else {
      print('โŒ ${result.failedTests} test(s) failed');
      for (final failure in result.failures) {
        print('   ${failure.testName}: ${failure.error}');
      }
    }
  }
  
  Future<void> _startDevelopmentServices() async {
    // Start admin UI server
    await _startAdminUI();
    
    // Start API documentation server
    await _startApiDocs();
    
    // Start metrics collection
    await _startMetricsCollection();
  }
  
  Future<void> _startAdminUI() async {
    // Implementation for development admin UI
    print('๐ŸŽ›๏ธ Admin UI available at http://localhost:${_server.httpPort}/admin');
  }
  
  Future<void> _startApiDocs() async {
    // Implementation for API documentation
    print('๐Ÿ“š API docs available at http://localhost:${_server.httpPort}/docs');
  }
  
  Future<void> _startMetricsCollection() async {
    // Implementation for development metrics
    print('๐Ÿ“Š Metrics available at http://localhost:${_server.httpPort}/metrics');
  }
}

enum ReloadType {
  codeGeneration,
  hotReload,
  migration,
  fullRestart,
}

File Watching System

class FileWatcher {
  final Map<String, StreamSubscription> _watchers = {};
  final Map<String, List<void Function(FileChangeEvent)>> _callbacks = {};
  
  Future<void> watch(String pattern, void Function(FileChangeEvent) callback) async {
    _callbacks.putIfAbsent(pattern, () => []).add(callback);
    
    if (!_watchers.containsKey(pattern)) {
      await _startWatching(pattern);
    }
  }
  
  Future<void> _startWatching(String pattern) async {
    final glob = Glob(pattern);
    final watcher = DirectoryWatcher('.');
    
    final subscription = watcher.events.listen((event) {
      if (glob.matches(event.path)) {
        final changeEvent = FileChangeEvent(
          path: event.path,
          type: _mapEventType(event.type),
          timestamp: DateTime.now(),
        );
        
        final callbacks = _callbacks[pattern] ?? [];
        for (final callback in callbacks) {
          try {
            callback(changeEvent);
          } catch (e) {
            print('Error in file watcher callback: $e');
          }
        }
      }
    });
    
    _watchers[pattern] = subscription;
  }
  
  FileChangeType _mapEventType(ChangeType type) {
    switch (type) {
      case ChangeType.ADD:
        return FileChangeType.created;
      case ChangeType.MODIFY:
        return FileChangeType.modified;
      case ChangeType.REMOVE:
        return FileChangeType.deleted;
      default:
        return FileChangeType.modified;
    }
  }
  
  Future<void> stop() async {
    for (final subscription in _watchers.values) {
      await subscription.cancel();
    }
    _watchers.clear();
    _callbacks.clear();
  }
}

class FileChangeEvent {
  final String path;
  final FileChangeType type;
  final DateTime timestamp;
  
  FileChangeEvent({
    required this.path,
    required this.type,
    required this.timestamp,
  });
}

enum FileChangeType {
  created,
  modified,
  deleted,
}

Code Generation Workflow

Advanced Code Generator

class AdvancedCodeGenerator {
  final SchemaLoader _schemaLoader;
  final TemplateEngine _templateEngine;
  final CodeFormatter _formatter;
  final DependencyAnalyzer _dependencyAnalyzer;
  
  AdvancedCodeGenerator({
    required SchemaLoader schemaLoader,
    required TemplateEngine templateEngine,
    required CodeFormatter formatter,
    required DependencyAnalyzer dependencyAnalyzer,
  }) : _schemaLoader = schemaLoader,
       _templateEngine = templateEngine,
       _formatter = formatter,
       _dependencyAnalyzer = dependencyAnalyzer;
  
  Future<GenerationResult> generateAll() async {
    final stopwatch = Stopwatch()..start();
    
    try {
      // Load and validate schemas
      final schemas = await _schemaLoader.loadAll();
      final validation = await validateSchemas(schemas);
      
      if (!validation.isValid) {
        return GenerationResult.failure(validation.errors);
      }
      
      // Analyze dependencies
      final dependencyGraph = await _dependencyAnalyzer.analyze(schemas);
      
      // Generate code in dependency order
      final generatedFiles = <GeneratedFile>[];
      
      for (final schema in dependencyGraph.topologicalSort()) {
        final files = await _generateForSchema(schema);
        generatedFiles.addAll(files);
      }
      
      // Format generated code
      await _formatGeneratedFiles(generatedFiles);
      
      // Write files to disk
      await _writeGeneratedFiles(generatedFiles);
      
      stopwatch.stop();
      
      return GenerationResult.success(
        generatedFiles: generatedFiles,
        duration: stopwatch.elapsed,
      );
      
    } catch (e) {
      return GenerationResult.failure([
        GenerationError(
          file: 'unknown',
          message: 'Code generation failed: $e',
          type: GenerationErrorType.internal,
        ),
      ]);
    }
  }
  
  Future<List<GeneratedFile>> _generateForSchema(Schema schema) async {
    final files = <GeneratedFile>[];
    
    // Generate models
    if (schema.models.isNotEmpty) {
      final modelFiles = await _generateModels(schema);
      files.addAll(modelFiles);
    }
    
    // Generate enums
    if (schema.enums.isNotEmpty) {
      final enumFiles = await _generateEnums(schema);
      files.addAll(enumFiles);
    }
    
    // Generate client code
    if (schema.endpoints.isNotEmpty) {
      final clientFiles = await _generateClient(schema);
      files.addAll(clientFiles);
    }
    
    // Generate server code
    if (schema.endpoints.isNotEmpty) {
      final serverFiles = await _generateServer(schema);
      files.addAll(serverFiles);
    }
    
    return files;
  }
  
  Future<List<GeneratedFile>> _generateModels(Schema schema) async {
    final files = <GeneratedFile>[];
    
    for (final model in schema.models.values) {
      final context = ModelGenerationContext(
        model: model,
        schema: schema,
        config: _getGenerationConfig(),
      );
      
      final content = await _templateEngine.render('model.dart.template', context);
      
      files.add(GeneratedFile(
        path: 'lib/generated/models/${model.name.snakeCase}.dart',
        content: content,
        type: GeneratedFileType.model,
      ));
    }
    
    // Generate barrel file
    final barrelContent = await _generateModelsBarrel(schema.models.values);
    files.add(GeneratedFile(
      path: 'lib/generated/models.dart',
      content: barrelContent,
      type: GeneratedFileType.barrel,
    ));
    
    return files;
  }
  
  Future<String> _generateModelsBarrel(Iterable<ModelDefinition> models) async {
    final buffer = StringBuffer();
    
    buffer.writeln('// GENERATED CODE - DO NOT MODIFY BY HAND');
    buffer.writeln('// Generated by Hypermodern CLI');
    buffer.writeln('');
    
    for (final model in models) {
      buffer.writeln("export '${model.name.snakeCase}.dart';");
    }
    
    return buffer.toString();
  }
  
  Future<void> _formatGeneratedFiles(List<GeneratedFile> files) async {
    for (final file in files) {
      if (file.path.endsWith('.dart')) {
        file.content = await _formatter.format(file.content);
      }
    }
  }
  
  Future<void> _writeGeneratedFiles(List<GeneratedFile> files) async {
    for (final file in files) {
      final fileObj = File(file.path);
      await fileObj.parent.create(recursive: true);
      await fileObj.writeAsString(file.content);
    }
  }
  
  Future<SchemaValidationResult> validateSchemas(List<Schema> schemas) async {
    final errors = <GenerationError>[];
    
    for (final schema in schemas) {
      // Validate model definitions
      for (final model in schema.models.values) {
        final modelErrors = await _validateModel(model, schema);
        errors.addAll(modelErrors);
      }
      
      // Validate endpoint definitions
      for (final endpoint in schema.endpoints.values) {
        final endpointErrors = await _validateEndpoint(endpoint, schema);
        errors.addAll(endpointErrors);
      }
      
      // Validate enum definitions
      for (final enumDef in schema.enums.values) {
        final enumErrors = await _validateEnum(enumDef);
        errors.addAll(enumErrors);
      }
    }
    
    return SchemaValidationResult(
      isValid: errors.isEmpty,
      errors: errors,
    );
  }
  
  Future<List<GenerationError>> _validateModel(
    ModelDefinition model,
    Schema schema,
  ) async {
    final errors = <GenerationError>[];
    
    // Check for reserved field names
    final reservedNames = ['hashCode', 'runtimeType', 'toString'];
    for (final field in model.fields.values) {
      if (reservedNames.contains(field.name)) {
        errors.add(GenerationError(
          file: schema.file,
          message: 'Field name "${field.name}" is reserved in model "${model.name}"',
          type: GenerationErrorType.validation,
        ));
      }
    }
    
    // Validate field types
    for (final field in model.fields.values) {
      if (field.type.isReference) {
        final referencedModel = schema.models[field.type.referenceName];
        if (referencedModel == null) {
          errors.add(GenerationError(
            file: schema.file,
            message: 'Unknown model reference "${field.type.referenceName}" in field "${field.name}"',
            type: GenerationErrorType.validation,
          ));
        }
      }
    }
    
    return errors;
  }
  
  GenerationConfig _getGenerationConfig() {
    return GenerationConfig(
      nullSafety: true,
      immutableModels: true,
      generateJsonSerialization: true,
      generateBinarySerialization: true,
      generateEquality: true,
      generateCopyWith: true,
      generateToString: true,
    );
  }
}

class GenerationResult {
  final bool success;
  final List<GeneratedFile> generatedFiles;
  final List<GenerationError> errors;
  final Duration? duration;
  
  GenerationResult({
    required this.success,
    this.generatedFiles = const [],
    this.errors = const [],
    this.duration,
  });
  
  factory GenerationResult.success({
    required List<GeneratedFile> generatedFiles,
    required Duration duration,
  }) {
    return GenerationResult(
      success: true,
      generatedFiles: generatedFiles,
      duration: duration,
    );
  }
  
  factory GenerationResult.failure(List<GenerationError> errors) {
    return GenerationResult(
      success: false,
      errors: errors,
    );
  }
}

class GeneratedFile {
  final String path;
  String content;
  final GeneratedFileType type;
  
  GeneratedFile({
    required this.path,
    required this.content,
    required this.type,
  });
}

enum GeneratedFileType {
  model,
  enum,
  client,
  server,
  barrel,
}

Template System

class TemplateEngine {
  final Map<String, Template> _templates = {};
  final TemplateLoader _loader;
  
  TemplateEngine(this._loader);
  
  Future<void> loadTemplates() async {
    final templateFiles = await _loader.loadAll();
    
    for (final file in templateFiles) {
      final template = Template.parse(file.content);
      _templates[file.name] = template;
    }
  }
  
  Future<String> render(String templateName, dynamic context) async {
    final template = _templates[templateName];
    if (template == null) {
      throw ArgumentError('Template not found: $templateName');
    }
    
    return template.render(context);
  }
}

class Template {
  final String _content;
  final List<TemplateNode> _nodes;
  
  Template._(this._content, this._nodes);
  
  factory Template.parse(String content) {
    final parser = TemplateParser(content);
    final nodes = parser.parse();
    return Template._(content, nodes);
  }
  
  String render(dynamic context) {
    final buffer = StringBuffer();
    
    for (final node in _nodes) {
      buffer.write(node.render(context));
    }
    
    return buffer.toString();
  }
}

// Example model template
const String modelTemplate = '''
// GENERATED CODE - DO NOT MODIFY BY HAND
// Generated by Hypermodern CLI

{{#imports}}
import '{{.}}';
{{/imports}}

{{#model.documentation}}
/// {{.}}
{{/model.documentation}}
class {{model.name}} {
{{#model.fields}}
  {{#documentation}}
  /// {{.}}
  {{/documentation}}
  final {{type.dartType}} {{name}};
{{/model.fields}}

  const {{model.name}}({
{{#model.fields}}
    {{#required}}required {{/required}}this.{{name}},
{{/model.fields}}
  });

{{#config.generateJsonSerialization}}
  Map<String, dynamic> toJson() => {
{{#model.fields}}
    '{{jsonName}}': {{#type.isOptional}}{{name}}{{#type.needsConversion}}?.{{conversionMethod}}(){{/type.needsConversion}}{{/type.isOptional}}{{^type.isOptional}}{{name}}{{#type.needsConversion}}.{{conversionMethod}}(){{/type.needsConversion}}{{/type.isOptional}},
{{/model.fields}}
  };

  factory {{model.name}}.fromJson(Map<String, dynamic> json) => {{model.name}}(
{{#model.fields}}
    {{name}}: {{#type.fromJsonExpression}}{{.}}{{/type.fromJsonExpression}},
{{/model.fields}}
  );
{{/config.generateJsonSerialization}}

{{#config.generateBinarySerialization}}
  Uint8List toBinary() {
    final writer = BinaryWriter();
{{#model.fields}}
    {{#type.binaryWriteMethod}}writer.{{.}}({{name}});{{/type.binaryWriteMethod}}
{{/model.fields}}
    return writer.toBytes();
  }

  factory {{model.name}}.fromBinary(Uint8List data) {
    final reader = BinaryReader(data);
    return {{model.name}}(
{{#model.fields}}
      {{name}}: {{#type.binaryReadMethod}}reader.{{.}}(){{/type.binaryReadMethod}},
{{/model.fields}}
    );
  }
{{/config.generateBinarySerialization}}

{{#config.generateEquality}}
  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is {{model.name}} &&
          runtimeType == other.runtimeType &&
{{#model.fields}}
          {{name}} == other.{{name}}{{^last}} &&{{/last}}
{{/model.fields}};

  @override
  int get hashCode => Object.hash(
{{#model.fields}}
    {{name}},
{{/model.fields}}
  );
{{/config.generateEquality}}

{{#config.generateToString}}
  @override
  String toString() => '{{model.name}}({{#model.fields}}{{name}}: ${{name}}{{^last}}, {{/last}}{{/model.fields}})';
{{/config.generateToString}}

{{#config.generateCopyWith}}
  {{model.name}} copyWith({
{{#model.fields}}
    {{type.dartType}}? {{name}},
{{/model.fields}}
  }) => {{model.name}}(
{{#model.fields}}
    {{name}}: {{name}} ?? this.{{name}},
{{/model.fields}}
  );
{{/config.generateCopyWith}}
}
''';

Testing Strategies

Multi-Protocol Testing Framework

class HypermodernTestFramework {
  final List<ProtocolTestClient> _clients;
  final TestDatabase _testDatabase;
  final MockDataGenerator _mockDataGenerator;
  
  HypermodernTestFramework({
    required List<ProtocolTestClient> clients,
    required TestDatabase testDatabase,
    required MockDataGenerator mockDataGenerator,
  }) : _clients = clients,
       _testDatabase = testDatabase,
       _mockDataGenerator = mockDataGenerator;
  
  Future<void> runEndpointTests(String endpoint, EndpointTestSuite testSuite) async {
    for (final client in _clients) {
      await _runEndpointTestsForProtocol(endpoint, testSuite, client);
    }
  }
  
  Future<void> _runEndpointTestsForProtocol(
    String endpoint,
    EndpointTestSuite testSuite,
    ProtocolTestClient client,
  ) async {
    group('${endpoint} - ${client.protocol}', () {
      setUp(() async {
        await _testDatabase.reset();
        await _mockDataGenerator.seedTestData();
        await client.connect();
      });
      
      tearDown(() async {
        await client.disconnect();
      });
      
      for (final testCase in testSuite.testCases) {
        test(testCase.name, () async {
          await _runTestCase(testCase, client);
        });
      }
    });
  }
  
  Future<void> _runTestCase(EndpointTestCase testCase, ProtocolTestClient client) async {
    try {
      // Execute the test case
      final result = await client.request(testCase.endpoint, testCase.request);
      
      // Validate response
      testCase.validateResponse(result);
      
      // Validate side effects
      if (testCase.sideEffectValidators.isNotEmpty) {
        for (final validator in testCase.sideEffectValidators) {
          await validator.validate(_testDatabase);
        }
      }
      
    } catch (e) {
      if (testCase.expectsError) {
        testCase.validateError(e);
      } else {
        rethrow;
      }
    }
  }
}

class EndpointTestSuite {
  final String endpoint;
  final List<EndpointTestCase> testCases;
  
  EndpointTestSuite({
    required this.endpoint,
    required this.testCases,
  });
}

class EndpointTestCase {
  final String name;
  final String endpoint;
  final dynamic request;
  final bool expectsError;
  final void Function(dynamic) validateResponse;
  final void Function(dynamic) validateError;
  final List<SideEffectValidator> sideEffectValidators;
  
  EndpointTestCase({
    required this.name,
    required this.endpoint,
    required this.request,
    this.expectsError = false,
    required this.validateResponse,
    this.validateError = _defaultErrorValidator,
    this.sideEffectValidators = const [],
  });
  
  static void _defaultErrorValidator(dynamic error) {
    // Default error validation - just ensure an error occurred
    expect(error, isNotNull);
  }
}

// Example test suite
class UserEndpointTests {
  static EndpointTestSuite createTestSuite() {
    return EndpointTestSuite(
      endpoint: 'user_management',
      testCases: [
        EndpointTestCase(
          name: 'should create user successfully',
          endpoint: 'create_user',
          request: CreateUserRequest(
            username: 'testuser',
            email: 'test@example.com',
            password: 'securepassword123',
          ),
          validateResponse: (response) {
            expect(response, isA<User>());
            final user = response as User;
            expect(user.username, equals('testuser'));
            expect(user.email, equals('test@example.com'));
            expect(user.id, isPositive);
          },
          sideEffectValidators: [
            DatabaseRecordValidator(
              table: 'users',
              expectedCount: 1,
              conditions: {'username': 'testuser'},
            ),
          ],
        ),
        
        EndpointTestCase(
          name: 'should reject duplicate email',
          endpoint: 'create_user',
          request: CreateUserRequest(
            username: 'testuser2',
            email: 'existing@example.com', // This email already exists
            password: 'securepassword123',
          ),
          expectsError: true,
          validateResponse: (_) => fail('Should have thrown an error'),
          validateError: (error) {
            expect(error, isA<EmailExistsException>());
          },
        ),
        
        EndpointTestCase(
          name: 'should get user by id',
          endpoint: 'get_user',
          request: GetUserRequest(id: 1), // Assuming user with ID 1 exists
          validateResponse: (response) {
            expect(response, isA<User>());
            final user = response as User;
            expect(user.id, equals(1));
          },
        ),
        
        EndpointTestCase(
          name: 'should return 404 for non-existent user',
          endpoint: 'get_user',
          request: GetUserRequest(id: 99999),
          expectsError: true,
          validateResponse: (_) => fail('Should have thrown an error'),
          validateError: (error) {
            expect(error, isA<NotFoundException>());
          },
        ),
      ],
    );
  }
}

Integration Testing

class IntegrationTestRunner {
  final HypermodernServer _server;
  final List<HypermodernClient> _clients;
  final TestDatabase _database;
  
  IntegrationTestRunner({
    required HypermodernServer server,
    required List<HypermodernClient> clients,
    required TestDatabase database,
  }) : _server = server,
       _clients = clients,
       _database = database;
  
  Future<void> runIntegrationTests() async {
    group('Integration Tests', () {
      setUpAll(() async {
        await _database.initialize();
        await _server.start();
        
        for (final client in _clients) {
          await client.connect();
        }
      });
      
      tearDownAll(() async {
        for (final client in _clients) {
          await client.disconnect();
        }
        
        await _server.stop();
        await _database.cleanup();
      });
      
      group('User Workflow', () {
        test('complete user registration and login flow', () async {
          await _testUserRegistrationFlow();
        });
        
        test('user profile management', () async {
          await _testUserProfileFlow();
        });
      });
      
      group('Real-time Features', () {
        test('WebSocket notifications', () async {
          await _testWebSocketNotifications();
        });
        
        test('TCP streaming', () async {
          await _testTcpStreaming();
        });
      });
      
      group('Cross-Protocol Consistency', () {
        test('same data across all protocols', () async {
          await _testCrossProtocolConsistency();
        });
      });
    });
  }
  
  Future<void> _testUserRegistrationFlow() async {
    final httpClient = _clients.firstWhere((c) => c.protocol == 'http');
    
    // Register user
    final registerRequest = RegisterRequest(
      username: 'integrationtest',
      email: 'integration@test.com',
      password: 'testpassword123',
    );
    
    final registerResponse = await httpClient.request<RegisterResponse>(
      'register',
      registerRequest,
    );
    
    expect(registerResponse.user.username, equals('integrationtest'));
    
    // Login with created user
    final loginRequest = LoginRequest(
      email: 'integration@test.com',
      password: 'testpassword123',
    );
    
    final loginResponse = await httpClient.request<LoginResponse>(
      'login',
      loginRequest,
    );
    
    expect(loginResponse.accessToken, isNotEmpty);
    expect(loginResponse.user.id, equals(registerResponse.user.id));
  }
  
  Future<void> _testWebSocketNotifications() async {
    final wsClient = _clients.firstWhere((c) => c.protocol == 'websocket');
    final httpClient = _clients.firstWhere((c) => c.protocol == 'http');
    
    // Set up WebSocket listener
    final notifications = <UserNotification>[];
    final subscription = wsClient.stream<UserNotification>(
      'user_notifications',
      UserNotificationRequest(userId: 1),
    ).listen((notification) {
      notifications.add(notification);
    });
    
    // Trigger notification via HTTP
    await httpClient.request<void>(
      'send_notification',
      SendNotificationRequest(
        userId: 1,
        message: 'Test notification',
        type: NotificationType.info,
      ),
    );
    
    // Wait for notification
    await Future.delayed(Duration(seconds: 1));
    
    expect(notifications, hasLength(1));
    expect(notifications.first.message, equals('Test notification'));
    
    await subscription.cancel();
  }
  
  Future<void> _testCrossProtocolConsistency() async {
    // Create user via HTTP
    final httpClient = _clients.firstWhere((c) => c.protocol == 'http');
    final createRequest = CreateUserRequest(
      username: 'crossprotocol',
      email: 'cross@protocol.com',
      password: 'password123',
    );
    
    final createdUser = await httpClient.request<User>('create_user', createRequest);
    
    // Fetch user via WebSocket
    final wsClient = _clients.firstWhere((c) => c.protocol == 'websocket');
    final wsUser = await wsClient.request<User>(
      'get_user',
      GetUserRequest(id: createdUser.id),
    );
    
    // Fetch user via TCP
    final tcpClient = _clients.firstWhere((c) => c.protocol == 'tcp');
    final tcpUser = await tcpClient.request<User>(
      'get_user',
      GetUserRequest(id: createdUser.id),
    );
    
    // Verify consistency
    expect(wsUser.id, equals(createdUser.id));
    expect(wsUser.username, equals(createdUser.username));
    expect(wsUser.email, equals(createdUser.email));
    
    expect(tcpUser.id, equals(createdUser.id));
    expect(tcpUser.username, equals(createdUser.username));
    expect(tcpUser.email, equals(createdUser.email));
  }
}

Performance Testing

class PerformanceTestRunner {
  final List<HypermodernClient> _clients;
  final TestDataGenerator _dataGenerator;
  
  PerformanceTestRunner({
    required List<HypermodernClient> clients,
    required TestDataGenerator dataGenerator,
  }) : _clients = clients,
       _dataGenerator = dataGenerator;
  
  Future<void> runPerformanceTests() async {
    group('Performance Tests', () {
      test('HTTP throughput test', () async {
        await _testProtocolThroughput('http', iterations: 1000);
      });
      
      test('WebSocket throughput test', () async {
        await _testProtocolThroughput('websocket', iterations: 1000);
      });
      
      test('TCP throughput test', () async {
        await _testProtocolThroughput('tcp', iterations: 1000);
      });
      
      test('Load test with concurrent users', () async {
        await _testConcurrentLoad(concurrency: 50, requestsPerUser: 100);
      });
      
      test('Memory usage under load', () async {
        await _testMemoryUsage();
      });
    });
  }
  
  Future<void> _testProtocolThroughput(String protocol, {required int iterations}) async {
    final client = _clients.firstWhere((c) => c.protocol == protocol);
    await client.connect();
    
    final request = GetUserRequest(id: 1);
    final stopwatch = Stopwatch()..start();
    
    for (int i = 0; i < iterations; i++) {
      await client.request<User>('get_user', request);
    }
    
    stopwatch.stop();
    
    final throughput = iterations / stopwatch.elapsed.inMilliseconds * 1000;
    print('$protocol throughput: ${throughput.toStringAsFixed(1)} req/sec');
    
    // Assert minimum performance requirements
    expect(throughput, greaterThan(100)); // At least 100 req/sec
    
    await client.disconnect();
  }
  
  Future<void> _testConcurrentLoad({required int concurrency, required int requestsPerUser}) async {
    final futures = <Future>[];
    
    for (int i = 0; i < concurrency; i++) {
      final client = HypermodernClient('http://localhost:8080');
      futures.add(_simulateUser(client, requestsPerUser));
    }
    
    final stopwatch = Stopwatch()..start();
    await Future.wait(futures);
    stopwatch.stop();
    
    final totalRequests = concurrency * requestsPerUser;
    final throughput = totalRequests / stopwatch.elapsed.inMilliseconds * 1000;
    
    print('Concurrent load test: ${throughput.toStringAsFixed(1)} req/sec with $concurrency users');
    
    // Assert performance under load
    expect(throughput, greaterThan(500)); // At least 500 req/sec under load
  }
  
  Future<void> _simulateUser(HypermodernClient client, int requests) async {
    await client.connect();
    
    try {
      for (int i = 0; i < requests; i++) {
        // Simulate realistic user behavior
        await client.request<User>('get_user', GetUserRequest(id: 1));
        
        if (i % 10 == 0) {
          // Occasionally create or update data
          await client.request<User>('update_user', UpdateUserRequest(
            id: 1,
            username: 'user_${Random().nextInt(1000)}',
          ));
        }
        
        // Small delay to simulate user thinking time
        await Future.delayed(Duration(milliseconds: 10));
      }
    } finally {
      await client.disconnect();
    }
  }
  
  Future<void> _testMemoryUsage() async {
    final initialMemory = _getCurrentMemoryUsage();
    
    // Generate load
    await _testConcurrentLoad(concurrency: 20, requestsPerUser: 50);
    
    // Force garbage collection
    await _forceGarbageCollection();
    
    final finalMemory = _getCurrentMemoryUsage();
    final memoryIncrease = finalMemory - initialMemory;
    
    print('Memory usage increase: ${_formatBytes(memoryIncrease)}');
    
    // Assert memory usage is reasonable (less than 100MB increase)
    expect(memoryIncrease, lessThan(100 * 1024 * 1024));
  }
  
  int _getCurrentMemoryUsage() {
    // This would use platform-specific memory measurement
    // Simplified for example
    return ProcessInfo.currentRss;
  }
  
  Future<void> _forceGarbageCollection() async {
    // Force multiple GC cycles
    for (int i = 0; i < 5; i++) {
      await Future.delayed(Duration(milliseconds: 100));
      // System.gc() equivalent in Dart
    }
  }
  
  String _formatBytes(int bytes) {
    if (bytes < 1024) return '${bytes}B';
    if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)}KB';
    return '${(bytes / (1024 * 1024)).toStringAsFixed(1)}MB';
  }
}

What's Next

You now have a comprehensive understanding of the Hypermodern development workflow, including CLI usage, hot reload capabilities, code generation, and testing strategies. The next chapter will cover production deployment, showing you how to build, containerize, and deploy Hypermodern applications to various environments with proper monitoring and scaling considerations.