Arri RPC

repository·master·Indexed 19 days ago

https://github.com/modiimedia/arri

A code-first RPC framework that automates the generation of type-safe clients from server-side definitions using a central App Definition. It supports building servers in Go—including standard HTTP procedures and Server-Sent Events (SSE) streams—and generating clients for languages such as Dart.

Tokens
72.7K
Snippets
244
Records
310
Agent score
64%

What's inside arri

  1. What is Arri RPC?

    master
    Arri RPC is a code-first RPC framework designed to provide type-safe clients automatically. Instead of manually writing clients for different platforms, you define your server code, and Arri generates the corresponding client implementations. This ensures that your client and server stay in sync without manual intervention.
  2. Overview of Arri Schema Interface

    master
    The Arri Schema Interface is an experimental universal validator interface designed for the Arri ecosystem. Inspired by standard-schema, its goal is to provide a standardized way for different validation libraries to implement an interface that is compatible with @arrirpc/server. This allows developers to use their preferred validation tools within an Arri RPC server implementation.
  3. Implement a custom Event type in Go

    master

    In Arri RPC, every procedure receives an Event type. This type is passed to every procedure and allows access to request-specific context. To use a custom event, you must implement the arri.Event interface, which requires Request() *http.Request and Writer() http.ResponseWriter methods.

    When initializing your app with arri.NewApp, you provide a factory function that creates your custom event using the incoming http.ResponseWriter and *http.Request.

    type MyCustomEvent struct {
        r *http.Request
        w http.ResponseWriter
    }
    
    func (e MyCustomEvent) Request() *http.Request {
        return e.r
    }
    
    func (e MyCustomEvent) Writer() http.ResponseWriter {
        return e.w
    }
    
    // In main:
    app := arri.NewApp(
        http.DefaultServeMux,
        arri.AppOptions[MyCustomEvent]{},
        func(w http.ResponseWriter, r *http.Request) (*MyCustomEvent, arri.RpcError) {
            return &MyCustomEvent{r: r, w: w}, nil
        },
    )
  4. Define messages using Go structs

    master

    In Arri RPC, all parameters and responses are defined as Go structs. Arri uses the reflect library to validate incoming requests against these structs and automatically converts them into Arri Type Definitions (ATD). This allows client generators to create type-safe clients automatically without manual work.

    Example struct definition:

    type User struct {
    	Id string
    	Name string
    	IsAdmin bool
    }
  5. How procedures map to endpoints

    master

    Arri uses a file-based routing system where procedure endpoints are derived from their file paths and names.

    Routing Rules:

    • Endpoints are converted to kebab-case (e.g., getStatus.rpc.ts becomes /get-status).
    • Directory structures are preserved in the URL (e.g., src/procedures/users/getUser.rpc.ts becomes /users/get-user).
    • All paths are relative to the rpcRoutePrefix option.

    HTTP Methods:

    • By default, all procedures use the post method.
    • You can override the method using the method option in defineRpc.
    • Supported methods: post, get, delete, patch, put.

    Special Note on GET requests: When using the get method, RPC parameters are mapped as query parameters and coerced using a.coerce from arri-validate. While scalar types are supported, arrays and nested objects are not supported for get methods.

    // procedures/users/getUser.rpc.ts
    export default defineRpc({
        method: 'get',
        // rest of config
    });
  6. Use Compiled Validators for high performance

    master

    For maximum performance, use a.compile(schema) to create a highly optimized validator. The compiled validator implements the standard-schema interface.

    Important Constraints:

    • Environment: Compiled validators use new Function() and will not work in a browser environment. They are intended for backend servers.
    • Performance: Compilation has overhead, so compile each schema only once.
    • Code Generation: To access the generated function bodies (e.g., for debugging or custom logic), pass true as the second argument to a.compile(schema, true). This is disabled by default to save memory.

    Compiled Methods:

    • validate(input)
    • parse(input)
    • parseUnsafe(input)
    • coerce(input)
    • coerceUnsafe(input)
    • serialize(input)
    • serializeUnsafe(input)
    const User = a.object({
        id: a.string(),
        email: a.nullable(a.string()),
        created: a.timestamp(),
    });
    
    const $$User = a.compile(User);
    
    $$User.validate(someInput);
    $$User.parse(someJson);
    $$User.coerce(someObject);
    $$User.serialize({ id: '1', email: null, created: new Date() });
    
    // Accessing generated code (requires second param true)
    const $$UserWithCode = a.compile(User, true);
    console.log($$UserWithCode.compiledCode.validate);
  7. Understand the Arri Definition File

    master

    The Arri server automatically generates a __definition.json file which serves as the API schema for all procedures and models. This schema is used by Arri to generate type-safe clients in multiple languages. It uses a superset of JSON Type Definition for models rather than standard JSON Schema to ensure better cross-language code generation.

    By default, you can view this schema at the /__definition endpoint while the server is running. Note that this endpoint is relative to your configured rpcRoutePrefix.

    {
        "procedures": {
            "sayHello": {
                "transport": "http",
                "path": "/say-hello",
                "method": "post",
                "params": "SayHelloParams",
                "response": "SayHelloResponse"
            }
        },
        "definitions": {
            "SayHelloParams": {
                "properties": {
                    "name": {
                        "type": "string"
                    }
                }
            }
        }
    }
  8. How to create tolerant Arri clients

    master

    To prevent crashes when the server's data model changes, Arri clients should be tolerant. Instead of failing validation, clients should assign fallback values for missing or unrecognized fields.

    Fallback Value Mapping:

    typefallback value
    any nullable typenull
    any optional typeundefined
    string""
    booleanfalse
    timestamp[Current Date and Time]
    floats0.0
    integers0
    enumsThe first specified enum value
    arrays[]
    records{}
    objectsAn instance of the object with all fields set to their fallback value
    discriminated unionAn instance of the first specified sub type with all fields set to their fallback value (skip recursive types)

    When to return an error instead of falling back:

    • Unable to connect to the server.
    • Server returned an error.
    • Server did not provide the correct content-type header.
    • Server response is not correctly formatted JSON.
  9. Use Metadata for cross-language code generation

    master

    Arri schemas support a metadata object passed as the second argument to schema definitions. This metadata is used by client generators to produce high-quality code in other languages.

    Supported Metadata Fields:

    • id: Used as the type name in generated clients.
    • description: Added as a documentation comment above the generated type.
    • isDeprecated: Marks generated code with the target language's deprecation annotation.
    const BookSchema = a.object(
        {
            title: a.string(),
            author: a.string(),
            publishDate: a.timestamp(),
        },
        {
            id: 'Book',
            description: 'This is a book',
        },
    );
  10. Understand Arri Generated Kotlin Models

    master

    All generated models are Kotlin data classes. They include built-in support for serialization and URL parameter conversion.

    Key Features:

    • Methods:
      • toJson(): String
      • toUrlQueryParams(): String
    • Factory Methods:
      • new()
      • fromJson(input: String)
      • fromJsonElement(input: JsonElement, instancePath: String)
    • Enums: All enums include a serialValue property.
    • Discriminator Schemas: Converted to Kotlin sealed classes.