11 August, 2026
This release reworks how computed fields are kept up to date. It fixes several cases where a stored aggregate could silently drift from the records it summarises, makes aggregate expressions in filters and permissions resolve correctly, and catches circular computed expressions when your schema is validated.
Computed fields rework
Computed fields that aggregate over related records, such as COUNT(customer.orders.id) or SUM(invoice.items.total), are maintained by the runtime whenever the underlying records change. This release fixes a set of cases where the stored value could end up wrong:
- A count written directly over a relation, for example
orderCount Number @computed(COUNT(customer.orders)), could keep a stale value after related records were created, deleted, or moved. These counts are now maintained like every other aggregate. - Moving a record from one parent to another now updates aggregates on both the parent it left and the one it joined. This includes aggregates that span more than one relation, where moving a record in the middle of the chain previously left both sides wrong.
- A count over a nullable field now skips rows where that field is null, matching how
COUNTtreats nulls everywhere else. - Writes that race each other on the same source records no longer produce a stale aggregate. The runtime locks the relevant sources while dependent values recompute.
Deploys that add or change computed fields are also more dependable. Backfills now run in dependency order, so a computed field that reads another computed field is filled in after its source. An interrupted backfill resumes on the next boot instead of staying half-done, and only fields whose expressions changed are recomputed.
Schema validation now also rejects computed field expressions that depend on each other in a cycle, whether across models or through a self-relation, and the error names the fields that form the loop.
Fixes and Improvements
- Expressions: Aggregate functions that end on a relation rather than a field, such as
COUNT(customer.orders.items)in a@wherefilter or@permissionexpression, previously failed with an internal error or counted the wrong rows. They now count the related records correctly.
For any issues or feedback, please contact us at help@keel.so.
Thank you for using Keel!