Skip to content

@scribe-atp/core

function fetchSite(
author: string,
siteSlug: string,
signal?: AbortSignal
): Promise<Site>

Fetches a site record from the author’s PDS. Resolves the author handle to a DID, discovers the PDS, and returns the full Site with embedded group and article metadata.

ParameterTypeDescription
authorstringAuthor handle (alice.bsky.social) or DID (did:plc:…)
siteSlugstringThe site’s rkey — derive with toSlug(domain)
signalAbortSignalOptional. Cancel the request when the signal fires

function fetchArticle(
author: string,
articleSlug: string,
signal?: AbortSignal
): Promise<Article>

Fetches a single article record (including full HTML content) from the author’s PDS.

ParameterTypeDescription
authorstringAuthor handle or DID
articleSlugstringThe article’s rkey / slug
signalAbortSignalOptional. Cancel the request when the signal fires

function listSites(
author: string,
signal?: AbortSignal
): Promise<SiteRecord[]>

Returns all site records for the given author. Calls com.atproto.repo.listRecords for the app.scribe.site collection and follows cursor pagination automatically.

ParameterTypeDescription
authorstringAuthor handle (alice.bsky.social) or DID (did:plc:…)
signalAbortSignalOptional. Cancel the request when the signal fires

Each SiteRecord is a Site with an additional uri field (the full AT URI of the record). Use slugFromUri(record.uri) to extract the rkey when you need to construct URLs.


function listArticles(
author: string,
signal?: AbortSignal
): Promise<ArticleRef[]>

Returns all article records for the given author as lightweight ArticleRef objects (no content field). Calls com.atproto.repo.listRecords for the app.scribe.article collection and follows cursor pagination automatically.

ParameterTypeDescription
authorstringAuthor handle or DID
signalAbortSignalOptional. Cancel the request when the signal fires

To determine the publication state of each article, cross-reference with listSites:

const [sites, articles] = await Promise.all([
listSites(author),
listArticles(author),
]);
const referencedUris = new Set(
sites.flatMap((s) => [
...s.groups.flatMap((g) => g.articles),
...s.ungroupedArticles,
]).map((a) => a.uri)
);
const drafts = articles.filter((a) => !referencedUris.has(a.uri));

Call fetchArticle to retrieve the full content for any article.


function toSlug(domain: string): string

Derives a site slug from a domain name by replacing . with - and removing non-alphanumeric characters.

toSlug('norobots.blog') // → "norobots-blog"
toSlug('anthonycregan.co.uk') // → "anthonycregan-co-uk"

function slugFromUri(uri: string): string

Extracts the rkey (slug) from an AT URI.

slugFromUri('at://did:plc:abc/app.scribe.article/my-post') // → "my-post"

function flattenArticles(
groups: Array<{ articles: ArticleRef[] }>
): ArticleRef[]

Flattens all ArticleRef objects from all groups into a single ordered array.

const allArticles = flattenArticles(site.groups);

function generateFeed(site: Site, options: FeedOptions): string

Returns a complete RSS 2.0 XML string for all published articles in the site.

FeedOptions

PropertyTypeRequiredDescription
baseUrlstringYesYour site’s origin, e.g. "https://alice.example.com"
feedUrlstringNoCanonical URL of the feed — used for the <atom:link> self-reference
languagestringNoRSS <language> tag. Default: "en"
limitnumberNoMaximum number of items to include

See the RSS feeds guide for usage examples.


function getSitemapEntries(
site: Site,
options: GetSitemapEntriesOptions
): SitemapEntry[]

Returns an array of sitemap entries for all published articles (plus the site root and group index pages).

GetSitemapEntriesOptions

PropertyTypeRequiredDescription
baseUrlstringYesYour site’s origin, e.g. "https://alice.example.com"

SitemapEntry

interface SitemapEntry {
url: string;
lastmod?: string; // ISO 8601 date, e.g. "2024-03-15"
}

See the Sitemaps guide for usage examples.


interface Site {
title: string;
url: string; // Domain without protocol, e.g. "alice.bsky.social"
urlPrefix: string; // Path prefix, e.g. "blog" — empty string if content is at root
description?: string;
splashImageUrl?: string;
logoImageUrl?: string;
groups: SiteGroup[];
ungroupedArticles: ArticleRef[];
}
interface SiteGroup {
slug: string;
title: string;
articles: ArticleRef[];
}

A lightweight snapshot of article metadata, embedded in the site record to avoid N+1 fetch patterns. Does not include content.

interface ArticleRef {
uri: string; // AT URI, e.g. "at://did:plc:…/app.scribe.article/my-post"
title: string;
url?: string; // Article slug / rkey
splashImageUrl: string | null;
synopsis?: string | null;
createdAt: string; // ISO 8601
updatedAt?: string; // ISO 8601
}

The full article record, including HTML content. Returned by fetchArticle.

interface Article {
title: string;
content: string; // Sanitised HTML — safe to render directly
url: string; // Article slug / rkey
splashImageUrl?: string;
synopsis?: string;
createdAt: string; // ISO 8601
updatedAt: string; // ISO 8601
}

A Site with the AT URI of its underlying record included. Returned by listSites.

interface SiteRecord extends Site {
uri: string; // AT URI, e.g. "at://did:plc:…/app.scribe.site/norobots-blog"
}

Use slugFromUri(record.uri) to extract the rkey when constructing URLs.