@atproto/bsky 0.3.0 → 0.3.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 CHANGED
@@ -1,5 +1,13 @@
1
1
  # @atproto/bsky
2
2
 
3
+ ## 0.3.1
4
+
5
+ ### Patch Changes
6
+
7
+ - [#5677](https://github.com/bluesky-social/atproto/pull/5677) [`9354e5d`](https://github.com/bluesky-social/atproto/commit/9354e5d9b12ebf1add5ebccf36596031992abc1a) Thanks [@estrattonbailey](https://github.com/estrattonbailey)! - Report external view hydration XRPC endpoint, duration, and root/dependency resolution.
8
+
9
+ - [#5677](https://github.com/bluesky-social/atproto/pull/5677) [`9354e5d`](https://github.com/bluesky-social/atproto/commit/9354e5d9b12ebf1add5ebccf36596031992abc1a) Thanks [@estrattonbailey](https://github.com/estrattonbailey)! - Fetch generic external records in requests of at most 100 entries so large dependency passes, such as Atmosphere Explore galleries, no longer exceed the dataplane limit, and report external hydration traversals that recovered from dependency failures with a `partial` outcome.
10
+
3
11
  ## 0.3.0
4
12
 
5
13
  ### Minor Changes
@@ -1 +1 @@
1
- {"version":3,"file":"getEmbedExternalView.d.ts","sourceRoot":"","sources":["../../../../../src/api/app/bsky/embed/getEmbedExternalView.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAA;AAElD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AAuBxD,MAAM,CAAC,OAAO,WAAW,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,QAqBvD"}
1
+ {"version":3,"file":"getEmbedExternalView.d.ts","sourceRoot":"","sources":["../../../../../src/api/app/bsky/embed/getEmbedExternalView.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAA;AAElD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AAuBxD,MAAM,CAAC,OAAO,WAAW,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,QAyBvD"}
@@ -12,7 +12,11 @@ export default function (server, ctx) {
12
12
  handler: async ({ params, auth, req }) => {
13
13
  const viewer = auth.credentials.iss;
14
14
  const labelers = ctx.reqLabelers(req);
15
- const hydrateCtx = await ctx.hydrator.createContext({ labelers, viewer });
15
+ const hydrateCtx = await ctx.hydrator.createContext({
16
+ labelers,
17
+ viewer,
18
+ xrpcEndpoint: app.bsky.embed.getEmbedExternalView.$lxm,
19
+ });
16
20
  const result = await getEmbedExternalView({ ...params, hydrateCtx }, ctx);
17
21
  return {
18
22
  encoding: 'application/json',
@@ -1 +1 @@
1
- {"version":3,"file":"getEmbedExternalView.js","sourceRoot":"","sources":["../../../../../src/api/app/bsky/embed/getEmbedExternalView.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type AtUriString,\n type LexMap,\n type TypedLexMap,\n isDidString,\n} from '@atproto/lex'\nimport { parseAtUriString } from '@atproto/syntax'\nimport type { Server } from '@atproto/xrpc-server'\nimport type { ServerConfig } from '../../../../config.js'\nimport type { AppContext } from '../../../../context.js'\nimport type { ExternalRecords } from '../../../../hydration/external.js'\nimport type {\n HydrateCtx,\n HydrationState,\n Hydrator,\n} from '../../../../hydration/hydrator.js'\nimport { app, com, site, social } from '../../../../lexicons/index.js'\nimport {\n type HydrationFnInput,\n type PresentationFnInput,\n type SkeletonFnInput,\n createPipeline,\n noRules,\n} from '../../../../pipeline.js'\nimport { isExternalRecordUrlSafe } from '../../../../util/external/index.js'\nimport {\n getStandardSiteRecordsForUri,\n validateStandardSiteRecordsForUrl,\n} from '../../../../util/external/standard-site.js'\nimport type { Views } from '../../../../views/index.js'\nimport { resHeaders } from '../../../util.js'\n\nexport default function (server: Server, ctx: AppContext) {\n const getEmbedExternalView = createPipeline(\n skeleton,\n hydration,\n noRules,\n presentation,\n )\n server.add(app.bsky.embed.getEmbedExternalView, {\n auth: ctx.authVerifier.standardOptional,\n handler: async ({ params, auth, req }) => {\n const viewer = auth.credentials.iss\n const labelers = ctx.reqLabelers(req)\n const hydrateCtx = await ctx.hydrator.createContext({ labelers, viewer })\n const result = await getEmbedExternalView({ ...params, hydrateCtx }, ctx)\n return {\n encoding: 'application/json',\n body: result,\n headers: resHeaders({ labelers: hydrateCtx.labelers }),\n }\n },\n })\n}\n\nconst skeleton = async (\n inputs: SkeletonFnInput<Context, Params>,\n): Promise<Skeleton> => {\n return { canonicalUri: normalizeCanonicalUri(inputs.params) }\n}\n\nconst hydration = async (\n inputs: HydrationFnInput<Context, Params, Skeleton>,\n): Promise<HydrationState> => {\n const { ctx, params, skeleton } = inputs\n if (!skeleton.canonicalUri) return { ctx: params.hydrateCtx }\n return ctx.hydrator.hydrateEmbedExternalViewFromUris(\n [skeleton.canonicalUri],\n params.hydrateCtx,\n )\n}\n\nconst presentation = (\n inputs: PresentationFnInput<Context, Params, Skeleton>,\n): Output => {\n const { ctx, params, skeleton, hydration } = inputs\n const { canonicalUri } = skeleton\n if (!canonicalUri) return {}\n\n if (!hydration.externalRecords?.get(canonicalUri)) return {}\n\n const { associatedRefs, associatedRecords } = collectAssociations(\n canonicalUri,\n hydration.externalRecords,\n )\n\n // Validate before building any view: trust must not depend on whether the\n // records happen to be renderable.\n const standardSite = isStandardSiteUri(canonicalUri)\n const standardSiteRecords = standardSite\n ? getStandardSiteRecordsForUri(canonicalUri, hydration.externalRecords)\n : undefined\n const verified = standardSiteRecords\n ? validateStandardSiteRecordsForUrl(\n standardSiteRecords.document,\n standardSiteRecords.publication,\n params.url,\n )\n : isExternalRecordUrlSafe(params.url, ctx.cfg.externalRecordsSafeDomains)\n\n if (!verified) {\n return {\n unverifiedAssociatedRefs: associatedRefs,\n unverifiedAssociatedRecords: associatedRecords,\n }\n }\n\n const data = ctx.views.externalRecordView(canonicalUri, hydration)\n\n let view: Output['view']\n if (standardSiteRecords) {\n const standardSiteView = ctx.views.externalEmbedFromStandardSiteRecords({\n document: standardSiteRecords.document,\n publication: standardSiteRecords.publication,\n state: hydration,\n assumedUrl: params.url,\n })\n if (standardSiteView) {\n view = app.bsky.embed.external.view.$build({\n external: {\n ...standardSiteView,\n uri: params.url,\n associatedRefs: associatedRefs,\n },\n })\n }\n }\n\n return {\n view,\n data,\n associatedRefs: associatedRefs,\n associatedRecords: associatedRecords,\n }\n}\n\ntype RecordUri = { uri: AtUriString; collection: string }\n\n/** A complete DID-based record URI: record key present, no fragment. */\nconst parseRecordUri = (value: string): RecordUri | undefined => {\n const parsed = parseAtUriString(value)\n if (\n parsed.success &&\n isDidString(parsed.value.authority) &&\n parsed.value.collection &&\n parsed.value.rkey &&\n !parsed.value.hash\n ) {\n return { uri: value as AtUriString, collection: parsed.value.collection }\n }\n return undefined\n}\n\nconst isStandardSiteUri = (uri: AtUriString): boolean => {\n const collection = parseRecordUri(uri)?.collection\n return (\n collection === site.standard.document.$type ||\n collection === site.standard.publication.$type\n )\n}\n\n/**\n * Reduce the request to one record URI. An explicit `canonicalUri` wins and\n * is never retried with `uris`. Otherwise the legacy `uris` input selects the\n * first Standard.site document, or else the first publication, regardless of\n * array order. No I/O: selection depends only on the supplied URIs.\n */\nconst normalizeCanonicalUri = (params: Params): AtUriString | undefined => {\n if (params.canonicalUri !== undefined) {\n return parseRecordUri(params.canonicalUri)?.uri\n }\n const candidates = (params.uris ?? []).flatMap((value) => {\n const parsed = parseRecordUri(value)\n return parsed ? [parsed] : []\n })\n const preferred =\n candidates.find((c) => c.collection === site.standard.document.$type) ??\n candidates.find((c) => c.collection === site.standard.publication.$type)\n return preferred?.uri\n}\n\n/**\n * Persistence policy, not render-time hydration: the canonical record (when\n * available, whatever its collection) plus only the additional collections\n * opted in here, so records baked into a post stay slim. Hydration still holds\n * the full state (e.g. recommendation, subscription and favorite records that\n * back the rendered `likers`/`subscribers` previews); those are deliberately\n * not persisted. An absent entry means no additional collections. Add a\n * collection here only when a post must carry it.\n */\nconst PERSISTED_DEPENDENCY_COLLECTIONS: ReadonlyMap<string, readonly string[]> =\n new Map<string, readonly string[]>([\n [site.standard.document.$type, [site.standard.publication.$type]],\n [\n social.grain.gallery.$type,\n [social.grain.gallery.item.$type, social.grain.photo.$type],\n ],\n ])\n\n/**\n * The canonical record, then the hydrated dependencies whose URI collection is\n * opted in by `PERSISTED_DEPENDENCY_COLLECTIONS` for the canonical collection.\n * Refs and associatedRecords are index-aligned, with CIDs taken from\n * hydration; unavailable records are excluded.\n */\nconst collectAssociations = (\n canonicalUri: AtUriString,\n records: ExternalRecords,\n): { associatedRefs: StrongRef[]; associatedRecords: LexMap[] } => {\n const associatedRefs: StrongRef[] = []\n const associatedRecords: LexMap[] = []\n const canonicalCollection = parseRecordUri(canonicalUri)?.collection\n const persisted =\n (canonicalCollection &&\n PERSISTED_DEPENDENCY_COLLECTIONS.get(canonicalCollection)) ||\n []\n const uris = [\n canonicalUri,\n ...[...records.keys()].filter((uri) => uri !== canonicalUri),\n ]\n for (const uri of uris) {\n const info = records.get(uri)\n if (!info) continue\n if (uri !== canonicalUri) {\n const collection = parseRecordUri(uri)?.collection\n if (!collection || !persisted.includes(collection)) continue\n }\n associatedRefs.push(\n com.atproto.repo.strongRef.$build({ uri, cid: info.cid }),\n )\n associatedRecords.push(info.record as TypedLexMap)\n }\n return { associatedRefs, associatedRecords }\n}\n\ntype StrongRef = com.atproto.repo.strongRef.Main\n\ntype Context = {\n hydrator: Hydrator\n views: Views\n cfg: Pick<ServerConfig, 'externalRecordsSafeDomains'>\n}\n\ntype Params = app.bsky.embed.getEmbedExternalView.$Params & {\n hydrateCtx: HydrateCtx\n}\n\ntype Skeleton = {\n canonicalUri?: AtUriString\n}\n\ntype Output = app.bsky.embed.getEmbedExternalView.$OutputBody\n"]}
1
+ {"version":3,"file":"getEmbedExternalView.js","sourceRoot":"","sources":["../../../../../src/api/app/bsky/embed/getEmbedExternalView.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type AtUriString,\n type LexMap,\n type TypedLexMap,\n isDidString,\n} from '@atproto/lex'\nimport { parseAtUriString } from '@atproto/syntax'\nimport type { Server } from '@atproto/xrpc-server'\nimport type { ServerConfig } from '../../../../config.js'\nimport type { AppContext } from '../../../../context.js'\nimport type { ExternalRecords } from '../../../../hydration/external.js'\nimport type {\n HydrateCtx,\n HydrationState,\n Hydrator,\n} from '../../../../hydration/hydrator.js'\nimport { app, com, site, social } from '../../../../lexicons/index.js'\nimport {\n type HydrationFnInput,\n type PresentationFnInput,\n type SkeletonFnInput,\n createPipeline,\n noRules,\n} from '../../../../pipeline.js'\nimport { isExternalRecordUrlSafe } from '../../../../util/external/index.js'\nimport {\n getStandardSiteRecordsForUri,\n validateStandardSiteRecordsForUrl,\n} from '../../../../util/external/standard-site.js'\nimport type { Views } from '../../../../views/index.js'\nimport { resHeaders } from '../../../util.js'\n\nexport default function (server: Server, ctx: AppContext) {\n const getEmbedExternalView = createPipeline(\n skeleton,\n hydration,\n noRules,\n presentation,\n )\n server.add(app.bsky.embed.getEmbedExternalView, {\n auth: ctx.authVerifier.standardOptional,\n handler: async ({ params, auth, req }) => {\n const viewer = auth.credentials.iss\n const labelers = ctx.reqLabelers(req)\n const hydrateCtx = await ctx.hydrator.createContext({\n labelers,\n viewer,\n xrpcEndpoint: app.bsky.embed.getEmbedExternalView.$lxm,\n })\n const result = await getEmbedExternalView({ ...params, hydrateCtx }, ctx)\n return {\n encoding: 'application/json',\n body: result,\n headers: resHeaders({ labelers: hydrateCtx.labelers }),\n }\n },\n })\n}\n\nconst skeleton = async (\n inputs: SkeletonFnInput<Context, Params>,\n): Promise<Skeleton> => {\n return { canonicalUri: normalizeCanonicalUri(inputs.params) }\n}\n\nconst hydration = async (\n inputs: HydrationFnInput<Context, Params, Skeleton>,\n): Promise<HydrationState> => {\n const { ctx, params, skeleton } = inputs\n if (!skeleton.canonicalUri) return { ctx: params.hydrateCtx }\n return ctx.hydrator.hydrateEmbedExternalViewFromUris(\n [skeleton.canonicalUri],\n params.hydrateCtx,\n )\n}\n\nconst presentation = (\n inputs: PresentationFnInput<Context, Params, Skeleton>,\n): Output => {\n const { ctx, params, skeleton, hydration } = inputs\n const { canonicalUri } = skeleton\n if (!canonicalUri) return {}\n\n if (!hydration.externalRecords?.get(canonicalUri)) return {}\n\n const { associatedRefs, associatedRecords } = collectAssociations(\n canonicalUri,\n hydration.externalRecords,\n )\n\n // Validate before building any view: trust must not depend on whether the\n // records happen to be renderable.\n const standardSite = isStandardSiteUri(canonicalUri)\n const standardSiteRecords = standardSite\n ? getStandardSiteRecordsForUri(canonicalUri, hydration.externalRecords)\n : undefined\n const verified = standardSiteRecords\n ? validateStandardSiteRecordsForUrl(\n standardSiteRecords.document,\n standardSiteRecords.publication,\n params.url,\n )\n : isExternalRecordUrlSafe(params.url, ctx.cfg.externalRecordsSafeDomains)\n\n if (!verified) {\n return {\n unverifiedAssociatedRefs: associatedRefs,\n unverifiedAssociatedRecords: associatedRecords,\n }\n }\n\n const data = ctx.views.externalRecordView(canonicalUri, hydration)\n\n let view: Output['view']\n if (standardSiteRecords) {\n const standardSiteView = ctx.views.externalEmbedFromStandardSiteRecords({\n document: standardSiteRecords.document,\n publication: standardSiteRecords.publication,\n state: hydration,\n assumedUrl: params.url,\n })\n if (standardSiteView) {\n view = app.bsky.embed.external.view.$build({\n external: {\n ...standardSiteView,\n uri: params.url,\n associatedRefs: associatedRefs,\n },\n })\n }\n }\n\n return {\n view,\n data,\n associatedRefs: associatedRefs,\n associatedRecords: associatedRecords,\n }\n}\n\ntype RecordUri = { uri: AtUriString; collection: string }\n\n/** A complete DID-based record URI: record key present, no fragment. */\nconst parseRecordUri = (value: string): RecordUri | undefined => {\n const parsed = parseAtUriString(value)\n if (\n parsed.success &&\n isDidString(parsed.value.authority) &&\n parsed.value.collection &&\n parsed.value.rkey &&\n !parsed.value.hash\n ) {\n return { uri: value as AtUriString, collection: parsed.value.collection }\n }\n return undefined\n}\n\nconst isStandardSiteUri = (uri: AtUriString): boolean => {\n const collection = parseRecordUri(uri)?.collection\n return (\n collection === site.standard.document.$type ||\n collection === site.standard.publication.$type\n )\n}\n\n/**\n * Reduce the request to one record URI. An explicit `canonicalUri` wins and\n * is never retried with `uris`. Otherwise the legacy `uris` input selects the\n * first Standard.site document, or else the first publication, regardless of\n * array order. No I/O: selection depends only on the supplied URIs.\n */\nconst normalizeCanonicalUri = (params: Params): AtUriString | undefined => {\n if (params.canonicalUri !== undefined) {\n return parseRecordUri(params.canonicalUri)?.uri\n }\n const candidates = (params.uris ?? []).flatMap((value) => {\n const parsed = parseRecordUri(value)\n return parsed ? [parsed] : []\n })\n const preferred =\n candidates.find((c) => c.collection === site.standard.document.$type) ??\n candidates.find((c) => c.collection === site.standard.publication.$type)\n return preferred?.uri\n}\n\n/**\n * Persistence policy, not render-time hydration: the canonical record (when\n * available, whatever its collection) plus only the additional collections\n * opted in here, so records baked into a post stay slim. Hydration still holds\n * the full state (e.g. recommendation, subscription and favorite records that\n * back the rendered `likers`/`subscribers` previews); those are deliberately\n * not persisted. An absent entry means no additional collections. Add a\n * collection here only when a post must carry it.\n */\nconst PERSISTED_DEPENDENCY_COLLECTIONS: ReadonlyMap<string, readonly string[]> =\n new Map<string, readonly string[]>([\n [site.standard.document.$type, [site.standard.publication.$type]],\n [\n social.grain.gallery.$type,\n [social.grain.gallery.item.$type, social.grain.photo.$type],\n ],\n ])\n\n/**\n * The canonical record, then the hydrated dependencies whose URI collection is\n * opted in by `PERSISTED_DEPENDENCY_COLLECTIONS` for the canonical collection.\n * Refs and associatedRecords are index-aligned, with CIDs taken from\n * hydration; unavailable records are excluded.\n */\nconst collectAssociations = (\n canonicalUri: AtUriString,\n records: ExternalRecords,\n): { associatedRefs: StrongRef[]; associatedRecords: LexMap[] } => {\n const associatedRefs: StrongRef[] = []\n const associatedRecords: LexMap[] = []\n const canonicalCollection = parseRecordUri(canonicalUri)?.collection\n const persisted =\n (canonicalCollection &&\n PERSISTED_DEPENDENCY_COLLECTIONS.get(canonicalCollection)) ||\n []\n const uris = [\n canonicalUri,\n ...[...records.keys()].filter((uri) => uri !== canonicalUri),\n ]\n for (const uri of uris) {\n const info = records.get(uri)\n if (!info) continue\n if (uri !== canonicalUri) {\n const collection = parseRecordUri(uri)?.collection\n if (!collection || !persisted.includes(collection)) continue\n }\n associatedRefs.push(\n com.atproto.repo.strongRef.$build({ uri, cid: info.cid }),\n )\n associatedRecords.push(info.record as TypedLexMap)\n }\n return { associatedRefs, associatedRecords }\n}\n\ntype StrongRef = com.atproto.repo.strongRef.Main\n\ntype Context = {\n hydrator: Hydrator\n views: Views\n cfg: Pick<ServerConfig, 'externalRecordsSafeDomains'>\n}\n\ntype Params = app.bsky.embed.getEmbedExternalView.$Params & {\n hydrateCtx: HydrateCtx\n}\n\ntype Skeleton = {\n canonicalUri?: AtUriString\n}\n\ntype Output = app.bsky.embed.getEmbedExternalView.$OutputBody\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"getAtmosphereExploreTab.d.ts","sourceRoot":"","sources":["../../../../../src/api/app/bsky/unspecced/getAtmosphereExploreTab.ts"],"names":[],"mappings":"AAIA,OAAO,EAEL,KAAK,MAAM,EAGZ,MAAM,sBAAsB,CAAA;AAE7B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AA8CxD,MAAM,CAAC,OAAO,WAAW,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,QA0DvD"}
1
+ {"version":3,"file":"getAtmosphereExploreTab.d.ts","sourceRoot":"","sources":["../../../../../src/api/app/bsky/unspecced/getAtmosphereExploreTab.ts"],"names":[],"mappings":"AAIA,OAAO,EAEL,KAAK,MAAM,EAGZ,MAAM,sBAAsB,CAAA;AAE7B,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAA;AA8CxD,MAAM,CAAC,OAAO,WAAW,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,QA2DvD"}
@@ -55,6 +55,7 @@ export default function (server, ctx) {
55
55
  viewer,
56
56
  labelers: ctx.reqLabelers(req),
57
57
  features: ctx.featureGatesClient.scope(ctx.featureGatesClient.parseUserContextFromHandler({ viewer, req })),
58
+ xrpcEndpoint: app.bsky.unspecced.getAtmosphereExploreTab.$lxm,
58
59
  });
59
60
  const useIris = hydrateCtx.features.checkGate(hydrateCtx.features.Gate.AtmosphereExploreArticlesIrisEnable);
60
61
  // Use 501 instead of 500 as these are not considered retry-able by clients
@@ -1 +1 @@
1
- {"version":3,"file":"getAtmosphereExploreTab.js","sourceRoot":"","sources":["../../../../../src/api/app/bsky/unspecced/getAtmosphereExploreTab.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IncomingHttpHeaders } from 'node:http'\nimport { mapDefined, noUndefinedVals } from '@atproto/common'\nimport { type AtUriString, type Client, l } from '@atproto/lex'\nimport { AtUri } from '@atproto/syntax'\nimport {\n MethodNotImplementedError,\n type Server,\n UpstreamFailureError,\n UpstreamTimeoutError,\n} from '@atproto/xrpc-server'\nimport { safeFetchWrap } from '@atproto-labs/fetch-node'\nimport type { AppContext } from '../../../../context.js'\nimport type { HydrateCtx, Hydrator } from '../../../../hydration/hydrator.js'\nimport { app, site } from '../../../../lexicons/index.js'\nimport { httpLogger } from '../../../../logger.js'\nimport {\n type HydrationFnInput,\n type PresentationFnInput,\n type SkeletonFnInput,\n createPipeline,\n noRules,\n} from '../../../../pipeline.js'\nimport { getAtprotoPassthroughHeaders } from '../../../../util/headers.js'\nimport type { Views } from '../../../../views/index.js'\n\nconst MAX_CONFIG_BYTES = 512 * 1024\nconst MAX_LIST_ITEMS = 10\n\nconst groupSchema = l.object({\n canonicalUri: l.optional(l.unknown()),\n startsAt: l.optional(l.nullable(l.string({ format: 'datetime' }))),\n endsAt: l.optional(l.nullable(l.string({ format: 'datetime' }))),\n tags: l.optional(l.array(l.string({ maxLength: 128 }), { maxLength: 100 })),\n locations: l.optional(\n l.array(l.string({ maxLength: 16 }), { maxLength: 100 }),\n ),\n})\nconst bannerSchema = l.object({\n status: l.string({ maxLength: 128 }),\n startsAt: l.optional(l.nullable(l.string({ format: 'datetime' }))),\n endsAt: l.optional(l.nullable(l.string({ format: 'datetime' }))),\n locations: l.optional(\n l.array(l.string({ maxLength: 16 }), { maxLength: 100 }),\n ),\n})\n// Ingestion is best-effort per item: each section is an unvalidated value, and\n// every entry is validated on its own so one bad entry never fails the rest.\nconst configSchema = l.object({\n announcementBanners: l.optional(l.unknown()),\n articles: l.optional(l.unknown()),\n publications: l.optional(l.unknown()),\n photos: l.optional(l.unknown()),\n livestreams: l.optional(l.unknown()),\n apps: l.optional(l.unknown()),\n})\nconst MAX_INPUT_ENTRIES = 100\n\nexport default function (server: Server, ctx: AppContext) {\n const getExplore = createPipeline(skeleton, hydration, noRules, presentation)\n const fetchExploreTabSkeleton = createFetchExploreTabSkeleton()\n server.add(app.bsky.unspecced.getAtmosphereExploreTab, {\n auth: ctx.authVerifier.standardOptional,\n handler: async ({ auth, params, req, signal }) => {\n const service = ctx.cfg.atmosphereExploreTabService\n if (!service)\n throw new MethodNotImplementedError(\n 'Atmosphere Explore service not configured',\n )\n let workerUrl: URL\n try {\n // Location filtering happens locally, so fetch the full config as-is.\n workerUrl = new URL(service)\n } catch {\n throw new UpstreamFailureError(\n 'Unable to fetch Atmosphere Explore configuration',\n )\n }\n const viewer = auth.credentials.iss\n const hydrateCtx = await ctx.hydrator.createContext({\n viewer,\n labelers: ctx.reqLabelers(req),\n features: ctx.featureGatesClient.scope(\n ctx.featureGatesClient.parseUserContextFromHandler({ viewer, req }),\n ),\n })\n const useIris = hydrateCtx.features.checkGate(\n hydrateCtx.features.Gate.AtmosphereExploreArticlesIrisEnable,\n )\n // Use 501 instead of 500 as these are not considered retry-able by clients\n if (useIris && !ctx.irisClient)\n throw new MethodNotImplementedError('Iris agent not available')\n const result = await getExplore(\n {\n ...params,\n hydrateCtx,\n workerUrl,\n requestHeaders: req.headers,\n headers: noUndefinedVals({\n 'accept-language': params.langs?.length\n ? params.langs.join(',')\n : req.headers['accept-language'],\n ...getAtprotoPassthroughHeaders(req),\n }),\n signal,\n },\n {\n hydrator: ctx.hydrator,\n views: ctx.views,\n irisClient: useIris ? ctx.irisClient : undefined,\n fetchExploreTabSkeleton,\n },\n )\n return { encoding: 'application/json', body: result }\n },\n })\n}\n\nasync function skeleton({\n ctx,\n params,\n}: SkeletonFnInput<Context, Params>): Promise<SkeletonState> {\n const [config, recommendations] = await Promise.all([\n ctx.fetchExploreTabSkeleton(params.workerUrl, params.signal),\n ctx.irisClient\n ? ctx.irisClient\n .call(\n app.bsky.unspecced.getAtmosphereExploreArticleRecommendations,\n {\n limit: MAX_LIST_ITEMS,\n viewer: params.hydrateCtx.viewer ?? undefined,\n },\n {\n headers: params.headers,\n signal: params.signal,\n validateResponse: true,\n },\n )\n // @NOTE Request and response-validation failures use curated articles.\n .catch((err) => {\n if (params.signal.aborted) throw err\n httpLogger.warn(\n { err },\n 'Unable to fetch Atmosphere Explore article recommendations',\n )\n return undefined\n })\n : undefined,\n ])\n const now = Date.now()\n const announcementBanner = selectBanner(\n config.announcementBanners,\n params,\n now,\n )\n const select = createSelector(params, params.requestHeaders, now)\n return {\n now,\n announcementBanner,\n articles: orderGroups(\n recommendations && recommendations.articles.length > 0\n ? mapDefined(\n recommendations.articles.slice(0, MAX_LIST_ITEMS),\n ({ uri }) => {\n const root = recordUri(uri)\n return root &&\n new AtUri(root).collection === site.standard.document.$type\n ? { featured: false, uri: root }\n : undefined\n },\n )\n : completeGroups(select(config.articles)),\n ),\n articleRecIdStr: recommendations?.recIdStr,\n publications: orderGroups(completeGroups(select(config.publications))),\n photos: orderGroups(completeGroups(select(config.photos))),\n livestreams: orderGroups(completeGroups(select(config.livestreams))),\n // Apps have no hydration root or featured flag: keep worker order.\n apps: config.apps.slice(0, MAX_LIST_ITEMS),\n }\n}\n\nasync function hydration({\n ctx,\n params,\n skeleton,\n}: HydrationFnInput<Context, Params, SkeletonState>) {\n const uris = [\n ...new Set(\n [\n ...skeleton.articles,\n ...skeleton.publications,\n ...skeleton.photos,\n ...skeleton.livestreams,\n ].map(({ uri }) => uri),\n ),\n ]\n return ctx.hydrator.hydrateEmbedExternalViewFromUris(uris, params.hydrateCtx)\n}\n\nfunction presentation({\n ctx,\n skeleton,\n hydration: state,\n}: PresentationFnInput<\n Context,\n Params,\n SkeletonState\n>): app.bsky.unspecced.getAtmosphereExploreTab.$OutputBody {\n const { now, articles, publications, photos, livestreams } = skeleton\n return {\n announcementBanner: skeleton.announcementBanner,\n articles: {\n items: mapDefined(articles, ({ featured, uri }) => {\n const view = ctx.views.externalRecordView(uri, state, now)\n return view && app.bsky.embed.external.viewArticle.$isTypeOf(view)\n ? { featured, view }\n : undefined\n }),\n recIdStr: skeleton.articleRecIdStr,\n },\n publications: {\n items: mapDefined(publications, ({ featured, uri }) => {\n const view = ctx.views.externalRecordView(uri, state, now)\n return view &&\n app.bsky.embed.external.viewArticlePublication.$isTypeOf(view)\n ? { featured, view }\n : undefined\n }),\n },\n photos: {\n items: mapDefined(photos, ({ featured, uri }) => {\n const view = ctx.views.externalRecordView(uri, state, now)\n return view &&\n app.bsky.embed.external.viewGallery.$isTypeOf(view) &&\n view.items.length\n ? { featured, view }\n : undefined\n }),\n },\n livestreams: {\n items: mapDefined(livestreams, ({ featured, uri }) => {\n const view = ctx.views.externalRecordView(uri, state, now)\n return view && app.bsky.embed.external.viewLivestream.$isTypeOf(view)\n ? { featured, view }\n : undefined\n }),\n },\n apps: skeleton.apps,\n }\n}\n\nfunction createFetchExploreTabSkeleton() {\n // @NOTE The wrapper caps decoded response bytes, including compressed bodies.\n const fetchConfig = safeFetchWrap({\n timeout: 5000,\n responseMaxSize: MAX_CONFIG_BYTES,\n })\n\n return async function fetchExploreTabSkeleton(\n url: URL,\n signal: AbortSignal,\n ): Promise<ExploreConfig> {\n try {\n const response = await fetchConfig(url, {\n headers: { accept: 'application/json' },\n redirect: 'error',\n signal,\n })\n if (!response.ok) {\n await response.body?.cancel()\n throw new UpstreamFailureError(\n `Atmosphere Explore service returned HTTP ${response.status}`,\n )\n }\n return normalizeConfig(await response.json())\n } catch (err) {\n if (err instanceof UpstreamFailureError) throw err\n if (\n err instanceof Error &&\n (err.name === 'TimeoutError' || err.name === 'AbortError')\n ) {\n throw new UpstreamTimeoutError('Atmosphere Explore request timed out')\n }\n throw new UpstreamFailureError(\n 'Unable to fetch Atmosphere Explore configuration',\n )\n }\n }\n}\n\ntype Group = l.Infer<typeof groupSchema>\ntype AppCard = app.bsky.unspecced.getAtmosphereExploreTab.AppCard\ntype ValidBanner = {\n banner: l.Infer<typeof bannerSchema>\n presentation: AnnouncementBanner\n}\ntype ExploreConfig = {\n announcementBanners: ValidBanner[]\n articles: Group[]\n publications: Group[]\n photos: Group[]\n livestreams: Group[]\n apps: AppCard[]\n}\n\n/**\n * Keeps only the well-formed items of a successfully decoded worker body. Only\n * the first `MAX_INPUT_ENTRIES` entries of each section are examined, and\n * malformed entries are skipped whole, never coerced or repaired.\n */\nfunction normalizeConfig(body: unknown): ExploreConfig {\n const raw = configSchema.safeParse(body)\n const config = raw.success ? raw.value : {}\n const entries = (value: unknown): unknown[] =>\n Array.isArray(value) ? value.slice(0, MAX_INPUT_ENTRIES) : []\n const valid = <T>(schema: l.Schema<T>, value: unknown): T[] =>\n entries(value).flatMap((entry) => {\n const result = schema.safeParse(entry)\n return result.success ? [result.value] : []\n })\n return {\n announcementBanners: entries(config.announcementBanners).flatMap(\n (entry) => {\n const banner = bannerSchema.safeParse(entry)\n const presentation =\n app.bsky.unspecced.getAtmosphereExploreTab.announcementBanner.$safeValidate(\n entry,\n )\n return banner.success && presentation.success\n ? [{ banner: banner.value, presentation: presentation.value }]\n : []\n },\n ),\n articles: valid(groupSchema, config.articles),\n publications: valid(groupSchema, config.publications),\n photos: valid(groupSchema, config.photos),\n livestreams: valid(groupSchema, config.livestreams),\n apps: valid(\n app.bsky.unspecced.getAtmosphereExploreTab.appCard,\n config.apps,\n ),\n }\n}\n\ntype Context = {\n hydrator: Hydrator\n views: Views\n irisClient: Client | undefined\n fetchExploreTabSkeleton: ReturnType<typeof createFetchExploreTabSkeleton>\n}\n\ntype Params = app.bsky.unspecced.getAtmosphereExploreTab.$Params & {\n hydrateCtx: HydrateCtx\n workerUrl: URL\n /** Recommendation headers for Iris only. */\n headers: Record<string, string>\n requestHeaders: IncomingHttpHeaders\n signal: AbortSignal\n}\n\ntype RecordGroup = { featured: boolean; uri: AtUriString }\n\ntype SkeletonState = {\n now: number\n announcementBanner?: app.bsky.unspecced.getAtmosphereExploreTab.AnnouncementBanner\n articles: RecordGroup[]\n articleRecIdStr?: string\n publications: RecordGroup[]\n photos: RecordGroup[]\n livestreams: RecordGroup[]\n apps: app.bsky.unspecced.getAtmosphereExploreTab.AppCard[]\n}\n\n/**\n * Randomizes a selected list (Fisher-Yates), then places featured items ahead\n * of ordinary ones. Runs before hydration, so presentation keeps this order.\n */\nfunction orderGroups(groups: RecordGroup[]): RecordGroup[] {\n const shuffled = [...groups]\n for (let i = shuffled.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1))\n ;[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]\n }\n return [\n ...shuffled.filter(({ featured }) => featured),\n ...shuffled.filter(({ featured }) => !featured),\n ].slice(0, MAX_LIST_ITEMS)\n}\n\nfunction completeGroups(groups: SelectedGroup[] = []): RecordGroup[] {\n return mapDefined(groups, ({ featured, canonicalUri }) => {\n const uri = recordUri(canonicalUri)\n if (!uri) return\n return { featured, uri }\n })\n}\n\ntype AnnouncementBanner =\n app.bsky.unspecced.getAtmosphereExploreTab.AnnouncementBanner\n\n/**\n * Returns the single (already validated) banner to show. Eligible (enabled, in window) banners targeted at a matching location\n * win, first in worker order; otherwise the unrestricted eligible banner with\n * the furthest-out `endsAt` (absent means no expiration; ties keep worker order).\n */\nfunction selectBanner(\n banners: ValidBanner[],\n params: { countryCode?: string; regionCode?: string },\n now: number,\n): AnnouncementBanner | undefined {\n let matched: AnnouncementBanner | undefined\n let fallback: AnnouncementBanner | undefined\n let fallbackEnd = -Infinity\n for (const { banner, presentation } of banners) {\n const { status, startsAt, endsAt, locations } = banner\n if (status !== 'enabled') continue\n if (startsAt != null && Date.parse(startsAt) > now) continue\n const end = endsAt == null ? Infinity : Date.parse(endsAt)\n if (end <= now) continue\n if (locations?.length) {\n if (!matched && matchesLocation(locations, params)) {\n matched = presentation\n }\n } else if (end > fallbackEnd) {\n fallback = presentation\n fallbackEnd = end\n }\n }\n const selected = matched ?? fallback\n if (!selected) return\n const { id, title, description, url, image, overlayColor, textColor } =\n selected\n return { id, title, description, url, image, overlayColor, textColor }\n}\n\ntype Selectable = {\n tags?: string[]\n locations?: string[]\n startsAt?: string | null\n endsAt?: string | null\n}\ntype SelectedGroup = l.Infer<typeof groupSchema> & { featured: boolean }\n\n/**\n * Builds a per-list selector that drops location-ineligible entries, orders\n * the rest by priority bucket (location, requested topic, `general`, untagged;\n * stable within a bucket), moves entries with an active schedule window ahead\n * of the rest (stable, so bucket order holds within each side), and keeps the\n * first `MAX_LIST_ITEMS`. A worker-supplied `featured` flag is ignored.\n */\nfunction createSelector(\n params: { countryCode?: string; regionCode?: string },\n headers: IncomingHttpHeaders,\n now: number,\n) {\n const topics = parseTopics(headers['x-atproto-bsky-topics'])\n return (groups: l.Infer<typeof groupSchema>[] = []): SelectedGroup[] => {\n const buckets: l.Infer<typeof groupSchema>[][] = [[], [], [], []]\n for (const group of groups) {\n const bucket = selectionBucket(group, params, topics)\n if (bucket !== undefined) buckets[bucket].push(group)\n }\n const featured: SelectedGroup[] = []\n const ordinary: SelectedGroup[] = []\n for (const group of buckets.flat()) {\n const active = isFeatured(group, now)\n ;(active ? featured : ordinary).push({ ...group, featured: active })\n }\n return [...featured, ...ordinary].slice(0, MAX_LIST_ITEMS)\n }\n}\n\n/** Unlike banners, content needs both bounds to define a featured window. */\nfunction isFeatured({ startsAt, endsAt }: Selectable, now: number): boolean {\n return (\n startsAt != null &&\n endsAt != null &&\n Date.parse(startsAt) <= now &&\n now < Date.parse(endsAt)\n )\n}\n\nfunction selectionBucket(\n { tags, locations }: Selectable,\n params: { countryCode?: string; regionCode?: string },\n topics: Set<string>,\n): 0 | 1 | 2 | 3 | undefined {\n if (locations?.length) {\n if (!matchesLocation(locations, params)) return\n return 0\n }\n if (tags?.some((tag) => topics.has(tag))) return 1\n if (tags?.includes('general')) return 2\n if (!tags?.length) return 3\n}\n\nfunction parseTopics(header: string | string[] | undefined): Set<string> {\n const topics = new Set<string>()\n const value = Array.isArray(header) ? header.join(',') : header\n for (const token of value?.split(',') ?? []) {\n const topic = token.trim()\n if (topic) topics.add(topic)\n }\n return topics\n}\n\n/**\n * True only when a nonempty `locations` list matches the request. Entries are\n * a country (`JP`) or country and region (`US-WI`); a region alone never\n * matches. A full `regionCode` (`US-WI`) is honored only when its country\n * agrees with `countryCode`.\n */\nfunction matchesLocation(\n locations: string[] | undefined,\n { countryCode, regionCode }: { countryCode?: string; regionCode?: string },\n): boolean {\n const country = countryCode?.toUpperCase()\n if (!locations?.length || !country) return false\n let region = regionCode?.toUpperCase() || undefined\n if (region?.includes('-')) {\n const [regionCountry, subdivision] = region.split('-')\n region = regionCountry === country ? subdivision : undefined\n }\n return locations.some((location) => {\n const [locationCountry, locationRegion, ...rest] = location\n .toUpperCase()\n .split('-')\n if (rest.length || locationCountry !== country) return false\n return (\n locationRegion === undefined || (!!region && locationRegion === region)\n )\n })\n}\n\nfunction recordUri(value: unknown): AtUriString | undefined {\n if (!l.isAtUriString(value)) return\n const uri = new AtUri(value)\n return uri.collection && uri.rkey && l.isDidString(uri.hostname)\n ? value\n : undefined\n}\n"]}
1
+ {"version":3,"file":"getAtmosphereExploreTab.js","sourceRoot":"","sources":["../../../../../src/api/app/bsky/unspecced/getAtmosphereExploreTab.ts"],"names":[],"mappings":"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","sourcesContent":["import type { IncomingHttpHeaders } from 'node:http'\nimport { mapDefined, noUndefinedVals } from '@atproto/common'\nimport { type AtUriString, type Client, l } from '@atproto/lex'\nimport { AtUri } from '@atproto/syntax'\nimport {\n MethodNotImplementedError,\n type Server,\n UpstreamFailureError,\n UpstreamTimeoutError,\n} from '@atproto/xrpc-server'\nimport { safeFetchWrap } from '@atproto-labs/fetch-node'\nimport type { AppContext } from '../../../../context.js'\nimport type { HydrateCtx, Hydrator } from '../../../../hydration/hydrator.js'\nimport { app, site } from '../../../../lexicons/index.js'\nimport { httpLogger } from '../../../../logger.js'\nimport {\n type HydrationFnInput,\n type PresentationFnInput,\n type SkeletonFnInput,\n createPipeline,\n noRules,\n} from '../../../../pipeline.js'\nimport { getAtprotoPassthroughHeaders } from '../../../../util/headers.js'\nimport type { Views } from '../../../../views/index.js'\n\nconst MAX_CONFIG_BYTES = 512 * 1024\nconst MAX_LIST_ITEMS = 10\n\nconst groupSchema = l.object({\n canonicalUri: l.optional(l.unknown()),\n startsAt: l.optional(l.nullable(l.string({ format: 'datetime' }))),\n endsAt: l.optional(l.nullable(l.string({ format: 'datetime' }))),\n tags: l.optional(l.array(l.string({ maxLength: 128 }), { maxLength: 100 })),\n locations: l.optional(\n l.array(l.string({ maxLength: 16 }), { maxLength: 100 }),\n ),\n})\nconst bannerSchema = l.object({\n status: l.string({ maxLength: 128 }),\n startsAt: l.optional(l.nullable(l.string({ format: 'datetime' }))),\n endsAt: l.optional(l.nullable(l.string({ format: 'datetime' }))),\n locations: l.optional(\n l.array(l.string({ maxLength: 16 }), { maxLength: 100 }),\n ),\n})\n// Ingestion is best-effort per item: each section is an unvalidated value, and\n// every entry is validated on its own so one bad entry never fails the rest.\nconst configSchema = l.object({\n announcementBanners: l.optional(l.unknown()),\n articles: l.optional(l.unknown()),\n publications: l.optional(l.unknown()),\n photos: l.optional(l.unknown()),\n livestreams: l.optional(l.unknown()),\n apps: l.optional(l.unknown()),\n})\nconst MAX_INPUT_ENTRIES = 100\n\nexport default function (server: Server, ctx: AppContext) {\n const getExplore = createPipeline(skeleton, hydration, noRules, presentation)\n const fetchExploreTabSkeleton = createFetchExploreTabSkeleton()\n server.add(app.bsky.unspecced.getAtmosphereExploreTab, {\n auth: ctx.authVerifier.standardOptional,\n handler: async ({ auth, params, req, signal }) => {\n const service = ctx.cfg.atmosphereExploreTabService\n if (!service)\n throw new MethodNotImplementedError(\n 'Atmosphere Explore service not configured',\n )\n let workerUrl: URL\n try {\n // Location filtering happens locally, so fetch the full config as-is.\n workerUrl = new URL(service)\n } catch {\n throw new UpstreamFailureError(\n 'Unable to fetch Atmosphere Explore configuration',\n )\n }\n const viewer = auth.credentials.iss\n const hydrateCtx = await ctx.hydrator.createContext({\n viewer,\n labelers: ctx.reqLabelers(req),\n features: ctx.featureGatesClient.scope(\n ctx.featureGatesClient.parseUserContextFromHandler({ viewer, req }),\n ),\n xrpcEndpoint: app.bsky.unspecced.getAtmosphereExploreTab.$lxm,\n })\n const useIris = hydrateCtx.features.checkGate(\n hydrateCtx.features.Gate.AtmosphereExploreArticlesIrisEnable,\n )\n // Use 501 instead of 500 as these are not considered retry-able by clients\n if (useIris && !ctx.irisClient)\n throw new MethodNotImplementedError('Iris agent not available')\n const result = await getExplore(\n {\n ...params,\n hydrateCtx,\n workerUrl,\n requestHeaders: req.headers,\n headers: noUndefinedVals({\n 'accept-language': params.langs?.length\n ? params.langs.join(',')\n : req.headers['accept-language'],\n ...getAtprotoPassthroughHeaders(req),\n }),\n signal,\n },\n {\n hydrator: ctx.hydrator,\n views: ctx.views,\n irisClient: useIris ? ctx.irisClient : undefined,\n fetchExploreTabSkeleton,\n },\n )\n return { encoding: 'application/json', body: result }\n },\n })\n}\n\nasync function skeleton({\n ctx,\n params,\n}: SkeletonFnInput<Context, Params>): Promise<SkeletonState> {\n const [config, recommendations] = await Promise.all([\n ctx.fetchExploreTabSkeleton(params.workerUrl, params.signal),\n ctx.irisClient\n ? ctx.irisClient\n .call(\n app.bsky.unspecced.getAtmosphereExploreArticleRecommendations,\n {\n limit: MAX_LIST_ITEMS,\n viewer: params.hydrateCtx.viewer ?? undefined,\n },\n {\n headers: params.headers,\n signal: params.signal,\n validateResponse: true,\n },\n )\n // @NOTE Request and response-validation failures use curated articles.\n .catch((err) => {\n if (params.signal.aborted) throw err\n httpLogger.warn(\n { err },\n 'Unable to fetch Atmosphere Explore article recommendations',\n )\n return undefined\n })\n : undefined,\n ])\n const now = Date.now()\n const announcementBanner = selectBanner(\n config.announcementBanners,\n params,\n now,\n )\n const select = createSelector(params, params.requestHeaders, now)\n return {\n now,\n announcementBanner,\n articles: orderGroups(\n recommendations && recommendations.articles.length > 0\n ? mapDefined(\n recommendations.articles.slice(0, MAX_LIST_ITEMS),\n ({ uri }) => {\n const root = recordUri(uri)\n return root &&\n new AtUri(root).collection === site.standard.document.$type\n ? { featured: false, uri: root }\n : undefined\n },\n )\n : completeGroups(select(config.articles)),\n ),\n articleRecIdStr: recommendations?.recIdStr,\n publications: orderGroups(completeGroups(select(config.publications))),\n photos: orderGroups(completeGroups(select(config.photos))),\n livestreams: orderGroups(completeGroups(select(config.livestreams))),\n // Apps have no hydration root or featured flag: keep worker order.\n apps: config.apps.slice(0, MAX_LIST_ITEMS),\n }\n}\n\nasync function hydration({\n ctx,\n params,\n skeleton,\n}: HydrationFnInput<Context, Params, SkeletonState>) {\n const uris = [\n ...new Set(\n [\n ...skeleton.articles,\n ...skeleton.publications,\n ...skeleton.photos,\n ...skeleton.livestreams,\n ].map(({ uri }) => uri),\n ),\n ]\n return ctx.hydrator.hydrateEmbedExternalViewFromUris(uris, params.hydrateCtx)\n}\n\nfunction presentation({\n ctx,\n skeleton,\n hydration: state,\n}: PresentationFnInput<\n Context,\n Params,\n SkeletonState\n>): app.bsky.unspecced.getAtmosphereExploreTab.$OutputBody {\n const { now, articles, publications, photos, livestreams } = skeleton\n return {\n announcementBanner: skeleton.announcementBanner,\n articles: {\n items: mapDefined(articles, ({ featured, uri }) => {\n const view = ctx.views.externalRecordView(uri, state, now)\n return view && app.bsky.embed.external.viewArticle.$isTypeOf(view)\n ? { featured, view }\n : undefined\n }),\n recIdStr: skeleton.articleRecIdStr,\n },\n publications: {\n items: mapDefined(publications, ({ featured, uri }) => {\n const view = ctx.views.externalRecordView(uri, state, now)\n return view &&\n app.bsky.embed.external.viewArticlePublication.$isTypeOf(view)\n ? { featured, view }\n : undefined\n }),\n },\n photos: {\n items: mapDefined(photos, ({ featured, uri }) => {\n const view = ctx.views.externalRecordView(uri, state, now)\n return view &&\n app.bsky.embed.external.viewGallery.$isTypeOf(view) &&\n view.items.length\n ? { featured, view }\n : undefined\n }),\n },\n livestreams: {\n items: mapDefined(livestreams, ({ featured, uri }) => {\n const view = ctx.views.externalRecordView(uri, state, now)\n return view && app.bsky.embed.external.viewLivestream.$isTypeOf(view)\n ? { featured, view }\n : undefined\n }),\n },\n apps: skeleton.apps,\n }\n}\n\nfunction createFetchExploreTabSkeleton() {\n // @NOTE The wrapper caps decoded response bytes, including compressed bodies.\n const fetchConfig = safeFetchWrap({\n timeout: 5000,\n responseMaxSize: MAX_CONFIG_BYTES,\n })\n\n return async function fetchExploreTabSkeleton(\n url: URL,\n signal: AbortSignal,\n ): Promise<ExploreConfig> {\n try {\n const response = await fetchConfig(url, {\n headers: { accept: 'application/json' },\n redirect: 'error',\n signal,\n })\n if (!response.ok) {\n await response.body?.cancel()\n throw new UpstreamFailureError(\n `Atmosphere Explore service returned HTTP ${response.status}`,\n )\n }\n return normalizeConfig(await response.json())\n } catch (err) {\n if (err instanceof UpstreamFailureError) throw err\n if (\n err instanceof Error &&\n (err.name === 'TimeoutError' || err.name === 'AbortError')\n ) {\n throw new UpstreamTimeoutError('Atmosphere Explore request timed out')\n }\n throw new UpstreamFailureError(\n 'Unable to fetch Atmosphere Explore configuration',\n )\n }\n }\n}\n\ntype Group = l.Infer<typeof groupSchema>\ntype AppCard = app.bsky.unspecced.getAtmosphereExploreTab.AppCard\ntype ValidBanner = {\n banner: l.Infer<typeof bannerSchema>\n presentation: AnnouncementBanner\n}\ntype ExploreConfig = {\n announcementBanners: ValidBanner[]\n articles: Group[]\n publications: Group[]\n photos: Group[]\n livestreams: Group[]\n apps: AppCard[]\n}\n\n/**\n * Keeps only the well-formed items of a successfully decoded worker body. Only\n * the first `MAX_INPUT_ENTRIES` entries of each section are examined, and\n * malformed entries are skipped whole, never coerced or repaired.\n */\nfunction normalizeConfig(body: unknown): ExploreConfig {\n const raw = configSchema.safeParse(body)\n const config = raw.success ? raw.value : {}\n const entries = (value: unknown): unknown[] =>\n Array.isArray(value) ? value.slice(0, MAX_INPUT_ENTRIES) : []\n const valid = <T>(schema: l.Schema<T>, value: unknown): T[] =>\n entries(value).flatMap((entry) => {\n const result = schema.safeParse(entry)\n return result.success ? [result.value] : []\n })\n return {\n announcementBanners: entries(config.announcementBanners).flatMap(\n (entry) => {\n const banner = bannerSchema.safeParse(entry)\n const presentation =\n app.bsky.unspecced.getAtmosphereExploreTab.announcementBanner.$safeValidate(\n entry,\n )\n return banner.success && presentation.success\n ? [{ banner: banner.value, presentation: presentation.value }]\n : []\n },\n ),\n articles: valid(groupSchema, config.articles),\n publications: valid(groupSchema, config.publications),\n photos: valid(groupSchema, config.photos),\n livestreams: valid(groupSchema, config.livestreams),\n apps: valid(\n app.bsky.unspecced.getAtmosphereExploreTab.appCard,\n config.apps,\n ),\n }\n}\n\ntype Context = {\n hydrator: Hydrator\n views: Views\n irisClient: Client | undefined\n fetchExploreTabSkeleton: ReturnType<typeof createFetchExploreTabSkeleton>\n}\n\ntype Params = app.bsky.unspecced.getAtmosphereExploreTab.$Params & {\n hydrateCtx: HydrateCtx\n workerUrl: URL\n /** Recommendation headers for Iris only. */\n headers: Record<string, string>\n requestHeaders: IncomingHttpHeaders\n signal: AbortSignal\n}\n\ntype RecordGroup = { featured: boolean; uri: AtUriString }\n\ntype SkeletonState = {\n now: number\n announcementBanner?: app.bsky.unspecced.getAtmosphereExploreTab.AnnouncementBanner\n articles: RecordGroup[]\n articleRecIdStr?: string\n publications: RecordGroup[]\n photos: RecordGroup[]\n livestreams: RecordGroup[]\n apps: app.bsky.unspecced.getAtmosphereExploreTab.AppCard[]\n}\n\n/**\n * Randomizes a selected list (Fisher-Yates), then places featured items ahead\n * of ordinary ones. Runs before hydration, so presentation keeps this order.\n */\nfunction orderGroups(groups: RecordGroup[]): RecordGroup[] {\n const shuffled = [...groups]\n for (let i = shuffled.length - 1; i > 0; i--) {\n const j = Math.floor(Math.random() * (i + 1))\n ;[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]]\n }\n return [\n ...shuffled.filter(({ featured }) => featured),\n ...shuffled.filter(({ featured }) => !featured),\n ].slice(0, MAX_LIST_ITEMS)\n}\n\nfunction completeGroups(groups: SelectedGroup[] = []): RecordGroup[] {\n return mapDefined(groups, ({ featured, canonicalUri }) => {\n const uri = recordUri(canonicalUri)\n if (!uri) return\n return { featured, uri }\n })\n}\n\ntype AnnouncementBanner =\n app.bsky.unspecced.getAtmosphereExploreTab.AnnouncementBanner\n\n/**\n * Returns the single (already validated) banner to show. Eligible (enabled, in window) banners targeted at a matching location\n * win, first in worker order; otherwise the unrestricted eligible banner with\n * the furthest-out `endsAt` (absent means no expiration; ties keep worker order).\n */\nfunction selectBanner(\n banners: ValidBanner[],\n params: { countryCode?: string; regionCode?: string },\n now: number,\n): AnnouncementBanner | undefined {\n let matched: AnnouncementBanner | undefined\n let fallback: AnnouncementBanner | undefined\n let fallbackEnd = -Infinity\n for (const { banner, presentation } of banners) {\n const { status, startsAt, endsAt, locations } = banner\n if (status !== 'enabled') continue\n if (startsAt != null && Date.parse(startsAt) > now) continue\n const end = endsAt == null ? Infinity : Date.parse(endsAt)\n if (end <= now) continue\n if (locations?.length) {\n if (!matched && matchesLocation(locations, params)) {\n matched = presentation\n }\n } else if (end > fallbackEnd) {\n fallback = presentation\n fallbackEnd = end\n }\n }\n const selected = matched ?? fallback\n if (!selected) return\n const { id, title, description, url, image, overlayColor, textColor } =\n selected\n return { id, title, description, url, image, overlayColor, textColor }\n}\n\ntype Selectable = {\n tags?: string[]\n locations?: string[]\n startsAt?: string | null\n endsAt?: string | null\n}\ntype SelectedGroup = l.Infer<typeof groupSchema> & { featured: boolean }\n\n/**\n * Builds a per-list selector that drops location-ineligible entries, orders\n * the rest by priority bucket (location, requested topic, `general`, untagged;\n * stable within a bucket), moves entries with an active schedule window ahead\n * of the rest (stable, so bucket order holds within each side), and keeps the\n * first `MAX_LIST_ITEMS`. A worker-supplied `featured` flag is ignored.\n */\nfunction createSelector(\n params: { countryCode?: string; regionCode?: string },\n headers: IncomingHttpHeaders,\n now: number,\n) {\n const topics = parseTopics(headers['x-atproto-bsky-topics'])\n return (groups: l.Infer<typeof groupSchema>[] = []): SelectedGroup[] => {\n const buckets: l.Infer<typeof groupSchema>[][] = [[], [], [], []]\n for (const group of groups) {\n const bucket = selectionBucket(group, params, topics)\n if (bucket !== undefined) buckets[bucket].push(group)\n }\n const featured: SelectedGroup[] = []\n const ordinary: SelectedGroup[] = []\n for (const group of buckets.flat()) {\n const active = isFeatured(group, now)\n ;(active ? featured : ordinary).push({ ...group, featured: active })\n }\n return [...featured, ...ordinary].slice(0, MAX_LIST_ITEMS)\n }\n}\n\n/** Unlike banners, content needs both bounds to define a featured window. */\nfunction isFeatured({ startsAt, endsAt }: Selectable, now: number): boolean {\n return (\n startsAt != null &&\n endsAt != null &&\n Date.parse(startsAt) <= now &&\n now < Date.parse(endsAt)\n )\n}\n\nfunction selectionBucket(\n { tags, locations }: Selectable,\n params: { countryCode?: string; regionCode?: string },\n topics: Set<string>,\n): 0 | 1 | 2 | 3 | undefined {\n if (locations?.length) {\n if (!matchesLocation(locations, params)) return\n return 0\n }\n if (tags?.some((tag) => topics.has(tag))) return 1\n if (tags?.includes('general')) return 2\n if (!tags?.length) return 3\n}\n\nfunction parseTopics(header: string | string[] | undefined): Set<string> {\n const topics = new Set<string>()\n const value = Array.isArray(header) ? header.join(',') : header\n for (const token of value?.split(',') ?? []) {\n const topic = token.trim()\n if (topic) topics.add(topic)\n }\n return topics\n}\n\n/**\n * True only when a nonempty `locations` list matches the request. Entries are\n * a country (`JP`) or country and region (`US-WI`); a region alone never\n * matches. A full `regionCode` (`US-WI`) is honored only when its country\n * agrees with `countryCode`.\n */\nfunction matchesLocation(\n locations: string[] | undefined,\n { countryCode, regionCode }: { countryCode?: string; regionCode?: string },\n): boolean {\n const country = countryCode?.toUpperCase()\n if (!locations?.length || !country) return false\n let region = regionCode?.toUpperCase() || undefined\n if (region?.includes('-')) {\n const [regionCountry, subdivision] = region.split('-')\n region = regionCountry === country ? subdivision : undefined\n }\n return locations.some((location) => {\n const [locationCountry, locationRegion, ...rest] = location\n .toUpperCase()\n .split('-')\n if (rest.length || locationCountry !== country) return false\n return (\n locationRegion === undefined || (!!region && locationRegion === region)\n )\n })\n}\n\nfunction recordUri(value: unknown): AtUriString | undefined {\n if (!l.isAtUriString(value)) return\n const uri = new AtUri(value)\n return uri.collection && uri.rkey && l.isDidString(uri.hostname)\n ? value\n : undefined\n}\n"]}
@@ -1,6 +1,6 @@
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
+ import { type ExternalHydrationRoot, type ExternalHydrationWork, type HydrationXrpcEndpoint } from '../telemetry/events.js';
4
4
  import type { SiteStandardDocumentRecord, SiteStandardPublicationRecord } from '../views/types.js';
5
5
  import type { Actors } from './actor.js';
6
6
  import type { Labels } from './label.js';
@@ -60,10 +60,23 @@ export declare class ExternalHydrator {
60
60
  static readonly MAX_BACKLINK_FANOUT = 8;
61
61
  /** Hard cap on generic batches per traversal, counting the root as pass 1. */
62
62
  static readonly MAX_EXTERNAL_HYDRATION_PASSES = 8;
63
+ /**
64
+ * Entries per generic record RPC. The dataplane rejects larger URI lookups;
65
+ * exact-ref lookups use the same bound defensively.
66
+ */
67
+ static readonly MAX_RECORDS_PER_REQUEST = 100;
63
68
  constructor(dataplane: DataPlaneClient);
64
- /** Fetch exact record versions; unavailable records are represented by null. */
69
+ /**
70
+ * Fetch exact record versions; unavailable records are represented by null.
71
+ * Large inputs are fetched in sequential bounded requests; any failed request
72
+ * rejects the whole call.
73
+ */
65
74
  getRecordsByRef(refs: Required<ItemRef>[], includeTakedowns?: boolean): Promise<ExternalRecordsByRef>;
66
- /** Fetch the latest indexed versions without collection-specific validation. */
75
+ /**
76
+ * Fetch the latest indexed versions without collection-specific validation.
77
+ * Large inputs are fetched in sequential bounded requests; any failed request
78
+ * rejects the whole call.
79
+ */
67
80
  getRecordsByURI(uris: AtUriString[], includeTakedowns?: boolean): Promise<ExternalRecords>;
68
81
  /** Fetch a page of activity refs for subsequent generic record hydration. */
69
82
  getAtmosphereTimeline(viewerDid: DidString, opts?: {
@@ -115,18 +128,28 @@ export declare function actionExternalRecordTakedowns({ externalRecords, externa
115
128
  }, includeTakedowns?: boolean): void;
116
129
  /**
117
130
  * Generic lookup work of one external hydration traversal, shared by reference
118
- * through its nested passes. `capped` records hitting the pass limit.
131
+ * through its nested passes. `capped` records hitting the pass limit; `partial`
132
+ * records a recovered best-effort dependency failure.
119
133
  */
120
134
  export type ExternalHydrationTracker = ExternalHydrationWork & {
121
135
  capped: boolean;
136
+ partial: boolean;
137
+ /**
138
+ * Internal: set by the traversal's first dependency discovery, whose refs
139
+ * are its roots.
140
+ */
141
+ rootsCounted: boolean;
122
142
  };
123
143
  /**
124
144
  * 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.
145
+ * there is none. Only the owner reports, once, including on failure, with the
146
+ * context's XRPC endpoint (`'unknown'` if unset) and the traversal's wall time. A
147
+ * returned traversal is `capped` if it hit the pass limit, else `partial` if
148
+ * it recovered from a dependency failure. Owners that completed without
149
+ * scheduling a batch did no work and are not reported; other outcomes always
150
+ * are.
128
151
  */
129
- export declare function trackExternalHydration<T>(root: ExternalHydrationRoot, hydrationMetrics: ExternalHydrationTracker | undefined, fn: (hydrationMetrics: ExternalHydrationTracker) => Promise<T>): Promise<T>;
152
+ export declare function trackExternalHydration<T>(root: ExternalHydrationRoot, xrpcEndpoint: HydrationXrpcEndpoint | undefined, hydrationMetrics: ExternalHydrationTracker | undefined, fn: (hydrationMetrics: ExternalHydrationTracker) => Promise<T>): Promise<T>;
130
153
  export type AssociatedSiteStandardRecord<T> = {
131
154
  ref: {
132
155
  uri: AtUriString;
@@ -1 +1 @@
1
- {"version":3,"file":"external.d.ts","sourceRoot":"","sources":["../../src/hydration/external.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,WAAW,EAChB,KAAK,SAAS,EAEd,KAAK,UAAU,EAEf,KAAK,WAAW,EAGjB,MAAM,cAAc,CAAA;AAErB,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAA;AAOpE,OAAO,EAEL,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAE3B,MAAM,wBAAwB,CAAA;AAE/B,OAAO,KAAK,EACV,0BAA0B,EAC1B,6BAA6B,EAC9B,MAAM,mBAAmB,CAAA;AAC1B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AACxC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAA;AACxC,OAAO,EACL,YAAY,EACZ,KAAK,OAAO,EACZ,KAAK,UAAU,EAGhB,MAAM,WAAW,CAAA;AAElB,8EAA8E;AAC9E,MAAM,MAAM,cAAc,GAAG,UAAU,CAAC,WAAW,CAAC,CAAA;AACpD,MAAM,MAAM,eAAe,GAAG,YAAY,CAAC,WAAW,EAAE,cAAc,CAAC,CAAA;AACvE,6EAA6E;AAC7E,MAAM,MAAM,oBAAoB,GAAG,YAAY,CAAC,cAAc,EAAE,cAAc,CAAC,CAAA;AAC/E,MAAM,MAAM,uBAAuB,GAAG,YAAY,CAAC,WAAW,EAAE,WAAW,EAAE,CAAC,CAAA;AAE9E,MAAM,MAAM,sBAAsB,GAAG;IACnC,GAAG,EAAE,WAAW,CAAA;IAChB,QAAQ,EAAE,IAAI,CAAA;CACf,CAAA;AAED,MAAM,MAAM,kBAAkB,GAAG;IAC/B,KAAK,EAAE,sBAAsB,EAAE,CAAA;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,MAAM,MAAM,mBAAmB,GAAG;IAChC,SAAS,EAAE,sBAAsB,EAAE,CAAA;IACnC,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB,CAAA;AAED,MAAM,MAAM,4BAA4B,GAAG,YAAY,CACrD,WAAW,EACX,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CACpC,CAAA;AAED,MAAM,MAAM,0BAA0B,GAAG;IACvC,SAAS,EAAE,YAAY,CAAC,WAAW,EAAE,WAAW,EAAE,CAAC,CAAA;IACnD,SAAS,EAAE,OAAO,CAAA;CACnB,CAAA;AAED,OAAO,CAAC,MAAM,oBAAoB,EAAE,OAAO,MAAM,CAAA;AAEjD;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG;IAAE,CAAC,oBAAoB,CAAC,EAAE,IAAI,CAAA;CAAE,CAAA;AAEtE,MAAM,MAAM,oBAAoB,GAAG,UAAU,CAAC,0BAA0B,CAAC,CAAA;AACzE,MAAM,MAAM,uBAAuB,GAAG,UAAU,CAAC,6BAA6B,CAAC,CAAA;AAE/E;;;;;GAKG;AACH,MAAM,MAAM,qBAAqB,GAAG,YAAY,CAC9C,cAAc,EACd,oBAAoB,CACrB,CAAA;AACD;;GAEG;AACH,MAAM,MAAM,wBAAwB,GAAG,YAAY,CACjD,cAAc,EACd,uBAAuB,CACxB,CAAA;AACD,MAAM,MAAM,mBAAmB,GAAG;IAChC,SAAS,EAAE,qBAAqB,CAAA;IAChC,YAAY,EAAE,wBAAwB,CAAA;CACvC,CAAA;AAED,qBAAa,gBAAgB;IAOR,SAAS,EAAE,eAAe;IAN7C,MAAM,CAAC,QAAQ,CAAC,qBAAqB,KAAI;IACzC,MAAM,CAAC,QAAQ,CAAC,0BAA0B,MAAK;IAC/C,MAAM,CAAC,QAAQ,CAAC,mBAAmB,KAAI;IACvC,8EAA8E;IAC9E,MAAM,CAAC,QAAQ,CAAC,6BAA6B,KAAI;IAEjD,YAAmB,SAAS,EAAE,eAAe,EAAI;IAEjD,gFAAgF;IAC1E,eAAe,CACnB,IAAI,EAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,EACzB,gBAAgB,UAAQ,GACvB,OAAO,CAAC,oBAAoB,CAAC,CAY/B;IAED,gFAAgF;IAC1E,eAAe,CACnB,IAAI,EAAE,WAAW,EAAE,EACnB,gBAAgB,UAAQ,GACvB,OAAO,CAAC,eAAe,CAAC,CAY1B;IAED,6EAA6E;IACvE,qBAAqB,CACzB,SAAS,EAAE,SAAS,EACpB,IAAI,GAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAO,GAC7C,OAAO,CAAC,kBAAkB,CAAC,CAY7B;IAED,2EAA2E;IACrE,2BAA2B,CAC/B,UAAU,EAAE,WAAW,EAAE,EACzB,UAAU,CAAC,EAAE,UAAU,GACtB,OAAO,CAAC,4BAA4B,CAAC,CAoBvC;IAED,0DAA0D;IACpD,sBAAsB,CAC1B,SAAS,EAAE,WAAW,EACtB,UAAU,EAAE,UAAU,EACtB,IAAI,GAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAO,GAC7C,OAAO,CAAC,mBAAmB,CAAC,CAa9B;IAED,8EAA8E;IACxE,6BAA6B,CACjC,UAAU,EAAE,WAAW,EAAE,EACzB,QAAQ,EAAE,SAAS,EACnB,UAAU,EAAE,UAAU,GACrB,OAAO,CAAC,0BAA0B,CAAC,CAiBrC;IAED;;;OAGG;IACG,2BAA2B,CAC/B,IAAI,EAAE,OAAO,EAAE,EACf,gBAAgB,UAAQ,GACvB,OAAO,CAAC,mBAAmB,CAAC,CAa9B;CACF;AAED;;;;;;;;GAQG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,QAAQ,CAAC,OAAO,CAAC,GAAG,cAAc,CAAA;AACxE,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAAA;AAOtD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,cAAc,GAAG,QAAQ,CAAC,OAAO,CAAC,CAS5E;AAED;;;;;GAKG;AACH,wBAAgB,6BAA6B,CAC3C,EACE,eAAe,EACf,oBAAoB,EACpB,MAAM,EACN,MAAM,GACP,EAAE;IACD,eAAe,CAAC,EAAE,eAAe,CAAA;IACjC,oBAAoB,CAAC,EAAE,oBAAoB,CAAA;IAC3C,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB,EACD,gBAAgB,CAAC,EAAE,OAAO,GACzB,IAAI,CAgBN;AAED;;;GAGG;AACH,MAAM,MAAM,wBAAwB,GAAG,qBAAqB,GAAG;IAC7D,MAAM,EAAE,OAAO,CAAA;CAChB,CAAA;AAED;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAAC,CAAC,EAC5C,IAAI,EAAE,qBAAqB,EAC3B,gBAAgB,EAAE,wBAAwB,GAAG,SAAS,EACtD,EAAE,EAAE,CAAC,gBAAgB,EAAE,wBAAwB,KAAK,OAAO,CAAC,CAAC,CAAC,GAC7D,OAAO,CAAC,CAAC,CAAC,CAoBZ;AAyFD,MAAM,MAAM,4BAA4B,CAAC,CAAC,IAAI;IAC5C,GAAG,EAAE;QAAE,GAAG,EAAE,WAAW,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,CAAA;IACtC,IAAI,EAAE,CAAC,CAAA;CACR,CAAA;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,6CAA6C,mBACxC,SAAS;IAAE,GAAG,EAAE,WAAW,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,EAAE,GAAG,SAAS,aAC7D,qBAAqB,GAAG,SAAS,gBAC9B,wBAAwB,GAAG,SAAS,KACjD;IACD,QAAQ,EAAE,4BAA4B,CAAC,oBAAoB,CAAC,GAAG,SAAS,CAAA;IACxE,WAAW,EAAE,4BAA4B,CAAC,uBAAuB,CAAC,GAAG,SAAS,CAAA;CAuD/E,CAAA"}
1
+ {"version":3,"file":"external.d.ts","sourceRoot":"","sources":["../../src/hydration/external.ts"],"names":[],"mappings":"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"}
@@ -1,3 +1,5 @@
1
+ import { performance } from 'node:perf_hooks';
2
+ import { chunkArray } from '@atproto/common';
1
3
  import { isTypedLexMap, lexParseJsonBytes, } from '@atproto/lex';
2
4
  import { parseAtUriString } from '@atproto/syntax';
3
5
  import { site } from '../lexicons/index.js';
@@ -12,28 +14,43 @@ export class ExternalHydrator {
12
14
  static { this.MAX_BACKLINK_FANOUT = 8; }
13
15
  /** Hard cap on generic batches per traversal, counting the root as pass 1. */
14
16
  static { this.MAX_EXTERNAL_HYDRATION_PASSES = 8; }
17
+ /**
18
+ * Entries per generic record RPC. The dataplane rejects larger URI lookups;
19
+ * exact-ref lookups use the same bound defensively.
20
+ */
21
+ static { this.MAX_RECORDS_PER_REQUEST = 100; }
15
22
  constructor(dataplane) {
16
23
  this.dataplane = dataplane;
17
24
  }
18
- /** Fetch exact record versions; unavailable records are represented by null. */
25
+ /**
26
+ * Fetch exact record versions; unavailable records are represented by null.
27
+ * Large inputs are fetched in sequential bounded requests; any failed request
28
+ * rejects the whole call.
29
+ */
19
30
  async getRecordsByRef(refs, includeTakedowns = false) {
20
31
  const map = new HydrationMap();
21
- if (!refs.length)
22
- return map;
23
- const res = await this.dataplane.getRecordsByRef({ refs });
24
- for (let i = 0; i < refs.length; i++) {
25
- map.set(genericRecordKey(refs[i]), parseGenericRecord(res.results[i], includeTakedowns) ?? null);
32
+ for (const chunk of chunkArray(refs, ExternalHydrator.MAX_RECORDS_PER_REQUEST)) {
33
+ const res = await this.dataplane.getRecordsByRef({ refs: chunk });
34
+ // Results are positional within each request.
35
+ for (let i = 0; i < chunk.length; i++) {
36
+ map.set(genericRecordKey(chunk[i]), parseGenericRecord(res.results[i], includeTakedowns) ?? null);
37
+ }
26
38
  }
27
39
  return map;
28
40
  }
29
- /** Fetch the latest indexed versions without collection-specific validation. */
41
+ /**
42
+ * Fetch the latest indexed versions without collection-specific validation.
43
+ * Large inputs are fetched in sequential bounded requests; any failed request
44
+ * rejects the whole call.
45
+ */
30
46
  async getRecordsByURI(uris, includeTakedowns = false) {
31
47
  const map = new HydrationMap();
32
- if (!uris.length)
33
- return map;
34
- const res = await this.dataplane.getRecordsByURI({ uris });
35
- for (let i = 0; i < uris.length; i++) {
36
- map.set(uris[i], parseGenericRecord(res.results[i], includeTakedowns) ?? null);
48
+ for (const chunk of chunkArray(uris, ExternalHydrator.MAX_RECORDS_PER_REQUEST)) {
49
+ const res = await this.dataplane.getRecordsByURI({ uris: chunk });
50
+ // Results are positional within each request.
51
+ for (let i = 0; i < chunk.length; i++) {
52
+ map.set(chunk[i], parseGenericRecord(res.results[i], includeTakedowns) ?? null);
53
+ }
37
54
  }
38
55
  return map;
39
56
  }
@@ -160,29 +177,49 @@ export function actionExternalRecordTakedowns({ externalRecords, externalRecords
160
177
  }
161
178
  /**
162
179
  * 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.
180
+ * there is none. Only the owner reports, once, including on failure, with the
181
+ * context's XRPC endpoint (`'unknown'` if unset) and the traversal's wall time. A
182
+ * returned traversal is `capped` if it hit the pass limit, else `partial` if
183
+ * it recovered from a dependency failure. Owners that completed without
184
+ * scheduling a batch did no work and are not reported; other outcomes always
185
+ * are.
166
186
  */
167
- export async function trackExternalHydration(root, hydrationMetrics, fn) {
187
+ export async function trackExternalHydration(root, xrpcEndpoint, hydrationMetrics, fn) {
168
188
  if (hydrationMetrics)
169
189
  return fn(hydrationMetrics);
170
- const owned = { recordLookups: 0, batches: 0, maxPass: 0, capped: false };
190
+ const owned = {
191
+ recordLookups: 0,
192
+ batches: 0,
193
+ maxPass: 0,
194
+ roots: 0,
195
+ rootsResolved: 0,
196
+ dependencies: 0,
197
+ dependenciesResolved: 0,
198
+ capped: false,
199
+ partial: false,
200
+ rootsCounted: false,
201
+ };
171
202
  let outcome = 'failed';
203
+ const start = performance.now();
172
204
  try {
173
205
  const state = await fn(owned);
174
- outcome = owned.capped ? 'capped' : 'completed';
206
+ outcome = owned.capped ? 'capped' : owned.partial ? 'partial' : 'completed';
175
207
  return state;
176
208
  }
177
209
  finally {
178
210
  if (owned.batches || outcome !== 'completed') {
179
- const { recordLookups, batches, maxPass } = owned;
180
211
  events.externalHydrationTraversal({
212
+ xrpcEndpoint: xrpcEndpoint ?? 'unknown',
181
213
  root,
182
214
  outcome,
183
- recordLookups,
184
- batches,
185
- maxPass,
215
+ durationMs: performance.now() - start,
216
+ recordLookups: owned.recordLookups,
217
+ batches: owned.batches,
218
+ maxPass: owned.maxPass,
219
+ roots: owned.roots,
220
+ rootsResolved: owned.rootsResolved,
221
+ dependencies: owned.dependencies,
222
+ dependenciesResolved: owned.dependenciesResolved,
186
223
  });
187
224
  }
188
225
  }