OverviewFilesCommitsIssuesDocsReleasesPeers

atproto-did-peer commits / 5aa4f28

0.2.10: listRecords honours limit, reverse and cursor
com.atproto.repo.listRecords ACCEPTED these params and ignored them, so a client asking for the newest 10 got the oldest N thousand, and a pager looped forever on a cursor that never advanced. Non-conformance that looks like success is the worst kind — nothing errors, the answer is just wrong. Implemented to the lexicon: limit clamped 1..100 (default 50), reverse = descending rkey order, cursor = resume STRICTLY after the given rkey. The return shape is now { records, cursor } rather than a bare array, and the cursor is ABSENT on the final page — its absence is how a client knows to stop, and returning one at the end is precisely what makes a pager loop. Three existing tests pinned the old array shape; they were encoding the non-conformance, so they were updated rather than worked around. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
5aa4f28628cf8a9b565475c78c93bbf63fbb863d
by plankatron <fleeky@gmail.com> · 2026-08-01T00:55:37.000Z

Changed files (2) +21 -6

lib/did-peer.js +20 -5
……@@ -330,13 +330,28 @@ export class DidPeer {
330330 }
331331
332332 // com.atproto.repo.listRecords
333 async listRecords (collection) {
334 if (this.repo) return rvListRecords(this.repo, collection, { did: this.did })
335 if (this._noRepo) return []
333 // Returns { records, cursor } per the lexicon. `limit`/`reverse`/`cursor` were dropped on the floor
334 // here as well as in read-view, so a caller's paging parameters never reached anything that honoured
335 // them. Both derived and repo-backed paths now page identically — a client must not get different
336 // semantics depending on which internal path answered.
337 async listRecords (collection, { limit = 50, reverse = false, cursor = null } = {}) {
338 if (this.repo) return rvListRecords(this.repo, collection, { did: this.did, limit, reverse, cursor })
339 if (this._noRepo) return { records: [], cursor: undefined }
340 const n = Math.min(100, Math.max(1, Number(limit) || 50))
336341 const prefix = collection + '/'
337 return (await this._derivedRecords())
342 const all = (await this._derivedRecords())
338343 .filter((r) => r.key.startsWith(prefix))
339 .map((r) => ({ uri: `at://${this.did}/${r.key}`, cid: r.cid, value: r.value }))
344 .map((r) => ({ rkey: r.key.slice(prefix.length), uri: `at://${this.did}/${r.key}`, cid: r.cid, value: r.value }))
345 if (reverse) all.reverse()
346 let start = 0
347 if (cursor) {
348 const i = all.findIndex((r) => r.rkey === cursor)
349 start = i >= 0 ? i + 1 : all.length
350 }
351 const page = all.slice(start, start + n)
352 const more = start + page.length < all.length
353 const last = page.length ? page[page.length - 1].rkey : null
354 return { records: page.map(({ uri, cid, value }) => ({ uri, cid, value })), cursor: more && last ? last : undefined }
340355 }
341356
342357 // com.atproto.sync.getLatestCommit -> { cid, rev } from the verified head (keyless).
package.json +1 -1
……@@ -1,6 +1,6 @@
11{
22 "name": "atproto-did-peer",
3 "version": "0.2.9",
3 "version": "0.2.10",
44 "description": "Keyless, direction-free P2P reader/seeder for an atproto DID's repo+firehose Hypercores: announce-push discovery + event-driven head reconciliation + a keyless read surface. The shared primitive beneath atproto-bridge, atproto-relay and favela (extracted from atproto-bridge did-peer.js).",
55 "type": "module",
66 "main": "index.js",