Skip to content

fix(codegen): unwrap NewTypes before classifying return types for field selection#60

Merged
Torkan merged 1 commit intoash-project:mainfrom
mike1o1:59-fix-newtype-fields
Mar 28, 2026
Merged

fix(codegen): unwrap NewTypes before classifying return types for field selection#60
Torkan merged 1 commit intoash-project:mainfrom
mike1o1:59-fix-newtype-fields

Conversation

@mike1o1
Copy link
Copy Markdown
Contributor

@mike1o1 mike1o1 commented Mar 28, 2026

tl;dr

Fix missing call to unwrap new types, so fields get properly added/generated. Basically a one-line change - everything else is just tests.

Closes #59

Summary

When a generic action returns a NewType that wraps :map with field constraints (e.g., {:array, MyApp.Types.MySuggestion}), the generated TypeScript function is missing the fields parameter. However, the runtime RPC pipeline correctly requires field selection, resulting in a "requires_field_selection" error at runtime.

Root Cause

ActionIntrospection.check_action_returns/1 in action_introspection.ex does not call Introspection.unwrap_new_type/2 before classifying the return type. The NewType module (e.g., MyApp.Types.MySuggestion) is not in @field_constrained_types ([Ash.Type.Map, Ash.Type.Keyword, Ash.Type.Tuple]), so classify_return_type/2 returns {:error, :not_field_selectable_type}.

Meanwhile, the runtime field processing in field_selector.ex (line 116) does properly unwrap NewTypes before classification, sees the underlying Ash.Type.Map with field constraints, and demands field selection — creating the mismatch.

How to Reproduce

1. Define a NewType wrapping a map with field constraints
defmodule MyApp.Types.MySuggestion do
  use Ash.Type.NewType,
    subtype_of: :map,
    constraints: [
      fields: [
        name: [type: :string, allow_nil?: false],
        category: [type: :string, allow_nil?: true],
        score: [type: :integer, allow_nil?: false]
      ]
    ]
end
2. Define a generic action returning an array of this type
defmodule MyApp.MyResource do
  use Ash.Resource, ...

  actions do
    action :search, {:array, MyApp.Types.MySuggestion} do
      argument :query, :string, default: ""

      run fn input, _context ->
        {:ok, [%{name: "test", category: nil, score: 1}]}
      end
    end
  end
end
3. Expose via typescript_rpc
typescript_rpc do
  resource MyApp.MyResource do
    rpc_action :search, :search
  end
end
4. Run codegen and observe

The generated TypeScript function will NOT include a fields parameter:

// Generated — missing `fields`
export async function search(config: {
  input?: SearchInput;
  // No fields parameter here!
}): Promise<SearchResult> { ... }
5. Call the RPC and get runtime error
{
  "type": "requires_field_selection",
  "message": "%{field_type} requires field selection",
  "shortMessage": "Field selection required",
  "vars": { "fieldType": "Field_constrained_type" }
}

Proposed Fix

Add Introspection.unwrap_new_type/2 call in check_action_returns/1 before classification:

# lib/ash_typescript/rpc/codegen/helpers/action_introspection.ex

defp check_action_returns(action) do
  {base_type, constraints, is_array} = unwrap_return_type(action)

  # FIX: Unwrap NewTypes before classification, matching field_selector.ex behavior
  {unwrapped_type, unwrapped_constraints} =
    Introspection.unwrap_new_type(base_type, constraints)

  case classify_return_type(unwrapped_type, unwrapped_constraints) do
    {:resource, resource} ->
      type = if is_array, do: :array_of_resource, else: :resource
      {:ok, type, resource}

    {:typed_map, fields} ->
      type = if is_array, do: :array_of_typed_map, else: :typed_map
      {:ok, type, fields}

    {:typed_struct, value} ->
      type = if is_array, do: :array_of_typed_struct, else: :typed_struct
      {:ok, type, value}

    :unconstrained_map ->
      type = if is_array, do: :array_of_unconstrained_map, else: :unconstrained_map
      {:ok, type, nil}

    {:error, reason} ->
      {:error, reason}
  end
end

This mirrors the pattern already used in field_selector.ex (line 116) and ensures codegen and runtime agree on whether field selection is required.


Contributor checklist

Leave anything that you believe does not apply unchecked.

  • I accept the AI Policy, or AI was not used in the creation of this PR.
  • Bug fixes include regression tests
  • Chores
  • Documentation changes
  • Features include unit/acceptance tests
  • Refactoring
  • Update dependencies

@Torkan
Copy link
Copy Markdown
Collaborator

Torkan commented Mar 28, 2026

Thanks a bunch! 🤩

@Torkan Torkan merged commit df912a9 into ash-project:main Mar 28, 2026
23 checks passed
@mike1o1 mike1o1 deleted the 59-fix-newtype-fields branch March 29, 2026 05:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Codegen omits fields parameter for generic actions returning NewType-wrapped maps

2 participants