ewan's projects — docs

projects

@ewanc26/atproto

March 6, 2026

# library# atproto# typescript# monorepo

@ewanc26/atproto is the AT Protocol service layer extracted from ewancroft.uk. It handles all communication with the AT Protocol network: resolving identities, fetching profiles and lexicon records, reading Standard.site blog posts and documents, streaming Bluesky posts, and looking up music/mood status — all with a built-in in-memory cache.

Part of the website monorepo.

Installation

pnpm add @ewanc26/atproto

Requires @atproto/api >= 0.13.0 as a peer dependency.

Key Design

All functions that previously read PUBLIC_ATPROTO_DID from the SvelteKit environment now accept did: string as their first argument, making the package fully portable outside of SvelteKit.

An optional fetchFn parameter is accepted throughout for custom fetch implementations (e.g. SvelteKit's server-side fetch).

Usage

Profile

import { fetchProfile } from '@ewanc26/atproto';

const profile = await fetchProfile('did:plc:yourdidherexx');
// { did, handle, displayName, avatar, banner, followersCount, pronouns, … }

Standard.site Documents & Blog Posts

import { fetchPublications, fetchDocuments, fetchBlogPosts, fetchRecentDocuments } from '@ewanc26/atproto';

const pubs  = await fetchPublications(did);
const docs  = await fetchDocuments(did, publicationRkey);
const posts = await fetchBlogPosts(did, publicationRkey);
const recent = await fetchRecentDocuments(did, 5);

Bluesky Posts

import { fetchLatestBlueskyPost, fetchPostFromUri } from '@ewanc26/atproto';

const latest = await fetchLatestBlueskyPost(did);
const post   = await fetchPostFromUri(did, 'at://did:plc:…/app.bsky.feed.post/rkey');

Status Records

import { fetchMusicStatus, fetchKibunStatus } from '@ewanc26/atproto';

const music = await fetchMusicStatus(did);
// { trackName, artists, releaseName, artworkUrl, … }

const mood = await fetchKibunStatus(did);
// { text, emoji, createdAt }
import { fetchTangledRepos, fetchLinks, fetchSiteInfo } from '@ewanc26/atproto';

const repos   = await fetchTangledRepos(did);
const links   = await fetchLinks(did);        // blue.linkat.board
const siteInfo = await fetchSiteInfo(did);    // uk.ewancroft.site.info

Engagement (Constellation API)

import { fetchEngagementFromConstellation, fetchAllEngagement } from '@ewanc26/atproto';

const counts = await fetchEngagementFromConstellation(postUri);
// { likeCount, repostCount }

Identity Resolution

import { resolveIdentity, getPublicAgent, getPDSAgent } from '@ewanc26/atproto';

const { did, pds } = await resolveIdentity('alice.bsky.social');
const agent = await getPDSAgent(did);

Pagination

import { fetchAllRecords, fetchAllUserRecords } from '@ewanc26/atproto';

const records = await fetchAllRecords({ did, collection: 'app.bsky.feed.post' });

Music Artwork

import { findArtwork } from '@ewanc26/atproto';

const url = await findArtwork(trackName, artistName, releaseName, releaseMbId);
// Cascading fallback: MusicBrainz → iTunes → Deezer → Last.fm → null

Caching

All fetch functions use a shared ATProtoCache instance. TTLs are configured via the exported CACHE_TTL object:

Key Default TTL
PROFILE 1 hour
SITE_INFO 2 hours
MUSIC_STATUS 10 minutes
KIBUN_STATUS 15 minutes
BLOG_POSTS 30 minutes
IDENTITY 24 hours

You can import cache directly to clear or inspect entries:

import { cache } from '@ewanc26/atproto';

cache.clear();
cache.delete('profile:did:plc:…');

Types

All interfaces are exported from the package root:

import type {
  ProfileData, BlueskyPost, BlogPost, PostAuthor,
  MusicStatusData, KibunStatusData, TangledRepo,
  StandardSiteDocument, StandardSitePublication,
  SiteInfoData, LinkData, ResolvedIdentity
} from '@ewanc26/atproto';

Tech Stack

TypeScript 5.9+. Peer dependency on @atproto/api. Compiled to ESM with tsc.

Licence

See the website repository.


← all docs