@atproto/bsky 0.3.13 → 0.5.0
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.
- package/CHANGELOG.md +61 -0
- package/dist/api/app/bsky/actor/getProfile.d.ts.map +1 -1
- package/dist/api/app/bsky/actor/getProfile.js +1 -0
- package/dist/api/app/bsky/actor/getProfile.js.map +1 -1
- package/dist/api/app/bsky/actor/getProfiles.d.ts.map +1 -1
- package/dist/api/app/bsky/actor/getProfiles.js +1 -0
- package/dist/api/app/bsky/actor/getProfiles.js.map +1 -1
- package/dist/api/app/bsky/actor/getSuggestions.d.ts.map +1 -1
- package/dist/api/app/bsky/actor/getSuggestions.js +1 -0
- package/dist/api/app/bsky/actor/getSuggestions.js.map +1 -1
- package/dist/api/app/bsky/actor/searchActors.d.ts.map +1 -1
- package/dist/api/app/bsky/actor/searchActors.js +1 -0
- package/dist/api/app/bsky/actor/searchActors.js.map +1 -1
- package/dist/api/app/bsky/actor/searchActorsTypeahead.d.ts.map +1 -1
- package/dist/api/app/bsky/actor/searchActorsTypeahead.js +1 -0
- package/dist/api/app/bsky/actor/searchActorsTypeahead.js.map +1 -1
- package/dist/api/app/bsky/bookmark/getBookmarks.d.ts.map +1 -1
- package/dist/api/app/bsky/bookmark/getBookmarks.js +1 -0
- package/dist/api/app/bsky/bookmark/getBookmarks.js.map +1 -1
- package/dist/api/app/bsky/contact/getMatches.d.ts.map +1 -1
- package/dist/api/app/bsky/contact/getMatches.js +1 -0
- package/dist/api/app/bsky/contact/getMatches.js.map +1 -1
- package/dist/api/app/bsky/contact/importContacts.d.ts.map +1 -1
- package/dist/api/app/bsky/contact/importContacts.js +1 -0
- package/dist/api/app/bsky/contact/importContacts.js.map +1 -1
- package/dist/api/app/bsky/feed/getActorFeeds.d.ts.map +1 -1
- package/dist/api/app/bsky/feed/getActorFeeds.js +5 -1
- package/dist/api/app/bsky/feed/getActorFeeds.js.map +1 -1
- package/dist/api/app/bsky/feed/getActorLikes.d.ts.map +1 -1
- package/dist/api/app/bsky/feed/getActorLikes.js +1 -0
- package/dist/api/app/bsky/feed/getActorLikes.js.map +1 -1
- package/dist/api/app/bsky/feed/getAuthorFeed.d.ts.map +1 -1
- package/dist/api/app/bsky/feed/getAuthorFeed.js +1 -0
- package/dist/api/app/bsky/feed/getAuthorFeed.js.map +1 -1
- package/dist/api/app/bsky/feed/getFeed.d.ts.map +1 -1
- package/dist/api/app/bsky/feed/getFeed.js +1 -0
- package/dist/api/app/bsky/feed/getFeed.js.map +1 -1
- package/dist/api/app/bsky/feed/getFeedGenerator.d.ts.map +1 -1
- package/dist/api/app/bsky/feed/getFeedGenerator.js +5 -1
- package/dist/api/app/bsky/feed/getFeedGenerator.js.map +1 -1
- package/dist/api/app/bsky/feed/getFeedGenerators.d.ts.map +1 -1
- package/dist/api/app/bsky/feed/getFeedGenerators.js +5 -1
- package/dist/api/app/bsky/feed/getFeedGenerators.js.map +1 -1
- package/dist/api/app/bsky/feed/getLikes.d.ts.map +1 -1
- package/dist/api/app/bsky/feed/getLikes.js +1 -0
- package/dist/api/app/bsky/feed/getLikes.js.map +1 -1
- package/dist/api/app/bsky/feed/getListFeed.d.ts.map +1 -1
- package/dist/api/app/bsky/feed/getListFeed.js +1 -0
- package/dist/api/app/bsky/feed/getListFeed.js.map +1 -1
- package/dist/api/app/bsky/feed/getPostThread.d.ts.map +1 -1
- package/dist/api/app/bsky/feed/getPostThread.js +1 -0
- package/dist/api/app/bsky/feed/getPostThread.js.map +1 -1
- package/dist/api/app/bsky/feed/getPosts.d.ts.map +1 -1
- package/dist/api/app/bsky/feed/getPosts.js +5 -1
- package/dist/api/app/bsky/feed/getPosts.js.map +1 -1
- package/dist/api/app/bsky/feed/getQuotes.d.ts.map +1 -1
- package/dist/api/app/bsky/feed/getQuotes.js +1 -0
- package/dist/api/app/bsky/feed/getQuotes.js.map +1 -1
- package/dist/api/app/bsky/feed/getRepostedBy.d.ts.map +1 -1
- package/dist/api/app/bsky/feed/getRepostedBy.js +1 -0
- package/dist/api/app/bsky/feed/getRepostedBy.js.map +1 -1
- package/dist/api/app/bsky/feed/getSuggestedFeeds.d.ts.map +1 -1
- package/dist/api/app/bsky/feed/getSuggestedFeeds.js +5 -1
- package/dist/api/app/bsky/feed/getSuggestedFeeds.js.map +1 -1
- package/dist/api/app/bsky/feed/getTimeline.d.ts.map +1 -1
- package/dist/api/app/bsky/feed/getTimeline.js +1 -0
- package/dist/api/app/bsky/feed/getTimeline.js.map +1 -1
- package/dist/api/app/bsky/feed/searchPosts.d.ts.map +1 -1
- package/dist/api/app/bsky/feed/searchPosts.js +1 -0
- package/dist/api/app/bsky/feed/searchPosts.js.map +1 -1
- package/dist/api/app/bsky/feed/searchPostsV2.d.ts.map +1 -1
- package/dist/api/app/bsky/feed/searchPostsV2.js +1 -0
- package/dist/api/app/bsky/feed/searchPostsV2.js.map +1 -1
- package/dist/api/app/bsky/graph/getActorStarterPacks.d.ts.map +1 -1
- package/dist/api/app/bsky/graph/getActorStarterPacks.js +1 -0
- package/dist/api/app/bsky/graph/getActorStarterPacks.js.map +1 -1
- package/dist/api/app/bsky/graph/getBlocks.d.ts.map +1 -1
- package/dist/api/app/bsky/graph/getBlocks.js +5 -1
- package/dist/api/app/bsky/graph/getBlocks.js.map +1 -1
- package/dist/api/app/bsky/graph/getFollowers.d.ts.map +1 -1
- package/dist/api/app/bsky/graph/getFollowers.js +1 -0
- package/dist/api/app/bsky/graph/getFollowers.js.map +1 -1
- package/dist/api/app/bsky/graph/getFollows.d.ts.map +1 -1
- package/dist/api/app/bsky/graph/getFollows.js +1 -0
- package/dist/api/app/bsky/graph/getFollows.js.map +1 -1
- package/dist/api/app/bsky/graph/getKnownFollowers.d.ts.map +1 -1
- package/dist/api/app/bsky/graph/getKnownFollowers.js +1 -0
- package/dist/api/app/bsky/graph/getKnownFollowers.js.map +1 -1
- package/dist/api/app/bsky/graph/getList.d.ts.map +1 -1
- package/dist/api/app/bsky/graph/getList.js +1 -0
- package/dist/api/app/bsky/graph/getList.js.map +1 -1
- package/dist/api/app/bsky/graph/getListBlocks.d.ts.map +1 -1
- package/dist/api/app/bsky/graph/getListBlocks.js +5 -1
- package/dist/api/app/bsky/graph/getListBlocks.js.map +1 -1
- package/dist/api/app/bsky/graph/getListMutes.d.ts.map +1 -1
- package/dist/api/app/bsky/graph/getListMutes.js +5 -1
- package/dist/api/app/bsky/graph/getListMutes.js.map +1 -1
- package/dist/api/app/bsky/graph/getLists.d.ts.map +1 -1
- package/dist/api/app/bsky/graph/getLists.js +1 -0
- package/dist/api/app/bsky/graph/getLists.js.map +1 -1
- package/dist/api/app/bsky/graph/getListsWithMembership.d.ts.map +1 -1
- package/dist/api/app/bsky/graph/getListsWithMembership.js +1 -0
- package/dist/api/app/bsky/graph/getListsWithMembership.js.map +1 -1
- package/dist/api/app/bsky/graph/getMutes.d.ts.map +1 -1
- package/dist/api/app/bsky/graph/getMutes.js +5 -1
- package/dist/api/app/bsky/graph/getMutes.js.map +1 -1
- package/dist/api/app/bsky/graph/getStarterPack.d.ts.map +1 -1
- package/dist/api/app/bsky/graph/getStarterPack.js +1 -0
- package/dist/api/app/bsky/graph/getStarterPack.js.map +1 -1
- package/dist/api/app/bsky/graph/getStarterPacks.d.ts.map +1 -1
- package/dist/api/app/bsky/graph/getStarterPacks.js +1 -0
- package/dist/api/app/bsky/graph/getStarterPacks.js.map +1 -1
- package/dist/api/app/bsky/graph/getStarterPacksWithMembership.d.ts.map +1 -1
- package/dist/api/app/bsky/graph/getStarterPacksWithMembership.js +1 -0
- package/dist/api/app/bsky/graph/getStarterPacksWithMembership.js.map +1 -1
- package/dist/api/app/bsky/graph/getSuggestedFollowsByActor.d.ts.map +1 -1
- package/dist/api/app/bsky/graph/getSuggestedFollowsByActor.js +1 -0
- package/dist/api/app/bsky/graph/getSuggestedFollowsByActor.js.map +1 -1
- package/dist/api/app/bsky/graph/searchStarterPacks.d.ts.map +1 -1
- package/dist/api/app/bsky/graph/searchStarterPacks.js +1 -0
- package/dist/api/app/bsky/graph/searchStarterPacks.js.map +1 -1
- package/dist/api/app/bsky/graph/searchStarterPacksV2.d.ts.map +1 -1
- package/dist/api/app/bsky/graph/searchStarterPacksV2.js +1 -0
- package/dist/api/app/bsky/graph/searchStarterPacksV2.js.map +1 -1
- package/dist/api/app/bsky/labeler/getServices.d.ts.map +1 -1
- package/dist/api/app/bsky/labeler/getServices.js +1 -0
- package/dist/api/app/bsky/labeler/getServices.js.map +1 -1
- package/dist/api/app/bsky/notification/getGroupedNotifications.d.ts.map +1 -1
- package/dist/api/app/bsky/notification/getGroupedNotifications.js +41 -20
- package/dist/api/app/bsky/notification/getGroupedNotifications.js.map +1 -1
- package/dist/api/app/bsky/notification/grouping/algoGravity.d.ts +4 -3
- package/dist/api/app/bsky/notification/grouping/algoGravity.d.ts.map +1 -1
- package/dist/api/app/bsky/notification/grouping/algoGravity.js +53 -42
- package/dist/api/app/bsky/notification/grouping/algoGravity.js.map +1 -1
- package/dist/api/app/bsky/notification/grouping/algoLookback.d.ts +4 -3
- package/dist/api/app/bsky/notification/grouping/algoLookback.d.ts.map +1 -1
- package/dist/api/app/bsky/notification/grouping/algoLookback.js +56 -42
- package/dist/api/app/bsky/notification/grouping/algoLookback.js.map +1 -1
- package/dist/api/app/bsky/notification/grouping/grouping.d.ts +12 -2
- package/dist/api/app/bsky/notification/grouping/grouping.d.ts.map +1 -1
- package/dist/api/app/bsky/notification/grouping/grouping.js +20 -6
- package/dist/api/app/bsky/notification/grouping/grouping.js.map +1 -1
- package/dist/api/app/bsky/notification/grouping/spotlight.d.ts +2 -2
- package/dist/api/app/bsky/notification/grouping/spotlight.d.ts.map +1 -1
- package/dist/api/app/bsky/notification/grouping/spotlight.js +35 -22
- package/dist/api/app/bsky/notification/grouping/spotlight.js.map +1 -1
- package/dist/api/app/bsky/notification/listActivitySubscriptions.d.ts.map +1 -1
- package/dist/api/app/bsky/notification/listActivitySubscriptions.js +1 -0
- package/dist/api/app/bsky/notification/listActivitySubscriptions.js.map +1 -1
- package/dist/api/app/bsky/notification/listNotifications.d.ts.map +1 -1
- package/dist/api/app/bsky/notification/listNotifications.js +5 -1
- package/dist/api/app/bsky/notification/listNotifications.js.map +1 -1
- package/dist/api/app/bsky/unspecced/atmosphere-grouping.d.ts +16 -0
- package/dist/api/app/bsky/unspecced/atmosphere-grouping.d.ts.map +1 -0
- package/dist/api/app/bsky/unspecced/atmosphere-grouping.js +124 -0
- package/dist/api/app/bsky/unspecced/atmosphere-grouping.js.map +1 -0
- package/dist/api/app/bsky/unspecced/atmosphere-hydration.d.ts +32 -0
- package/dist/api/app/bsky/unspecced/atmosphere-hydration.d.ts.map +1 -0
- package/dist/api/app/bsky/unspecced/atmosphere-hydration.js +201 -0
- package/dist/api/app/bsky/unspecced/atmosphere-hydration.js.map +1 -0
- package/dist/api/app/bsky/unspecced/atmosphere-pagination.d.ts +6 -0
- package/dist/api/app/bsky/unspecced/atmosphere-pagination.d.ts.map +1 -0
- package/dist/api/app/bsky/unspecced/atmosphere-pagination.js +164 -0
- package/dist/api/app/bsky/unspecced/atmosphere-pagination.js.map +1 -0
- package/dist/api/app/bsky/unspecced/getAtmosphereExploreTab.d.ts.map +1 -1
- package/dist/api/app/bsky/unspecced/getAtmosphereExploreTab.js +120 -55
- package/dist/api/app/bsky/unspecced/getAtmosphereExploreTab.js.map +1 -1
- package/dist/api/app/bsky/unspecced/getAtmosphereTimeline.d.ts +4 -0
- package/dist/api/app/bsky/unspecced/getAtmosphereTimeline.d.ts.map +1 -0
- package/dist/api/app/bsky/unspecced/getAtmosphereTimeline.js +30 -0
- package/dist/api/app/bsky/unspecced/getAtmosphereTimeline.js.map +1 -0
- package/dist/api/app/bsky/unspecced/getOnboardingSuggestedStarterPacks.d.ts.map +1 -1
- package/dist/api/app/bsky/unspecced/getOnboardingSuggestedStarterPacks.js +4 -7
- package/dist/api/app/bsky/unspecced/getOnboardingSuggestedStarterPacks.js.map +1 -1
- package/dist/api/app/bsky/unspecced/getPopularFeedGenerators.d.ts.map +1 -1
- package/dist/api/app/bsky/unspecced/getPopularFeedGenerators.js +1 -0
- package/dist/api/app/bsky/unspecced/getPopularFeedGenerators.js.map +1 -1
- package/dist/api/app/bsky/unspecced/getPostThreadOtherV2.d.ts.map +1 -1
- package/dist/api/app/bsky/unspecced/getPostThreadOtherV2.js +1 -0
- package/dist/api/app/bsky/unspecced/getPostThreadOtherV2.js.map +1 -1
- package/dist/api/app/bsky/unspecced/getPostThreadV2.d.ts.map +1 -1
- package/dist/api/app/bsky/unspecced/getPostThreadV2.js +1 -0
- package/dist/api/app/bsky/unspecced/getPostThreadV2.js.map +1 -1
- package/dist/api/app/bsky/unspecced/getSuggestedFeeds.d.ts.map +1 -1
- package/dist/api/app/bsky/unspecced/getSuggestedFeeds.js +4 -7
- package/dist/api/app/bsky/unspecced/getSuggestedFeeds.js.map +1 -1
- package/dist/api/app/bsky/unspecced/getSuggestedOnboardingUsers.d.ts.map +1 -1
- package/dist/api/app/bsky/unspecced/getSuggestedOnboardingUsers.js +1 -0
- package/dist/api/app/bsky/unspecced/getSuggestedOnboardingUsers.js.map +1 -1
- package/dist/api/app/bsky/unspecced/getSuggestedStarterPacks.d.ts.map +1 -1
- package/dist/api/app/bsky/unspecced/getSuggestedStarterPacks.js +4 -7
- package/dist/api/app/bsky/unspecced/getSuggestedStarterPacks.js.map +1 -1
- package/dist/api/app/bsky/unspecced/getSuggestedUsers.d.ts.map +1 -1
- package/dist/api/app/bsky/unspecced/getSuggestedUsers.js +2 -19
- package/dist/api/app/bsky/unspecced/getSuggestedUsers.js.map +1 -1
- package/dist/api/app/bsky/unspecced/getSuggestedUsersForDiscover.d.ts.map +1 -1
- package/dist/api/app/bsky/unspecced/getSuggestedUsersForDiscover.js +1 -0
- package/dist/api/app/bsky/unspecced/getSuggestedUsersForDiscover.js.map +1 -1
- package/dist/api/app/bsky/unspecced/getSuggestedUsersForExplore.d.ts.map +1 -1
- package/dist/api/app/bsky/unspecced/getSuggestedUsersForExplore.js +1 -0
- package/dist/api/app/bsky/unspecced/getSuggestedUsersForExplore.js.map +1 -1
- package/dist/api/app/bsky/unspecced/getSuggestedUsersForSeeMore.d.ts.map +1 -1
- package/dist/api/app/bsky/unspecced/getSuggestedUsersForSeeMore.js +1 -0
- package/dist/api/app/bsky/unspecced/getSuggestedUsersForSeeMore.js.map +1 -1
- package/dist/api/app/bsky/unspecced/getTrendingTopics.d.ts.map +1 -1
- package/dist/api/app/bsky/unspecced/getTrendingTopics.js +6 -2
- package/dist/api/app/bsky/unspecced/getTrendingTopics.js.map +1 -1
- package/dist/api/app/bsky/unspecced/getTrends.d.ts.map +1 -1
- package/dist/api/app/bsky/unspecced/getTrends.js +6 -2
- package/dist/api/app/bsky/unspecced/getTrends.js.map +1 -1
- package/dist/api/blob-resolver.d.ts.map +1 -1
- package/dist/api/blob-resolver.js +9 -8
- package/dist/api/blob-resolver.js.map +1 -1
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +2 -0
- package/dist/api/index.js.map +1 -1
- package/dist/api/internal/bsky/actor/getProfiles.d.ts.map +1 -1
- package/dist/api/internal/bsky/actor/getProfiles.js +1 -0
- package/dist/api/internal/bsky/actor/getProfiles.js.map +1 -1
- package/dist/config.d.ts +2 -6
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +7 -16
- package/dist/config.js.map +1 -1
- package/dist/context.d.ts +0 -2
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +0 -3
- package/dist/context.js.map +1 -1
- package/dist/feature-gates/gates.d.ts +0 -4
- package/dist/feature-gates/gates.d.ts.map +1 -1
- package/dist/feature-gates/gates.js +0 -4
- package/dist/feature-gates/gates.js.map +1 -1
- package/dist/hydration/external.d.ts +58 -45
- package/dist/hydration/external.d.ts.map +1 -1
- package/dist/hydration/external.js +134 -114
- package/dist/hydration/external.js.map +1 -1
- package/dist/hydration/hydrator.d.ts +15 -9
- package/dist/hydration/hydrator.d.ts.map +1 -1
- package/dist/hydration/hydrator.js +170 -210
- package/dist/hydration/hydrator.js.map +1 -1
- package/dist/image/server.d.ts.map +1 -1
- package/dist/image/server.js +12 -1
- package/dist/image/server.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -19
- package/dist/index.js.map +1 -1
- package/dist/lexicons/app/bsky/unspecced/getAtmosphereExploreTab.defs.d.ts +29 -0
- package/dist/lexicons/app/bsky/unspecced/getAtmosphereExploreTab.defs.d.ts.map +1 -1
- package/dist/lexicons/app/bsky/unspecced/getAtmosphereExploreTab.defs.js +2 -1
- package/dist/lexicons/app/bsky/unspecced/getAtmosphereExploreTab.defs.js.map +1 -1
- package/dist/lexicons/app/bsky/unspecced/getAtmosphereTimeline.d.ts +3 -0
- package/dist/lexicons/app/bsky/unspecced/getAtmosphereTimeline.d.ts.map +1 -0
- package/dist/lexicons/app/bsky/unspecced/getAtmosphereTimeline.defs.d.ts +85 -0
- package/dist/lexicons/app/bsky/unspecced/getAtmosphereTimeline.defs.d.ts.map +1 -0
- package/dist/lexicons/app/bsky/unspecced/getAtmosphereTimeline.defs.js +29 -0
- package/dist/lexicons/app/bsky/unspecced/getAtmosphereTimeline.defs.js.map +1 -0
- package/dist/lexicons/app/bsky/unspecced/getAtmosphereTimeline.js +6 -0
- package/dist/lexicons/app/bsky/unspecced/getAtmosphereTimeline.js.map +1 -0
- package/dist/lexicons/app/bsky/unspecced.d.ts +1 -0
- package/dist/lexicons/app/bsky/unspecced.d.ts.map +1 -1
- package/dist/lexicons/app/bsky/unspecced.js +1 -0
- package/dist/lexicons/app/bsky/unspecced.js.map +1 -1
- package/dist/lexicons/tools/ozone/moderation/emitEvent.defs.d.ts +2 -2
- package/dist/lexicons/tools/ozone/moderation/emitEvent.defs.js.map +1 -1
- package/dist/lexicons/tools/ozone/report/closeReports.defs.d.ts +1 -1
- package/dist/lexicons/tools/ozone/report/closeReports.defs.d.ts.map +1 -1
- package/dist/lexicons/tools/ozone/report/closeReports.defs.js +1 -1
- package/dist/lexicons/tools/ozone/report/closeReports.defs.js.map +1 -1
- package/dist/telemetry/events.d.ts +14 -6
- package/dist/telemetry/events.d.ts.map +1 -1
- package/dist/telemetry/events.js +40 -5
- package/dist/telemetry/events.js.map +1 -1
- package/dist/util/external/standard-site.d.ts +2 -3
- package/dist/util/external/standard-site.d.ts.map +1 -1
- package/dist/util/external/standard-site.js +2 -3
- package/dist/util/external/standard-site.js.map +1 -1
- package/dist/util/uris.d.ts +5 -2
- package/dist/util/uris.d.ts.map +1 -1
- package/dist/util/uris.js +14 -1
- package/dist/util/uris.js.map +1 -1
- package/dist/views/external.d.ts +5 -0
- package/dist/views/external.d.ts.map +1 -1
- package/dist/views/external.js +38 -11
- package/dist/views/external.js.map +1 -1
- package/dist/views/index.d.ts +2 -2
- package/dist/views/index.d.ts.map +1 -1
- package/dist/views/index.js +17 -11
- package/dist/views/index.js.map +1 -1
- package/package.json +13 -14
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import type { GroupBuilderFactory, NotificationItem } from './grouping.js';
|
|
2
|
+
export declare const createAlgoGravityGroupBuilder: GroupBuilderFactory;
|
|
3
|
+
/** Split newest-first notifications into gravity-bound bursts, without a size cap. */
|
|
4
|
+
export declare const splitGravityBursts: (items: NotificationItem[], now: number, attractionSpanMultiplier: number) => NotificationItem[][];
|
|
4
5
|
//# sourceMappingURL=algoGravity.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"algoGravity.d.ts","sourceRoot":"","sources":["../../../../../../src/api/app/bsky/notification/grouping/algoGravity.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"algoGravity.d.ts","sourceRoot":"","sources":["../../../../../../src/api/app/bsky/notification/grouping/algoGravity.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,mBAAmB,EAEnB,gBAAgB,EACjB,MAAM,eAAe,CAAA;AA2CtB,eAAO,MAAM,6BAA6B,EAAE,mBAmD3C,CAAA;AAED,sFAAsF;AACtF,eAAO,MAAM,kBAAkB,UACtB,gBAAgB,EAAE,OACpB,MAAM,4BACe,MAAM,KAC/B,gBAAgB,EAAE,EAUpB,CAAA"}
|
|
@@ -12,50 +12,62 @@ const PARAMS = {
|
|
|
12
12
|
attractionCoefficient: 0.9,
|
|
13
13
|
notificationWeight: 0.005,
|
|
14
14
|
};
|
|
15
|
-
export const
|
|
15
|
+
export const createAlgoGravityGroupBuilder = (limit, now, seenAt, feed = NotificationFeed.ALL) => {
|
|
16
16
|
const zones = PARAMS.zones;
|
|
17
17
|
const chains = new Map();
|
|
18
18
|
let groupCount = 0;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
item.raw.reason,
|
|
23
|
-
|
|
24
|
-
canGroup ?
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const active = candidateGroups.at(-1);
|
|
28
|
-
let nextCandidateGroups;
|
|
29
|
-
if (!active || active.closed) {
|
|
30
|
-
nextCandidateGroups = [
|
|
31
|
-
...candidateGroups,
|
|
32
|
-
createCandidateGroup([item], now, zones),
|
|
33
|
-
];
|
|
34
|
-
}
|
|
35
|
-
else {
|
|
36
|
-
const extended = appendItem(active, item, PARAMS.attractionCoefficient);
|
|
37
|
-
const stableCandidateGroups = splitUntilStable(extended, PARAMS.notificationWeight, PARAMS.attractionCoefficient, now, zones).map((candidateGroup) => candidateGroup.items.length === MAX_GROUP_SIZE
|
|
19
|
+
let previousItem;
|
|
20
|
+
return (items) => {
|
|
21
|
+
for (const item of items) {
|
|
22
|
+
const canGroup = canGroupNotification(item.raw.reason, feed);
|
|
23
|
+
// @NOTE Null bytes cannot occur in these values, so the key is collision-safe.
|
|
24
|
+
const key = `${item.raw.reason}\0${item.groupingKey}\0${canGroup ? '' : item.id}`;
|
|
25
|
+
const candidateGroups = chains.get(key) ?? [];
|
|
26
|
+
const nextCandidateGroups = extendCandidateGroups(candidateGroups, item, now, zones).map((candidateGroup) => candidateGroup.items.length === MAX_GROUP_SIZE
|
|
38
27
|
? { ...candidateGroup, closed: true }
|
|
39
28
|
: candidateGroup);
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
29
|
+
const nextGroupCount = groupCount - candidateGroups.length + nextCandidateGroups.length;
|
|
30
|
+
// If that would produce more groups than we can fit in the limit,
|
|
31
|
+
// return the groups without having this item.
|
|
32
|
+
if (nextGroupCount > limit) {
|
|
33
|
+
return {
|
|
34
|
+
groups: toGroups(chains, seenAt),
|
|
35
|
+
cursor: previousItem?.raw.indexedAt,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
// If adding this item didn't produce a group that makes us overflow the limit,
|
|
39
|
+
// we can continue appending.
|
|
40
|
+
chains.set(key, nextCandidateGroups);
|
|
41
|
+
groupCount = nextGroupCount;
|
|
42
|
+
previousItem = item;
|
|
51
43
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
return {
|
|
56
|
-
groups: toGroups(chains, seenAt),
|
|
44
|
+
return {
|
|
45
|
+
groups: toGroups(chains, seenAt),
|
|
46
|
+
};
|
|
57
47
|
};
|
|
58
48
|
};
|
|
49
|
+
/** Split newest-first notifications into gravity-bound bursts, without a size cap. */
|
|
50
|
+
export const splitGravityBursts = (items, now, attractionSpanMultiplier) => {
|
|
51
|
+
const zones = PARAMS.zones.map((zone) => ({
|
|
52
|
+
...zone,
|
|
53
|
+
attractionSpanMs: zone.attractionSpanMs * attractionSpanMultiplier,
|
|
54
|
+
}));
|
|
55
|
+
let groups = [];
|
|
56
|
+
for (const item of items) {
|
|
57
|
+
groups = extendCandidateGroups(groups, item, now, zones);
|
|
58
|
+
}
|
|
59
|
+
return groups.map((group) => group.items);
|
|
60
|
+
};
|
|
61
|
+
const extendCandidateGroups = (groups, item, now, zones) => {
|
|
62
|
+
const active = groups.at(-1);
|
|
63
|
+
if (!active || active.closed) {
|
|
64
|
+
return [...groups, createCandidateGroup([item], now, zones)];
|
|
65
|
+
}
|
|
66
|
+
return [
|
|
67
|
+
...groups.slice(0, -1),
|
|
68
|
+
...splitUntilStable(appendItem(active, item, PARAMS.attractionCoefficient), PARAMS.notificationWeight, PARAMS.attractionCoefficient, now, zones),
|
|
69
|
+
];
|
|
70
|
+
};
|
|
59
71
|
const splitUntilStable = (candidateGroup, notificationWeight, attractionCoefficient, now, zones) => {
|
|
60
72
|
if (candidateGroup.items.length < 2 ||
|
|
61
73
|
candidateGroup.occupiedSeconds *
|
|
@@ -74,27 +86,26 @@ const splitUntilStable = (candidateGroup, notificationWeight, attractionCoeffici
|
|
|
74
86
|
};
|
|
75
87
|
const createCandidateGroup = (items, now, zones, attractionCoefficient = PARAMS.attractionCoefficient) => {
|
|
76
88
|
const newest = items[0];
|
|
77
|
-
const age = Math.max(0, now -
|
|
89
|
+
const age = Math.max(0, now - newest.indexedTime);
|
|
78
90
|
const zone = zones.find(({ ageMs: maxAgeMs }) => maxAgeMs === null || age <= maxAgeMs);
|
|
79
91
|
const candidateGroup = {
|
|
80
92
|
items,
|
|
81
|
-
occupiedSeconds: new Set(items.map(({
|
|
93
|
+
occupiedSeconds: new Set(items.map(({ indexedTime }) => Math.floor(indexedTime / SECOND))).size,
|
|
82
94
|
zone,
|
|
83
95
|
weakestAttraction: Number.POSITIVE_INFINITY,
|
|
84
96
|
weakestBindingIndexes: [],
|
|
85
97
|
closed: false,
|
|
86
98
|
};
|
|
87
99
|
for (let index = 0; index < items.length - 1; index++) {
|
|
88
|
-
const gap =
|
|
89
|
-
Date.parse(items[index + 1].raw.indexedAt);
|
|
100
|
+
const gap = items[index].indexedTime - items[index + 1].indexedTime;
|
|
90
101
|
addBindingAttraction(candidateGroup, index, linearAttraction(gap, attractionCoefficient, candidateGroup.zone.attractionSpanMs));
|
|
91
102
|
}
|
|
92
103
|
return candidateGroup;
|
|
93
104
|
};
|
|
94
105
|
const appendItem = (candidateGroup, item, attractionCoefficient) => {
|
|
95
106
|
const newestBindingIndex = candidateGroup.items.length - 1;
|
|
96
|
-
const previousTime =
|
|
97
|
-
const itemTime =
|
|
107
|
+
const previousTime = candidateGroup.items.at(-1).indexedTime;
|
|
108
|
+
const itemTime = item.indexedTime;
|
|
98
109
|
const gap = previousTime - itemTime;
|
|
99
110
|
const extended = {
|
|
100
111
|
...candidateGroup,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"algoGravity.js","sourceRoot":"","sources":["../../../../../../src/api/app/bsky/notification/grouping/algoGravity.ts"],"names":[],"mappings":"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","sourcesContent":["import { HOUR, MINUTE, SECOND } from '@atproto/common'\nimport { NotificationFeed } from '../../../../../proto/bsky_pb.js'\nimport type {\n GroupingResult,\n NotificationGroup,\n NotificationItem,\n} from './grouping.js'\nimport {\n MAX_GROUP_SIZE,\n canGroupNotification,\n compareNotificationGroupsNewestFirst,\n isNotificationRead,\n} from './shared.js'\n\ntype Zone = {\n ageMs: number | null\n gravity: number\n attractionSpanMs: number\n}\n\ntype Params = {\n zones: Zone[]\n attractionCoefficient: number\n notificationWeight: number\n}\n\nconst PARAMS: Params = {\n zones: [\n // @NOTE: Keep in ascending ageMs, null last.\n { ageMs: 30 * MINUTE, gravity: 3, attractionSpanMs: 30 * MINUTE },\n { ageMs: HOUR, gravity: 2.2, attractionSpanMs: HOUR },\n { ageMs: 8 * HOUR, gravity: 1, attractionSpanMs: 12 * HOUR },\n { ageMs: null, gravity: 0.5, attractionSpanMs: 24 * HOUR },\n ],\n attractionCoefficient: 0.9,\n notificationWeight: 0.005,\n}\n\n// A notification group while Gravity is building it: holds its notifications and\n// the measurements used to decide whether to split it or accept more notifications.\ntype CandidateGroup = {\n items: NotificationItem[]\n occupiedSeconds: number\n zone: Zone\n weakestAttraction: number\n weakestBindingIndexes: number[]\n closed: boolean\n}\n\nexport const buildAlgoGravityGroups = (\n items: NotificationItem[],\n limit: number,\n now: number,\n seenAt?: number,\n feed = NotificationFeed.ALL,\n): GroupingResult => {\n const zones = PARAMS.zones\n const chains = new Map<string, CandidateGroup[]>()\n let groupCount = 0\n\n for (const [itemIndex, item] of items.entries()) {\n const canGroup = canGroupNotification(item.raw.reason, feed)\n const key = JSON.stringify([\n item.raw.reason,\n item.groupingKey,\n canGroup ? undefined : item.id,\n ])\n const candidateGroups = chains.get(key) ?? []\n const active = candidateGroups.at(-1)\n let nextCandidateGroups: CandidateGroup[]\n\n if (!active || active.closed) {\n nextCandidateGroups = [\n ...candidateGroups,\n createCandidateGroup([item], now, zones),\n ]\n } else {\n const extended = appendItem(active, item, PARAMS.attractionCoefficient)\n const stableCandidateGroups = splitUntilStable(\n extended,\n PARAMS.notificationWeight,\n PARAMS.attractionCoefficient,\n now,\n zones,\n ).map((candidateGroup) =>\n candidateGroup.items.length === MAX_GROUP_SIZE\n ? { ...candidateGroup, closed: true }\n : candidateGroup,\n )\n nextCandidateGroups = [\n ...candidateGroups.slice(0, -1),\n ...stableCandidateGroups,\n ]\n }\n\n const nextGroupCount =\n groupCount - candidateGroups.length + nextCandidateGroups.length\n if (nextGroupCount > limit) {\n return {\n groups: toGroups(chains, seenAt),\n cursor: items[itemIndex - 1]?.raw.indexedAt,\n }\n }\n\n chains.set(key, nextCandidateGroups)\n groupCount = nextGroupCount\n }\n\n return {\n groups: toGroups(chains, seenAt),\n }\n}\n\nconst splitUntilStable = (\n candidateGroup: CandidateGroup,\n notificationWeight: number,\n attractionCoefficient: number,\n now: number,\n zones: Zone[],\n): CandidateGroup[] => {\n if (\n candidateGroup.items.length < 2 ||\n candidateGroup.occupiedSeconds *\n notificationWeight *\n candidateGroup.zone.gravity <=\n candidateGroup.weakestAttraction\n ) {\n return [candidateGroup]\n }\n\n // Resolve equal weakest bindings by cutting nearest the segment midpoint.\n const splitIndex = midpointIndex(\n candidateGroup.weakestBindingIndexes,\n candidateGroup.items.length,\n )\n const newer = createCandidateGroup(\n candidateGroup.items.slice(0, splitIndex + 1),\n now,\n zones,\n attractionCoefficient,\n )\n const older = createCandidateGroup(\n candidateGroup.items.slice(splitIndex + 1),\n now,\n zones,\n attractionCoefficient,\n )\n const newerCandidateGroups = splitUntilStable(\n newer,\n notificationWeight,\n attractionCoefficient,\n now,\n zones,\n ).map((part) => ({ ...part, closed: true }))\n const olderCandidateGroups = splitUntilStable(\n older,\n notificationWeight,\n attractionCoefficient,\n now,\n zones,\n )\n return [...newerCandidateGroups, ...olderCandidateGroups]\n}\n\nconst createCandidateGroup = (\n items: NotificationItem[],\n now: number,\n zones: Zone[],\n attractionCoefficient = PARAMS.attractionCoefficient,\n): CandidateGroup => {\n const newest = items[0]!\n const age = Math.max(0, now - Date.parse(newest.raw.indexedAt))\n const zone = zones.find(\n ({ ageMs: maxAgeMs }) => maxAgeMs === null || age <= maxAgeMs,\n )!\n const candidateGroup: CandidateGroup = {\n items,\n occupiedSeconds: new Set(\n items.map(({ raw }) => Math.floor(Date.parse(raw.indexedAt) / SECOND)),\n ).size,\n zone,\n weakestAttraction: Number.POSITIVE_INFINITY,\n weakestBindingIndexes: [],\n closed: false,\n }\n\n for (let index = 0; index < items.length - 1; index++) {\n const gap =\n Date.parse(items[index]!.raw.indexedAt) -\n Date.parse(items[index + 1]!.raw.indexedAt)\n addBindingAttraction(\n candidateGroup,\n index,\n linearAttraction(\n gap,\n attractionCoefficient,\n candidateGroup.zone.attractionSpanMs,\n ),\n )\n }\n\n return candidateGroup\n}\n\nconst appendItem = (\n candidateGroup: CandidateGroup,\n item: NotificationItem,\n attractionCoefficient: number,\n): CandidateGroup => {\n const newestBindingIndex = candidateGroup.items.length - 1\n const previousTime = Date.parse(candidateGroup.items.at(-1)!.raw.indexedAt)\n const itemTime = Date.parse(item.raw.indexedAt)\n const gap = previousTime - itemTime\n const extended: CandidateGroup = {\n ...candidateGroup,\n items: [...candidateGroup.items, item],\n occupiedSeconds:\n candidateGroup.occupiedSeconds +\n Number(\n Math.floor(previousTime / SECOND) !== Math.floor(itemTime / SECOND),\n ),\n weakestBindingIndexes: [...candidateGroup.weakestBindingIndexes],\n }\n addBindingAttraction(\n extended,\n newestBindingIndex,\n linearAttraction(\n gap,\n attractionCoefficient,\n candidateGroup.zone.attractionSpanMs,\n ),\n )\n return extended\n}\n\nconst addBindingAttraction = (\n candidateGroup: CandidateGroup,\n index: number,\n attraction: number,\n) => {\n if (attraction < candidateGroup.weakestAttraction) {\n candidateGroup.weakestAttraction = attraction\n candidateGroup.weakestBindingIndexes = [index]\n } else if (attraction === candidateGroup.weakestAttraction) {\n candidateGroup.weakestBindingIndexes.push(index)\n }\n}\n\nconst midpointIndex = (indexes: number[], itemCount: number): number => {\n const midpoint = (itemCount - 2) / 2\n return indexes.reduce((best, index) =>\n Math.abs(index - midpoint) < Math.abs(best - midpoint) ? index : best,\n )\n}\n\nconst linearAttraction = (\n gapMs: number,\n attractionCoefficient: number,\n attractionSpanMs: number,\n) =>\n attractionCoefficient * Math.max(0, 1 - Math.max(0, gapMs) / attractionSpanMs)\n\nconst toGroups = (\n chains: Map<string, CandidateGroup[]>,\n seenAt: number | undefined,\n): NotificationGroup[] =>\n [...chains.values()]\n .flatMap((candidateGroups) => candidateGroups)\n .map((candidateGroup) => {\n const newest = candidateGroup.items[0]!\n const oldest = candidateGroup.items.at(-1)!\n return {\n id: newest.id,\n kind: newest.raw.reason,\n groupingKey: newest.groupingKey,\n actorDids: candidateGroup.items.map(({ actorDid }) => actorDid),\n itemCount: candidateGroup.items.length,\n indexedAt: newest.raw.indexedAt,\n firstIndexedAt: oldest.raw.indexedAt,\n isRead: isNotificationRead(newest.raw.indexedAt, seenAt),\n items: candidateGroup.items,\n }\n })\n .sort(compareNotificationGroupsNewestFirst)\n"]}
|
|
1
|
+
{"version":3,"file":"algoGravity.js","sourceRoot":"","sources":["../../../../../../src/api/app/bsky/notification/grouping/algoGravity.ts"],"names":[],"mappings":"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","sourcesContent":["import { HOUR, MINUTE, SECOND } from '@atproto/common'\nimport { NotificationFeed } from '../../../../../proto/bsky_pb.js'\nimport type {\n GroupBuilderFactory,\n NotificationGroup,\n NotificationItem,\n} from './grouping.js'\nimport {\n MAX_GROUP_SIZE,\n canGroupNotification,\n compareNotificationGroupsNewestFirst,\n isNotificationRead,\n} from './shared.js'\n\ntype Zone = {\n ageMs: number | null\n gravity: number\n attractionSpanMs: number\n}\n\ntype Params = {\n zones: Zone[]\n attractionCoefficient: number\n notificationWeight: number\n}\n\nconst PARAMS: Params = {\n zones: [\n // @NOTE: Keep in ascending ageMs, null last.\n { ageMs: 30 * MINUTE, gravity: 3, attractionSpanMs: 30 * MINUTE },\n { ageMs: HOUR, gravity: 2.2, attractionSpanMs: HOUR },\n { ageMs: 8 * HOUR, gravity: 1, attractionSpanMs: 12 * HOUR },\n { ageMs: null, gravity: 0.5, attractionSpanMs: 24 * HOUR },\n ],\n attractionCoefficient: 0.9,\n notificationWeight: 0.005,\n}\n\n// A notification group while Gravity is building it: holds its notifications and\n// the measurements used to decide whether to split it or accept more notifications.\ntype CandidateGroup = {\n items: NotificationItem[]\n occupiedSeconds: number\n zone: Zone\n weakestAttraction: number\n weakestBindingIndexes: number[]\n closed: boolean\n}\n\nexport const createAlgoGravityGroupBuilder: GroupBuilderFactory = (\n limit,\n now,\n seenAt,\n feed = NotificationFeed.ALL,\n) => {\n const zones = PARAMS.zones\n const chains = new Map<string, CandidateGroup[]>()\n let groupCount = 0\n let previousItem: NotificationItem | undefined\n\n return (items) => {\n for (const item of items) {\n const canGroup = canGroupNotification(item.raw.reason, feed)\n // @NOTE Null bytes cannot occur in these values, so the key is collision-safe.\n const key = `${item.raw.reason}\\0${item.groupingKey}\\0${canGroup ? '' : item.id}`\n const candidateGroups = chains.get(key) ?? []\n const nextCandidateGroups = extendCandidateGroups(\n candidateGroups,\n item,\n now,\n zones,\n ).map((candidateGroup) =>\n candidateGroup.items.length === MAX_GROUP_SIZE\n ? { ...candidateGroup, closed: true }\n : candidateGroup,\n )\n\n const nextGroupCount =\n groupCount - candidateGroups.length + nextCandidateGroups.length\n\n // If that would produce more groups than we can fit in the limit,\n // return the groups without having this item.\n if (nextGroupCount > limit) {\n return {\n groups: toGroups(chains, seenAt),\n cursor: previousItem?.raw.indexedAt,\n }\n }\n\n // If adding this item didn't produce a group that makes us overflow the limit,\n // we can continue appending.\n chains.set(key, nextCandidateGroups)\n groupCount = nextGroupCount\n previousItem = item\n }\n\n return {\n groups: toGroups(chains, seenAt),\n }\n }\n}\n\n/** Split newest-first notifications into gravity-bound bursts, without a size cap. */\nexport const splitGravityBursts = (\n items: NotificationItem[],\n now: number,\n attractionSpanMultiplier: number,\n): NotificationItem[][] => {\n const zones = PARAMS.zones.map((zone) => ({\n ...zone,\n attractionSpanMs: zone.attractionSpanMs * attractionSpanMultiplier,\n }))\n let groups: CandidateGroup[] = []\n for (const item of items) {\n groups = extendCandidateGroups(groups, item, now, zones)\n }\n return groups.map((group) => group.items)\n}\n\nconst extendCandidateGroups = (\n groups: CandidateGroup[],\n item: NotificationItem,\n now: number,\n zones: Zone[],\n): CandidateGroup[] => {\n const active = groups.at(-1)\n if (!active || active.closed) {\n return [...groups, createCandidateGroup([item], now, zones)]\n }\n return [\n ...groups.slice(0, -1),\n ...splitUntilStable(\n appendItem(active, item, PARAMS.attractionCoefficient),\n PARAMS.notificationWeight,\n PARAMS.attractionCoefficient,\n now,\n zones,\n ),\n ]\n}\n\nconst splitUntilStable = (\n candidateGroup: CandidateGroup,\n notificationWeight: number,\n attractionCoefficient: number,\n now: number,\n zones: Zone[],\n): CandidateGroup[] => {\n if (\n candidateGroup.items.length < 2 ||\n candidateGroup.occupiedSeconds *\n notificationWeight *\n candidateGroup.zone.gravity <=\n candidateGroup.weakestAttraction\n ) {\n return [candidateGroup]\n }\n\n // Resolve equal weakest bindings by cutting nearest the segment midpoint.\n const splitIndex = midpointIndex(\n candidateGroup.weakestBindingIndexes,\n candidateGroup.items.length,\n )\n const newer = createCandidateGroup(\n candidateGroup.items.slice(0, splitIndex + 1),\n now,\n zones,\n attractionCoefficient,\n )\n const older = createCandidateGroup(\n candidateGroup.items.slice(splitIndex + 1),\n now,\n zones,\n attractionCoefficient,\n )\n const newerCandidateGroups = splitUntilStable(\n newer,\n notificationWeight,\n attractionCoefficient,\n now,\n zones,\n ).map((part) => ({ ...part, closed: true }))\n const olderCandidateGroups = splitUntilStable(\n older,\n notificationWeight,\n attractionCoefficient,\n now,\n zones,\n )\n return [...newerCandidateGroups, ...olderCandidateGroups]\n}\n\nconst createCandidateGroup = (\n items: NotificationItem[],\n now: number,\n zones: Zone[],\n attractionCoefficient = PARAMS.attractionCoefficient,\n): CandidateGroup => {\n const newest = items[0]!\n const age = Math.max(0, now - newest.indexedTime)\n const zone = zones.find(\n ({ ageMs: maxAgeMs }) => maxAgeMs === null || age <= maxAgeMs,\n )!\n const candidateGroup: CandidateGroup = {\n items,\n occupiedSeconds: new Set(\n items.map(({ indexedTime }) => Math.floor(indexedTime / SECOND)),\n ).size,\n zone,\n weakestAttraction: Number.POSITIVE_INFINITY,\n weakestBindingIndexes: [],\n closed: false,\n }\n\n for (let index = 0; index < items.length - 1; index++) {\n const gap = items[index]!.indexedTime - items[index + 1]!.indexedTime\n addBindingAttraction(\n candidateGroup,\n index,\n linearAttraction(\n gap,\n attractionCoefficient,\n candidateGroup.zone.attractionSpanMs,\n ),\n )\n }\n\n return candidateGroup\n}\n\nconst appendItem = (\n candidateGroup: CandidateGroup,\n item: NotificationItem,\n attractionCoefficient: number,\n): CandidateGroup => {\n const newestBindingIndex = candidateGroup.items.length - 1\n const previousTime = candidateGroup.items.at(-1)!.indexedTime\n const itemTime = item.indexedTime\n const gap = previousTime - itemTime\n const extended: CandidateGroup = {\n ...candidateGroup,\n items: [...candidateGroup.items, item],\n occupiedSeconds:\n candidateGroup.occupiedSeconds +\n Number(\n Math.floor(previousTime / SECOND) !== Math.floor(itemTime / SECOND),\n ),\n weakestBindingIndexes: [...candidateGroup.weakestBindingIndexes],\n }\n addBindingAttraction(\n extended,\n newestBindingIndex,\n linearAttraction(\n gap,\n attractionCoefficient,\n candidateGroup.zone.attractionSpanMs,\n ),\n )\n return extended\n}\n\nconst addBindingAttraction = (\n candidateGroup: CandidateGroup,\n index: number,\n attraction: number,\n) => {\n if (attraction < candidateGroup.weakestAttraction) {\n candidateGroup.weakestAttraction = attraction\n candidateGroup.weakestBindingIndexes = [index]\n } else if (attraction === candidateGroup.weakestAttraction) {\n candidateGroup.weakestBindingIndexes.push(index)\n }\n}\n\nconst midpointIndex = (indexes: number[], itemCount: number): number => {\n const midpoint = (itemCount - 2) / 2\n return indexes.reduce((best, index) =>\n Math.abs(index - midpoint) < Math.abs(best - midpoint) ? index : best,\n )\n}\n\nconst linearAttraction = (\n gapMs: number,\n attractionCoefficient: number,\n attractionSpanMs: number,\n) =>\n attractionCoefficient * Math.max(0, 1 - Math.max(0, gapMs) / attractionSpanMs)\n\nconst toGroups = (\n chains: Map<string, CandidateGroup[]>,\n seenAt: number | undefined,\n): NotificationGroup[] =>\n [...chains.values()]\n .flatMap((candidateGroups) => candidateGroups)\n .map((candidateGroup) => {\n const newest = candidateGroup.items[0]!\n const oldest = candidateGroup.items.at(-1)!\n return {\n id: newest.id,\n kind: newest.raw.reason,\n groupingKey: newest.groupingKey,\n actorDids: candidateGroup.items.map(({ actorDid }) => actorDid),\n itemCount: candidateGroup.items.length,\n indexedAt: newest.raw.indexedAt,\n firstIndexedAt: oldest.raw.indexedAt,\n isRead: isNotificationRead(newest.raw.indexedAt, seenAt),\n items: candidateGroup.items,\n }\n })\n .sort(compareNotificationGroupsNewestFirst)\n"]}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import type { GroupBuilderFactory, NotificationItem } from './grouping.js';
|
|
2
|
+
export declare const createAlgoLookbackGroupBuilder: GroupBuilderFactory;
|
|
3
|
+
/** Split newest-first notifications into lookback windows, without a size cap. */
|
|
4
|
+
export declare const splitLookbackBursts: (items: NotificationItem[], now: number, timeSpanMultiplier: number) => NotificationItem[][];
|
|
4
5
|
//# sourceMappingURL=algoLookback.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"algoLookback.d.ts","sourceRoot":"","sources":["../../../../../../src/api/app/bsky/notification/grouping/algoLookback.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"algoLookback.d.ts","sourceRoot":"","sources":["../../../../../../src/api/app/bsky/notification/grouping/algoLookback.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,mBAAmB,EAEnB,gBAAgB,EACjB,MAAM,eAAe,CAAA;AA0BtB,eAAO,MAAM,8BAA8B,EAAE,mBAmD5C,CAAA;AAED,kFAAkF;AAClF,eAAO,MAAM,mBAAmB,UACvB,gBAAgB,EAAE,OACpB,MAAM,sBACS,MAAM,KACzB,gBAAgB,EAAE,EAWpB,CAAA"}
|
|
@@ -10,52 +10,66 @@ const PARAMS = {
|
|
|
10
10
|
{ ageMs: null, lookbackMs: 8 * HOUR },
|
|
11
11
|
],
|
|
12
12
|
};
|
|
13
|
-
export const
|
|
13
|
+
export const createAlgoLookbackGroupBuilder = (limit, now, seenAt, feed = NotificationFeed.ALL) => {
|
|
14
14
|
const groups = [];
|
|
15
15
|
const activeGroups = new Map();
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
item.raw.reason,
|
|
20
|
-
|
|
21
|
-
canGroup ?
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
16
|
+
let previousItem;
|
|
17
|
+
return (items) => {
|
|
18
|
+
for (const item of items) {
|
|
19
|
+
const canGroup = canGroupNotification(item.raw.reason, feed);
|
|
20
|
+
// @NOTE Null bytes cannot occur in these values, so the key is collision-safe.
|
|
21
|
+
const key = `${item.raw.reason}\0${item.groupingKey}\0${canGroup ? '' : item.id}`;
|
|
22
|
+
const active = activeGroups.get(key);
|
|
23
|
+
if (active &&
|
|
24
|
+
active.itemCount < MAX_GROUP_SIZE &&
|
|
25
|
+
isWithinLookback(active.items[0], item, now, 1)) {
|
|
26
|
+
active.actorDids.push(item.actorDid);
|
|
27
|
+
active.itemCount++;
|
|
28
|
+
active.firstIndexedAt = item.raw.indexedAt;
|
|
29
|
+
active.items.push(item);
|
|
30
|
+
previousItem = item;
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
if (groups.length >= limit) {
|
|
34
|
+
return { groups, cursor: previousItem?.raw.indexedAt };
|
|
35
|
+
}
|
|
36
|
+
const group = {
|
|
37
|
+
id: item.id,
|
|
38
|
+
kind: item.raw.reason,
|
|
39
|
+
groupingKey: item.groupingKey,
|
|
40
|
+
actorDids: [item.actorDid],
|
|
41
|
+
itemCount: 1,
|
|
42
|
+
indexedAt: item.raw.indexedAt,
|
|
43
|
+
firstIndexedAt: item.raw.indexedAt,
|
|
44
|
+
isRead: isNotificationRead(item.raw.indexedAt, seenAt),
|
|
45
|
+
items: [item],
|
|
46
|
+
};
|
|
47
|
+
groups.push(group);
|
|
48
|
+
activeGroups.set(key, group);
|
|
49
|
+
previousItem = item;
|
|
41
50
|
}
|
|
42
|
-
|
|
43
|
-
|
|
51
|
+
return { groups };
|
|
52
|
+
};
|
|
53
|
+
};
|
|
54
|
+
/** Split newest-first notifications into lookback windows, without a size cap. */
|
|
55
|
+
export const splitLookbackBursts = (items, now, timeSpanMultiplier) => {
|
|
56
|
+
const groups = [];
|
|
57
|
+
for (const item of items) {
|
|
58
|
+
const active = groups.at(-1);
|
|
59
|
+
if (active && isWithinLookback(active[0], item, now, timeSpanMultiplier)) {
|
|
60
|
+
active.push(item);
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
groups.push([item]);
|
|
44
64
|
}
|
|
45
|
-
const group = {
|
|
46
|
-
id: item.id,
|
|
47
|
-
kind: item.raw.reason,
|
|
48
|
-
groupingKey: item.groupingKey,
|
|
49
|
-
actorDids: [item.actorDid],
|
|
50
|
-
itemCount: 1,
|
|
51
|
-
indexedAt: item.raw.indexedAt,
|
|
52
|
-
firstIndexedAt: item.raw.indexedAt,
|
|
53
|
-
isRead: isNotificationRead(item.raw.indexedAt, seenAt),
|
|
54
|
-
items: [item],
|
|
55
|
-
};
|
|
56
|
-
groups.push(group);
|
|
57
|
-
activeGroups.set(key, group);
|
|
58
65
|
}
|
|
59
|
-
return
|
|
66
|
+
return groups;
|
|
67
|
+
};
|
|
68
|
+
const isWithinLookback = (newest, item, now, timeSpanMultiplier) => {
|
|
69
|
+
const newestTime = newest.indexedTime;
|
|
70
|
+
const age = Math.max(0, now - newestTime);
|
|
71
|
+
const zone = PARAMS.zones.find(({ ageMs: maxAgeMs }, index) => maxAgeMs === null || (index === 0 ? age < maxAgeMs : age <= maxAgeMs));
|
|
72
|
+
const gap = newestTime - item.indexedTime;
|
|
73
|
+
return gap <= zone.lookbackMs * timeSpanMultiplier;
|
|
60
74
|
};
|
|
61
75
|
//# sourceMappingURL=algoLookback.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"algoLookback.js","sourceRoot":"","sources":["../../../../../../src/api/app/bsky/notification/grouping/algoLookback.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AAC9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAA;AAMlE,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,aAAa,CAAA;AAWpB,MAAM,MAAM,GAAW;IACrB,KAAK,EAAE;QACL,6CAA6C;QAC7C,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,GAAG,MAAM,EAAE;QACxC,EAAE,KAAK,EAAE,EAAE,GAAG,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE;QACtC,EAAE,KAAK,EAAE,EAAE,GAAG,IAAI,EAAE,UAAU,EAAE,CAAC,GAAG,IAAI,EAAE;QAC1C,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,GAAG,IAAI,EAAE;KACtC;CACF,CAAA;AAED,MAAM,CAAC,MAAM,
|
|
1
|
+
{"version":3,"file":"algoLookback.js","sourceRoot":"","sources":["../../../../../../src/api/app/bsky/notification/grouping/algoLookback.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAA;AAC9C,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAA;AAMlE,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,kBAAkB,GACnB,MAAM,aAAa,CAAA;AAWpB,MAAM,MAAM,GAAW;IACrB,KAAK,EAAE;QACL,6CAA6C;QAC7C,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,GAAG,MAAM,EAAE;QACxC,EAAE,KAAK,EAAE,EAAE,GAAG,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE;QACtC,EAAE,KAAK,EAAE,EAAE,GAAG,IAAI,EAAE,UAAU,EAAE,CAAC,GAAG,IAAI,EAAE;QAC1C,EAAE,KAAK,EAAE,IAAI,EAAE,UAAU,EAAE,CAAC,GAAG,IAAI,EAAE;KACtC;CACF,CAAA;AAED,MAAM,CAAC,MAAM,8BAA8B,GAAwB,CACjE,KAAK,EACL,GAAG,EACH,MAAM,EACN,IAAI,GAAG,gBAAgB,CAAC,GAAG,EAC3B,EAAE;IACF,MAAM,MAAM,GAAwB,EAAE,CAAA;IACtC,MAAM,YAAY,GAAG,IAAI,GAAG,EAA6B,CAAA;IACzD,IAAI,YAA0C,CAAA;IAE9C,OAAO,CAAC,KAAK,EAAE,EAAE;QACf,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,QAAQ,GAAG,oBAAoB,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;YAC5D,+EAA+E;YAC/E,MAAM,GAAG,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,KAAK,IAAI,CAAC,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,CAAA;YACjF,MAAM,MAAM,GAAG,YAAY,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;YACpC,IACE,MAAM;gBACN,MAAM,CAAC,SAAS,GAAG,cAAc;gBACjC,gBAAgB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAE,EAAE,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC,EAChD,CAAC;gBACD,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAA;gBACpC,MAAM,CAAC,SAAS,EAAE,CAAA;gBAClB,MAAM,CAAC,cAAc,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,CAAA;gBAC1C,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;gBACvB,YAAY,GAAG,IAAI,CAAA;gBACnB,SAAQ;YACV,CAAC;YAED,IAAI,MAAM,CAAC,MAAM,IAAI,KAAK,EAAE,CAAC;gBAC3B,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,GAAG,CAAC,SAAS,EAAE,CAAA;YACxD,CAAC;YAED,MAAM,KAAK,GAAsB;gBAC/B,EAAE,EAAE,IAAI,CAAC,EAAE;gBACX,IAAI,EAAE,IAAI,CAAC,GAAG,CAAC,MAAM;gBACrB,WAAW,EAAE,IAAI,CAAC,WAAW;gBAC7B,SAAS,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC;gBAC1B,SAAS,EAAE,CAAC;gBACZ,SAAS,EAAE,IAAI,CAAC,GAAG,CAAC,SAAS;gBAC7B,cAAc,EAAE,IAAI,CAAC,GAAG,CAAC,SAAS;gBAClC,MAAM,EAAE,kBAAkB,CAAC,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,MAAM,CAAC;gBACtD,KAAK,EAAE,CAAC,IAAI,CAAC;aACd,CAAA;YACD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAA;YAClB,YAAY,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;YAC5B,YAAY,GAAG,IAAI,CAAA;QACrB,CAAC;QAED,OAAO,EAAE,MAAM,EAAE,CAAA;IACnB,CAAC,CAAA;AACH,CAAC,CAAA;AAED,kFAAkF;AAClF,MAAM,CAAC,MAAM,mBAAmB,GAAG,CACjC,KAAyB,EACzB,GAAW,EACX,kBAA0B,EACJ,EAAE;IACxB,MAAM,MAAM,GAAyB,EAAE,CAAA;IACvC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAA;QAC5B,IAAI,MAAM,IAAI,gBAAgB,CAAC,MAAM,CAAC,CAAC,CAAE,EAAE,IAAI,EAAE,GAAG,EAAE,kBAAkB,CAAC,EAAE,CAAC;YAC1E,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACnB,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC,CAAA;QACrB,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAA;AACf,CAAC,CAAA;AAED,MAAM,gBAAgB,GAAG,CACvB,MAAwB,EACxB,IAAsB,EACtB,GAAW,EACX,kBAA0B,EACjB,EAAE;IACX,MAAM,UAAU,GAAG,MAAM,CAAC,WAAW,CAAA;IACrC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,GAAG,UAAU,CAAC,CAAA;IACzC,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,CAC5B,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,KAAK,EAAE,EAAE,CAC7B,QAAQ,KAAK,IAAI,IAAI,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,QAAQ,CAAC,CAAC,CAAC,GAAG,IAAI,QAAQ,CAAC,CACvE,CAAA;IACF,MAAM,GAAG,GAAG,UAAU,GAAG,IAAI,CAAC,WAAW,CAAA;IACzC,OAAO,GAAG,IAAI,IAAI,CAAC,UAAU,GAAG,kBAAkB,CAAA;AACpD,CAAC,CAAA","sourcesContent":["import { HOUR, MINUTE } from '@atproto/common'\nimport { NotificationFeed } from '../../../../../proto/bsky_pb.js'\nimport type {\n GroupBuilderFactory,\n NotificationGroup,\n NotificationItem,\n} from './grouping.js'\nimport {\n MAX_GROUP_SIZE,\n canGroupNotification,\n isNotificationRead,\n} from './shared.js'\n\ntype Zone = {\n ageMs: number | null\n lookbackMs: number\n}\n\ntype Params = {\n zones: Zone[]\n}\n\nconst PARAMS: Params = {\n zones: [\n // @NOTE: Keep in ascending ageMs, null last.\n { ageMs: HOUR, lookbackMs: 30 * MINUTE },\n { ageMs: 12 * HOUR, lookbackMs: HOUR },\n { ageMs: 24 * HOUR, lookbackMs: 4 * HOUR },\n { ageMs: null, lookbackMs: 8 * HOUR },\n ],\n}\n\nexport const createAlgoLookbackGroupBuilder: GroupBuilderFactory = (\n limit,\n now,\n seenAt,\n feed = NotificationFeed.ALL,\n) => {\n const groups: NotificationGroup[] = []\n const activeGroups = new Map<string, NotificationGroup>()\n let previousItem: NotificationItem | undefined\n\n return (items) => {\n for (const item of items) {\n const canGroup = canGroupNotification(item.raw.reason, feed)\n // @NOTE Null bytes cannot occur in these values, so the key is collision-safe.\n const key = `${item.raw.reason}\\0${item.groupingKey}\\0${canGroup ? '' : item.id}`\n const active = activeGroups.get(key)\n if (\n active &&\n active.itemCount < MAX_GROUP_SIZE &&\n isWithinLookback(active.items[0]!, item, now, 1)\n ) {\n active.actorDids.push(item.actorDid)\n active.itemCount++\n active.firstIndexedAt = item.raw.indexedAt\n active.items.push(item)\n previousItem = item\n continue\n }\n\n if (groups.length >= limit) {\n return { groups, cursor: previousItem?.raw.indexedAt }\n }\n\n const group: NotificationGroup = {\n id: item.id,\n kind: item.raw.reason,\n groupingKey: item.groupingKey,\n actorDids: [item.actorDid],\n itemCount: 1,\n indexedAt: item.raw.indexedAt,\n firstIndexedAt: item.raw.indexedAt,\n isRead: isNotificationRead(item.raw.indexedAt, seenAt),\n items: [item],\n }\n groups.push(group)\n activeGroups.set(key, group)\n previousItem = item\n }\n\n return { groups }\n }\n}\n\n/** Split newest-first notifications into lookback windows, without a size cap. */\nexport const splitLookbackBursts = (\n items: NotificationItem[],\n now: number,\n timeSpanMultiplier: number,\n): NotificationItem[][] => {\n const groups: NotificationItem[][] = []\n for (const item of items) {\n const active = groups.at(-1)\n if (active && isWithinLookback(active[0]!, item, now, timeSpanMultiplier)) {\n active.push(item)\n } else {\n groups.push([item])\n }\n }\n return groups\n}\n\nconst isWithinLookback = (\n newest: NotificationItem,\n item: NotificationItem,\n now: number,\n timeSpanMultiplier: number,\n): boolean => {\n const newestTime = newest.indexedTime\n const age = Math.max(0, now - newestTime)\n const zone = PARAMS.zones.find(\n ({ ageMs: maxAgeMs }, index) =>\n maxAgeMs === null || (index === 0 ? age < maxAgeMs : age <= maxAgeMs),\n )!\n const gap = newestTime - item.indexedTime\n return gap <= zone.lookbackMs * timeSpanMultiplier\n}\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AtUriString, DatetimeString, DidString } from '@atproto/lex';
|
|
2
2
|
import type { NotificationsV2Algorithm } from '../../../../../config.js';
|
|
3
|
-
import {
|
|
3
|
+
import type { Notification, NotificationFeed } from '../../../../../proto/bsky_pb.js';
|
|
4
4
|
import { type AppviewNotificationReason, type DataplaneNotificationReason, NOTIFICATION_REASON } from '../constants.js';
|
|
5
5
|
type DataplaneNotificationReasonWithSubject = typeof NOTIFICATION_REASON.LIKE | typeof NOTIFICATION_REASON.REPOST | typeof NOTIFICATION_REASON.LIKE_VIA_REPOST | typeof NOTIFICATION_REASON.REPOST_VIA_REPOST | typeof NOTIFICATION_REASON.REPLY | typeof NOTIFICATION_REASON.QUOTE | typeof NOTIFICATION_REASON.STARTERPACK_JOINED;
|
|
6
6
|
export type RawNotification = {
|
|
@@ -22,7 +22,9 @@ export type NotificationItem = {
|
|
|
22
22
|
id: string;
|
|
23
23
|
groupingKey: string;
|
|
24
24
|
actorDid: DidString;
|
|
25
|
+
indexedTime: number;
|
|
25
26
|
};
|
|
27
|
+
export declare const buildNotificationItem: (raw: RawNotification, id: string, groupingKey: string, actorDid: DidString) => NotificationItem;
|
|
26
28
|
export type NotificationGroup = {
|
|
27
29
|
id: string;
|
|
28
30
|
kind: AppviewNotificationReason;
|
|
@@ -38,7 +40,15 @@ export type GroupingResult = {
|
|
|
38
40
|
groups: NotificationGroup[];
|
|
39
41
|
cursor?: string;
|
|
40
42
|
};
|
|
43
|
+
export type GroupBuilder = (items: NotificationItem[]) => GroupingResult;
|
|
44
|
+
export type GroupBuilderFactory = (limit: number, now: number, seenAt?: number, feed?: NotificationFeed) => GroupBuilder;
|
|
45
|
+
export type BurstSplitter = (items: NotificationItem[], now: number, timeSpanMultiplier: number) => NotificationItem[][];
|
|
46
|
+
type GroupingAlgorithm = {
|
|
47
|
+
createGroupBuilder: GroupBuilderFactory;
|
|
48
|
+
splitBursts: BurstSplitter;
|
|
49
|
+
};
|
|
41
50
|
export declare function parseRawNotification(notification: Notification): RawNotification | undefined;
|
|
42
|
-
|
|
51
|
+
/** Select matching implementations for ordinary grouping and spotlight bursts. */
|
|
52
|
+
export declare const getGroupingAlgorithm: (algorithm: NotificationsV2Algorithm) => GroupingAlgorithm;
|
|
43
53
|
export {};
|
|
44
54
|
//# sourceMappingURL=grouping.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grouping.d.ts","sourceRoot":"","sources":["../../../../../../src/api/app/bsky/notification/grouping/grouping.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAC1E,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAA;AACxE,OAAO,
|
|
1
|
+
{"version":3,"file":"grouping.d.ts","sourceRoot":"","sources":["../../../../../../src/api/app/bsky/notification/grouping/grouping.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,14 @@
|
|
|
1
|
-
import { NotificationFeed, } from '../../../../../proto/bsky_pb.js';
|
|
2
1
|
import { NOTIFICATION_REASON, } from '../constants.js';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
2
|
+
import { createAlgoGravityGroupBuilder, splitGravityBursts, } from './algoGravity.js';
|
|
3
|
+
import { createAlgoLookbackGroupBuilder, splitLookbackBursts, } from './algoLookback.js';
|
|
4
|
+
// Builds a NotificationItem, caching its parsed timestamp so grouping never re-parses it.
|
|
5
|
+
export const buildNotificationItem = (raw, id, groupingKey, actorDid) => ({
|
|
6
|
+
raw,
|
|
7
|
+
id,
|
|
8
|
+
groupingKey,
|
|
9
|
+
actorDid,
|
|
10
|
+
indexedTime: Date.parse(raw.indexedAt),
|
|
11
|
+
});
|
|
5
12
|
export function parseRawNotification(notification) {
|
|
6
13
|
const { recipientDid, uri, reason, reasonSubject, timestamp } = notification;
|
|
7
14
|
if (!recipientDid || !uri || !timestamp)
|
|
@@ -51,13 +58,20 @@ export function parseRawNotification(notification) {
|
|
|
51
58
|
return;
|
|
52
59
|
}
|
|
53
60
|
}
|
|
54
|
-
|
|
61
|
+
/** Select matching implementations for ordinary grouping and spotlight bursts. */
|
|
62
|
+
export const getGroupingAlgorithm = (algorithm) => {
|
|
55
63
|
switch (algorithm) {
|
|
56
64
|
case 'algoLookback':
|
|
57
|
-
return
|
|
65
|
+
return {
|
|
66
|
+
createGroupBuilder: createAlgoLookbackGroupBuilder,
|
|
67
|
+
splitBursts: splitLookbackBursts,
|
|
68
|
+
};
|
|
58
69
|
case 'algoGravity':
|
|
59
70
|
default:
|
|
60
|
-
return
|
|
71
|
+
return {
|
|
72
|
+
createGroupBuilder: createAlgoGravityGroupBuilder,
|
|
73
|
+
splitBursts: splitGravityBursts,
|
|
74
|
+
};
|
|
61
75
|
}
|
|
62
76
|
};
|
|
63
77
|
//# sourceMappingURL=grouping.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"grouping.js","sourceRoot":"","sources":["../../../../../../src/api/app/bsky/notification/grouping/grouping.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"grouping.js","sourceRoot":"","sources":["../../../../../../src/api/app/bsky/notification/grouping/grouping.ts"],"names":[],"mappings":"AAMA,OAAO,EAGL,mBAAmB,GACpB,MAAM,iBAAiB,CAAA;AACxB,OAAO,EACL,6BAA6B,EAC7B,kBAAkB,GACnB,MAAM,kBAAkB,CAAA;AACzB,OAAO,EACL,8BAA8B,EAC9B,mBAAmB,GACpB,MAAM,mBAAmB,CAAA;AAuD1B,0FAA0F;AAC1F,MAAM,CAAC,MAAM,qBAAqB,GAAG,CACnC,GAAoB,EACpB,EAAU,EACV,WAAmB,EACnB,QAAmB,EACD,EAAE,CAAC,CAAC;IACtB,GAAG;IACH,EAAE;IACF,WAAW;IACX,QAAQ;IACR,WAAW,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC;CACvC,CAAC,CAAA;AA0CF,MAAM,UAAU,oBAAoB,CAClC,YAA0B;IAE1B,MAAM,EAAE,YAAY,EAAE,GAAG,EAAE,MAAM,EAAE,aAAa,EAAE,SAAS,EAAE,GAAG,YAAY,CAAA;IAC5E,IAAI,CAAC,YAAY,IAAI,CAAC,GAAG,IAAI,CAAC,SAAS;QAAE,OAAM;IAC/C,MAAM,SAAS,GAAG,SAAS,CAAC,MAAM,EAA8B,CAAA;IAEhE,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,mBAAmB,CAAC,IAAI,CAAC;QAC9B,KAAK,mBAAmB,CAAC,MAAM,CAAC;QAChC,KAAK,mBAAmB,CAAC,eAAe,CAAC;QACzC,KAAK,mBAAmB,CAAC,iBAAiB,CAAC;QAC3C,KAAK,mBAAmB,CAAC,KAAK,CAAC;QAC/B,KAAK,mBAAmB,CAAC,KAAK,CAAC;QAC/B,KAAK,mBAAmB,CAAC,kBAAkB;YACzC,IAAI,CAAC,aAAa;gBAAE,OAAM;YAC1B,OAAO;gBACL,YAAY,EAAE,YAAyB;gBACvC,GAAG,EAAE,GAAkB;gBACvB,MAAM;gBACN,aAAa,EAAE,aAA4B;gBAC3C,SAAS;aACV,CAAA;QACH,KAAK,mBAAmB,CAAC,MAAM,CAAC;QAChC,KAAK,mBAAmB,CAAC,WAAW;YAClC,OAAO;gBACL,YAAY,EAAE,YAAyB;gBACvC,GAAG,EAAE,GAAkB;gBACvB,MAAM;gBACN,aAAa,EAAE,aAAa;oBAC1B,CAAC,CAAE,aAA6B;oBAChC,CAAC,CAAC,SAAS;gBACb,SAAS;aACV,CAAA;QACH,KAAK,mBAAmB,CAAC,aAAa,CAAC;QACvC,KAAK,mBAAmB,CAAC,OAAO,CAAC;QACjC,KAAK,mBAAmB,CAAC,eAAe,CAAC;QACzC,KAAK,mBAAmB,CAAC,UAAU,CAAC;QACpC,KAAK,mBAAmB,CAAC,QAAQ;YAC/B,OAAO;gBACL,YAAY,EAAE,YAAyB;gBACvC,GAAG,EAAE,GAAkB;gBACvB,MAAM;gBACN,aAAa,EAAE,SAAS;gBACxB,SAAS;aACV,CAAA;QACH;YACE,OAAM;IACV,CAAC;AACH,CAAC;AAED,kFAAkF;AAClF,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAClC,SAAmC,EAChB,EAAE;IACrB,QAAQ,SAAS,EAAE,CAAC;QAClB,KAAK,cAAc;YACjB,OAAO;gBACL,kBAAkB,EAAE,8BAA8B;gBAClD,WAAW,EAAE,mBAAmB;aACjC,CAAA;QACH,KAAK,aAAa,CAAC;QACnB;YACE,OAAO;gBACL,kBAAkB,EAAE,6BAA6B;gBACjD,WAAW,EAAE,kBAAkB;aAChC,CAAA;IACL,CAAC;AACH,CAAC,CAAA","sourcesContent":["import type { AtUriString, DatetimeString, DidString } from '@atproto/lex'\nimport type { NotificationsV2Algorithm } from '../../../../../config.js'\nimport type {\n Notification,\n NotificationFeed,\n} from '../../../../../proto/bsky_pb.js'\nimport {\n type AppviewNotificationReason,\n type DataplaneNotificationReason,\n NOTIFICATION_REASON,\n} from '../constants.js'\nimport {\n createAlgoGravityGroupBuilder,\n splitGravityBursts,\n} from './algoGravity.js'\nimport {\n createAlgoLookbackGroupBuilder,\n splitLookbackBursts,\n} from './algoLookback.js'\n\n// Notification reasons that require reasonSubject to be non-empty.\n// It should be the AT-URI of a record belonging to the notification recipient\ntype DataplaneNotificationReasonWithSubject =\n | typeof NOTIFICATION_REASON.LIKE\n | typeof NOTIFICATION_REASON.REPOST\n | typeof NOTIFICATION_REASON.LIKE_VIA_REPOST\n | typeof NOTIFICATION_REASON.REPOST_VIA_REPOST\n | typeof NOTIFICATION_REASON.REPLY\n | typeof NOTIFICATION_REASON.QUOTE\n | typeof NOTIFICATION_REASON.STARTERPACK_JOINED\n\n// A dataplane notification that has passed our checks.\n// We just don't use the underlying proto type because\n// it has optional fields that we validate and ensure are present with this type.\nexport type RawNotification = {\n recipientDid: DidString\n uri: AtUriString\n indexedAt: DatetimeString\n} & (\n | {\n // Cases that always have a reason subject.\n reason: DataplaneNotificationReasonWithSubject\n reasonSubject: AtUriString\n }\n | {\n // Cases that have optional reason subject.\n reason:\n | typeof NOTIFICATION_REASON.FOLLOW\n | typeof NOTIFICATION_REASON.FOLLOW_BACK\n reasonSubject: AtUriString | undefined\n }\n | {\n // Cases that never have a reason subject.\n reason: Exclude<\n DataplaneNotificationReason,\n | DataplaneNotificationReasonWithSubject\n | typeof NOTIFICATION_REASON.FOLLOW\n | typeof NOTIFICATION_REASON.FOLLOW_BACK\n >\n reasonSubject: undefined\n }\n)\n\n// NotificationItem contains the raw notification plus computed data from it,\n// that will be used to calculate its grouping.\nexport type NotificationItem = {\n raw: RawNotification\n id: string\n groupingKey: string\n actorDid: DidString\n indexedTime: number\n}\n\n// Builds a NotificationItem, caching its parsed timestamp so grouping never re-parses it.\nexport const buildNotificationItem = (\n raw: RawNotification,\n id: string,\n groupingKey: string,\n actorDid: DidString,\n): NotificationItem => ({\n raw,\n id,\n groupingKey,\n actorDid,\n indexedTime: Date.parse(raw.indexedAt),\n})\n\n// NotificationGroup is the grouping algorithm’s result: the notification items it grouped together,\n// plus their count and timestamps, before Views builds the lexicon response.\nexport type NotificationGroup = {\n id: string\n kind: AppviewNotificationReason\n groupingKey: string\n actorDids: DidString[]\n itemCount: number\n indexedAt: DatetimeString\n firstIndexedAt: DatetimeString\n isRead: boolean\n items: NotificationItem[]\n}\n\nexport type GroupingResult = {\n groups: NotificationGroup[]\n cursor?: string\n}\n\n// @NOTE Feed batches newest-first and stop when a cursor is returned.\nexport type GroupBuilder = (items: NotificationItem[]) => GroupingResult\n\nexport type GroupBuilderFactory = (\n limit: number,\n now: number,\n seenAt?: number,\n feed?: NotificationFeed,\n) => GroupBuilder\n\nexport type BurstSplitter = (\n items: NotificationItem[],\n now: number,\n timeSpanMultiplier: number,\n) => NotificationItem[][]\n\ntype GroupingAlgorithm = {\n createGroupBuilder: GroupBuilderFactory\n splitBursts: BurstSplitter\n}\n\nexport function parseRawNotification(\n notification: Notification,\n): RawNotification | undefined {\n const { recipientDid, uri, reason, reasonSubject, timestamp } = notification\n if (!recipientDid || !uri || !timestamp) return\n const indexedAt = timestamp.toJson() as string as DatetimeString\n\n switch (reason) {\n case NOTIFICATION_REASON.LIKE:\n case NOTIFICATION_REASON.REPOST:\n case NOTIFICATION_REASON.LIKE_VIA_REPOST:\n case NOTIFICATION_REASON.REPOST_VIA_REPOST:\n case NOTIFICATION_REASON.REPLY:\n case NOTIFICATION_REASON.QUOTE:\n case NOTIFICATION_REASON.STARTERPACK_JOINED:\n if (!reasonSubject) return\n return {\n recipientDid: recipientDid as DidString,\n uri: uri as AtUriString,\n reason,\n reasonSubject: reasonSubject as AtUriString,\n indexedAt,\n }\n case NOTIFICATION_REASON.FOLLOW:\n case NOTIFICATION_REASON.FOLLOW_BACK:\n return {\n recipientDid: recipientDid as DidString,\n uri: uri as AtUriString,\n reason,\n reasonSubject: reasonSubject\n ? (reasonSubject as AtUriString)\n : undefined,\n indexedAt,\n }\n case NOTIFICATION_REASON.CONTACT_MATCH:\n case NOTIFICATION_REASON.MENTION:\n case NOTIFICATION_REASON.SUBSCRIBED_POST:\n case NOTIFICATION_REASON.UNVERIFIED:\n case NOTIFICATION_REASON.VERIFIED:\n return {\n recipientDid: recipientDid as DidString,\n uri: uri as AtUriString,\n reason,\n reasonSubject: undefined,\n indexedAt,\n }\n default:\n return\n }\n}\n\n/** Select matching implementations for ordinary grouping and spotlight bursts. */\nexport const getGroupingAlgorithm = (\n algorithm: NotificationsV2Algorithm,\n): GroupingAlgorithm => {\n switch (algorithm) {\n case 'algoLookback':\n return {\n createGroupBuilder: createAlgoLookbackGroupBuilder,\n splitBursts: splitLookbackBursts,\n }\n case 'algoGravity':\n default:\n return {\n createGroupBuilder: createAlgoGravityGroupBuilder,\n splitBursts: splitGravityBursts,\n }\n }\n}\n"]}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import type { GroupingResult, NotificationGroup, NotificationItem } from './grouping.js';
|
|
2
|
-
export declare
|
|
1
|
+
import type { BurstSplitter, GroupingResult, NotificationGroup, NotificationItem } from './grouping.js';
|
|
2
|
+
export declare const buildSpotlight: (items: NotificationItem[], groups: NotificationGroup[], cursor: string | undefined, limit: number, now: number, seenAt: number | undefined, splitBursts: BurstSplitter) => GroupingResult;
|
|
3
3
|
//# sourceMappingURL=spotlight.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spotlight.d.ts","sourceRoot":"","sources":["../../../../../../src/api/app/bsky/notification/grouping/spotlight.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACV,cAAc,EACd,iBAAiB,EACjB,gBAAgB,EACjB,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"spotlight.d.ts","sourceRoot":"","sources":["../../../../../../src/api/app/bsky/notification/grouping/spotlight.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EACV,aAAa,EACb,cAAc,EACd,iBAAiB,EACjB,gBAAgB,EACjB,MAAM,eAAe,CAAA;AA+BtB,eAAO,MAAM,cAAc,UAClB,gBAAgB,EAAE,UACjB,iBAAiB,EAAE,UACnB,MAAM,GAAG,SAAS,SACnB,MAAM,OACR,MAAM,UACH,MAAM,GAAG,SAAS,eACb,aAAa,KACzB,cA6GF,CAAA"}
|