@atproto/bsky 0.1.7 → 0.2.1
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 +58 -0
- package/dist/api/app/bsky/actor/getProfile.js +1 -1
- package/dist/api/app/bsky/actor/getProfile.js.map +1 -1
- package/dist/api/app/bsky/actor/getProfiles.js +1 -1
- package/dist/api/app/bsky/actor/getProfiles.js.map +1 -1
- package/dist/api/app/bsky/actor/getSuggestions.js +2 -2
- package/dist/api/app/bsky/actor/getSuggestions.js.map +1 -1
- package/dist/api/app/bsky/embed/getEmbedExternalView.d.ts.map +1 -1
- package/dist/api/app/bsky/embed/getEmbedExternalView.js +132 -53
- package/dist/api/app/bsky/embed/getEmbedExternalView.js.map +1 -1
- package/dist/api/app/bsky/feed/getAuthorFeed.d.ts.map +1 -1
- package/dist/api/app/bsky/feed/getAuthorFeed.js +4 -2
- package/dist/api/app/bsky/feed/getAuthorFeed.js.map +1 -1
- package/dist/api/app/bsky/graph/getSuggestedFollowsByActor.js +2 -2
- package/dist/api/app/bsky/graph/getSuggestedFollowsByActor.js.map +1 -1
- package/dist/api/app/bsky/notification/constants.d.ts +4 -1
- package/dist/api/app/bsky/notification/constants.d.ts.map +1 -1
- package/dist/api/app/bsky/notification/constants.js +3 -0
- package/dist/api/app/bsky/notification/constants.js.map +1 -1
- package/dist/api/app/bsky/notification/getGroupedNotifications.d.ts +1 -1
- package/dist/api/app/bsky/notification/getGroupedNotifications.d.ts.map +1 -1
- package/dist/api/app/bsky/notification/getGroupedNotifications.js +44 -26
- package/dist/api/app/bsky/notification/getGroupedNotifications.js.map +1 -1
- package/dist/api/app/bsky/notification/grouping/spotlight.d.ts +1 -1
- package/dist/api/app/bsky/notification/grouping/spotlight.d.ts.map +1 -1
- package/dist/api/app/bsky/notification/grouping/spotlight.js +114 -80
- package/dist/api/app/bsky/notification/grouping/spotlight.js.map +1 -1
- package/dist/api/app/bsky/notification/listActivitySubscriptions.js +3 -2
- package/dist/api/app/bsky/notification/listActivitySubscriptions.js.map +1 -1
- package/dist/api/app/bsky/unspecced/getAtmosphereExploreTab.d.ts.map +1 -1
- package/dist/api/app/bsky/unspecced/getAtmosphereExploreTab.js +16 -148
- package/dist/api/app/bsky/unspecced/getAtmosphereExploreTab.js.map +1 -1
- package/dist/api/internal/bsky/actor/getProfiles.js +4 -1
- package/dist/api/internal/bsky/actor/getProfiles.js.map +1 -1
- package/dist/config.d.ts +10 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +43 -0
- package/dist/config.js.map +1 -1
- package/dist/data-plane/server/indexing/index.d.ts +3 -1
- package/dist/data-plane/server/indexing/index.d.ts.map +1 -1
- package/dist/data-plane/server/indexing/index.js +2 -0
- package/dist/data-plane/server/indexing/index.js.map +1 -1
- package/dist/data-plane/server/indexing/plugins/profile-link.d.ts +8 -0
- package/dist/data-plane/server/indexing/plugins/profile-link.d.ts.map +1 -0
- package/dist/data-plane/server/indexing/plugins/profile-link.js +30 -0
- package/dist/data-plane/server/indexing/plugins/profile-link.js.map +1 -0
- package/dist/data-plane/server/routes/records.d.ts.map +1 -1
- package/dist/data-plane/server/routes/records.js +1 -0
- package/dist/data-plane/server/routes/records.js.map +1 -1
- package/dist/hydration/actor.d.ts +4 -1
- package/dist/hydration/actor.d.ts.map +1 -1
- package/dist/hydration/actor.js +11 -0
- package/dist/hydration/actor.js.map +1 -1
- package/dist/hydration/external.d.ts +67 -64
- package/dist/hydration/external.d.ts.map +1 -1
- package/dist/hydration/external.js +79 -133
- package/dist/hydration/external.js.map +1 -1
- package/dist/hydration/hydrator.d.ts +53 -14
- package/dist/hydration/hydrator.d.ts.map +1 -1
- package/dist/hydration/hydrator.js +422 -105
- package/dist/hydration/hydrator.js.map +1 -1
- package/dist/lexicons/app/bsky/actor/defs.defs.d.ts +25 -0
- package/dist/lexicons/app/bsky/actor/defs.defs.d.ts.map +1 -1
- package/dist/lexicons/app/bsky/actor/defs.defs.js +16 -0
- package/dist/lexicons/app/bsky/actor/defs.defs.js.map +1 -1
- package/dist/lexicons/app/bsky/actor/link.d.ts +3 -0
- package/dist/lexicons/app/bsky/actor/link.d.ts.map +1 -0
- package/dist/lexicons/app/bsky/actor/link.defs.d.ts +120 -0
- package/dist/lexicons/app/bsky/actor/link.defs.d.ts.map +1 -0
- package/dist/lexicons/app/bsky/actor/link.defs.js +39 -0
- package/dist/lexicons/app/bsky/actor/link.defs.js.map +1 -0
- package/dist/lexicons/app/bsky/actor/link.js +6 -0
- package/dist/lexicons/app/bsky/actor/link.js.map +1 -0
- package/dist/lexicons/app/bsky/actor/profile.defs.d.ts +14 -0
- package/dist/lexicons/app/bsky/actor/profile.defs.d.ts.map +1 -1
- package/dist/lexicons/app/bsky/actor/profile.defs.js +3 -0
- package/dist/lexicons/app/bsky/actor/profile.defs.js.map +1 -1
- package/dist/lexicons/app/bsky/actor.d.ts +1 -0
- package/dist/lexicons/app/bsky/actor.d.ts.map +1 -1
- package/dist/lexicons/app/bsky/actor.js +1 -0
- package/dist/lexicons/app/bsky/actor.js.map +1 -1
- package/dist/lexicons/app/bsky/authFullApp.defs.d.ts.map +1 -1
- package/dist/lexicons/app/bsky/authFullApp.defs.js +1 -0
- package/dist/lexicons/app/bsky/authFullApp.defs.js.map +1 -1
- package/dist/lexicons/app/bsky/authManageProfile.defs.d.ts.map +1 -1
- package/dist/lexicons/app/bsky/authManageProfile.defs.js +1 -0
- package/dist/lexicons/app/bsky/authManageProfile.defs.js.map +1 -1
- package/dist/lexicons/app/bsky/embed/getEmbedExternalView.defs.d.ts +13 -3
- package/dist/lexicons/app/bsky/embed/getEmbedExternalView.defs.d.ts.map +1 -1
- package/dist/lexicons/app/bsky/embed/getEmbedExternalView.defs.js +11 -3
- package/dist/lexicons/app/bsky/embed/getEmbedExternalView.defs.js.map +1 -1
- package/dist/lexicons/app/bsky/notification/getGroupedNotifications.defs.d.ts +62 -3
- package/dist/lexicons/app/bsky/notification/getGroupedNotifications.defs.d.ts.map +1 -1
- package/dist/lexicons/app/bsky/notification/getGroupedNotifications.defs.js +53 -2
- package/dist/lexicons/app/bsky/notification/getGroupedNotifications.defs.js.map +1 -1
- package/dist/lexicons/tools/ozone/inbox/defs.defs.d.ts +14 -1
- package/dist/lexicons/tools/ozone/inbox/defs.defs.d.ts.map +1 -1
- package/dist/lexicons/tools/ozone/inbox/defs.defs.js +6 -1
- package/dist/lexicons/tools/ozone/inbox/defs.defs.js.map +1 -1
- package/dist/lexicons/tools/ozone/inbox/getNotificationPreferences.defs.d.ts +1 -0
- package/dist/lexicons/tools/ozone/inbox/getNotificationPreferences.defs.d.ts.map +1 -1
- package/dist/lexicons/tools/ozone/inbox/getNotificationPreferences.defs.js +1 -0
- package/dist/lexicons/tools/ozone/inbox/getNotificationPreferences.defs.js.map +1 -1
- package/dist/lexicons/tools/ozone/inbox/putNotificationPreferences.defs.d.ts +7 -2
- package/dist/lexicons/tools/ozone/inbox/putNotificationPreferences.defs.d.ts.map +1 -1
- package/dist/lexicons/tools/ozone/inbox/putNotificationPreferences.defs.js +4 -1
- package/dist/lexicons/tools/ozone/inbox/putNotificationPreferences.defs.js.map +1 -1
- package/dist/proto/bsky_connect.d.ts +10 -1
- package/dist/proto/bsky_connect.d.ts.map +1 -1
- package/dist/proto/bsky_connect.js +10 -1
- package/dist/proto/bsky_connect.js.map +1 -1
- package/dist/proto/bsky_pb.d.ts +34 -0
- package/dist/proto/bsky_pb.d.ts.map +1 -1
- package/dist/proto/bsky_pb.js +60 -0
- package/dist/proto/bsky_pb.js.map +1 -1
- package/dist/telemetry/events.d.ts +35 -0
- package/dist/telemetry/events.d.ts.map +1 -1
- package/dist/telemetry/events.js +68 -0
- package/dist/telemetry/events.js.map +1 -1
- package/dist/util/external/index.d.ts +8 -0
- package/dist/util/external/index.d.ts.map +1 -0
- package/dist/util/external/index.js +50 -0
- package/dist/util/external/index.js.map +1 -0
- package/dist/util/{standard-site.d.ts → external/standard-site.d.ts} +30 -4
- package/dist/util/external/standard-site.d.ts.map +1 -0
- package/dist/util/{standard-site.js → external/standard-site.js} +73 -4
- package/dist/util/external/standard-site.js.map +1 -0
- package/dist/views/external.js +1 -1
- package/dist/views/external.js.map +1 -1
- package/dist/views/index.d.ts +11 -7
- package/dist/views/index.d.ts.map +1 -1
- package/dist/views/index.js +94 -43
- package/dist/views/index.js.map +1 -1
- package/dist/views/profile-link-rules.d.ts +10 -0
- package/dist/views/profile-link-rules.d.ts.map +1 -0
- package/dist/views/profile-link-rules.js +48 -0
- package/dist/views/profile-link-rules.js.map +1 -0
- package/dist/views/types.d.ts +2 -0
- package/dist/views/types.d.ts.map +1 -1
- package/dist/views/types.js.map +1 -1
- package/package.json +7 -7
- package/dist/util/standard-site.d.ts.map +0 -1
- package/dist/util/standard-site.js.map +0 -1
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import assert from 'node:assert';
|
|
2
|
-
import { dedupeStrs, mapDefined } from '@atproto/common';
|
|
3
|
-
import { atUri } from '@atproto/lex';
|
|
4
|
-
import { AtUri, } from '@atproto/syntax';
|
|
2
|
+
import { chunkArray, dedupeStrs, mapDefined } from '@atproto/common';
|
|
3
|
+
import { atUri, isDidString } from '@atproto/lex';
|
|
4
|
+
import { AtUri, parseAtUriString, } from '@atproto/syntax';
|
|
5
5
|
import { NOTIFICATION_REASON } from '../api/app/bsky/notification/constants.js';
|
|
6
|
-
import { app, chat, com } from '../lexicons/index.js';
|
|
6
|
+
import { app, chat, com, place, site, social } from '../lexicons/index.js';
|
|
7
7
|
import { hydrationLogger } from '../logger.js';
|
|
8
8
|
import { events } from '../telemetry/events.js';
|
|
9
|
-
import { SITE_STANDARD_NSID_PREFIX } from '../util/standard-site.js';
|
|
9
|
+
import { SITE_STANDARD_NSID_PREFIX } from '../util/external/standard-site.js';
|
|
10
10
|
import { uriToDid, uriToDid as didFromUri } from '../util/uris.js';
|
|
11
|
+
import { MAX_PROFILE_LINKS } from '../views/profile-link-rules.js';
|
|
11
12
|
import { isExternalEmbedType, isListRuleType, isRecordEmbedType, isRecordWithMediaType, } from '../views/types.js';
|
|
12
13
|
import { ActorHydrator, } from './actor.js';
|
|
13
|
-
import { ExternalHydrator, parseGenericRecordKey, } from './external.js';
|
|
14
|
+
import { ExternalHydrator, actionExternalRecordTakedowns, genericRecordKey, parseGenericRecordKey, trackExternalHydration, } from './external.js';
|
|
14
15
|
import { FeedHydrator, } from './feed.js';
|
|
15
16
|
import { GraphHydrator, } from './graph.js';
|
|
16
17
|
import { LabelHydrator, Labels, } from './label.js';
|
|
@@ -74,7 +75,130 @@ export class Hydrator {
|
|
|
74
75
|
// app.bsky.actor.defs#profileView
|
|
75
76
|
// - profile
|
|
76
77
|
// - list basic
|
|
77
|
-
async hydrateProfiles(dids, ctx) {
|
|
78
|
+
async hydrateProfiles(dids, ctx, opts) {
|
|
79
|
+
return this.hydrateProfilesWorker(dids, ctx, 'profile', opts);
|
|
80
|
+
}
|
|
81
|
+
// app.bsky.actor.defs#profileViewBasic
|
|
82
|
+
// - profile basic
|
|
83
|
+
// - profile
|
|
84
|
+
// - list basic
|
|
85
|
+
async hydrateProfilesBasic(dids, ctx, opts) {
|
|
86
|
+
return this.hydrateProfilesWorker(dids, ctx, 'basic', opts);
|
|
87
|
+
}
|
|
88
|
+
// app.bsky.actor.defs#profileViewDetailed
|
|
89
|
+
// - profile detailed
|
|
90
|
+
// - profile
|
|
91
|
+
// - list basic
|
|
92
|
+
// - starterpack
|
|
93
|
+
// - profile
|
|
94
|
+
// - list basic
|
|
95
|
+
// - labels
|
|
96
|
+
async hydrateProfilesDetailed(dids, ctx, opts) {
|
|
97
|
+
return this.hydrateProfilesWorker(dids, ctx, 'detailed', opts);
|
|
98
|
+
}
|
|
99
|
+
async hydrateProfilesWorker(dids, ctx, level, opts) {
|
|
100
|
+
const detailed = level === 'detailed';
|
|
101
|
+
const knownFollowersDids = opts?.knownFollowersDids;
|
|
102
|
+
const includeActivitySubscriptions = opts?.activitySubscriptions ?? false;
|
|
103
|
+
// Batch 1: known followers, profile counts, activity subscriptions.
|
|
104
|
+
const knownFollowersPromise = knownFollowersDids?.length
|
|
105
|
+
? this.actor
|
|
106
|
+
.getKnownFollowers(knownFollowersDids, ctx.viewer)
|
|
107
|
+
.catch((err) => {
|
|
108
|
+
hydrationLogger.error({ err }, 'Failed to get known followers for profiles');
|
|
109
|
+
return new HydrationMap();
|
|
110
|
+
})
|
|
111
|
+
: Promise.resolve(undefined);
|
|
112
|
+
const profileAggsPromise = detailed
|
|
113
|
+
? this.actor.getProfileAggregates(dids)
|
|
114
|
+
: Promise.resolve(undefined);
|
|
115
|
+
const activitySubscriptionsPromise = includeActivitySubscriptions
|
|
116
|
+
? this.actor
|
|
117
|
+
.getActivitySubscriptions(dids, ctx.viewer)
|
|
118
|
+
.catch((err) => {
|
|
119
|
+
if (!detailed && opts?.activitySubscriptions)
|
|
120
|
+
throw err;
|
|
121
|
+
hydrationLogger.error({ err }, 'Failed to get activity subscriptions state for profiles');
|
|
122
|
+
return new HydrationMap();
|
|
123
|
+
})
|
|
124
|
+
: Promise.resolve(undefined);
|
|
125
|
+
const profilesPromise = (async () => {
|
|
126
|
+
// Wait for known followers to collect their DIDs for batch 2.
|
|
127
|
+
const knownFollowers = await knownFollowersPromise;
|
|
128
|
+
const subjectsToKnownFollowersMap = new Map();
|
|
129
|
+
knownFollowers?.forEach((known, did) => {
|
|
130
|
+
if (known)
|
|
131
|
+
subjectsToKnownFollowersMap.set(did, known.followers);
|
|
132
|
+
});
|
|
133
|
+
const allKnownFollowerDids = knownFollowers
|
|
134
|
+
? Array.from(knownFollowers.values())
|
|
135
|
+
.filter(Boolean)
|
|
136
|
+
.flatMap((f) => f.followers)
|
|
137
|
+
: [];
|
|
138
|
+
const allDids = Array.from(new Set(dids.concat(allKnownFollowerDids)));
|
|
139
|
+
// Batch 2: profiles, bidirectional blocks between requested DIDs and their known followers.
|
|
140
|
+
const profileStatePromise = this.hydrateProfileState(allDids, ctx);
|
|
141
|
+
const bidirectionalBlocksPromise = knownFollowers
|
|
142
|
+
? this.hydrateBidirectionalBlocks(subjectsToKnownFollowersMap, ctx)
|
|
143
|
+
: undefined;
|
|
144
|
+
// Batch 3: joined-via starter packs, profile links.
|
|
145
|
+
const starterPackStatePromise = detailed
|
|
146
|
+
? (async () => {
|
|
147
|
+
const state = await profileStatePromise;
|
|
148
|
+
const starterPackUriSet = new Set();
|
|
149
|
+
state.actors?.forEach((actor) => {
|
|
150
|
+
if (actor?.profile?.joinedViaStarterPack) {
|
|
151
|
+
starterPackUriSet.add(actor.profile.joinedViaStarterPack.uri);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
return this.hydrateStarterPacksBasic([...starterPackUriSet], ctx);
|
|
155
|
+
})()
|
|
156
|
+
: undefined;
|
|
157
|
+
const profileLinksPromises = detailed
|
|
158
|
+
? (async () => {
|
|
159
|
+
const state = await profileStatePromise;
|
|
160
|
+
const linkUris = profileLinkUris(dids, state.actors);
|
|
161
|
+
return Promise.all([
|
|
162
|
+
this.actor
|
|
163
|
+
.getProfileLinks(linkUris, ctx.includeTakedowns)
|
|
164
|
+
.catch((err) => {
|
|
165
|
+
hydrationLogger.error({ err }, 'Failed to get profile links');
|
|
166
|
+
return new HydrationMap();
|
|
167
|
+
}),
|
|
168
|
+
this.label.getLabelsForSubjects(linkUris, ctx.labelers),
|
|
169
|
+
]);
|
|
170
|
+
})()
|
|
171
|
+
: undefined;
|
|
172
|
+
const [profileState, bidirectionalBlocks, starterPackState, [profileLinks, profileLinksLabels],] = await Promise.all([
|
|
173
|
+
profileStatePromise,
|
|
174
|
+
bidirectionalBlocksPromise,
|
|
175
|
+
starterPackStatePromise,
|
|
176
|
+
profileLinksPromises || [],
|
|
177
|
+
]);
|
|
178
|
+
if (!ctx.includeTakedowns && profileLinks && profileLinksLabels) {
|
|
179
|
+
actionTakedownLabels([...profileLinks.keys()], profileLinks, profileLinksLabels);
|
|
180
|
+
}
|
|
181
|
+
return mergeManyStates(profileState, starterPackState ?? {}, {
|
|
182
|
+
knownFollowers,
|
|
183
|
+
bidirectionalBlocks,
|
|
184
|
+
profileLinks,
|
|
185
|
+
labels: profileLinksLabels,
|
|
186
|
+
ctx,
|
|
187
|
+
});
|
|
188
|
+
})();
|
|
189
|
+
// Also await counts and subscriptions from batch 1.
|
|
190
|
+
const [state, profileAggs, activitySubscriptions] = await Promise.all([
|
|
191
|
+
profilesPromise,
|
|
192
|
+
profileAggsPromise,
|
|
193
|
+
activitySubscriptionsPromise,
|
|
194
|
+
]);
|
|
195
|
+
return mergeStates(state, {
|
|
196
|
+
profileAggs,
|
|
197
|
+
activitySubscriptions,
|
|
198
|
+
ctx,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
async hydrateProfileState(dids, ctx) {
|
|
78
202
|
/**
|
|
79
203
|
* Special case here, we want to include takedowns in special cases, like
|
|
80
204
|
* `getProfile`, since we throw client-facing errors later in the pipeline.
|
|
@@ -124,66 +248,6 @@ export class Hydrator {
|
|
|
124
248
|
ctx,
|
|
125
249
|
});
|
|
126
250
|
}
|
|
127
|
-
// app.bsky.actor.defs#profileViewBasic
|
|
128
|
-
// - profile basic
|
|
129
|
-
// - profile
|
|
130
|
-
// - list basic
|
|
131
|
-
async hydrateProfilesBasic(dids, ctx) {
|
|
132
|
-
return this.hydrateProfiles(dids, ctx);
|
|
133
|
-
}
|
|
134
|
-
// app.bsky.actor.defs#profileViewDetailed
|
|
135
|
-
// - profile detailed
|
|
136
|
-
// - profile
|
|
137
|
-
// - list basic
|
|
138
|
-
// - starterpack
|
|
139
|
-
// - profile
|
|
140
|
-
// - list basic
|
|
141
|
-
// - labels
|
|
142
|
-
async hydrateProfilesDetailed(dids, ctx, opts) {
|
|
143
|
-
const [knownFollowers, activitySubscriptions] = await Promise.all([
|
|
144
|
-
this.actor
|
|
145
|
-
.getKnownFollowers(opts?.knownFollowersDids ?? dids, ctx.viewer)
|
|
146
|
-
.catch((err) => {
|
|
147
|
-
hydrationLogger.error({ err }, 'Failed to get known followers for profiles');
|
|
148
|
-
return new HydrationMap();
|
|
149
|
-
}),
|
|
150
|
-
this.actor
|
|
151
|
-
.getActivitySubscriptions(dids, ctx.viewer)
|
|
152
|
-
.catch((err) => {
|
|
153
|
-
hydrationLogger.error({ err }, 'Failed to get activity subscriptions state for profiles');
|
|
154
|
-
return new HydrationMap();
|
|
155
|
-
}),
|
|
156
|
-
]);
|
|
157
|
-
const subjectsToKnownFollowersMap = new Map();
|
|
158
|
-
for (const did of knownFollowers.keys()) {
|
|
159
|
-
const known = knownFollowers.get(did);
|
|
160
|
-
if (known)
|
|
161
|
-
subjectsToKnownFollowersMap.set(did, known.followers);
|
|
162
|
-
}
|
|
163
|
-
const allKnownFollowerDids = Array.from(knownFollowers.values())
|
|
164
|
-
.filter(Boolean)
|
|
165
|
-
.flatMap((f) => f.followers);
|
|
166
|
-
const allDids = Array.from(new Set(dids.concat(allKnownFollowerDids)));
|
|
167
|
-
const [state, profileAggs, bidirectionalBlocks] = await Promise.all([
|
|
168
|
-
this.hydrateProfiles(allDids, ctx),
|
|
169
|
-
this.actor.getProfileAggregates(dids),
|
|
170
|
-
this.hydrateBidirectionalBlocks(subjectsToKnownFollowersMap, ctx),
|
|
171
|
-
]);
|
|
172
|
-
const starterPackUriSet = new Set();
|
|
173
|
-
state.actors?.forEach((actor) => {
|
|
174
|
-
if (actor?.profile?.joinedViaStarterPack) {
|
|
175
|
-
starterPackUriSet.add(actor?.profile?.joinedViaStarterPack?.uri);
|
|
176
|
-
}
|
|
177
|
-
});
|
|
178
|
-
const starterPackState = await this.hydrateStarterPacksBasic([...starterPackUriSet], ctx);
|
|
179
|
-
return mergeManyStates(state, starterPackState, {
|
|
180
|
-
profileAggs,
|
|
181
|
-
knownFollowers,
|
|
182
|
-
activitySubscriptions,
|
|
183
|
-
ctx,
|
|
184
|
-
bidirectionalBlocks,
|
|
185
|
-
});
|
|
186
|
-
}
|
|
187
251
|
// app.bsky.graph.defs#listView
|
|
188
252
|
// - list
|
|
189
253
|
// - profile basic
|
|
@@ -556,47 +620,244 @@ export class Hydrator {
|
|
|
556
620
|
});
|
|
557
621
|
}
|
|
558
622
|
/**
|
|
559
|
-
* Hydrate the
|
|
560
|
-
*
|
|
561
|
-
*
|
|
562
|
-
*
|
|
623
|
+
* Hydrate the latest versions of external records, plus labels, profiles, and
|
|
624
|
+
* their view dependencies. `hydrationPass` is the 1-based number of this
|
|
625
|
+
* generic batch within the traversal (the root batch is pass 1); nested
|
|
626
|
+
* batches continue the count. Nested passes share the caller's
|
|
627
|
+
* `hydrationMetrics` tracker; without one, this call owns the traversal and
|
|
628
|
+
* reports its totals.
|
|
629
|
+
*/
|
|
630
|
+
async hydrateEmbedExternalViewFromUris(uris, ctx, seenRecordKeys = new Set(), hydrationPass = 1, hydrationMetrics) {
|
|
631
|
+
return trackExternalHydration('uris', hydrationMetrics, (hydrationMetrics) => this.hydrateEmbedExternalView(uris.map(uriToRef), ctx, seenRecordKeys, hydrationPass, hydrationMetrics));
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Like `hydrateEmbedExternalViewFromUris`, but for exact record versions
|
|
635
|
+
* pinned by strong refs. Roots land only in `externalRecordsByRef`; their
|
|
636
|
+
* URI dependencies are hydrated as latest versions by the URI path.
|
|
563
637
|
*/
|
|
564
|
-
async
|
|
565
|
-
|
|
566
|
-
|
|
638
|
+
async hydrateEmbedExternalViewFromRefs(refs, ctx, seenRecordKeys = new Set(), hydrationPass = 1, hydrationMetrics) {
|
|
639
|
+
return trackExternalHydration('refs', hydrationMetrics, (hydrationMetrics) => this.hydrateEmbedExternalView(refs, ctx, seenRecordKeys, hydrationPass, hydrationMetrics));
|
|
640
|
+
}
|
|
641
|
+
async hydrateEmbedExternalView(refs, ctx, seenRecordKeys, hydrationPass, hydrationMetrics) {
|
|
642
|
+
// Filter at the entry point so each lookup runs at most once per shared
|
|
643
|
+
// traversal, even for callers that did not prefilter their input. Null or
|
|
644
|
+
// unavailable results stay seen as completed lookups. Nothing unseen means
|
|
645
|
+
// no hydration work, and no pass is consumed.
|
|
646
|
+
const items = takeUnseenRefs(refs, seenRecordKeys);
|
|
647
|
+
if (!items.length)
|
|
567
648
|
return { ctx };
|
|
568
|
-
|
|
569
|
-
const
|
|
570
|
-
|
|
571
|
-
|
|
649
|
+
// Get any bare URIs to fetch
|
|
650
|
+
const byUriValues = items
|
|
651
|
+
.filter(({ cid }) => cid === undefined)
|
|
652
|
+
.map(({ uri }) => uri);
|
|
653
|
+
// Get any exact refs to fetch
|
|
654
|
+
const byRefValues = items.filter(isExactRef);
|
|
655
|
+
// Pull out all URIs for label/profile hydration
|
|
656
|
+
const allUris = dedupeStrs(items.map(({ uri }) => uri));
|
|
657
|
+
// Get all the actors that are part of this hydration
|
|
658
|
+
const dids = dedupeStrs(allUris.map(uriToDid));
|
|
659
|
+
const fetchRecords = async () => {
|
|
660
|
+
const [externalRecords, exactRecords] = await Promise.all([
|
|
661
|
+
byUriValues.length
|
|
662
|
+
? this.external.getRecordsByURI(byUriValues, ctx.includeTakedowns)
|
|
663
|
+
: undefined,
|
|
664
|
+
byRefValues.length
|
|
665
|
+
? this.external.getRecordsByRef(byRefValues, ctx.includeTakedowns)
|
|
666
|
+
: undefined,
|
|
667
|
+
]);
|
|
668
|
+
// Build the by-ref map used by some endpoints. Latest versions were not
|
|
669
|
+
// requested exactly, so they are not marked as completed exact lookups,
|
|
670
|
+
// and requested exact entries take precedence over them.
|
|
671
|
+
const externalRecordsByRef = new HydrationMap();
|
|
672
|
+
for (const [uri, info] of externalRecords ?? []) {
|
|
673
|
+
if (!info)
|
|
674
|
+
continue;
|
|
675
|
+
externalRecordsByRef.set(genericRecordKey({ uri, cid: info.cid }), info);
|
|
676
|
+
}
|
|
677
|
+
if (exactRecords)
|
|
678
|
+
externalRecordsByRef.merge(exactRecords);
|
|
679
|
+
return { externalRecords, externalRecordsByRef };
|
|
680
|
+
};
|
|
681
|
+
// Count this batch where its generic fetches are scheduled; a failed batch
|
|
682
|
+
// was still attempted.
|
|
683
|
+
hydrationMetrics.recordLookups += items.length;
|
|
684
|
+
hydrationMetrics.batches += 1;
|
|
685
|
+
hydrationMetrics.maxPass = Math.max(hydrationMetrics.maxPass, hydrationPass);
|
|
686
|
+
const [{ externalRecords, externalRecordsByRef }, labels, profiles] = await Promise.all([
|
|
687
|
+
fetchRecords(),
|
|
688
|
+
this.label.getLabelsForSubjects(allUris, ctx.labelers),
|
|
572
689
|
this.hydrateProfilesBasic(dids, ctx),
|
|
573
690
|
]);
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
// Edge case: a document's `site` may resolve to a publication owned by a
|
|
578
|
-
// different repo than any of the input URIs (the dataplane returns it
|
|
579
|
-
// even though it wasn't requested directly). Top up profile coverage for
|
|
580
|
-
// any such DIDs with a serial second hydration so `associatedProfiles`
|
|
581
|
-
// is complete.
|
|
582
|
-
const knownDids = new Set(dids);
|
|
583
|
-
const extraDids = [];
|
|
584
|
-
for (const key of publications.keys()) {
|
|
585
|
-
const did = uriToDid(parseGenericRecordKey(key).uri);
|
|
586
|
-
if (!knownDids.has(did)) {
|
|
587
|
-
knownDids.add(did);
|
|
588
|
-
extraDids.push(did);
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
const profilesState = extraDids.length
|
|
592
|
-
? mergeStates(profiles, await this.hydrateProfilesBasic(extraDids, ctx))
|
|
593
|
-
: profiles;
|
|
594
|
-
return mergeStates(profilesState, {
|
|
691
|
+
// Merge record and profile state, then hide moderated records so they
|
|
692
|
+
// trigger no dependency expansion.
|
|
693
|
+
const baseState = mergeStates(profiles, {
|
|
595
694
|
ctx,
|
|
596
695
|
labels,
|
|
597
|
-
|
|
598
|
-
|
|
696
|
+
externalRecords,
|
|
697
|
+
externalRecordsByRef,
|
|
599
698
|
});
|
|
699
|
+
actionExternalRecordTakedowns(baseState, ctx.includeTakedowns);
|
|
700
|
+
// Discover and hydrate any additional view dependencies
|
|
701
|
+
const depsState = await this.hydrateExternalViewDependencies(baseState, items, ctx, seenRecordKeys, hydrationPass, hydrationMetrics);
|
|
702
|
+
// A supplemental latest lookup returning the same URI and CID is the same
|
|
703
|
+
// version, so it may replace an unavailable exact entry; other CIDs have
|
|
704
|
+
// other keys and never satisfy a pin.
|
|
705
|
+
return mergeStates(baseState, depsState);
|
|
706
|
+
}
|
|
707
|
+
/**
|
|
708
|
+
* Discover view dependencies by URI collection and hydrate new URIs in a batch.
|
|
709
|
+
* Returns additional state. Each input is inspected in the map matching its
|
|
710
|
+
* identity: `(uri, cid)` refs in `externalRecordsByRef`, bare URIs in
|
|
711
|
+
* `externalRecords`. Every pinned version is inspected, since versions of
|
|
712
|
+
* one URI can reference different dependencies.
|
|
713
|
+
*
|
|
714
|
+
* `seenRecordKeys` prevents repeated lookups and cycles across nested passes,
|
|
715
|
+
* but not a chain of distinct URIs; `MAX_EXTERNAL_HYDRATION_PASSES` bounds
|
|
716
|
+
* that, as a guard against future association rules or unexpected expansion.
|
|
717
|
+
* `hydrationPass` is the number of the generic batch that produced `state`
|
|
718
|
+
* (1-based, default 1 for prehydrated state, which is treated as the root
|
|
719
|
+
* batch); the next nested batch is `hydrationPass + 1`. `hydrationMetrics`
|
|
720
|
+
* is shared as in `hydrateEmbedExternalViewFromUris`.
|
|
721
|
+
*/
|
|
722
|
+
async hydrateExternalViewDependencies(state, refs, ctx, seenRecordKeys = new Set(), hydrationPass = 1, hydrationMetrics) {
|
|
723
|
+
return trackExternalHydration('dependencies', hydrationMetrics, (hydrationMetrics) => this.discoverExternalViewDependencies(state, refs, ctx, seenRecordKeys, hydrationPass, hydrationMetrics));
|
|
724
|
+
}
|
|
725
|
+
async discoverExternalViewDependencies(state, refs, ctx, seenRecordKeys, hydrationPass, hydrationMetrics) {
|
|
726
|
+
// Backlink sources to sample per target, each with its own bound.
|
|
727
|
+
const targets = new Map();
|
|
728
|
+
const discoveredUris = new Set();
|
|
729
|
+
for (const ref of refs) {
|
|
730
|
+
/*
|
|
731
|
+
* Additional safety in case this method is ever called directly.
|
|
732
|
+
* `takeUnseenRefs` already populates this map, so this is a no-op under
|
|
733
|
+
* normal circumstances.
|
|
734
|
+
*/
|
|
735
|
+
seenRecordKeys.add(genericRecordKey(ref));
|
|
736
|
+
const { uri, cid } = ref;
|
|
737
|
+
const record = (cid === undefined
|
|
738
|
+
? state.externalRecords?.get(uri)
|
|
739
|
+
: state.externalRecordsByRef?.get(genericRecordKey({ uri, cid })))?.record;
|
|
740
|
+
if (!record)
|
|
741
|
+
continue;
|
|
742
|
+
const parsed = parseAtUriString(uri);
|
|
743
|
+
if (!parsed.success ||
|
|
744
|
+
!isDidString(parsed.value.authority) ||
|
|
745
|
+
!parsed.value.rkey ||
|
|
746
|
+
parsed.value.hash) {
|
|
747
|
+
continue;
|
|
748
|
+
}
|
|
749
|
+
/*
|
|
750
|
+
* Collection-specific rules for discovering additional view dependencies.
|
|
751
|
+
*/
|
|
752
|
+
const { collection } = parsed.value;
|
|
753
|
+
if (collection === site.standard.document.$type &&
|
|
754
|
+
site.standard.document.$matches(record, { strict: false })) {
|
|
755
|
+
targets.set(uri, [
|
|
756
|
+
{
|
|
757
|
+
collection: site.standard.graph.recommend.$type,
|
|
758
|
+
limit: ExternalHydrator.MAX_BACKLINK_PREVIEWS,
|
|
759
|
+
},
|
|
760
|
+
]);
|
|
761
|
+
const publicationUri = recordUriOf(record.site, site.standard.publication.$type);
|
|
762
|
+
if (publicationUri)
|
|
763
|
+
discoveredUris.add(publicationUri);
|
|
764
|
+
}
|
|
765
|
+
else if (collection === site.standard.publication.$type &&
|
|
766
|
+
site.standard.publication.$matches(record, { strict: false })) {
|
|
767
|
+
targets.set(uri, [
|
|
768
|
+
{
|
|
769
|
+
collection: site.standard.graph.subscription.$type,
|
|
770
|
+
limit: ExternalHydrator.MAX_BACKLINK_PREVIEWS,
|
|
771
|
+
},
|
|
772
|
+
]);
|
|
773
|
+
}
|
|
774
|
+
else if (collection === place.stream.livestream.$type &&
|
|
775
|
+
place.stream.livestream.$matches(record, { strict: false })) {
|
|
776
|
+
// Nothing to do atm
|
|
777
|
+
}
|
|
778
|
+
else if (collection === social.grain.gallery.$type &&
|
|
779
|
+
social.grain.gallery.$matches(record, { strict: false })) {
|
|
780
|
+
targets.set(uri, [
|
|
781
|
+
{
|
|
782
|
+
collection: social.grain.favorite.$type,
|
|
783
|
+
limit: ExternalHydrator.MAX_BACKLINK_PREVIEWS,
|
|
784
|
+
},
|
|
785
|
+
{
|
|
786
|
+
collection: social.grain.gallery.item.$type,
|
|
787
|
+
limit: ExternalHydrator.MAX_BACKLINK_GALLERY_ITEMS,
|
|
788
|
+
},
|
|
789
|
+
]);
|
|
790
|
+
}
|
|
791
|
+
else if (collection === social.grain.gallery.item.$type &&
|
|
792
|
+
social.grain.gallery.item.$matches(record, { strict: false })) {
|
|
793
|
+
// Items have no backlinks; they only point at their photo.
|
|
794
|
+
const photoUri = recordUriOf(record.item, social.grain.photo.$type);
|
|
795
|
+
if (photoUri)
|
|
796
|
+
discoveredUris.add(photoUri);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
let externalRecordBacklinks;
|
|
800
|
+
let externalRecordBacklinkCounts;
|
|
801
|
+
if (targets.size) {
|
|
802
|
+
const backlinks = new HydrationMap();
|
|
803
|
+
const queries = [...targets].flatMap(([uri, sources]) => sources.map((source) => ({ uri, ...source })));
|
|
804
|
+
// @NOTE Backlink data is best effort: a failed lookup leaves its state
|
|
805
|
+
// absent and builders render what remains.
|
|
806
|
+
const [counts] = await Promise.all([
|
|
807
|
+
this.external
|
|
808
|
+
.getAtmosphereBacklinkCounts([...targets.keys()])
|
|
809
|
+
.catch((err) => {
|
|
810
|
+
hydrationLogger.warn({ err }, 'failed to hydrate external record backlink counts');
|
|
811
|
+
return undefined;
|
|
812
|
+
}),
|
|
813
|
+
(async () => {
|
|
814
|
+
// @NOTE Bound fan-out as well as each sample; counts are independent.
|
|
815
|
+
for (const batch of chunkArray(queries, ExternalHydrator.MAX_BACKLINK_FANOUT)) {
|
|
816
|
+
await Promise.all(batch.map(async ({ uri, collection, limit }) => {
|
|
817
|
+
let page;
|
|
818
|
+
try {
|
|
819
|
+
page = await this.external.getAtmosphereBacklinks(uri, collection, { limit });
|
|
820
|
+
}
|
|
821
|
+
catch (err) {
|
|
822
|
+
hydrationLogger.warn({ err, uri, collection }, 'failed to hydrate external record backlinks');
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
const links = page.backlinks
|
|
826
|
+
.filter((b) => parseAtUriString(b.uri).success)
|
|
827
|
+
.slice(0, limit)
|
|
828
|
+
.map((b) => b.uri);
|
|
829
|
+
backlinks.set(uri, dedupeStrs([...(backlinks.get(uri) ?? []), ...links]));
|
|
830
|
+
links.forEach((link) => discoveredUris.add(link));
|
|
831
|
+
}));
|
|
832
|
+
}
|
|
833
|
+
})(),
|
|
834
|
+
]);
|
|
835
|
+
externalRecordBacklinks = backlinks;
|
|
836
|
+
externalRecordBacklinkCounts = counts;
|
|
837
|
+
}
|
|
838
|
+
const newUris = [...discoveredUris].filter((uri) => !seenRecordKeys.has(genericRecordKey({ uri })));
|
|
839
|
+
let nested = {};
|
|
840
|
+
if (newUris.length &&
|
|
841
|
+
hydrationPass >= ExternalHydrator.MAX_EXTERNAL_HYDRATION_PASSES) {
|
|
842
|
+
// Keep the partial state. Skipped URIs are not marked seen: they were
|
|
843
|
+
// never fetched.
|
|
844
|
+
hydrationMetrics.capped = true;
|
|
845
|
+
hydrationLogger.warn({
|
|
846
|
+
hydrationPass,
|
|
847
|
+
maxPasses: ExternalHydrator.MAX_EXTERNAL_HYDRATION_PASSES,
|
|
848
|
+
skipped: newUris.length,
|
|
849
|
+
}, 'external hydration pass limit reached, skipping dependencies');
|
|
850
|
+
}
|
|
851
|
+
else if (newUris.length) {
|
|
852
|
+
try {
|
|
853
|
+
nested = await this.hydrateEmbedExternalViewFromUris(newUris, ctx, seenRecordKeys, hydrationPass + 1, hydrationMetrics);
|
|
854
|
+
}
|
|
855
|
+
catch (err) {
|
|
856
|
+
// Skip the whole batch; partial child state could miss moderation.
|
|
857
|
+
hydrationLogger.warn({ err, hydrationPass, skipped: newUris.length }, 'failed to hydrate external view dependencies');
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
return mergeStates({ ctx, externalRecordBacklinks, externalRecordBacklinkCounts }, nested);
|
|
600
861
|
}
|
|
601
862
|
// app.bsky.feed.defs#threadViewPost
|
|
602
863
|
// - post
|
|
@@ -892,10 +1153,11 @@ export class Hydrator {
|
|
|
892
1153
|
ctx,
|
|
893
1154
|
});
|
|
894
1155
|
}
|
|
895
|
-
async hydrateGroupedNotifications(notifs, ctx) {
|
|
1156
|
+
async hydrateGroupedNotifications(notifs, ctx, opts) {
|
|
896
1157
|
if (!notifs.length)
|
|
897
1158
|
return { ctx };
|
|
898
1159
|
const notificationUris = dedupeStrs(notifs.map((notif) => notif.uri));
|
|
1160
|
+
const authorDids = dedupeStrs(notificationUris.map(didFromUri));
|
|
899
1161
|
const collections = urisByCollection(notificationUris);
|
|
900
1162
|
const notificationPostUris = collections.get(app.bsky.feed.post.$type) ?? [];
|
|
901
1163
|
const likeUris = collections.get(app.bsky.feed.like.$type) ?? [];
|
|
@@ -934,9 +1196,12 @@ export class Hydrator {
|
|
|
934
1196
|
this.feed.getReposts([...repostUris], ctx.includeTakedowns),
|
|
935
1197
|
this.graph.getFollows(followUris, ctx.includeTakedowns),
|
|
936
1198
|
this.label.getLabelsForSubjects(
|
|
937
|
-
// Fetch labels for likes and follows here;
|
|
1199
|
+
// Fetch labels for likes and follows here; profile and post hydration fetch their own labels.
|
|
938
1200
|
[...likeUris, ...followUris], ctx.labelers),
|
|
939
|
-
|
|
1201
|
+
// Hydrate known followers only for the subset selected by the endpoint.
|
|
1202
|
+
this.hydrateProfilesBasic(authorDids, ctx, {
|
|
1203
|
+
knownFollowersDids: opts?.knownFollowersDids,
|
|
1204
|
+
}),
|
|
940
1205
|
]);
|
|
941
1206
|
reposts.forEach((repost) => {
|
|
942
1207
|
if (repost)
|
|
@@ -955,7 +1220,7 @@ export class Hydrator {
|
|
|
955
1220
|
const [postState, feedGenState, starterPackState] = await Promise.all([
|
|
956
1221
|
this.hydratePosts([...postUris].map((uri) => ({ uri })), ctx, { posts }),
|
|
957
1222
|
this.hydrateFeedGens([...feedGenUris], ctx),
|
|
958
|
-
this.
|
|
1223
|
+
this.hydrateStarterPacksBasic([...starterPackUris], ctx),
|
|
959
1224
|
]);
|
|
960
1225
|
return mergeManyStates(profileState, postState, feedGenState, starterPackState, { likes, reposts, follows, labels, ctx });
|
|
961
1226
|
}
|
|
@@ -1144,6 +1409,10 @@ export class Hydrator {
|
|
|
1144
1409
|
return;
|
|
1145
1410
|
return ((await this.actor.getGermDeclarations([uri], includeTakedowns)).get(uri) ?? undefined);
|
|
1146
1411
|
}
|
|
1412
|
+
else if (collection === app.bsky.actor.link.$type) {
|
|
1413
|
+
return ((await this.actor.getProfileLinks([uri], includeTakedowns)).get(uri) ??
|
|
1414
|
+
undefined);
|
|
1415
|
+
}
|
|
1147
1416
|
else if (collection === app.bsky.notification.declaration.$type) {
|
|
1148
1417
|
if (parsed.rkey !== 'self')
|
|
1149
1418
|
return;
|
|
@@ -1352,6 +1621,24 @@ const pairsToMap = (pairs) => {
|
|
|
1352
1621
|
}
|
|
1353
1622
|
return map;
|
|
1354
1623
|
};
|
|
1624
|
+
/**
|
|
1625
|
+
* The link records a profile points to, in order. Only the first
|
|
1626
|
+
* MAX_PROFILE_LINKS are shown, and only links in the profile's own repo count.
|
|
1627
|
+
*/
|
|
1628
|
+
export const profileLinkUris = (dids, actors) => {
|
|
1629
|
+
const uris = [];
|
|
1630
|
+
for (const did of dids) {
|
|
1631
|
+
const refs = actors?.get(did)?.profile?.links ?? [];
|
|
1632
|
+
for (const ref of refs.slice(0, MAX_PROFILE_LINKS)) {
|
|
1633
|
+
const parsed = new AtUri(ref.uri);
|
|
1634
|
+
if (parsed.host === did &&
|
|
1635
|
+
parsed.collection === app.bsky.actor.link.$type) {
|
|
1636
|
+
uris.push(ref.uri);
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
return dedupeStrs(uris);
|
|
1641
|
+
};
|
|
1355
1642
|
export const mergeStates = (stateA, stateB) => {
|
|
1356
1643
|
assert(!stateA.ctx?.viewer ||
|
|
1357
1644
|
!stateB.ctx?.viewer ||
|
|
@@ -1385,6 +1672,7 @@ export const mergeStates = (stateA, stateB) => {
|
|
|
1385
1672
|
feedgenViewers: mergeMaps(stateA.feedgenViewers, stateB.feedgenViewers),
|
|
1386
1673
|
starterPacks: mergeMaps(stateA.starterPacks, stateB.starterPacks),
|
|
1387
1674
|
starterPackAggs: mergeMaps(stateA.starterPackAggs, stateB.starterPackAggs),
|
|
1675
|
+
profileLinks: mergeMaps(stateA.profileLinks, stateB.profileLinks),
|
|
1388
1676
|
labelers: mergeMaps(stateA.labelers, stateB.labelers),
|
|
1389
1677
|
labelerAggs: mergeMaps(stateA.labelerAggs, stateB.labelerAggs),
|
|
1390
1678
|
labelerViewers: mergeMaps(stateA.labelerViewers, stateB.labelerViewers),
|
|
@@ -1395,6 +1683,7 @@ export const mergeStates = (stateA, stateB) => {
|
|
|
1395
1683
|
verifications: mergeMaps(stateA.verifications, stateB.verifications),
|
|
1396
1684
|
bookmarks: mergeNestedMaps(stateA.bookmarks, stateB.bookmarks),
|
|
1397
1685
|
externalRecords: mergeMaps(stateA.externalRecords, stateB.externalRecords),
|
|
1686
|
+
externalRecordsByRef: mergeMaps(stateA.externalRecordsByRef, stateB.externalRecordsByRef),
|
|
1398
1687
|
externalRecordBacklinks: mergeMaps(stateA.externalRecordBacklinks, stateB.externalRecordBacklinks),
|
|
1399
1688
|
externalRecordBacklinkCounts: mergeMaps(stateA.externalRecordBacklinkCounts, stateB.externalRecordBacklinkCounts),
|
|
1400
1689
|
siteStandardDocuments: mergeMaps(stateA.siteStandardDocuments, stateB.siteStandardDocuments),
|
|
@@ -1411,6 +1700,20 @@ const actionTakedownLabels = (keys, hydrationMap, labels) => {
|
|
|
1411
1700
|
}
|
|
1412
1701
|
}
|
|
1413
1702
|
};
|
|
1703
|
+
/**
|
|
1704
|
+
* The URI when an externally authored reference is a complete DID-based
|
|
1705
|
+
* record URI in `collection`, with a record key and no fragment.
|
|
1706
|
+
*/
|
|
1707
|
+
const recordUriOf = (value, collection) => {
|
|
1708
|
+
const parsed = parseAtUriString(value);
|
|
1709
|
+
return parsed.success &&
|
|
1710
|
+
isDidString(parsed.value.authority) &&
|
|
1711
|
+
parsed.value.collection === collection &&
|
|
1712
|
+
parsed.value.rkey &&
|
|
1713
|
+
!parsed.value.hash
|
|
1714
|
+
? value
|
|
1715
|
+
: undefined;
|
|
1716
|
+
};
|
|
1414
1717
|
/**
|
|
1415
1718
|
* Apply takedown labels to the site.standard hydration maps. Per-record
|
|
1416
1719
|
* takedowns null any entry whose subject URI is taken down; pair takedowns
|
|
@@ -1465,4 +1768,18 @@ const actionSiteStandardTakedownLabels = (documents, publications, labels) => {
|
|
|
1465
1768
|
const uriToRef = (uri) => {
|
|
1466
1769
|
return { uri };
|
|
1467
1770
|
};
|
|
1771
|
+
/**
|
|
1772
|
+
* Drop refs whose lookup key is already in `seenRecordKeys`, and mark the rest
|
|
1773
|
+
* synchronously so concurrent work in the traversal cannot schedule them again.
|
|
1774
|
+
* Latest (bare URI) and exact (`uri@cid`) keys are distinct identities in the
|
|
1775
|
+
* shared set: neither implies nor suppresses the other, or another version.
|
|
1776
|
+
*/
|
|
1777
|
+
const takeUnseenRefs = (refs, seenRecordKeys) => refs.filter((ref) => {
|
|
1778
|
+
const key = genericRecordKey(ref);
|
|
1779
|
+
if (seenRecordKeys.has(key))
|
|
1780
|
+
return false;
|
|
1781
|
+
seenRecordKeys.add(key);
|
|
1782
|
+
return true;
|
|
1783
|
+
});
|
|
1784
|
+
const isExactRef = (ref) => ref.cid !== undefined;
|
|
1468
1785
|
//# sourceMappingURL=hydrator.js.map
|