Skip to content

qwlong/dorval

Repository files navigation

🎯 Dorval

CI Status npm version npm downloads codecov license dart version typescript version
Generate type-safe Dart/Flutter API clients from OpenAPI specifications
Dorval = Dart + Orval. Built on the solid foundation of Orval's architecture.

Overview

Dorval brings the power of Orval to the Dart/Flutter ecosystem, automatically generating:

  • 🎯 Type-safe API clients - Never worry about API contracts again
  • 🔒 Freezed models - Immutable data classes with JSON serialization
  • 🌐 HTTP client support - Dio client with ApiClient wrapper (more coming soon)
  • Full null safety - Leveraging Dart's sound null safety
  • 🔀 Smart header consolidation - Automatically reduces duplicate header classes

Project Structure

dorval/
├── packages/
│   ├── core/                   # Core generation logic (@dorval/core)
│   │   ├── src/
│   │   │   ├── __tests__/      # Test files
│   │   │   ├── generators/     # Dart code generators
│   │   │   ├── getters/        # Schema getters
│   │   │   ├── parser/         # OpenAPI parsing
│   │   │   ├── resolvers/      # Reference resolvers
│   │   │   ├── templates/      # Handlebars templates
│   │   │   ├── utils/          # Utilities
│   │   │   └── writers/        # File writers
│   │   ├── dist/               # Compiled output
│   │   ├── package.json
│   │   └── tsconfig.json
│   │
│   ├── dorval/                 # CLI tool (dorval)
│   │   ├── src/
│   │   │   ├── bin/           # CLI entry point
│   │   │   ├── commands/      # CLI commands
│   │   │   └── cli.ts         # CLI configuration
│   │   ├── dist/              # Compiled output
│   │   └── package.json
│   │
│   ├── dio/                    # Dio client templates (@dorval/dio)
│   │   ├── src/
│   │   │   └── builders/      # Dio-specific builders
│   │   └── package.json
│   │
│   └── custom/                 # Custom client templates (@dorval/custom)
│       ├── src/
│       │   └── builders/      # Custom client builders
│       └── package.json
│
├── .github/
│   └── workflows/             # GitHub Actions CI/CD
│       ├── ci.yml            # Continuous Integration
│       ├── release.yml       # Auto release (semantic-release)
│       └── manual-publish.yml # Manual backup publish
│
└── docs/                       # Documentation
    ├── PUBLISHING.md          # Publishing guide
    └── RELEASE.md            # Release process

Getting Started

Installation

Install from npm

# Install globally
npm install -g dorval

# Or use with npx
npx dorval generate -i ./openapi.yaml -o ./lib/api

# Or add as a dev dependency
npm install --save-dev dorval

Build from source

# Clone the repository
git clone https://github.com/qwlong/dorval.git
cd dorval

# Install dependencies
yarn install

# Build the project
yarn build

# Link CLI globally (optional)
cd packages/dorval
npm link

Quick Start

  1. Create a configuration file:

You can use either CommonJS or ES Modules format:

ES Modules (dorval.config.js or dorval.config.mjs):

export default {
  petstore: {
    input: './petstore.yaml',
    output: {
      target: './lib/api',
      mode: 'split',
      client: 'dio',
      override: {
        generator: {
          freezed: true,
          jsonSerializable: true,
          nullSafety: true
        },
        methodNaming: 'methodPath'  // 'operationId' | 'methodPath'
      }
    }
  }
}

CommonJS (dorval.config.cjs):

module.exports = {
  petstore: {
    input: './petstore.yaml',
    output: {
      target: './lib/api',
      mode: 'split',
      client: 'dio',
      override: {
        generator: {
          freezed: true,
          jsonSerializable: true,
          nullSafety: true
        },
        methodNaming: 'methodPath'  // 'operationId' | 'methodPath'
      }
    }
  }
}

TypeScript (dorval.config.ts):

export default {
  petstore: {
    input: './petstore.yaml',
    output: {
      target: './lib/api',
      mode: 'split',
      client: 'dio',
      override: {
        generator: {
          freezed: true,
          jsonSerializable: true,
          nullSafety: true
        },
        methodNaming: 'methodPath'  // 'operationId' | 'methodPath'
      }
    }
  }
}
  1. Run the generator:
# Using config file
dorval generate

# Using command line options
dorval generate -i ./petstore.yaml -o ./lib/api

# Watch mode (coming soon)
# dorval watch
  1. Use the generated code in your Flutter app:
import 'package:dio/dio.dart';
import 'api/api_client.dart';
import 'api/services/pets_service.dart';
import 'api/models/index.dart';

void main() async {
  // Initialize the API client
  final apiClient = ApiClient(
    baseUrl: 'https://petstore.swagger.io/v2',
  );
  
  // Create service instance
  final petsService = PetsService(apiClient);
  
  // Type-safe API calls with generated models!
  try {
    // List all pets with optional query parameters
    final pets = await petsService.listPets(
      params: ListPetsParams(limit: 10),
    );
    
    // Create a new pet
    final newPet = NewPet(
      name: 'Fluffy',
      tag: 'cat',
    );
    final createdPet = await petsService.createPets(newPet);
    
    // Get pet by ID
    final pet = await petsService.showPetById('123');
    
    // Update a pet
    final updatedPet = await petsService.updatePet(
      '123',
      NewPet(name: 'Fluffy Updated'),
    );
    
    // Delete a pet
    await petsService.deletePet('123');
    
  } catch (e) {
    if (e is ApiException) {
      print('API Error: ${e.statusCode} - ${e.message}');
    }
  }
  
  // Don't forget to dispose when done
  apiClient.dispose();
}

Programmatic Usage

Dorval can be used programmatically in your Node.js scripts. Both CommonJS and ES Modules are supported:

ES Modules (.mjs or package.json with "type": "module")

import { generateDartCode } from '@dorval/core';

// Using async/await
const files = await generateDartCode({
  input: './openapi.yaml',
  output: {
    target: './lib/api',
    mode: 'split',
    client: 'dio'
  }
});

console.log(`Generated ${files.length} files`);

CommonJS (.cjs or .js without "type": "module")

const { generateDartCode } = require('@dorval/core');

// Using promises
generateDartCode({
  input: './openapi.yaml',
  output: {
    target: './lib/api',
    mode: 'split',
    client: 'dio'
  }
}).then(files => {
  console.log(`Generated ${files.length} files`);
});

// Or with async/await in CommonJS
(async () => {
  const files = await generateDartCode({
    input: './openapi.yaml',
    output: {
      target: './lib/api',
      mode: 'split',
      client: 'dio'
    }
  });
  console.log(`Generated ${files.length} files`);
})();

TypeScript

import { generateDartCode } from '@dorval/core';
import type { DorvalConfig } from '@dorval/core';

const config: DorvalConfig = {
  input: './openapi.yaml',
  output: {
    target: './lib/api',
    mode: 'split',
    client: 'dio'
  }
};

const files = await generateDartCode(config);
console.log(`Generated ${files.length} files`);

Development

Setup

# Install dependencies
yarn install

# Build all packages
yarn build

# Run in development mode
yarn dev

# Run tests
yarn test

Commit Convention

This project uses Conventional Commits for automatic versioning and changelog generation.

Commit Format

<type>(<scope>): <subject>

<body>

<footer>

Commit Types & Version Bumps

Type Version Description Example
fix Patch (0.2.0 → 0.2.1) Bug fixes fix: resolve null reference in parser
feat Minor (0.2.0 → 0.3.0) New features feat: add support for oneOf schemas
feat! or BREAKING CHANGE Major (0.2.0 → 1.0.0) Breaking changes feat!: change API structure
perf Patch Performance improvements perf: optimize model generation
docs No release Documentation only docs: update configuration guide
style No release Code style changes style: format code
refactor No release Code refactoring refactor: simplify parser logic
test No release Test changes test: add parser tests
chore No release Maintenance tasks chore: update dependencies
chore(deps) Patch Dependency updates chore(deps): update dio to v5.4

Examples

# Bug fix (patch release)
git commit -m "fix: handle nullable array items correctly"

# New feature (minor release)
git commit -m "feat: add Retrofit client support"

# Breaking change (major release)
git commit -m "feat!: rename generateDartCode to generate

BREAKING CHANGE: The main API function has been renamed for consistency"

# Multiple changes in one commit
git commit -m "feat(models): add union type support

- Add oneOf schema handling
- Generate Freezed union types
- Support discriminator property

Closes #123"

Scope Examples

  • core: Core generation logic
  • cli: CLI tool changes
  • models: Model generation
  • services: Service generation
  • parser: OpenAPI parser
  • templates: Template changes
  • deps: Dependencies

Architecture

This project reuses the OpenAPI parsing infrastructure from Orval while implementing Dart-specific code generation:

  1. Parse: OpenAPI spec → normalized schema (using @apidevtools/swagger-parser)
  2. Transform: Schema → Dart AST representation
  3. Generate: AST → Dart code using templates
  4. Format: Run dart format on generated files

Features

✅ Completed

  • OpenAPI 3.0 and Swagger 2.0 support - Full spec parsing and validation
  • Dio HTTP client generation - Complete with ApiClient wrapper
  • Freezed model generation - Immutable models with JSON serialization
  • Full null safety support - Leveraging Dart's sound null safety
  • Complex type handling - Nested objects, arrays, enums, oneOf/discriminators
  • All parameter types - Path, query, header, and body parameters
  • All HTTP methods - GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS
  • Reference resolution - Complete $ref and component resolution
  • Smart header consolidation - Automatic deduplication of header classes
  • Query parameter flattening - Complex objects serialized to query strings
  • Inline object extraction - Generates proper classes for inline schemas
  • Custom file naming - .f.dart extension for Freezed models
  • Automated CI/CD - GitHub Actions with semantic-release
  • Dual package support - Both CommonJS and ES Modules
  • Comprehensive test suite - 68 tests with 100% pass rate

🚧 In Progress

  • Watch mode - Auto-regeneration on spec changes
  • Additional HTTP clients - Retrofit, Chopper, HTTP package support
  • Mock data generation - Test fixtures from schemas
  • Performance optimization - For large OpenAPI specs

📋 Planned

  • State management integrations - Riverpod, GetX, Bloc
  • GraphQL support - Generate GraphQL clients
  • Custom interceptors - User-defined request/response handlers
  • Interactive CLI wizard - Guided configuration setup
  • VS Code extension - IDE integration
  • API documentation generation - From OpenAPI descriptions

Generated Code Features

ApiClient

The generated ApiClient class provides:

  • Automatic base URL configuration from OpenAPI spec
  • Built-in error handling with status code interceptors
  • Debug logging enabled via environment variable
  • Request/response timeout configuration
  • Default headers management
  • Custom interceptors support
  • Automatic retry logic for failed requests (configurable)

Service Classes

Each service class includes:

  • Type-safe methods for all endpoints
  • Automatic parameter handling (path, query, header, body)
  • Proper null safety with nullable parameters
  • Exception handling with custom ApiException
  • Response type mapping to generated models
  • Request cancellation support via CancelToken

Model Classes

Generated with Freezed for:

  • Immutability by default
  • JSON serialization/deserialization
  • CopyWith methods for easy updates
  • Equality operators and hashCode
  • toString implementations
  • Union types for oneOf/discriminated unions
  • Nested object support

Parameter Classes

Separate parameter classes for:

  • Query parameters with automatic null filtering
  • Header parameters with consolidation support
  • Type-safe parameter passing
  • Optional vs required distinction

Generated File Structure

lib/api/
├── api_client.dart           # Dio client wrapper with configuration
├── models/                   # Data models
│   ├── pet.f.dart           # Freezed model definition
│   ├── pet.f.freezed.dart   # Generated Freezed code
│   ├── pet.f.g.dart         # Generated JSON serialization
│   ├── params/              # Parameter classes
│   │   ├── list_pets_params.f.dart
│   │   └── index.dart
│   ├── headers/             # Header classes (if custom headers)
│   │   ├── api_key_header.f.dart
│   │   └── index.dart
│   └── index.dart           # Barrel export file
└── services/                 # API services
    ├── pets_service.dart    # Service with typed methods
    ├── api_exception.dart   # Custom exception handling
    └── index.dart           # Barrel export file

Configuration Options

Complete Configuration Reference

export default {
  apiName: {  // Name your API (can have multiple APIs in one config)
    input: './openapi.yaml',        // Path to OpenAPI spec file or URL
    output: {
      target: './lib/api',           // Output directory path
      
      mode: 'split',                 // File organization mode
      // Options: 'single' | 'split' | 'tags'
      // - 'single': All code in one file
      // - 'split': Separate files for models and services (default)
      // - 'tags': Group by OpenAPI tags
      
      client: 'dio',                 // HTTP client library (currently only 'dio' is supported)
      // Future options planned: 'http' | 'chopper' | 'retrofit'
      
      override: {
        generator: {
          freezed: true,             // Use Freezed for immutable models
          jsonSerializable: true,    // Add JSON serialization
          nullSafety: true,          // Enable null safety (default: true)
          partFiles: true,           // Generate part files (default: true)
          equatable: false           // Add Equatable support (default: false)
        },
        
        methodNaming: 'methodPath',  // Method naming strategy
        // Options: 'operationId' | 'methodPath'
        // - 'operationId': Use OpenAPI operationId
        // - 'methodPath': Generate from HTTP method + path (e.g., getUsersById)
        
        dio: {
          baseUrl: 'https://api.example.com',  // Override base URL
          interceptors: ['AuthInterceptor']    // Custom interceptors
        },
        
        // Custom header consolidation (new feature!)
        headers: {
          // Define reusable header classes
          definitions: {
            ApiKeyHeader: {
              fields: ['x-api-key'],
              required: ['x-api-key'],
              description: 'API key authentication'
            },
            CompanyHeaders: {
              fields: ['x-api-key', 'x-company-id', 'x-user-id'],
              required: ['x-api-key', 'x-company-id'],  // x-user-id is optional
              description: 'Company context headers'
            }
          },
          
          // Optional: Map specific endpoints to header classes
          mapping: {
            '/v1/health/*': 'ApiKeyHeader',
            '/v1/companies/**': 'CompanyHeaders'
          },
          
          // Enable smart header matching
          customMatch: true,           // Auto-match endpoints to header definitions
          matchStrategy: 'exact',      // 'exact' | 'subset' | 'fuzzy'
          customConsolidate: true,     // Auto-create shared classes for common patterns
          consolidationThreshold: 3    // Min endpoints to trigger consolidation
        }
      }
    },
    hooks: {
      afterAllFilesWrite: 'dart format .'  // Commands to run after generation
    }
  }
}

Configuration Examples

Basic Configuration

export default {
  petstore: {
    input: './petstore.yaml',
    output: {
      target: './lib/api'
    }
  }
}

Advanced Configuration

export default {
  myApi: {
    input: './tests/openapi.json',
    output: {
      target: './generated-api',
      mode: 'split',
      client: 'dio',
      override: {
        generator: {
          freezed: true,
          jsonSerializable: true,
          nullSafety: true
        },
        methodNaming: 'methodPath'  // getUsersById instead of getUserById
      }
    },
    hooks: {
      afterAllFilesWrite: [
        'dart format ./generated-api',
        'flutter pub run build_runner build'
      ]
    }
  }
}

Header Consolidation Configuration

Dorval can intelligently consolidate duplicate header classes across your API:

export default {
  api: {
    input: './openapi.json',
    output: {
      target: './lib/api',
      override: {
        headers: {
          // Define common header patterns
          definitions: {
            BasicAuth: {
              fields: ['x-api-key'],
              required: ['x-api-key']
            },
            UserContext: {
              fields: ['x-api-key', 'x-user-id', 'x-session-id'],
              required: ['x-api-key', 'x-user-id']  // x-session-id is optional
            },
            AdminContext: {
              fields: ['x-api-key', 'x-admin-id', 'x-permission-level'],
              required: ['x-api-key', 'x-admin-id', 'x-permission-level']
            }
          },
          
          // Enable automatic matching
          customMatch: true,
          matchStrategy: 'exact',  // Matches must have exact same fields and required status
          
          // Auto-consolidate common patterns
          customConsolidate: true,
          consolidationThreshold: 3  // Create shared class if 3+ endpoints use same headers
        }
      }
    }
  }
}

Benefits:

  • Reduces duplication: Instead of 85 header classes, you might only need 5-10
  • Order-independent: Headers with same fields but different order are recognized as identical
  • Required-aware: Distinguishes between required and optional fields
  • Smart naming: Auto-generates meaningful names for consolidated classes

Match Strategies:

  • exact: Fields and required status must match exactly (recommended)
  • subset: Endpoint headers can be a subset of the definition
  • fuzzy: Best-effort matching using similarity scoring

Examples

Sample projects are coming soon. For now, refer to the configuration examples above.

CLI Usage

# Generate from OpenAPI spec
dorval generate [options]

Options:
  -c, --config <path>   Path to config file (orval.config.ts)
  -i, --input <path>    OpenAPI spec file or URL
  -o, --output <path>   Output directory
  --client <type>       Client type (currently only 'dio')
  --help               Display help

# Watch mode (coming soon)
# dorval watch [options]

Troubleshooting

Common Issues

  1. "Cannot find module '@dorval/core'" error

    • Ensure you've installed dorval: npm install -g dorval
    • Or use npx: npx dorval generate -i spec.yaml -o ./lib
  2. Generated methods return correct model types

    • ✅ Fixed: Response types are now properly mapped to generated models
    • Models are generated with proper Freezed annotations
  3. Import errors in generated Dart code

    • Run flutter pub get after generation
    • Run flutter pub run build_runner build --delete-conflicting-outputs
    • Ensure all dependencies are in pubspec.yaml
  4. Duplicate header classes

    • ✅ Fixed: Smart header consolidation automatically reduces duplicates
    • Configure consolidation threshold in config file if needed

Development Roadmap

✅ Completed Features

  • Comprehensive test suite - 68 tests, 100% passing
  • CI/CD pipeline - GitHub Actions with automated testing
  • Semantic release - Automated versioning and npm publishing
  • Smart header consolidation - Reduces duplicate header classes
  • Proper response type mapping - Models correctly typed
  • OneOf with discriminator - Generates proper Freezed union types
  • Inline object handling - Generates nested classes instead of Map
  • Query parameter flattening - Complex objects properly serialized

🎯 High Priority Tasks

Testing & Quality

  • Expand test coverage
    • Unit tests for generators (✅ Done)
    • Integration tests (✅ Done)
    • More E2E tests with real-world OpenAPI specs
    • Performance benchmarks
    • Test coverage badges

Client Implementations

  • Custom client support

    • Allow users to provide custom HTTP client implementations
    • Support for custom request/response interceptors
    • Custom error handling strategies
    • Request retry logic configuration
  • Additional HTTP clients

    • Complete Retrofit implementation
    • Chopper client support
    • Built-in HTTP package support
    • GraphQL client generation

Core Features

  • Enhanced type handling

    • Better support for oneOf/anyOf/allOf
    • Discriminated unions with proper type checking
    • Recursive type definitions
    • Better handling of nullable types
  • Configuration improvements

    • Support for .dorvalrc configuration files
    • Environment-specific configurations
    • Global vs project-specific settings
    • Configuration validation and error messages

🚀 Medium Priority Features

Developer Experience

  • CLI enhancements

    • Interactive configuration wizard
    • Watch mode with hot reload
    • Diff view for regenerated files
    • Dry-run mode to preview changes
    • Progress indicators for large specs
  • IDE integrations

    • VS Code extension
    • IntelliJ/Android Studio plugin
    • Code completion for generated APIs
    • Inline documentation from OpenAPI descriptions

Flutter Ecosystem Integration

  • State management integrations

    • Riverpod providers generation
    • Bloc/Cubit pattern support
    • GetX controllers generation
    • Provider package integration
  • Testing utilities

    • Mock server generation
    • Fake data generators from schemas
    • Test fixtures from OpenAPI examples
    • Integration with mockito

🌟 Nice-to-Have Features

Advanced Generation

  • Code optimization

    • Tree-shaking unused models
    • Lazy loading for large APIs
    • Code splitting by feature/module
    • Minification options
  • Documentation

    • Generate API documentation website
    • Dartdoc comments from OpenAPI descriptions
    • Usage examples generation
    • Postman collection export

Community Features

  • Plugin system

    • Custom template support
    • Hook system for pre/post generation
    • Custom validators
    • Community plugin marketplace
  • Multi-language support

    • Generate both Dart and TypeScript from same spec
    • Shared model definitions
    • Cross-platform type safety

🐛 Known Issues to Fix

  1. Large file handling - Performance issues with very large OpenAPI specs (>10MB)
  2. Error messages - Improve error messages for invalid OpenAPI specs
  3. Windows compatibility - Path handling issues on Windows
  4. Watch mode - Not yet implemented
  5. Multiple client support - Currently only Dio is supported

📊 Performance Goals

  • Generate 1000+ endpoints in < 5 seconds
  • Support OpenAPI specs up to 50MB
  • Memory usage < 500MB for large specs
  • Incremental generation for faster updates

🔧 Technical Debt

  • Refactor parser to use visitor pattern
  • Improve template organization
  • Add proper logging system
  • Implement caching for parsed specs
  • Better error recovery during generation
  • Standardize naming conventions across codebase

📚 Documentation Needs

  • Complete API reference documentation
  • Video tutorials for common use cases
  • Migration guide from other generators
  • Best practices guide
  • Troubleshooting guide
  • Performance optimization guide

Contributing

We welcome contributions! See CONTRIBUTING.md for guidelines.

Want to help? Check the roadmap above and pick a task! We welcome contributions of all sizes.

License

MIT © 2025

Acknowledgments

Built on top of the excellent Orval project.

About

No description, website, or topics provided.

Resources

Contributing

Stars

Watchers

Forks

Packages

No packages published

Contributors 4

  •  
  •  
  •  
  •