Typelizer

repository·main·Indexed 42 days ago

https://github.com/skryukov/typelizer

A tool for Ruby on Rails applications that automatically generates TypeScript types, route helpers, and OpenAPI schemas from serializers. It supports Alba, ActiveModel::Serializer, Oj::Serializer, and Panko::Serializer to ensure type safety between the Rails backend and frontend consumers.

Tokens
19.4K
Snippets
80
Records
101
Agent score
50%

What's inside Typelizer

  1. Overview of Typelizer features

    main

    Typelizer synchronizes Ruby on Rails backends with frontend types through several key capabilities:

    • TypeScript Interface Generation: Automatically creates interfaces from your serializers.
    • Type-safe Route Helpers: Generates helpers derived from your Rails routes.
    • Serializer Support: Compatible with Alba, ActiveModel::Serializer, Oj::Serializer, and Panko::Serializer.
    • OpenAPI Support: Generates OpenAPI 3.0/3.1 schemas.
    • Flexible Output: Supports multiple output writers with layered configuration.
    • Development Workflow: Includes file watching for automatic regeneration during development.
  2. Understand the Generated Route File Structure

    main

    Typelizer generates a structured set of files in your output directory:

    • {Controller}Controller.ts: Contains route helper methods for a specific Rails controller. The default export is an object where keys are action names.
    • index.ts: A barrel file that re-exports controller namespaces and provides named route shortcuts.
    • runtime.ts: Contains shared types and the buildUrl utility. Do not edit this file directly.

    Namespaced controllers (e.g., Admin::UsersController) are placed in subdirectories (e.g., Admin/UsersController.ts). Files include a fingerprint comment; unchanged files are skipped during regeneration, and stale files are automatically deleted.

  3. Use Alba Traits with Typelizer

    main

    Typelizer generates separate TypeScript types for each Alba trait. For a trait named :detailed on UserResource, it generates a type named UserDetailedTrait.

    class UserResource < ApplicationResource
      attributes :id, :name
    
      trait :detailed do
        attributes :email, :created_at
      end
    end

    // Generates:

    type UserDetailedTrait = {
      email: string;
      created_at: string;
    }
  4. How the multi-writer system works

    main

    The multi-writer system allows you to generate multiple, distinct TypeScript outputs from the same serializers. Each output is managed by a named writer with its own isolated configuration. This is useful for generating different naming conventions (e.g., snake_case and camelCase) or directing outputs to different directories for different parts of your application.

    Typelizer.configure do |config|
      # Define a specific writer
      config.writer(:camel_case) do |c|
        c.output_dir = "app/javascript/types/camel_case"
      end
    end
  5. Handle conditional attributes in AMS

    main

    Attributes defined with if: or unless: conditions in your AMS serializer are automatically treated as optional properties in the generated TypeScript types (using the ? optional marker).

    class UserSerializer < ActiveModel::Serializer
      attributes :id, :name
      attribute :email, if: :admin?
    end
  6. Access Routes via Index Exports

    main

    The index.ts barrel file provides two ways to access routes:

    1. Namespace exports: Access all routes for a specific controller.

      import { users, adminUsers } from './routes';
      users.index();
      adminUsers.show(1);
    2. Named route exports: Shortcuts for specific routes that have a defined Rails route name.

      import { root, editUser } from './routes';
      root();
      editUser(5);

    Note: If a named export collides with a namespace export of the same name, the named export is skipped.

  7. Handle Nullable or Optional Panko Associations

    main
    By default, Panko associations are generated as required (non-optional and non-nullable) in the resulting types. If you require an association to be marked as optional or nullable, you must manually override its type using the typelize method.
  8. Handle Nested Attributes in Alba

    main

    Alba's nested blocks are converted into inline object types in the generated TypeScript. This allows for deeply nested structures to be represented as nested type definitions.

    class UserResource < ApplicationResource
      attributes :id, :name
    
      nested :details do
        attributes :role
        nested :timestamps do
          attributes :created_at, :updated_at
        end
      end
    end

    // Generates:

    export type User = {
      id: number;
      name: string;
      details: {
        role: string;
        timestamps: {
          created_at: string;
          updated_at: string;
        };
      };
    }
  9. Understand Typelizer Configuration Layers

    main

    Typelizer uses a hierarchical resolution system for settings. When multiple settings conflict, the layer with the higher number takes precedence:

    1. Library Defaults: Built-in gem values.
    2. Global Settings: Application-wide settings in a Typelizer.configure block.
    3. Writer-Specific Settings: Settings defined within a config.writer(:name) block.
    4. Per-Serializer Overrides: Settings defined using typelizer_config inside a specific serializer class (Highest priority).
  10. Handle Key Transformation in Alba

    main

    Typelizer respects Alba's transform_keys configuration. When you use transformations like :lower_camel, Typelizer will transform the property names in the generated TypeScript types to match the transformed keys.

    class PostResource < ApplicationResource
      transform_keys :lower_camel
    
      attributes :id, :title, :created_at
    end

    // Generates:

    export type Post = {
      id: number;
      title: string;
      createdAt: string;
    }
  11. Understand writer inheritance rules

    main

    Writers follow a specific hierarchy for configuration inheritance:

    1. Global Settings: Settings applied directly to config (e.g., config.comments = true) are inherited by all writers.
    2. Writer-to-Writer Inheritance: By using the from: :other_writer option, a new writer clones the complete configuration of the specified writer.
    3. The :default Exception: Settings defined inside a config.writer(:default) block are not inherited by other writers. They are specific to the :default writer only.

    Inheritance Order Summary:

    • Global $\rightarrow$ All Writers (including :default and custom ones)
    • :default block $\rightarrow$ Only :default writer (not inherited)
    • from: :other_writer $\rightarrow$ Clones :other_writer
  12. Respect AMS key transformation settings

    main

    Typelizer respects the key_transform configuration set in your ActiveModelSerializers configuration. For example, if you set the transform to :camel_lower, the generated TypeScript keys will follow that convention.

    # config/initializers/ams.rb
    ActiveModelSerializers.config.key_transform = :camel_lower