perlsky is a Perl 5 implementation of an AT Protocol Personal Data Server.
13
fork

Configure Feed

Select the types of activity you want to include in your feed.

Fix paginated cursors for ordered listings

alice 7d333ebc 09d5739a

+73 -2
+4 -2
lib/ATProto/PDS/Store/SQLite.pm
··· 1851 1851 my @items = @$rows; 1852 1852 my $cursor; 1853 1853 if (@items > $limit) { 1854 - my $last = pop @items; 1854 + pop @items; 1855 + my $last = $items[-1]; 1855 1856 $cursor = ref($cursor_key) eq 'CODE' 1856 1857 ? $cursor_key->($last) 1857 - : $last->{$cursor_key}; 1858 + : $last->{$cursor_key} 1859 + if $last; 1858 1860 } 1859 1861 return { 1860 1862 items => \@items,
+22
t/external-surface.t
··· 164 164 ))->status_is(200) 165 165 ->json_is('/cids/0' => $blob_cid); 166 166 167 + $t->post_ok('/xrpc/com.atproto.repo.uploadBlob' => { 168 + Authorization => "Bearer $access", 169 + 'Content-Type' => 'text/plain', 170 + } => 'blob-two')->status_is(200); 171 + 172 + my $blob_two_cid = $t->tx->res->json->{blob}{ref}{'$link'}; 173 + my @sorted_blob_cids = sort ($blob_cid, $blob_two_cid); 174 + 175 + $t->get_ok(Mojo::URL->new('/xrpc/com.atproto.sync.listBlobs')->query( 176 + did => $did, 177 + limit => 1, 178 + ))->status_is(200) 179 + ->json_is('/cids/0' => $sorted_blob_cids[0]) 180 + ->json_is('/cursor' => $sorted_blob_cids[0]); 181 + 182 + $t->get_ok(Mojo::URL->new('/xrpc/com.atproto.sync.listBlobs')->query( 183 + did => $did, 184 + limit => 1, 185 + cursor => $sorted_blob_cids[0], 186 + ))->status_is(200) 187 + ->json_is('/cids/0' => $sorted_blob_cids[1]); 188 + 167 189 $t->get_ok(Mojo::URL->new('/xrpc/com.atproto.sync.getBlob')->query( 168 190 did => $second_did, 169 191 cid => $blob_cid,
+47
t/store-sqlite.t
··· 98 98 is($store->list_blobs_by_did($account->{did})->{items}[0]{cid}, 'bafkreigh2akiscaildc', 'shared blob lists for first owner'); 99 99 is($store->list_blobs_by_did($second_account->{did})->{items}[0]{cid}, 'bafkreigh2akiscaildc', 'shared blob lists for second owner'); 100 100 101 + $store->put_blob( 102 + cid => 'bafkreighsecondblob', 103 + did => $account->{did}, 104 + mime_type => 'image/jpeg', 105 + byte_size => 4321, 106 + storage_path => 'blobs/bafk-second.jpg', 107 + ); 108 + 109 + my $blob_page_one = $store->list_blobs_by_did($account->{did}, limit => 1); 110 + is( 111 + [ map { $_->{cid} } @{ $blob_page_one->{items} } ], 112 + ['bafkreigh2akiscaildc'], 113 + 'blob pagination returns the first page item', 114 + ); 115 + is($blob_page_one->{cursor}, 'bafkreigh2akiscaildc', 'blob pagination returns the last emitted cid as the cursor'); 116 + is( 117 + [ map { $_->{cid} } @{ $store->list_blobs_by_did($account->{did}, limit => 1, cursor => $blob_page_one->{cursor})->{items} } ], 118 + ['bafkreighsecondblob'], 119 + 'blob pagination resumes from the emitted cursor without skipping items', 120 + ); 121 + 101 122 $store->set_repo_head( 102 123 did => $account->{did}, 103 124 commit_cid => 'bafycommit', ··· 166 187 [ map { $_->{did} } @$feed_records ], 167 188 [$account->{did}, $second_account->{did}], 168 189 'collection-scoped record listings preserve did ordering for batched account lookup', 190 + ); 191 + 192 + $store->put_record( 193 + did => $account->{did}, 194 + collection => 'app.bsky.feed.post', 195 + rkey => 'post-2', 196 + cid => 'bafypost2', 197 + record_bytes => q(), 198 + value => { 199 + '$type' => 'app.bsky.feed.post', 200 + text => 'goodbye', 201 + createdAt => '2026-03-11T19:02:00Z', 202 + }, 203 + ); 204 + 205 + my $record_page_one = $store->list_records($account->{did}, 'app.bsky.feed.post', limit => 1); 206 + is( 207 + [ map { $_->{rkey} } @{ $record_page_one->{items} } ], 208 + ['post-1'], 209 + 'record pagination returns the first page item', 210 + ); 211 + is($record_page_one->{cursor}, 'post-1', 'record pagination returns the last emitted rkey as the cursor'); 212 + is( 213 + [ map { $_->{rkey} } @{ $store->list_records($account->{did}, 'app.bsky.feed.post', limit => 1, cursor => $record_page_one->{cursor})->{items} } ], 214 + ['post-2'], 215 + 'record pagination resumes from the emitted cursor without skipping items', 169 216 ); 170 217 171 218 $store->revoke_session('sess-1', revoked_at => 123);