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.
โก 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
- Installation
- Quick Start
- Core Concepts
- Usage Examples
- Advanced Features
- Configuration
- Mix Tasks
- API Reference
- Requirements
- Troubleshooting
- Contributing
- License
๐๏ธ Core Concepts
How it works
- Resource Definition: Define your Ash resources with attributes, relationships, and actions
- RPC Configuration: Expose specific actions through your domain's RPC configuration
- Type Generation: Run
mix ash_typescript.codegen
to generate TypeScript types - 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:
- TypeScript interfaces for all resources
- RPC client functions for each exposed action
- Field selection types for type-safe field specification
- Custom type imports for external TypeScript definitions
- Enum types for Ash enum types
- 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:
- Ensure generated types are up to date:
mix ash_typescript.codegen
- Check that all referenced resources are properly configured
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
- Documentation: hexdocs.pm/ash_typescript
- Issues: GitHub Issues
- Discussions: GitHub Discussions
- Ash Community: Ash Framework Discord
Built with โค๏ธ by the Ash Framework team
Generate once, type everywhere. Make your Elixir-TypeScript integration bulletproof.