AshTypescript

View Source

๐Ÿ”ฅ Automatic TypeScript type generation for Ash resources and actions

Generate type-safe TypeScript clients directly from your Elixir Ash resources, ensuring end-to-end type safety between your backend and frontend. Never write API types manually again.

Hex.pm Documentation License

โšก Quick Start

Get up and running in under 5 minutes:

1. Installation

Add to your mix.exs:

def deps do
  [
    {:ash_typescript, "~> 0.1.0"}
  ]
end

2. Configure your domain

defmodule MyApp.Domain do
  use Ash.Domain, extensions: [AshTypescript.Rpc]

  rpc do
    resource MyApp.Todo do
      rpc_action :list_todos, :read
      rpc_action :create_todo, :create
      rpc_action :get_todo, :get
    end
  end

  resources do
    resource MyApp.Todo
  end
end

3. Generate TypeScript types

mix ash_typescript.codegen --output "assets/js/ash_rpc.ts"

4. Use in your frontend

import { listTodos, createTodo } from './ash_rpc';

// โœ… Fully type-safe API calls
const todos = await listTodos({
  fields: ["id", "title", "completed"],
  filter: { completed: false }
});

const newTodo = await createTodo({
  fields: ["id", "title", { user: ["name", "email"] }],
  input: { title: "Learn AshTypescript", priority: "high" }
});

๐ŸŽ‰ That's it! Your TypeScript frontend now has compile-time type safety for your Elixir backend.

๐Ÿš€ Features

  • ๐Ÿ”ฅ Zero-config TypeScript generation - Automatically generates types from Ash resources
  • ๐Ÿ›ก๏ธ End-to-end type safety - Catch integration errors at compile time, not runtime
  • โšก Smart field selection - Request only needed fields with full type inference
  • ๐ŸŽฏ RPC client generation - Type-safe function calls for all action types
  • ๐Ÿข Multitenancy ready - Automatic tenant parameter handling
  • ๐Ÿ“ฆ Advanced type support - Enums, unions, embedded resources, and calculations
  • ๐Ÿ”ง Highly configurable - Custom endpoints, formatting, and output options
  • ๐Ÿงช Runtime validation - Zod schemas for runtime type checking (coming soon)

๐Ÿ“š Table of Contents

๐Ÿ—๏ธ Core Concepts

How it works

  1. Resource Definition: Define your Ash resources with attributes, relationships, and actions
  2. RPC Configuration: Expose specific actions through your domain's RPC configuration
  3. Type Generation: Run mix ash_typescript.codegen to generate TypeScript types
  4. Frontend Integration: Import and use fully type-safe client functions

Type Safety Benefits

  • Compile-time validation - TypeScript compiler catches API misuse
  • Autocomplete support - Full IntelliSense for all resource fields and actions
  • Refactoring safety - Rename fields in Elixir, get TypeScript errors immediately
  • Documentation - Generated types serve as living API documentation

๐Ÿ’ก Usage Examples

Basic CRUD Operations

import { listTodos, getTodo, createTodo, updateTodo, destroyTodo } from './ash_rpc';

// List todos with field selection
const todos = await listTodos({
  fields: ["id", "title", "completed", "priority"],
  filter: { status: "active" },
  sort: "-priority,+createdAt"
});

// Get single todo with relationships
const todo = await getTodo({
  fields: ["id", "title", { user: ["name", "email"] }],
  id: "todo-123"
});

// Create new todo
const newTodo = await createTodo({
  fields: ["id", "title", "created_at"],
  input: {
    title: "Learn AshTypescript",
    priority: "high",
    due_date: "2024-01-01"
  }
});

Advanced Field Selection

// Complex nested field selection
const todoWithDetails = await getTodo({
  fields: [
    "id", "title", "description",
    { user: ["name", "email", "avatar_url"] },
    { comments: ["id", "text", { author: ["name"] }] },
    { tags: ["name", "color"] }
  ],
  id: "todo-123"
});

// Calculations with arguments
const todoWithCalc = await getTodo({
  fields: [
    "id", "title",
    {
      "priority_score": {
        "args": { "multiplier": 2 },
        "fields": ["score", "rank"]
      }
    }
  ],
  id: "todo-123"
});

Error Handling

try {
  const todo = await createTodo({
    fields: ["id", "title"],
    input: { title: "New Todo" }
  });
} catch (error) {
  // Handle validation errors, network errors, etc.
  console.error('Failed to create todo:', error);
}

Custom Headers and Authentication

import { listTodos, buildCSRFHeaders } from './ash_rpc';

// With CSRF protection
const todos = await listTodos({
  fields: ["id", "title"],
  headers: buildCSRFHeaders()
});

// With custom authentication
const todos = await listTodos({
  fields: ["id", "title"],
  headers: {
    "Authorization": "Bearer your-token-here",
    "X-Custom-Header": "value"
  }
});

๐Ÿ”ง Advanced Features

Embedded Resources

Full support for embedded resources with type safety:

# In your resource
attribute :metadata, MyApp.TodoMetadata do
  public? true
end
// TypeScript usage
const todo = await getTodo({
  fields: [
    "id", "title",
    { metadata: ["priority", "tags", "custom_fields"] }
  ],
  id: "todo-123"
});

Union Types

Support for Ash union types with selective field access:

# In your resource
attribute :content, :union do
  constraints types: [
    text: [type: :string],
    checklist: [type: MyApp.ChecklistContent]
  ]
end
// TypeScript usage with union field selection
const todo = await getTodo({
  fields: [
    "id", "title",
    { content: ["text", { checklist: ["items", "completed_count"] }] }
  ],
  id: "todo-123"
});

Multitenancy Support

Automatic tenant parameter handling for multitenant resources:

# Configuration
config :ash_typescript, require_tenant_parameters: true
// Tenant parameters automatically added to function signatures
const todos = await listTodos({
  fields: ["id", "title"],
  tenant: "org-123"
});

Calculations and Aggregates

Full support for Ash calculations with type inference:

# In your resource
calculations do
  calculate :full_name, :string do
    expr(first_name <> " " <> last_name)
  end
end
// TypeScript usage
const users = await listUsers({
  fields: ["id", "first_name", "last_name", "full_name"]
});

โš™๏ธ Configuration

Application Configuration

# config/config.exs
config :ash_typescript,
  output_file: "assets/js/ash_rpc.ts",
  run_endpoint: "/rpc/run",
  validate_endpoint: "/rpc/validate",
  require_tenant_parameters: false,
  import_into_generated: [
    %{
      import_name: "CustomTypes",
      file: "./customTypes"
    }
  ]

Domain Configuration

defmodule MyApp.Domain do
  use Ash.Domain, extensions: [AshTypescript.Rpc]

  rpc do
    resource MyApp.Todo do
      # Standard CRUD actions
      rpc_action :list_todos, :read
      rpc_action :get_todo, :get
      rpc_action :create_todo, :create
      rpc_action :update_todo, :update
      rpc_action :destroy_todo, :destroy

      # Custom actions
      rpc_action :complete_todo, :complete
      rpc_action :archive_todo, :archive
    end

    resource MyApp.User do
      rpc_action :list_users, :read
      rpc_action :get_user, :get
    end
  end
end

Field Formatting

Customize how field names are formatted in generated TypeScript:

# Default: snake_case โ†’ camelCase
# user_name โ†’ userName
# created_at โ†’ createdAt

Custom Types

Create custom Ash types with TypeScript integration:

# 1. Create custom type in Elixir
defmodule MyApp.PriorityScore do
  use Ash.Type

  def storage_type(_), do: :integer
  def cast_input(value, _) when is_integer(value) and value >= 1 and value <= 100, do: {:ok, value}
  def cast_input(_, _), do: {:error, "must be integer 1-100"}
  def cast_stored(value, _), do: {:ok, value}
  def dump_to_native(value, _), do: {:ok, value}
  def apply_constraints(value, _), do: {:ok, value}

  # AshTypescript integration
  def typescript_type_name, do: "CustomTypes.PriorityScore"
end
// 2. Create TypeScript type definitions in customTypes.ts
export type PriorityScore = number;

export type ColorPalette = {
  primary: string;
  secondary: string;
  accent: string;
};
# 3. Use in your resources
defmodule MyApp.Todo do
  use Ash.Resource, domain: MyApp.Domain

  attributes do
    uuid_primary_key :id
    attribute :title, :string, public?: true
    attribute :priority_score, MyApp.PriorityScore, public?: true
  end
end

The generated TypeScript will automatically include your custom types:

// Generated TypeScript includes imports
import * as CustomTypes from "./customTypes";

// Your resource types use the custom types
interface TodoFieldsSchema {
  id: string;
  title: string;
  priorityScore?: CustomTypes.PriorityScore | null;
}

๐Ÿ› ๏ธ Mix Tasks

mix ash_typescript.codegen

Generate TypeScript types and RPC clients.

Options:

  • --output - Output file path (default: assets/js/ash_rpc.ts)
  • --run_endpoint - RPC run endpoint (default: /rpc/run)
  • --validate_endpoint - RPC validate endpoint (default: /rpc/validate)
  • --check - Check if generated code is up to date (useful for CI)
  • --dry_run - Print generated code without writing to file

Examples:

# Basic generation
mix ash_typescript.codegen

# Custom output location
mix ash_typescript.codegen --output "frontend/src/api/ash.ts"

# Custom RPC endpoints
mix ash_typescript.codegen \
  --run_endpoint "/api/rpc/run" \
  --validate_endpoint "/api/rpc/validate"

# Check if generated code is up to date (CI usage)
mix ash_typescript.codegen --check

๐Ÿ“– API Reference

Generated Code Structure

AshTypescript generates:

  1. TypeScript interfaces for all resources
  2. RPC client functions for each exposed action
  3. Field selection types for type-safe field specification
  4. Custom type imports for external TypeScript definitions
  5. Enum types for Ash enum types
  6. Utility functions for headers and validation

Generated Functions

For each rpc_action in your domain, AshTypescript generates:

// For rpc_action :list_todos, :read
function listTodos(params: {
  fields: TodoFields;
  filter?: TodoFilter;
  sort?: TodoSort;
  headers?: Record<string, string>;
}): Promise<Todo[]>;

// For rpc_action :create_todo, :create
function createTodo(params: {
  fields: TodoFields;
  input: TodoInput;
  headers?: Record<string, string>;
}): Promise<Todo>;

Utility Functions

// CSRF protection for Phoenix applications
function getPhoenixCSRFToken(): string | null;
function buildCSRFHeaders(): Record<string, string>;

๐Ÿ“‹ Requirements

  • Elixir ~> 1.15
  • Ash ~> 3.5
  • AshPhoenix ~> 2.0 (for RPC endpoints)

๐Ÿ› Troubleshooting

Common Issues

TypeScript compilation errors:

RPC endpoint errors:

  • Verify AshPhoenix RPC endpoints are configured in your router
  • Check that actions are properly exposed in domain RPC configuration

Type inference issues:

  • Ensure all attributes are marked as public? true
  • Check that relationships are properly defined

Debug Commands

# Check generated output without writing
mix ash_typescript.codegen --dry_run

# Validate TypeScript compilation
cd assets/js && npx tsc --noEmit

# Check for updates
mix ash_typescript.codegen --check

๐Ÿค Contributing

Development Setup

# Clone the repository
git clone https://github.com/ash-project/ash_typescript.git
cd ash_typescript

# Install dependencies
mix deps.get

# Run tests
mix test

# Generate test types
mix test.codegen

๐Ÿ“„ License

This project is licensed under the MIT License - see the LICENSE file for details.

๐Ÿ†˜ Support


Built with โค๏ธ by the Ash Framework team

Generate once, type everywhere. Make your Elixir-TypeScript integration bulletproof.