@atproto/bsky 0.0.273 → 0.0.274

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (249) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/dist/api/app/bsky/actor/getSuggestions.d.ts.map +1 -1
  3. package/dist/api/app/bsky/actor/getSuggestions.js +7 -2
  4. package/dist/api/app/bsky/actor/getSuggestions.js.map +1 -1
  5. package/dist/api/app/bsky/actor/searchActors.d.ts.map +1 -1
  6. package/dist/api/app/bsky/actor/searchActors.js +13 -6
  7. package/dist/api/app/bsky/actor/searchActors.js.map +1 -1
  8. package/dist/api/app/bsky/bookmark/getBookmarks.d.ts.map +1 -1
  9. package/dist/api/app/bsky/bookmark/getBookmarks.js +7 -2
  10. package/dist/api/app/bsky/bookmark/getBookmarks.js.map +1 -1
  11. package/dist/api/app/bsky/contact/getMatches.d.ts.map +1 -1
  12. package/dist/api/app/bsky/contact/getMatches.js +8 -2
  13. package/dist/api/app/bsky/contact/getMatches.js.map +1 -1
  14. package/dist/api/app/bsky/feed/getActorFeeds.d.ts.map +1 -1
  15. package/dist/api/app/bsky/feed/getActorFeeds.js +7 -2
  16. package/dist/api/app/bsky/feed/getActorFeeds.js.map +1 -1
  17. package/dist/api/app/bsky/feed/getActorLikes.d.ts.map +1 -1
  18. package/dist/api/app/bsky/feed/getActorLikes.js +7 -2
  19. package/dist/api/app/bsky/feed/getActorLikes.js.map +1 -1
  20. package/dist/api/app/bsky/feed/getAuthorFeed.d.ts.map +1 -1
  21. package/dist/api/app/bsky/feed/getAuthorFeed.js +7 -2
  22. package/dist/api/app/bsky/feed/getAuthorFeed.js.map +1 -1
  23. package/dist/api/app/bsky/feed/getFeed.d.ts +13 -0
  24. package/dist/api/app/bsky/feed/getFeed.d.ts.map +1 -1
  25. package/dist/api/app/bsky/feed/getFeed.js +41 -8
  26. package/dist/api/app/bsky/feed/getFeed.js.map +1 -1
  27. package/dist/api/app/bsky/feed/getLikes.d.ts.map +1 -1
  28. package/dist/api/app/bsky/feed/getLikes.js +7 -2
  29. package/dist/api/app/bsky/feed/getLikes.js.map +1 -1
  30. package/dist/api/app/bsky/feed/getListFeed.d.ts.map +1 -1
  31. package/dist/api/app/bsky/feed/getListFeed.js +7 -2
  32. package/dist/api/app/bsky/feed/getListFeed.js.map +1 -1
  33. package/dist/api/app/bsky/feed/getQuotes.d.ts.map +1 -1
  34. package/dist/api/app/bsky/feed/getQuotes.js +7 -2
  35. package/dist/api/app/bsky/feed/getQuotes.js.map +1 -1
  36. package/dist/api/app/bsky/feed/getRepostedBy.d.ts.map +1 -1
  37. package/dist/api/app/bsky/feed/getRepostedBy.js +7 -2
  38. package/dist/api/app/bsky/feed/getRepostedBy.js.map +1 -1
  39. package/dist/api/app/bsky/feed/getSuggestedFeeds.d.ts.map +1 -1
  40. package/dist/api/app/bsky/feed/getSuggestedFeeds.js +20 -13
  41. package/dist/api/app/bsky/feed/getSuggestedFeeds.js.map +1 -1
  42. package/dist/api/app/bsky/feed/getTimeline.d.ts.map +1 -1
  43. package/dist/api/app/bsky/feed/getTimeline.js +7 -2
  44. package/dist/api/app/bsky/feed/getTimeline.js.map +1 -1
  45. package/dist/api/app/bsky/feed/searchPosts.d.ts.map +1 -1
  46. package/dist/api/app/bsky/feed/searchPosts.js +14 -7
  47. package/dist/api/app/bsky/feed/searchPosts.js.map +1 -1
  48. package/dist/api/app/bsky/feed/searchPostsV2.d.ts.map +1 -1
  49. package/dist/api/app/bsky/feed/searchPostsV2.js +17 -9
  50. package/dist/api/app/bsky/feed/searchPostsV2.js.map +1 -1
  51. package/dist/api/app/bsky/graph/getActorStarterPacks.d.ts.map +1 -1
  52. package/dist/api/app/bsky/graph/getActorStarterPacks.js +7 -2
  53. package/dist/api/app/bsky/graph/getActorStarterPacks.js.map +1 -1
  54. package/dist/api/app/bsky/graph/getBlocks.d.ts.map +1 -1
  55. package/dist/api/app/bsky/graph/getBlocks.js +7 -2
  56. package/dist/api/app/bsky/graph/getBlocks.js.map +1 -1
  57. package/dist/api/app/bsky/graph/getFollowers.d.ts.map +1 -1
  58. package/dist/api/app/bsky/graph/getFollowers.js +10 -18
  59. package/dist/api/app/bsky/graph/getFollowers.js.map +1 -1
  60. package/dist/api/app/bsky/graph/getFollows.d.ts.map +1 -1
  61. package/dist/api/app/bsky/graph/getFollows.js +7 -2
  62. package/dist/api/app/bsky/graph/getFollows.js.map +1 -1
  63. package/dist/api/app/bsky/graph/getList.d.ts.map +1 -1
  64. package/dist/api/app/bsky/graph/getList.js +7 -2
  65. package/dist/api/app/bsky/graph/getList.js.map +1 -1
  66. package/dist/api/app/bsky/graph/getListBlocks.d.ts.map +1 -1
  67. package/dist/api/app/bsky/graph/getListBlocks.js +7 -2
  68. package/dist/api/app/bsky/graph/getListBlocks.js.map +1 -1
  69. package/dist/api/app/bsky/graph/getListMutes.d.ts.map +1 -1
  70. package/dist/api/app/bsky/graph/getListMutes.js +7 -2
  71. package/dist/api/app/bsky/graph/getListMutes.js.map +1 -1
  72. package/dist/api/app/bsky/graph/getLists.d.ts.map +1 -1
  73. package/dist/api/app/bsky/graph/getLists.js +9 -4
  74. package/dist/api/app/bsky/graph/getLists.js.map +1 -1
  75. package/dist/api/app/bsky/graph/getListsWithMembership.d.ts.map +1 -1
  76. package/dist/api/app/bsky/graph/getListsWithMembership.js +9 -4
  77. package/dist/api/app/bsky/graph/getListsWithMembership.js.map +1 -1
  78. package/dist/api/app/bsky/graph/getMutes.d.ts.map +1 -1
  79. package/dist/api/app/bsky/graph/getMutes.js +22 -30
  80. package/dist/api/app/bsky/graph/getMutes.js.map +1 -1
  81. package/dist/api/app/bsky/graph/getStarterPacksWithMembership.d.ts.map +1 -1
  82. package/dist/api/app/bsky/graph/getStarterPacksWithMembership.js +7 -2
  83. package/dist/api/app/bsky/graph/getStarterPacksWithMembership.js.map +1 -1
  84. package/dist/api/app/bsky/graph/searchStarterPacks.d.ts.map +1 -1
  85. package/dist/api/app/bsky/graph/searchStarterPacks.js +13 -6
  86. package/dist/api/app/bsky/graph/searchStarterPacks.js.map +1 -1
  87. package/dist/api/app/bsky/graph/searchStarterPacksV2.d.ts.map +1 -1
  88. package/dist/api/app/bsky/graph/searchStarterPacksV2.js +7 -2
  89. package/dist/api/app/bsky/graph/searchStarterPacksV2.js.map +1 -1
  90. package/dist/api/app/bsky/notification/listActivitySubscriptions.d.ts.map +1 -1
  91. package/dist/api/app/bsky/notification/listActivitySubscriptions.js +7 -2
  92. package/dist/api/app/bsky/notification/listActivitySubscriptions.js.map +1 -1
  93. package/dist/api/app/bsky/notification/listNotifications.d.ts.map +1 -1
  94. package/dist/api/app/bsky/notification/listNotifications.js +17 -35
  95. package/dist/api/app/bsky/notification/listNotifications.js.map +1 -1
  96. package/dist/api/app/bsky/unspecced/getPopularFeedGenerators.d.ts.map +1 -1
  97. package/dist/api/app/bsky/unspecced/getPopularFeedGenerators.js +49 -43
  98. package/dist/api/app/bsky/unspecced/getPopularFeedGenerators.js.map +1 -1
  99. package/dist/api/app/bsky/unspecced/getTrendingTopics.d.ts.map +1 -1
  100. package/dist/api/app/bsky/unspecced/getTrendingTopics.js +3 -14
  101. package/dist/api/app/bsky/unspecced/getTrendingTopics.js.map +1 -1
  102. package/dist/api/app/bsky/unspecced/getTrends.d.ts.map +1 -1
  103. package/dist/api/app/bsky/unspecced/getTrends.js +3 -14
  104. package/dist/api/app/bsky/unspecced/getTrends.js.map +1 -1
  105. package/dist/api/util.d.ts +15 -0
  106. package/dist/api/util.d.ts.map +1 -1
  107. package/dist/api/util.js +26 -0
  108. package/dist/api/util.js.map +1 -1
  109. package/dist/config.d.ts +2 -0
  110. package/dist/config.d.ts.map +1 -1
  111. package/dist/config.js +5 -0
  112. package/dist/config.js.map +1 -1
  113. package/dist/data-plane/server/db/pagination.d.ts +8 -0
  114. package/dist/data-plane/server/db/pagination.d.ts.map +1 -1
  115. package/dist/data-plane/server/db/pagination.js +20 -2
  116. package/dist/data-plane/server/db/pagination.js.map +1 -1
  117. package/dist/data-plane/server/op-thread.d.ts +7 -0
  118. package/dist/data-plane/server/op-thread.d.ts.map +1 -0
  119. package/dist/data-plane/server/op-thread.js +71 -0
  120. package/dist/data-plane/server/op-thread.js.map +1 -0
  121. package/dist/data-plane/server/routes/activity-subscription.js +3 -3
  122. package/dist/data-plane/server/routes/activity-subscription.js.map +1 -1
  123. package/dist/data-plane/server/routes/blocks.js +6 -6
  124. package/dist/data-plane/server/routes/blocks.js.map +1 -1
  125. package/dist/data-plane/server/routes/bookmarks.js +3 -3
  126. package/dist/data-plane/server/routes/bookmarks.js.map +1 -1
  127. package/dist/data-plane/server/routes/drafts.js +3 -3
  128. package/dist/data-plane/server/routes/drafts.js.map +1 -1
  129. package/dist/data-plane/server/routes/feed-gens.js +14 -12
  130. package/dist/data-plane/server/routes/feed-gens.js.map +1 -1
  131. package/dist/data-plane/server/routes/feeds.d.ts.map +1 -1
  132. package/dist/data-plane/server/routes/feeds.js +36 -15
  133. package/dist/data-plane/server/routes/feeds.js.map +1 -1
  134. package/dist/data-plane/server/routes/follows.js +6 -6
  135. package/dist/data-plane/server/routes/follows.js.map +1 -1
  136. package/dist/data-plane/server/routes/likes.js +6 -6
  137. package/dist/data-plane/server/routes/likes.js.map +1 -1
  138. package/dist/data-plane/server/routes/lists.js +6 -6
  139. package/dist/data-plane/server/routes/lists.js.map +1 -1
  140. package/dist/data-plane/server/routes/mutes.js +7 -7
  141. package/dist/data-plane/server/routes/mutes.js.map +1 -1
  142. package/dist/data-plane/server/routes/notifs.js +3 -3
  143. package/dist/data-plane/server/routes/notifs.js.map +1 -1
  144. package/dist/data-plane/server/routes/quotes.js +3 -3
  145. package/dist/data-plane/server/routes/quotes.js.map +1 -1
  146. package/dist/data-plane/server/routes/records.d.ts +3 -1
  147. package/dist/data-plane/server/routes/records.d.ts.map +1 -1
  148. package/dist/data-plane/server/routes/records.js +41 -4
  149. package/dist/data-plane/server/routes/records.js.map +1 -1
  150. package/dist/data-plane/server/routes/reposts.js +6 -6
  151. package/dist/data-plane/server/routes/reposts.js.map +1 -1
  152. package/dist/data-plane/server/routes/search.js +9 -9
  153. package/dist/data-plane/server/routes/search.js.map +1 -1
  154. package/dist/data-plane/server/routes/starter-packs.js +3 -3
  155. package/dist/data-plane/server/routes/starter-packs.js.map +1 -1
  156. package/dist/data-plane/server/routes/threads.d.ts.map +1 -1
  157. package/dist/data-plane/server/routes/threads.js +3 -74
  158. package/dist/data-plane/server/routes/threads.js.map +1 -1
  159. package/dist/feature-gates/gates.d.ts +2 -1
  160. package/dist/feature-gates/gates.d.ts.map +1 -1
  161. package/dist/feature-gates/gates.js +2 -1
  162. package/dist/feature-gates/gates.js.map +1 -1
  163. package/dist/hydration/feed.d.ts +3 -0
  164. package/dist/hydration/feed.d.ts.map +1 -1
  165. package/dist/hydration/feed.js +12 -0
  166. package/dist/hydration/feed.js.map +1 -1
  167. package/dist/hydration/hydrator.d.ts.map +1 -1
  168. package/dist/hydration/hydrator.js +4 -2
  169. package/dist/hydration/hydrator.js.map +1 -1
  170. package/dist/image/server.d.ts +1 -1
  171. package/dist/image/server.d.ts.map +1 -1
  172. package/dist/image/server.js +40 -22
  173. package/dist/image/server.js.map +1 -1
  174. package/dist/lexicons/app/bsky/actor/contentVisibilityDeclaration.d.ts +3 -0
  175. package/dist/lexicons/app/bsky/actor/contentVisibilityDeclaration.d.ts.map +1 -0
  176. package/dist/lexicons/app/bsky/actor/contentVisibilityDeclaration.defs.d.ts +66 -0
  177. package/dist/lexicons/app/bsky/actor/contentVisibilityDeclaration.defs.d.ts.map +1 -0
  178. package/dist/lexicons/app/bsky/actor/contentVisibilityDeclaration.defs.js +26 -0
  179. package/dist/lexicons/app/bsky/actor/contentVisibilityDeclaration.defs.js.map +1 -0
  180. package/dist/lexicons/app/bsky/actor/contentVisibilityDeclaration.js +6 -0
  181. package/dist/lexicons/app/bsky/actor/contentVisibilityDeclaration.js.map +1 -0
  182. package/dist/lexicons/app/bsky/actor.d.ts +1 -0
  183. package/dist/lexicons/app/bsky/actor.d.ts.map +1 -1
  184. package/dist/lexicons/app/bsky/actor.js +1 -0
  185. package/dist/lexicons/app/bsky/actor.js.map +1 -1
  186. package/dist/lexicons/app/bsky/authCreatePosts.defs.d.ts.map +1 -1
  187. package/dist/lexicons/app/bsky/authCreatePosts.defs.js +5 -0
  188. package/dist/lexicons/app/bsky/authCreatePosts.defs.js.map +1 -1
  189. package/dist/lexicons/app/bsky/authFullApp.defs.d.ts.map +1 -1
  190. package/dist/lexicons/app/bsky/authFullApp.defs.js +5 -0
  191. package/dist/lexicons/app/bsky/authFullApp.defs.js.map +1 -1
  192. package/dist/lexicons/app/bsky/video/abortUpload.d.ts +3 -0
  193. package/dist/lexicons/app/bsky/video/abortUpload.d.ts.map +1 -0
  194. package/dist/lexicons/app/bsky/video/abortUpload.defs.d.ts +53 -0
  195. package/dist/lexicons/app/bsky/video/abortUpload.defs.d.ts.map +1 -0
  196. package/dist/lexicons/app/bsky/video/abortUpload.defs.js +26 -0
  197. package/dist/lexicons/app/bsky/video/abortUpload.defs.js.map +1 -0
  198. package/dist/lexicons/app/bsky/video/abortUpload.js +6 -0
  199. package/dist/lexicons/app/bsky/video/abortUpload.js.map +1 -0
  200. package/dist/lexicons/app/bsky/video/defs.defs.d.ts +1 -1
  201. package/dist/lexicons/app/bsky/video/defs.defs.d.ts.map +1 -1
  202. package/dist/lexicons/app/bsky/video/defs.defs.js.map +1 -1
  203. package/dist/lexicons/app/bsky/video/finishUpload.d.ts +3 -0
  204. package/dist/lexicons/app/bsky/video/finishUpload.d.ts.map +1 -0
  205. package/dist/lexicons/app/bsky/video/finishUpload.defs.d.ts +42 -0
  206. package/dist/lexicons/app/bsky/video/finishUpload.defs.d.ts.map +1 -0
  207. package/dist/lexicons/app/bsky/video/finishUpload.defs.js +30 -0
  208. package/dist/lexicons/app/bsky/video/finishUpload.defs.js.map +1 -0
  209. package/dist/lexicons/app/bsky/video/finishUpload.js +6 -0
  210. package/dist/lexicons/app/bsky/video/finishUpload.js.map +1 -0
  211. package/dist/lexicons/app/bsky/video/getUploadStatus.d.ts +3 -0
  212. package/dist/lexicons/app/bsky/video/getUploadStatus.d.ts.map +1 -0
  213. package/dist/lexicons/app/bsky/video/getUploadStatus.defs.d.ts +73 -0
  214. package/dist/lexicons/app/bsky/video/getUploadStatus.defs.d.ts.map +1 -0
  215. package/dist/lexicons/app/bsky/video/getUploadStatus.defs.js +30 -0
  216. package/dist/lexicons/app/bsky/video/getUploadStatus.defs.js.map +1 -0
  217. package/dist/lexicons/app/bsky/video/getUploadStatus.js +6 -0
  218. package/dist/lexicons/app/bsky/video/getUploadStatus.js.map +1 -0
  219. package/dist/lexicons/app/bsky/video/startUpload.d.ts +3 -0
  220. package/dist/lexicons/app/bsky/video/startUpload.d.ts.map +1 -0
  221. package/dist/lexicons/app/bsky/video/startUpload.defs.d.ts +63 -0
  222. package/dist/lexicons/app/bsky/video/startUpload.defs.d.ts.map +1 -0
  223. package/dist/lexicons/app/bsky/video/startUpload.defs.js +36 -0
  224. package/dist/lexicons/app/bsky/video/startUpload.defs.js.map +1 -0
  225. package/dist/lexicons/app/bsky/video/startUpload.js +6 -0
  226. package/dist/lexicons/app/bsky/video/startUpload.js.map +1 -0
  227. package/dist/lexicons/app/bsky/video/uploadPart.d.ts +3 -0
  228. package/dist/lexicons/app/bsky/video/uploadPart.d.ts.map +1 -0
  229. package/dist/lexicons/app/bsky/video/uploadPart.defs.d.ts +37 -0
  230. package/dist/lexicons/app/bsky/video/uploadPart.defs.d.ts.map +1 -0
  231. package/dist/lexicons/app/bsky/video/uploadPart.defs.js +31 -0
  232. package/dist/lexicons/app/bsky/video/uploadPart.defs.js.map +1 -0
  233. package/dist/lexicons/app/bsky/video/uploadPart.js +6 -0
  234. package/dist/lexicons/app/bsky/video/uploadPart.js.map +1 -0
  235. package/dist/lexicons/app/bsky/video.d.ts +5 -0
  236. package/dist/lexicons/app/bsky/video.d.ts.map +1 -1
  237. package/dist/lexicons/app/bsky/video.js +5 -0
  238. package/dist/lexicons/app/bsky/video.js.map +1 -1
  239. package/dist/proto/bsky_pb.d.ts +39 -0
  240. package/dist/proto/bsky_pb.d.ts.map +1 -1
  241. package/dist/proto/bsky_pb.js +55 -0
  242. package/dist/proto/bsky_pb.js.map +1 -1
  243. package/dist/views/index.d.ts.map +1 -1
  244. package/dist/views/index.js +2 -0
  245. package/dist/views/index.js.map +1 -1
  246. package/dist/views/types.d.ts +4 -1
  247. package/dist/views/types.d.ts.map +1 -1
  248. package/dist/views/types.js.map +1 -1
  249. package/package.json +10 -10
@@ -0,0 +1,7 @@
1
+ export type OpThreadReply = {
2
+ uri: string;
3
+ parentUri: string;
4
+ deletedAt: string | null;
5
+ };
6
+ export declare const resolveCanonicalOpThread: (rootUri: string, opReplies: OpThreadReply[]) => string[] | undefined;
7
+ //# sourceMappingURL=op-thread.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"op-thread.d.ts","sourceRoot":"","sources":["../../../src/data-plane/server/op-thread.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,aAAa,GAAG;IAC1B,GAAG,EAAE,MAAM,CAAA;IACX,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB,CAAA;AAMD,eAAO,MAAM,wBAAwB,YAC1B,MAAM,aACJ,aAAa,EAAE,KACzB,MAAM,EAAE,GAAG,SAwDb,CAAA"}
@@ -0,0 +1,71 @@
1
+ import { TID } from '@atproto/common';
2
+ import { AtUri } from '@atproto/syntax';
3
+ // Resolves the oldest contiguous line of OP replies from a thread root.
4
+ // A deleted reply keeps its place while replies below it survive. Once
5
+ // nothing survives below it, only a reply written after the deletion may
6
+ // claim the vacated slot.
7
+ export const resolveCanonicalOpThread = (rootUri, opReplies) => {
8
+ const repliesByParent = new Map();
9
+ const deletedAtByUri = new Map();
10
+ for (const reply of opReplies) {
11
+ const siblings = repliesByParent.get(reply.parentUri) ?? [];
12
+ siblings.push(reply.uri);
13
+ repliesByParent.set(reply.parentUri, siblings);
14
+ if (reply.deletedAt !== null) {
15
+ deletedAtByUri.set(reply.uri, new Date(reply.deletedAt).getTime());
16
+ }
17
+ }
18
+ // Sort replies (TID enables this) for each parent so we can
19
+ // deterministically pick the oldest reply.
20
+ for (const replies of repliesByParent.values()) {
21
+ replies.sort();
22
+ }
23
+ // Track the current path so malformed denormalized rows cannot introduce a
24
+ // cycle into the result.
25
+ const visited = new Set([rootUri]);
26
+ let validOpThread = true;
27
+ const resolve = (parentUri) => {
28
+ let vacatedAt = 0;
29
+ for (const reply of repliesByParent.get(parentUri) ?? []) {
30
+ if (visited.has(reply)) {
31
+ validOpThread = false;
32
+ return [];
33
+ }
34
+ if (vacatedAt !== 0 && !isOpReplyNewerThanTimestamp(reply, vacatedAt)) {
35
+ continue;
36
+ }
37
+ visited.add(reply);
38
+ const below = resolve(reply);
39
+ if (!validOpThread) {
40
+ return [];
41
+ }
42
+ const deletedAt = deletedAtByUri.get(reply);
43
+ if (deletedAt === undefined || below.length > 0) {
44
+ return [reply, ...below];
45
+ }
46
+ visited.delete(reply);
47
+ if (deletedAt > vacatedAt) {
48
+ vacatedAt = deletedAt;
49
+ }
50
+ }
51
+ return [];
52
+ };
53
+ const opThread = [rootUri, ...resolve(rootUri)];
54
+ // A chain of one is just the root; only threads with OP replies count.
55
+ return validOpThread && opThread.length > 1 ? opThread : undefined;
56
+ };
57
+ // isOpReplyNewerThanTimestamp reports whether the post's rkey places its creation after ts.
58
+ // The rkey is a TID, so this is the post's own claimed creation time rather
59
+ // than when we observed it. An unparseable rkey is treated as not newer, which
60
+ // keeps a malformed reply from claiming a vacated slot.
61
+ const isOpReplyNewerThanTimestamp = (uri, ts) => {
62
+ try {
63
+ const tid = TID.fromStr(new AtUri(uri).rkey);
64
+ // TID timestamps are in microseconds.
65
+ return tid.timestamp() / 1000 > ts;
66
+ }
67
+ catch {
68
+ return false;
69
+ }
70
+ };
71
+ //# sourceMappingURL=op-thread.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"op-thread.js","sourceRoot":"","sources":["../../../src/data-plane/server/op-thread.ts"],"names":[],"mappings":"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","sourcesContent":["import { TID } from '@atproto/common'\nimport { AtUri } from '@atproto/syntax'\n\nexport type OpThreadReply = {\n uri: string\n parentUri: string\n deletedAt: string | null\n}\n\n// Resolves the oldest contiguous line of OP replies from a thread root.\n// A deleted reply keeps its place while replies below it survive. Once\n// nothing survives below it, only a reply written after the deletion may\n// claim the vacated slot.\nexport const resolveCanonicalOpThread = (\n rootUri: string,\n opReplies: OpThreadReply[],\n): string[] | undefined => {\n const repliesByParent = new Map<string, string[]>()\n const deletedAtByUri = new Map<string, number>()\n for (const reply of opReplies) {\n const siblings = repliesByParent.get(reply.parentUri) ?? []\n siblings.push(reply.uri)\n repliesByParent.set(reply.parentUri, siblings)\n if (reply.deletedAt !== null) {\n deletedAtByUri.set(reply.uri, new Date(reply.deletedAt).getTime())\n }\n }\n\n // Sort replies (TID enables this) for each parent so we can\n // deterministically pick the oldest reply.\n for (const replies of repliesByParent.values()) {\n replies.sort()\n }\n\n // Track the current path so malformed denormalized rows cannot introduce a\n // cycle into the result.\n const visited = new Set([rootUri])\n let validOpThread = true\n const resolve = (parentUri: string): string[] => {\n let vacatedAt = 0\n for (const reply of repliesByParent.get(parentUri) ?? []) {\n if (visited.has(reply)) {\n validOpThread = false\n return []\n }\n\n if (vacatedAt !== 0 && !isOpReplyNewerThanTimestamp(reply, vacatedAt)) {\n continue\n }\n\n visited.add(reply)\n\n const below = resolve(reply)\n if (!validOpThread) {\n return []\n }\n const deletedAt = deletedAtByUri.get(reply)\n if (deletedAt === undefined || below.length > 0) {\n return [reply, ...below]\n }\n\n visited.delete(reply)\n if (deletedAt > vacatedAt) {\n vacatedAt = deletedAt\n }\n }\n return []\n }\n\n const opThread = [rootUri, ...resolve(rootUri)]\n // A chain of one is just the root; only threads with OP replies count.\n return validOpThread && opThread.length > 1 ? opThread : undefined\n}\n\n// isOpReplyNewerThanTimestamp reports whether the post's rkey places its creation after ts.\n// The rkey is a TID, so this is the post's own claimed creation time rather\n// than when we observed it. An unparseable rkey is treated as not newer, which\n// keeps a malformed reply from claiming a vacated slot.\nconst isOpReplyNewerThanTimestamp = (uri: string, ts: number): boolean => {\n try {\n const tid = TID.fromStr(new AtUri(uri).rkey)\n // TID timestamps are in microseconds.\n return tid.timestamp() / 1000 > ts\n } catch {\n return false\n }\n}\n"]}
@@ -55,11 +55,11 @@ export default (db) => ({
55
55
  cursor,
56
56
  limit,
57
57
  });
58
- const res = await builder.execute();
59
- const dids = res.map(({ subjectDid }) => subjectDid);
58
+ const page = key.page(await builder.execute(), limit);
59
+ const dids = page.items.map(({ subjectDid }) => subjectDid);
60
60
  return {
61
61
  dids,
62
- cursor: key.packFromResult(res),
62
+ cursor: page.cursor,
63
63
  };
64
64
  },
65
65
  });
@@ -1 +1 @@
1
- {"version":3,"file":"activity-subscription.js","sourceRoot":"","sources":["../../../../src/data-plane/server/routes/activity-subscription.ts"],"names":[],"mappings":"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","sourcesContent":["import type { PlainMessage } from '@bufbuild/protobuf'\nimport type { ServiceImpl } from '@connectrpc/connect'\nimport { keyBy } from '@atproto/common'\nimport type { Service } from '../../../proto/bsky_connect.js'\nimport {\n type ActivitySubscription,\n GetActivitySubscriptionsByActorAndSubjectsResponse,\n} from '../../../proto/bsky_pb.js'\nimport { Namespaces } from '../../../stash.js'\nimport type { Database } from '../db/index.js'\nimport { StashKeyKey } from '../db/pagination.js'\n\nexport default (db: Database): Partial<ServiceImpl<typeof Service>> => ({\n async getActivitySubscriptionsByActorAndSubjects(req) {\n const { actorDid, subjectDids } = req\n if (subjectDids.length === 0) {\n return new GetActivitySubscriptionsByActorAndSubjectsResponse({\n subscriptions: [],\n })\n }\n\n const res = await db.db\n .selectFrom('activity_subscription')\n .selectAll()\n .where('creator', '=', actorDid)\n .where('subjectDid', 'in', subjectDids)\n .execute()\n\n const bySubject = keyBy(res, 'subjectDid')\n const subscriptions = subjectDids.map(\n (did): PlainMessage<ActivitySubscription> => {\n const subject = bySubject.get(did)\n if (!subject) {\n return {\n actorDid,\n namespace:\n Namespaces.AppBskyNotificationDefsSubjectActivitySubscription\n .$type,\n key: '',\n post: undefined,\n reply: undefined,\n subjectDid: '',\n }\n }\n\n return {\n actorDid,\n namespace:\n Namespaces.AppBskyNotificationDefsSubjectActivitySubscription.$type,\n key: subject.key,\n post: subject.post ? {} : undefined,\n reply: subject.reply ? {} : undefined,\n subjectDid: subject.subjectDid,\n }\n },\n )\n\n return {\n subscriptions,\n }\n },\n\n async getActivitySubscriptionDids(req) {\n const { actorDid, cursor, limit } = req\n\n let builder = db.db\n .selectFrom('activity_subscription')\n .selectAll()\n .where('creator', '=', actorDid)\n\n const { ref } = db.db.dynamic\n const key = new StashKeyKey(ref('activity_subscription.key'))\n builder = key.paginate(builder, {\n cursor,\n limit,\n })\n const res = await builder.execute()\n const dids = res.map(({ subjectDid }) => subjectDid)\n return {\n dids,\n cursor: key.packFromResult(res),\n }\n },\n})\n"]}
1
+ {"version":3,"file":"activity-subscription.js","sourceRoot":"","sources":["../../../../src/data-plane/server/routes/activity-subscription.ts"],"names":[],"mappings":"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","sourcesContent":["import type { PlainMessage } from '@bufbuild/protobuf'\nimport type { ServiceImpl } from '@connectrpc/connect'\nimport { keyBy } from '@atproto/common'\nimport type { Service } from '../../../proto/bsky_connect.js'\nimport {\n type ActivitySubscription,\n GetActivitySubscriptionsByActorAndSubjectsResponse,\n} from '../../../proto/bsky_pb.js'\nimport { Namespaces } from '../../../stash.js'\nimport type { Database } from '../db/index.js'\nimport { StashKeyKey } from '../db/pagination.js'\n\nexport default (db: Database): Partial<ServiceImpl<typeof Service>> => ({\n async getActivitySubscriptionsByActorAndSubjects(req) {\n const { actorDid, subjectDids } = req\n if (subjectDids.length === 0) {\n return new GetActivitySubscriptionsByActorAndSubjectsResponse({\n subscriptions: [],\n })\n }\n\n const res = await db.db\n .selectFrom('activity_subscription')\n .selectAll()\n .where('creator', '=', actorDid)\n .where('subjectDid', 'in', subjectDids)\n .execute()\n\n const bySubject = keyBy(res, 'subjectDid')\n const subscriptions = subjectDids.map(\n (did): PlainMessage<ActivitySubscription> => {\n const subject = bySubject.get(did)\n if (!subject) {\n return {\n actorDid,\n namespace:\n Namespaces.AppBskyNotificationDefsSubjectActivitySubscription\n .$type,\n key: '',\n post: undefined,\n reply: undefined,\n subjectDid: '',\n }\n }\n\n return {\n actorDid,\n namespace:\n Namespaces.AppBskyNotificationDefsSubjectActivitySubscription.$type,\n key: subject.key,\n post: subject.post ? {} : undefined,\n reply: subject.reply ? {} : undefined,\n subjectDid: subject.subjectDid,\n }\n },\n )\n\n return {\n subscriptions,\n }\n },\n\n async getActivitySubscriptionDids(req) {\n const { actorDid, cursor, limit } = req\n\n let builder = db.db\n .selectFrom('activity_subscription')\n .selectAll()\n .where('creator', '=', actorDid)\n\n const { ref } = db.db.dynamic\n const key = new StashKeyKey(ref('activity_subscription.key'))\n builder = key.paginate(builder, {\n cursor,\n limit,\n })\n const page = key.page(await builder.execute(), limit)\n const dids = page.items.map(({ subjectDid }) => subjectDid)\n return {\n dids,\n cursor: page.cursor,\n }\n },\n})\n"]}
@@ -34,10 +34,10 @@ export default (db) => ({
34
34
  cursor,
35
35
  keyset,
36
36
  });
37
- const blocks = await builder.execute();
37
+ const page = keyset.page(await builder.execute(), limit);
38
38
  return {
39
- blockUris: blocks.map((b) => b.uri),
40
- cursor: keyset.packFromResult(blocks),
39
+ blockUris: page.items.map((b) => b.uri),
40
+ cursor: page.cursor,
41
41
  };
42
42
  },
43
43
  async getBidirectionalBlockViaList(req) {
@@ -92,10 +92,10 @@ export default (db) => ({
92
92
  cursor,
93
93
  keyset,
94
94
  });
95
- const lists = await builder.execute();
95
+ const page = keyset.page(await builder.execute(), limit);
96
96
  return {
97
- listUris: lists.map((l) => l.uri),
98
- cursor: keyset.packFromResult(lists),
97
+ listUris: page.items.map((l) => l.uri),
98
+ cursor: page.cursor,
99
99
  };
100
100
  },
101
101
  });
@@ -1 +1 @@
1
- {"version":3,"file":"blocks.js","sourceRoot":"","sources":["../../../../src/data-plane/server/routes/blocks.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ServiceImpl } from '@connectrpc/connect'\nimport type { Service } from '../../../proto/bsky_connect.js'\nimport type { Database } from '../db/index.js'\nimport { TimeCidKeyset, paginate } from '../db/pagination.js'\n\nexport default (db: Database): Partial<ServiceImpl<typeof Service>> => ({\n async getBidirectionalBlock(req) {\n const { actorDid, targetDid } = req\n const res = await db.db\n .selectFrom('actor_block')\n .where((eb) =>\n eb.or([\n eb.and([\n eb('actor_block.creator', '=', actorDid),\n eb('actor_block.subjectDid', '=', targetDid),\n ]),\n eb.and([\n eb('actor_block.creator', '=', targetDid),\n eb('actor_block.subjectDid', '=', actorDid),\n ]),\n ]),\n )\n .limit(1)\n .selectAll()\n .executeTakeFirst()\n\n return {\n blockUri: res?.uri,\n }\n },\n\n async getBlocks(req) {\n const { actorDid, cursor, limit } = req\n const { ref } = db.db.dynamic\n\n let builder = db.db\n .selectFrom('actor_block')\n .where('actor_block.creator', '=', actorDid)\n .selectAll()\n\n const keyset = new TimeCidKeyset(\n ref('actor_block.sortAt'),\n ref('actor_block.cid'),\n )\n builder = paginate(builder, {\n limit,\n cursor,\n keyset,\n })\n\n const blocks = await builder.execute()\n return {\n blockUris: blocks.map((b) => b.uri),\n cursor: keyset.packFromResult(blocks),\n }\n },\n\n async getBidirectionalBlockViaList(req) {\n const { actorDid, targetDid } = req\n const res = await db.db\n .selectFrom('list_block')\n .innerJoin('list_item', 'list_item.listUri', 'list_block.subjectUri')\n .where((eb) =>\n eb.or([\n eb.and([\n eb('list_block.creator', '=', actorDid),\n eb('list_item.subjectDid', '=', targetDid),\n ]),\n eb.and([\n eb('list_block.creator', '=', targetDid),\n eb('list_item.subjectDid', '=', actorDid),\n ]),\n ]),\n )\n .limit(1)\n .selectAll('list_block')\n .executeTakeFirst()\n\n return {\n listUri: res?.subjectUri,\n }\n },\n\n async getBlocklistSubscription(req) {\n const { actorDid, listUri } = req\n const res = await db.db\n .selectFrom('list_block')\n .where('creator', '=', actorDid)\n .where('subjectUri', '=', listUri)\n .selectAll()\n .limit(1)\n .executeTakeFirst()\n return {\n listblockUri: res?.uri,\n }\n },\n\n async getBlocklistSubscriptions(req) {\n const { actorDid, limit, cursor } = req\n const { ref } = db.db.dynamic\n let builder = db.db\n .selectFrom('list')\n .where(({ exists }) =>\n exists(\n db.db\n .selectFrom('list_block')\n .where('list_block.creator', '=', actorDid)\n .whereRef('list_block.subjectUri', '=', ref('list.uri'))\n .selectAll(),\n ),\n )\n .selectAll('list')\n\n const keyset = new TimeCidKeyset(ref('list.createdAt'), ref('list.cid'))\n builder = paginate(builder, {\n limit,\n cursor,\n keyset,\n })\n const lists = await builder.execute()\n\n return {\n listUris: lists.map((l) => l.uri),\n cursor: keyset.packFromResult(lists),\n }\n },\n})\n"]}
1
+ {"version":3,"file":"blocks.js","sourceRoot":"","sources":["../../../../src/data-plane/server/routes/blocks.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ServiceImpl } from '@connectrpc/connect'\nimport type { Service } from '../../../proto/bsky_connect.js'\nimport type { Database } from '../db/index.js'\nimport { TimeCidKeyset, paginate } from '../db/pagination.js'\n\nexport default (db: Database): Partial<ServiceImpl<typeof Service>> => ({\n async getBidirectionalBlock(req) {\n const { actorDid, targetDid } = req\n const res = await db.db\n .selectFrom('actor_block')\n .where((eb) =>\n eb.or([\n eb.and([\n eb('actor_block.creator', '=', actorDid),\n eb('actor_block.subjectDid', '=', targetDid),\n ]),\n eb.and([\n eb('actor_block.creator', '=', targetDid),\n eb('actor_block.subjectDid', '=', actorDid),\n ]),\n ]),\n )\n .limit(1)\n .selectAll()\n .executeTakeFirst()\n\n return {\n blockUri: res?.uri,\n }\n },\n\n async getBlocks(req) {\n const { actorDid, cursor, limit } = req\n const { ref } = db.db.dynamic\n\n let builder = db.db\n .selectFrom('actor_block')\n .where('actor_block.creator', '=', actorDid)\n .selectAll()\n\n const keyset = new TimeCidKeyset(\n ref('actor_block.sortAt'),\n ref('actor_block.cid'),\n )\n builder = paginate(builder, {\n limit,\n cursor,\n keyset,\n })\n\n const page = keyset.page(await builder.execute(), limit)\n return {\n blockUris: page.items.map((b) => b.uri),\n cursor: page.cursor,\n }\n },\n\n async getBidirectionalBlockViaList(req) {\n const { actorDid, targetDid } = req\n const res = await db.db\n .selectFrom('list_block')\n .innerJoin('list_item', 'list_item.listUri', 'list_block.subjectUri')\n .where((eb) =>\n eb.or([\n eb.and([\n eb('list_block.creator', '=', actorDid),\n eb('list_item.subjectDid', '=', targetDid),\n ]),\n eb.and([\n eb('list_block.creator', '=', targetDid),\n eb('list_item.subjectDid', '=', actorDid),\n ]),\n ]),\n )\n .limit(1)\n .selectAll('list_block')\n .executeTakeFirst()\n\n return {\n listUri: res?.subjectUri,\n }\n },\n\n async getBlocklistSubscription(req) {\n const { actorDid, listUri } = req\n const res = await db.db\n .selectFrom('list_block')\n .where('creator', '=', actorDid)\n .where('subjectUri', '=', listUri)\n .selectAll()\n .limit(1)\n .executeTakeFirst()\n return {\n listblockUri: res?.uri,\n }\n },\n\n async getBlocklistSubscriptions(req) {\n const { actorDid, limit, cursor } = req\n const { ref } = db.db.dynamic\n let builder = db.db\n .selectFrom('list')\n .where(({ exists }) =>\n exists(\n db.db\n .selectFrom('list_block')\n .where('list_block.creator', '=', actorDid)\n .whereRef('list_block.subjectUri', '=', ref('list.uri'))\n .selectAll(),\n ),\n )\n .selectAll('list')\n\n const keyset = new TimeCidKeyset(ref('list.createdAt'), ref('list.cid'))\n builder = paginate(builder, {\n limit,\n cursor,\n keyset,\n })\n const page = keyset.page(await builder.execute(), limit)\n\n return {\n listUris: page.items.map((l) => l.uri),\n cursor: page.cursor,\n }\n },\n})\n"]}
@@ -16,13 +16,13 @@ export default (db) => ({
16
16
  cursor,
17
17
  limit,
18
18
  });
19
- const res = await builder.execute();
19
+ const page = key.page(await builder.execute(), limit);
20
20
  return {
21
- bookmarks: res.map((b) => ({
21
+ bookmarks: page.items.map((b) => ({
22
22
  key: b.key,
23
23
  subject: b.subjectUri,
24
24
  })),
25
- cursor: key.packFromResult(res),
25
+ cursor: page.cursor,
26
26
  };
27
27
  },
28
28
  async getBookmarksByActorAndSubjects(req) {
@@ -1 +1 @@
1
- {"version":3,"file":"bookmarks.js","sourceRoot":"","sources":["../../../../src/data-plane/server/routes/bookmarks.ts"],"names":[],"mappings":"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","sourcesContent":["import { type PlainMessage, Timestamp } from '@bufbuild/protobuf'\nimport type { ServiceImpl } from '@connectrpc/connect'\nimport { keyBy } from '@atproto/common'\nimport type { Service } from '../../../proto/bsky_connect.js'\nimport {\n type Bookmark,\n GetBookmarksByActorAndSubjectsResponse,\n} from '../../../proto/bsky_pb.js'\nimport { Namespaces } from '../../../stash.js'\nimport type { Database } from '../db/index.js'\nimport { StashKeyKey } from '../db/pagination.js'\n\nexport default (db: Database): Partial<ServiceImpl<typeof Service>> => ({\n async getActorBookmarks(req) {\n const { actorDid, cursor, limit } = req\n const { ref } = db.db.dynamic\n\n let builder = db.db\n .selectFrom('bookmark')\n .where('bookmark.creator', '=', actorDid)\n .selectAll()\n\n const key = new StashKeyKey(ref('bookmark.key'))\n builder = key.paginate(builder, {\n cursor,\n limit,\n })\n\n const res = await builder.execute()\n return {\n bookmarks: res.map((b) => ({\n key: b.key,\n subject: b.subjectUri,\n })),\n cursor: key.packFromResult(res),\n }\n },\n\n async getBookmarksByActorAndSubjects(req) {\n const { actorDid, uris } = req\n\n if (uris.length === 0) {\n return new GetBookmarksByActorAndSubjectsResponse({\n bookmarks: [],\n })\n }\n\n const res = await db.db\n .selectFrom('bookmark')\n .where('bookmark.creator', '=', actorDid)\n .where('bookmark.subjectUri', 'in', uris)\n .selectAll()\n .execute()\n\n const byUri = keyBy(res, 'subjectUri')\n const bookmarks = uris.map((did): PlainMessage<Bookmark> => {\n const bookmark = byUri.get(did)\n if (!bookmark) {\n return {\n ref: undefined,\n subjectUri: '',\n subjectCid: '',\n indexedAt: undefined,\n }\n }\n\n return {\n ref: {\n actorDid,\n namespace: Namespaces.AppBskyBookmarkDefsBookmark.$type,\n key: bookmark.key,\n },\n subjectUri: bookmark.subjectUri,\n subjectCid: bookmark.subjectCid,\n indexedAt: Timestamp.fromDate(new Date(bookmark.indexedAt)),\n }\n })\n\n return {\n bookmarks,\n }\n },\n})\n"]}
1
+ {"version":3,"file":"bookmarks.js","sourceRoot":"","sources":["../../../../src/data-plane/server/routes/bookmarks.ts"],"names":[],"mappings":"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","sourcesContent":["import { type PlainMessage, Timestamp } from '@bufbuild/protobuf'\nimport type { ServiceImpl } from '@connectrpc/connect'\nimport { keyBy } from '@atproto/common'\nimport type { Service } from '../../../proto/bsky_connect.js'\nimport {\n type Bookmark,\n GetBookmarksByActorAndSubjectsResponse,\n} from '../../../proto/bsky_pb.js'\nimport { Namespaces } from '../../../stash.js'\nimport type { Database } from '../db/index.js'\nimport { StashKeyKey } from '../db/pagination.js'\n\nexport default (db: Database): Partial<ServiceImpl<typeof Service>> => ({\n async getActorBookmarks(req) {\n const { actorDid, cursor, limit } = req\n const { ref } = db.db.dynamic\n\n let builder = db.db\n .selectFrom('bookmark')\n .where('bookmark.creator', '=', actorDid)\n .selectAll()\n\n const key = new StashKeyKey(ref('bookmark.key'))\n builder = key.paginate(builder, {\n cursor,\n limit,\n })\n\n const page = key.page(await builder.execute(), limit)\n return {\n bookmarks: page.items.map((b) => ({\n key: b.key,\n subject: b.subjectUri,\n })),\n cursor: page.cursor,\n }\n },\n\n async getBookmarksByActorAndSubjects(req) {\n const { actorDid, uris } = req\n\n if (uris.length === 0) {\n return new GetBookmarksByActorAndSubjectsResponse({\n bookmarks: [],\n })\n }\n\n const res = await db.db\n .selectFrom('bookmark')\n .where('bookmark.creator', '=', actorDid)\n .where('bookmark.subjectUri', 'in', uris)\n .selectAll()\n .execute()\n\n const byUri = keyBy(res, 'subjectUri')\n const bookmarks = uris.map((did): PlainMessage<Bookmark> => {\n const bookmark = byUri.get(did)\n if (!bookmark) {\n return {\n ref: undefined,\n subjectUri: '',\n subjectCid: '',\n indexedAt: undefined,\n }\n }\n\n return {\n ref: {\n actorDid,\n namespace: Namespaces.AppBskyBookmarkDefsBookmark.$type,\n key: bookmark.key,\n },\n subjectUri: bookmark.subjectUri,\n subjectCid: bookmark.subjectCid,\n indexedAt: Timestamp.fromDate(new Date(bookmark.indexedAt)),\n }\n })\n\n return {\n bookmarks,\n }\n },\n})\n"]}
@@ -13,15 +13,15 @@ export default (db) => ({
13
13
  cursor,
14
14
  limit,
15
15
  });
16
- const res = await builder.execute();
16
+ const page = key.page(await builder.execute(), limit);
17
17
  return {
18
- drafts: res.map((d) => ({
18
+ drafts: page.items.map((d) => ({
19
19
  key: d.key,
20
20
  payload: Buffer.from(d.payload),
21
21
  createdAt: Timestamp.fromDate(new Date(d.createdAt)),
22
22
  updatedAt: Timestamp.fromDate(new Date(d.updatedAt)),
23
23
  })),
24
- cursor: key.packFromResult(res),
24
+ cursor: page.cursor,
25
25
  };
26
26
  },
27
27
  });
@@ -1 +1 @@
1
- {"version":3,"file":"drafts.js","sourceRoot":"","sources":["../../../../src/data-plane/server/routes/drafts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAKjE,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAErD,eAAe,CAAC,EAAY,EAAwC,EAAE,CAAC,CAAC;IACtE,KAAK,CAAC,cAAc,CAAC,GAAG;QACtB,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,CAAA;QACvC,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,OAAO,CAAA;QAE7B,IAAI,OAAO,GAAG,EAAE,CAAC,EAAE;aAChB,UAAU,CAAC,OAAO,CAAC;aACnB,KAAK,CAAC,eAAe,EAAE,GAAG,EAAE,QAAQ,CAAC;aACrC,SAAS,EAAE,CAAA;QAEd,MAAM,GAAG,GAAG,IAAI,eAAe,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAA;QACvD,OAAO,GAAG,GAAG,CAAC,QAAQ,CAAC,OAAO,EAAE;YAC9B,MAAM;YACN,KAAK;SACN,CAAC,CAAA;QAEF,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,CAAA;QACnC,OAAO;YACL,MAAM,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAA2B,EAAE,CAAC,CAAC;gBAC/C,GAAG,EAAE,CAAC,CAAC,GAAG;gBACV,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC;gBAC/B,SAAS,EAAE,SAAS,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;gBACpD,SAAS,EAAE,SAAS,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;aACrD,CAAC,CAAC;YACH,MAAM,EAAE,GAAG,CAAC,cAAc,CAAC,GAAG,CAAC;SAChC,CAAA;IACH,CAAC;CACF,CAAC,CAAA","sourcesContent":["import { type PlainMessage, Timestamp } from '@bufbuild/protobuf'\nimport type { ServiceImpl } from '@connectrpc/connect'\nimport type { Service } from '../../../proto/bsky_connect.js'\nimport type { DraftInfo } from '../../../proto/bsky_pb.js'\nimport type { Database } from '../db/index.js'\nimport { IsoUpdatedAtKey } from '../db/pagination.js'\n\nexport default (db: Database): Partial<ServiceImpl<typeof Service>> => ({\n async getActorDrafts(req) {\n const { actorDid, cursor, limit } = req\n const { ref } = db.db.dynamic\n\n let builder = db.db\n .selectFrom('draft')\n .where('draft.creator', '=', actorDid)\n .selectAll()\n\n const key = new IsoUpdatedAtKey(ref('draft.updatedAt'))\n builder = key.paginate(builder, {\n cursor,\n limit,\n })\n\n const res = await builder.execute()\n return {\n drafts: res.map((d): PlainMessage<DraftInfo> => ({\n key: d.key,\n payload: Buffer.from(d.payload),\n createdAt: Timestamp.fromDate(new Date(d.createdAt)),\n updatedAt: Timestamp.fromDate(new Date(d.updatedAt)),\n })),\n cursor: key.packFromResult(res),\n }\n },\n})\n"]}
1
+ {"version":3,"file":"drafts.js","sourceRoot":"","sources":["../../../../src/data-plane/server/routes/drafts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAKjE,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAErD,eAAe,CAAC,EAAY,EAAwC,EAAE,CAAC,CAAC;IACtE,KAAK,CAAC,cAAc,CAAC,GAAG;QACtB,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,CAAA;QACvC,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC,OAAO,CAAA;QAE7B,IAAI,OAAO,GAAG,EAAE,CAAC,EAAE;aAChB,UAAU,CAAC,OAAO,CAAC;aACnB,KAAK,CAAC,eAAe,EAAE,GAAG,EAAE,QAAQ,CAAC;aACrC,SAAS,EAAE,CAAA;QAEd,MAAM,GAAG,GAAG,IAAI,eAAe,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAA;QACvD,OAAO,GAAG,GAAG,CAAC,QAAQ,CAAC,OAAO,EAAE;YAC9B,MAAM;YACN,KAAK;SACN,CAAC,CAAA;QAEF,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,MAAM,OAAO,CAAC,OAAO,EAAE,EAAE,KAAK,CAAC,CAAA;QACrD,OAAO;YACL,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAA2B,EAAE,CAAC,CAAC;gBACtD,GAAG,EAAE,CAAC,CAAC,GAAG;gBACV,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC;gBAC/B,SAAS,EAAE,SAAS,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;gBACpD,SAAS,EAAE,SAAS,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;aACrD,CAAC,CAAC;YACH,MAAM,EAAE,IAAI,CAAC,MAAM;SACpB,CAAA;IACH,CAAC;CACF,CAAC,CAAA","sourcesContent":["import { type PlainMessage, Timestamp } from '@bufbuild/protobuf'\nimport type { ServiceImpl } from '@connectrpc/connect'\nimport type { Service } from '../../../proto/bsky_connect.js'\nimport type { DraftInfo } from '../../../proto/bsky_pb.js'\nimport type { Database } from '../db/index.js'\nimport { IsoUpdatedAtKey } from '../db/pagination.js'\n\nexport default (db: Database): Partial<ServiceImpl<typeof Service>> => ({\n async getActorDrafts(req) {\n const { actorDid, cursor, limit } = req\n const { ref } = db.db.dynamic\n\n let builder = db.db\n .selectFrom('draft')\n .where('draft.creator', '=', actorDid)\n .selectAll()\n\n const key = new IsoUpdatedAtKey(ref('draft.updatedAt'))\n builder = key.paginate(builder, {\n cursor,\n limit,\n })\n\n const page = key.page(await builder.execute(), limit)\n return {\n drafts: page.items.map((d): PlainMessage<DraftInfo> => ({\n key: d.key,\n payload: Buffer.from(d.payload),\n createdAt: Timestamp.fromDate(new Date(d.createdAt)),\n updatedAt: Timestamp.fromDate(new Date(d.updatedAt)),\n })),\n cursor: page.cursor,\n }\n },\n})\n"]}
@@ -13,10 +13,10 @@ export default (db) => ({
13
13
  cursor,
14
14
  keyset,
15
15
  });
16
- const feeds = await builder.execute();
16
+ const page = keyset.page(await builder.execute(), limit);
17
17
  return {
18
- uris: feeds.map((f) => f.uri),
19
- cursor: keyset.packFromResult(feeds),
18
+ uris: page.items.map((f) => f.uri),
19
+ cursor: page.cursor,
20
20
  };
21
21
  },
22
22
  async getSuggestedFeeds(req) {
@@ -24,19 +24,21 @@ export default (db) => ({
24
24
  .selectFrom('suggested_feed')
25
25
  .orderBy('suggested_feed.order', 'asc')
26
26
  .$if(!!req.cursor, (q) => q.where('order', '>', parseInt(req.cursor, 10)))
27
- .limit(req.limit || 50)
27
+ .limit((req.limit || 50) + 1)
28
28
  .selectAll()
29
29
  .execute();
30
+ const limit = req.limit || 50;
31
+ const items = feeds.slice(0, limit);
30
32
  return {
31
- uris: feeds.map((f) => f.uri),
32
- cursor: feeds.at(-1)?.order.toString(),
33
+ uris: items.map((f) => f.uri),
34
+ cursor: feeds.length > limit ? items.at(-1)?.order.toString() : undefined,
33
35
  };
34
36
  },
35
37
  async searchFeedGenerators(req) {
36
38
  return searchFeedGeneratorsImpl(db, req.query, req.limit);
37
39
  },
38
40
  async searchFeedGeneratorsV2(req) {
39
- const { uris, cursor } = await searchFeedGeneratorsImpl(db, req.params?.query ?? '', req.params?.limit ?? 25);
41
+ const { uris, cursor } = await searchFeedGeneratorsImpl(db, req.params?.query ?? '', req.params?.limit ?? 25, req.params?.cursor);
40
42
  return {
41
43
  feedGenerators: uris.map((uri) => ({ uri, score: 0 })),
42
44
  pageInfo: { cursor: cursor ?? '', hitsTotal: 0n },
@@ -46,7 +48,7 @@ export default (db) => ({
46
48
  throw new Error('unimplemented');
47
49
  },
48
50
  });
49
- const searchFeedGeneratorsImpl = async (db, query, limit) => {
51
+ const searchFeedGeneratorsImpl = async (db, query, limit, cursor) => {
50
52
  const { ref } = db.db.dynamic;
51
53
  const trimmed = query.trim();
52
54
  let builder = db.db
@@ -54,11 +56,11 @@ const searchFeedGeneratorsImpl = async (db, query, limit) => {
54
56
  .$if(!!trimmed, (q) => q.where('displayName', 'ilike', `%${trimmed}%`))
55
57
  .selectAll();
56
58
  const keyset = new TimeCidKeyset(ref('feed_generator.createdAt'), ref('feed_generator.cid'));
57
- builder = paginate(builder, { limit, keyset });
58
- const feeds = await builder.execute();
59
+ builder = paginate(builder, { limit, cursor, keyset });
60
+ const page = keyset.page(await builder.execute(), limit);
59
61
  return {
60
- uris: feeds.map((f) => f.uri),
61
- cursor: keyset.packFromResult(feeds),
62
+ uris: page.items.map((f) => f.uri),
63
+ cursor: page.cursor,
62
64
  };
63
65
  };
64
66
  //# sourceMappingURL=feed-gens.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"feed-gens.js","sourceRoot":"","sources":["../../../../src/data-plane/server/routes/feed-gens.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ServiceImpl } from '@connectrpc/connect'\nimport type { Service } from '../../../proto/bsky_connect.js'\nimport type { Database } from '../db/index.js'\nimport { TimeCidKeyset, paginate } from '../db/pagination.js'\n\nexport default (db: Database): Partial<ServiceImpl<typeof Service>> => ({\n async getActorFeeds(req) {\n const { actorDid, limit, cursor } = req\n\n const { ref } = db.db.dynamic\n let builder = db.db\n .selectFrom('feed_generator')\n .selectAll()\n .where('feed_generator.creator', '=', actorDid)\n\n const keyset = new TimeCidKeyset(\n ref('feed_generator.createdAt'),\n ref('feed_generator.cid'),\n )\n builder = paginate(builder, {\n limit,\n cursor,\n keyset,\n })\n const feeds = await builder.execute()\n\n return {\n uris: feeds.map((f) => f.uri),\n cursor: keyset.packFromResult(feeds),\n }\n },\n\n async getSuggestedFeeds(req) {\n const feeds = await db.db\n .selectFrom('suggested_feed')\n .orderBy('suggested_feed.order', 'asc')\n .$if(!!req.cursor, (q) => q.where('order', '>', parseInt(req.cursor, 10)))\n .limit(req.limit || 50)\n .selectAll()\n .execute()\n return {\n uris: feeds.map((f) => f.uri),\n cursor: feeds.at(-1)?.order.toString(),\n }\n },\n\n async searchFeedGenerators(req) {\n return searchFeedGeneratorsImpl(db, req.query, req.limit)\n },\n\n async searchFeedGeneratorsV2(req) {\n const { uris, cursor } = await searchFeedGeneratorsImpl(\n db,\n req.params?.query ?? '',\n req.params?.limit ?? 25,\n )\n return {\n feedGenerators: uris.map((uri) => ({ uri, score: 0 })),\n pageInfo: { cursor: cursor ?? '', hitsTotal: 0n },\n }\n },\n\n async getFeedGeneratorStatus() {\n throw new Error('unimplemented')\n },\n})\n\nconst searchFeedGeneratorsImpl = async (\n db: Database,\n query: string,\n limit: number,\n) => {\n const { ref } = db.db.dynamic\n const trimmed = query.trim()\n let builder = db.db\n .selectFrom('feed_generator')\n .$if(!!trimmed, (q) => q.where('displayName', 'ilike', `%${trimmed}%`))\n .selectAll()\n const keyset = new TimeCidKeyset(\n ref('feed_generator.createdAt'),\n ref('feed_generator.cid'),\n )\n builder = paginate(builder, { limit, keyset })\n const feeds = await builder.execute()\n return {\n uris: feeds.map((f) => f.uri),\n cursor: keyset.packFromResult(feeds),\n }\n}\n"]}
1
+ {"version":3,"file":"feed-gens.js","sourceRoot":"","sources":["../../../../src/data-plane/server/routes/feed-gens.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ServiceImpl } from '@connectrpc/connect'\nimport type { Service } from '../../../proto/bsky_connect.js'\nimport type { Database } from '../db/index.js'\nimport { TimeCidKeyset, paginate } from '../db/pagination.js'\n\nexport default (db: Database): Partial<ServiceImpl<typeof Service>> => ({\n async getActorFeeds(req) {\n const { actorDid, limit, cursor } = req\n\n const { ref } = db.db.dynamic\n let builder = db.db\n .selectFrom('feed_generator')\n .selectAll()\n .where('feed_generator.creator', '=', actorDid)\n\n const keyset = new TimeCidKeyset(\n ref('feed_generator.createdAt'),\n ref('feed_generator.cid'),\n )\n builder = paginate(builder, {\n limit,\n cursor,\n keyset,\n })\n const page = keyset.page(await builder.execute(), limit)\n\n return {\n uris: page.items.map((f) => f.uri),\n cursor: page.cursor,\n }\n },\n\n async getSuggestedFeeds(req) {\n const feeds = await db.db\n .selectFrom('suggested_feed')\n .orderBy('suggested_feed.order', 'asc')\n .$if(!!req.cursor, (q) => q.where('order', '>', parseInt(req.cursor, 10)))\n .limit((req.limit || 50) + 1)\n .selectAll()\n .execute()\n const limit = req.limit || 50\n const items = feeds.slice(0, limit)\n return {\n uris: items.map((f) => f.uri),\n cursor: feeds.length > limit ? items.at(-1)?.order.toString() : undefined,\n }\n },\n\n async searchFeedGenerators(req) {\n return searchFeedGeneratorsImpl(db, req.query, req.limit)\n },\n\n async searchFeedGeneratorsV2(req) {\n const { uris, cursor } = await searchFeedGeneratorsImpl(\n db,\n req.params?.query ?? '',\n req.params?.limit ?? 25,\n req.params?.cursor,\n )\n return {\n feedGenerators: uris.map((uri) => ({ uri, score: 0 })),\n pageInfo: { cursor: cursor ?? '', hitsTotal: 0n },\n }\n },\n\n async getFeedGeneratorStatus() {\n throw new Error('unimplemented')\n },\n})\n\nconst searchFeedGeneratorsImpl = async (\n db: Database,\n query: string,\n limit: number,\n cursor?: string,\n) => {\n const { ref } = db.db.dynamic\n const trimmed = query.trim()\n let builder = db.db\n .selectFrom('feed_generator')\n .$if(!!trimmed, (q) => q.where('displayName', 'ilike', `%${trimmed}%`))\n .selectAll()\n const keyset = new TimeCidKeyset(\n ref('feed_generator.createdAt'),\n ref('feed_generator.cid'),\n )\n builder = paginate(builder, { limit, cursor, keyset })\n const page = keyset.page(await builder.execute(), limit)\n return {\n uris: page.items.map((f) => f.uri),\n cursor: page.cursor,\n }\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"feeds.d.ts","sourceRoot":"","sources":["../../../../src/data-plane/server/routes/feeds.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gCAAgC,CAAA;AAE7D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;;0BAG9B,EAAE,EAAE,QAAQ,GAAG,OAAO,CAAC,WAAW,CAAC,OAAO,OAAO,CAAC,CAAC"}
1
+ {"version":3,"file":"feeds.d.ts","sourceRoot":"","sources":["../../../../src/data-plane/server/routes/feeds.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACtD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gCAAgC,CAAA;AAE7D,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;;0BAG9B,EAAE,EAAE,QAAQ,GAAG,OAAO,CAAC,WAAW,CAAC,OAAO,OAAO,CAAC,CAAC"}
@@ -1,3 +1,4 @@
1
+ import assert from 'node:assert';
1
2
  import { FeedType } from '../../../proto/bsky_pb.js';
2
3
  import { TimeCidKeyset, paginate } from '../db/pagination.js';
3
4
  export default (db) => ({
@@ -52,10 +53,10 @@ export default (db) => ({
52
53
  cursor,
53
54
  keyset,
54
55
  });
55
- const feedItems = await builder.execute();
56
+ const page = keyset.page(await builder.execute(), limit);
56
57
  return {
57
- items: feedItems.map(feedItemFromRow),
58
- cursor: keyset.packFromResult(feedItems),
58
+ items: page.items.map(feedItemFromRow),
59
+ cursor: page.cursor,
59
60
  };
60
61
  },
61
62
  async getTimeline(req) {
@@ -78,7 +79,7 @@ export default (db) => ({
78
79
  .where('feed_item.originatorDid', '=', actorDid)
79
80
  .selectAll('feed_item');
80
81
  selfQb = paginate(selfQb, {
81
- limit: Math.min(limit, 10),
82
+ limit,
82
83
  cursor,
83
84
  keyset,
84
85
  tryIndex: true,
@@ -87,19 +88,39 @@ export default (db) => ({
87
88
  followQb.execute(),
88
89
  selfQb.execute(),
89
90
  ]);
90
- const feedItems = [...followRes, ...selfRes]
91
- .sort((a, b) => {
91
+ // Use own posts to fill space left by followed-account posts. When followed-account
92
+ // posts already fill the page, still allow up to 10 own posts into the merged results.
93
+ const selfLimit = Math.max(
94
+ // Allow up to 10 own posts even when followed-account posts can fill the page.
95
+ Math.min(limit, 10),
96
+ // Allow enough own posts to fill the slots not occupied by followed-account posts.
97
+ limit - Math.min(followRes.length, limit));
98
+ // The extra own post proves that another page exists, but is not part of this page.
99
+ const selfHasMore = selfRes.length > selfLimit;
100
+ const selfItems = selfRes.slice(0, selfLimit);
101
+ const feedItems = [...followRes, ...selfItems].sort((a, b) => {
92
102
  if (a.sortAt > b.sortAt)
93
103
  return -1;
94
104
  if (a.sortAt < b.sortAt)
95
105
  return 1;
96
106
  return a.cid > b.cid ? -1 : 1;
97
- })
98
- .slice(0, limit);
99
- return {
100
- items: feedItems.map(feedItemFromRow),
101
- cursor: keyset.packFromResult(feedItems),
102
- };
107
+ });
108
+ const page = keyset.page(feedItems, limit);
109
+ if (page.items.length === 0) {
110
+ return { items: [], cursor: undefined };
111
+ }
112
+ const items = page.items.map(feedItemFromRow);
113
+ // The combined results exceeded the requested limit.
114
+ if (page.cursor) {
115
+ return { items, cursor: page.cursor };
116
+ }
117
+ // Own posts exceeded their separate cap, but the combined results did not exceed the requested limit.
118
+ if (selfHasMore) {
119
+ const lastItem = page.items.at(-1);
120
+ assert(lastItem);
121
+ return { items, cursor: keyset.packFromResult(lastItem) };
122
+ }
123
+ return { items, cursor: undefined };
103
124
  },
104
125
  async getListFeed(req) {
105
126
  const { listUri, cursor, limit } = req;
@@ -116,10 +137,10 @@ export default (db) => ({
116
137
  keyset,
117
138
  tryIndex: true,
118
139
  });
119
- const feedItems = await builder.execute();
140
+ const page = keyset.page(await builder.execute(), limit);
120
141
  return {
121
- items: feedItems.map((item) => ({ uri: item.uri, cid: item.cid })),
122
- cursor: keyset.packFromResult(feedItems),
142
+ items: page.items.map((item) => ({ uri: item.uri, cid: item.cid })),
143
+ cursor: page.cursor,
123
144
  };
124
145
  },
125
146
  });
@@ -1 +1 @@
1
- {"version":3,"file":"feeds.js","sourceRoot":"","sources":["../../../../src/data-plane/server/routes/feeds.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ServiceImpl } from '@connectrpc/connect'\nimport type { Service } from '../../../proto/bsky_connect.js'\nimport { FeedType } from '../../../proto/bsky_pb.js'\nimport type { Database } from '../db/index.js'\nimport { TimeCidKeyset, paginate } from '../db/pagination.js'\n\nexport default (db: Database): Partial<ServiceImpl<typeof Service>> => ({\n async getAuthorFeed(req) {\n const { actorDid, limit, cursor, feedType } = req\n const { ref } = db.db.dynamic\n\n // defaults to posts, reposts, and replies\n let builder = db.db\n .selectFrom('feed_item')\n .innerJoin('post', 'post.uri', 'feed_item.postUri')\n .selectAll('feed_item')\n .where('originatorDid', '=', actorDid)\n\n if (feedType === FeedType.POSTS_WITH_MEDIA) {\n builder = builder\n // only your own posts\n .where('type', '=', 'post')\n // only posts with media\n .where((eb) =>\n eb.or([\n eb.exists(\n eb\n .selectFrom('post_embed_image')\n .select('post_embed_image.postUri')\n .whereRef('post_embed_image.postUri', '=', 'feed_item.postUri'),\n ),\n eb.exists(\n eb\n .selectFrom('post_embed_gallery_image')\n .select('post_embed_gallery_image.postUri')\n .whereRef(\n 'post_embed_gallery_image.postUri',\n '=',\n 'feed_item.postUri',\n ),\n ),\n ]),\n )\n } else if (feedType === FeedType.POSTS_WITH_VIDEO) {\n builder = builder\n // only your own posts\n .where('type', '=', 'post')\n // only posts with video\n .where(({ eb, exists }) =>\n exists(\n eb\n .selectFrom('post_embed_video')\n .select('post_embed_video.postUri')\n .whereRef('post_embed_video.postUri', '=', 'feed_item.postUri'),\n ),\n )\n } else if (feedType === FeedType.POSTS_NO_REPLIES) {\n builder = builder.where((eb) =>\n eb.or([eb('post.replyParent', 'is', null), eb('type', '=', 'repost')]),\n )\n } else if (feedType === FeedType.POSTS_AND_AUTHOR_THREADS) {\n builder = builder.where((eb) =>\n eb.or([\n eb('type', '=', 'repost'),\n eb('post.replyParent', 'is', null),\n eb('post.replyRoot', 'like', `at://${actorDid}/%`),\n ]),\n )\n }\n\n const keyset = new TimeCidKeyset(\n ref('feed_item.sortAt'),\n ref('feed_item.cid'),\n )\n\n builder = paginate(builder, {\n limit,\n cursor,\n keyset,\n })\n\n const feedItems = await builder.execute()\n\n return {\n items: feedItems.map(feedItemFromRow),\n cursor: keyset.packFromResult(feedItems),\n }\n },\n\n async getTimeline(req) {\n const { actorDid, limit, cursor } = req\n const { ref } = db.db.dynamic\n\n const keyset = new TimeCidKeyset(\n ref('feed_item.sortAt'),\n ref('feed_item.cid'),\n )\n\n let followQb = db.db\n .selectFrom('feed_item')\n .innerJoin('follow', 'follow.subjectDid', 'feed_item.originatorDid')\n .where('follow.creator', '=', actorDid)\n .selectAll('feed_item')\n\n followQb = paginate(followQb, {\n limit,\n cursor,\n keyset,\n tryIndex: true,\n })\n\n let selfQb = db.db\n .selectFrom('feed_item')\n .where('feed_item.originatorDid', '=', actorDid)\n .selectAll('feed_item')\n\n selfQb = paginate(selfQb, {\n limit: Math.min(limit, 10),\n cursor,\n keyset,\n tryIndex: true,\n })\n\n const [followRes, selfRes] = await Promise.all([\n followQb.execute(),\n selfQb.execute(),\n ])\n\n const feedItems = [...followRes, ...selfRes]\n .sort((a, b) => {\n if (a.sortAt > b.sortAt) return -1\n if (a.sortAt < b.sortAt) return 1\n return a.cid > b.cid ? -1 : 1\n })\n .slice(0, limit)\n\n return {\n items: feedItems.map(feedItemFromRow),\n cursor: keyset.packFromResult(feedItems),\n }\n },\n\n async getListFeed(req) {\n const { listUri, cursor, limit } = req\n const { ref } = db.db.dynamic\n\n let builder = db.db\n .selectFrom('post')\n .selectAll('post')\n .innerJoin('list_item', 'list_item.subjectDid', 'post.creator')\n .where('list_item.listUri', '=', listUri)\n\n const keyset = new TimeCidKeyset(ref('post.sortAt'), ref('post.cid'))\n builder = paginate(builder, {\n limit,\n cursor,\n keyset,\n tryIndex: true,\n })\n const feedItems = await builder.execute()\n\n return {\n items: feedItems.map((item) => ({ uri: item.uri, cid: item.cid })),\n cursor: keyset.packFromResult(feedItems),\n }\n },\n})\n\n// @NOTE does not support additional fields in the protos specific to author feeds\n// and timelines. at the time of writing, hydration/view implementations do not rely on them.\nconst feedItemFromRow = (row: { postUri: string; uri: string }) => {\n return {\n uri: row.postUri,\n repost: row.uri === row.postUri ? undefined : row.uri,\n }\n}\n"]}
1
+ {"version":3,"file":"feeds.js","sourceRoot":"","sources":["../../../../src/data-plane/server/routes/feeds.ts"],"names":[],"mappings":"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","sourcesContent":["import assert from 'node:assert'\nimport type { ServiceImpl } from '@connectrpc/connect'\nimport type { Service } from '../../../proto/bsky_connect.js'\nimport { FeedType } from '../../../proto/bsky_pb.js'\nimport type { Database } from '../db/index.js'\nimport { TimeCidKeyset, paginate } from '../db/pagination.js'\n\nexport default (db: Database): Partial<ServiceImpl<typeof Service>> => ({\n async getAuthorFeed(req) {\n const { actorDid, limit, cursor, feedType } = req\n const { ref } = db.db.dynamic\n\n // defaults to posts, reposts, and replies\n let builder = db.db\n .selectFrom('feed_item')\n .innerJoin('post', 'post.uri', 'feed_item.postUri')\n .selectAll('feed_item')\n .where('originatorDid', '=', actorDid)\n\n if (feedType === FeedType.POSTS_WITH_MEDIA) {\n builder = builder\n // only your own posts\n .where('type', '=', 'post')\n // only posts with media\n .where((eb) =>\n eb.or([\n eb.exists(\n eb\n .selectFrom('post_embed_image')\n .select('post_embed_image.postUri')\n .whereRef('post_embed_image.postUri', '=', 'feed_item.postUri'),\n ),\n eb.exists(\n eb\n .selectFrom('post_embed_gallery_image')\n .select('post_embed_gallery_image.postUri')\n .whereRef(\n 'post_embed_gallery_image.postUri',\n '=',\n 'feed_item.postUri',\n ),\n ),\n ]),\n )\n } else if (feedType === FeedType.POSTS_WITH_VIDEO) {\n builder = builder\n // only your own posts\n .where('type', '=', 'post')\n // only posts with video\n .where(({ eb, exists }) =>\n exists(\n eb\n .selectFrom('post_embed_video')\n .select('post_embed_video.postUri')\n .whereRef('post_embed_video.postUri', '=', 'feed_item.postUri'),\n ),\n )\n } else if (feedType === FeedType.POSTS_NO_REPLIES) {\n builder = builder.where((eb) =>\n eb.or([eb('post.replyParent', 'is', null), eb('type', '=', 'repost')]),\n )\n } else if (feedType === FeedType.POSTS_AND_AUTHOR_THREADS) {\n builder = builder.where((eb) =>\n eb.or([\n eb('type', '=', 'repost'),\n eb('post.replyParent', 'is', null),\n eb('post.replyRoot', 'like', `at://${actorDid}/%`),\n ]),\n )\n }\n\n const keyset = new TimeCidKeyset(\n ref('feed_item.sortAt'),\n ref('feed_item.cid'),\n )\n\n builder = paginate(builder, {\n limit,\n cursor,\n keyset,\n })\n\n const page = keyset.page(await builder.execute(), limit)\n\n return {\n items: page.items.map(feedItemFromRow),\n cursor: page.cursor,\n }\n },\n\n async getTimeline(req) {\n const { actorDid, limit, cursor } = req\n const { ref } = db.db.dynamic\n\n const keyset = new TimeCidKeyset(\n ref('feed_item.sortAt'),\n ref('feed_item.cid'),\n )\n\n let followQb = db.db\n .selectFrom('feed_item')\n .innerJoin('follow', 'follow.subjectDid', 'feed_item.originatorDid')\n .where('follow.creator', '=', actorDid)\n .selectAll('feed_item')\n\n followQb = paginate(followQb, {\n limit,\n cursor,\n keyset,\n tryIndex: true,\n })\n\n let selfQb = db.db\n .selectFrom('feed_item')\n .where('feed_item.originatorDid', '=', actorDid)\n .selectAll('feed_item')\n\n selfQb = paginate(selfQb, {\n limit,\n cursor,\n keyset,\n tryIndex: true,\n })\n\n const [followRes, selfRes] = await Promise.all([\n followQb.execute(),\n selfQb.execute(),\n ])\n\n // Use own posts to fill space left by followed-account posts. When followed-account\n // posts already fill the page, still allow up to 10 own posts into the merged results.\n const selfLimit = Math.max(\n // Allow up to 10 own posts even when followed-account posts can fill the page.\n Math.min(limit, 10),\n // Allow enough own posts to fill the slots not occupied by followed-account posts.\n limit - Math.min(followRes.length, limit),\n )\n\n // The extra own post proves that another page exists, but is not part of this page.\n const selfHasMore = selfRes.length > selfLimit\n\n const selfItems = selfRes.slice(0, selfLimit)\n const feedItems = [...followRes, ...selfItems].sort((a, b) => {\n if (a.sortAt > b.sortAt) return -1\n if (a.sortAt < b.sortAt) return 1\n return a.cid > b.cid ? -1 : 1\n })\n\n const page = keyset.page(feedItems, limit)\n if (page.items.length === 0) {\n return { items: [], cursor: undefined }\n }\n const items = page.items.map(feedItemFromRow)\n\n // The combined results exceeded the requested limit.\n if (page.cursor) {\n return { items, cursor: page.cursor }\n }\n\n // Own posts exceeded their separate cap, but the combined results did not exceed the requested limit.\n if (selfHasMore) {\n const lastItem = page.items.at(-1)\n assert(lastItem)\n return { items, cursor: keyset.packFromResult(lastItem) }\n }\n\n return { items, cursor: undefined }\n },\n\n async getListFeed(req) {\n const { listUri, cursor, limit } = req\n const { ref } = db.db.dynamic\n\n let builder = db.db\n .selectFrom('post')\n .selectAll('post')\n .innerJoin('list_item', 'list_item.subjectDid', 'post.creator')\n .where('list_item.listUri', '=', listUri)\n\n const keyset = new TimeCidKeyset(ref('post.sortAt'), ref('post.cid'))\n builder = paginate(builder, {\n limit,\n cursor,\n keyset,\n tryIndex: true,\n })\n const page = keyset.page(await builder.execute(), limit)\n\n return {\n items: page.items.map((item) => ({ uri: item.uri, cid: item.cid })),\n cursor: page.cursor,\n }\n },\n})\n\n// @NOTE does not support additional fields in the protos specific to author feeds\n// and timelines. at the time of writing, hydration/view implementations do not rely on them.\nconst feedItemFromRow = (row: { postUri: string; uri: string }) => {\n return {\n uri: row.postUri,\n repost: row.uri === row.postUri ? undefined : row.uri,\n }\n}\n"]}
@@ -41,14 +41,14 @@ export default (db) => ({
41
41
  keyset,
42
42
  tryIndex: true,
43
43
  });
44
- const followers = await followersReq.execute();
44
+ const page = keyset.page(await followersReq.execute(), limit);
45
45
  return {
46
- followers: followers.map((f) => ({
46
+ followers: page.items.map((f) => ({
47
47
  uri: f.uri,
48
48
  actorDid: f.creatorDid,
49
49
  subjectDid: f.subjectDid,
50
50
  })),
51
- cursor: keyset.packFromResult(followers),
51
+ cursor: page.cursor,
52
52
  };
53
53
  },
54
54
  async getFollows(req) {
@@ -73,14 +73,14 @@ export default (db) => ({
73
73
  keyset,
74
74
  tryIndex: true,
75
75
  });
76
- const follows = await followsReq.execute();
76
+ const page = keyset.page(await followsReq.execute(), limit);
77
77
  return {
78
- follows: follows.map((f) => ({
78
+ follows: page.items.map((f) => ({
79
79
  uri: f.uri,
80
80
  actorDid: f.creatorDid,
81
81
  subjectDid: f.subjectDid,
82
82
  })),
83
- cursor: keyset.packFromResult(follows),
83
+ cursor: page.cursor,
84
84
  };
85
85
  },
86
86
  /**