π₯ 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.
The errors field in all action responses is now always of type AshRpcError[], providing more consistent error handling:
// β Before (0.7.x) - errors could be different types
const result = await createTodo({...});
if (!result.success) {
// errors could be various shapes
console.log(result.errors); // Type was inconsistent
}
// β
After (0.8.0) - errors is always AshRpcError[]
const result = await createTodo({...});
if (!result.success) {
// errors is always AshRpcError[]
result.errors.forEach(error => {
console.log(error.message, error.field, error.code);
});
}
export type AshRpcError = {
/** Machine-readable error type (e.g., "invalid_changes", "not_found") */
type: string;
/** Full error message (may contain template variables like %{key}) */
message: string;
/** Concise version of the message */
shortMessage: string;
/** Variables to interpolate into the message template */
vars: Record<string, any>;
/** List of affected field names (for field-level errors) */
fields: string[];
/** Path to the error location in the data structure */
path: string[];
/** Optional map with extra details (e.g., suggestions, hints) */
details?: Record<string, any>;
}Type inference for certain composite types has improved after some internal refactoring. Earlier, the type-checking allowed users to select some composite fields using the string syntax, which would return the entire value.
Now however, since AshTypescript is able to more accurately see that a field is a composite type, you may experience that explicit field selection is now required in certain places where a string value earlier was okay.
// β Before (0.7.x) - string syntax worked where fields should really be required
const todos = await listTodos({
fields: ["id", "title", "item"] // β "item" is a composite type
});
// β
After (0.8.0) - must specify fields for composite types
const todos = await listTodos({
fields: ["id", "title", { item: ["id", "name", "description"] }]
});
**Migration Guide:**
1. Update error handling code to expect `AshRpcError[]` for the `errors` field
2. Replace string field names with object syntax for any composite types (embedded resources, union types, etc.)
3. Run TypeScript compilation after upgrading to catch any remaining type errors
## β¨ 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
- **π‘ Phoenix Channel support** - Generate channel-based RPC functions for real-time applications
- **πͺ Lifecycle hooks** - Inject custom logic before/after requests (auth, logging, telemetry, error tracking)
- **π’ Multitenancy ready** - Automatic tenant parameter handling
- **π¦ Advanced type support** - Enums, unions, embedded resources, and calculations
- **π Action metadata support** - Attach and retrieve additional context with action results
- **π§ Highly configurable** - Custom endpoints, formatting, and output options
- **π§ͺ Runtime validation** - Zod schemas for runtime type checking and form validation
- **π Auto-generated filters** - Type-safe filtering with comprehensive operator support
- **π Form validation** - Client-side validation functions for all actions
- **π― Typed queries** - Pre-configured queries for SSR and optimized data fetching
- **π¨ Flexible field formatting** - Separate input/output formatters (camelCase, snake_case, etc.)
- **π Custom HTTP clients** - Support for custom fetch functions and request options (axios, interceptors, etc.)
- **π·οΈ Field/argument name mapping** - Map invalid TypeScript identifiers to valid names
## β‘ Quick Start
**Get up and running in under 5 minutes:**
```bash
# Basic installation
mix igniter.install ash_typescript
# Full-stack Phoenix + React setup
mix igniter.install ash_typescript --framework reactdefmodule MyApp.Todo do
use Ash.Resource,
domain: MyApp.Domain,
extensions: [AshTypescript.Resource]
typescript do
type_name "Todo"
end
attributes do
uuid_primary_key :id
attribute :title, :string, allow_nil?: false
attribute :completed, :boolean, default: false
end
enddefmodule MyApp.Domain do
use Ash.Domain, extensions: [AshTypescript.Rpc]
typescript_rpc do
resource MyApp.Todo do
rpc_action :list_todos, :read
rpc_action :create_todo, :create
rpc_action :get_todo, :get
end
end
endmix ash.codegen --devimport { 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.
π For complete setup instructions, see the Getting Started Guide
- Getting Started - Complete installation and setup guide
- React Setup - Full Phoenix + React + TypeScript integration
- Basic CRUD Operations - Create, read, update, delete patterns
- Field Selection - Advanced field selection and nested relationships
- Error Handling - Comprehensive error handling strategies
- Custom Fetch Functions - Using custom HTTP clients and request options
- Lifecycle Hooks - Inject custom logic (auth, logging, telemetry)
- Phoenix Channels - Real-time WebSocket-based RPC actions
- Embedded Resources - Working with embedded data structures
- Union Types - Type-safe union type handling
- Multitenancy - Multi-tenant application support
- Action Metadata - Attach and retrieve action metadata
- Form Validation - Client-side validation functions
- Zod Schemas - Runtime validation with Zod
- Configuration - Complete configuration options
- Mix Tasks - Available Mix tasks and commands
- Troubleshooting - Common issues and solutions
AshTypescript bridges the gap between Elixir and TypeScript by automatically generating type-safe client code:
- Resource Definition - Define Ash resources with attributes, relationships, and actions
- RPC Configuration - Expose specific actions through your domain's RPC configuration
- Type Generation - Run
mix ash.codegento generate TypeScript types and RPC functions - Frontend Integration - Import and use fully type-safe client functions in your TypeScript code
- Compile-time validation - TypeScript compiler catches API misuse before runtime
- Autocomplete support - Full IntelliSense for all resource fields and actions
- Refactoring safety - Rename fields in Elixir, get TypeScript errors immediately
- Living documentation - Generated types serve as up-to-date API documentation
Check out the AshTypescript Demo by Christian Alexander featuring:
- Complete Phoenix + React + TypeScript integration
- TanStack Query for data fetching
- TanStack Table for data display
- Best practices and patterns
- Elixir 1.15 or later
- Ash 3.0 or later
- Phoenix (for RPC controller integration)
- Node.js 16+ (for TypeScript)
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes with tests
- Ensure all tests pass (
mix test) - Run code formatter (
mix format) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Please ensure:
- All tests pass
- Code is formatted with
mix format - Documentation is updated for new features
- Commits follow conventional commit format
This project is licensed under the MIT License - see the MIT.txt file for details.
- Documentation: https://hexdocs.pm/ash_typescript
- GitHub Issues: https://github.com/ash-project/ash_typescript/issues
- Discord: Ash Framework Discord
- Forum: Elixir Forum - Ash Framework
