@atproto/bsky 0.3.12 → 0.4.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 +43 -0
- package/dist/api/app/bsky/notification/getGroupedNotifications.d.ts.map +1 -1
- package/dist/api/app/bsky/notification/getGroupedNotifications.js +14 -3
- package/dist/api/app/bsky/notification/getGroupedNotifications.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/config.d.ts +2 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +7 -6
- package/dist/config.js.map +1 -1
- package/dist/hydration/external.d.ts +52 -45
- package/dist/hydration/external.d.ts.map +1 -1
- package/dist/hydration/external.js +132 -114
- package/dist/hydration/external.js.map +1 -1
- package/dist/hydration/hydrator.d.ts +10 -8
- package/dist/hydration/hydrator.d.ts.map +1 -1
- package/dist/hydration/hydrator.js +82 -189
- 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/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 +11 -4
- package/dist/telemetry/events.d.ts.map +1 -1
- package/dist/telemetry/events.js +29 -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/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 +11 -12
|
@@ -6,12 +6,11 @@ import { NOTIFICATION_REASON } from '../api/app/bsky/notification/constants.js';
|
|
|
6
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 {
|
|
10
|
-
import { uriToDid, uriToDid as didFromUri } from '../util/uris.js';
|
|
9
|
+
import { recordUriOf, uriToDid, uriToDid as didFromUri } from '../util/uris.js';
|
|
11
10
|
import { MAX_PROFILE_LINKS } from '../views/profile-link-rules.js';
|
|
12
|
-
import {
|
|
11
|
+
import { isListRuleType, isRecordEmbedType, isRecordWithMediaType, } from '../views/types.js';
|
|
13
12
|
import { ActorHydrator, } from './actor.js';
|
|
14
|
-
import { ExternalHydrator, actionExternalRecordTakedowns,
|
|
13
|
+
import { EXTERNAL_HYDRATION_LIMITS, ExternalHydrator, MAX_CONCURRENT_BACKLINK_REQUESTS, MAX_EXTERNAL_HYDRATION_PASSES, actionExternalRecordTakedowns, externalAssociatedRefsFromPosts, genericRecordKey, trackExternalHydration, } from './external.js';
|
|
15
14
|
import { FeedHydrator, } from './feed.js';
|
|
16
15
|
import { GraphHydrator, } from './graph.js';
|
|
17
16
|
import { LabelHydrator, Labels, } from './label.js';
|
|
@@ -424,13 +423,7 @@ export class Hydrator {
|
|
|
424
423
|
postUrisWithPostgates.add(uri);
|
|
425
424
|
}
|
|
426
425
|
}
|
|
427
|
-
const
|
|
428
|
-
const siteStandardLabelSubjects = dedupeStrs(siteStandardRefs.map((ref) => ref.uri));
|
|
429
|
-
// Site-standard record owners need profile basics so we can populate
|
|
430
|
-
// `associatedProfiles` on the external embed view. Include them in the
|
|
431
|
-
// post-author profile fetch below; any DIDs surfaced later by the
|
|
432
|
-
// dataplane are picked up by the top-up after the parallel batch.
|
|
433
|
-
const ssRefDids = siteStandardRefs.map((ref) => uriToDid(ref.uri));
|
|
426
|
+
const externalRefs = externalAssociatedRefsFromPosts(postsLayer0, postsLayer1, postsLayer2);
|
|
434
427
|
// Collect known-liker profiles and author-liker block relationships.
|
|
435
428
|
const knownLikers = await knownLikersPromise;
|
|
436
429
|
const knownLikerDids = [];
|
|
@@ -446,15 +439,14 @@ export class Hydrator {
|
|
|
446
439
|
});
|
|
447
440
|
const knownProfileDids = dedupeStrs([
|
|
448
441
|
...allPostUris.map(didFromUri),
|
|
449
|
-
...ssRefDids,
|
|
450
442
|
...knownLikerDids,
|
|
451
443
|
]);
|
|
452
|
-
const [postAggs, postViewers, labels, { postBlocks, additionalRelationshipsBidirectionalBlocks: authorsAndKnownLikersBidirectionalBlocks, }, profileState, listState, feedGenState, labelerState, starterPackState, postgates,
|
|
444
|
+
const [postAggs, postViewers, labels, { postBlocks, additionalRelationshipsBidirectionalBlocks: authorsAndKnownLikersBidirectionalBlocks, }, profileState, listState, feedGenState, labelerState, starterPackState, postgates, externalState,] = await Promise.all([
|
|
453
445
|
this.feed.getPostAggregates(allRefs, ctx.viewer),
|
|
454
446
|
ctx.viewer
|
|
455
447
|
? this.feed.getPostViewerStates(threadRefs, ctx.viewer)
|
|
456
448
|
: undefined,
|
|
457
|
-
this.label.getLabelsForSubjects(
|
|
449
|
+
this.label.getLabelsForSubjects(allPostUris, ctx.labelers),
|
|
458
450
|
this.hydratePostBlocks(posts, ctx, authorsAndKnownLikersRelationships),
|
|
459
451
|
this.hydrateProfiles(knownProfileDids, ctx),
|
|
460
452
|
this.hydrateLists([...nestedListUris, ...threadgateListUris], ctx),
|
|
@@ -462,29 +454,15 @@ export class Hydrator {
|
|
|
462
454
|
this.hydrateLabelers(nestedLabelerDids, ctx),
|
|
463
455
|
this.hydrateStarterPacksBasic(nestedStarterPackUris, ctx),
|
|
464
456
|
this.feed.getPostgatesForPosts([...postUrisWithPostgates.values()]),
|
|
465
|
-
this.
|
|
457
|
+
this.hydrateEmbedExternalViewFromRefs(externalRefs, ctx, {
|
|
458
|
+
hydration: 'basic',
|
|
459
|
+
}),
|
|
466
460
|
]);
|
|
467
461
|
if (!ctx.includeTakedowns) {
|
|
468
462
|
actionTakedownLabels(allPostUris, posts, labels);
|
|
469
|
-
actionSiteStandardTakedownLabels(siteStandardDocuments, siteStandardPublications, labels);
|
|
470
|
-
}
|
|
471
|
-
// Defensive top-up: in the unlikely case the dataplane returned a
|
|
472
|
-
// publication owned by a DID not present in the post-author or
|
|
473
|
-
// pinned-ref sets, fetch its profile serially and fold it in.
|
|
474
|
-
const knownProfileDidsSet = new Set(knownProfileDids);
|
|
475
|
-
const extraSsDids = [];
|
|
476
|
-
for (const key of siteStandardPublications.keys()) {
|
|
477
|
-
const did = uriToDid(parseGenericRecordKey(key).uri);
|
|
478
|
-
if (!knownProfileDidsSet.has(did)) {
|
|
479
|
-
knownProfileDidsSet.add(did);
|
|
480
|
-
extraSsDids.push(did);
|
|
481
|
-
}
|
|
482
463
|
}
|
|
483
|
-
const mergedProfileState = extraSsDids.length
|
|
484
|
-
? mergeStates(profileState, await this.hydrateProfilesBasic(extraSsDids, ctx))
|
|
485
|
-
: profileState;
|
|
486
464
|
// combine all hydration state
|
|
487
|
-
return mergeManyStates(
|
|
465
|
+
return mergeManyStates(externalState, profileState, listState, feedGenState, labelerState, starterPackState, {
|
|
488
466
|
posts,
|
|
489
467
|
postAggs,
|
|
490
468
|
postViewers,
|
|
@@ -494,8 +472,6 @@ export class Hydrator {
|
|
|
494
472
|
labels,
|
|
495
473
|
threadgates,
|
|
496
474
|
postgates,
|
|
497
|
-
siteStandardDocuments,
|
|
498
|
-
siteStandardPublications,
|
|
499
475
|
ctx,
|
|
500
476
|
});
|
|
501
477
|
}
|
|
@@ -626,20 +602,23 @@ export class Hydrator {
|
|
|
626
602
|
* generic batch within the traversal (the root batch is pass 1); nested
|
|
627
603
|
* batches continue the count. Nested passes share the caller's
|
|
628
604
|
* `hydrationMetrics` tracker; without one, this call owns the traversal and
|
|
629
|
-
* reports its totals.
|
|
605
|
+
* reports its totals. `options.hydration` (default `'detailed'`) applies to
|
|
606
|
+
* the whole traversal and is propagated to every nested pass.
|
|
630
607
|
*/
|
|
631
|
-
async hydrateEmbedExternalViewFromUris(uris, ctx,
|
|
632
|
-
|
|
608
|
+
async hydrateEmbedExternalViewFromUris(uris, ctx, options = {}) {
|
|
609
|
+
const { hydration = 'detailed', seenRecordKeys = new Set(), hydrationPass = 1, hydrationMetrics, } = options;
|
|
610
|
+
return trackExternalHydration('uris', ctx.xrpcEndpoint, hydrationMetrics, (hydrationMetrics) => this.hydrateEmbedExternalView(uris.map(uriToRef), ctx, hydration, seenRecordKeys, hydrationPass, hydrationMetrics));
|
|
633
611
|
}
|
|
634
612
|
/**
|
|
635
613
|
* Like `hydrateEmbedExternalViewFromUris`, but for exact record versions
|
|
636
614
|
* pinned by strong refs. Roots land only in `externalRecordsByRef`; their
|
|
637
615
|
* URI dependencies are hydrated as latest versions by the URI path.
|
|
638
616
|
*/
|
|
639
|
-
async hydrateEmbedExternalViewFromRefs(refs, ctx,
|
|
640
|
-
|
|
617
|
+
async hydrateEmbedExternalViewFromRefs(refs, ctx, options = {}) {
|
|
618
|
+
const { hydration = 'detailed', seenRecordKeys = new Set(), hydrationPass = 1, hydrationMetrics, } = options;
|
|
619
|
+
return trackExternalHydration('refs', ctx.xrpcEndpoint, hydrationMetrics, (hydrationMetrics) => this.hydrateEmbedExternalView(refs, ctx, hydration, seenRecordKeys, hydrationPass, hydrationMetrics));
|
|
641
620
|
}
|
|
642
|
-
async hydrateEmbedExternalView(refs, ctx, seenRecordKeys, hydrationPass, hydrationMetrics) {
|
|
621
|
+
async hydrateEmbedExternalView(refs, ctx, hydration, seenRecordKeys, hydrationPass, hydrationMetrics) {
|
|
643
622
|
// Filter at the entry point so each lookup runs at most once per shared
|
|
644
623
|
// traversal, even for callers that did not prefilter their input. Null or
|
|
645
624
|
// unavailable results stay seen as completed lookups. Nothing unseen means
|
|
@@ -699,7 +678,12 @@ export class Hydrator {
|
|
|
699
678
|
});
|
|
700
679
|
actionExternalRecordTakedowns(baseState, ctx.includeTakedowns);
|
|
701
680
|
// Discover and hydrate any additional view dependencies
|
|
702
|
-
const depsState = await this.hydrateExternalViewDependencies(baseState, items, ctx,
|
|
681
|
+
const depsState = await this.hydrateExternalViewDependencies(baseState, items, ctx, {
|
|
682
|
+
hydration,
|
|
683
|
+
seenRecordKeys,
|
|
684
|
+
hydrationPass,
|
|
685
|
+
hydrationMetrics,
|
|
686
|
+
});
|
|
703
687
|
// A supplemental latest lookup returning the same URI and CID is the same
|
|
704
688
|
// version, so it may replace an unavailable exact entry; other CIDs have
|
|
705
689
|
// other keys and never satisfy a pin.
|
|
@@ -718,13 +702,18 @@ export class Hydrator {
|
|
|
718
702
|
* `hydrationPass` is the number of the generic batch that produced `state`
|
|
719
703
|
* (1-based, default 1 for prehydrated state, which is treated as the root
|
|
720
704
|
* batch); the next nested batch is `hydrationPass + 1`. `hydrationMetrics`
|
|
721
|
-
* is shared as in `hydrateEmbedExternalViewFromUris`.
|
|
705
|
+
* is shared as in `hydrateEmbedExternalViewFromUris`. `options.hydration`
|
|
706
|
+
* (default `'detailed'`) decides, per collection, whether social-preview
|
|
707
|
+
* samples are scheduled; backlink counts and content dependencies are
|
|
708
|
+
* discovered at both levels.
|
|
722
709
|
*/
|
|
723
|
-
async hydrateExternalViewDependencies(state, refs, ctx,
|
|
724
|
-
|
|
710
|
+
async hydrateExternalViewDependencies(state, refs, ctx, options = {}) {
|
|
711
|
+
const { hydration = 'detailed', seenRecordKeys = new Set(), hydrationPass = 1, hydrationMetrics, } = options;
|
|
712
|
+
return trackExternalHydration('dependencies', ctx.xrpcEndpoint, hydrationMetrics, (hydrationMetrics) => this.discoverExternalViewDependencies(state, refs, ctx, hydration, seenRecordKeys, hydrationPass, hydrationMetrics));
|
|
725
713
|
}
|
|
726
|
-
async discoverExternalViewDependencies(state, refs, ctx, seenRecordKeys, hydrationPass, hydrationMetrics) {
|
|
727
|
-
//
|
|
714
|
+
async discoverExternalViewDependencies(state, refs, ctx, hydration, seenRecordKeys, hydrationPass, hydrationMetrics) {
|
|
715
|
+
// Social-preview backlink sources to sample per target, each with its own
|
|
716
|
+
// bound. A target with no samples still gets its aggregate count.
|
|
728
717
|
const targets = new Map();
|
|
729
718
|
const discoveredUris = new Set();
|
|
730
719
|
const rootBatch = !hydrationMetrics.rootsCounted;
|
|
@@ -756,7 +745,8 @@ export class Hydrator {
|
|
|
756
745
|
if (!parsed.success ||
|
|
757
746
|
!isDidString(parsed.value.authority) ||
|
|
758
747
|
!parsed.value.rkey ||
|
|
759
|
-
parsed.value.
|
|
748
|
+
parsed.value.query !== undefined ||
|
|
749
|
+
parsed.value.hash !== undefined) {
|
|
760
750
|
continue;
|
|
761
751
|
}
|
|
762
752
|
/*
|
|
@@ -765,24 +755,30 @@ export class Hydrator {
|
|
|
765
755
|
const { collection } = parsed.value;
|
|
766
756
|
if (collection === site.standard.document.$type &&
|
|
767
757
|
site.standard.document.$matches(record, { strict: false })) {
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
758
|
+
const limits = EXTERNAL_HYDRATION_LIMITS[site.standard.document.$type];
|
|
759
|
+
targets.set(uri, hydration === 'detailed'
|
|
760
|
+
? [
|
|
761
|
+
{
|
|
762
|
+
collection: site.standard.graph.recommend.$type,
|
|
763
|
+
limit: limits.recommendationPreviews,
|
|
764
|
+
},
|
|
765
|
+
]
|
|
766
|
+
: []);
|
|
774
767
|
const publicationUri = recordUriOf(record.site, site.standard.publication.$type);
|
|
775
768
|
if (publicationUri)
|
|
776
769
|
discoveredUris.add(publicationUri);
|
|
777
770
|
}
|
|
778
771
|
else if (collection === site.standard.publication.$type &&
|
|
779
772
|
site.standard.publication.$matches(record, { strict: false })) {
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
773
|
+
const limits = EXTERNAL_HYDRATION_LIMITS[site.standard.publication.$type];
|
|
774
|
+
targets.set(uri, hydration === 'detailed'
|
|
775
|
+
? [
|
|
776
|
+
{
|
|
777
|
+
collection: site.standard.graph.subscription.$type,
|
|
778
|
+
limit: limits.subscriberPreviews,
|
|
779
|
+
},
|
|
780
|
+
]
|
|
781
|
+
: []);
|
|
786
782
|
}
|
|
787
783
|
else if (collection === place.stream.livestream.$type &&
|
|
788
784
|
place.stream.livestream.$matches(record, { strict: false })) {
|
|
@@ -790,16 +786,20 @@ export class Hydrator {
|
|
|
790
786
|
}
|
|
791
787
|
else if (collection === social.grain.gallery.$type &&
|
|
792
788
|
social.grain.gallery.$matches(record, { strict: false })) {
|
|
793
|
-
|
|
794
|
-
|
|
789
|
+
const limits = EXTERNAL_HYDRATION_LIMITS[social.grain.gallery.$type];
|
|
790
|
+
// Membership items are content, sampled at every level.
|
|
791
|
+
const samples = [];
|
|
792
|
+
if (hydration === 'detailed') {
|
|
793
|
+
samples.push({
|
|
795
794
|
collection: social.grain.favorite.$type,
|
|
796
|
-
limit:
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
795
|
+
limit: limits.favoritePreviews,
|
|
796
|
+
});
|
|
797
|
+
}
|
|
798
|
+
samples.push({
|
|
799
|
+
collection: social.grain.gallery.item.$type,
|
|
800
|
+
limit: limits.items,
|
|
801
|
+
});
|
|
802
|
+
targets.set(uri, samples);
|
|
803
803
|
}
|
|
804
804
|
else if (collection === social.grain.gallery.item.$type &&
|
|
805
805
|
social.grain.gallery.item.$matches(record, { strict: false })) {
|
|
@@ -826,7 +826,7 @@ export class Hydrator {
|
|
|
826
826
|
}),
|
|
827
827
|
(async () => {
|
|
828
828
|
// @NOTE Bound fan-out as well as each sample; counts are independent.
|
|
829
|
-
for (const batch of chunkArray(queries,
|
|
829
|
+
for (const batch of chunkArray(queries, MAX_CONCURRENT_BACKLINK_REQUESTS)) {
|
|
830
830
|
await Promise.all(batch.map(async ({ uri, collection, limit }) => {
|
|
831
831
|
let page;
|
|
832
832
|
try {
|
|
@@ -838,9 +838,11 @@ export class Hydrator {
|
|
|
838
838
|
return;
|
|
839
839
|
}
|
|
840
840
|
const links = page.backlinks
|
|
841
|
-
.
|
|
842
|
-
|
|
843
|
-
|
|
841
|
+
.flatMap(({ uri }) => {
|
|
842
|
+
const link = recordUriOf(uri, collection);
|
|
843
|
+
return link ? [link] : [];
|
|
844
|
+
})
|
|
845
|
+
.slice(0, limit);
|
|
844
846
|
backlinks.set(uri, dedupeStrs([...(backlinks.get(uri) ?? []), ...links]));
|
|
845
847
|
links.forEach((link) => discoveredUris.add(link));
|
|
846
848
|
}));
|
|
@@ -852,20 +854,24 @@ export class Hydrator {
|
|
|
852
854
|
}
|
|
853
855
|
const newUris = [...discoveredUris].filter((uri) => !seenRecordKeys.has(genericRecordKey({ uri })));
|
|
854
856
|
let nested = {};
|
|
855
|
-
if (newUris.length &&
|
|
856
|
-
hydrationPass >= ExternalHydrator.MAX_EXTERNAL_HYDRATION_PASSES) {
|
|
857
|
+
if (newUris.length && hydrationPass >= MAX_EXTERNAL_HYDRATION_PASSES) {
|
|
857
858
|
// Keep the partial state. Skipped URIs are not marked seen: they were
|
|
858
859
|
// never fetched.
|
|
859
860
|
hydrationMetrics.capped = true;
|
|
860
861
|
hydrationLogger.warn({
|
|
861
862
|
hydrationPass,
|
|
862
|
-
maxPasses:
|
|
863
|
+
maxPasses: MAX_EXTERNAL_HYDRATION_PASSES,
|
|
863
864
|
skipped: newUris.length,
|
|
864
865
|
}, 'external hydration pass limit reached, skipping dependencies');
|
|
865
866
|
}
|
|
866
867
|
else if (newUris.length) {
|
|
867
868
|
try {
|
|
868
|
-
nested = await this.hydrateEmbedExternalViewFromUris(newUris, ctx,
|
|
869
|
+
nested = await this.hydrateEmbedExternalViewFromUris(newUris, ctx, {
|
|
870
|
+
hydration,
|
|
871
|
+
seenRecordKeys,
|
|
872
|
+
hydrationPass: hydrationPass + 1,
|
|
873
|
+
hydrationMetrics,
|
|
874
|
+
});
|
|
869
875
|
}
|
|
870
876
|
catch (err) {
|
|
871
877
|
// Skip the whole batch; partial child state could miss moderation.
|
|
@@ -1573,52 +1579,6 @@ const nestedRecordUris = (post) => {
|
|
|
1573
1579
|
}
|
|
1574
1580
|
return uris;
|
|
1575
1581
|
};
|
|
1576
|
-
/**
|
|
1577
|
-
* Returns every strongRef the post embedded as `external.associatedRefs`,
|
|
1578
|
-
* regardless of collection. Callers are responsible for filtering to NSIDs
|
|
1579
|
-
* they care about.
|
|
1580
|
-
*/
|
|
1581
|
-
const externalAssociatedRefs = (post) => {
|
|
1582
|
-
const embed = post?.embed;
|
|
1583
|
-
if (!embed)
|
|
1584
|
-
return [];
|
|
1585
|
-
if (isExternalEmbedType(embed)) {
|
|
1586
|
-
return embed.external.associatedRefs ?? [];
|
|
1587
|
-
}
|
|
1588
|
-
if (isRecordWithMediaType(embed) && isExternalEmbedType(embed.media)) {
|
|
1589
|
-
return embed.media.external.associatedRefs ?? [];
|
|
1590
|
-
}
|
|
1591
|
-
return [];
|
|
1592
|
-
};
|
|
1593
|
-
/**
|
|
1594
|
-
* Collects standard.site refs across all post layers, deduped by `uri+cid` so
|
|
1595
|
-
* the dataplane batch is minimal even if multiple posts in the layer reference
|
|
1596
|
-
* the same exact version of an SS record. The same `${uri}@${cid}` keys are
|
|
1597
|
-
* what `getSiteStandardRecordsByRef` uses on the resulting hydration maps,
|
|
1598
|
-
* which lets `lookupAssociatedSiteStandardRecords` do an O(1) version-exact
|
|
1599
|
-
* lookup at view time.
|
|
1600
|
-
*/
|
|
1601
|
-
const siteStandardRefsFromPosts = (...postLayers) => {
|
|
1602
|
-
const seen = new Set();
|
|
1603
|
-
const out = [];
|
|
1604
|
-
for (const layer of postLayers) {
|
|
1605
|
-
for (const item of layer.values()) {
|
|
1606
|
-
if (!item)
|
|
1607
|
-
continue;
|
|
1608
|
-
for (const ref of externalAssociatedRefs(item.record)) {
|
|
1609
|
-
if (!new AtUri(ref.uri).collection.startsWith(SITE_STANDARD_NSID_PREFIX)) {
|
|
1610
|
-
continue;
|
|
1611
|
-
}
|
|
1612
|
-
const key = `${ref.uri}@${ref.cid}`;
|
|
1613
|
-
if (seen.has(key))
|
|
1614
|
-
continue;
|
|
1615
|
-
seen.add(key);
|
|
1616
|
-
out.push({ uri: ref.uri, cid: ref.cid });
|
|
1617
|
-
}
|
|
1618
|
-
}
|
|
1619
|
-
}
|
|
1620
|
-
return out;
|
|
1621
|
-
};
|
|
1622
1582
|
const getListUrisFromThreadgates = (gates) => {
|
|
1623
1583
|
const uris = [];
|
|
1624
1584
|
for (const gate of gates.values()) {
|
|
@@ -1708,8 +1668,6 @@ export const mergeStates = (stateA, stateB) => {
|
|
|
1708
1668
|
externalRecordsByRef: mergeMaps(stateA.externalRecordsByRef, stateB.externalRecordsByRef),
|
|
1709
1669
|
externalRecordBacklinks: mergeMaps(stateA.externalRecordBacklinks, stateB.externalRecordBacklinks),
|
|
1710
1670
|
externalRecordBacklinkCounts: mergeMaps(stateA.externalRecordBacklinkCounts, stateB.externalRecordBacklinkCounts),
|
|
1711
|
-
siteStandardDocuments: mergeMaps(stateA.siteStandardDocuments, stateB.siteStandardDocuments),
|
|
1712
|
-
siteStandardPublications: mergeMaps(stateA.siteStandardPublications, stateB.siteStandardPublications),
|
|
1713
1671
|
};
|
|
1714
1672
|
};
|
|
1715
1673
|
export const mergeManyStates = (...states) => {
|
|
@@ -1722,71 +1680,6 @@ const actionTakedownLabels = (keys, hydrationMap, labels) => {
|
|
|
1722
1680
|
}
|
|
1723
1681
|
}
|
|
1724
1682
|
};
|
|
1725
|
-
/**
|
|
1726
|
-
* The URI when an externally authored reference is a complete DID-based
|
|
1727
|
-
* record URI in `collection`, with a record key and no fragment.
|
|
1728
|
-
*/
|
|
1729
|
-
const recordUriOf = (value, collection) => {
|
|
1730
|
-
const parsed = parseAtUriString(value);
|
|
1731
|
-
return parsed.success &&
|
|
1732
|
-
isDidString(parsed.value.authority) &&
|
|
1733
|
-
parsed.value.collection === collection &&
|
|
1734
|
-
parsed.value.rkey &&
|
|
1735
|
-
!parsed.value.hash
|
|
1736
|
-
? value
|
|
1737
|
-
: undefined;
|
|
1738
|
-
};
|
|
1739
|
-
/**
|
|
1740
|
-
* Apply takedown labels to the site.standard hydration maps. Per-record
|
|
1741
|
-
* takedowns null any entry whose subject URI is taken down; pair takedowns
|
|
1742
|
-
* propagate that null across doc/publication pairs so we never render half
|
|
1743
|
-
* of a moderated embed. Pairs are discovered via `doc.record.site`; orphan
|
|
1744
|
-
* docs and orphan publications are subject only to the per-record sweep.
|
|
1745
|
-
*/
|
|
1746
|
-
const actionSiteStandardTakedownLabels = (documents, publications, labels) => {
|
|
1747
|
-
// Pairings have to be captured before nulling — the doc record carries
|
|
1748
|
-
// the publication URI in its `site` field, and we lose it once we null.
|
|
1749
|
-
const pairings = [];
|
|
1750
|
-
if (documents.size > 0 && publications.size > 0) {
|
|
1751
|
-
const pubKeysByUri = new Map();
|
|
1752
|
-
for (const key of publications.keys()) {
|
|
1753
|
-
const { uri } = parseGenericRecordKey(key);
|
|
1754
|
-
const list = pubKeysByUri.get(uri);
|
|
1755
|
-
if (list)
|
|
1756
|
-
list.push(key);
|
|
1757
|
-
else
|
|
1758
|
-
pubKeysByUri.set(uri, [key]);
|
|
1759
|
-
}
|
|
1760
|
-
for (const [docKey, doc] of documents) {
|
|
1761
|
-
const site = doc?.record.site;
|
|
1762
|
-
if (!site || !site.startsWith('at://'))
|
|
1763
|
-
continue;
|
|
1764
|
-
for (const pubKey of pubKeysByUri.get(site) ?? []) {
|
|
1765
|
-
pairings.push({ docKey, pubKey });
|
|
1766
|
-
}
|
|
1767
|
-
}
|
|
1768
|
-
}
|
|
1769
|
-
// Per-record takedowns: null any entry whose subject URI is taken down.
|
|
1770
|
-
for (const key of documents.keys()) {
|
|
1771
|
-
const { uri } = parseGenericRecordKey(key);
|
|
1772
|
-
if (labels.get(uri)?.isTakendown)
|
|
1773
|
-
documents.set(key, null);
|
|
1774
|
-
}
|
|
1775
|
-
for (const key of publications.keys()) {
|
|
1776
|
-
const { uri } = parseGenericRecordKey(key);
|
|
1777
|
-
if (labels.get(uri)?.isTakendown)
|
|
1778
|
-
publications.set(key, null);
|
|
1779
|
-
}
|
|
1780
|
-
// Pair takedowns: if exactly one side of a pair was nulled above, mirror.
|
|
1781
|
-
for (const { docKey, pubKey } of pairings) {
|
|
1782
|
-
const doc = documents.get(docKey);
|
|
1783
|
-
const pub = publications.get(pubKey);
|
|
1784
|
-
if (doc === null && pub !== null)
|
|
1785
|
-
publications.set(pubKey, null);
|
|
1786
|
-
if (pub === null && doc !== null)
|
|
1787
|
-
documents.set(docKey, null);
|
|
1788
|
-
}
|
|
1789
|
-
};
|
|
1790
1683
|
const uriToRef = (uri) => {
|
|
1791
1684
|
return { uri };
|
|
1792
1685
|
};
|