Release Notes — 25e5a26
Despite the "refs and cleanup" commit message, this contains two behavioral bug fixes plus a refactor.
🐛 Bug Fixes
Connection pagination: after is now an exclusive cursor for in-memory lists
The IList/in-memory overload of ConnectionConverter treated the after argument as inclusive (skip = after ?? 0), so paging with after: 1 would re-return the item at index 1. It now starts strictly after the cursor (skip = after + 1 ?? 0), matching the IQueryable path and the Relay connection spec.
⚠️ Behavioral change: queries that page over in-memory lists using after will shift by one position. For example, first: 2, after: 1 now returns ["c", "d"] instead of ["b", "c"]. Several connection snapshot tests were updated to reflect the corrected cursors.
HasPreviousPage no longer incorrectly reports false
The condition was skip > 0 && take < count. The take < count guard hid the existence of a previous page whenever the page size covered all remaining items. It's now simply skip > 0 — if you've skipped past the start, a previous page exists. For example, first: 10, after: 0 over a 10-item list now correctly reports HasPreviousPage: true.
Property lookup across inherited interfaces is fixed
PropertyCache.GetPropertyOrField failed to resolve members declared on a base interface when a type implemented multiple inherited interfaces. The old loop break'd after the very first interface regardless of whether the member was found, and recursed into a method that throws on a miss — so lookups depended on the unspecified ordering of Type.GetInterfaces() and would throw for members on later interfaces. The interface walk now probes each base interface individually and stops only when a match is found.
♻️ Refactoring
PropertyCache member resolution split into a non-throwing TryGetPropertyOrField (returns null on miss) and a throwing GetPropertyOrField wrapper, making the recursive interface walk correct and clearer.
✅ Tests
List_after_is_an_exclusive_cursor — verifies after exclusivity on in-memory lists.
List_HasPreviousPage_true_when_page_size_covers_remaining_items — guards the HasPreviousPage fix.
PropertyOnInheritedInterface — covers member resolution across multiple inherited interfaces (asserts both members so it's independent of GetInterfaces() ordering).
- Updated 5 connection pagination snapshot files for the corrected cursor / page-info values.
Release Notes —
25e5a26Despite the "refs and cleanup" commit message, this contains two behavioral bug fixes plus a refactor.
🐛 Bug Fixes
Connection pagination:
afteris now an exclusive cursor for in-memory listsThe
IList/in-memory overload ofConnectionConvertertreated theafterargument as inclusive (skip = after ?? 0), so paging withafter: 1would re-return the item at index 1. It now starts strictly after the cursor (skip = after + 1 ?? 0), matching theIQueryablepath and the Relay connection spec.HasPreviousPageno longer incorrectly reportsfalseThe condition was
skip > 0 && take < count. Thetake < countguard hid the existence of a previous page whenever the page size covered all remaining items. It's now simplyskip > 0— if you've skipped past the start, a previous page exists. For example,first: 10, after: 0over a 10-item list now correctly reportsHasPreviousPage: true.Property lookup across inherited interfaces is fixed
PropertyCache.GetPropertyOrFieldfailed to resolve members declared on a base interface when a type implemented multiple inherited interfaces. The old loopbreak'd after the very first interface regardless of whether the member was found, and recursed into a method that throws on a miss — so lookups depended on the unspecified ordering ofType.GetInterfaces()and would throw for members on later interfaces. The interface walk now probes each base interface individually and stops only when a match is found.♻️ Refactoring
PropertyCachemember resolution split into a non-throwingTryGetPropertyOrField(returnsnullon miss) and a throwingGetPropertyOrFieldwrapper, making the recursive interface walk correct and clearer.✅ Tests
List_after_is_an_exclusive_cursor— verifiesafterexclusivity on in-memory lists.List_HasPreviousPage_true_when_page_size_covers_remaining_items— guards theHasPreviousPagefix.PropertyOnInheritedInterface— covers member resolution across multiple inherited interfaces (asserts both members so it's independent ofGetInterfaces()ordering).