Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions lib/ash_typescript/codegen/filter_types.ex
Original file line number Diff line number Diff line change
Expand Up @@ -140,8 +140,12 @@ defmodule AshTypescript.Codegen.FilterTypes do
next, acc -> Ash.Resource.Info.relationship(acc, next).destination
end)

attribute = Ash.Resource.Info.attribute(related_resource, aggregate.field)
generate_attribute_filter(%{attribute | name: aggregate.name}, resource)
# Try to find the field as an attribute first, then fall back to calculation
field =
Ash.Resource.Info.attribute(related_resource, aggregate.field) ||
Ash.Resource.Info.calculation(related_resource, aggregate.field)

generate_attribute_filter(%{field | name: aggregate.name}, resource)
end

defp get_applicable_operations(type, base_type) do
Expand Down
14 changes: 13 additions & 1 deletion lib/ash_typescript/codegen/type_aliases.ex
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,19 @@ defmodule AshTypescript.Codegen.TypeAliases do
types =
resource
|> Ash.Resource.Info.public_calculations()
|> Enum.reduce(types, fn calc, types -> MapSet.put(types, calc.type) end)
|> Enum.reduce(types, fn calc, types ->
# Add the calculation's return type
types = MapSet.put(types, calc.type)

# Also add types from calculation arguments
Enum.reduce(calc.arguments, types, fn arg, types ->
if Ash.Type.ash_type?(arg.type) do
MapSet.put(types, arg.type)
else
types
end
end)
end)

resource
|> Ash.Resource.Info.public_aggregates()
Expand Down
58 changes: 58 additions & 0 deletions test/ash_typescript/codegen/type_aliases_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# SPDX-FileCopyrightText: 2025 ash_typescript contributors <https://github.com/ash-project/ash_typescript/graphs.contributors>
#
# SPDX-License-Identifier: MIT

defmodule AshTypescript.Codegen.TypeAliasesTest do
use ExUnit.Case, async: true

alias AshTypescript.Codegen.TypeAliases

describe "generate_ash_type_aliases/3 for calculation arguments" do
test "discovers types from calculation arguments" do
# Todo has a :filtered_data calculation with arguments using Ash.Type.Date and Ash.Type.UUID
# These types should be discovered and generate type aliases

resources = [AshTypescript.Test.Todo]
actions = []

result = TypeAliases.generate_ash_type_aliases(resources, actions, :ash_typescript)

# Ash.Type.UUID should generate a UUID type alias
assert result =~ "export type UUID = string;"

# Ash.Type.Date should generate an AshDate type alias
assert result =~ "export type AshDate = string;"
end

test "discovers types from both calculation return type and arguments" do
# This tests that we collect types from:
# 1. The calculation's return type
# 2. The calculation's argument types

resources = [AshTypescript.Test.TodoMetadata]
actions = []

result = TypeAliases.generate_ash_type_aliases(resources, actions, :ash_typescript)

# TodoMetadata has calculations with various argument types
# The :adjusted_priority calculation has :float, :boolean, :integer arguments
# These are primitive types so they don't generate aliases, but the function should not error

# Verify the function executes successfully and returns a string
assert is_binary(result)
end

test "handles calculations without arguments" do
# Calculations without arguments should still work correctly

resources = [AshTypescript.Test.TodoComment]
actions = []

result = TypeAliases.generate_ash_type_aliases(resources, actions, :ash_typescript)

# TodoComment has a :weighted_score calculation with no arguments
# This should not cause any errors
assert is_binary(result)
end
end
end
15 changes: 15 additions & 0 deletions test/ash_typescript/typescript_filter_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -202,4 +202,19 @@ defmodule AshTypescript.FilterTest do
assert String.contains?(result, "name?: {")
end
end

describe "aggregate filter types" do
test "generates filter type for sum aggregate over a calculation field" do
# Todo has a :total_weighted_score sum aggregate that references
# the :weighted_score calculation on TodoComment (not an attribute)
result = FilterTypes.generate_filter_type(AshTypescript.Test.Todo)

# Should generate filter type for the sum aggregate over calculation
assert String.contains?(result, "totalWeightedScore?: {")
# Sum aggregates over integer calculations should have numeric operations
assert String.contains?(result, "eq?: number")
assert String.contains?(result, "greaterThan?: number")
assert String.contains?(result, "lessThan?: number")
end
end
end
22 changes: 22 additions & 0 deletions test/support/resources/todo.ex
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,12 @@ defmodule AshTypescript.Test.Todo do
public? true
sort created_at: :desc
end

# Sum aggregate over a calculation field (not an attribute)
# This tests the fix that allows aggregates to reference calculation fields
sum :total_weighted_score, :comments, :weighted_score do
public? true
end
end

calculations do
Expand Down Expand Up @@ -341,6 +347,22 @@ defmodule AshTypescript.Test.Todo do
AshTypescript.Test.SummaryCalculation do
public? true
end

# Calculation with arguments that use types requiring type aliases
# This tests that calculation argument types are discovered for type alias generation
calculate :filtered_data, :string, expr("filtered") do
public? true

argument :after_date, Ash.Type.Date do
allow_nil? true
default nil
end

argument :user_id, Ash.Type.UUID do
allow_nil? true
default nil
end
end
end

actions do
Expand Down
7 changes: 7 additions & 0 deletions test/support/resources/todo_comment.ex
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ defmodule AshTypescript.Test.TodoComment do
update_timestamp :updated_at
end

calculations do
# A weighted score calculation for testing sum aggregates over calculations
calculate :weighted_score, :integer, expr(rating * if(is_helpful, 2, 1)) do
public? true
end
end

relationships do
belongs_to :todo, AshTypescript.Test.Todo do
allow_nil? false
Expand Down
Loading