Skip to content

chrisvander/at-astro-loader

Repository files navigation

at-astro-loader

AT Protocol-powered loaders for Astro content collections.

This package exposes:

  • atLoader(...) for build-time/static collection syncing
  • atLiveLoader(...) for Astro live collections
  • atZodSchema(...) to derive a Zod schema from a generated lexicon record schema

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 output directory).

Quick start

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,
    }),
  }),
};

API

atLoader(ns, config?)

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

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

Example:

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

const posts = defineCollection({
  loader: atLoader(app.bsky.feed.post, {
    repo: "myhandle.com",
    limit: 100,
    reverse: true,
  }),
});

atLiveLoader(ns, config?)

Creates an Astro LiveLoader 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

Example:

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

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

atZodSchema(ns)

Builds a Zod schema using the lexicon schema safeParse, useful when you need schema reuse outside atLoader.

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

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

Config (ATLoaderConfig)

ATLoaderConfig extends ATProto CallOptions and supports:

  • repo?: AtIdentifierString
    DID or handle. Defaults to the authenticated user's DID (ATProto client behavior).
  • client?: Client
    Provide a preconfigured client (auth/session/custom headers).
  • endpoint?: string
    Defaults to https://public.api.bsky.app.

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

Schema input shape

All exported helpers accept either:

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

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

Error behavior

ATLoaderError is used for loader failures:

  • Static loader (atLoader) throws when list returns invalid records.
  • Live loader (atLiveLoader) returns { error: ATLoaderError } when:
    • a fetched record has no CID
    • list returns invalid records

Notes

  • If you don't pass client, one is created lazily from @atproto/lex.
  • Default service endpoint is public Bluesky API: https://public.api.bsky.app.
  • Entry IDs are CIDs from ATProto records.