AppView in a box as a Vite plugin thing hatk.dev
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

docs: update for BaseContext, new hydrate signature, and viewer type

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

+20 -16
+1 -1
docs/site/guides/auth.md
··· 181 181 }); 182 182 ``` 183 183 184 - `ctx.viewer` is the same `{ did: string }` shape in both XRPC handlers and feed `generate`/`hydrate` functions. 184 + `ctx.viewer` is the same `{ did: string; handle?: string }` shape in both XRPC handlers and feed `generate`/`hydrate` functions. 185 185 186 186 ## Complete example 187 187
+16 -13
docs/site/guides/feeds.md
··· 53 53 | `params` | `Record<string, string>` | Query string parameters from the request | 54 54 | `limit` | number | Requested page size | 55 55 | `cursor` | string \| undefined | Pagination cursor from the client | 56 - | `viewer` | `{ did: string }` \| null | The authenticated user, or null | 56 + | `viewer` | `{ did: string; handle?: string }` \| null | The authenticated user, or null | 57 57 | `ok` | function | Wraps your return value with type checking | 58 58 | `paginate` | function | Run a paginated query (handles cursor, ORDER BY, LIMIT) | 59 59 | `packCursor` | function | Encode a `(primary, cid)` pair into an opaque cursor string | ··· 135 135 136 136 The optional `hydrate` function enriches feed results with additional data. After `generate` returns URIs, the framework resolves them into full records, then passes those records to `hydrate`. 137 137 138 - ### Hydrate context reference 138 + ### Hydrate function signature 139 + 140 + `hydrate` receives a `BaseContext` and an array of `Row<T>` items (the resolved records). Each row has `uri`, `did`, `handle`, and `value`. 141 + 142 + ### BaseContext reference 139 143 140 144 | Field | Type | Description | 141 145 | ------------ | ------------------------- | --------------------------------------------------------------- | 142 - | `items` | `Row[]` | The resolved records (each has `uri`, `did`, `handle`, `value`) | 143 - | `viewer` | `{ did: string }` \| null | The authenticated user, or null | 146 + | `viewer` | `{ did: string; handle?: string }` \| null | The authenticated user, or null | 144 147 | `db.query` | function | Run SQL queries against your SQLite database | 145 148 | `getRecords` | function | Fetch records by URI from another collection | 146 149 | `lookup` | function | Look up records by a field value (e.g. profiles by DID) | ··· 153 156 This feed queries status records and hydrates each one with the author's profile: 154 157 155 158 ```typescript 156 - import { defineFeed, views, type Status, type Profile, type HydrateContext } from "$hatk"; 159 + import { defineFeed, views, type Status, type Profile, type BaseContext, type Row } from "$hatk"; 157 160 158 161 export default defineFeed({ 159 162 collection: "xyz.statusphere.status", 160 163 label: "Recent", 161 164 162 - hydrate: (ctx) => hydrateStatuses(ctx), 165 + hydrate: (ctx, items) => hydrateStatuses(ctx, items as Row<Status>[]), 163 166 164 167 async generate(ctx) { 165 168 const { rows, cursor } = await ctx.paginate<{ uri: string }>( ··· 171 174 }, 172 175 }); 173 176 174 - async function hydrateStatuses(ctx: HydrateContext<Status>) { 175 - const dids = [...new Set(ctx.items.map((item) => item.did).filter(Boolean))]; 177 + async function hydrateStatuses(ctx: BaseContext, items: Row<Status>[]) { 178 + const dids = [...new Set(items.map((item) => item.did).filter(Boolean))]; 176 179 const profiles = await ctx.lookup<Profile>("app.bsky.actor.profile", "did", dids); 177 180 178 - return ctx.items.map((item) => { 181 + return items.map((item) => { 179 182 const author = profiles.get(item.did); 180 183 return views.statusView({ 181 184 uri: item.uri, ··· 204 207 Hydration can also use `ctx.viewer` to add viewer-specific data like bookmarks: 205 208 206 209 ```typescript 207 - async function hydratePlays(ctx: HydrateContext<Play>) { 208 - const dids = [...new Set(ctx.items.map((item) => item.did).filter(Boolean))]; 210 + async function hydratePlays(ctx: BaseContext, items: Row<Play>[]) { 211 + const dids = [...new Set(items.map((item) => item.did).filter(Boolean))]; 209 212 const profiles = await ctx.lookup<Profile>("app.bsky.actor.profile", "did", dids); 210 213 211 214 // Load viewer's bookmarks 212 215 const bookmarks = new Map<string, string>(); 213 - if (ctx.viewer?.did && ctx.items.length > 0) { 216 + if (ctx.viewer?.did && items.length > 0) { 214 217 const rows = await ctx.db.query( 215 218 `SELECT subject, uri FROM "community.lexicon.bookmarks.bookmark" WHERE did = $1`, 216 219 [ctx.viewer.did], ··· 220 223 } 221 224 } 222 225 223 - return ctx.items.map((item) => { 226 + return items.map((item) => { 224 227 const author = profiles.get(item.did); 225 228 return views.playView({ 226 229 record: { uri: item.uri, did: item.did, handle: item.handle, ...item.value },
+3 -2
docs/site/guides/xrpc-handlers.md
··· 87 87 | `input` | object | Request body (procedures only), typed from the lexicon's input schema | 88 88 | `db.query` | function | Run SQL queries against your SQLite database | 89 89 | `db.run` | function | Execute SQL statements (INSERT, UPDATE, DELETE) | 90 - | `viewer` | `{ did: string }` \| null | The authenticated user, or null | 90 + | `viewer` | `{ did: string; handle?: string }` \| null | The authenticated user, or null | 91 91 | `limit` | number | Requested page size | 92 92 | `cursor` | string \| undefined | Pagination cursor | 93 93 | `resolve` | function | Resolve AT URIs into full records | 94 + | `getRecords` | function | Fetch records by URI from another collection | 94 95 | `lookup` | function | Look up records by a field value | 95 96 | `count` | function | Count records by field value | 96 97 | `exists` | function | Check if a record exists matching field filters | ··· 142 143 143 144 ### `ctx.viewer` 144 145 145 - `viewer` is `{ did: string }` when the request comes from an authenticated user, or `null` for unauthenticated requests. Check it to protect endpoints that require authentication: 146 + `viewer` is `{ did: string; handle?: string }` when the request comes from an authenticated user, or `null` for unauthenticated requests. Check it to protect endpoints that require authentication: 146 147 147 148 ```typescript 148 149 if (!viewer) throw new Error("Authentication required");