fix: stabilize TS field order for :auto calcs with literal map exprs#67
Closed
barnabasJ wants to merge 1 commit intoash-project:mainfrom
Closed
fix: stabilize TS field order for :auto calcs with literal map exprs#67barnabasJ wants to merge 1 commit intoash-project:mainfrom
barnabasJ wants to merge 1 commit intoash-project:mainfrom
Conversation
Ash resolves `expr(%{a: ..., b: ...})` in `:auto`-typed calcs through a
runtime Erlang map, whose iteration order depends on atom term ordering
at BEAM load time (warm `_build/dev` vs clean `_build/test` can differ).
This leaked into generated TypeScript as non-deterministic field order.
Fix is local to ash_typescript: sort the `:fields` constraint
alphabetically at the calc- and publication-introspection sites known to
carry auto-derived constraints. User-declared typed maps are unaffected
(their source order is preserved by the DSL).
3 tasks
zachdaniel
approved these changes
Apr 17, 2026
Contributor
Author
The fix in Ash should be enough, I thought I stopped this work before the PR was opened. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
:auto-typed Ash calculations with literal map expressions (e.g.calculate :summary, :auto, expr(%{a: id, b: name})) emit TypeScript interfaces whose field order changes non-deterministically between compiles. Ash materializes the literal map through a runtime Erlang map whose iteration order depends on atom term ordering at BEAM load time — so warm_build/devand clean_build/testcan disagree. This manifests as flaky snapshot diffs and unstable generated TS.:fieldsalphabetically at the calc- and publication-introspection sites known to carry auto-derived constraints. User-declared typed maps (attribute :foo, :map, constraints: [fields: [...]]) pass through unchanged — their source order is preserved by the DSL and was already deterministic.Changes
lib/ash_typescript/codegen/helpers.ex— Addssort_auto_fields/1andauto_safe_calc_constraints/1. The latter only sorts when the calc is expression-based (calc.calculation = {Ash.Resource.Calculation.Expression, _}) — the sole source of non-determinism.lib/ash_typescript/codegen/type_mapper.ex— Wrapscalc.constraintsthrough the helper in the%Ash.Resource.Calculation{}schema-emission branch.lib/ash_typescript/codegen/resource_schemas.ex— Same treatment inget_calculation_return_type_for_metadata/2.lib/ash_typescript/typed_channel/codegen.ex— When a pub'stransform:is a calc-name atom (meaning Ash derivedreturns/constraintsfrom a calc), sort fields viasort_auto_fields/1before passing tomap_channel_payload_type/2.Tests — New
test/ash_typescript/codegen/auto_calc_field_order_test.exscovers::fieldsorderings throughauto_safe_calc_constraints/1+map_type/3and asserts identical output.TrackerOrderedCardPayloademits alphabetically sorted fields.Caveat on detection power: because the non-determinism lives upstream in Ash's expression-to-type resolver, we can't force a reliably non-alphabetical input from a test. These tests verify the fix's contract and post-fix behavior; they don't guarantee to fail on every pre-fix BEAM load for every input.
Three existing assertions in
typed_channel/codegen_test.exslocked in source order forTrackerDetailPayload,TrackerDeepDetailPayload, andTrackerReportPayloadand have been updated to the new alphabetical order.Why the fix lives here
The upstream root cause is in Ash's
determine_typepath — resolving a map literal expr into{Ash.Type.Map, fields: [...]}. Fixing it there would require either a new expression-tree struct (propagates toash_postgresand every other data layer) or a side-channel for ordering through the DSL pipeline. Neither is worth it to give one consumer (codegen) a stable input. Since TypeScript field order is purely cosmetic, alphabetizing at the ash_typescript consumer is the right layer.Test plan
mix test— 2226 tests, 0 failuresmix format --check-formattedcleanmix credo --strictcleanauto_safe_calc_constraints/1only sorts for expression-based calcs)