This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

web: optimize profile page fetch to single enrichResponse call

Signed-off-by: dawn <dawn@tangled.org>

author
dawn
date (Aug 1, 2026, 1:57 PM +0300) commit 2796c1f4 parent 6b4b5877 change-id mwkyusrm
+55 -36
+55 -36
web/src/routes/[handle]/+layout.ts
··· 1 1 import { error, redirect } from "@sveltejs/kit"; 2 2 import { createBobbinClient } from "$lib/api/client"; 3 - import { resolveMiniDoc } from "$lib/api/identity"; 3 + import type { MiniDoc } from "$lib/api/identity"; 4 4 import { getProfile, type ProfileRecord } from "$lib/api/records"; 5 - import { count } from "$lib/api/count"; 6 - import { parallel, toHttpError, httpStatusFor } from "$lib/api/load"; 5 + import { 6 + enrich, 7 + countOf, 8 + viewerUriOf, 9 + TYPE_COUNT, 10 + TYPE_VIEWER, 11 + type LinkDescriptor, 12 + type LinkSource 13 + } from "$lib/api/enrich"; 14 + import { toHttpError, httpStatusFor } from "$lib/api/load"; 7 15 import { ClientResponseError } from "$lib/api/client"; 8 - import { getFollowRkey } from "$lib/api/graph"; 16 + import { rkeyFromUri } from "$lib/api/uri"; 9 17 import type { ProfileCounts } from "$lib/components/profile/types"; 10 18 import type { LayoutLoad } from "./$types"; 11 19 20 + const REPOS: LinkSource = "sh.tangled.repo:subject"; 21 + const STRINGS: LinkSource = "sh.tangled.string:subject"; 22 + const STARS: LinkSource = "sh.tangled.feed.star:.repo"; 23 + const FOLLOWERS: LinkSource = "sh.tangled.graph.follow:subject"; 24 + const FOLLOWING: LinkSource = "sh.tangled.graph.follow:.repo"; 25 + const VOUCHES: LinkSource = "sh.tangled.graph.vouch:subject"; 26 + 27 + const COUNT_DESCRIPTORS: LinkDescriptor[] = [ 28 + REPOS, 29 + STRINGS, 30 + STARS, 31 + FOLLOWERS, 32 + FOLLOWING, 33 + VOUCHES 34 + ].map((source) => ({ source, type: TYPE_COUNT })); 35 + 12 36 export const load: LayoutLoad = async (event) => { 13 37 const parent = await event.parent(); 14 38 const identifier = decodeURIComponent(event.params.handle); ··· 19 43 } 20 44 21 45 const ctx = createBobbinClient({ serviceUrl: parent.publicConfig.bobbinUrl, fetch: event.fetch }); 22 - const doc = await resolveMiniDoc(ctx, identifier).catch((cause) => 23 - toHttpError(cause, "Could not resolve user") 24 - ); 46 + const viewerDid = parent.auth?.did; 47 + const resolved = await enrich<MiniDoc>(ctx, { 48 + xrpc: "com.bad-example.identity.resolveMiniDoc", 49 + params: { identifier }, 50 + enrich: [ 51 + ...COUNT_DESCRIPTORS, 52 + ...(viewerDid ? [{ source: FOLLOWERS, type: TYPE_VIEWER }] : []) 53 + ], 54 + ...(viewerDid ? { viewer: viewerDid } : {}) 55 + }).catch((cause) => toHttpError(cause, "Could not resolve user")); 56 + const doc = resolved.output; 25 57 26 58 // redirects dids and stale handles to the canonical handle url 27 59 const canonical = doc.handle && !doc.handle.endsWith(".invalid") ? doc.handle : null; ··· 30 62 } 31 63 32 64 const did = doc.did; 33 - const viewerDid = parent.auth?.did; 34 65 35 - let profile: ProfileRecord | null = null; 36 - try { 37 - profile = (await getProfile(ctx, did)).value; 38 - } catch (cause) { 39 - if (!(cause instanceof ClientResponseError && httpStatusFor(cause) === 404)) { 40 - toHttpError(cause, "Could not load profile"); 41 - } 42 - } 43 - 44 - const raw = await parallel({ 45 - repos: count(ctx, "sh.tangled.repo.countRepos", did), 46 - strings: count(ctx, "sh.tangled.string.countStrings", did), 47 - stars: count(ctx, "sh.tangled.feed.countStarsBy", did), 48 - followers: count(ctx, "sh.tangled.graph.countFollows", did), 49 - following: count(ctx, "sh.tangled.graph.countFollowsBy", did), 50 - vouches: count(ctx, "sh.tangled.graph.countVouches", did), 51 - viewerFollowRkey: 52 - viewerDid && viewerDid !== did 53 - ? getFollowRkey(ctx, viewerDid, did).catch(() => null) 54 - : Promise.resolve(null) 55 - }); 66 + const profile: ProfileRecord | null = await getProfile(ctx, did) 67 + .then((view) => view.value) 68 + .catch((cause) => { 69 + if (cause instanceof ClientResponseError && httpStatusFor(cause) === 404) return null; 70 + return toHttpError(cause, "Could not load profile"); 71 + }); 56 72 57 73 const counts: ProfileCounts = { 58 - repos: raw.repos.count, 59 - strings: raw.strings.count, 60 - stars: raw.stars.count, 61 - followers: raw.followers.count, 62 - following: raw.following.count, 63 - vouches: raw.vouches.count 74 + repos: countOf(resolved.data, did, REPOS), 75 + strings: countOf(resolved.data, did, STRINGS), 76 + stars: countOf(resolved.data, did, STARS), 77 + followers: countOf(resolved.data, did, FOLLOWERS), 78 + following: countOf(resolved.data, did, FOLLOWING), 79 + vouches: countOf(resolved.data, did, VOUCHES) 64 80 }; 81 + const viewerFollowUri = 82 + viewerDid && viewerDid !== did ? viewerUriOf(resolved.data, did, FOLLOWERS) : null; 83 + const viewerFollowRkey = viewerFollowUri ? rkeyFromUri(viewerFollowUri) : viewerFollowUri; 65 84 66 85 const notJoined = !profile && Object.values(counts).every((n) => n === 0); 67 86 ··· 69 88 identity: { did, handle: doc.handle }, 70 89 profile, 71 90 counts, 72 - viewerFollowRkey: raw.viewerFollowRkey, 91 + viewerFollowRkey, 73 92 notJoined 74 93 }; 75 94 };