Developer Experience Features

View Source

AshTypescript provides features to improve developer experience: namespace organization, JSDoc generation, and API manifest documentation.

Namespaces

Namespaces organize RPC actions into logical groups, improving discoverability in large codebases.

Configuration Levels

Namespaces can be configured at three levels with cascading precedence:

Domain Level - Default namespace for all resources in a domain:

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

  typescript_rpc do
    namespace :api  # All resources default to "api" namespace

    resource MyApp.Todo do
      rpc_action :list_todos, :read
    end
  end
end

Resource Level - Override for all actions on a specific resource:

typescript_rpc do
  namespace :api

  resource MyApp.Todo do
    namespace :todos  # Overrides domain namespace

    rpc_action :list_todos, :read
    rpc_action :create_todo, :create
  end

  resource MyApp.User do
    # Uses domain namespace "api"
    rpc_action :list_users, :read
  end
end

Action Level - Override for a specific action:

typescript_rpc do
  namespace :api

  resource MyApp.Todo do
    namespace :todos

    rpc_action :list_todos, :read  # Uses "todos"
    rpc_action :admin_list, :read, namespace: :admin  # Uses "admin"
  end
end

Precedence Order

Action namespace > Resource namespace > Domain namespace > nil

Generated Output

With namespaces enabled, the generated JSDoc includes the namespace:

/**
 * List all todos
 *
 * @ashActionType :read
 * @namespace todos
 */
export async function listTodos(...) { ... }

JSDoc Generation

Generated TypeScript functions include JSDoc comments that provide IDE discoverability through hover documentation and autocomplete hints.

Default Output

Every generated RPC function includes basic JSDoc:

/**
 * List all todos
 *
 * @ashActionType :read
 */
export async function listTodos(...) { ... }

Exposing Ash Metadata

Enable detailed Ash metadata in JSDoc for development:

config :ash_typescript,
  add_ash_internals_to_jsdoc: true

This adds internal references:

/**
 * List all todos
 *
 * @ashActionType :read
 * @ashResource MyApp.Todo
 * @ashAction :list_todos
 * @ashActionDef lib/my_app/resources/todo.ex
 * @rpcActionDef lib/my_app/domain.ex
 * @namespace todos
 */
export async function listTodos(...) { ... }

JSDoc Tags Reference

TagDescriptionWhen Shown
@ashActionTypeAsh action type (:read, :create, etc.)Always
@ashResourceFull Elixir module nameWhen add_ash_internals_to_jsdoc: true
@ashActionInternal Ash action nameWhen add_ash_internals_to_jsdoc: true
@ashActionDefSource file of Ash action definitionWhen add_ash_internals_to_jsdoc: true
@rpcActionDefSource file of RPC action configurationWhen add_ash_internals_to_jsdoc: true
@namespaceAction namespaceWhen namespace is configured
@seeRelated actionsWhen see: option is configured
@deprecatedDeprecation noticeWhen deprecated: option is configured

Source Path Prefix (Monorepos)

For monorepo setups where Elixir code is in a subdirectory:

config :ash_typescript,
  source_path_prefix: "backend"

Output:

/**
 * @ashActionDef backend/lib/my_app/resources/todo.ex
 * @rpcActionDef backend/lib/my_app/domain.ex
 */

Custom Descriptions

Override default descriptions per action:

typescript_rpc do
  resource MyApp.Todo do
    rpc_action :list_todos, :read,
      description: "Fetch all todos for the current user"
  end
end

When add_ash_internals_to_jsdoc: true, the Ash action's description is used as fallback if no RPC description is set.

Link related actions in JSDoc using the see option:

rpc_action :list_todos, :read,
  see: [:create_todo, :update_todo]

Output:

/**
 * @see createTodo
 * @see updateTodo
 */

Deprecation Notices

Mark actions as deprecated:

rpc_action :old_list, :read,
  deprecated: true

rpc_action :legacy_list, :read,
  deprecated: "Use listTodos instead"

Output:

/**
 * @deprecated
 */

/**
 * @deprecated Use listTodos instead
 */

Manifest Generation

Generate a Markdown manifest documenting all RPC actions for API documentation and developer onboarding.

Configuration

config :ash_typescript,
  manifest_file: "./docs/RPC_MANIFEST.md",
  add_ash_internals_to_manifest: true

Sample Output

# RPC Action Manifest

Generated: 2025-01-15

## Namespace: todos

### Todo

| Function | Action Type | Ash Action | Resource | Validation | Zod Schema | Channel |
|----------|-------------|------------|----------|------------|------------|---------|
| `listTodos` | read | `list` | `MyApp.Todo` | `validateListTodos` | `ListTodosInputSchema` | `listTodosChannel` |
| `createTodo` | create | `create` | `MyApp.Todo` | `validateCreateTodo` | `CreateTodoInputSchema` | `createTodoChannel` |

- **`listTodos`**: Fetch all todos for the current user
- **`createTodo`**: Create a new Todo | **See also:** `listTodos`

**Typed Queries:**
- `todoFields` -> `TodoFieldsResult`: Pre-defined field selection

Grouping Behavior

  • With namespaces: Actions grouped by namespace
  • Without namespaces: Actions grouped by domain

Controlling Manifest Content

The add_ash_internals_to_manifest config controls whether internal Ash details are shown:

SettingColumns Shown
falseFunction, Action Type
trueFunction, Action Type, Ash Action, Resource

Configuration Reference

All Developer Experience Options

config :ash_typescript,
  # JSDoc configuration
  add_ash_internals_to_jsdoc: false,  # Show Ash module/action details
  source_path_prefix: nil,            # Prefix for source paths (monorepos)

  # Manifest configuration
  manifest_file: nil,                 # Path to generate manifest (nil = disabled)
  add_ash_internals_to_manifest: false  # Show Ash details in manifest

Development vs Production

Development Configuration:

# config/dev.exs
config :ash_typescript,
  add_ash_internals_to_jsdoc: true,
  add_ash_internals_to_manifest: true,
  manifest_file: "./docs/RPC_MANIFEST.md"

Production Configuration:

# config/prod.exs
config :ash_typescript,
  add_ash_internals_to_jsdoc: false,
  add_ash_internals_to_manifest: false

Common Patterns

Namespace Organization by Feature

typescript_rpc do
  resource MyApp.Todo do
    namespace :todos
    rpc_action :list_todos, :read
    rpc_action :create_todo, :create
  end

  resource MyApp.User do
    namespace :users
    rpc_action :list_users, :read
    rpc_action :get_current_user, :get_current, namespace: :auth
  end

  resource MyApp.Session do
    namespace :auth
    rpc_action :login, :create
    rpc_action :logout, :destroy
  end
end

Development-Only Metadata

# config/config.exs
config :ash_typescript,
  add_ash_internals_to_jsdoc: Mix.env() == :dev,
  add_ash_internals_to_manifest: Mix.env() == :dev

Monorepo Setup

# backend/config/config.exs
config :ash_typescript,
  source_path_prefix: "backend",
  output_file: "../frontend/src/generated/ash_rpc.ts"

Next Steps