diff --git a/README.md b/README.md index 5886a85..38049df 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,151 @@ # at-astro-loader -This loader provides live and build-time collections from AT Protocol data for Astro. For example, you could publish a custom lexicon like `com.mywebsite.collection` and use this tool to load any data matching that lexicon into your Astro site, type-safe. +AT Protocol-powered loaders for Astro content collections. -Type safety requires a little bit of setup. +This package exposes: -## Usage +- `atLoader(...)` for build-time/static collection syncing +- `atLiveLoader(...)` for Astro live collections +- `atZodSchema(...)` to derive a Zod schema from a generated lexicon record schema -First, use the `@atproto/lex` CLI to pull the lexicons you want to make into an Astro collection, and then build them: +It is designed for lexicons generated by `@atproto/lex`. + +## Install + +```bash +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: ```bash -npx -p @atproto/lex lex install [nsids] +npx -p @atproto/lex lex install npx -p @atproto/lex lex build ``` -This will put lexicons into your `src/lexicons` directory. Make sure to add that to your `.gitignore` file and add the `lex build` command to your build process. +This typically generates modules under `src/lexicons` (or your configured output directory). + +## Quick start + +```ts +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: + +```ts +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: + +```ts +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`. + +```ts +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.