Add rc.sortBy custom operator (#4088)
### Motivation
Ordering a list is the one thing `reduce` cannot fake: comparing an item
against the *others* in the same collection needs a scope the engine
does not expose. Sorting through `rc.entries` key ordering is the
closest workaround, and it collapses duplicate keys and misorders
numbers.
Android counterpart of
https://github.com/RevenueCat/purchases-ios/pull/7504.
### Description
`{"rc.sortBy": [array, keyExpression]}` returns the original items in
ascending key order. The key template is evaluated once per item with
scope rebound to it, exactly as `map` does, so `rc.rootVar` is how a key
reaches top-level data.
* Keys must be all strings or all finite numbers. A mix, or a
null/bool/array/object key, throws `typeMismatch` rather than inventing
a cross-type ordering. String keys compare by UTF-16 code unit, matching
JS and the iOS engine.
* Ties keep input order.
* Always ascending. A third argument is rejected rather than ignored. To
sort descending on a numeric key, negate it: `{"-": [0, {"var":
"score"}]}`.
The 21 fixtures are byte-identical to the iOS ones.
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Low Risk**
> Isolated addition to the internal rules engine with strict typing and
broad fixture coverage; no auth, billing, or persistence changes.
>
> **Overview**
> Adds **`rc.sortBy`** to the RevenueCat JSON Logic rules engine so
predicates can return array elements sorted by a per-item key
expression—addressing ordering that `reduce` cannot express and
workarounds like `rc.fromEntries` that collapse duplicates or misorder
numbers.
>
> **`{"rc.sortBy": [array, keyExpression]}`** takes exactly two
operands, evaluates the key template once per element with scope rebound
to that item (same as `map`), and returns the **original items** in
ascending key order with **stable** ties. Keys must be uniformly strings
(UTF-16 code unit order) or finite numbers; mixed or invalid key types,
NaN/infinity, wrong arity, or a non-array input raise `typeMismatch`.
There is no descending flag—numeric descending sorts use negated keys.
**`CustomOperators`** dispatches the new operator; conformance coverage
is added via **`rc_sort_by.json`** and the predicate fixture count is
bumped to 559 (iOS-aligned fixtures).
>
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
c8a6df061da5d3bdb8d303f3e5033d4da868033b. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
---------
Co-authored-by: Cursor <cursoragent@cursor.com> A
Antonio Pallares committed
0a7b5fbf1d0fe2245e1f04b39703534e093274c8
Parent: 8a004ee
Committed by GitHub <noreply@github.com>
on 8/31/2026, 10:31:15 AM