Repository navigation
List endpoints return unbounded collections from in-memory stores with no pagination #229
Description
Priority: Medium · Area: API design / resource limits · Est. effort: 7–10 h
📌 Problem
The service exposes collection endpoints across src/routes/anchors.ts, liquidity.ts and settlements.ts, backed by repositories that hold every record in memory (src/repositories/inMemoryRepository.ts).
Establish and report the current behaviour first: for each list endpoint, does it accept any limit or cursor, or does it serialise the entire collection on every request?
If collections are returned whole, three problems follow:
- Response size grows without bound. Settlements are append-only; the list only ever gets longer, so response time and memory degrade continuously with no ceiling.
- Adding pagination later is breaking. Clients written against an unpaginated endpoint assume the response is complete. Introducing pagination after adoption silently truncates for anyone who did not update — the worst failure mode, because it looks like data loss rather than an error.
- Amplification.
GETis unauthenticated and unrate-limited (both tracked separately), so an unbounded list endpoint is a cheap way to make the server do a lot of work.
Doing this now, while the API is young, is far cheaper than after clients depend on the current shape.
🎯 Design decision required
State and defend:
- Offset or cursor pagination? Offset is simpler; cursor is stable under concurrent inserts — and settlements are append-only, which is exactly where offset pagination skips or repeats rows. Argue one against this data.
- Default and maximum page size. Justify both numbers. A default that returns everything is not a default.
- Response envelope. Changing the shape from a bare array to
{ items, next }is breaking. Decide whether to break now, version, or add pagination while keeping the current shape for unparameterised requests, and argue it. - Ordering. Pagination requires a stable sort. State the ordering per collection and why it is deterministic.
🧩 Requirements and context
- The maximum page size must be enforced server-side — a client asking for more must be clamped, not obeyed. Test it.
- Ordering must be deterministic, including for records that would otherwise tie. Test that too.
src/openapi.tsmust document the parameters and the response shape.- Coordinate with the persistence issue: pagination implemented against in-memory arrays should map cleanly onto database queries later. Say how your design does.
- All 42 test files must pass.
🛠️ Suggested execution
- Report each list endpoint's current behaviour.
- Post your pagination model and envelope decision on this issue; wait for agreement if it is breaking.
- Implement, with server-side clamping.
- Add tests: clamping, stable ordering, traversal to the end without skips or duplicates.
- Update the OpenAPI spec.
✅ Acceptance criteria
- The PR reports current behaviour for every list endpoint.
- Pagination is implemented with documented default and maximum page sizes.
- Requests exceeding the maximum are clamped server-side, with a test.
- Ordering is deterministic, with a test covering ties.
- A test traverses a full collection and asserts no skipped or duplicated records.
-
src/openapi.tsdocuments parameters and response shape. - Any breaking change is stated with a migration note.
-
npm run lint,npm run buildandnpm testpass.
🚫 Out of scope
- The persistence layer — separate issue.
- Authentication and rate limiting on reads — separate issues.
- Adding filtering or search.
🧪 Verification
npm ci
npm test src/routes src/repositories
npm run lint && npm run build && npm test📤 What your PR must include
- Current behaviour per list endpoint.
- Your pagination model, page sizes and envelope decision with reasoning.
- The traversal test proving no skips or duplicates.
- How the design maps onto a future database query.
Closes #<n>.
🔒 Security notes
An unbounded list endpoint that requires no authentication and is not rate limited is a resource-exhaustion vector: each request forces the server to serialise the entire collection, and the cost to the attacker is one HTTP request. Because settlements are append-only, the asymmetry worsens over time — the same request gets more expensive for the server and no more expensive for the caller.
📋 Guidelines
- Minimum 95% test coverage on changed lines
- Clear documentation
- Timeframe: 96 hours from assignment
- One logical change per commit; no merge commits
💬 Join our community
Working on this, or want to sanity-check your approach before you start? Come and ask — the maintainers are there and happy to help.
Telegram: https://t.me/Grainlify
Activity
Baskarayelu commentedon Aug 27, 2026
I’d love to be assigned this issue and contribute across the relevant areas, whether it involves smart contracts, UI development, backend logic, design implementation, or overall system integration. I’m comfortable working across the full stack and can adapt to the project’s technical requirements to ensure a smooth and consistent contribution.
grantfox-oss commentedon Aug 27, 2026
🦊 GrantFox — @Baskarayelu has been assigned to this issue as part of the Third Campaign campaign!
Next steps:
Closes #229)Good luck! Track your progress on GrantFox.
Baskarayelu commentedon Aug 30, 2026
Implemented in PR #238: #238. The PR includes bounded cursor pagination, stable ordering/snapshot traversal, API documentation, compatibility behavior, and tests. It uses
Closes #229for automatic issue closure when merged.