@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.
Files changed (143) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/dist/api/app/bsky/actor/getProfile.js +1 -1
  3. package/dist/api/app/bsky/actor/getProfile.js.map +1 -1
  4. package/dist/api/app/bsky/actor/getProfiles.js +1 -1
  5. package/dist/api/app/bsky/actor/getProfiles.js.map +1 -1
  6. package/dist/api/app/bsky/actor/getSuggestions.js +2 -2
  7. package/dist/api/app/bsky/actor/getSuggestions.js.map +1 -1
  8. package/dist/api/app/bsky/embed/getEmbedExternalView.d.ts.map +1 -1
  9. package/dist/api/app/bsky/embed/getEmbedExternalView.js +132 -53
  10. package/dist/api/app/bsky/embed/getEmbedExternalView.js.map +1 -1
  11. package/dist/api/app/bsky/feed/getAuthorFeed.d.ts.map +1 -1
  12. package/dist/api/app/bsky/feed/getAuthorFeed.js +4 -2
  13. package/dist/api/app/bsky/feed/getAuthorFeed.js.map +1 -1
  14. package/dist/api/app/bsky/graph/getSuggestedFollowsByActor.js +2 -2
  15. package/dist/api/app/bsky/graph/getSuggestedFollowsByActor.js.map +1 -1
  16. package/dist/api/app/bsky/notification/constants.d.ts +4 -1
  17. package/dist/api/app/bsky/notification/constants.d.ts.map +1 -1
  18. package/dist/api/app/bsky/notification/constants.js +3 -0
  19. package/dist/api/app/bsky/notification/constants.js.map +1 -1
  20. package/dist/api/app/bsky/notification/getGroupedNotifications.d.ts +1 -1
  21. package/dist/api/app/bsky/notification/getGroupedNotifications.d.ts.map +1 -1
  22. package/dist/api/app/bsky/notification/getGroupedNotifications.js +44 -26
  23. package/dist/api/app/bsky/notification/getGroupedNotifications.js.map +1 -1
  24. package/dist/api/app/bsky/notification/grouping/spotlight.d.ts +1 -1
  25. package/dist/api/app/bsky/notification/grouping/spotlight.d.ts.map +1 -1
  26. package/dist/api/app/bsky/notification/grouping/spotlight.js +114 -80
  27. package/dist/api/app/bsky/notification/grouping/spotlight.js.map +1 -1
  28. package/dist/api/app/bsky/notification/listActivitySubscriptions.js +3 -2
  29. package/dist/api/app/bsky/notification/listActivitySubscriptions.js.map +1 -1
  30. package/dist/api/app/bsky/unspecced/getAtmosphereExploreTab.d.ts.map +1 -1
  31. package/dist/api/app/bsky/unspecced/getAtmosphereExploreTab.js +16 -148
  32. package/dist/api/app/bsky/unspecced/getAtmosphereExploreTab.js.map +1 -1
  33. package/dist/api/internal/bsky/actor/getProfiles.js +4 -1
  34. package/dist/api/internal/bsky/actor/getProfiles.js.map +1 -1
  35. package/dist/config.d.ts +10 -0
  36. package/dist/config.d.ts.map +1 -1
  37. package/dist/config.js +43 -0
  38. package/dist/config.js.map +1 -1
  39. package/dist/data-plane/server/indexing/index.d.ts +3 -1
  40. package/dist/data-plane/server/indexing/index.d.ts.map +1 -1
  41. package/dist/data-plane/server/indexing/index.js +2 -0
  42. package/dist/data-plane/server/indexing/index.js.map +1 -1
  43. package/dist/data-plane/server/indexing/plugins/profile-link.d.ts +8 -0
  44. package/dist/data-plane/server/indexing/plugins/profile-link.d.ts.map +1 -0
  45. package/dist/data-plane/server/indexing/plugins/profile-link.js +30 -0
  46. package/dist/data-plane/server/indexing/plugins/profile-link.js.map +1 -0
  47. package/dist/data-plane/server/routes/records.d.ts.map +1 -1
  48. package/dist/data-plane/server/routes/records.js +1 -0
  49. package/dist/data-plane/server/routes/records.js.map +1 -1
  50. package/dist/hydration/actor.d.ts +4 -1
  51. package/dist/hydration/actor.d.ts.map +1 -1
  52. package/dist/hydration/actor.js +11 -0
  53. package/dist/hydration/actor.js.map +1 -1
  54. package/dist/hydration/external.d.ts +67 -64
  55. package/dist/hydration/external.d.ts.map +1 -1
  56. package/dist/hydration/external.js +79 -133
  57. package/dist/hydration/external.js.map +1 -1
  58. package/dist/hydration/hydrator.d.ts +53 -14
  59. package/dist/hydration/hydrator.d.ts.map +1 -1
  60. package/dist/hydration/hydrator.js +422 -105
  61. package/dist/hydration/hydrator.js.map +1 -1
  62. package/dist/lexicons/app/bsky/actor/defs.defs.d.ts +25 -0
  63. package/dist/lexicons/app/bsky/actor/defs.defs.d.ts.map +1 -1
  64. package/dist/lexicons/app/bsky/actor/defs.defs.js +16 -0
  65. package/dist/lexicons/app/bsky/actor/defs.defs.js.map +1 -1
  66. package/dist/lexicons/app/bsky/actor/link.d.ts +3 -0
  67. package/dist/lexicons/app/bsky/actor/link.d.ts.map +1 -0
  68. package/dist/lexicons/app/bsky/actor/link.defs.d.ts +120 -0
  69. package/dist/lexicons/app/bsky/actor/link.defs.d.ts.map +1 -0
  70. package/dist/lexicons/app/bsky/actor/link.defs.js +39 -0
  71. package/dist/lexicons/app/bsky/actor/link.defs.js.map +1 -0
  72. package/dist/lexicons/app/bsky/actor/link.js +6 -0
  73. package/dist/lexicons/app/bsky/actor/link.js.map +1 -0
  74. package/dist/lexicons/app/bsky/actor/profile.defs.d.ts +14 -0
  75. package/dist/lexicons/app/bsky/actor/profile.defs.d.ts.map +1 -1
  76. package/dist/lexicons/app/bsky/actor/profile.defs.js +3 -0
  77. package/dist/lexicons/app/bsky/actor/profile.defs.js.map +1 -1
  78. package/dist/lexicons/app/bsky/actor.d.ts +1 -0
  79. package/dist/lexicons/app/bsky/actor.d.ts.map +1 -1
  80. package/dist/lexicons/app/bsky/actor.js +1 -0
  81. package/dist/lexicons/app/bsky/actor.js.map +1 -1
  82. package/dist/lexicons/app/bsky/authFullApp.defs.d.ts.map +1 -1
  83. package/dist/lexicons/app/bsky/authFullApp.defs.js +1 -0
  84. package/dist/lexicons/app/bsky/authFullApp.defs.js.map +1 -1
  85. package/dist/lexicons/app/bsky/authManageProfile.defs.d.ts.map +1 -1
  86. package/dist/lexicons/app/bsky/authManageProfile.defs.js +1 -0
  87. package/dist/lexicons/app/bsky/authManageProfile.defs.js.map +1 -1
  88. package/dist/lexicons/app/bsky/embed/getEmbedExternalView.defs.d.ts +13 -3
  89. package/dist/lexicons/app/bsky/embed/getEmbedExternalView.defs.d.ts.map +1 -1
  90. package/dist/lexicons/app/bsky/embed/getEmbedExternalView.defs.js +11 -3
  91. package/dist/lexicons/app/bsky/embed/getEmbedExternalView.defs.js.map +1 -1
  92. package/dist/lexicons/app/bsky/notification/getGroupedNotifications.defs.d.ts +62 -3
  93. package/dist/lexicons/app/bsky/notification/getGroupedNotifications.defs.d.ts.map +1 -1
  94. package/dist/lexicons/app/bsky/notification/getGroupedNotifications.defs.js +53 -2
  95. package/dist/lexicons/app/bsky/notification/getGroupedNotifications.defs.js.map +1 -1
  96. package/dist/lexicons/tools/ozone/inbox/defs.defs.d.ts +14 -1
  97. package/dist/lexicons/tools/ozone/inbox/defs.defs.d.ts.map +1 -1
  98. package/dist/lexicons/tools/ozone/inbox/defs.defs.js +6 -1
  99. package/dist/lexicons/tools/ozone/inbox/defs.defs.js.map +1 -1
  100. package/dist/lexicons/tools/ozone/inbox/getNotificationPreferences.defs.d.ts +1 -0
  101. package/dist/lexicons/tools/ozone/inbox/getNotificationPreferences.defs.d.ts.map +1 -1
  102. package/dist/lexicons/tools/ozone/inbox/getNotificationPreferences.defs.js +1 -0
  103. package/dist/lexicons/tools/ozone/inbox/getNotificationPreferences.defs.js.map +1 -1
  104. package/dist/lexicons/tools/ozone/inbox/putNotificationPreferences.defs.d.ts +7 -2
  105. package/dist/lexicons/tools/ozone/inbox/putNotificationPreferences.defs.d.ts.map +1 -1
  106. package/dist/lexicons/tools/ozone/inbox/putNotificationPreferences.defs.js +4 -1
  107. package/dist/lexicons/tools/ozone/inbox/putNotificationPreferences.defs.js.map +1 -1
  108. package/dist/proto/bsky_connect.d.ts +10 -1
  109. package/dist/proto/bsky_connect.d.ts.map +1 -1
  110. package/dist/proto/bsky_connect.js +10 -1
  111. package/dist/proto/bsky_connect.js.map +1 -1
  112. package/dist/proto/bsky_pb.d.ts +34 -0
  113. package/dist/proto/bsky_pb.d.ts.map +1 -1
  114. package/dist/proto/bsky_pb.js +60 -0
  115. package/dist/proto/bsky_pb.js.map +1 -1
  116. package/dist/telemetry/events.d.ts +35 -0
  117. package/dist/telemetry/events.d.ts.map +1 -1
  118. package/dist/telemetry/events.js +68 -0
  119. package/dist/telemetry/events.js.map +1 -1
  120. package/dist/util/external/index.d.ts +8 -0
  121. package/dist/util/external/index.d.ts.map +1 -0
  122. package/dist/util/external/index.js +50 -0
  123. package/dist/util/external/index.js.map +1 -0
  124. package/dist/util/{standard-site.d.ts → external/standard-site.d.ts} +30 -4
  125. package/dist/util/external/standard-site.d.ts.map +1 -0
  126. package/dist/util/{standard-site.js → external/standard-site.js} +73 -4
  127. package/dist/util/external/standard-site.js.map +1 -0
  128. package/dist/views/external.js +1 -1
  129. package/dist/views/external.js.map +1 -1
  130. package/dist/views/index.d.ts +11 -7
  131. package/dist/views/index.d.ts.map +1 -1
  132. package/dist/views/index.js +94 -43
  133. package/dist/views/index.js.map +1 -1
  134. package/dist/views/profile-link-rules.d.ts +10 -0
  135. package/dist/views/profile-link-rules.d.ts.map +1 -0
  136. package/dist/views/profile-link-rules.js +48 -0
  137. package/dist/views/profile-link-rules.js.map +1 -0
  138. package/dist/views/types.d.ts +2 -0
  139. package/dist/views/types.d.ts.map +1 -1
  140. package/dist/views/types.js.map +1 -1
  141. package/package.json +7 -7
  142. package/dist/util/standard-site.d.ts.map +0 -1
  143. package/dist/util/standard-site.js.map +0 -1
@@ -1,12 +1,15 @@
1
1
  import { type AtUriString, type DidString, type NsidString, type TypedLexMap } from '@atproto/lex';
2
2
  import type { DataPlaneClient } from '../data-plane/client/index.js';
3
+ import { type ExternalHydrationRoot, type ExternalHydrationWork } from '../telemetry/events.js';
3
4
  import type { SiteStandardDocumentRecord, SiteStandardPublicationRecord } from '../views/types.js';
5
+ import type { Actors } from './actor.js';
6
+ import type { Labels } from './label.js';
4
7
  import { HydrationMap, type ItemRef, type RecordInfo } from './util.js';
5
8
  /** Generic record bodies are decoded, but not validated against a Lexicon. */
6
9
  export type ExternalRecord = RecordInfo<TypedLexMap>;
7
10
  export type ExternalRecords = HydrationMap<AtUriString, ExternalRecord>;
8
- /** Keyed by `genericRecordKey(uri, cid)` to retain multiple versions of a URI. */
9
- export type ExternalRecordsByRef = HydrationMap<string, ExternalRecord>;
11
+ /** Keyed by `genericRecordKey({ uri, cid })` to retain versions of a URI. */
12
+ export type ExternalRecordsByRef = HydrationMap<ExactRecordKey, ExternalRecord>;
10
13
  export type ExternalRecordBacklinks = HydrationMap<AtUriString, AtUriString[]>;
11
14
  export type AtmosphereActivityItem = {
12
15
  uri: AtUriString;
@@ -25,17 +28,13 @@ export type AtmosphereBacklinksByActor = {
25
28
  backlinks: HydrationMap<AtUriString, AtUriString[]>;
26
29
  truncated: boolean;
27
30
  };
31
+ declare const exactRecordKeySymbol: unique symbol;
28
32
  /**
29
- * Composes a stable map key from an `(uri, cid)` pair. A single hydration
30
- * batch can pull more than one version of the same record URI (different
31
- * posts pinning different cids), so the composite is needed for O(1)
32
- * version-exact lookups.
33
+ * A version-exact `${uri}@${cid}` key, only produced by `genericRecordKey` for
34
+ * a ref with a CID, so it can be parsed back by `parseGenericRecordKey`.
33
35
  */
34
- export declare function genericRecordKey(uri: AtUriString, cid: string): string;
35
- /** Recover the URI and CID from a composite hydration key. */
36
- export declare function parseGenericRecordKey(key: string): {
37
- uri: AtUriString;
38
- cid: string;
36
+ export type ExactRecordKey = string & {
37
+ [exactRecordKeySymbol]: true;
39
38
  };
40
39
  export type SiteStandardDocument = RecordInfo<SiteStandardDocumentRecord>;
41
40
  export type SiteStandardPublication = RecordInfo<SiteStandardPublicationRecord>;
@@ -45,24 +44,22 @@ export type SiteStandardPublication = RecordInfo<SiteStandardPublicationRecord>;
45
44
  * pinning different cids), so the composite key is needed for O(1)
46
45
  * version-exact lookups.
47
46
  */
48
- export type SiteStandardDocuments = HydrationMap<string, SiteStandardDocument>;
47
+ export type SiteStandardDocuments = HydrationMap<ExactRecordKey, SiteStandardDocument>;
49
48
  /**
50
49
  * Keyed by `${uri}@${cid}`. See `SiteStandardDocuments` for the rationale.
51
50
  */
52
- export type SiteStandardPublications = HydrationMap<string, SiteStandardPublication>;
51
+ export type SiteStandardPublications = HydrationMap<ExactRecordKey, SiteStandardPublication>;
53
52
  export type SiteStandardRecords = {
54
53
  documents: SiteStandardDocuments;
55
54
  publications: SiteStandardPublications;
56
55
  };
57
- export type AssociatedSiteStandardRecord<T> = {
58
- ref: {
59
- uri: AtUriString;
60
- cid: string;
61
- };
62
- info: T;
63
- };
64
56
  export declare class ExternalHydrator {
65
57
  dataplane: DataPlaneClient;
58
+ static readonly MAX_BACKLINK_PREVIEWS = 3;
59
+ static readonly MAX_BACKLINK_GALLERY_ITEMS = 10;
60
+ static readonly MAX_BACKLINK_FANOUT = 8;
61
+ /** Hard cap on generic batches per traversal, counting the root as pass 1. */
62
+ static readonly MAX_EXTERNAL_HYDRATION_PASSES = 8;
66
63
  constructor(dataplane: DataPlaneClient);
67
64
  /** Fetch exact record versions; unavailable records are represented by null. */
68
65
  getRecordsByRef(refs: Required<ItemRef>[], includeTakedowns?: boolean): Promise<ExternalRecordsByRef>;
@@ -87,29 +84,62 @@ export declare class ExternalHydrator {
87
84
  * resolve a document's publication; callers must pin it explicitly.
88
85
  */
89
86
  getSiteStandardRecordsByRef(refs: ItemRef[], includeTakedowns?: boolean): Promise<SiteStandardRecords>;
90
- /**
91
- * Fetch the latest `site.standard.{document,publication}` versions. The
92
- * generic record lookup does not follow references, so publications named
93
- * by the `site` field of the hydrated documents are fetched in a second
94
- * call.
95
- */
96
- getSiteStandardRecordsByURI(uris: AtUriString[], includeTakedowns?: boolean): Promise<SiteStandardRecords>;
97
- private addSiteStandardRecordsByURI;
98
87
  }
88
+ /**
89
+ * Composes the lookup key for a ref: the bare URI for a latest-version lookup,
90
+ * or `${uri}@${cid}` for an exact version. A single hydration batch can pull
91
+ * several versions of one URI (different posts pinning different cids), so
92
+ * exact maps need the composite for O(1) version-exact lookups. The two forms
93
+ * never collide, so latest and exact lookups can share one traversal set.
94
+ *
95
+ * Throws on an empty CID rather than treating it as a latest lookup.
96
+ */
97
+ export declare function genericRecordKey(ref: Required<ItemRef>): ExactRecordKey;
98
+ export declare function genericRecordKey(ref: ItemRef): string;
99
+ /**
100
+ * Recover the URI and CID from an exact record key, splitting at the last `@`.
101
+ * Throws if either part is missing. Components are trusted, not revalidated.
102
+ */
103
+ export declare function parseGenericRecordKey(key: ExactRecordKey): Required<ItemRef>;
104
+ /**
105
+ * Null generic external records (in both the URI-keyed and exact-ref maps,
106
+ * keeping their keys) that are taken down by an actionable label, unless
107
+ * takedowns are included, or whose owner is explicitly unavailable. Applies to
108
+ * each record independently; counts and other records are unaffected.
109
+ */
110
+ export declare function actionExternalRecordTakedowns({ externalRecords, externalRecordsByRef, actors, labels, }: {
111
+ externalRecords?: ExternalRecords;
112
+ externalRecordsByRef?: ExternalRecordsByRef;
113
+ actors?: Actors;
114
+ labels?: Labels;
115
+ }, includeTakedowns?: boolean): void;
116
+ /**
117
+ * Generic lookup work of one external hydration traversal, shared by reference
118
+ * through its nested passes. `capped` records hitting the pass limit.
119
+ */
120
+ export type ExternalHydrationTracker = ExternalHydrationWork & {
121
+ capped: boolean;
122
+ };
123
+ /**
124
+ * Run `fn` with the caller's tracker, or as the owner of a new traversal when
125
+ * there is none. Only the owner reports, once, including on failure. Owners
126
+ * that completed without scheduling a batch did no work and are not reported;
127
+ * caps and failures always are.
128
+ */
129
+ export declare function trackExternalHydration<T>(root: ExternalHydrationRoot, hydrationMetrics: ExternalHydrationTracker | undefined, fn: (hydrationMetrics: ExternalHydrationTracker) => Promise<T>): Promise<T>;
130
+ export type AssociatedSiteStandardRecord<T> = {
131
+ ref: {
132
+ uri: AtUriString;
133
+ cid: string;
134
+ };
135
+ info: T;
136
+ };
99
137
  /**
100
138
  * Read-path resolution: caller pinned a set of `(uri, cid)` strongRefs in
101
139
  * the post at write time, and we honor those exact versions. Any pair
102
140
  * that doesn't structurally agree is rejected, so downstream callers can
103
141
  * trust the returned shape.
104
142
  *
105
- * Same observable contract as
106
- * `getSiteStandardRecordsFromHydrationMapsByDocumentUri` (the compose-path
107
- * sister) — both functions guarantee that a returned `(doc, pub)` pair
108
- * has matching site/uri, and reject orphan docs that claim a
109
- * non-hydrated publication. They differ only in how upstream availability
110
- * is asserted: this one trusts caller-supplied refs; the sister derives
111
- * everything from the dataplane response.
112
- *
113
143
  * Pairing rules:
114
144
  * - Both slots referenced: `doc.site` must equal `publication.ref.uri`,
115
145
  * else both come back `undefined`.
@@ -129,32 +159,5 @@ export declare const getSiteStandardRecordsFromHydrationMapsByRefs: (associatedR
129
159
  document: AssociatedSiteStandardRecord<SiteStandardDocument> | undefined;
130
160
  publication: AssociatedSiteStandardRecord<SiteStandardPublication> | undefined;
131
161
  };
132
- /**
133
- * Compose-path resolution: no caller-supplied refs — the dataplane
134
- * returned the latest version of each record (including any
135
- * publications it auto-resolved from a document's `site` field). Take
136
- * the first hydrated document and pair it with the publication its
137
- * `site` field points at.
138
- *
139
- * Same observable contract as
140
- * `getSiteStandardRecordsFromHydrationMapsByRefs` (the read-path
141
- * sister) — both functions guarantee that a returned `(doc, pub)` pair
142
- * has matching site/uri, and reject orphan docs that claim a
143
- * non-hydrated publication. They differ only in how upstream
144
- * availability is asserted: this one walks whatever was returned by the
145
- * dataplane; the sister checks against caller-supplied refs.
146
- *
147
- * Pairing rules:
148
- * - Doc with at-uri `site`: must find a hydrated publication at that
149
- * URI. If none was hydrated, the doc/publication chain is incomplete
150
- * and the function returns `undefined` for both slots; the doc alone
151
- * isn't useful without its source (can't verify the pub)
152
- * - Doc with web-URL `site` (loose): no publication.
153
- * - No doc hydrated: fall through to the first hydrated publication for
154
- * the publication-only resolution flow.
155
- */
156
- export declare const getSiteStandardRecordsFromHydrationMapsByDocumentUri: (documents: SiteStandardDocuments | undefined, publications: SiteStandardPublications | undefined) => {
157
- document: AssociatedSiteStandardRecord<SiteStandardDocument> | undefined;
158
- publication: AssociatedSiteStandardRecord<SiteStandardPublication> | undefined;
159
- };
162
+ export {};
160
163
  //# sourceMappingURL=external.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"external.d.ts","sourceRoot":"","sources":["../../src/hydration/external.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"external.d.ts","sourceRoot":"","sources":["../../src/hydration/external.ts"],"names":[],"mappings":"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"}
@@ -3,25 +3,15 @@ import { parseAtUriString } from '@atproto/syntax';
3
3
  import { site } from '../lexicons/index.js';
4
4
  import { hydrationLogger } from '../logger.js';
5
5
  import { RecordLookupStatus, } from '../proto/bsky_pb.js';
6
+ import { events, } from '../telemetry/events.js';
7
+ import { uriToDid } from '../util/uris.js';
6
8
  import { HydrationMap, parseDate, safeTakedownRef, } from './util.js';
7
- /**
8
- * Composes a stable map key from an `(uri, cid)` pair. A single hydration
9
- * batch can pull more than one version of the same record URI (different
10
- * posts pinning different cids), so the composite is needed for O(1)
11
- * version-exact lookups.
12
- */
13
- export function genericRecordKey(uri, cid) {
14
- return `${uri}@${cid}`;
15
- }
16
- /** Recover the URI and CID from a composite hydration key. */
17
- export function parseGenericRecordKey(key) {
18
- const at = key.lastIndexOf('@');
19
- return {
20
- uri: key.slice(0, at),
21
- cid: key.slice(at + 1),
22
- };
23
- }
24
9
  export class ExternalHydrator {
10
+ static { this.MAX_BACKLINK_PREVIEWS = 3; }
11
+ static { this.MAX_BACKLINK_GALLERY_ITEMS = 10; }
12
+ static { this.MAX_BACKLINK_FANOUT = 8; }
13
+ /** Hard cap on generic batches per traversal, counting the root as pass 1. */
14
+ static { this.MAX_EXTERNAL_HYDRATION_PASSES = 8; }
25
15
  constructor(dataplane) {
26
16
  this.dataplane = dataplane;
27
17
  }
@@ -32,8 +22,7 @@ export class ExternalHydrator {
32
22
  return map;
33
23
  const res = await this.dataplane.getRecordsByRef({ refs });
34
24
  for (let i = 0; i < refs.length; i++) {
35
- const { uri, cid } = refs[i];
36
- map.set(genericRecordKey(uri, cid), parseGenericRecord(res.results[i], includeTakedowns) ?? null);
25
+ map.set(genericRecordKey(refs[i]), parseGenericRecord(res.results[i], includeTakedowns) ?? null);
37
26
  }
38
27
  return map;
39
28
  }
@@ -124,38 +113,77 @@ export class ExternalHydrator {
124
113
  }
125
114
  return out;
126
115
  }
127
- /**
128
- * Fetch the latest `site.standard.{document,publication}` versions. The
129
- * generic record lookup does not follow references, so publications named
130
- * by the `site` field of the hydrated documents are fetched in a second
131
- * call.
132
- */
133
- async getSiteStandardRecordsByURI(uris, includeTakedowns = false) {
134
- const requested = [...new Set(uris.filter((u) => siteStandardKind(u)))];
135
- const out = emptySiteStandardRecords();
136
- if (!requested.length)
137
- return out;
138
- await this.addSiteStandardRecordsByURI(out, requested, includeTakedowns);
139
- // Publications referenced by documents but not already requested.
140
- const requestedSet = new Set(requested);
141
- const pubUris = new Set();
142
- for (const doc of out.documents.values()) {
143
- const pubUri = doc && publicationUriFromSite(doc.record.site);
144
- if (pubUri && !requestedSet.has(pubUri))
145
- pubUris.add(pubUri);
146
- }
147
- if (pubUris.size) {
148
- await this.addSiteStandardRecordsByURI(out, [...pubUris], includeTakedowns);
116
+ }
117
+ export function genericRecordKey({ uri, cid }) {
118
+ if (cid === undefined)
119
+ return uri;
120
+ if (!cid)
121
+ throw new Error(`Empty CID in record key for ${uri}`);
122
+ return `${uri}@${cid}`;
123
+ }
124
+ /**
125
+ * Recover the URI and CID from an exact record key, splitting at the last `@`.
126
+ * Throws if either part is missing. Components are trusted, not revalidated.
127
+ */
128
+ export function parseGenericRecordKey(key) {
129
+ const at = key.lastIndexOf('@');
130
+ if (at <= 0 || at === key.length - 1) {
131
+ throw new Error(`Malformed exact record key: ${key}`);
132
+ }
133
+ return {
134
+ uri: key.slice(0, at),
135
+ cid: key.slice(at + 1),
136
+ };
137
+ }
138
+ /**
139
+ * Null generic external records (in both the URI-keyed and exact-ref maps,
140
+ * keeping their keys) that are taken down by an actionable label, unless
141
+ * takedowns are included, or whose owner is explicitly unavailable. Applies to
142
+ * each record independently; counts and other records are unaffected.
143
+ */
144
+ export function actionExternalRecordTakedowns({ externalRecords, externalRecordsByRef, actors, labels, }, includeTakedowns) {
145
+ // An absent actor entry was not hydrated; only `null` means unavailable.
146
+ const isHidden = (uri) => actors?.get(uriToDid(uri)) === null ||
147
+ (!includeTakedowns && labels?.get(uri)?.isTakendown);
148
+ for (const [uri, info] of externalRecords ?? []) {
149
+ if (!info || !isHidden(uri))
150
+ continue;
151
+ externalRecords?.set(uri, null);
152
+ externalRecordsByRef?.set(genericRecordKey({ uri, cid: info.cid }), null);
153
+ }
154
+ // Every exact version is checked on its own; versions are never collapsed.
155
+ for (const [key, info] of externalRecordsByRef ?? []) {
156
+ if (info && isHidden(parseGenericRecordKey(key).uri)) {
157
+ externalRecordsByRef?.set(key, null);
149
158
  }
150
- return out;
151
159
  }
152
- async addSiteStandardRecordsByURI(out, uris, includeTakedowns) {
153
- const records = await this.getRecordsByURI(uris, includeTakedowns);
154
- for (const [uri, info] of records) {
155
- // Unavailable records have no CID to key on, so they are left out.
156
- if (!info)
157
- continue;
158
- setSiteStandardRecord(out, uri, genericRecordKey(uri, info.cid), info);
160
+ }
161
+ /**
162
+ * Run `fn` with the caller's tracker, or as the owner of a new traversal when
163
+ * there is none. Only the owner reports, once, including on failure. Owners
164
+ * that completed without scheduling a batch did no work and are not reported;
165
+ * caps and failures always are.
166
+ */
167
+ export async function trackExternalHydration(root, hydrationMetrics, fn) {
168
+ if (hydrationMetrics)
169
+ return fn(hydrationMetrics);
170
+ const owned = { recordLookups: 0, batches: 0, maxPass: 0, capped: false };
171
+ let outcome = 'failed';
172
+ try {
173
+ const state = await fn(owned);
174
+ outcome = owned.capped ? 'capped' : 'completed';
175
+ return state;
176
+ }
177
+ finally {
178
+ if (owned.batches || outcome !== 'completed') {
179
+ const { recordLookups, batches, maxPass } = owned;
180
+ events.externalHydrationTraversal({
181
+ root,
182
+ outcome,
183
+ recordLookups,
184
+ batches,
185
+ maxPass,
186
+ });
159
187
  }
160
188
  }
161
189
  }
@@ -203,14 +231,6 @@ const siteStandardKind = (uri) => {
203
231
  return undefined;
204
232
  }
205
233
  };
206
- /** The AT-URI of the publication a document's `site` field points at. */
207
- const publicationUriFromSite = (value) => {
208
- const parsed = parseAtUriString(value);
209
- return parsed.success &&
210
- parsed.value.collection === site.standard.publication.$type
211
- ? value
212
- : undefined;
213
- };
214
234
  /**
215
235
  * Generic record bodies are unvalidated, so check them against the Lexicon
216
236
  * before exposing typed fields. Parse mode is not used, to preserve the
@@ -239,14 +259,6 @@ const setSiteStandardRecord = (out, uri, key, info) => {
239
259
  * that doesn't structurally agree is rejected, so downstream callers can
240
260
  * trust the returned shape.
241
261
  *
242
- * Same observable contract as
243
- * `getSiteStandardRecordsFromHydrationMapsByDocumentUri` (the compose-path
244
- * sister) — both functions guarantee that a returned `(doc, pub)` pair
245
- * has matching site/uri, and reject orphan docs that claim a
246
- * non-hydrated publication. They differ only in how upstream availability
247
- * is asserted: this one trusts caller-supplied refs; the sister derives
248
- * everything from the dataplane response.
249
- *
250
262
  * Pairing rules:
251
263
  * - Both slots referenced: `doc.site` must equal `publication.ref.uri`,
252
264
  * else both come back `undefined`.
@@ -268,7 +280,7 @@ export const getSiteStandardRecordsFromHydrationMapsByRefs = (associatedRefs, do
268
280
  let document;
269
281
  let publication;
270
282
  for (const ref of associatedRefs) {
271
- const key = genericRecordKey(ref.uri, ref.cid);
283
+ const key = genericRecordKey(ref);
272
284
  if (!document) {
273
285
  const hit = documents?.get(key);
274
286
  if (hit)
@@ -296,8 +308,7 @@ export const getSiteStandardRecordsFromHydrationMapsByRefs = (associatedRefs, do
296
308
  }
297
309
  }
298
310
  // Doc with at-uri `site` but no publication: the post should have
299
- // pinned the publication too. Treat as misconstructed — same contract
300
- // as the compose-path lookup.
311
+ // pinned the publication too. Treat as misconstructed.
301
312
  if (document && !publication) {
302
313
  const site = document.info.record.site;
303
314
  if (site && site.startsWith('at://')) {
@@ -307,69 +318,4 @@ export const getSiteStandardRecordsFromHydrationMapsByRefs = (associatedRefs, do
307
318
  }
308
319
  return { document, publication };
309
320
  };
310
- /**
311
- * Compose-path resolution: no caller-supplied refs — the dataplane
312
- * returned the latest version of each record (including any
313
- * publications it auto-resolved from a document's `site` field). Take
314
- * the first hydrated document and pair it with the publication its
315
- * `site` field points at.
316
- *
317
- * Same observable contract as
318
- * `getSiteStandardRecordsFromHydrationMapsByRefs` (the read-path
319
- * sister) — both functions guarantee that a returned `(doc, pub)` pair
320
- * has matching site/uri, and reject orphan docs that claim a
321
- * non-hydrated publication. They differ only in how upstream
322
- * availability is asserted: this one walks whatever was returned by the
323
- * dataplane; the sister checks against caller-supplied refs.
324
- *
325
- * Pairing rules:
326
- * - Doc with at-uri `site`: must find a hydrated publication at that
327
- * URI. If none was hydrated, the doc/publication chain is incomplete
328
- * and the function returns `undefined` for both slots; the doc alone
329
- * isn't useful without its source (can't verify the pub)
330
- * - Doc with web-URL `site` (loose): no publication.
331
- * - No doc hydrated: fall through to the first hydrated publication for
332
- * the publication-only resolution flow.
333
- */
334
- export const getSiteStandardRecordsFromHydrationMapsByDocumentUri = (documents, publications) => {
335
- // First hydrated doc.
336
- let document;
337
- for (const [key, info] of documents ?? []) {
338
- if (!info)
339
- continue;
340
- document = { ref: parseGenericRecordKey(key), info };
341
- break;
342
- }
343
- let publication;
344
- if (document) {
345
- const site = document.info.record.site;
346
- if (site && site.startsWith('at://')) {
347
- // Doc declared an at-uri publication; we need it.
348
- for (const [key, info] of publications ?? []) {
349
- if (!info)
350
- continue;
351
- const ref = parseGenericRecordKey(key);
352
- if (ref.uri === site) {
353
- publication = { ref, info };
354
- break;
355
- }
356
- }
357
- if (!publication) {
358
- hydrationLogger.warn({ documentUri: document.ref.uri, documentSite: site }, 'site.standard byDocumentUri lookup failed: document.site is AT URI but no matching publication was hydrated');
359
- return { document: undefined, publication: undefined };
360
- }
361
- }
362
- // else: loose doc (web-URL site), no publication needed.
363
- }
364
- else {
365
- // Publication-only flow: no doc, take the first hydrated publication.
366
- for (const [key, info] of publications ?? []) {
367
- if (!info)
368
- continue;
369
- publication = { ref: parseGenericRecordKey(key), info };
370
- break;
371
- }
372
- }
373
- return { document, publication };
374
- };
375
321
  //# sourceMappingURL=external.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"external.js","sourceRoot":"","sources":["../../src/hydration/external.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type AtUriString,\n type DidString,\n type InferInput,\n type NsidString,\n type RecordSchema,\n type TypedLexMap,\n isTypedLexMap,\n lexParseJsonBytes,\n} from '@atproto/lex'\nimport { parseAtUriString } from '@atproto/syntax'\nimport type { DataPlaneClient } from '../data-plane/client/index.js'\nimport { site } from '../lexicons/index.js'\nimport { hydrationLogger } from '../logger.js'\nimport {\n type RecordLookupResult,\n RecordLookupStatus,\n} from '../proto/bsky_pb.js'\nimport type {\n SiteStandardDocumentRecord,\n SiteStandardPublicationRecord,\n} from '../views/types.js'\nimport {\n HydrationMap,\n type ItemRef,\n type RecordInfo,\n parseDate,\n safeTakedownRef,\n} from './util.js'\n\n/** Generic record bodies are decoded, but not validated against a Lexicon. */\nexport type ExternalRecord = RecordInfo<TypedLexMap>\nexport type ExternalRecords = HydrationMap<AtUriString, ExternalRecord>\n/** Keyed by `genericRecordKey(uri, cid)` to retain multiple versions of a URI. */\nexport type ExternalRecordsByRef = HydrationMap<string, ExternalRecord>\nexport type ExternalRecordBacklinks = HydrationMap<AtUriString, AtUriString[]>\n\nexport type AtmosphereActivityItem = {\n uri: AtUriString\n sortedAt: Date\n}\n\nexport type AtmosphereTimeline = {\n items: AtmosphereActivityItem[]\n cursor?: string\n}\n\nexport type AtmosphereBacklinks = {\n backlinks: AtmosphereActivityItem[]\n cursor?: string\n}\n\nexport type ExternalRecordBacklinkCounts = HydrationMap<\n AtUriString,\n Partial<Record<NsidString, number>>\n>\n\nexport type AtmosphereBacklinksByActor = {\n backlinks: HydrationMap<AtUriString, AtUriString[]>\n truncated: boolean\n}\n\n/**\n * Composes a stable map key from an `(uri, cid)` pair. A single hydration\n * batch can pull more than one version of the same record URI (different\n * posts pinning different cids), so the composite is needed for O(1)\n * version-exact lookups.\n */\nexport function genericRecordKey(uri: AtUriString, cid: string): string {\n return `${uri}@${cid}`\n}\n\n/** Recover the URI and CID from a composite hydration key. */\nexport function parseGenericRecordKey(key: string): {\n uri: AtUriString\n cid: string\n} {\n const at = key.lastIndexOf('@')\n return {\n uri: key.slice(0, at) as AtUriString,\n cid: key.slice(at + 1),\n }\n}\n\nexport type SiteStandardDocument = RecordInfo<SiteStandardDocumentRecord>\nexport type SiteStandardPublication = RecordInfo<SiteStandardPublicationRecord>\n\n/**\n * Keyed by `${uri}@${cid}` — see `genericRecordKey`. A single hydration\n * batch can pull more than one version of the same URI (different posts\n * pinning different cids), so the composite key is needed for O(1)\n * version-exact lookups.\n */\nexport type SiteStandardDocuments = HydrationMap<string, SiteStandardDocument>\n/**\n * Keyed by `${uri}@${cid}`. See `SiteStandardDocuments` for the rationale.\n */\nexport type SiteStandardPublications = HydrationMap<\n string,\n SiteStandardPublication\n>\nexport type SiteStandardRecords = {\n documents: SiteStandardDocuments\n publications: SiteStandardPublications\n}\n\nexport type AssociatedSiteStandardRecord<T> = {\n ref: { uri: AtUriString; cid: string }\n info: T\n}\n\nexport class ExternalHydrator {\n constructor(public dataplane: DataPlaneClient) {}\n\n /** Fetch exact record versions; unavailable records are represented by null. */\n async getRecordsByRef(\n refs: Required<ItemRef>[],\n includeTakedowns = false,\n ): Promise<ExternalRecordsByRef> {\n const map: ExternalRecordsByRef = new HydrationMap()\n if (!refs.length) return map\n\n const res = await this.dataplane.getRecordsByRef({ refs })\n for (let i = 0; i < refs.length; i++) {\n const { uri, cid } = refs[i]\n map.set(\n genericRecordKey(uri, cid),\n parseGenericRecord(res.results[i], includeTakedowns) ?? null,\n )\n }\n return map\n }\n\n /** Fetch the latest indexed versions without collection-specific validation. */\n async getRecordsByURI(\n uris: AtUriString[],\n includeTakedowns = false,\n ): Promise<ExternalRecords> {\n const map: ExternalRecords = new HydrationMap()\n if (!uris.length) return map\n\n const res = await this.dataplane.getRecordsByURI({ uris })\n for (let i = 0; i < uris.length; i++) {\n map.set(\n uris[i],\n parseGenericRecord(res.results[i], includeTakedowns) ?? null,\n )\n }\n return map\n }\n\n /** Fetch a page of activity refs for subsequent generic record hydration. */\n async getAtmosphereTimeline(\n viewerDid: DidString,\n opts: { limit?: number; cursor?: string } = {},\n ): Promise<AtmosphereTimeline> {\n const res = await this.dataplane.getAtmosphereTimeline({\n viewerDid,\n ...opts,\n })\n return {\n items: res.items.map((item) => ({\n uri: item.uri as AtUriString,\n sortedAt: parseDate(item.sortedAt) ?? new Date(0),\n })),\n cursor: res.cursor || undefined,\n }\n }\n\n /** Fetch source-record counts, optionally restricted to one collection. */\n async getAtmosphereBacklinkCounts(\n targetUris: AtUriString[],\n collection?: NsidString,\n ): Promise<ExternalRecordBacklinkCounts> {\n const map: ExternalRecordBacklinkCounts = new HydrationMap()\n if (!targetUris.length) return map\n\n const res = await this.dataplane.getAtmosphereBacklinkCounts({\n targetUris,\n collection,\n })\n for (const result of res.results) {\n map.set(\n result.targetUri as AtUriString,\n Object.fromEntries(\n Object.entries(result.counts).map(([nsid, count]) => [\n nsid,\n Number(count),\n ]),\n ),\n )\n }\n return map\n }\n\n /** Fetch a page of source records linking to a target. */\n async getAtmosphereBacklinks(\n targetUri: AtUriString,\n collection: NsidString,\n opts: { limit?: number; cursor?: string } = {},\n ): Promise<AtmosphereBacklinks> {\n const res = await this.dataplane.getAtmosphereBacklinks({\n targetUri,\n collection,\n ...opts,\n })\n return {\n backlinks: res.backlinks.map((backlink) => ({\n uri: backlink.uri as AtUriString,\n sortedAt: parseDate(backlink.sortedAt) ?? new Date(0),\n })),\n cursor: res.cursor || undefined,\n }\n }\n\n /** Fetch an actor's links per target, retaining the batch truncation flag. */\n async getAtmosphereBacklinksByActor(\n targetUris: AtUriString[],\n actorDid: DidString,\n collection: NsidString,\n ): Promise<AtmosphereBacklinksByActor> {\n const backlinks: AtmosphereBacklinksByActor['backlinks'] =\n new HydrationMap()\n if (!targetUris.length) return { backlinks, truncated: false }\n\n const res = await this.dataplane.getAtmosphereBacklinksByActor({\n targetUris,\n actorDid,\n collection,\n })\n for (const result of res.results) {\n backlinks.set(\n result.targetUri as AtUriString,\n result.uris as AtUriString[],\n )\n }\n return { backlinks, truncated: res.truncated }\n }\n\n /**\n * Fetch exact `site.standard.{document,publication}` versions. Does NOT\n * resolve a document's publication; callers must pin it explicitly.\n */\n async getSiteStandardRecordsByRef(\n refs: ItemRef[],\n includeTakedowns = false,\n ): Promise<SiteStandardRecords> {\n const ssRefs = refs.filter(\n (ref): ref is Required<ItemRef> =>\n !!ref.cid && siteStandardKind(ref.uri) !== undefined,\n )\n const out = emptySiteStandardRecords()\n if (!ssRefs.length) return out\n\n const records = await this.getRecordsByRef(ssRefs, includeTakedowns)\n for (const [key, info] of records) {\n setSiteStandardRecord(out, parseGenericRecordKey(key).uri, key, info)\n }\n return out\n }\n\n /**\n * Fetch the latest `site.standard.{document,publication}` versions. The\n * generic record lookup does not follow references, so publications named\n * by the `site` field of the hydrated documents are fetched in a second\n * call.\n */\n async getSiteStandardRecordsByURI(\n uris: AtUriString[],\n includeTakedowns = false,\n ): Promise<SiteStandardRecords> {\n const requested = [...new Set(uris.filter((u) => siteStandardKind(u)))]\n const out = emptySiteStandardRecords()\n if (!requested.length) return out\n\n await this.addSiteStandardRecordsByURI(out, requested, includeTakedowns)\n\n // Publications referenced by documents but not already requested.\n const requestedSet = new Set<string>(requested)\n const pubUris = new Set<AtUriString>()\n for (const doc of out.documents.values()) {\n const pubUri = doc && publicationUriFromSite(doc.record.site)\n if (pubUri && !requestedSet.has(pubUri)) pubUris.add(pubUri)\n }\n if (pubUris.size) {\n await this.addSiteStandardRecordsByURI(\n out,\n [...pubUris],\n includeTakedowns,\n )\n }\n return out\n }\n\n private async addSiteStandardRecordsByURI(\n out: SiteStandardRecords,\n uris: AtUriString[],\n includeTakedowns: boolean,\n ) {\n const records = await this.getRecordsByURI(uris, includeTakedowns)\n for (const [uri, info] of records) {\n // Unavailable records have no CID to key on, so they are left out.\n if (!info) continue\n setSiteStandardRecord(out, uri, genericRecordKey(uri, info.cid), info)\n }\n }\n}\n\nfunction parseGenericRecord(\n result: RecordLookupResult | undefined,\n includeTakedowns: boolean,\n): ExternalRecord | undefined {\n if (\n result?.status !== RecordLookupStatus.FOUND &&\n !(includeTakedowns && result?.status === RecordLookupStatus.TAKEN_DOWN)\n ) {\n return undefined\n }\n const entry = result.record\n if (!entry?.cid || !entry.record.byteLength) return undefined\n if (!includeTakedowns && entry.takenDown) return undefined\n\n let record: ReturnType<typeof lexParseJsonBytes>\n try {\n record = lexParseJsonBytes(entry.record, { strict: false })\n } catch {\n return undefined\n }\n if (!isTypedLexMap(record)) return undefined\n\n return {\n record,\n cid: entry.cid,\n sortedAt: parseDate(entry.sortedAt) ?? new Date(0),\n indexedAt: parseDate(entry.indexedAt) ?? new Date(0),\n takedownRef: safeTakedownRef(entry),\n }\n}\n\nconst emptySiteStandardRecords = (): SiteStandardRecords => ({\n documents: new HydrationMap(),\n publications: new HydrationMap(),\n})\n\nconst siteStandardKind = (\n uri: string,\n): keyof SiteStandardRecords | undefined => {\n const parsed = parseAtUriString(uri)\n if (!parsed.success) return undefined\n switch (parsed.value.collection) {\n case site.standard.document.$type:\n return 'documents'\n case site.standard.publication.$type:\n return 'publications'\n default:\n return undefined\n }\n}\n\n/** The AT-URI of the publication a document's `site` field points at. */\nconst publicationUriFromSite = (value: string): AtUriString | undefined => {\n const parsed = parseAtUriString(value)\n return parsed.success &&\n parsed.value.collection === site.standard.publication.$type\n ? (value as AtUriString)\n : undefined\n}\n\n/**\n * Generic record bodies are unvalidated, so check them against the Lexicon\n * before exposing typed fields. Parse mode is not used, to preserve the\n * stored record as-is.\n */\nconst matchRecordInfo = <TSchema extends RecordSchema>(\n schema: TSchema,\n info: ExternalRecord | null | undefined,\n): RecordInfo<InferInput<TSchema>> | null =>\n info && schema.$matches(info.record, { strict: false })\n ? { ...info, record: info.record }\n : null\n\n/**\n * Store a generic record in the map matching its collection. Records failing\n * Lexicon validation are stored as null.\n */\nconst setSiteStandardRecord = (\n out: SiteStandardRecords,\n uri: string,\n key: string,\n info: ExternalRecord | null | undefined,\n) => {\n switch (siteStandardKind(uri)) {\n case 'documents':\n out.documents.set(key, matchRecordInfo(site.standard.document.main, info))\n break\n case 'publications':\n out.publications.set(\n key,\n matchRecordInfo(site.standard.publication.main, info),\n )\n break\n }\n}\n\n/**\n * Read-path resolution: caller pinned a set of `(uri, cid)` strongRefs in\n * the post at write time, and we honor those exact versions. Any pair\n * that doesn't structurally agree is rejected, so downstream callers can\n * trust the returned shape.\n *\n * Same observable contract as\n * `getSiteStandardRecordsFromHydrationMapsByDocumentUri` (the compose-path\n * sister) — both functions guarantee that a returned `(doc, pub)` pair\n * has matching site/uri, and reject orphan docs that claim a\n * non-hydrated publication. They differ only in how upstream availability\n * is asserted: this one trusts caller-supplied refs; the sister derives\n * everything from the dataplane response.\n *\n * Pairing rules:\n * - Both slots referenced: `doc.site` must equal `publication.ref.uri`,\n * else both come back `undefined`.\n * - Doc with at-uri `site` referenced but no matching publication ref:\n * reject the whole pairing (doc claims a publication that should have\n * been pinned, but wasn't).\n * - Loose doc (web-URL `site`) referenced: publication stays `undefined`.\n * - Only a publication referenced: document stays `undefined`.\n *\n * Each returned slot carries the matching `ref` so callers can recover\n * the owner DID for blob-cdn URL building, etc.\n */\nexport const getSiteStandardRecordsFromHydrationMapsByRefs = (\n associatedRefs: readonly { uri: AtUriString; cid: string }[] | undefined,\n documents: SiteStandardDocuments | undefined,\n publications: SiteStandardPublications | undefined,\n): {\n document: AssociatedSiteStandardRecord<SiteStandardDocument> | undefined\n publication: AssociatedSiteStandardRecord<SiteStandardPublication> | undefined\n} => {\n if (!associatedRefs?.length) {\n return { document: undefined, publication: undefined }\n }\n\n // Resolve each ref against the hydration maps, taking the first hit on\n // each side.\n let document: AssociatedSiteStandardRecord<SiteStandardDocument> | undefined\n let publication:\n AssociatedSiteStandardRecord<SiteStandardPublication> | undefined\n for (const ref of associatedRefs) {\n const key = genericRecordKey(ref.uri, ref.cid)\n if (!document) {\n const hit = documents?.get(key)\n if (hit) document = { ref, info: hit }\n }\n if (!publication) {\n const hit = publications?.get(key)\n if (hit) publication = { ref, info: hit }\n }\n if (document && publication) break\n }\n\n // Both refs resolved: enforce that the doc's `site` actually points at\n // the supplied publication. Mismatch means the post was misconstructed\n // (or tampered with), so reject the whole pairing.\n if (document && publication) {\n if (document.info.record.site !== publication.ref.uri) {\n hydrationLogger.warn(\n {\n documentUri: document.ref.uri,\n documentSite: document.info.record.site,\n publicationUri: publication.ref.uri,\n },\n 'site.standard byRefs lookup failed: doc.site does not match hydrated publication.uri',\n )\n return { document: undefined, publication: undefined }\n }\n }\n\n // Doc with at-uri `site` but no publication: the post should have\n // pinned the publication too. Treat as misconstructed — same contract\n // as the compose-path lookup.\n if (document && !publication) {\n const site = document.info.record.site\n if (site && site.startsWith('at://')) {\n hydrationLogger.warn(\n { documentUri: document.ref.uri, documentSite: site },\n 'site.standard byRefs lookup failed: document.site is AT URI but no matching publication was hydrated',\n )\n return { document: undefined, publication: undefined }\n }\n }\n\n return { document, publication }\n}\n\n/**\n * Compose-path resolution: no caller-supplied refs — the dataplane\n * returned the latest version of each record (including any\n * publications it auto-resolved from a document's `site` field). Take\n * the first hydrated document and pair it with the publication its\n * `site` field points at.\n *\n * Same observable contract as\n * `getSiteStandardRecordsFromHydrationMapsByRefs` (the read-path\n * sister) — both functions guarantee that a returned `(doc, pub)` pair\n * has matching site/uri, and reject orphan docs that claim a\n * non-hydrated publication. They differ only in how upstream\n * availability is asserted: this one walks whatever was returned by the\n * dataplane; the sister checks against caller-supplied refs.\n *\n * Pairing rules:\n * - Doc with at-uri `site`: must find a hydrated publication at that\n * URI. If none was hydrated, the doc/publication chain is incomplete\n * and the function returns `undefined` for both slots; the doc alone\n * isn't useful without its source (can't verify the pub)\n * - Doc with web-URL `site` (loose): no publication.\n * - No doc hydrated: fall through to the first hydrated publication for\n * the publication-only resolution flow.\n */\nexport const getSiteStandardRecordsFromHydrationMapsByDocumentUri = (\n documents: SiteStandardDocuments | undefined,\n publications: SiteStandardPublications | undefined,\n): {\n document: AssociatedSiteStandardRecord<SiteStandardDocument> | undefined\n publication: AssociatedSiteStandardRecord<SiteStandardPublication> | undefined\n} => {\n // First hydrated doc.\n let document: AssociatedSiteStandardRecord<SiteStandardDocument> | undefined\n for (const [key, info] of documents ?? []) {\n if (!info) continue\n document = { ref: parseGenericRecordKey(key), info }\n break\n }\n\n let publication:\n AssociatedSiteStandardRecord<SiteStandardPublication> | undefined\n if (document) {\n const site = document.info.record.site\n if (site && site.startsWith('at://')) {\n // Doc declared an at-uri publication; we need it.\n for (const [key, info] of publications ?? []) {\n if (!info) continue\n const ref = parseGenericRecordKey(key)\n if (ref.uri === site) {\n publication = { ref, info }\n break\n }\n }\n if (!publication) {\n hydrationLogger.warn(\n { documentUri: document.ref.uri, documentSite: site },\n 'site.standard byDocumentUri lookup failed: document.site is AT URI but no matching publication was hydrated',\n )\n return { document: undefined, publication: undefined }\n }\n }\n // else: loose doc (web-URL site), no publication needed.\n } else {\n // Publication-only flow: no doc, take the first hydrated publication.\n for (const [key, info] of publications ?? []) {\n if (!info) continue\n publication = { ref: parseGenericRecordKey(key), info }\n break\n }\n }\n\n return { document, publication }\n}\n"]}
1
+ {"version":3,"file":"external.js","sourceRoot":"","sources":["../../src/hydration/external.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type AtUriString,\n type DidString,\n type InferInput,\n type NsidString,\n type RecordSchema,\n type TypedLexMap,\n isTypedLexMap,\n lexParseJsonBytes,\n} from '@atproto/lex'\nimport { parseAtUriString } from '@atproto/syntax'\nimport type { DataPlaneClient } from '../data-plane/client/index.js'\nimport { site } from '../lexicons/index.js'\nimport { hydrationLogger } from '../logger.js'\nimport {\n type RecordLookupResult,\n RecordLookupStatus,\n} from '../proto/bsky_pb.js'\nimport {\n type ExternalHydrationOutcome,\n type ExternalHydrationRoot,\n type ExternalHydrationWork,\n events,\n} from '../telemetry/events.js'\nimport { uriToDid } from '../util/uris.js'\nimport type {\n SiteStandardDocumentRecord,\n SiteStandardPublicationRecord,\n} from '../views/types.js'\nimport type { Actors } from './actor.js'\nimport type { Labels } from './label.js'\nimport {\n HydrationMap,\n type ItemRef,\n type RecordInfo,\n parseDate,\n safeTakedownRef,\n} from './util.js'\n\n/** Generic record bodies are decoded, but not validated against a Lexicon. */\nexport type ExternalRecord = RecordInfo<TypedLexMap>\nexport type ExternalRecords = HydrationMap<AtUriString, ExternalRecord>\n/** Keyed by `genericRecordKey({ uri, cid })` to retain versions of a URI. */\nexport type ExternalRecordsByRef = HydrationMap<ExactRecordKey, ExternalRecord>\nexport type ExternalRecordBacklinks = HydrationMap<AtUriString, AtUriString[]>\n\nexport type AtmosphereActivityItem = {\n uri: AtUriString\n sortedAt: Date\n}\n\nexport type AtmosphereTimeline = {\n items: AtmosphereActivityItem[]\n cursor?: string\n}\n\nexport type AtmosphereBacklinks = {\n backlinks: AtmosphereActivityItem[]\n cursor?: string\n}\n\nexport type ExternalRecordBacklinkCounts = HydrationMap<\n AtUriString,\n Partial<Record<NsidString, number>>\n>\n\nexport type AtmosphereBacklinksByActor = {\n backlinks: HydrationMap<AtUriString, AtUriString[]>\n truncated: boolean\n}\n\ndeclare const exactRecordKeySymbol: unique symbol\n\n/**\n * A version-exact `${uri}@${cid}` key, only produced by `genericRecordKey` for\n * a ref with a CID, so it can be parsed back by `parseGenericRecordKey`.\n */\nexport type ExactRecordKey = string & { [exactRecordKeySymbol]: true }\n\nexport type SiteStandardDocument = RecordInfo<SiteStandardDocumentRecord>\nexport type SiteStandardPublication = RecordInfo<SiteStandardPublicationRecord>\n\n/**\n * Keyed by `${uri}@${cid}` — see `genericRecordKey`. A single hydration\n * batch can pull more than one version of the same URI (different posts\n * pinning different cids), so the composite key is needed for O(1)\n * version-exact lookups.\n */\nexport type SiteStandardDocuments = HydrationMap<\n ExactRecordKey,\n SiteStandardDocument\n>\n/**\n * Keyed by `${uri}@${cid}`. See `SiteStandardDocuments` for the rationale.\n */\nexport type SiteStandardPublications = HydrationMap<\n ExactRecordKey,\n SiteStandardPublication\n>\nexport type SiteStandardRecords = {\n documents: SiteStandardDocuments\n publications: SiteStandardPublications\n}\n\nexport class ExternalHydrator {\n static readonly MAX_BACKLINK_PREVIEWS = 3\n static readonly MAX_BACKLINK_GALLERY_ITEMS = 10\n static readonly MAX_BACKLINK_FANOUT = 8\n /** Hard cap on generic batches per traversal, counting the root as pass 1. */\n static readonly MAX_EXTERNAL_HYDRATION_PASSES = 8\n\n constructor(public dataplane: DataPlaneClient) {}\n\n /** Fetch exact record versions; unavailable records are represented by null. */\n async getRecordsByRef(\n refs: Required<ItemRef>[],\n includeTakedowns = false,\n ): Promise<ExternalRecordsByRef> {\n const map: ExternalRecordsByRef = new HydrationMap()\n if (!refs.length) return map\n\n const res = await this.dataplane.getRecordsByRef({ refs })\n for (let i = 0; i < refs.length; i++) {\n map.set(\n genericRecordKey(refs[i]),\n parseGenericRecord(res.results[i], includeTakedowns) ?? null,\n )\n }\n return map\n }\n\n /** Fetch the latest indexed versions without collection-specific validation. */\n async getRecordsByURI(\n uris: AtUriString[],\n includeTakedowns = false,\n ): Promise<ExternalRecords> {\n const map: ExternalRecords = new HydrationMap()\n if (!uris.length) return map\n\n const res = await this.dataplane.getRecordsByURI({ uris })\n for (let i = 0; i < uris.length; i++) {\n map.set(\n uris[i],\n parseGenericRecord(res.results[i], includeTakedowns) ?? null,\n )\n }\n return map\n }\n\n /** Fetch a page of activity refs for subsequent generic record hydration. */\n async getAtmosphereTimeline(\n viewerDid: DidString,\n opts: { limit?: number; cursor?: string } = {},\n ): Promise<AtmosphereTimeline> {\n const res = await this.dataplane.getAtmosphereTimeline({\n viewerDid,\n ...opts,\n })\n return {\n items: res.items.map((item) => ({\n uri: item.uri as AtUriString,\n sortedAt: parseDate(item.sortedAt) ?? new Date(0),\n })),\n cursor: res.cursor || undefined,\n }\n }\n\n /** Fetch source-record counts, optionally restricted to one collection. */\n async getAtmosphereBacklinkCounts(\n targetUris: AtUriString[],\n collection?: NsidString,\n ): Promise<ExternalRecordBacklinkCounts> {\n const map: ExternalRecordBacklinkCounts = new HydrationMap()\n if (!targetUris.length) return map\n\n const res = await this.dataplane.getAtmosphereBacklinkCounts({\n targetUris,\n collection,\n })\n for (const result of res.results) {\n map.set(\n result.targetUri as AtUriString,\n Object.fromEntries(\n Object.entries(result.counts).map(([nsid, count]) => [\n nsid,\n Number(count),\n ]),\n ),\n )\n }\n return map\n }\n\n /** Fetch a page of source records linking to a target. */\n async getAtmosphereBacklinks(\n targetUri: AtUriString,\n collection: NsidString,\n opts: { limit?: number; cursor?: string } = {},\n ): Promise<AtmosphereBacklinks> {\n const res = await this.dataplane.getAtmosphereBacklinks({\n targetUri,\n collection,\n ...opts,\n })\n return {\n backlinks: res.backlinks.map((backlink) => ({\n uri: backlink.uri as AtUriString,\n sortedAt: parseDate(backlink.sortedAt) ?? new Date(0),\n })),\n cursor: res.cursor || undefined,\n }\n }\n\n /** Fetch an actor's links per target, retaining the batch truncation flag. */\n async getAtmosphereBacklinksByActor(\n targetUris: AtUriString[],\n actorDid: DidString,\n collection: NsidString,\n ): Promise<AtmosphereBacklinksByActor> {\n const backlinks: AtmosphereBacklinksByActor['backlinks'] =\n new HydrationMap()\n if (!targetUris.length) return { backlinks, truncated: false }\n\n const res = await this.dataplane.getAtmosphereBacklinksByActor({\n targetUris,\n actorDid,\n collection,\n })\n for (const result of res.results) {\n backlinks.set(\n result.targetUri as AtUriString,\n result.uris as AtUriString[],\n )\n }\n return { backlinks, truncated: res.truncated }\n }\n\n /**\n * Fetch exact `site.standard.{document,publication}` versions. Does NOT\n * resolve a document's publication; callers must pin it explicitly.\n */\n async getSiteStandardRecordsByRef(\n refs: ItemRef[],\n includeTakedowns = false,\n ): Promise<SiteStandardRecords> {\n const ssRefs = refs.filter(\n (ref): ref is Required<ItemRef> =>\n !!ref.cid && siteStandardKind(ref.uri) !== undefined,\n )\n const out = emptySiteStandardRecords()\n if (!ssRefs.length) return out\n\n const records = await this.getRecordsByRef(ssRefs, includeTakedowns)\n for (const [key, info] of records) {\n setSiteStandardRecord(out, parseGenericRecordKey(key).uri, key, info)\n }\n return out\n }\n}\n\n/**\n * Composes the lookup key for a ref: the bare URI for a latest-version lookup,\n * or `${uri}@${cid}` for an exact version. A single hydration batch can pull\n * several versions of one URI (different posts pinning different cids), so\n * exact maps need the composite for O(1) version-exact lookups. The two forms\n * never collide, so latest and exact lookups can share one traversal set.\n *\n * Throws on an empty CID rather than treating it as a latest lookup.\n */\nexport function genericRecordKey(ref: Required<ItemRef>): ExactRecordKey\nexport function genericRecordKey(ref: ItemRef): string\nexport function genericRecordKey({ uri, cid }: ItemRef): string {\n if (cid === undefined) return uri\n if (!cid) throw new Error(`Empty CID in record key for ${uri}`)\n return `${uri}@${cid}`\n}\n\n/**\n * Recover the URI and CID from an exact record key, splitting at the last `@`.\n * Throws if either part is missing. Components are trusted, not revalidated.\n */\nexport function parseGenericRecordKey(key: ExactRecordKey): Required<ItemRef> {\n const at = key.lastIndexOf('@')\n if (at <= 0 || at === key.length - 1) {\n throw new Error(`Malformed exact record key: ${key}`)\n }\n return {\n uri: key.slice(0, at) as AtUriString,\n cid: key.slice(at + 1),\n }\n}\n\n/**\n * Null generic external records (in both the URI-keyed and exact-ref maps,\n * keeping their keys) that are taken down by an actionable label, unless\n * takedowns are included, or whose owner is explicitly unavailable. Applies to\n * each record independently; counts and other records are unaffected.\n */\nexport function actionExternalRecordTakedowns(\n {\n externalRecords,\n externalRecordsByRef,\n actors,\n labels,\n }: {\n externalRecords?: ExternalRecords\n externalRecordsByRef?: ExternalRecordsByRef\n actors?: Actors\n labels?: Labels\n },\n includeTakedowns?: boolean,\n): void {\n // An absent actor entry was not hydrated; only `null` means unavailable.\n const isHidden = (uri: AtUriString) =>\n actors?.get(uriToDid(uri)) === null ||\n (!includeTakedowns && labels?.get(uri)?.isTakendown)\n for (const [uri, info] of externalRecords ?? []) {\n if (!info || !isHidden(uri)) continue\n externalRecords?.set(uri, null)\n externalRecordsByRef?.set(genericRecordKey({ uri, cid: info.cid }), null)\n }\n // Every exact version is checked on its own; versions are never collapsed.\n for (const [key, info] of externalRecordsByRef ?? []) {\n if (info && isHidden(parseGenericRecordKey(key).uri)) {\n externalRecordsByRef?.set(key, null)\n }\n }\n}\n\n/**\n * Generic lookup work of one external hydration traversal, shared by reference\n * through its nested passes. `capped` records hitting the pass limit.\n */\nexport type ExternalHydrationTracker = ExternalHydrationWork & {\n capped: boolean\n}\n\n/**\n * Run `fn` with the caller's tracker, or as the owner of a new traversal when\n * there is none. Only the owner reports, once, including on failure. Owners\n * that completed without scheduling a batch did no work and are not reported;\n * caps and failures always are.\n */\nexport async function trackExternalHydration<T>(\n root: ExternalHydrationRoot,\n hydrationMetrics: ExternalHydrationTracker | undefined,\n fn: (hydrationMetrics: ExternalHydrationTracker) => Promise<T>,\n): Promise<T> {\n if (hydrationMetrics) return fn(hydrationMetrics)\n const owned = { recordLookups: 0, batches: 0, maxPass: 0, capped: false }\n let outcome: ExternalHydrationOutcome = 'failed'\n try {\n const state = await fn(owned)\n outcome = owned.capped ? 'capped' : 'completed'\n return state\n } finally {\n if (owned.batches || outcome !== 'completed') {\n const { recordLookups, batches, maxPass } = owned\n events.externalHydrationTraversal({\n root,\n outcome,\n recordLookups,\n batches,\n maxPass,\n })\n }\n }\n}\n\nfunction parseGenericRecord(\n result: RecordLookupResult | undefined,\n includeTakedowns: boolean,\n): ExternalRecord | undefined {\n if (\n result?.status !== RecordLookupStatus.FOUND &&\n !(includeTakedowns && result?.status === RecordLookupStatus.TAKEN_DOWN)\n ) {\n return undefined\n }\n const entry = result.record\n if (!entry?.cid || !entry.record.byteLength) return undefined\n if (!includeTakedowns && entry.takenDown) return undefined\n\n let record: ReturnType<typeof lexParseJsonBytes>\n try {\n record = lexParseJsonBytes(entry.record, { strict: false })\n } catch {\n return undefined\n }\n if (!isTypedLexMap(record)) return undefined\n\n return {\n record,\n cid: entry.cid,\n sortedAt: parseDate(entry.sortedAt) ?? new Date(0),\n indexedAt: parseDate(entry.indexedAt) ?? new Date(0),\n takedownRef: safeTakedownRef(entry),\n }\n}\n\nconst emptySiteStandardRecords = (): SiteStandardRecords => ({\n documents: new HydrationMap(),\n publications: new HydrationMap(),\n})\n\nconst siteStandardKind = (\n uri: string,\n): keyof SiteStandardRecords | undefined => {\n const parsed = parseAtUriString(uri)\n if (!parsed.success) return undefined\n switch (parsed.value.collection) {\n case site.standard.document.$type:\n return 'documents'\n case site.standard.publication.$type:\n return 'publications'\n default:\n return undefined\n }\n}\n\n/**\n * Generic record bodies are unvalidated, so check them against the Lexicon\n * before exposing typed fields. Parse mode is not used, to preserve the\n * stored record as-is.\n */\nconst matchRecordInfo = <TSchema extends RecordSchema>(\n schema: TSchema,\n info: ExternalRecord | null | undefined,\n): RecordInfo<InferInput<TSchema>> | null =>\n info && schema.$matches(info.record, { strict: false })\n ? { ...info, record: info.record }\n : null\n\n/**\n * Store a generic record in the map matching its collection. Records failing\n * Lexicon validation are stored as null.\n */\nconst setSiteStandardRecord = (\n out: SiteStandardRecords,\n uri: string,\n key: ExactRecordKey,\n info: ExternalRecord | null | undefined,\n) => {\n switch (siteStandardKind(uri)) {\n case 'documents':\n out.documents.set(key, matchRecordInfo(site.standard.document.main, info))\n break\n case 'publications':\n out.publications.set(\n key,\n matchRecordInfo(site.standard.publication.main, info),\n )\n break\n }\n}\n\nexport type AssociatedSiteStandardRecord<T> = {\n ref: { uri: AtUriString; cid: string }\n info: T\n}\n\n/**\n * Read-path resolution: caller pinned a set of `(uri, cid)` strongRefs in\n * the post at write time, and we honor those exact versions. Any pair\n * that doesn't structurally agree is rejected, so downstream callers can\n * trust the returned shape.\n *\n * Pairing rules:\n * - Both slots referenced: `doc.site` must equal `publication.ref.uri`,\n * else both come back `undefined`.\n * - Doc with at-uri `site` referenced but no matching publication ref:\n * reject the whole pairing (doc claims a publication that should have\n * been pinned, but wasn't).\n * - Loose doc (web-URL `site`) referenced: publication stays `undefined`.\n * - Only a publication referenced: document stays `undefined`.\n *\n * Each returned slot carries the matching `ref` so callers can recover\n * the owner DID for blob-cdn URL building, etc.\n */\nexport const getSiteStandardRecordsFromHydrationMapsByRefs = (\n associatedRefs: readonly { uri: AtUriString; cid: string }[] | undefined,\n documents: SiteStandardDocuments | undefined,\n publications: SiteStandardPublications | undefined,\n): {\n document: AssociatedSiteStandardRecord<SiteStandardDocument> | undefined\n publication: AssociatedSiteStandardRecord<SiteStandardPublication> | undefined\n} => {\n if (!associatedRefs?.length) {\n return { document: undefined, publication: undefined }\n }\n\n // Resolve each ref against the hydration maps, taking the first hit on\n // each side.\n let document: AssociatedSiteStandardRecord<SiteStandardDocument> | undefined\n let publication:\n AssociatedSiteStandardRecord<SiteStandardPublication> | undefined\n for (const ref of associatedRefs) {\n const key = genericRecordKey(ref)\n if (!document) {\n const hit = documents?.get(key)\n if (hit) document = { ref, info: hit }\n }\n if (!publication) {\n const hit = publications?.get(key)\n if (hit) publication = { ref, info: hit }\n }\n if (document && publication) break\n }\n\n // Both refs resolved: enforce that the doc's `site` actually points at\n // the supplied publication. Mismatch means the post was misconstructed\n // (or tampered with), so reject the whole pairing.\n if (document && publication) {\n if (document.info.record.site !== publication.ref.uri) {\n hydrationLogger.warn(\n {\n documentUri: document.ref.uri,\n documentSite: document.info.record.site,\n publicationUri: publication.ref.uri,\n },\n 'site.standard byRefs lookup failed: doc.site does not match hydrated publication.uri',\n )\n return { document: undefined, publication: undefined }\n }\n }\n\n // Doc with at-uri `site` but no publication: the post should have\n // pinned the publication too. Treat as misconstructed.\n if (document && !publication) {\n const site = document.info.record.site\n if (site && site.startsWith('at://')) {\n hydrationLogger.warn(\n { documentUri: document.ref.uri, documentSite: site },\n 'site.standard byRefs lookup failed: document.site is AT URI but no matching publication was hydrated',\n )\n return { document: undefined, publication: undefined }\n }\n }\n\n return { document, publication }\n}\n"]}