npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

at-astro-loader

v1.3.0

Published

An Astro content loader for records on ATProto.

Downloads

462

Readme

at-astro-loader

NPM Package Bundle Size Build Status Downloads Issues

AT Protocol-powered loaders for Astro content collections.

This package exposes:

  • atLoader(...) for build-time/static content collection syncing
  • atLiveLoader(...) for Astro live collections
  • atZodSchema(...) to derive a Zod schema from a generated lexicon record schema
  • defineStandardSiteDocumentRenderer(...) for rendering site.standard.document records

It is designed for lexicons generated by @atproto/lex.

Install

npm i at-astro-loader
# or
pnpm add at-astro-loader
# or
bun add at-astro-loader

Generate Lexicons

Generate the record schema types you want to load:

npx -p @atproto/lex lex install <nsid...>
npx -p @atproto/lex lex build

This typically generates modules under src/lexicons, or your configured lexicon output directory.

Load Records

Use atLoader(...) for Astro content collections that sync during content collection loading.

import { defineCollection } from "astro:content"
import { atLoader } from "at-astro-loader"
import * as app from "../src/lexicons/app"

export const collections = {
  posts: defineCollection({
    loader: atLoader(app.bsky.feed.post, {
      repo: "myhandle.com",
      endpoint: "https://public.api.bsky.app",
      limit: 100,
      reverse: true,
    }),
  }),
}

Use atLiveLoader(...) for Astro live collections.

import { defineLiveCollection } from "astro:content"
import { atLiveLoader } from "at-astro-loader"
import * as app from "../src/lexicons/app"

export const collections = {
  posts: defineLiveCollection({
    loader: atLiveLoader(app.bsky.feed.post, {
      repo: "myhandle.com",
      endpoint: "https://public.api.bsky.app",
    }),
  }),
}

Render site.standard.document

defineStandardSiteDocumentRenderer(...) provides a generic renderer entry point for site.standard.document records.

Right now, the built-in renderer only supports documents whose content is app.offprint.content, blog.pckt.content, and pub.leaflet.content. Other site.standard.document content types return no rendered output until support is added.

import { defineCollection } from "astro:content"
import { atLoader, defineStandardSiteDocumentRenderer } from "at-astro-loader"
import * as site from "../src/lexicons/site"

export const collections = {
  documents: defineCollection({
    loader: atLoader(site.standard.document, {
      repo: "chrisvanderloo.com",
      endpoint: "https://bsky.social",
      renderer: defineStandardSiteDocumentRenderer({
        shikiConfig: {
          themes: {
            light: "github-light",
            dark: "github-dark",
          },
        },
      }),
    }),
  }),
}

The renderer receives the repository DID and endpoint from the loader. Blob-backed images are rendered through the ATProto com.atproto.sync.getBlob endpoint.

Code blocks are rendered with Shiki. Math blocks are rendered with KaTeX. The renderer supports Offprint blocks such as text, headings, lists, task lists, blockquotes, callouts, bookmarks, embeds, images, image grids, image diffs, and math/code blocks.

Custom Renderers

You can pass your own renderer to either atLoader(...) or atLiveLoader(...).

import type { RendererFunction } from "at-astro-loader"

const renderer: RendererFunction<MyRecord> = async (record, ctx) => {
  return {
    html: `<p>${record.title}</p>`,
    metadata: {},
  }
}

Renderer context includes:

  • repoDid: the resolved repository DID
  • endpoint: the configured ATProto service endpoint, when provided

For static collections, getMarkdown(...) is also available if you only need Astro's Markdown renderer. If renderer is provided, it takes precedence over getMarkdown.

API

atLoader(ns, config?)

Creates a regular Astro content loader that fetches records via client.list(...) during content sync.

  • Clears the store on each load
  • Validates/coerces each record with Astro parseData()
  • Stores entries keyed by record CID
  • Uses generateDigest(data) for digests
  • Throws ATLoaderError if invalid records are returned

atLiveLoader(ns, config?)

Creates an Astro live loader backed by an ATProto record schema.

  • loadEntry delegates to client.get(...)
  • loadCollection delegates to client.list(...)
  • Returns entry IDs as CIDs
  • Returns ATLoaderError in the live loader response error channel
  • Applies renderer when one is configured

atZodSchema(ns)

Builds a Zod schema using the lexicon schema's safeParse.

import { atZodSchema } from "at-astro-loader"
import * as app from "../src/lexicons/app"

const postSchema = atZodSchema(app.bsky.feed.post)

Config

atLoader(...) and atLiveLoader(...) accept ATProto list/get options plus:

  • repo?: AtIdentifierString
    DID or handle. Handles are resolved to DIDs when a renderer needs repository context.
  • client?: Client
    Provide a preconfigured ATProto client.
  • endpoint?: string
    ATProto service endpoint. Defaults to https://public.api.bsky.app when the package creates a client.
  • renderer?: RendererFunction<Infer<T>>
    Render record data into Astro rendered content.
  • getMarkdown?: (data: Infer<T>) => string
    Static-loader-only Markdown extraction hook.

Any additional list/get options are forwarded to the underlying ATProto client calls.

Schema Input Shape

All exported helpers accept either:

  • a generated schema object T
  • a namespace wrapper { main: T }

This supports both direct exports and namespace module patterns from generated lexicons.

Notes

  • If you do not pass client, one is created lazily from @atproto/lex.
  • Default service endpoint is https://public.api.bsky.app.
  • Entry IDs are CIDs from ATProto records.
  • Static loader records are stored as JSON-safe data via lexToJson(...).
  • Renderers receive the raw record value so blob refs and CID-like values can still be handled for rendering.