From 8a0564269f34c2ea5e3ee663978ca632ddd2db6c Mon Sep 17 00:00:00 2001 From: Mike Wilson Date: Mon, 30 Mar 2026 16:30:31 -0700 Subject: [PATCH] fix: camelize nested keys in read action typed map metadata --- lib/ash_typescript/rpc/pipeline.ex | 9 ++++- test/ash_typescript/rpc/rpc_metadata_test.exs | 32 +++++++++++++++ test/support/domain.ex | 3 ++ test/support/resources/task.ex | 39 +++++++++++++++++++ 4 files changed, 82 insertions(+), 1 deletion(-) diff --git a/lib/ash_typescript/rpc/pipeline.ex b/lib/ash_typescript/rpc/pipeline.ex index 891313ed..420f791d 100644 --- a/lib/ash_typescript/rpc/pipeline.ex +++ b/lib/ash_typescript/rpc/pipeline.ex @@ -1341,7 +1341,14 @@ defmodule AshTypescript.Rpc.Pipeline do when is_map(filtered_record) do metadata_map = Map.get(original_record, :__metadata__, %{}) extracted_metadata = extract_metadata_fields(metadata_map, show_metadata, rpc_action) - Map.merge(filtered_record, extracted_metadata) + formatter = Rpc.output_field_formatter() + + formatted_metadata = + Enum.into(extracted_metadata, %{}, fn {key, value} -> + {key, format_field_names(value, formatter)} + end) + + Map.merge(filtered_record, formatted_metadata) end defp do_add_read_metadata(filtered_record, _original_record, _show_metadata, _rpc_action) do diff --git a/test/ash_typescript/rpc/rpc_metadata_test.exs b/test/ash_typescript/rpc/rpc_metadata_test.exs index 032beec5..022102fb 100644 --- a/test/ash_typescript/rpc/rpc_metadata_test.exs +++ b/test/ash_typescript/rpc/rpc_metadata_test.exs @@ -729,6 +729,38 @@ defmodule AshTypescript.Rpc.MetadataTest do end end + describe "READ actions - typed map metadata nested key formatting" do + test "typed map metadata values have camelCase nested keys" do + task = create_task("Test Task") + + params = %{ + "action" => "read_tasks_with_typed_map_metadata", + "fields" => ["id", "title"], + "metadataFields" => ["auditEntries", "completionInfo"] + } + + conn = %Plug.Conn{} + result = Rpc.run_action(:ash_typescript, conn, params) + + assert result["success"] == true + tasks = result["data"] + task_result = Enum.find(tasks, &(&1["id"] == task.id)) + + # Nested keys in {:array, :map} metadata should be camelCase + first_entry = List.first(task_result["auditEntries"]) + assert first_entry["fieldName"] == "title" + assert first_entry["oldValue"] == "Old Title" + refute Map.has_key?(first_entry, "field_name") + refute Map.has_key?(first_entry, "old_value") + + # Nested keys in :map metadata should be camelCase + assert task_result["completionInfo"]["completedAt"] == "2025-01-15T10:30:00Z" + assert task_result["completionInfo"]["completedBy"] == "user_123" + refute Map.has_key?(task_result["completionInfo"], "completed_at") + refute Map.has_key?(task_result["completionInfo"], "completed_by") + end + end + # Helper function to create tasks defp create_task(title) do Task diff --git a/test/support/domain.ex b/test/support/domain.ex index 2abbea44..a1e8150a 100644 --- a/test/support/domain.ex +++ b/test/support/domain.ex @@ -211,6 +211,9 @@ defmodule AshTypescript.Test.Domain do rpc_action :destroy_task_metadata_empty, :destroy, show_metadata: [] rpc_action :destroy_task_metadata_one, :destroy, show_metadata: [:some_string] rpc_action :destroy_task_metadata_two, :destroy, show_metadata: [:some_string, :some_number] + + rpc_action :read_tasks_with_typed_map_metadata, :read_with_typed_map_metadata, + show_metadata: nil end resource AshTypescript.Test.PostComment diff --git a/test/support/resources/task.ex b/test/support/resources/task.ex index 12681561..7612df23 100644 --- a/test/support/resources/task.ex +++ b/test/support/resources/task.ex @@ -89,6 +89,45 @@ defmodule AshTypescript.Test.Task do end end + read :read_with_typed_map_metadata do + metadata :audit_entries, {:array, :map}, + constraints: [ + items: [ + fields: [ + field_name: [type: :string], + old_value: [type: :string] + ] + ] + ] + + metadata :completion_info, :map, + constraints: [ + fields: [ + completed_at: [type: :string], + completed_by: [type: :string] + ] + ] + + prepare fn query, _context -> + Ash.Query.after_action(query, fn _query, results -> + results_with_metadata = + Enum.map(results, fn record -> + record + |> Ash.Resource.put_metadata(:audit_entries, [ + %{field_name: "title", old_value: "Old Title"}, + %{field_name: "completed", old_value: "false"} + ]) + |> Ash.Resource.put_metadata(:completion_info, %{ + completed_at: "2025-01-15T10:30:00Z", + completed_by: "user_123" + }) + end) + + {:ok, results_with_metadata} + end) + end + end + read :read_with_invalid_metadata_names do metadata :meta_1, :string, allow_nil?: false, default: "metadata_value" metadata :is_valid?, :boolean, allow_nil?: false, default: true