Revali: Annotate your Dart

website·Indexed 31 days ago

https://www.revali.dev

A Dart tool that allows developers to annotate code to generate the remaining API implementation.

Tokens
1.2K
Snippets
6
Records
7
Agent score
8%

What's inside Revali

  1. Overview of Revali code generation

    Revali reads annotations on Dart classes to generate several production-ready artifacts, ensuring the client, documentation, and deployment stay in sync with the business logic. Generated artifacts include:

    • Server: Routing, binding, validation, and response pipeline.
    • Dart Client (revali_client): A type-safe client with matching method names and types for use in Flutter or Dart apps.
    • OpenAPI Spec (revali_swagger): A valid OpenAPI 3.0.3 document in YAML and JSON inferred from parameters and return types.
    • Dockerfile (revali_docker): A multi-stage build that AOT-compiles the server to a native executable on Alpine Linux.
  2. Quickstart: Install and run Revali

    To get a Revali server running, add the necessary packages, define a controller with annotations, and start the development server.
    # 1. Add packages
    dart pub add revali --dev
    dart pub add revali_router
    
    # 2. Run the dev server
    dart run revali dev
    // 3. Example Controller
    @Controller('hello')
    class HelloController {
      const HelloController();
    
      @Get()
      String world() => 'Hello, world!';
    }
  3. Implement Request Lifecycle Components

    Create classes that implement LifecycleComponent to hook into the request pipeline. The return type of the method determines its role in the lifecycle:

    1. Wrapper: Returns WrapperResult (timing, tracing).
    2. Middleware: Returns MiddlewareResult (request modification).
    3. Guard: Returns GuardResult (access control/blocking).
    4. Interceptor (Pre): Returns InterceptorPreResult (last look before handler).
    5. Endpoint: The actual controller method.
    6. Interceptor (Post): Returns InterceptorPostResult (response shaping).
    class Session implements LifecycleComponent {
      const Session(this.tokens);
      final TokenService tokens;
    
      // A Guard: can stop the request
      Future<GuardResult> authenticated(
        @Header('authorization') String? auth,
      ) async {
        if (await tokens.valid(auth)) {
          return const GuardResult.pass();
        }
        return const GuardResult.block(statusCode: 401);
      }
    
      // An Interceptor: runs after the handler
      InterceptorPostResult timing(Context context) {
        context.response.headers.add('x-served-by', 'revali');
        return const InterceptorPostResult.next();
      }
    }
  4. Implement WebSockets and SSE

    Revali provides built-in support for real-time communication via @WebSocket and @SSE annotations.
    // WebSocket Example
    @Controller('chat')
    class ChatController {
      const ChatController();
    
      @WebSocket('messages')
      String handle(@Body() String message) {
        return 'Echo: $message';
      }
    }
    
    // Server-Sent Events (SSE) Example
    @Controller('events')
    class EventController {
      const EventController();
    
      @SSE('live')
      Stream<String> live(CleanUp cleanUp) async* {
        cleanUp.add(feed.dispose);
        yield* feed.stream;
      }
    }
  5. Define API endpoints using Controllers

    Use the @Controller annotation to define a base path and method-level annotations (@Get, @Post, etc.) to define endpoints. You can specify path parameters, status codes, and bind request data using @Param, @Body, @Query, @Header, and @Cookie.
    @Controller('users')
    class UsersController {
      const UsersController(this.users);
      final UserService users;
    
      @Get()
      Future<List<User>> all() => users.all();
    
      @Get(':id')
      Future<User> byId(@Param() String id) {
        return users.find(id);
      }
    
      @Post()
      @StatusCode(201)
      Future<User> create(@Body() NewUser body) {
        return users.create(body);
      }
    
      @Get('search')
      Future<Page<User>> search(
        @Query() String q,
        @Query('page') int page,
        @Header('accept-language') String? locale,
        @Cookie('session') String? session,
      ) async {
        return users.search(q, page: page);
      }
    }
  6. Revali CLI Commands Reference

    Revali provides several CLI commands for development, debugging, and scaffolding.
    # Start development server with hot reload
    dart run revali dev
    
    # Print the route table from the generated manifest
    dart run revali routes
    
    # Check kernel, constructs, and generated output for issues
    dart run revali doctor
    
    # Generate controllers, apps, pipes, observers, and lifecycle components
    revali create
  7. Configure Dependency Injection in Revali

    Register dependencies within an AppConfig class by overriding the configureDependencies method. Revali supports singletons, lazy singletons, and factories, which are automatically injected into controllers and lifecycle components.
    @App()
    final class MainApp extends AppConfig {
      const MainApp() : super(port: 8080);
    
      @override
      Future<void> configureDependencies(DI di) async {
        di.registerSingleton(await Database.connect());
        di.registerLazySingleton<UserService>(UserServiceImpl.new);
        di.registerFactory<Mailer>(SmtpMailer.new);
      }
    }