@atproto/bsky 0.0.251 → 0.0.254

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 (463) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/dist/api/age-assurance/const.js +19 -19
  3. package/dist/api/age-assurance/const.js.map +1 -1
  4. package/dist/kws.d.ts +8 -0
  5. package/dist/kws.d.ts.map +1 -1
  6. package/dist/kws.js +53 -4
  7. package/dist/kws.js.map +1 -1
  8. package/dist/lexicons/app/bsky/actor/defs.defs.d.ts +6 -0
  9. package/dist/lexicons/app/bsky/actor/defs.defs.d.ts.map +1 -1
  10. package/dist/lexicons/app/bsky/actor/defs.defs.js +1 -0
  11. package/dist/lexicons/app/bsky/actor/defs.defs.js.map +1 -1
  12. package/package.json +28 -23
  13. package/bin/migration-create.ts +0 -46
  14. package/buf.gen.yaml +0 -12
  15. package/proto/bsky.proto +0 -1907
  16. package/proto/courier.proto +0 -84
  17. package/proto/rolodex.proto +0 -116
  18. package/src/api/age-assurance/const.ts +0 -203
  19. package/src/api/age-assurance/index.ts +0 -34
  20. package/src/api/age-assurance/kws/age-verified.test.ts +0 -98
  21. package/src/api/age-assurance/kws/age-verified.ts +0 -81
  22. package/src/api/age-assurance/kws/const.ts +0 -33
  23. package/src/api/age-assurance/kws/external-payload.test.ts +0 -72
  24. package/src/api/age-assurance/kws/external-payload.ts +0 -149
  25. package/src/api/age-assurance/redirects/kws-age-verified.ts +0 -107
  26. package/src/api/age-assurance/stash.ts +0 -23
  27. package/src/api/age-assurance/types.ts +0 -10
  28. package/src/api/age-assurance/util.ts +0 -68
  29. package/src/api/age-assurance/webhooks/kws-age-verified.ts +0 -75
  30. package/src/api/app/bsky/actor/getProfile.ts +0 -106
  31. package/src/api/app/bsky/actor/getProfiles.ts +0 -99
  32. package/src/api/app/bsky/actor/getSuggestions.ts +0 -163
  33. package/src/api/app/bsky/actor/searchActors.ts +0 -171
  34. package/src/api/app/bsky/actor/searchActorsTypeahead.ts +0 -164
  35. package/src/api/app/bsky/ageassurance/begin.ts +0 -175
  36. package/src/api/app/bsky/ageassurance/getConfig.ts +0 -16
  37. package/src/api/app/bsky/ageassurance/getState.ts +0 -58
  38. package/src/api/app/bsky/bookmark/createBookmark.ts +0 -39
  39. package/src/api/app/bsky/bookmark/deleteBookmark.ts +0 -32
  40. package/src/api/app/bsky/bookmark/getBookmarks.ts +0 -97
  41. package/src/api/app/bsky/bookmark/util.ts +0 -13
  42. package/src/api/app/bsky/contact/dismissMatch.ts +0 -26
  43. package/src/api/app/bsky/contact/getMatches.ts +0 -111
  44. package/src/api/app/bsky/contact/getSyncStatus.ts +0 -38
  45. package/src/api/app/bsky/contact/importContacts.ts +0 -119
  46. package/src/api/app/bsky/contact/removeData.ts +0 -25
  47. package/src/api/app/bsky/contact/sendNotification.ts +0 -33
  48. package/src/api/app/bsky/contact/startPhoneVerification.ts +0 -26
  49. package/src/api/app/bsky/contact/util.ts +0 -92
  50. package/src/api/app/bsky/contact/verifyPhone.ts +0 -29
  51. package/src/api/app/bsky/draft/createDraft.ts +0 -45
  52. package/src/api/app/bsky/draft/deleteDraft.ts +0 -20
  53. package/src/api/app/bsky/draft/getDrafts.ts +0 -44
  54. package/src/api/app/bsky/draft/updateDraft.ts +0 -26
  55. package/src/api/app/bsky/embed/getEmbedExternalView.ts +0 -154
  56. package/src/api/app/bsky/feed/getActorFeeds.ts +0 -101
  57. package/src/api/app/bsky/feed/getActorLikes.ts +0 -128
  58. package/src/api/app/bsky/feed/getAuthorFeed.ts +0 -279
  59. package/src/api/app/bsky/feed/getFeed.ts +0 -282
  60. package/src/api/app/bsky/feed/getFeedGenerator.ts +0 -68
  61. package/src/api/app/bsky/feed/getFeedGenerators.ts +0 -86
  62. package/src/api/app/bsky/feed/getLikes.ts +0 -151
  63. package/src/api/app/bsky/feed/getListFeed.ts +0 -147
  64. package/src/api/app/bsky/feed/getPostThread.ts +0 -156
  65. package/src/api/app/bsky/feed/getPosts.ts +0 -100
  66. package/src/api/app/bsky/feed/getQuotes.ts +0 -122
  67. package/src/api/app/bsky/feed/getRepostedBy.ts +0 -121
  68. package/src/api/app/bsky/feed/getSuggestedFeeds.ts +0 -39
  69. package/src/api/app/bsky/feed/getTimeline.ts +0 -127
  70. package/src/api/app/bsky/feed/searchPosts.ts +0 -274
  71. package/src/api/app/bsky/feed/searchPostsV2.ts +0 -243
  72. package/src/api/app/bsky/graph/getActorStarterPacks.ts +0 -103
  73. package/src/api/app/bsky/graph/getBlocks.ts +0 -94
  74. package/src/api/app/bsky/graph/getFollowers.ts +0 -153
  75. package/src/api/app/bsky/graph/getFollows.ts +0 -151
  76. package/src/api/app/bsky/graph/getKnownFollowers.ts +0 -123
  77. package/src/api/app/bsky/graph/getList.ts +0 -152
  78. package/src/api/app/bsky/graph/getListBlocks.ts +0 -87
  79. package/src/api/app/bsky/graph/getListMutes.ts +0 -87
  80. package/src/api/app/bsky/graph/getLists.ts +0 -125
  81. package/src/api/app/bsky/graph/getListsWithMembership.ts +0 -146
  82. package/src/api/app/bsky/graph/getMutes.ts +0 -87
  83. package/src/api/app/bsky/graph/getRelationships.ts +0 -58
  84. package/src/api/app/bsky/graph/getStarterPack.ts +0 -82
  85. package/src/api/app/bsky/graph/getStarterPacks.ts +0 -86
  86. package/src/api/app/bsky/graph/getStarterPacksWithMembership.ts +0 -136
  87. package/src/api/app/bsky/graph/getSuggestedFollowsByActor.ts +0 -171
  88. package/src/api/app/bsky/graph/muteActor.ts +0 -24
  89. package/src/api/app/bsky/graph/muteActorList.ts +0 -19
  90. package/src/api/app/bsky/graph/muteThread.ts +0 -19
  91. package/src/api/app/bsky/graph/searchStarterPacks.ts +0 -169
  92. package/src/api/app/bsky/graph/unmuteActor.ts +0 -21
  93. package/src/api/app/bsky/graph/unmuteActorList.ts +0 -19
  94. package/src/api/app/bsky/graph/unmuteThread.ts +0 -19
  95. package/src/api/app/bsky/labeler/getServices.ts +0 -41
  96. package/src/api/app/bsky/notification/getPreferences.ts +0 -49
  97. package/src/api/app/bsky/notification/getUnreadCount.ts +0 -83
  98. package/src/api/app/bsky/notification/listActivitySubscriptions.ts +0 -116
  99. package/src/api/app/bsky/notification/listNotifications.ts +0 -272
  100. package/src/api/app/bsky/notification/putActivitySubscription.ts +0 -69
  101. package/src/api/app/bsky/notification/putPreferences.ts +0 -17
  102. package/src/api/app/bsky/notification/putPreferencesV2.ts +0 -64
  103. package/src/api/app/bsky/notification/registerPush.ts +0 -40
  104. package/src/api/app/bsky/notification/unregisterPush.ts +0 -39
  105. package/src/api/app/bsky/notification/updateSeen.ts +0 -52
  106. package/src/api/app/bsky/notification/util.ts +0 -134
  107. package/src/api/app/bsky/unspecced/getAgeAssuranceState.ts +0 -48
  108. package/src/api/app/bsky/unspecced/getConfig.ts +0 -20
  109. package/src/api/app/bsky/unspecced/getOnboardingSuggestedStarterPacks.ts +0 -162
  110. package/src/api/app/bsky/unspecced/getPopularFeedGenerators.ts +0 -91
  111. package/src/api/app/bsky/unspecced/getPostThreadOtherV2.ts +0 -126
  112. package/src/api/app/bsky/unspecced/getPostThreadV2.ts +0 -148
  113. package/src/api/app/bsky/unspecced/getSuggestedFeeds.ts +0 -100
  114. package/src/api/app/bsky/unspecced/getSuggestedOnboardingUsers.ts +0 -143
  115. package/src/api/app/bsky/unspecced/getSuggestedStarterPacks.ts +0 -165
  116. package/src/api/app/bsky/unspecced/getSuggestedUsers.ts +0 -183
  117. package/src/api/app/bsky/unspecced/getSuggestedUsersForDiscover.ts +0 -175
  118. package/src/api/app/bsky/unspecced/getSuggestedUsersForExplore.ts +0 -178
  119. package/src/api/app/bsky/unspecced/getSuggestedUsersForSeeMore.ts +0 -178
  120. package/src/api/app/bsky/unspecced/getTaggedSuggestions.ts +0 -27
  121. package/src/api/app/bsky/unspecced/getTrendingTopics.ts +0 -96
  122. package/src/api/app/bsky/unspecced/getTrends.ts +0 -162
  123. package/src/api/app/bsky/unspecced/initAgeAssurance.ts +0 -150
  124. package/src/api/blob-dispatcher.ts +0 -41
  125. package/src/api/blob-resolver.ts +0 -401
  126. package/src/api/com/atproto/admin/getAccountInfos.ts +0 -49
  127. package/src/api/com/atproto/admin/getSubjectStatus.ts +0 -79
  128. package/src/api/com/atproto/admin/updateSubjectStatus.ts +0 -80
  129. package/src/api/com/atproto/identity/resolveHandle.ts +0 -19
  130. package/src/api/com/atproto/label/queryLabels.ts +0 -42
  131. package/src/api/com/atproto/repo/getRecord.ts +0 -44
  132. package/src/api/com/atproto/temp/fetchLabels.ts +0 -9
  133. package/src/api/external.ts +0 -15
  134. package/src/api/health.ts +0 -47
  135. package/src/api/index.ts +0 -216
  136. package/src/api/internal/bsky/actor/getProfiles.ts +0 -87
  137. package/src/api/kws/api.ts +0 -113
  138. package/src/api/kws/index.ts +0 -29
  139. package/src/api/kws/types.ts +0 -78
  140. package/src/api/kws/util.test.ts +0 -50
  141. package/src/api/kws/util.ts +0 -109
  142. package/src/api/kws/webhook.ts +0 -134
  143. package/src/api/sitemap.ts +0 -76
  144. package/src/api/util.ts +0 -45
  145. package/src/api/well-known.ts +0 -44
  146. package/src/auth-verifier.ts +0 -522
  147. package/src/bsync.ts +0 -41
  148. package/src/cache/read-through.ts +0 -157
  149. package/src/config.ts +0 -719
  150. package/src/context.ts +0 -143
  151. package/src/courier.ts +0 -41
  152. package/src/data-plane/client/hosts.ts +0 -103
  153. package/src/data-plane/client/index.ts +0 -125
  154. package/src/data-plane/client/util.test.ts +0 -29
  155. package/src/data-plane/client/util.ts +0 -74
  156. package/src/data-plane/index.ts +0 -2
  157. package/src/data-plane/server/background.ts +0 -66
  158. package/src/data-plane/server/bsync-subscription.ts +0 -590
  159. package/src/data-plane/server/db/database-schema.ts +0 -90
  160. package/src/data-plane/server/db/db.ts +0 -204
  161. package/src/data-plane/server/db/index.ts +0 -2
  162. package/src/data-plane/server/db/migrations/20230309T045948368Z-init.ts +0 -305
  163. package/src/data-plane/server/db/migrations/20230408T152211201Z-notification-init.ts +0 -25
  164. package/src/data-plane/server/db/migrations/20230417T210628672Z-moderation-init.ts +0 -127
  165. package/src/data-plane/server/db/migrations/20230420T211446071Z-did-cache.ts +0 -14
  166. package/src/data-plane/server/db/migrations/20230427T194702079Z-notif-record-index.ts +0 -14
  167. package/src/data-plane/server/db/migrations/20230605T144730094Z-post-profile-aggs.ts +0 -23
  168. package/src/data-plane/server/db/migrations/20230607T211442112Z-feed-generator-init.ts +0 -34
  169. package/src/data-plane/server/db/migrations/20230608T155101190Z-algo-whats-hot-view.ts +0 -85
  170. package/src/data-plane/server/db/migrations/20230608T201813132Z-mute-lists.ts +0 -78
  171. package/src/data-plane/server/db/migrations/20230608T205147239Z-mutes.ts +0 -15
  172. package/src/data-plane/server/db/migrations/20230609T153623961Z-blocks.ts +0 -33
  173. package/src/data-plane/server/db/migrations/20230609T232122649Z-actor-deletion-indexes.ts +0 -27
  174. package/src/data-plane/server/db/migrations/20230610T203555962Z-suggested-follows.ts +0 -13
  175. package/src/data-plane/server/db/migrations/20230611T215300060Z-actor-state.ts +0 -13
  176. package/src/data-plane/server/db/migrations/20230620T161134972Z-post-langs.ts +0 -9
  177. package/src/data-plane/server/db/migrations/20230627T212437895Z-optional-handle.ts +0 -15
  178. package/src/data-plane/server/db/migrations/20230629T220835893Z-remove-post-hierarchy.ts +0 -32
  179. package/src/data-plane/server/db/migrations/20230703T045536691Z-feed-and-label-indices.ts +0 -31
  180. package/src/data-plane/server/db/migrations/20230720T164800037Z-posts-cursor-idx.ts +0 -19
  181. package/src/data-plane/server/db/migrations/20230807T035309811Z-feed-item-delete-invite-for-user-idx.ts +0 -14
  182. package/src/data-plane/server/db/migrations/20230808T172902639Z-repo-rev.ts +0 -12
  183. package/src/data-plane/server/db/migrations/20230810T203349843Z-action-duration.ts +0 -23
  184. package/src/data-plane/server/db/migrations/20230817T195936007Z-native-notifications.ts +0 -16
  185. package/src/data-plane/server/db/migrations/20230830T205507322Z-suggested-feeds.ts +0 -13
  186. package/src/data-plane/server/db/migrations/20230904T211011773Z-block-lists.ts +0 -24
  187. package/src/data-plane/server/db/migrations/20230906T222220386Z-thread-gating.ts +0 -27
  188. package/src/data-plane/server/db/migrations/20230920T213858047Z-add-tags-to-post.ts +0 -9
  189. package/src/data-plane/server/db/migrations/20230929T192920807Z-record-cursor-indexes.ts +0 -40
  190. package/src/data-plane/server/db/migrations/20231003T202833377Z-create-moderation-subject-status.ts +0 -123
  191. package/src/data-plane/server/db/migrations/20231220T225126090Z-blob-takedowns.ts +0 -66
  192. package/src/data-plane/server/db/migrations/20240124T023719200Z-tagged-suggestions.ts +0 -15
  193. package/src/data-plane/server/db/migrations/20240226T225725627Z-labelers.ts +0 -27
  194. package/src/data-plane/server/db/migrations/20240530T170337073Z-account-deactivation.ts +0 -12
  195. package/src/data-plane/server/db/migrations/20240606T171229898Z-thread-mutes.ts +0 -15
  196. package/src/data-plane/server/db/migrations/20240606T222548219Z-starter-packs.ts +0 -45
  197. package/src/data-plane/server/db/migrations/20240719T203853939Z-priority-notifs.ts +0 -25
  198. package/src/data-plane/server/db/migrations/20240723T220700077Z-quotes-post-aggs.ts +0 -12
  199. package/src/data-plane/server/db/migrations/20240723T220703655Z-quotes.ts +0 -28
  200. package/src/data-plane/server/db/migrations/20240801T193939827Z-post-gate.ts +0 -17
  201. package/src/data-plane/server/db/migrations/20240808T224251220Z-post-gate-flags.ts +0 -25
  202. package/src/data-plane/server/db/migrations/20240829T211238293Z-simplify-actor-sync.ts +0 -23
  203. package/src/data-plane/server/db/migrations/20240831T134810923Z-pinned-posts.ts +0 -17
  204. package/src/data-plane/server/db/migrations/20241114T153108102Z-add-starter-packs-name.ts +0 -19
  205. package/src/data-plane/server/db/migrations/20250116T222618297Z-post-embed-video.ts +0 -17
  206. package/src/data-plane/server/db/migrations/20250207T174822012Z-add-label-exp.ts +0 -9
  207. package/src/data-plane/server/db/migrations/20250404T163421487Z-verifications.ts +0 -39
  208. package/src/data-plane/server/db/migrations/20250526T023712742Z-like-repost-via.ts +0 -17
  209. package/src/data-plane/server/db/migrations/20250528T221913281Z-add-record-tags.ts +0 -9
  210. package/src/data-plane/server/db/migrations/20250602T190357447Z-add-private-data.ts +0 -22
  211. package/src/data-plane/server/db/migrations/20250611T140649895Z-add-activity-subscription.ts +0 -22
  212. package/src/data-plane/server/db/migrations/20250627T025331240Z-add-actor-age-assurance-columns.ts +0 -22
  213. package/src/data-plane/server/db/migrations/20250812T183735692Z-add-bookmarks.ts +0 -23
  214. package/src/data-plane/server/db/migrations/20250813T174955711Z-add-post-agg-bookmarks.ts +0 -12
  215. package/src/data-plane/server/db/migrations/20251120T004738098Z-update-actor-age-assurance-v2.ts +0 -28
  216. package/src/data-plane/server/db/migrations/20260112T133951271Z-add-drafts.ts +0 -24
  217. package/src/data-plane/server/db/migrations/20260604T224952774Z-post-embed-gallery-image.ts +0 -19
  218. package/src/data-plane/server/db/migrations/index.ts +0 -60
  219. package/src/data-plane/server/db/migrations/provider.ts +0 -26
  220. package/src/data-plane/server/db/pagination.ts +0 -341
  221. package/src/data-plane/server/db/tables/activity-subscription.ts +0 -12
  222. package/src/data-plane/server/db/tables/actor-block.ts +0 -14
  223. package/src/data-plane/server/db/tables/actor-state.ts +0 -10
  224. package/src/data-plane/server/db/tables/actor-sync.ts +0 -9
  225. package/src/data-plane/server/db/tables/actor.ts +0 -19
  226. package/src/data-plane/server/db/tables/algo.ts +0 -11
  227. package/src/data-plane/server/db/tables/blob-takedown.ts +0 -9
  228. package/src/data-plane/server/db/tables/bookmark.ts +0 -11
  229. package/src/data-plane/server/db/tables/did-cache.ts +0 -13
  230. package/src/data-plane/server/db/tables/draft.ts +0 -11
  231. package/src/data-plane/server/db/tables/duplicate-record.ts +0 -12
  232. package/src/data-plane/server/db/tables/feed-generator.ts +0 -21
  233. package/src/data-plane/server/db/tables/feed-item.ts +0 -12
  234. package/src/data-plane/server/db/tables/follow.ts +0 -15
  235. package/src/data-plane/server/db/tables/label.ts +0 -13
  236. package/src/data-plane/server/db/tables/labeler.ts +0 -16
  237. package/src/data-plane/server/db/tables/like.ts +0 -18
  238. package/src/data-plane/server/db/tables/list-block.ts +0 -15
  239. package/src/data-plane/server/db/tables/list-item.ts +0 -16
  240. package/src/data-plane/server/db/tables/list-mute.ts +0 -9
  241. package/src/data-plane/server/db/tables/list.ts +0 -19
  242. package/src/data-plane/server/db/tables/mute.ts +0 -9
  243. package/src/data-plane/server/db/tables/notification-push-token.ts +0 -10
  244. package/src/data-plane/server/db/tables/notification.ts +0 -16
  245. package/src/data-plane/server/db/tables/post-agg.ts +0 -16
  246. package/src/data-plane/server/db/tables/post-embed.ts +0 -47
  247. package/src/data-plane/server/db/tables/post-gate.ts +0 -12
  248. package/src/data-plane/server/db/tables/post.ts +0 -28
  249. package/src/data-plane/server/db/tables/private-data.ts +0 -13
  250. package/src/data-plane/server/db/tables/profile-agg.ts +0 -14
  251. package/src/data-plane/server/db/tables/profile.ts +0 -17
  252. package/src/data-plane/server/db/tables/quote.ts +0 -15
  253. package/src/data-plane/server/db/tables/record.ts +0 -15
  254. package/src/data-plane/server/db/tables/repost.ts +0 -18
  255. package/src/data-plane/server/db/tables/starter-pack.ts +0 -17
  256. package/src/data-plane/server/db/tables/subscription.ts +0 -9
  257. package/src/data-plane/server/db/tables/suggested-feed.ts +0 -10
  258. package/src/data-plane/server/db/tables/suggested-follow.ts +0 -10
  259. package/src/data-plane/server/db/tables/tagged-suggestion.ts +0 -11
  260. package/src/data-plane/server/db/tables/thread-gate.ts +0 -12
  261. package/src/data-plane/server/db/tables/thread-mute.ts +0 -9
  262. package/src/data-plane/server/db/tables/verification.ts +0 -20
  263. package/src/data-plane/server/db/tables/view-param.ts +0 -12
  264. package/src/data-plane/server/db/types.ts +0 -12
  265. package/src/data-plane/server/db/util.ts +0 -67
  266. package/src/data-plane/server/index.ts +0 -44
  267. package/src/data-plane/server/indexing/index.ts +0 -458
  268. package/src/data-plane/server/indexing/plugins/block.ts +0 -84
  269. package/src/data-plane/server/indexing/plugins/chat-declaration.ts +0 -60
  270. package/src/data-plane/server/indexing/plugins/feed-generator.ts +0 -80
  271. package/src/data-plane/server/indexing/plugins/follow.ts +0 -132
  272. package/src/data-plane/server/indexing/plugins/germ-declaration.ts +0 -57
  273. package/src/data-plane/server/indexing/plugins/labeler.ts +0 -74
  274. package/src/data-plane/server/indexing/plugins/like.ts +0 -151
  275. package/src/data-plane/server/indexing/plugins/list-block.ts +0 -84
  276. package/src/data-plane/server/indexing/plugins/list-item.ts +0 -92
  277. package/src/data-plane/server/indexing/plugins/list.ts +0 -80
  278. package/src/data-plane/server/indexing/plugins/notif-declaration.ts +0 -57
  279. package/src/data-plane/server/indexing/plugins/post-gate.ts +0 -101
  280. package/src/data-plane/server/indexing/plugins/post.ts +0 -675
  281. package/src/data-plane/server/indexing/plugins/profile.ts +0 -90
  282. package/src/data-plane/server/indexing/plugins/repost.ts +0 -176
  283. package/src/data-plane/server/indexing/plugins/starter-pack.ts +0 -74
  284. package/src/data-plane/server/indexing/plugins/status.ts +0 -57
  285. package/src/data-plane/server/indexing/plugins/thread-gate.ts +0 -101
  286. package/src/data-plane/server/indexing/plugins/verification.ts +0 -113
  287. package/src/data-plane/server/indexing/processor.ts +0 -292
  288. package/src/data-plane/server/routes/activity-subscription.ts +0 -84
  289. package/src/data-plane/server/routes/blocks.ts +0 -127
  290. package/src/data-plane/server/routes/bookmarks.ts +0 -83
  291. package/src/data-plane/server/routes/drafts.ts +0 -37
  292. package/src/data-plane/server/routes/feed-gens.ts +0 -89
  293. package/src/data-plane/server/routes/feeds.ts +0 -176
  294. package/src/data-plane/server/routes/follows.ts +0 -141
  295. package/src/data-plane/server/routes/identity.ts +0 -59
  296. package/src/data-plane/server/routes/index.ts +0 -67
  297. package/src/data-plane/server/routes/interactions.ts +0 -120
  298. package/src/data-plane/server/routes/labels.ts +0 -55
  299. package/src/data-plane/server/routes/likes.ts +0 -92
  300. package/src/data-plane/server/routes/lists.ts +0 -88
  301. package/src/data-plane/server/routes/moderation.ts +0 -102
  302. package/src/data-plane/server/routes/mutes.ts +0 -195
  303. package/src/data-plane/server/routes/notifs.ts +0 -239
  304. package/src/data-plane/server/routes/profile.ts +0 -220
  305. package/src/data-plane/server/routes/quotes.ts +0 -32
  306. package/src/data-plane/server/routes/records.ts +0 -119
  307. package/src/data-plane/server/routes/relationships.ts +0 -174
  308. package/src/data-plane/server/routes/reposts.ts +0 -76
  309. package/src/data-plane/server/routes/search.ts +0 -181
  310. package/src/data-plane/server/routes/site-standard.ts +0 -12
  311. package/src/data-plane/server/routes/sitemap.ts +0 -43
  312. package/src/data-plane/server/routes/starter-packs.ts +0 -32
  313. package/src/data-plane/server/routes/suggestions.ts +0 -175
  314. package/src/data-plane/server/routes/sync.ts +0 -16
  315. package/src/data-plane/server/routes/threads.ts +0 -33
  316. package/src/data-plane/server/subscription.ts +0 -152
  317. package/src/data-plane/server/util.ts +0 -202
  318. package/src/error.ts +0 -12
  319. package/src/etcd.ts +0 -90
  320. package/src/feature-gates/README.md +0 -83
  321. package/src/feature-gates/gates.ts +0 -25
  322. package/src/feature-gates/index.ts +0 -235
  323. package/src/feature-gates/metrics.test.ts +0 -196
  324. package/src/feature-gates/metrics.ts +0 -107
  325. package/src/feature-gates/types.ts +0 -62
  326. package/src/feature-gates/utils.test.ts +0 -66
  327. package/src/feature-gates/utils.ts +0 -135
  328. package/src/hydration/actor.ts +0 -578
  329. package/src/hydration/external.test.ts +0 -281
  330. package/src/hydration/external.ts +0 -268
  331. package/src/hydration/feed.ts +0 -455
  332. package/src/hydration/graph.ts +0 -354
  333. package/src/hydration/hydrator.ts +0 -1835
  334. package/src/hydration/label.ts +0 -207
  335. package/src/hydration/util.ts +0 -199
  336. package/src/image/index.ts +0 -2
  337. package/src/image/invalidator.ts +0 -33
  338. package/src/image/logger.ts +0 -6
  339. package/src/image/server.ts +0 -228
  340. package/src/image/sharp.ts +0 -89
  341. package/src/image/uri.ts +0 -114
  342. package/src/image/util.ts +0 -41
  343. package/src/index.ts +0 -312
  344. package/src/kws.ts +0 -197
  345. package/src/logger.ts +0 -40
  346. package/src/pipeline.ts +0 -60
  347. package/src/redis.ts +0 -205
  348. package/src/rolodex.ts +0 -42
  349. package/src/stash.ts +0 -76
  350. package/src/util/debug.ts +0 -10
  351. package/src/util/http.ts +0 -42
  352. package/src/util/retry.ts +0 -10
  353. package/src/util/standard-site.test.ts +0 -423
  354. package/src/util/standard-site.ts +0 -196
  355. package/src/util/uris.ts +0 -38
  356. package/src/util.ts +0 -48
  357. package/src/views/index.ts +0 -2783
  358. package/src/views/threads-v2.ts +0 -510
  359. package/src/views/types.ts +0 -193
  360. package/src/views/util.ts +0 -114
  361. package/test.env +0 -2
  362. package/tests/__snapshots__/feed-generation.test.ts.snap +0 -2510
  363. package/tests/_util.ts +0 -292
  364. package/tests/admin/admin-auth.test.ts +0 -190
  365. package/tests/admin/moderation.test.ts +0 -222
  366. package/tests/auth.test.ts +0 -90
  367. package/tests/blob-resolver.test.ts +0 -125
  368. package/tests/data-plane/__snapshots__/indexing.test.ts.snap +0 -887
  369. package/tests/data-plane/db.test.ts +0 -189
  370. package/tests/data-plane/duplicate-records.test.ts +0 -156
  371. package/tests/data-plane/handle-invalidation.test.ts +0 -153
  372. package/tests/data-plane/indexing.test.ts +0 -827
  373. package/tests/data-plane/subscription.test.ts +0 -128
  374. package/tests/data-plane/thread-mutes.test.ts +0 -175
  375. package/tests/entryway-auth.test.ts +0 -177
  376. package/tests/etcd.test.ts +0 -302
  377. package/tests/feed-generation.test.ts +0 -965
  378. package/tests/hydration/util.test.ts +0 -119
  379. package/tests/image/server.test.ts +0 -97
  380. package/tests/image/sharp.test.ts +0 -199
  381. package/tests/image/uri.test.ts +0 -106
  382. package/tests/label-hydration.test.ts +0 -245
  383. package/tests/postgates.test.ts +0 -182
  384. package/tests/query-labels.test.ts +0 -85
  385. package/tests/redis-cache.test.ts +0 -230
  386. package/tests/seed/feed-hidden-replies.ts +0 -63
  387. package/tests/seed/get-suggested-starter-packs.ts +0 -64
  388. package/tests/seed/get-trends.ts +0 -71
  389. package/tests/seed/known-followers.ts +0 -111
  390. package/tests/seed/postgates.ts +0 -57
  391. package/tests/server.test.ts +0 -115
  392. package/tests/sitemap.test.ts +0 -75
  393. package/tests/stash.test.ts +0 -180
  394. package/tests/utils.test.ts +0 -36
  395. package/tests/views/__snapshots__/actor-search.test.ts.snap +0 -155
  396. package/tests/views/__snapshots__/author-feed.test.ts.snap +0 -6301
  397. package/tests/views/__snapshots__/block-lists.test.ts.snap +0 -690
  398. package/tests/views/__snapshots__/blocks.test.ts.snap +0 -462
  399. package/tests/views/__snapshots__/bookmarks.test.ts.snap +0 -379
  400. package/tests/views/__snapshots__/follows.test.ts.snap +0 -798
  401. package/tests/views/__snapshots__/labeler-service.test.ts.snap +0 -233
  402. package/tests/views/__snapshots__/likes.test.ts.snap +0 -124
  403. package/tests/views/__snapshots__/list-feed.test.ts.snap +0 -883
  404. package/tests/views/__snapshots__/lists.test.ts.snap +0 -650
  405. package/tests/views/__snapshots__/mute-lists.test.ts.snap +0 -705
  406. package/tests/views/__snapshots__/mutes.test.ts.snap +0 -353
  407. package/tests/views/__snapshots__/notifications.test.ts.snap +0 -3074
  408. package/tests/views/__snapshots__/posts.test.ts.snap +0 -1019
  409. package/tests/views/__snapshots__/profile.test.ts.snap +0 -811
  410. package/tests/views/__snapshots__/quotes.test.ts.snap +0 -445
  411. package/tests/views/__snapshots__/reposts.test.ts.snap +0 -112
  412. package/tests/views/__snapshots__/starter-packs.test.ts.snap +0 -836
  413. package/tests/views/__snapshots__/thread-v2.test.ts.snap +0 -1298
  414. package/tests/views/__snapshots__/thread.test.ts.snap +0 -2051
  415. package/tests/views/__snapshots__/threadgating.test.ts.snap +0 -189
  416. package/tests/views/__snapshots__/timeline.test.ts.snap +0 -7252
  417. package/tests/views/account-deactivation.test.ts +0 -81
  418. package/tests/views/actor-likes.test.ts +0 -138
  419. package/tests/views/actor-search.test.ts +0 -261
  420. package/tests/views/age-assurance-v2.test.ts +0 -823
  421. package/tests/views/age-assurance.test.ts +0 -479
  422. package/tests/views/author-feed.test.ts +0 -694
  423. package/tests/views/block-lists.test.ts +0 -661
  424. package/tests/views/blocks.test.ts +0 -889
  425. package/tests/views/bookmarks.test.ts +0 -345
  426. package/tests/views/drafts.test.ts +0 -430
  427. package/tests/views/feed-hidden-replies.test.ts +0 -243
  428. package/tests/views/feed-view-post.test.ts +0 -501
  429. package/tests/views/follows.test.ts +0 -372
  430. package/tests/views/get-config.test.ts +0 -64
  431. package/tests/views/get-suggested-onboarding-users.test.ts +0 -193
  432. package/tests/views/get-suggested-starter-packs.test.ts +0 -137
  433. package/tests/views/get-trends.test.ts +0 -141
  434. package/tests/views/internal-actor.test.ts +0 -127
  435. package/tests/views/known-followers.test.ts +0 -193
  436. package/tests/views/labeler-service.test.ts +0 -242
  437. package/tests/views/labels-needs-review.test.ts +0 -316
  438. package/tests/views/labels-takedown.test.ts +0 -247
  439. package/tests/views/likes.test.ts +0 -192
  440. package/tests/views/list-feed.test.ts +0 -195
  441. package/tests/views/lists.test.ts +0 -412
  442. package/tests/views/mute-lists.test.ts +0 -509
  443. package/tests/views/mutes.test.ts +0 -308
  444. package/tests/views/notifications.test.ts +0 -1581
  445. package/tests/views/post-search.test.ts +0 -281
  446. package/tests/views/posts-debug.test.ts +0 -79
  447. package/tests/views/posts.test.ts +0 -427
  448. package/tests/views/profile-debug.test.ts +0 -75
  449. package/tests/views/profile.test.ts +0 -795
  450. package/tests/views/quotes.test.ts +0 -133
  451. package/tests/views/reposts.test.ts +0 -112
  452. package/tests/views/starter-packs.test.ts +0 -395
  453. package/tests/views/suggestions.test.ts +0 -145
  454. package/tests/views/thread-v2.test.ts +0 -2005
  455. package/tests/views/thread.test.ts +0 -637
  456. package/tests/views/threadgating.test.ts +0 -843
  457. package/tests/views/timeline.test.ts +0 -328
  458. package/tests/views/verification.test.ts +0 -296
  459. package/tsconfig.build.json +0 -10
  460. package/tsconfig.build.tsbuildinfo +0 -1
  461. package/tsconfig.json +0 -7
  462. package/tsconfig.tests.json +0 -8
  463. package/vitest.config.ts +0 -10
@@ -1,1835 +0,0 @@
1
- import assert from 'node:assert'
2
- import { dedupeStrs, mapDefined } from '@atproto/common'
3
- import { atUri } from '@atproto/lex'
4
- import { AtUri, AtUriString, DidString, UriString } from '@atproto/syntax'
5
- import { DataPlaneClient } from '../data-plane/client/index.js'
6
- import {
7
- FeatureGatesClient,
8
- ScopedFeatureGatesClient,
9
- } from '../feature-gates/index.js'
10
- import { app, chat, com } from '../lexicons/index.js'
11
- import { hydrationLogger } from '../logger.js'
12
- import {
13
- Bookmark as BookmarkLex,
14
- BookmarkInfo,
15
- Notification,
16
- RecordRef,
17
- } from '../proto/bsky_pb.js'
18
- import {
19
- SITE_STANDARD_NSID_PREFIX,
20
- parseSiteStandardRecordKey,
21
- } from '../util/standard-site.js'
22
- import { uriToDid, uriToDid as didFromUri } from '../util/uris.js'
23
- import { ParsedLabelers } from '../util.js'
24
- import {
25
- ProfileRecord,
26
- isExternalEmbedType,
27
- isListRuleType,
28
- isRecordEmbedType,
29
- isRecordWithMediaType,
30
- } from '../views/types.js'
31
- import {
32
- ActivitySubscriptionStates,
33
- ActorHydrator,
34
- Actors,
35
- KnownFollowersStates,
36
- ProfileAggs,
37
- ProfileViewerState,
38
- ProfileViewerStates,
39
- } from './actor.js'
40
- import {
41
- ExternalHydrator,
42
- SiteStandardDocuments,
43
- SiteStandardPublications,
44
- } from './external.js'
45
- import {
46
- FeedGenAggs,
47
- FeedGenViewerStates,
48
- FeedGens,
49
- FeedHydrator,
50
- FeedItem,
51
- type GetPostsHydrationOptions,
52
- Likes,
53
- Post,
54
- PostAggs,
55
- PostViewerStates,
56
- Postgates,
57
- Posts,
58
- Reposts,
59
- ThreadContexts,
60
- ThreadRef,
61
- Threadgates,
62
- } from './feed.js'
63
- import {
64
- BlockEntry,
65
- Follows,
66
- GraphHydrator,
67
- ListAggs,
68
- ListItems,
69
- ListMembershipState,
70
- ListMembershipStates,
71
- ListViewerStates,
72
- Lists,
73
- RelationshipPair,
74
- StarterPackAggs,
75
- StarterPacks,
76
- Verifications,
77
- } from './graph.js'
78
- import {
79
- LabelHydrator,
80
- LabelerAggs,
81
- LabelerViewerStates,
82
- Labelers,
83
- Labels,
84
- } from './label.js'
85
- import {
86
- HydrationMap,
87
- ItemRef,
88
- RecordInfo,
89
- mergeManyMaps,
90
- mergeMaps,
91
- mergeNestedMaps,
92
- parseDate,
93
- urisByCollection,
94
- } from './util.js'
95
-
96
- export class HydrateCtx {
97
- labelers = this.vals.labelers
98
- viewer = this.vals.viewer !== null ? serviceRefToDid(this.vals.viewer) : null
99
- includeTakedowns = this.vals.includeTakedowns
100
- overrideIncludeTakedownsForActor = this.vals.overrideIncludeTakedownsForActor
101
- include3pBlocks = this.vals.include3pBlocks
102
- skipViewerBlocks = this.vals.skipViewerBlocks
103
- includeDebugField = this.vals.includeDebugField
104
- features = this.vals.features
105
- constructor(private vals: HydrateCtxVals) {}
106
- // Convenience with use with dataplane.getActors cache control
107
- get skipCacheForViewer() {
108
- if (!this.viewer) return
109
- return [this.viewer]
110
- }
111
- copy<V extends Partial<HydrateCtxVals>>(vals?: V): HydrateCtx & V {
112
- return new HydrateCtx({ ...this.vals, ...vals }) as HydrateCtx & V
113
- }
114
- }
115
-
116
- export type HydrateCtxWithViewer = HydrateCtx & { viewer: string }
117
-
118
- export type HydrateCtxVals = {
119
- labelers: ParsedLabelers
120
- viewer: DidString | null
121
- includeTakedowns?: boolean
122
- overrideIncludeTakedownsForActor?: boolean
123
- include3pBlocks?: boolean
124
- skipViewerBlocks?: boolean
125
- includeDebugField?: boolean
126
- features: ScopedFeatureGatesClient
127
- }
128
-
129
- export type HydrationState = {
130
- ctx?: HydrateCtx
131
- actors?: Actors
132
- profileViewers?: ProfileViewerStates
133
- profileAggs?: ProfileAggs
134
- posts?: Posts
135
- postAggs?: PostAggs
136
- postViewers?: PostViewerStates
137
- threadContexts?: ThreadContexts
138
- postBlocks?: PostBlocks
139
- reposts?: Reposts
140
- follows?: Follows
141
- followBlocks?: FollowBlocks
142
- threadgates?: Threadgates
143
- postgates?: Postgates
144
- lists?: Lists
145
- listAggs?: ListAggs
146
- listMemberships?: ListMembershipStates
147
- listViewers?: ListViewerStates
148
- listItems?: ListItems
149
- likes?: Likes
150
- likeBlocks?: LikeBlocks
151
- labels?: Labels
152
- feedgens?: FeedGens
153
- feedgenViewers?: FeedGenViewerStates
154
- feedgenAggs?: FeedGenAggs
155
- starterPacks?: StarterPacks
156
- starterPackAggs?: StarterPackAggs
157
- labelers?: Labelers
158
- labelerViewers?: LabelerViewerStates
159
- labelerAggs?: LabelerAggs
160
- knownFollowers?: KnownFollowersStates
161
- activitySubscriptions?: ActivitySubscriptionStates
162
- bidirectionalBlocks?: BidirectionalBlocks
163
- verifications?: Verifications
164
- bookmarks?: Bookmarks
165
- siteStandardDocuments?: SiteStandardDocuments
166
- siteStandardPublications?: SiteStandardPublications
167
- }
168
-
169
- export type PostBlock = { embed: boolean; parent: boolean; root: boolean }
170
- export type PostBlocks = HydrationMap<AtUriString, PostBlock>
171
- type PostBlockPairs = {
172
- embed?: RelationshipPair
173
- parent?: RelationshipPair
174
- root?: RelationshipPair
175
- }
176
-
177
- export type LikeBlock = boolean
178
- export type LikeBlocks = HydrationMap<AtUriString, LikeBlock>
179
-
180
- export type FollowBlock = boolean
181
- export type FollowBlocks = HydrationMap<AtUriString, FollowBlock>
182
-
183
- export type BidirectionalBlocks = HydrationMap<
184
- DidString,
185
- HydrationMap<DidString, boolean>
186
- >
187
-
188
- export type Bookmark = {
189
- ref?: { key: string }
190
- subjectUri: string
191
- subjectCid: string
192
- indexedAt?: Date
193
- }
194
-
195
- // actor DID -> stash key -> bookmark
196
- export type Bookmarks = HydrationMap<DidString, HydrationMap<string, Bookmark>>
197
-
198
- /**
199
- * Additional config passed from `ServerConfig` to the `Hydrator` instance.
200
- * Values within this config object may be passed to other sub-hydrators.
201
- */
202
- export type HydratorConfig = {
203
- debugFieldAllowedDids: Set<string>
204
- featureGatesClient: FeatureGatesClient
205
- }
206
-
207
- export class Hydrator {
208
- actor: ActorHydrator
209
- external: ExternalHydrator
210
- feed: FeedHydrator
211
- graph: GraphHydrator
212
- label: LabelHydrator
213
- serviceLabelers: Set<string>
214
- config: HydratorConfig
215
-
216
- constructor(
217
- public dataplane: DataPlaneClient,
218
- serviceLabelers: readonly DidString[] = [],
219
- config: HydratorConfig,
220
- ) {
221
- this.config = config
222
- this.actor = new ActorHydrator(dataplane)
223
- this.external = new ExternalHydrator(dataplane)
224
- this.feed = new FeedHydrator(dataplane)
225
- this.graph = new GraphHydrator(dataplane)
226
- this.label = new LabelHydrator(dataplane)
227
- this.serviceLabelers = new Set(serviceLabelers)
228
- }
229
-
230
- // app.bsky.actor.defs#profileView
231
- // - profile viewer
232
- // - list basic
233
- // Note: builds on the naive profile viewer hydrator and removes references to lists that have been deleted
234
- async hydrateProfileViewers(
235
- dids: DidString[],
236
- ctx: HydrateCtx,
237
- ): Promise<HydrationState> {
238
- const viewer = ctx.viewer
239
- if (!viewer) return {}
240
- const profileViewers = await this.actor.getProfileViewerStatesNaive(
241
- dids,
242
- viewer,
243
- )
244
- const listUris: AtUriString[] = []
245
- profileViewers.forEach((item) => {
246
- listUris.push(...listUrisFromProfileViewer(item))
247
- })
248
- const listState = await this.hydrateListsBasic(listUris, ctx)
249
- // if a list no longer exists or is not a mod list, then remove from viewer state
250
- profileViewers.forEach((item) => {
251
- removeNonModListsFromProfileViewer(item, listState)
252
- })
253
-
254
- return mergeStates(listState, {
255
- profileViewers,
256
- ctx,
257
- })
258
- }
259
-
260
- // app.bsky.actor.defs#profileView
261
- // - profile
262
- // - list basic
263
- async hydrateProfiles(
264
- dids: DidString[],
265
- ctx: HydrateCtx,
266
- ): Promise<HydrationState> {
267
- /**
268
- * Special case here, we want to include takedowns in special cases, like
269
- * `getProfile`, since we throw client-facing errors later in the pipeline.
270
- */
271
- const includeTakedowns =
272
- ctx.includeTakedowns || ctx.overrideIncludeTakedownsForActor
273
- const [actors, labels, profileViewersState] = await Promise.all([
274
- this.actor.getActors(dids, {
275
- includeTakedowns,
276
- skipCacheForDids: ctx.skipCacheForViewer,
277
- }),
278
- this.label.getLabelsForSubjects(labelSubjectsForDid(dids), ctx.labelers),
279
- this.hydrateProfileViewers(dids, ctx),
280
- ])
281
- // Hydrate verification issuer actors so the verification view can expose
282
- // the issuer's current handle and displayName. Skipped when no hydrated
283
- // actor has any verifications, which is the common case.
284
- const issuerDids: DidString[] = []
285
- const issuerDidSet = new Set<DidString>()
286
- for (const actor of actors.values()) {
287
- if (!actor) continue
288
- for (const verification of actor.verifications) {
289
- const issuer = verification.issuer
290
- if (actors.has(issuer) || issuerDidSet.has(issuer)) continue
291
- issuerDidSet.add(issuer)
292
- issuerDids.push(issuer)
293
- }
294
- }
295
- if (issuerDids.length > 0) {
296
- const issuerActors = await this.actor.getActors(issuerDids, {
297
- includeTakedowns,
298
- })
299
- // Merge into actors without overwriting existing entries (the original
300
- // dids may have been fetched with skipCacheForDids, etc.).
301
- for (const [did, actor] of issuerActors) {
302
- if (!actors.has(did)) actors.set(did, actor)
303
- }
304
- }
305
- if (!includeTakedowns) {
306
- actionTakedownLabels(dids, actors, labels)
307
- }
308
- return mergeStates(profileViewersState ?? {}, {
309
- actors,
310
- labels,
311
- ctx,
312
- })
313
- }
314
-
315
- // app.bsky.actor.defs#profileViewBasic
316
- // - profile basic
317
- // - profile
318
- // - list basic
319
- async hydrateProfilesBasic(
320
- dids: DidString[],
321
- ctx: HydrateCtx,
322
- ): Promise<HydrationState> {
323
- return this.hydrateProfiles(dids, ctx)
324
- }
325
-
326
- // app.bsky.actor.defs#profileViewDetailed
327
- // - profile detailed
328
- // - profile
329
- // - list basic
330
- // - starterpack
331
- // - profile
332
- // - list basic
333
- // - labels
334
- async hydrateProfilesDetailed(
335
- dids: DidString[],
336
- ctx: HydrateCtx,
337
- opts?: {
338
- // when set, restricts known followers hydration to this subset of dids
339
- knownFollowersDids?: DidString[]
340
- },
341
- ): Promise<HydrationState> {
342
- const [knownFollowers, activitySubscriptions] = await Promise.all([
343
- this.actor
344
- .getKnownFollowers(opts?.knownFollowersDids ?? dids, ctx.viewer)
345
- .catch((err): KnownFollowersStates => {
346
- hydrationLogger.error(
347
- { err },
348
- 'Failed to get known followers for profiles',
349
- )
350
- return new HydrationMap()
351
- }),
352
- this.actor
353
- .getActivitySubscriptions(dids, ctx.viewer)
354
- .catch((err): ActivitySubscriptionStates => {
355
- hydrationLogger.error(
356
- { err },
357
- 'Failed to get activity subscriptions state for profiles',
358
- )
359
- return new HydrationMap()
360
- }),
361
- ])
362
-
363
- const subjectsToKnownFollowersMap = new Map<DidString, DidString[]>()
364
-
365
- for (const did of knownFollowers.keys()) {
366
- const known = knownFollowers.get(did)
367
- if (known) subjectsToKnownFollowersMap.set(did, known.followers)
368
- }
369
-
370
- const allKnownFollowerDids = Array.from(knownFollowers.values())
371
- .filter(Boolean)
372
- .flatMap((f) => f!.followers)
373
- const allDids = Array.from(new Set(dids.concat(allKnownFollowerDids)))
374
- const [state, profileAggs, bidirectionalBlocks] = await Promise.all([
375
- this.hydrateProfiles(allDids, ctx),
376
- this.actor.getProfileAggregates(dids),
377
- this.hydrateBidirectionalBlocks(subjectsToKnownFollowersMap, ctx),
378
- ])
379
- const starterPackUriSet = new Set<AtUriString>()
380
- state.actors?.forEach((actor) => {
381
- if (actor?.profile?.joinedViaStarterPack) {
382
- starterPackUriSet.add(actor?.profile?.joinedViaStarterPack?.uri)
383
- }
384
- })
385
- const starterPackState = await this.hydrateStarterPacksBasic(
386
- [...starterPackUriSet],
387
- ctx,
388
- )
389
- return mergeManyStates(state, starterPackState, {
390
- profileAggs,
391
- knownFollowers,
392
- activitySubscriptions,
393
- ctx,
394
- bidirectionalBlocks,
395
- })
396
- }
397
-
398
- // app.bsky.graph.defs#listView
399
- // - list
400
- // - profile basic
401
- async hydrateLists(
402
- uris: AtUriString[],
403
- ctx: HydrateCtx,
404
- ): Promise<HydrationState> {
405
- const [listsState, profilesState] = await Promise.all([
406
- this.hydrateListsBasic(uris, ctx, {
407
- skipAuthors: true, // handled via author profile hydration
408
- }),
409
- this.hydrateProfilesBasic(uris.map(didFromUri), ctx),
410
- ])
411
- return mergeStates(listsState, profilesState)
412
- }
413
-
414
- // app.bsky.graph.defs#listViewBasic
415
- // - list basic
416
- async hydrateListsBasic(
417
- uris: AtUriString[],
418
- ctx: HydrateCtx,
419
- opts?: { skipAuthors: boolean },
420
- ): Promise<HydrationState> {
421
- const includeAuthorDids = opts?.skipAuthors ? [] : uris.map(uriToDid)
422
- const [lists, listAggs, listViewers, labels, actors] = await Promise.all([
423
- this.graph.getLists(uris, ctx.includeTakedowns),
424
- this.graph.getListAggregates(uris.map((uri) => ({ uri }))),
425
- ctx.viewer ? this.graph.getListViewerStates(uris, ctx.viewer) : undefined,
426
- this.label.getLabelsForSubjects(
427
- [...uris, ...includeAuthorDids],
428
- ctx.labelers,
429
- ),
430
- this.actor.getActors(includeAuthorDids, {
431
- includeTakedowns: ctx.includeTakedowns,
432
- skipCacheForDids: ctx.skipCacheForViewer,
433
- }),
434
- ])
435
-
436
- if (!ctx.includeTakedowns) {
437
- actionTakedownLabels(uris, lists, labels)
438
- actionTakedownLabels(includeAuthorDids, actors, labels)
439
- }
440
-
441
- return { lists, listAggs, listViewers, labels, actors, ctx }
442
- }
443
-
444
- // app.bsky.graph.defs#listItemView
445
- // - list item
446
- // - profile
447
- // - list basic
448
- async hydrateListItems(
449
- uris: AtUriString[],
450
- ctx: HydrateCtx,
451
- ): Promise<HydrationState> {
452
- const listItems = await this.graph.getListItems(uris)
453
- const dids: DidString[] = []
454
- listItems.forEach((item) => {
455
- if (item) {
456
- dids.push(item.record.subject)
457
- }
458
- })
459
- const profileState = await this.hydrateProfiles(dids, ctx)
460
- return mergeStates(profileState, { listItems, ctx })
461
- }
462
-
463
- async hydrateListsMembership(
464
- uris: AtUriString[],
465
- did: DidString,
466
- ctx: HydrateCtx,
467
- ): Promise<HydrationState> {
468
- const [
469
- actorsHydrationState,
470
- listsHydrationState,
471
- { listitemUris: listItemUris },
472
- ] = await Promise.all([
473
- this.hydrateProfiles([did], ctx),
474
- this.hydrateLists(uris, ctx),
475
- this.dataplane.getListMembership({
476
- actorDid: did,
477
- listUris: uris,
478
- }),
479
- ])
480
-
481
- // mapping uri -> did -> { actorListItemUri }
482
- const listMemberships = new HydrationMap(
483
- uris.map((uri, i) => {
484
- const listItemUri = listItemUris[i] as '' | AtUriString
485
- return [
486
- uri,
487
- new HydrationMap<DidString, ListMembershipState>([
488
- listItemUri
489
- ? [did, { actorListItemUri: listItemUri }]
490
- : [did, null],
491
- ]),
492
- ]
493
- }),
494
- )
495
-
496
- return mergeManyStates(actorsHydrationState, listsHydrationState, {
497
- listMemberships,
498
- ctx,
499
- })
500
- }
501
-
502
- // app.bsky.feed.defs#postView
503
- // - post
504
- // - profile
505
- // - list basic
506
- // - list
507
- // - profile
508
- // - list basic
509
- // - feedgen
510
- // - profile
511
- // - list basic
512
- // - mod service
513
- // - profile
514
- // - list basic
515
- async hydratePosts(
516
- refs: { uri: AtUriString }[],
517
- ctx: HydrateCtx,
518
- state: HydrationState = {},
519
- options: Pick<GetPostsHydrationOptions, 'processDynamicTagsForView'> = {},
520
- ): Promise<HydrationState> {
521
- const uris = refs.map((ref) => ref.uri)
522
-
523
- state.posts ??= new HydrationMap()
524
- const addPostsToHydrationState = (posts: Posts) => {
525
- posts.forEach((post, uri) => {
526
- state.posts ??= new HydrationMap()
527
- state.posts.set(uri, post)
528
- })
529
- }
530
-
531
- // layer 0: the posts in the thread
532
- const postsLayer0 = await this.feed.getPosts(
533
- uris,
534
- ctx.includeTakedowns,
535
- state.posts,
536
- ctx.viewer,
537
- {
538
- processDynamicTagsForView: options.processDynamicTagsForView,
539
- },
540
- )
541
- addPostsToHydrationState(postsLayer0)
542
-
543
- const additionalRootUris = rootUrisFromPosts(postsLayer0) // supports computing threadgates
544
- const threadRootUris = new Set<AtUriString>()
545
- for (const [uri, post] of postsLayer0) {
546
- if (post) {
547
- threadRootUris.add(rootUriFromPost(post) ?? uri)
548
- }
549
- }
550
- const postUrisWithThreadgates = new Set<AtUriString>()
551
- for (const uri of threadRootUris) {
552
- const post = postsLayer0.get(uri)
553
- /*
554
- * Checking `post.hasThreadGate` is an optimization, which tells us that
555
- * this post has a threadgate record associated with it. `hydratePosts`
556
- * always hydrates root posts via `additionalRootUris`, so we try to
557
- * check the optimization flag were possible. If the post is unavailable
558
- * for whatever reason, we fall back to requesting threadgate records
559
- * that may not exist.
560
- */
561
- if (!post || post.hasThreadGate) {
562
- postUrisWithThreadgates.add(uri)
563
- }
564
- }
565
-
566
- // layer 1: first level embeds plus thread roots we haven't fetched yet
567
- const urisLayer1 = nestedRecordUrisFromPosts(postsLayer0)
568
- const urisLayer1ByCollection = urisByCollection(urisLayer1)
569
- const embedPostUrisLayer1 =
570
- urisLayer1ByCollection.get(app.bsky.feed.post.$type) ?? []
571
- const postsLayer1 = await this.feed.getPosts(
572
- [...embedPostUrisLayer1, ...additionalRootUris],
573
- ctx.includeTakedowns,
574
- state.posts,
575
- )
576
- addPostsToHydrationState(postsLayer1)
577
-
578
- // layer 2: second level embeds, ignoring any additional root uris we mixed-in to the previous layer
579
- const urisLayer2 = nestedRecordUrisFromPosts(
580
- postsLayer1,
581
- embedPostUrisLayer1,
582
- )
583
- const urisLayer2ByCollection = urisByCollection(urisLayer2)
584
- const embedPostUrisLayer2 =
585
- urisLayer2ByCollection.get(app.bsky.feed.post.$type) ?? []
586
-
587
- const [postsLayer2, threadgates] = await Promise.all([
588
- this.feed.getPosts(
589
- embedPostUrisLayer2,
590
- ctx.includeTakedowns,
591
- state.posts,
592
- ),
593
- this.feed.getThreadgatesForPosts([...postUrisWithThreadgates.values()]),
594
- ])
595
- addPostsToHydrationState(postsLayer2)
596
-
597
- // collect list/feedgen embeds, lists in threadgates, post record hydration
598
- const threadgateListUris = getListUrisFromThreadgates(threadgates)
599
- const nestedListUris = [
600
- ...(urisLayer1ByCollection.get(app.bsky.graph.list.$type) ?? []),
601
- ...(urisLayer2ByCollection.get(app.bsky.graph.list.$type) ?? []),
602
- ]
603
- const nestedFeedGenUris = [
604
- ...(urisLayer1ByCollection.get(app.bsky.feed.generator.$type) ?? []),
605
- ...(urisLayer2ByCollection.get(app.bsky.feed.generator.$type) ?? []),
606
- ]
607
- const nestedLabelerDids = [
608
- ...(urisLayer1ByCollection.get(app.bsky.labeler.service.$type) ?? []),
609
- ...(urisLayer2ByCollection.get(app.bsky.labeler.service.$type) ?? []),
610
- ].map(didFromUri)
611
- const nestedStarterPackUris = [
612
- ...(urisLayer1ByCollection.get(app.bsky.graph.starterpack.$type) ?? []),
613
- ...(urisLayer2ByCollection.get(app.bsky.graph.starterpack.$type) ?? []),
614
- ]
615
- const posts =
616
- mergeManyMaps(postsLayer0, postsLayer1, postsLayer2) ?? postsLayer0
617
- const allPostUris = [...posts.keys()]
618
- const allRefs = [
619
- ...refs,
620
- ...embedPostUrisLayer1.map(uriToRef), // supports aggregates on embed #viewRecords
621
- ...embedPostUrisLayer2.map(uriToRef),
622
- ]
623
- const threadRefs = allRefs.map((ref) => ({
624
- ...ref,
625
- threadRoot: posts.get(ref.uri)?.record.reply?.root.uri ?? ref.uri,
626
- }))
627
- const postUrisWithPostgates = new Set<AtUriString>()
628
- for (const [uri, post] of posts) {
629
- if (post && post.hasPostGate) {
630
- postUrisWithPostgates.add(uri)
631
- }
632
- }
633
-
634
- const siteStandardRefs = siteStandardRefsFromPosts(
635
- postsLayer0,
636
- postsLayer1,
637
- postsLayer2,
638
- )
639
- const siteStandardLabelSubjects = dedupeStrs(
640
- siteStandardRefs.map((ref) => ref.uri),
641
- )
642
- // Site-standard record owners need profile basics so we can populate
643
- // `associatedProfiles` on the external embed view. Include them in the
644
- // post-author profile fetch below; any DIDs surfaced later by the
645
- // dataplane are picked up by the top-up after the parallel batch.
646
- const ssRefDids = siteStandardRefs.map((ref) => uriToDid(ref.uri))
647
- const knownProfileDids = dedupeStrs([
648
- ...allPostUris.map(didFromUri),
649
- ...ssRefDids,
650
- ])
651
-
652
- const [
653
- postAggs,
654
- postViewers,
655
- labels,
656
- postBlocks,
657
- profileState,
658
- listState,
659
- feedGenState,
660
- labelerState,
661
- starterPackState,
662
- postgates,
663
- {
664
- documents: siteStandardDocuments,
665
- publications: siteStandardPublications,
666
- },
667
- ] = await Promise.all([
668
- this.feed.getPostAggregates(allRefs, ctx.viewer),
669
- ctx.viewer
670
- ? this.feed.getPostViewerStates(threadRefs, ctx.viewer)
671
- : undefined,
672
- this.label.getLabelsForSubjects(
673
- [...allPostUris, ...siteStandardLabelSubjects],
674
- ctx.labelers,
675
- ),
676
- this.hydratePostBlocks(posts, ctx),
677
- this.hydrateProfiles(knownProfileDids, ctx),
678
- this.hydrateLists([...nestedListUris, ...threadgateListUris], ctx),
679
- this.hydrateFeedGens(nestedFeedGenUris, ctx),
680
- this.hydrateLabelers(nestedLabelerDids, ctx),
681
- this.hydrateStarterPacksBasic(nestedStarterPackUris, ctx),
682
- this.feed.getPostgatesForPosts([...postUrisWithPostgates.values()]),
683
- this.external.getSiteStandardRecordsByRef(
684
- siteStandardRefs,
685
- ctx.includeTakedowns,
686
- ),
687
- ])
688
- if (!ctx.includeTakedowns) {
689
- actionTakedownLabels(allPostUris, posts, labels)
690
- actionSiteStandardTakedownLabels(
691
- siteStandardDocuments,
692
- siteStandardPublications,
693
- labels,
694
- )
695
- }
696
-
697
- // Defensive top-up: in the unlikely case the dataplane returned a
698
- // publication owned by a DID not present in the post-author or
699
- // pinned-ref sets, fetch its profile serially and fold it in.
700
- const knownProfileDidsSet = new Set(knownProfileDids)
701
- const extraSsDids: DidString[] = []
702
- for (const key of siteStandardPublications.keys()) {
703
- const did = uriToDid(parseSiteStandardRecordKey(key).uri)
704
- if (!knownProfileDidsSet.has(did)) {
705
- knownProfileDidsSet.add(did)
706
- extraSsDids.push(did)
707
- }
708
- }
709
- const mergedProfileState = extraSsDids.length
710
- ? mergeStates(
711
- profileState,
712
- await this.hydrateProfilesBasic(extraSsDids, ctx),
713
- )
714
- : profileState
715
- // combine all hydration state
716
- return mergeManyStates(
717
- mergedProfileState,
718
- listState,
719
- feedGenState,
720
- labelerState,
721
- starterPackState,
722
- {
723
- posts,
724
- postAggs,
725
- postViewers,
726
- postBlocks,
727
- labels,
728
- threadgates,
729
- postgates,
730
- siteStandardDocuments,
731
- siteStandardPublications,
732
- ctx,
733
- },
734
- )
735
- }
736
-
737
- private async hydratePostBlocks(
738
- posts: Posts,
739
- ctx: HydrateCtx,
740
- ): Promise<PostBlocks> {
741
- const postBlocks: PostBlocks = new HydrationMap()
742
- const postBlocksPairs = new Map<AtUriString, PostBlockPairs>()
743
- const relationships: RelationshipPair[] = []
744
- for (const [uri, item] of posts) {
745
- if (!item) continue
746
- const post = item.record
747
- const creator = didFromUri(uri)
748
- const postBlockPairs: PostBlockPairs = {}
749
- postBlocksPairs.set(uri, postBlockPairs)
750
- // 3p block for replies
751
- const parentUri = post.reply?.parent.uri
752
- const parentDid = parentUri && didFromUri(parentUri)
753
- if (parentDid && parentDid !== creator) {
754
- const pair: RelationshipPair = [creator, parentDid]
755
- relationships.push(pair)
756
- postBlockPairs.parent = pair
757
- }
758
- const rootUri = post.reply?.root.uri
759
- const rootDid = rootUri && didFromUri(rootUri)
760
- if (rootDid && rootDid !== creator) {
761
- const pair: RelationshipPair = [creator, rootDid]
762
- relationships.push(pair)
763
- postBlockPairs.root = pair
764
- }
765
- // 3p block for record embeds
766
- for (const embedUri of nestedRecordUris(post)) {
767
- const pair: RelationshipPair = [creator, didFromUri(embedUri)]
768
- relationships.push(pair)
769
- postBlockPairs.embed = pair
770
- }
771
- }
772
- // replace embed/parent/root pairs with block state
773
- const blocks = await this.hydrateBidirectionalBlocks(
774
- pairsToMap(relationships),
775
- ctx,
776
- )
777
- for (const [uri, { embed, parent, root }] of postBlocksPairs) {
778
- postBlocks.set(uri, {
779
- embed: !!embed && !!isBlocked(blocks, embed),
780
- parent: !!parent && !!isBlocked(blocks, parent),
781
- root: !!root && !!isBlocked(blocks, root),
782
- })
783
- }
784
- return postBlocks
785
- }
786
-
787
- // app.bsky.feed.defs#feedViewPost
788
- // - post (+ replies w/ reply parent author)
789
- // - profile
790
- // - list basic
791
- // - list
792
- // - profile
793
- // - list basic
794
- // - feedgen
795
- // - profile
796
- // - list basic
797
- // - repost
798
- // - profile
799
- // - list basic
800
- // - post
801
- // - ...
802
- async hydrateFeedItems(
803
- items: FeedItem[],
804
- ctx: HydrateCtx,
805
- ): Promise<HydrationState> {
806
- // get posts, collect reply refs
807
- const posts = await this.feed.getPosts(
808
- items.map((item) => item.post.uri),
809
- ctx.includeTakedowns,
810
- )
811
- const rootUris: AtUriString[] = []
812
- const parentUris: AtUriString[] = []
813
- const postAndReplyRefs: ItemRef[] = []
814
- posts.forEach((post, uri) => {
815
- if (!post) return
816
- postAndReplyRefs.push({ uri, cid: post.cid })
817
- if (post.record.reply) {
818
- rootUris.push(post.record.reply.root.uri)
819
- parentUris.push(post.record.reply.parent.uri)
820
- postAndReplyRefs.push(post.record.reply.root, post.record.reply.parent)
821
- }
822
- })
823
- // get replies, collect reply parent authors
824
- const replies = await this.feed.getPosts(
825
- [...rootUris, ...parentUris],
826
- ctx.includeTakedowns,
827
- )
828
- const replyParentAuthors: DidString[] = []
829
- parentUris.forEach((uri) => {
830
- const parent = replies.get(uri)
831
- if (!parent?.record.reply) return
832
- replyParentAuthors.push(didFromUri(parent.record.reply.parent.uri))
833
- })
834
- // hydrate state for all posts, reposts, authors of reposts + reply parent authors
835
- const repostUris = mapDefined(items, (item) => item.repost?.uri)
836
- const [postState, repostProfileState, reposts] = await Promise.all([
837
- this.hydratePosts(postAndReplyRefs, ctx, {
838
- posts: posts.merge(replies), // avoids refetches of posts
839
- }),
840
- this.hydrateProfiles(
841
- [...repostUris.map(didFromUri), ...replyParentAuthors],
842
- ctx,
843
- ),
844
- this.feed.getReposts(repostUris, ctx.includeTakedowns),
845
- ])
846
- return mergeManyStates(postState, repostProfileState, {
847
- reposts,
848
- ctx,
849
- })
850
- }
851
-
852
- /**
853
- * Hydrate the state needed to build an `app.bsky.embed.external#view` for
854
- * a set of `site.standard.*` AT-URIs at compose-time. Filters input URIs to
855
- * SS collections, fetches latest indexed versions plus labels, and gates
856
- * taken-down records.
857
- */
858
- async hydrateEmbedExternalViewFromUris(
859
- uris: AtUriString[],
860
- ctx: HydrateCtx,
861
- ): Promise<HydrationState> {
862
- const ssUris = dedupeStrs(
863
- uris.filter((u) =>
864
- new AtUri(u).collection.startsWith(SITE_STANDARD_NSID_PREFIX),
865
- ),
866
- ) as AtUriString[]
867
- if (!ssUris.length) return { ctx }
868
- const dids = dedupeStrs(ssUris.map((uri) => uriToDid(uri)))
869
-
870
- const [{ documents, publications }, labels, profiles] = await Promise.all([
871
- this.external.getSiteStandardRecordsByURI(ssUris, ctx.includeTakedowns),
872
- this.label.getLabelsForSubjects(ssUris, ctx.labelers),
873
- this.hydrateProfilesBasic(dids, ctx),
874
- ])
875
- if (!ctx.includeTakedowns) {
876
- actionSiteStandardTakedownLabels(documents, publications, labels)
877
- }
878
- // Edge case: a document's `site` may resolve to a publication owned by a
879
- // different repo than any of the input URIs (the dataplane returns it
880
- // even though it wasn't requested directly). Top up profile coverage for
881
- // any such DIDs with a serial second hydration so `associatedProfiles`
882
- // is complete.
883
- const knownDids = new Set<string>(dids)
884
- const extraDids: DidString[] = []
885
- for (const key of publications.keys()) {
886
- const did = uriToDid(parseSiteStandardRecordKey(key).uri)
887
- if (!knownDids.has(did)) {
888
- knownDids.add(did)
889
- extraDids.push(did)
890
- }
891
- }
892
- const profilesState = extraDids.length
893
- ? mergeStates(profiles, await this.hydrateProfilesBasic(extraDids, ctx))
894
- : profiles
895
-
896
- return mergeStates(profilesState, {
897
- ctx,
898
- labels,
899
- siteStandardDocuments: documents,
900
- siteStandardPublications: publications,
901
- })
902
- }
903
-
904
- // app.bsky.feed.defs#threadViewPost
905
- // - post
906
- // - profile
907
- // - list basic
908
- // - list
909
- // - profile
910
- // - list basic
911
- // - feedgen
912
- // - profile
913
- // - list basic
914
- async hydrateThreadPosts(
915
- refs: ItemRef[],
916
- ctx: HydrateCtx,
917
- ): Promise<HydrationState> {
918
- const postsState = await this.hydratePosts(refs, ctx, undefined, {
919
- processDynamicTagsForView: ctx.features.checkGate(
920
- ctx.features.Gate.ThreadsReplyRankingExplorationEnable,
921
- )
922
- ? 'thread'
923
- : undefined,
924
- })
925
-
926
- const threadRefs: ThreadRef[] = []
927
-
928
- if (postsState.posts) {
929
- for (const [uri, post] of postsState.posts.entries()) {
930
- if (post) {
931
- threadRefs.push({
932
- uri,
933
- cid: post.cid,
934
- threadRoot: post.record.reply?.root.uri ?? uri,
935
- })
936
- }
937
- }
938
- }
939
-
940
- const threadContexts = await this.feed.getThreadContexts(threadRefs)
941
-
942
- return mergeStates(postsState, { threadContexts })
943
- }
944
-
945
- // app.bsky.feed.defs#generatorView
946
- // - feedgen
947
- // - profile
948
- // - list basic
949
- async hydrateFeedGens(
950
- uris: AtUriString[], // @TODO any way to get refs here?
951
- ctx: HydrateCtx,
952
- ): Promise<HydrationState> {
953
- const [feedgens, feedgenAggs, feedgenViewers, profileState, labels] =
954
- await Promise.all([
955
- this.feed.getFeedGens(uris, ctx.includeTakedowns),
956
- this.feed.getFeedGenAggregates(
957
- uris.map((uri) => ({ uri })),
958
- ctx.viewer,
959
- ),
960
- ctx.viewer
961
- ? this.feed.getFeedGenViewerStates(uris, ctx.viewer)
962
- : undefined,
963
- this.hydrateProfiles(uris.map(didFromUri), ctx),
964
- this.label.getLabelsForSubjects(uris, ctx.labelers),
965
- ])
966
- if (!ctx.includeTakedowns) {
967
- actionTakedownLabels(uris, feedgens, labels)
968
- }
969
- return mergeStates(profileState, {
970
- feedgens,
971
- feedgenAggs,
972
- feedgenViewers,
973
- labels,
974
- ctx,
975
- })
976
- }
977
-
978
- // app.bsky.graph.defs#starterPackViewBasic
979
- // - starterpack
980
- // - profile
981
- // - list basic
982
- // - labels
983
- async hydrateStarterPacksBasic(
984
- uris: AtUriString[],
985
- ctx: HydrateCtx,
986
- ): Promise<HydrationState> {
987
- const [starterPacks, starterPackAggs, profileState, labels] =
988
- await Promise.all([
989
- this.graph.getStarterPacks(uris, ctx.includeTakedowns),
990
- this.graph.getStarterPackAggregates(uris.map((uri) => ({ uri }))),
991
- this.hydrateProfiles(uris.map(didFromUri), ctx),
992
- this.label.getLabelsForSubjects(uris, ctx.labelers),
993
- ])
994
- if (!ctx.includeTakedowns) {
995
- actionTakedownLabels(uris, starterPacks, labels)
996
- }
997
- return mergeStates(profileState, {
998
- starterPacks,
999
- starterPackAggs,
1000
- labels,
1001
- ctx,
1002
- })
1003
- }
1004
-
1005
- // app.bsky.graph.defs#starterPackView
1006
- // - starterpack
1007
- // - profile
1008
- // - list basic
1009
- // - feedgen
1010
- // - profile
1011
- // - list basic
1012
- // - list basic
1013
- // - list item
1014
- // - profile
1015
- // - list basic
1016
- // - labels
1017
- async hydrateStarterPacks(
1018
- uris: AtUriString[],
1019
- ctx: HydrateCtx,
1020
- ): Promise<HydrationState> {
1021
- const starterPackState = await this.hydrateStarterPacksBasic(uris, ctx)
1022
- // gather feed and list uris
1023
- const feedUriSet = new Set<AtUriString>()
1024
- const listUriSet = new Set<AtUriString>()
1025
- starterPackState.starterPacks?.forEach((sp) => {
1026
- sp?.record.feeds?.forEach((feed) => feedUriSet.add(feed.uri))
1027
- if (sp?.record.list) {
1028
- listUriSet.add(sp?.record.list)
1029
- }
1030
- })
1031
- const feedUris = [...feedUriSet]
1032
- const listUris = [...listUriSet]
1033
- // hydrate feeds, lists, and their members
1034
- const [feedGenState, listState, ...listsMembers] = await Promise.all([
1035
- this.hydrateFeedGens(feedUris, ctx),
1036
- this.hydrateLists(listUris, ctx),
1037
- ...listUris.map((uri) =>
1038
- this.dataplane.getListMembers({ listUri: uri, limit: 50 }),
1039
- ),
1040
- ])
1041
- // collect list info
1042
- const listMembersByList = new Map(
1043
- listUris.map((uri, i) => [uri, listsMembers[i]]),
1044
- )
1045
- const listMemberDids = listsMembers.flatMap((lm) =>
1046
- lm.listitems.map((li) => li.did as DidString),
1047
- )
1048
- const listCreatorMemberPairs = [...listMembersByList.entries()].flatMap(
1049
- ([listUri, members]) => {
1050
- const creator = didFromUri(listUri)
1051
- return members.listitems.map(
1052
- (li): RelationshipPair => [creator, li.did as DidString],
1053
- )
1054
- },
1055
- )
1056
- const [blocks, listMemberAggs] = await Promise.all([
1057
- this.hydrateBidirectionalBlocks(pairsToMap(listCreatorMemberPairs), ctx),
1058
- // sample top list items per starter pack based on their follows
1059
- this.actor.getProfileAggregates(listMemberDids),
1060
- ])
1061
- const listItemUris: AtUriString[] = []
1062
- uris.forEach((uri) => {
1063
- const sp = starterPackState.starterPacks?.get(uri)
1064
- const agg = starterPackState.starterPackAggs?.get(uri)
1065
- if (!sp?.record.list || !agg) return
1066
- const members = listMembersByList.get(sp.record.list)
1067
- if (!members) return
1068
- const creator = didFromUri(sp.record.list)
1069
- // update aggregation with list items for top 12 most followed members
1070
- agg.listItemSampleUris = [
1071
- ...members.listitems.filter(
1072
- (li) =>
1073
- ctx.viewer === creator ||
1074
- !isBlocked(blocks, [creator, li.did as DidString]),
1075
- ),
1076
- ]
1077
- .sort((li1, li2) => {
1078
- const score1 =
1079
- listMemberAggs.get(li1.did as DidString)?.followers ?? 0
1080
- const score2 =
1081
- listMemberAggs.get(li2.did as DidString)?.followers ?? 0
1082
- return score2 - score1
1083
- })
1084
- .slice(0, 12)
1085
- .map((li) => li.uri as AtUriString)
1086
- listItemUris.push(...agg.listItemSampleUris)
1087
- })
1088
- // hydrate sampled list items
1089
- const listItemState = await this.hydrateListItems(listItemUris, ctx)
1090
- return mergeManyStates(
1091
- starterPackState,
1092
- feedGenState,
1093
- listState,
1094
- listItemState,
1095
- )
1096
- }
1097
-
1098
- // app.bsky.feed.getLikes#like
1099
- // - like
1100
- // - profile
1101
- // - list basic
1102
- async hydrateLikes(
1103
- authorDid: DidString,
1104
- uris: AtUriString[],
1105
- ctx: HydrateCtx,
1106
- ): Promise<HydrationState> {
1107
- const [likes, profileState] = await Promise.all([
1108
- this.feed.getLikes(uris, ctx.includeTakedowns),
1109
- this.hydrateProfiles(uris.map(didFromUri), ctx),
1110
- ])
1111
-
1112
- const pairs: RelationshipPair[] = []
1113
- for (const [uri, like] of likes) {
1114
- if (like) {
1115
- pairs.push([authorDid, didFromUri(uri)])
1116
- }
1117
- }
1118
- const blocks = await this.hydrateBidirectionalBlocks(pairsToMap(pairs), ctx)
1119
- const likeBlocks = new HydrationMap<AtUriString, LikeBlock>()
1120
- for (const [uri, like] of likes) {
1121
- if (like) {
1122
- likeBlocks.set(uri, isBlocked(blocks, [authorDid, didFromUri(uri)]))
1123
- } else {
1124
- likeBlocks.set(uri, null)
1125
- }
1126
- }
1127
-
1128
- return mergeStates(profileState, { likes, likeBlocks, ctx })
1129
- }
1130
-
1131
- // app.bsky.feed.getRepostedBy#repostedBy
1132
- // - repost
1133
- // - profile
1134
- // - list basic
1135
- async hydrateReposts(uris: AtUriString[], ctx: HydrateCtx) {
1136
- const [reposts, profileState] = await Promise.all([
1137
- this.feed.getReposts(uris, ctx.includeTakedowns),
1138
- this.hydrateProfiles(uris.map(didFromUri), ctx),
1139
- ])
1140
- return mergeStates(profileState, { reposts, ctx })
1141
- }
1142
-
1143
- // app.bsky.notification.listNotifications#notification
1144
- // - notification
1145
- // - profile
1146
- // - list basic
1147
- async hydrateNotifications(
1148
- notifs: Notification[],
1149
- ctx: HydrateCtx,
1150
- ): Promise<HydrationState> {
1151
- const uris = notifs.map((notif) => notif.uri as AtUriString)
1152
- const collections = urisByCollection(uris)
1153
- const postUris = collections.get(app.bsky.feed.post.$type) ?? []
1154
- const likeUris = collections.get(app.bsky.feed.like.$type) ?? []
1155
- const repostUris = collections.get(app.bsky.feed.repost.$type) ?? []
1156
- const followUris = collections.get(app.bsky.graph.follow.$type) ?? []
1157
- const verificationUris =
1158
- collections.get(app.bsky.graph.verification.$type) ?? []
1159
- const [
1160
- posts,
1161
- likes,
1162
- reposts,
1163
- follows,
1164
- verifications,
1165
- labels,
1166
- profileState,
1167
- ] = await Promise.all([
1168
- this.feed.getPosts(postUris), // reason: mention, reply, quote
1169
- this.feed.getLikes(likeUris), // reason: like
1170
- this.feed.getReposts(repostUris), // reason: repost
1171
- this.graph.getFollows(followUris), // reason: follow
1172
- this.graph.getVerifications(verificationUris), // reason: verified
1173
- this.label.getLabelsForSubjects(uris, ctx.labelers),
1174
- this.hydrateProfiles(uris.map(didFromUri), ctx),
1175
- ])
1176
- const viewerRootPostUris = new Set<AtUriString>()
1177
- for (const notif of notifs) {
1178
- if (notif.reason === 'reply') {
1179
- const post = posts.get(notif.uri as AtUriString)
1180
- if (post) {
1181
- const rootUri = post.record.reply?.root.uri
1182
- if (rootUri && didFromUri(rootUri) === ctx.viewer) {
1183
- viewerRootPostUris.add(rootUri)
1184
- }
1185
- }
1186
- }
1187
- }
1188
- const threadgates = await this.feed.getThreadgatesForPosts([
1189
- ...viewerRootPostUris.values(),
1190
- ])
1191
- actionTakedownLabels(postUris, posts, labels)
1192
- return mergeStates(profileState, {
1193
- posts,
1194
- likes,
1195
- reposts,
1196
- follows,
1197
- verifications,
1198
- labels,
1199
- threadgates,
1200
- ctx,
1201
- })
1202
- }
1203
-
1204
- async hydrateBookmarks(
1205
- bookmarkInfos: BookmarkInfo[],
1206
- ctx: HydrateCtx,
1207
- ): Promise<HydrationState> {
1208
- const viewer = ctx.viewer
1209
- if (!viewer) return {}
1210
- const bookmarksRes = await this.dataplane.getBookmarksByActorAndSubjects({
1211
- actorDid: viewer,
1212
- uris: bookmarkInfos.map((b) => b.subject),
1213
- })
1214
-
1215
- type BookmarkWithRef = BookmarkLex & { ref: RecordRef }
1216
- const bookmarks: BookmarkWithRef[] = bookmarksRes.bookmarks.filter(
1217
- (bookmark): bookmark is BookmarkWithRef => !!bookmark.ref?.key,
1218
- )
1219
-
1220
- // mapping DID -> stash key -> bookmark
1221
- const bookmarksMap = new HydrationMap<
1222
- DidString,
1223
- HydrationMap<string, Bookmark>
1224
- >()
1225
-
1226
- bookmarksMap.set(
1227
- viewer,
1228
- new HydrationMap(
1229
- bookmarks.map((bookmark) => [
1230
- bookmark.ref.key,
1231
- {
1232
- ref: bookmark.ref,
1233
- subjectUri: bookmark.subjectUri,
1234
- subjectCid: bookmark.subjectCid,
1235
- indexedAt: parseDate(bookmark.indexedAt),
1236
- },
1237
- ]),
1238
- ),
1239
- )
1240
-
1241
- // @NOTE: The `createBookmark` endpoint limits bookmarks to be of posts,
1242
- // so we can assume currently all subjects are posts.
1243
- const postsState = await this.hydratePosts(
1244
- bookmarks.map((bookmark) => ({
1245
- uri: bookmark.subjectUri as AtUriString,
1246
- })),
1247
- ctx,
1248
- )
1249
-
1250
- return mergeStates(postsState, { bookmarks: bookmarksMap })
1251
- }
1252
-
1253
- // provides partial hydration state within getFollows / getFollowers, mainly for applying rules
1254
- async hydrateFollows(
1255
- uris: AtUriString[],
1256
- ctx: HydrateCtx,
1257
- ): Promise<HydrationState> {
1258
- const follows = await this.graph.getFollows(uris)
1259
- const pairs: RelationshipPair[] = []
1260
- for (const [uri, follow] of follows) {
1261
- if (follow) {
1262
- pairs.push([didFromUri(uri), follow.record.subject])
1263
- }
1264
- }
1265
- const blocks = await this.hydrateBidirectionalBlocks(pairsToMap(pairs), ctx)
1266
- const followBlocks: FollowBlocks = new HydrationMap()
1267
- for (const [uri, follow] of follows) {
1268
- if (follow) {
1269
- followBlocks.set(
1270
- uri,
1271
- isBlocked(blocks, [didFromUri(uri), follow.record.subject]),
1272
- )
1273
- } else {
1274
- followBlocks.set(uri, null)
1275
- }
1276
- }
1277
- return { follows, followBlocks }
1278
- }
1279
-
1280
- async hydrateBidirectionalBlocks(
1281
- didMap: Map<DidString, DidString[]>, // DID -> DID[]
1282
- ctx: HydrateCtx,
1283
- ): Promise<BidirectionalBlocks> {
1284
- const pairs: RelationshipPair[] = []
1285
- for (const [source, targets] of didMap) {
1286
- for (const target of targets) {
1287
- pairs.push([source, target])
1288
- }
1289
- }
1290
-
1291
- const blocks = await this.graph.getBidirectionalBlocks(pairs)
1292
- const listUrisSet = new Set<AtUriString>()
1293
- for (const [source, targets] of didMap) {
1294
- for (const target of targets) {
1295
- const block = blocks.get(source, target)
1296
- if (block?.blockListUri) {
1297
- listUrisSet.add(block.blockListUri)
1298
- }
1299
- }
1300
- }
1301
- const listUris = [...listUrisSet]
1302
-
1303
- // if a list no longer exists or is not a mod list, then remove from block entry
1304
- const listState = await this.hydrateListsBasic(listUris, ctx)
1305
- for (const [source, targets] of didMap) {
1306
- for (const target of targets) {
1307
- const block = blocks.get(source, target)
1308
- if (!isModList(block?.blockListUri, listState)) {
1309
- delete block?.blockListUri
1310
- }
1311
- }
1312
- }
1313
-
1314
- const result: BidirectionalBlocks = new HydrationMap()
1315
-
1316
- for (const [source, targets] of didMap) {
1317
- const didBlocks = new HydrationMap<DidString, boolean>()
1318
- for (const target of targets) {
1319
- const block = blocks.get(source, target)
1320
-
1321
- // If a list no longer exists or is not a mod list, then remove from block entry.
1322
- // isModList confirms the list exists in listState, which ensures it wasn't taken down.
1323
- if (!isModList(block?.blockListUri, listState)) {
1324
- delete block?.blockListUri
1325
- }
1326
-
1327
- const blockEntry: BlockEntry = {
1328
- blockUri: block?.blockUri,
1329
- blockListUri:
1330
- block?.blockListUri &&
1331
- listState.actors?.get(uriToDid(block.blockListUri))
1332
- ? block.blockListUri
1333
- : undefined,
1334
- }
1335
-
1336
- didBlocks.set(
1337
- target,
1338
- !!blockEntry.blockUri || !!blockEntry.blockListUri,
1339
- )
1340
- }
1341
- result.set(source, didBlocks)
1342
- }
1343
-
1344
- return result
1345
- }
1346
-
1347
- // app.bsky.labeler.def#labelerViewDetailed
1348
- // - labeler
1349
- // - profile
1350
- // - list basic
1351
- async hydrateLabelers(
1352
- dids: DidString[],
1353
- ctx: HydrateCtx,
1354
- ): Promise<HydrationState> {
1355
- const [labelers, labelerAggs, labelerViewers, profileState] =
1356
- await Promise.all([
1357
- this.label.getLabelers(dids, ctx.includeTakedowns),
1358
- this.label.getLabelerAggregates(dids, ctx.viewer),
1359
- ctx.viewer
1360
- ? this.label.getLabelerViewerStates(dids, ctx.viewer)
1361
- : undefined,
1362
- this.hydrateProfiles(dids, ctx),
1363
- ])
1364
- actionTakedownLabels(dids, labelers, profileState.labels ?? new Labels())
1365
- return mergeStates(profileState, {
1366
- labelers,
1367
- labelerAggs,
1368
- labelerViewers,
1369
- ctx,
1370
- })
1371
- }
1372
-
1373
- // ad-hoc record hydration
1374
- // in com.atproto.repo.getRecord
1375
- async getRecord(uri: AtUriString, includeTakedowns = false) {
1376
- const parsed = new AtUri(uri)
1377
- const collection = parsed.collection
1378
- if (collection === app.bsky.feed.post.$type) {
1379
- return (
1380
- (await this.feed.getPosts([uri], includeTakedowns)).get(uri) ??
1381
- undefined
1382
- )
1383
- } else if (collection === app.bsky.feed.repost.$type) {
1384
- return (
1385
- (await this.feed.getReposts([uri], includeTakedowns)).get(uri) ??
1386
- undefined
1387
- )
1388
- } else if (collection === app.bsky.feed.like.$type) {
1389
- return (
1390
- (await this.feed.getLikes([uri], includeTakedowns)).get(uri) ??
1391
- undefined
1392
- )
1393
- } else if (collection === app.bsky.graph.follow.$type) {
1394
- return (
1395
- (await this.graph.getFollows([uri], includeTakedowns)).get(uri) ??
1396
- undefined
1397
- )
1398
- } else if (collection === app.bsky.graph.list.$type) {
1399
- return (
1400
- (await this.graph.getLists([uri], includeTakedowns)).get(uri) ??
1401
- undefined
1402
- )
1403
- } else if (collection === app.bsky.graph.listitem.$type) {
1404
- return (
1405
- (await this.graph.getListItems([uri], includeTakedowns)).get(uri) ??
1406
- undefined
1407
- )
1408
- } else if (collection === app.bsky.graph.block.$type) {
1409
- return (
1410
- (await this.graph.getBlocks([uri], includeTakedowns)).get(uri) ??
1411
- undefined
1412
- )
1413
- } else if (collection === app.bsky.graph.starterpack.$type) {
1414
- return (
1415
- (await this.graph.getStarterPacks([uri], includeTakedowns)).get(uri) ??
1416
- undefined
1417
- )
1418
- } else if (collection === app.bsky.feed.generator.$type) {
1419
- return (
1420
- (await this.feed.getFeedGens([uri], includeTakedowns)).get(uri) ??
1421
- undefined
1422
- )
1423
- } else if (collection === app.bsky.feed.threadgate.$type) {
1424
- return (
1425
- (await this.feed.getThreadgateRecords([uri], includeTakedowns)).get(
1426
- uri,
1427
- ) ?? undefined
1428
- )
1429
- } else if (collection === app.bsky.feed.postgate.$type) {
1430
- return (
1431
- (await this.feed.getPostgateRecords([uri], includeTakedowns)).get(
1432
- uri,
1433
- ) ?? undefined
1434
- )
1435
- } else if (collection === app.bsky.labeler.service.$type) {
1436
- if (parsed.rkey !== 'self') return
1437
- const { did } = parsed
1438
- return (
1439
- (await this.label.getLabelers([did], includeTakedowns)).get(did) ??
1440
- undefined
1441
- )
1442
- } else if (collection === chat.bsky.actor.declaration.$type) {
1443
- if (parsed.rkey !== 'self') return
1444
- return (
1445
- (await this.actor.getChatDeclarations([uri], includeTakedowns)).get(
1446
- uri,
1447
- ) ?? undefined
1448
- )
1449
- } else if (collection === com.germnetwork.declaration.$type) {
1450
- if (parsed.rkey !== 'self') return
1451
- return (
1452
- (await this.actor.getGermDeclarations([uri], includeTakedowns)).get(
1453
- uri,
1454
- ) ?? undefined
1455
- )
1456
- } else if (collection === app.bsky.notification.declaration.$type) {
1457
- if (parsed.rkey !== 'self') return
1458
- return (
1459
- (
1460
- await this.actor.getNotificationDeclarations([uri], includeTakedowns)
1461
- ).get(uri) ?? undefined
1462
- )
1463
- } else if (collection === app.bsky.actor.status.$type) {
1464
- if (parsed.rkey !== 'self') return
1465
- return (
1466
- (await this.actor.getStatus([uri], includeTakedowns)).get(uri) ??
1467
- undefined
1468
- )
1469
- } else if (collection === app.bsky.actor.profile.$type) {
1470
- const did = parsed.did
1471
- const actor = (
1472
- await this.actor.getActors([did], { includeTakedowns })
1473
- ).get(did)
1474
- if (!actor?.profile || !actor?.profileCid) return undefined
1475
- const recordInfo: RecordInfo<ProfileRecord> = {
1476
- record: actor.profile,
1477
- cid: actor.profileCid,
1478
- sortedAt: actor.sortedAt ?? new Date(0), // @NOTE will be present since profile record is present
1479
- indexedAt: actor.indexedAt ?? new Date(0), // @NOTE will be present since profile record is present
1480
- takedownRef: actor.profileTakedownRef,
1481
- }
1482
-
1483
- return recordInfo
1484
- }
1485
- }
1486
-
1487
- async createContext<
1488
- V extends Omit<HydrateCtxVals, 'features'> & {
1489
- features?: ScopedFeatureGatesClient
1490
- },
1491
- >(vals: V): Promise<HydrateCtx & { viewer: V['viewer'] }> {
1492
- // ensures we're only apply labelers that exist and are not taken down
1493
- const labelers = vals.labelers.dids
1494
- const nonServiceLabelers = labelers.filter(
1495
- (did) => !this.serviceLabelers.has(did),
1496
- )
1497
- const labelerActors = await this.actor.getActors(nonServiceLabelers, {
1498
- includeTakedowns: vals.includeTakedowns,
1499
- })
1500
- const availableDids = labelers.filter(
1501
- (did) => this.serviceLabelers.has(did) || !!labelerActors.get(did),
1502
- )
1503
- const availableLabelers = {
1504
- dids: availableDids,
1505
- redact: vals.labelers.redact,
1506
- }
1507
- const includeDebugField =
1508
- !!vals.viewer && this.config.debugFieldAllowedDids.has(vals.viewer)
1509
- return new HydrateCtx({
1510
- labelers: availableLabelers,
1511
- viewer: vals.viewer,
1512
- includeTakedowns: vals.includeTakedowns,
1513
- include3pBlocks: vals.include3pBlocks,
1514
- skipViewerBlocks: vals.skipViewerBlocks,
1515
- includeDebugField,
1516
- // create default anonymous scope
1517
- features: vals.features || this.config.featureGatesClient.scope({}),
1518
- })
1519
- }
1520
-
1521
- async resolveUri(uriStr: AtUriString): Promise<AtUriString> {
1522
- const uri = new AtUri(uriStr)
1523
- const [did] = await this.actor.getDids([uri.host])
1524
- if (!did) return uriStr
1525
- uri.hostname = did
1526
- return uri.toString()
1527
- }
1528
- }
1529
-
1530
- // service refs may look like "did:plc:example#service_id". we want to extract the did part "did:plc:example".
1531
- const serviceRefToDid = (serviceRef: string) => {
1532
- const idx = serviceRef.indexOf('#')
1533
- return (idx !== -1 ? serviceRef.slice(0, idx) : serviceRef) as DidString
1534
- }
1535
-
1536
- const listUrisFromProfileViewer = (
1537
- item: ProfileViewerState | null,
1538
- ): AtUriString[] => {
1539
- const listUris: AtUriString[] = []
1540
- if (item?.mutedByList) {
1541
- listUris.push(item.mutedByList)
1542
- }
1543
- if (item?.blockingByList) {
1544
- listUris.push(item.blockingByList)
1545
- }
1546
- // blocked-by list does not appear in views, but will be used to evaluate the existence of a block between users.
1547
- if (item?.blockedByList) {
1548
- listUris.push(item.blockedByList)
1549
- }
1550
- return listUris
1551
- }
1552
-
1553
- const removeNonModListsFromProfileViewer = (
1554
- item: ProfileViewerState | null,
1555
- state: HydrationState,
1556
- ) => {
1557
- if (!isModList(item?.mutedByList, state)) {
1558
- delete item?.mutedByList
1559
- }
1560
- if (!isModList(item?.blockingByList, state)) {
1561
- delete item?.blockingByList
1562
- }
1563
- if (!isModList(item?.blockedByList, state)) {
1564
- delete item?.blockedByList
1565
- }
1566
- }
1567
-
1568
- const isModList = (
1569
- listUri: AtUriString | undefined,
1570
- state: HydrationState,
1571
- ): boolean => {
1572
- if (!listUri) return false
1573
- const list = state.lists?.get(listUri)
1574
- return list?.record.purpose === 'app.bsky.graph.defs#modlist'
1575
- }
1576
-
1577
- const labelSubjectsForDid = (dids: DidString[]) => {
1578
- return [
1579
- ...dids,
1580
- ...dids.map((did) => atUri(did, app.bsky.actor.profile)),
1581
- ...dids.map((did) => atUri(did, app.bsky.actor.status)),
1582
- ]
1583
- }
1584
-
1585
- const rootUrisFromPosts = (posts: Posts): AtUriString[] => {
1586
- const uris: AtUriString[] = []
1587
- for (const item of posts.values()) {
1588
- const rootUri = item && rootUriFromPost(item)
1589
- if (rootUri) {
1590
- uris.push(rootUri)
1591
- }
1592
- }
1593
- return uris
1594
- }
1595
-
1596
- const rootUriFromPost = (post: Post): AtUriString | undefined => {
1597
- return post.record.reply?.root.uri
1598
- }
1599
-
1600
- const nestedRecordUrisFromPosts = (
1601
- posts: Posts,
1602
- fromUris?: AtUriString[],
1603
- ): AtUriString[] => {
1604
- const uris: AtUriString[] = []
1605
- const postUris = fromUris ?? posts.keys()
1606
- for (const uri of postUris) {
1607
- const item = posts.get(uri)
1608
- if (item) {
1609
- uris.push(...nestedRecordUris(item.record))
1610
- }
1611
- }
1612
- return uris
1613
- }
1614
-
1615
- const nestedRecordUris = (post: Post['record']): AtUriString[] => {
1616
- const uris: AtUriString[] = []
1617
- if (!post?.embed) return uris
1618
- if (isRecordEmbedType(post.embed)) {
1619
- uris.push(post.embed.record.uri)
1620
- } else if (isRecordWithMediaType(post.embed)) {
1621
- uris.push(post.embed.record.record.uri)
1622
- }
1623
- return uris
1624
- }
1625
-
1626
- /**
1627
- * Returns every strongRef the post embedded as `external.associatedRefs`,
1628
- * regardless of collection. Callers are responsible for filtering to NSIDs
1629
- * they care about.
1630
- */
1631
- const externalAssociatedRefs = (
1632
- post: Post['record'],
1633
- ): { uri: AtUriString; cid: string }[] => {
1634
- const embed = post?.embed
1635
- if (!embed) return []
1636
- if (isExternalEmbedType(embed)) {
1637
- return embed.external.associatedRefs ?? []
1638
- }
1639
- if (isRecordWithMediaType(embed) && isExternalEmbedType(embed.media)) {
1640
- return embed.media.external.associatedRefs ?? []
1641
- }
1642
- return []
1643
- }
1644
-
1645
- /**
1646
- * Collects standard.site refs across all post layers, deduped by `uri+cid` so
1647
- * the dataplane batch is minimal even if multiple posts in the layer reference
1648
- * the same exact version of an SS record. The same `${uri}@${cid}` keys are
1649
- * what `getSiteStandardRecordsByRef` uses on the resulting hydration maps,
1650
- * which lets `lookupAssociatedSiteStandardRecords` do an O(1) version-exact
1651
- * lookup at view time.
1652
- */
1653
- const siteStandardRefsFromPosts = (...postLayers: Posts[]): ItemRef[] => {
1654
- const seen = new Set<string>()
1655
- const out: ItemRef[] = []
1656
- for (const layer of postLayers) {
1657
- for (const item of layer.values()) {
1658
- if (!item) continue
1659
- for (const ref of externalAssociatedRefs(item.record)) {
1660
- if (
1661
- !new AtUri(ref.uri).collection.startsWith(SITE_STANDARD_NSID_PREFIX)
1662
- ) {
1663
- continue
1664
- }
1665
- const key = `${ref.uri}@${ref.cid}`
1666
- if (seen.has(key)) continue
1667
- seen.add(key)
1668
- out.push({ uri: ref.uri, cid: ref.cid })
1669
- }
1670
- }
1671
- }
1672
- return out
1673
- }
1674
-
1675
- const getListUrisFromThreadgates = (gates: Threadgates): AtUriString[] => {
1676
- const uris: AtUriString[] = []
1677
- for (const gate of gates.values()) {
1678
- const listRules = gate?.record.allow?.filter(isListRuleType) ?? []
1679
- for (const rule of listRules) {
1680
- uris.push(rule.list)
1681
- }
1682
- }
1683
- return uris
1684
- }
1685
-
1686
- const isBlocked = (blocks: BidirectionalBlocks, [a, b]: RelationshipPair) => {
1687
- return blocks.get(a)?.get(b) ?? false
1688
- }
1689
-
1690
- const pairsToMap = <K extends string>(pairs: [a: K, b: K][]): Map<K, K[]> => {
1691
- const map = new Map<K, K[]>()
1692
- for (const [a, b] of pairs) {
1693
- const list = map.get(a)
1694
- if (list) list.push(b)
1695
- else map.set(a, [b])
1696
- }
1697
- return map
1698
- }
1699
-
1700
- export const mergeStates = (
1701
- stateA: HydrationState,
1702
- stateB: HydrationState,
1703
- ): HydrationState => {
1704
- assert(
1705
- !stateA.ctx?.viewer ||
1706
- !stateB.ctx?.viewer ||
1707
- stateA.ctx?.viewer === stateB.ctx?.viewer,
1708
- 'incompatible viewers',
1709
- )
1710
- return {
1711
- ctx: stateA.ctx ?? stateB.ctx,
1712
- actors: mergeMaps(stateA.actors, stateB.actors),
1713
- profileAggs: mergeMaps(stateA.profileAggs, stateB.profileAggs),
1714
- profileViewers: mergeMaps(stateA.profileViewers, stateB.profileViewers),
1715
- posts: mergeMaps(stateA.posts, stateB.posts),
1716
- postAggs: mergeMaps(stateA.postAggs, stateB.postAggs),
1717
- postViewers: mergeMaps(stateA.postViewers, stateB.postViewers),
1718
- threadContexts: mergeMaps(stateA.threadContexts, stateB.threadContexts),
1719
- postBlocks: mergeMaps(stateA.postBlocks, stateB.postBlocks),
1720
- reposts: mergeMaps(stateA.reposts, stateB.reposts),
1721
- follows: mergeMaps(stateA.follows, stateB.follows),
1722
- followBlocks: mergeMaps(stateA.followBlocks, stateB.followBlocks),
1723
- threadgates: mergeMaps(stateA.threadgates, stateB.threadgates),
1724
- postgates: mergeMaps(stateA.postgates, stateB.postgates),
1725
- lists: mergeMaps(stateA.lists, stateB.lists),
1726
- listAggs: mergeMaps(stateA.listAggs, stateB.listAggs),
1727
- listMemberships: mergeNestedMaps(
1728
- stateA.listMemberships,
1729
- stateB.listMemberships,
1730
- ),
1731
- listViewers: mergeMaps(stateA.listViewers, stateB.listViewers),
1732
- listItems: mergeMaps(stateA.listItems, stateB.listItems),
1733
- likes: mergeMaps(stateA.likes, stateB.likes),
1734
- likeBlocks: mergeMaps(stateA.likeBlocks, stateB.likeBlocks),
1735
- labels: mergeMaps(stateA.labels, stateB.labels),
1736
- feedgens: mergeMaps(stateA.feedgens, stateB.feedgens),
1737
- feedgenAggs: mergeMaps(stateA.feedgenAggs, stateB.feedgenAggs),
1738
- feedgenViewers: mergeMaps(stateA.feedgenViewers, stateB.feedgenViewers),
1739
- starterPacks: mergeMaps(stateA.starterPacks, stateB.starterPacks),
1740
- starterPackAggs: mergeMaps(stateA.starterPackAggs, stateB.starterPackAggs),
1741
- labelers: mergeMaps(stateA.labelers, stateB.labelers),
1742
- labelerAggs: mergeMaps(stateA.labelerAggs, stateB.labelerAggs),
1743
- labelerViewers: mergeMaps(stateA.labelerViewers, stateB.labelerViewers),
1744
- knownFollowers: mergeMaps(stateA.knownFollowers, stateB.knownFollowers),
1745
- activitySubscriptions: mergeMaps(
1746
- stateA.activitySubscriptions,
1747
- stateB.activitySubscriptions,
1748
- ),
1749
- bidirectionalBlocks: mergeNestedMaps(
1750
- stateA.bidirectionalBlocks,
1751
- stateB.bidirectionalBlocks,
1752
- ),
1753
- verifications: mergeMaps(stateA.verifications, stateB.verifications),
1754
- bookmarks: mergeNestedMaps(stateA.bookmarks, stateB.bookmarks),
1755
- siteStandardDocuments: mergeMaps(
1756
- stateA.siteStandardDocuments,
1757
- stateB.siteStandardDocuments,
1758
- ),
1759
- siteStandardPublications: mergeMaps(
1760
- stateA.siteStandardPublications,
1761
- stateB.siteStandardPublications,
1762
- ),
1763
- }
1764
- }
1765
-
1766
- export const mergeManyStates = (...states: HydrationState[]) => {
1767
- return states.reduce(mergeStates, {} as HydrationState)
1768
- }
1769
-
1770
- const actionTakedownLabels = (
1771
- keys: UriString[],
1772
- hydrationMap: HydrationMap<UriString, unknown>,
1773
- labels: Labels,
1774
- ) => {
1775
- for (const key of keys) {
1776
- if (labels.get(key)?.isTakendown) {
1777
- hydrationMap.set(key, null)
1778
- }
1779
- }
1780
- }
1781
-
1782
- /**
1783
- * Apply takedown labels to the site.standard hydration maps. Per-record
1784
- * takedowns null any entry whose subject URI is taken down; pair takedowns
1785
- * propagate that null across doc/publication pairs so we never render half
1786
- * of a moderated embed. Pairs are discovered via `doc.record.site`; orphan
1787
- * docs and orphan publications are subject only to the per-record sweep.
1788
- */
1789
- const actionSiteStandardTakedownLabels = (
1790
- documents: SiteStandardDocuments,
1791
- publications: SiteStandardPublications,
1792
- labels: Labels,
1793
- ) => {
1794
- // Pairings have to be captured before nulling — the doc record carries
1795
- // the publication URI in its `site` field, and we lose it once we null.
1796
- const pairings: { docKey: string; pubKey: string }[] = []
1797
- if (documents.size > 0 && publications.size > 0) {
1798
- const pubKeysByUri = new Map<string, string[]>()
1799
- for (const key of publications.keys()) {
1800
- const { uri } = parseSiteStandardRecordKey(key)
1801
- const list = pubKeysByUri.get(uri)
1802
- if (list) list.push(key)
1803
- else pubKeysByUri.set(uri, [key])
1804
- }
1805
- for (const [docKey, doc] of documents) {
1806
- const site = doc?.record.site
1807
- if (!site || !site.startsWith('at://')) continue
1808
- for (const pubKey of pubKeysByUri.get(site) ?? []) {
1809
- pairings.push({ docKey, pubKey })
1810
- }
1811
- }
1812
- }
1813
-
1814
- // Per-record takedowns: null any entry whose subject URI is taken down.
1815
- for (const key of documents.keys()) {
1816
- const { uri } = parseSiteStandardRecordKey(key)
1817
- if (labels.get(uri)?.isTakendown) documents.set(key, null)
1818
- }
1819
- for (const key of publications.keys()) {
1820
- const { uri } = parseSiteStandardRecordKey(key)
1821
- if (labels.get(uri)?.isTakendown) publications.set(key, null)
1822
- }
1823
-
1824
- // Pair takedowns: if exactly one side of a pair was nulled above, mirror.
1825
- for (const { docKey, pubKey } of pairings) {
1826
- const doc = documents.get(docKey)
1827
- const pub = publications.get(pubKey)
1828
- if (doc === null && pub !== null) publications.set(pubKey, null)
1829
- if (pub === null && doc !== null) documents.set(docKey, null)
1830
- }
1831
- }
1832
-
1833
- const uriToRef = (uri: AtUriString): ItemRef => {
1834
- return { uri }
1835
- }