A lexicon-driven AppView for ATProto. happyview.dev
backfill firehose jetstream atproto appview oauth lexicon
8
fork

Configure Feed

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

docs: update links to work on Github

Trezy 8293c83c e956f030

+110 -110
+4 -4
docs/README.md
··· 1 1 # HappyView 2 2 3 - HappyView is the best way to build an [AppView](https://atproto.com/guides/glossary#app-view) for the [AT Protocol](https://atproto.com). Upload your [lexicon](reference/glossary#at-protocol-terms) schemas and get a fully functional AppView, complete with [XRPC](reference/glossary#at-protocol-terms) endpoints, OAuth, real-time network sync, and historical [backfill](guides/backfill), without writing a single line of server code. 3 + HappyView is the best way to build an [AppView](https://atproto.com/guides/glossary#app-view) for the [AT Protocol](https://atproto.com). Upload your [lexicon](reference/glossary.md#at-protocol-terms) schemas and get a fully functional AppView, complete with [XRPC](reference/glossary.md#at-protocol-terms) endpoints, OAuth, real-time network sync, and historical [backfill](guides/backfill.md), without writing a single line of server code. 4 4 5 5 Building an AppView from scratch means wiring up firehose connections, record storage, XRPC routing, OAuth flows, and PDS write proxying before you can even think about your application. HappyView handles all of that. Define your data model with lexicons, add custom logic with Lua scripts when you need it, and ship your app. 6 6 ··· 27 27 28 28 ## Next Steps 29 29 30 - - [Quickstart](getting-started/deployment/railway): Deploy HappyView on Railway or run it locally 31 - - [Lexicons](guides/lexicons): Upload lexicon schemas and start indexing records 32 - - [Lua Scripting](guides/scripting): Write custom query and procedure logic 30 + - [Quickstart](getting-started/deployment/railway.md): Deploy HappyView on Railway or run it locally 31 + - [Lexicons](guides/lexicons.md): Upload lexicon schemas and start indexing records 32 + - [Lua Scripting](guides/scripting.md): Write custom query and procedure logic
+2 -2
docs/getting-started/authentication.md
··· 19 19 20 20 ## Getting a token from the dashboard 21 21 22 - The easiest way to get a token for CLI or curl usage is through the [web dashboard](dashboard): 22 + The easiest way to get a token for CLI or curl usage is through the [web dashboard](dashboard.md): 23 23 24 24 1. Open the dashboard and log in with your AT Protocol identity 25 25 2. Open your browser's developer tools (F12 or Cmd+Shift+I) ··· 62 62 63 63 Admin endpoints require the authenticated user's DID to exist in the `admins` table. If the table is empty (fresh deployment), the first authenticated request to any admin endpoint auto-bootstraps that user as the initial admin. 64 64 65 - To add more admins, use `POST /admin/admins` or the [dashboard](dashboard). See [Admin API](../reference/admin-api#admin-management) for details. 65 + To add more admins, use `POST /admin/admins` or the [dashboard](dashboard.md). See [Admin API](../reference/admin-api.md#admin-management) for details.
+2 -2
docs/getting-started/configuration.md
··· 1 1 # Configuration 2 2 3 - HappyView is configured via environment variables. A `.env` file in the project root is loaded automatically on startup. See [Deployment](deployment/docker) for local setup or [Production Deployment](../reference/production-deployment) for production setup. 3 + HappyView is configured via environment variables. A `.env` file in the project root is loaded automatically on startup. See [Deployment](deployment/docker.md) for local setup or [Production Deployment](../reference/production-deployment.md) for production setup. 4 4 5 5 ## Environment variables 6 6 ··· 12 12 | `PORT` | no | `3000` | Bind port | 13 13 | `TAP_URL` | no | `http://localhost:2480` | [Tap](https://github.com/bluesky-social/indigo/tree/main/cmd/tap) instance URL for real-time record streaming and backfill | 14 14 | `TAP_ADMIN_PASSWORD` | no | --- | Shared secret for authenticating with Tap's admin endpoints | 15 - | `RELAY_URL` | no | `https://bsky.network` | Relay URL for [backfill](../guides/backfill) repo discovery | 15 + | `RELAY_URL` | no | `https://bsky.network` | Relay URL for [backfill](../guides/backfill.md) repo discovery | 16 16 | `PLC_URL` | no | `https://plc.directory` | [PLC directory](https://github.com/did-method-plc/did-method-plc) URL for DID resolution | 17 17 | `RUST_LOG` | no | `happyview=debug,tower_http=debug` | Log filter (uses `tracing_subscriber::EnvFilter`) | 18 18
+3 -3
docs/getting-started/dashboard.md
··· 1 1 # Dashboard 2 2 3 - HappyView ships with a web dashboard that provides a visual interface for everything the [admin API](../reference/admin-api) offers: managing lexicons, viewing indexed records, and monitoring backfill jobs. It runs as a separate Next.js application alongside the Rust backend. 3 + HappyView ships with a web dashboard that provides a visual interface for everything the [admin API](../reference/admin-api.md) offers: managing lexicons, viewing indexed records, and monitoring backfill jobs. It runs as a separate Next.js application alongside the Rust backend. 4 4 5 5 ## Logging in for the first time 6 6 ··· 21 21 22 22 Toggle **Enable backfill** to index historical records when uploading a record-type lexicon. 23 23 24 - **Network** lexicons are fetched from the AT Protocol network. Enter an NSID (e.g. `xyz.statusphere.status`) and HappyView resolves the schema automatically. If found, the lexicon JSON is displayed in a read-only editor. Click **Add** to track it. Network lexicons are kept up to date via Tap. See [Lexicons - Network lexicons](../guides/lexicons#network-lexicons) for how resolution works. 24 + **Network** lexicons are fetched from the AT Protocol network. Enter an NSID (e.g. `xyz.statusphere.status`) and HappyView resolves the schema automatically. If found, the lexicon JSON is displayed in a read-only editor. Click **Add** to track it. Network lexicons are kept up to date via Tap. See [Lexicons - Network lexicons](../guides/lexicons.md#network-lexicons) for how resolution works. 25 25 26 26 ### JSON editor 27 27 ··· 35 35 36 36 The Lua editor provides context-aware code completions, including suggestions for the `Record`, `db`, `input`, and `params` APIs as well as Lua keywords, builtins, and standard library functions. It also offers snippet templates for common constructs like `if`, `for`, and `function`. 37 37 38 - See [Lua Scripting](../guides/scripting) for the full runtime reference and examples. 38 + See [Lua Scripting](../guides/scripting.md) for the full runtime reference and examples.
+1 -1
docs/getting-started/deployment/docker.md
··· 46 46 47 47 ## Next steps 48 48 49 - Your HappyView stack is running. Follow the [Statusphere tutorial](../../tutorials/statusphere) to upload lexicons, add custom query logic, and start indexing records from the network. 49 + Your HappyView stack is running. Follow the [Statusphere tutorial](../../tutorials/statusphere.md) to upload lexicons, add custom query logic, and start indexing records from the network.
+3 -3
docs/getting-started/deployment/other.md
··· 1 1 # Local Development from Source 2 2 3 - This guide runs HappyView directly with `cargo run`, with you managing Postgres, AIP, and Tap separately. If you'd rather use Docker Compose to run everything together, see [Local Development with Docker](docker). 3 + This guide runs HappyView directly with `cargo run`, with you managing Postgres, AIP, and Tap separately. If you'd rather use Docker Compose to run everything together, see [Local Development with Docker](docker.md). 4 4 5 5 ## Prerequisites 6 6 ··· 26 26 TAP_ADMIN_PASSWORD=your-secret-here 27 27 ``` 28 28 29 - See [Configuration](../configuration) for all available variables. 29 + See [Configuration](../configuration.md) for all available variables. 30 30 31 31 ## 2. Create the database 32 32 ··· 52 52 53 53 ## Next steps 54 54 55 - Your HappyView instance is running. Follow the [Statusphere tutorial](../../tutorials/statusphere) to upload lexicons, add custom query logic, and start indexing records from the network. 55 + Your HappyView instance is running. Follow the [Statusphere tutorial](../../tutorials/statusphere.md) to upload lexicons, add custom query logic, and start indexing records from the network.
+11 -11
docs/getting-started/quickstart.md
··· 8 8 9 9 | Option | Best for | 10 10 |--------|----------| 11 - | [**Railway**](deployment/railway) | Fastest path — one-click deploy of the full stack (HappyView + AIP + Tap + Postgres) | 12 - | [**Docker Compose**](deployment/docker) | Local development with the full stack running in containers | 13 - | [**From source**](deployment/other) | Running HappyView with `cargo run` and managing dependencies yourself | 11 + | [**Railway**](deployment/railway.md) | Fastest path — one-click deploy of the full stack (HappyView + AIP + Tap + Postgres) | 12 + | [**Docker Compose**](deployment/docker.md) | Local development with the full stack running in containers | 13 + | [**From source**](deployment/other.md) | Running HappyView with `cargo run` and managing dependencies yourself | 14 14 15 15 If you're just trying HappyView for the first time, start with Railway. 16 16 17 17 ## 2. Log in to the dashboard 18 18 19 - Open your HappyView instance in a browser. The built-in [dashboard](dashboard) is served at the root URL. 19 + Open your HappyView instance in a browser. The built-in [dashboard](dashboard.md) is served at the root URL. 20 20 21 21 Click **Log in** and authenticate with your AT Protocol identity. On a fresh deployment with no admins configured, the first authenticated request to any admin endpoint automatically bootstraps that user as an admin. 22 22 ··· 31 31 32 32 HappyView immediately starts indexing records for that collection. A backfill job is created to fetch historical records, and new records stream in via Tap in real time. 33 33 34 - You can also upload lexicons manually via the dashboard or the [admin API](../reference/admin-api). See [Lexicons](../guides/lexicons) for the full details. 34 + You can also upload lexicons manually via the dashboard or the [admin API](../reference/admin-api.md). See [Lexicons](../guides/lexicons.md) for the full details. 35 35 36 36 ## 4. Verify records are being indexed 37 37 ··· 49 49 GET /xrpc/xyz.statusphere.listStatuses?limit=5 50 50 ``` 51 51 52 - For custom query logic, attach a [Lua script](../guides/scripting). 52 + For custom query logic, attach a [Lua script](../guides/scripting.md). 53 53 54 54 ## Next steps 55 55 56 56 You now have a working AppView. Here's where to go from here: 57 57 58 - - [**Statusphere tutorial**](../tutorials/statusphere): end-to-end walkthrough building a complete AppView with record, query, and procedure lexicons 59 - - [**Lexicons guide**](../guides/lexicons): target collections, backfill flag, network lexicons 60 - - [**Lua Scripting**](../guides/scripting): custom query and procedure logic 61 - - [**Configuration**](configuration): environment variables and tuning 62 - - [**Authentication**](authentication): how OAuth works and how to get API tokens 58 + - [**Statusphere tutorial**](../tutorials/statusphere.md): end-to-end walkthrough building a complete AppView with record, query, and procedure lexicons 59 + - [**Lexicons guide**](../guides/lexicons.md): target collections, backfill flag, network lexicons 60 + - [**Lua Scripting**](../guides/scripting.md): custom query and procedure logic 61 + - [**Configuration**](configuration.md): environment variables and tuning 62 + - [**Authentication**](authentication.md): how OAuth works and how to get API tokens
+6 -6
docs/guides/backfill.md
··· 4 4 5 5 ## When backfill runs 6 6 7 - - **Automatically** when a record-type lexicon is uploaded with `backfill: true` (the default). See [Lexicons - Backfill flag](lexicons#backfill-flag). 8 - - **Manually** via `POST /admin/backfill` or the [dashboard](../getting-started/dashboard). You can scope a manual backfill to a specific collection, a specific DID, or both. 7 + - **Automatically** when a record-type lexicon is uploaded with `backfill: true` (the default). See [Lexicons - Backfill flag](lexicons.md#backfill-flag). 8 + - **Manually** via `POST /admin/backfill` or the [dashboard](../getting-started/dashboard.md). You can scope a manual backfill to a specific collection, a specific DID, or both. 9 9 10 - See the [admin API](../reference/admin-api#backfill) for endpoint details. 10 + See the [admin API](../reference/admin-api.md#backfill) for endpoint details. 11 11 12 12 ## How it works 13 13 ··· 32 32 33 33 ## Next steps 34 34 35 - - [Lexicons](lexicons#backfill-flag): Control whether lexicons trigger backfill on upload 36 - - [Admin API](../reference/admin-api#backfill): Full reference for backfill endpoints 37 - - [Admin API - Tap Stats](../reference/admin-api#tap-stats): Monitor Tap's processing progress 35 + - [Lexicons](lexicons.md#backfill-flag): Control whether lexicons trigger backfill on upload 36 + - [Admin API](../reference/admin-api.md#backfill): Full reference for backfill endpoints 37 + - [Admin API - Tap Stats](../reference/admin-api.md#tap-stats): Monitor Tap's processing progress
+8 -8
docs/guides/lexicons.md
··· 2 2 3 3 Lexicons are the core building block of HappyView. They're [AT Protocol schema definitions](https://atproto.com/specs/lexicon) that describe your data model, and HappyView uses them to decide which records to index from the network and what XRPC endpoints to serve. 4 4 5 - You don't write route handlers or database queries; you upload a lexicon and HappyView generates the infrastructure from it. There are two ways to add lexicons: uploading them via the [admin API](../reference/admin-api#lexicons) or [dashboard](../getting-started/dashboard), or fetching them directly from the AT Protocol network via [DNS authority resolution](#network-lexicons). 5 + You don't write route handlers or database queries; you upload a lexicon and HappyView generates the infrastructure from it. There are two ways to add lexicons: uploading them via the [admin API](../reference/admin-api.md#lexicons) or [dashboard](../getting-started/dashboard.md), or fetching them directly from the AT Protocol network via [DNS authority resolution](#network-lexicons). 6 6 7 7 ## Supported lexicon types 8 8 ··· 13 13 | `procedure` | Registers a `POST /xrpc/{nsid}` endpoint that proxies writes to the user's PDS | 14 14 | `definitions` | Stored but does not generate routes or subscriptions | 15 15 16 - A typical setup has three lexicons working together: a **record** lexicon that defines the data and triggers indexing, a **query** lexicon that exposes a read endpoint, and a **procedure** lexicon that exposes a write endpoint. The [Statusphere tutorial](../tutorials/statusphere) walks through this pattern end-to-end. 16 + A typical setup has three lexicons working together: a **record** lexicon that defines the data and triggers indexing, a **query** lexicon that exposes a read endpoint, and a **procedure** lexicon that exposes a write endpoint. The [Statusphere tutorial](../tutorials/statusphere.md) walks through this pattern end-to-end. 17 17 18 18 ## Target collection 19 19 ··· 21 21 22 22 For example, a query lexicon `xyz.statusphere.listStatuses` would set `target_collection` to `xyz.statusphere.status` to read from that record collection. 23 23 24 - See the [admin API](../reference/admin-api#upload--upsert-a-lexicon) for how to set `target_collection` when uploading. 24 + See the [admin API](../reference/admin-api.md#upload--upsert-a-lexicon) for how to set `target_collection` when uploading. 25 25 26 26 :::note 27 27 The `target_collection` is available in Lua scripts as the `collection` global, but it is not required if your endpoint uses a Lua script. ··· 35 35 36 36 When record-type lexicons change (uploaded or deleted), HappyView automatically syncs the updated collection filter to Tap. HappyView always includes `com.atproto.lexicon.schema` in the filter to track network lexicon updates. 37 37 38 - Deleting a lexicon updates Tap's collection filters (stopping live indexing for that collection) but does **not** remove previously indexed repos or their cached state from Tap. To fully reset a collection's state, delete the lexicon, re-add it, and run a [backfill](backfill). 38 + Deleting a lexicon updates Tap's collection filters (stopping live indexing for that collection) but does **not** remove previously indexed repos or their cached state from Tap. To fully reset a collection's state, delete the lexicon, re-add it, and run a [backfill](backfill.md). 39 39 40 40 ## Network lexicons 41 41 ··· 78 78 79 79 ## Next steps 80 80 81 - - [Lua Scripting](scripting): Add custom query and procedure logic to your endpoints 82 - - [XRPC API](../reference/xrpc-api): Understand how the generated endpoints behave 83 - - [Backfill](backfill): Learn how historical records are indexed 84 - - [Admin API](../reference/admin-api): Full reference for lexicon management endpoints 81 + - [Lua Scripting](scripting.md): Add custom query and procedure logic to your endpoints 82 + - [XRPC API](../reference/xrpc-api.md): Understand how the generated endpoints behave 83 + - [Backfill](backfill.md): Learn how historical records are indexed 84 + - [Admin API](../reference/admin-api.md): Full reference for lexicon management endpoints
+15 -15
docs/guides/scripting.md
··· 270 270 end 271 271 ``` 272 272 273 - To see log output, make sure your `RUST_LOG` environment variable includes debug level for HappyView (the default `happyview=debug` works). See [Configuration](../getting-started/configuration). 273 + To see log output, make sure your `RUST_LOG` environment variable includes debug level for HappyView (the default `happyview=debug` works). See [Configuration](../getting-started/configuration.md). 274 274 275 275 ### Error messages 276 276 ··· 293 293 See the example script references for complete, ready-to-use scripts: 294 294 295 295 **Queries:** 296 - - [Get a record](../reference/scripts/get-record) — fetch a single record by AT URI 297 - - [Paginated list](../reference/scripts/paginated-list) — list records with cursor-based pagination and DID filtering 298 - - [List or fetch](../reference/scripts/list-or-fetch) — combined single-record lookup and paginated listing 299 - - [Expanded query](../reference/scripts/expanded-query) — list statuses with user profiles in a single response 296 + - [Get a record](../reference/scripts/get-record.md) — fetch a single record by AT URI 297 + - [Paginated list](../reference/scripts/paginated-list.md) — list records with cursor-based pagination and DID filtering 298 + - [List or fetch](../reference/scripts/list-or-fetch.md) — combined single-record lookup and paginated listing 299 + - [Expanded query](../reference/scripts/expanded-query.md) — list statuses with user profiles in a single response 300 300 301 301 **Procedures:** 302 - - [Create a record](../reference/scripts/create-record) — simple write that saves input as a record 303 - - [Upsert a record](../reference/scripts/upsert-record) — create or update using a deterministic rkey 304 - - [Update or delete](../reference/scripts/update-or-delete) — single endpoint handling create, update, and delete 305 - - [Batch save](../reference/scripts/batch-save) — create multiple records in parallel with `Record.save_all()` 306 - - [Sidecar records](../reference/scripts/sidecar-records) — create linked records across collections with a shared rkey 307 - - [Cascading delete](../reference/scripts/cascading-delete) — delete a record and all related records 308 - - [Complex mutations](../reference/scripts/complex-mutations) — load, transform, and save a record with multiple field changes 302 + - [Create a record](../reference/scripts/create-record.md) — simple write that saves input as a record 303 + - [Upsert a record](../reference/scripts/upsert-record.md) — create or update using a deterministic rkey 304 + - [Update or delete](../reference/scripts/update-or-delete.md) — single endpoint handling create, update, and delete 305 + - [Batch save](../reference/scripts/batch-save.md) — create multiple records in parallel with `Record.save_all()` 306 + - [Sidecar records](../reference/scripts/sidecar-records.md) — create linked records across collections with a shared rkey 307 + - [Cascading delete](../reference/scripts/cascading-delete.md) — delete a record and all related records 308 + - [Complex mutations](../reference/scripts/complex-mutations.md) — load, transform, and save a record with multiple field changes 309 309 310 310 ## Next steps 311 311 312 - - [Lexicons](lexicons): Understand how record, query, and procedure lexicons work together 313 - - [XRPC API](../reference/xrpc-api): See how endpoints behave with and without Lua scripts 314 - - [Dashboard](../getting-started/dashboard#lua-editor): Use the web editor with context-aware completions 312 + - [Lexicons](lexicons.md): Understand how record, query, and procedure lexicons work together 313 + - [XRPC API](../reference/xrpc-api.md): See how endpoints behave with and without Lua scripts 314 + - [Dashboard](../getting-started/dashboard.md#lua-editor): Use the web editor with context-aware completions
+3 -3
docs/reference/admin-api.md
··· 1 1 # Admin API 2 2 3 - The admin API lets you manage lexicons, monitor records, run backfill jobs, and control admin access. All endpoints live under `/admin` and require an [AIP](https://github.com/graze-social/aip)-issued Bearer token from a DID that exists in the `admins` table. You can also manage all of this through the [web dashboard](../getting-started/dashboard). 3 + The admin API lets you manage lexicons, monitor records, run backfill jobs, and control admin access. All endpoints live under `/admin` and require an [AIP](https://github.com/graze-social/aip)-issued Bearer token from a DID that exists in the `admins` table. You can also manage all of this through the [web dashboard](../getting-started/dashboard.md). 4 4 5 5 ## Auth 6 6 ··· 115 115 116 116 ## Network Lexicons 117 117 118 - Network lexicons are fetched from the AT Protocol network via DNS TXT resolution and kept updated via Tap. See [Lexicons - Network lexicons](../guides/lexicons#network-lexicons) for background. 118 + Network lexicons are fetched from the AT Protocol network via DNS TXT resolution and kept updated via Tap. See [Lexicons - Network lexicons](../guides/lexicons.md#network-lexicons) for background. 119 119 120 120 ### Add a network lexicon 121 121 ··· 212 212 213 213 ## Tap Stats 214 214 215 - Aggregate stats from the [Tap](https://github.com/bluesky-social/indigo/tree/main/cmd/tap) instance. Useful for monitoring backfill progress. See [Backfill - Job lifecycle](../guides/backfill#job-lifecycle) for context. 215 + Aggregate stats from the [Tap](https://github.com/bluesky-social/indigo/tree/main/cmd/tap) instance. Useful for monitoring backfill progress. See [Backfill - Job lifecycle](../guides/backfill.md#job-lifecycle) for context. 216 216 217 217 ### Get Tap stats 218 218
+1 -1
docs/reference/architecture.md
··· 1 1 # Architecture 2 2 3 - Guide for contributors working on HappyView itself. For a user-facing overview, see the [Introduction](/). 3 + Guide for contributors working on HappyView itself. For a user-facing overview, see the [Introduction](/README.md). 4 4 5 5 ## System overview 6 6
+5 -5
docs/reference/glossary.md
··· 12 12 13 13 **Handle** — A human-readable name for an account (e.g. `user.bsky.social`). Handles resolve to a DID via DNS or the PLC directory. 14 14 15 - **Lexicon** — A schema definition for AT Protocol data types and API methods. Lexicons define what records look like, what endpoints exist, and what parameters they accept. See [Lexicons](../guides/lexicons). 15 + **Lexicon** — A schema definition for AT Protocol data types and API methods. Lexicons define what records look like, what endpoints exist, and what parameters they accept. See [Lexicons](../guides/lexicons.md). 16 16 17 17 **NSID** (Namespaced Identifier) — A reverse-DNS identifier for a lexicon (e.g. `xyz.statusphere.status`). The authority is everything except the last segment. 18 18 ··· 22 22 23 23 **Record** — A single piece of data in an AT Protocol repository, identified by an AT URI (e.g. `at://did:plc:abc/xyz.statusphere.status/abc123`). 24 24 25 - **Relay** — A network service that aggregates repository data from many PDSes. HappyView queries the relay during [backfill](../guides/backfill) to discover which repos contain records for a given collection, then delegates the actual record fetching to Tap. 25 + **Relay** — A network service that aggregates repository data from many PDSes. HappyView queries the relay during [backfill](../guides/backfill.md) to discover which repos contain records for a given collection, then delegates the actual record fetching to Tap. 26 26 27 27 **rkey** (Record Key) — The unique key for a record within a collection and repo. These are most commonly TIDs (timestamp-based) or NSIDs. 28 28 29 29 **TID** (Timestamp Identifier) — A 13-character sortable identifier used as a record key. Generated from the current timestamp. 30 30 31 - **XRPC** — The HTTP-based RPC protocol used by the AT Protocol. Query methods map to GET requests, procedure methods map to POST requests. See [XRPC API](xrpc-api). 31 + **XRPC** — The HTTP-based RPC protocol used by the AT Protocol. Query methods map to GET requests, procedure methods map to POST requests. See [XRPC API](xrpc-api.md). 32 32 33 33 ## HappyView-specific terms 34 34 35 35 **AIP** — [Authentication and Identity Provider](https://github.com/graze-social/aip). An external service that handles AT Protocol OAuth for HappyView. Issues Bearer tokens used for authentication. 36 36 37 - **Backfill** — The process of bulk-indexing existing records from the network. HappyView discovers repos via the relay and delegates record fetching to Tap. Runs when a new record-type lexicon is uploaded or triggered manually. See [Backfill](../guides/backfill). 37 + **Backfill** — The process of bulk-indexing existing records from the network. HappyView discovers repos via the relay and delegates record fetching to Tap. Runs when a new record-type lexicon is uploaded or triggered manually. See [Backfill](../guides/backfill.md). 38 38 39 - **Network lexicon** — A lexicon fetched directly from the AT Protocol network via DNS authority resolution, rather than uploaded manually. See [Lexicons - Network lexicons](../guides/lexicons#network-lexicons). 39 + **Network lexicon** — A lexicon fetched directly from the AT Protocol network via DNS authority resolution, rather than uploaded manually. See [Lexicons - Network lexicons](../guides/lexicons.md#network-lexicons). 40 40 41 41 **Tap** — A [firehose consumer and backfill worker](https://github.com/bluesky-social/indigo/tree/main/cmd/tap) that handles real-time record streaming, cryptographic verification, and historical record fetching. HappyView connects to Tap via WebSocket to receive record events, and delegates backfill work to Tap via its HTTP API. 42 42
+5 -5
docs/reference/production-deployment.md
··· 1 1 # Deployment 2 2 3 - HappyView requires a Postgres database and an [AIP](https://github.com/graze-social/aip) instance for OAuth. The [Quickstart](../getting-started/deployment/railway) covers the fastest path with Railway. This page covers other deployment options. 3 + HappyView requires a Postgres database and an [AIP](https://github.com/graze-social/aip) instance for OAuth. The [Quickstart](../getting-started/deployment/railway.md) covers the fastest path with Railway. This page covers other deployment options. 4 4 5 5 ## Docker 6 6 ··· 10 10 docker build -t happyview . 11 11 ``` 12 12 13 - For local development, see [Docker deployment](../getting-started/deployment/docker). 13 + For local development, see [Docker deployment](../getting-started/deployment/docker.md). 14 14 15 15 ### Production Compose example 16 16 ··· 50 50 51 51 1. Provision a Postgres 17+ database 52 52 2. Deploy an [AIP](https://github.com/graze-social/aip) instance (handles OAuth for your AppView) 53 - 3. Set `DATABASE_URL` and `AIP_URL` environment variables (see [Configuration](../getting-started/configuration) for all options) 53 + 3. Set `DATABASE_URL` and `AIP_URL` environment variables (see [Configuration](../getting-started/configuration.md) for all options) 54 54 4. Deploy the Docker image or build from source 55 55 5. HappyView listens on `PORT` (default `3000`) 56 56 6. Health check: `GET /health` returns `ok` 57 57 58 - For Railway specifically, the [Quickstart](../getting-started/deployment/railway) template handles all of this with a single click. 58 + For Railway specifically, the [Quickstart](../getting-started/deployment/railway.md) template handles all of this with a single click. 59 59 60 60 ## Database 61 61 ··· 67 67 68 68 ## Logging 69 69 70 - HappyView uses the `RUST_LOG` environment variable to control log output. The default (`happyview=debug,tower_http=debug`) logs all HappyView activity and HTTP requests. For production, consider `happyview=info,tower_http=info` to reduce noise. See [Configuration](../getting-started/configuration) for details. 70 + HappyView uses the `RUST_LOG` environment variable to control log output. The default (`happyview=debug,tower_http=debug`) logs all HappyView activity and HTTP requests. For production, consider `happyview=info,tower_http=info` to reduce noise. See [Configuration](../getting-started/configuration.md) for details.
+2 -2
docs/reference/scripts/batch-save.md
··· 23 23 24 24 ## How it works 25 25 26 - 1. Iterate over `input.items` and create a [`Record`](../../guides/scripting#record-api) instance for each item. 27 - 2. Call [`Record.save_all()`](../../guides/scripting#static-methods) to save all records in parallel, rather than one at a time. 26 + 1. Iterate over `input.items` and create a [`Record`](../../guides/scripting.md#record-api) instance for each item. 27 + 2. Call [`Record.save_all()`](../../guides/scripting.md#static-methods) to save all records in parallel, rather than one at a time. 28 28 3. Collect the resulting AT URIs and return them. 29 29 30 30 ## Usage
+1 -1
docs/reference/scripts/cascading-delete.md
··· 49 49 50 50 1. Load the primary record by URI. Return early if it doesn't exist. 51 51 2. Query for related records, in this example comments by the same user that reference the primary record's URI. 52 - 3. Load each related record with [`Record.load`](../../guides/scripting#static-methods) to get a deletable `Record` instance. 52 + 3. Load each related record with [`Record.load`](../../guides/scripting.md#static-methods) to get a deletable `Record` instance. 53 53 4. Delete everything. Each `r:delete()` removes the record from the user's PDS and the local index. 54 54 55 55 ## Usage
+2 -2
docs/reference/scripts/complex-mutations.md
··· 56 56 57 57 ## How it works 58 58 59 - 1. Load the existing record with [`Record.load`](../../guides/scripting#static-methods). This gives you a mutable `Record` instance with all the current field values. 59 + 1. Load the existing record with [`Record.load`](../../guides/scripting.md#static-methods). This gives you a mutable `Record` instance with all the current field values. 60 60 2. Apply transformations directly on the record's fields: 61 61 - **Increment a counter**: use `or 0` to handle the field being `nil` on first access. 62 62 - **Merge tags**: iterate over `input.tags`, skip duplicates already in `r.tags`, append new ones, then trim the list to 10. 63 63 - **Normalize a string**: use `string.gsub` to trim whitespace. 64 - - **Set a timestamp**: use [`now()`](../../guides/scripting#utility-globals) for UTC ISO 8601. 64 + - **Set a timestamp**: use [`now()`](../../guides/scripting.md#utility-globals) for UTC ISO 8601. 65 65 3. Call `r:save()`. Since `_uri` is set (from the load), this calls `putRecord` to update the record on the user's PDS. 66 66 67 67 ## Usage
+1 -1
docs/reference/scripts/create-record.md
··· 14 14 15 15 ## How it works 16 16 17 - 1. Create a new [`Record`](../../guides/scripting#record-api) instance from the target collection, populated with the fields from the request body. 17 + 1. Create a new [`Record`](../../guides/scripting.md#record-api) instance from the target collection, populated with the fields from the request body. 18 18 2. Call `r:save()`, which creates the record on the caller's PDS and indexes it locally. 19 19 3. Return the AT URI and CID of the newly created record. 20 20
+1 -1
docs/reference/scripts/expanded-query.md
··· 51 51 1. Query statuses from the target collection with pagination, same as a normal list query. 52 52 2. Extract the unique DIDs from the returned status URIs using `string.match`. 53 53 3. Build an AT URI for each DID's `app.bsky.actor.profile/self` record (this is where Bluesky profiles live). 54 - 4. Load all profiles in parallel with [`Record.load_all`](../../guides/scripting#static-methods). Profiles that aren't indexed locally return `nil` and are skipped. 54 + 4. Load all profiles in parallel with [`Record.load_all`](../../guides/scripting.md#static-methods). Profiles that aren't indexed locally return `nil` and are skipped. 55 55 5. Return statuses and profiles as separate keys, with the cursor from the status query. 56 56 57 57 ## Usage
+1 -1
docs/reference/scripts/get-record.md
··· 22 22 ## How it works 23 23 24 24 1. Check that the `uri` query parameter is present. Return a structured error if missing. 25 - 2. Look up the record with [`db.get`](../../guides/scripting#dbget), which returns the record table or `nil`. 25 + 2. Look up the record with [`db.get`](../../guides/scripting.md#dbget), which returns the record table or `nil`. 26 26 3. Return the record wrapped in an object. 27 27 28 28 ## Usage
+2 -2
docs/reference/scripts/list-or-fetch.md
··· 25 25 26 26 ## How it works 27 27 28 - 1. If a `uri` query parameter is provided, fetch that single record with [`db.get`](../../guides/scripting#dbget) and return it. If it doesn't exist, return a structured error (using `error()` would trigger a 500 response). 29 - 2. Otherwise, list records from the target collection using [`db.query`](../../guides/scripting#dbquery), with optional filtering by `did` and pagination via `limit`/`offset`. Since query parameters arrive as strings, `tonumber()` converts them to numbers. 28 + 1. If a `uri` query parameter is provided, fetch that single record with [`db.get`](../../guides/scripting.md#dbget) and return it. If it doesn't exist, return a structured error (using `error()` would trigger a 500 response). 29 + 2. Otherwise, list records from the target collection using [`db.query`](../../guides/scripting.md#dbquery), with optional filtering by `did` and pagination via `limit`/`offset`. Since query parameters arrive as strings, `tonumber()` converts them to numbers. 30 30 31 31 ## Usage 32 32
+1 -1
docs/reference/scripts/paginated-list.md
··· 23 23 ## How it works 24 24 25 25 1. Parse `limit` from the query string, defaulting to 20 and capping at 100. 26 - 2. Call [`db.query`](../../guides/scripting#dbquery) with the target collection, optional DID filter, and offset-based pagination. 26 + 2. Call [`db.query`](../../guides/scripting.md#dbquery) with the target collection, optional DID filter, and offset-based pagination. 27 27 3. Return the result directly. `db.query` returns `{ records = [...], cursor = "..." }` where `cursor` is present when more records exist. 28 28 29 29 ## Usage
+2 -2
docs/reference/scripts/sidecar-records.md
··· 32 32 33 33 ## How it works 34 34 35 - 1. Generate a single [`TID()`](../../guides/scripting#utility-globals) to use as the rkey for both records. 35 + 1. Generate a single [`TID()`](../../guides/scripting.md#utility-globals) to use as the rkey for both records. 36 36 2. Create a `Record` for each collection and call `r:set_rkey()` with the shared rkey. 37 - 3. Save both records in parallel with [`Record.save_all()`](../../guides/scripting#static-methods). 37 + 3. Save both records in parallel with [`Record.save_all()`](../../guides/scripting.md#static-methods). 38 38 4. Return both URIs so the client knows the identity of each record. 39 39 40 40 ## Usage
+2 -2
docs/reference/scripts/update-or-delete.md
··· 30 30 31 31 ## How it works 32 32 33 - 1. If `input.delete` is truthy and `input.uri` is provided, load the record with [`Record.load`](../../guides/scripting#static-methods) and delete it. 34 - 2. If only `input.uri` is provided, load the existing record with [`Record.load`](../../guides/scripting#static-methods), update its fields, and save it back. Since `_uri` is already set, `r:save()` calls `putRecord` instead of `createRecord`. 33 + 1. If `input.delete` is truthy and `input.uri` is provided, load the record with [`Record.load`](../../guides/scripting.md#static-methods) and delete it. 34 + 2. If only `input.uri` is provided, load the existing record with [`Record.load`](../../guides/scripting.md#static-methods), update its fields, and save it back. Since `_uri` is already set, `r:save()` calls `putRecord` instead of `createRecord`. 35 35 3. If neither condition matches, create a new record from the input. 36 36 37 37 ## Usage
+2 -2
docs/reference/scripts/upsert-record.md
··· 32 32 33 33 ## How it works 34 34 35 - 1. Use the client-provided `input.rkey` if present, otherwise generate a new [`TID()`](../../guides/scripting#utility-globals). This means omitting `rkey` always creates, while providing one enables updates. 36 - 2. Build the AT URI from the caller's DID, the target collection, and the rkey, then try to load it with [`Record.load`](../../guides/scripting#static-methods). 35 + 1. Use the client-provided `input.rkey` if present, otherwise generate a new [`TID()`](../../guides/scripting.md#utility-globals). This means omitting `rkey` always creates, while providing one enables updates. 36 + 2. Build the AT URI from the caller's DID, the target collection, and the rkey, then try to load it with [`Record.load`](../../guides/scripting.md#static-methods). 37 37 3. If the record exists, update its fields and save. Since `_uri` is already set, `r:save()` calls `putRecord`. 38 38 4. If it doesn't exist, create a new record, set the rkey explicitly with `r:set_rkey()`, and save. This calls `createRecord` with the specified rkey. 39 39
+7 -7
docs/reference/troubleshooting.md
··· 8 8 9 9 **Causes**: 10 10 11 - - The lexicon hasn't been uploaded yet. Check with `GET /admin/lexicons` or the [dashboard](../getting-started/dashboard). 11 + - The lexicon hasn't been uploaded yet. Check with `GET /admin/lexicons` or the [dashboard](../getting-started/dashboard.md). 12 12 - The lexicon's `defs.main.type` doesn't match the HTTP method. Queries are `GET`, procedures are `POST`. 13 13 - The NSID in the URL doesn't match the `id` field in the uploaded lexicon JSON. 14 14 ··· 18 18 19 19 **Causes**: 20 20 21 - - The query lexicon is missing a `target_collection`. Without it, the query doesn't know which records to read. See [Lexicons - target_collection](../guides/lexicons#target-collection). 21 + - The query lexicon is missing a `target_collection`. Without it, the query doesn't know which records to read. See [Lexicons - target_collection](../guides/lexicons.md#target-collection). 22 22 - The record-type lexicon hasn't finished backfilling. Check backfill status with `GET /admin/backfill/status` or the dashboard. 23 - - Records exist on the network but HappyView hasn't indexed them yet. Tap only picks up new events from when the collection filter was added. Use [backfill](../guides/backfill) for historical records. 23 + - Records exist on the network but HappyView hasn't indexed them yet. Tap only picks up new events from when the collection filter was added. Use [backfill](../guides/backfill.md) for historical records. 24 24 25 25 ## Procedure returns 401 Unauthorized 26 26 ··· 51 51 52 52 1. Check the server logs: the full error message is logged at error level but not exposed to the client. 53 53 2. Use `log("message")` in your script to trace execution. Output appears in server logs at debug level (requires `RUST_LOG` to include debug). 54 - 3. If you hit the execution limit, your script likely has an infinite loop or is processing too much data. See [Lua Scripting - Sandbox](../guides/scripting#sandbox). 54 + 3. If you hit the execution limit, your script likely has an infinite loop or is processing too much data. See [Lua Scripting - Sandbox](../guides/scripting.md#sandbox). 55 55 56 - See [Lua Scripting - Debugging](../guides/scripting#debugging) for more. 56 + See [Lua Scripting - Debugging](../guides/scripting.md#debugging) for more. 57 57 58 58 ## Backfill job stuck in "pending" or "running" 59 59 ··· 65 65 - The relay (`RELAY_URL`) may be unreachable or slow to respond. Check connectivity. 66 66 - Individual PDS fetches can fail silently. The worker logs warnings and continues. Check server logs for details. 67 67 68 - See [Backfill](../guides/backfill) for how the process works. 68 + See [Backfill](../guides/backfill.md) for how the process works. 69 69 70 70 ## Records not appearing in real time 71 71 ··· 97 97 - The database user doesn't have sufficient permissions. HappyView needs to create tables (migrations run automatically on startup). 98 98 - Postgres version is too old. HappyView requires Postgres 17+. 99 99 100 - See [Configuration](../getting-started/configuration) for environment variable details. 100 + See [Configuration](../getting-started/configuration.md) for environment variable details.
+8 -8
docs/reference/xrpc-api.md
··· 1 1 # XRPC API 2 2 3 - [XRPC](https://atproto.com/specs/xrpc) is the HTTP-based RPC protocol used by the AT Protocol. HappyView dynamically registers XRPC endpoints based on your uploaded [lexicons](../guides/lexicons): query lexicons become `GET /xrpc/{nsid}` routes, procedure lexicons become `POST /xrpc/{nsid}` routes. 3 + [XRPC](https://atproto.com/specs/xrpc) is the HTTP-based RPC protocol used by the AT Protocol. HappyView dynamically registers XRPC endpoints based on your uploaded [lexicons](../guides/lexicons.md): query lexicons become `GET /xrpc/{nsid}` routes, procedure lexicons become `POST /xrpc/{nsid}` routes. 4 4 5 - If a query or procedure lexicon has a [Lua script](../guides/scripting) attached, the script handles the request. Otherwise, HappyView uses built-in default behavior (described below). 5 + If a query or procedure lexicon has a [Lua script](../guides/scripting.md) attached, the script handles the request. Otherwise, HappyView uses built-in default behavior (described below). 6 6 7 7 ## Auth 8 8 ··· 71 71 72 72 ## Dynamic query endpoints 73 73 74 - Query endpoints are generated from lexicons with `type: "query"`. Without a [Lua script](../guides/scripting), they support two built-in modes depending on whether a `uri` parameter is provided. 74 + Query endpoints are generated from lexicons with `type: "query"`. Without a [Lua script](../guides/scripting.md), they support two built-in modes depending on whether a `uri` parameter is provided. 75 75 76 76 ### Single record 77 77 ··· 133 133 134 134 ## Dynamic procedure endpoints 135 135 136 - Procedure endpoints are generated from lexicons with `type: "procedure"`. Without a [Lua script](../guides/scripting), HappyView auto-detects create vs update based on whether the request body contains a `uri` field. 136 + Procedure endpoints are generated from lexicons with `type: "procedure"`. Without a [Lua script](../guides/scripting.md), HappyView auto-detects create vs update based on whether the request body contains a `uri` field. 137 137 138 138 ### Create a record 139 139 ··· 195 195 - `{"error": "script execution failed"}`: syntax error, runtime error, or missing `handle()` function 196 196 - `{"error": "script exceeded execution time limit"}`: the script hit the 1,000,000 instruction limit 197 197 198 - The full error details are logged server-side but not exposed to the client. See [Lua Scripting - Debugging](../guides/scripting#debugging) for how to diagnose script issues. 198 + The full error details are logged server-side but not exposed to the client. See [Lua Scripting - Debugging](../guides/scripting.md#debugging) for how to diagnose script issues. 199 199 200 200 ### PDS errors 201 201 ··· 203 203 204 204 ## Next steps 205 205 206 - - [Lua Scripting](../guides/scripting): Override the default query and procedure behavior with custom logic 207 - - [Lexicons](../guides/lexicons): Understand how lexicons generate these endpoints 208 - - [Admin API](admin-api): Manage lexicons and monitor your instance 206 + - [Lua Scripting](../guides/scripting.md): Override the default query and procedure behavior with custom logic 207 + - [Lexicons](../guides/lexicons.md): Understand how lexicons generate these endpoints 208 + - [Admin API](admin-api.md): Manage lexicons and monitor your instance
+9 -9
docs/tutorials/statusphere.md
··· 5 5 In this tutorial, you'll set up HappyView to act as the AppView for Statusphere. By the end, you'll have automatically indexed records and automatically generated XPRC endpoints. 6 6 7 7 :::tip 8 - This tutorial assumes you have a running HappyView instance. If you don't, start with the [Quickstart](../getting-started/deployment/railway) or one of the local development guides ([Docker](../getting-started/deployment/docker), [from source](../getting-started/deployment/other)). 8 + This tutorial assumes you have a running HappyView instance. If you don't, start with the [Quickstart](../getting-started/deployment/railway.md) or one of the local development guides ([Docker](../getting-started/deployment/docker.md), [from source](../getting-started/deployment/other.md)). 9 9 ::: 10 10 11 11 ## The Statusphere lexicon ··· 23 23 24 24 First, upload the `xyz.statusphere.status` lexicon to HappyView. This tells HappyView to start indexing Statusphere records from across the network as they're created, updated, or deleted. 25 25 26 - The examples below use `$TOKEN` as a placeholder for an AIP-issued access token. See [Authentication](../getting-started/authentication) for how to get one. 26 + The examples below use `$TOKEN` as a placeholder for an AIP-issued access token. See [Authentication](../getting-started/authentication.md) for how to get one. 27 27 28 28 ```sh 29 29 curl -X POST http://localhost:3000/admin/lexicons \ ··· 52 52 }' 53 53 ``` 54 54 55 - HappyView now subscribes to `xyz.statusphere.status` via Tap. The `backfill` flag tells HappyView to also index existing status records from the network. You can monitor progress with `GET /admin/backfill/status` or the [dashboard](../getting-started/dashboard). 55 + HappyView now subscribes to `xyz.statusphere.status` via Tap. The `backfill` flag tells HappyView to also index existing status records from the network. You can monitor progress with `GET /admin/backfill/status` or the [dashboard](../getting-started/dashboard.md). 56 56 57 57 :::tip 58 - Since the `xyz.statusphere.status` lexicon is [published on the AT Protocol network](../guides/lexicons#network-lexicons), you can also add it as a network lexicon instead of uploading the JSON manually: 58 + Since the `xyz.statusphere.status` lexicon is [published on the AT Protocol network](../guides/lexicons.md#network-lexicons), you can also add it as a network lexicon instead of uploading the JSON manually: 59 59 60 60 ```sh 61 61 curl -X POST http://localhost:3000/admin/network-lexicons \ ··· 129 129 } 130 130 ``` 131 131 132 - See [XRPC API](../reference/xrpc-api) for the full default query behavior. 132 + See [XRPC API](../reference/xrpc-api.md) for the full default query behavior. 133 133 134 134 ## Step 4: Enhance the query with a Lua script 135 135 136 - The default query behavior works, but let's customize it with a [Lua script](../guides/scripting). Here's a script that handles single-record lookups by URI and paginated listing with an optional DID filter: 136 + The default query behavior works, but let's customize it with a [Lua script](../guides/scripting.md). Here's a script that handles single-record lookups by URI and paginated listing with an optional DID filter: 137 137 138 138 ```lua 139 139 function handle() ··· 298 298 299 299 ## Next steps 300 300 301 - - [Lua Scripting](../guides/scripting): Explore the full Record and database APIs to build more complex queries 302 - - [Lexicons](../guides/lexicons): Learn about network lexicons, the backfill flag, and target collections 303 - - [XRPC API](../reference/xrpc-api): Understand how the generated endpoints behave 301 + - [Lua Scripting](../guides/scripting.md): Explore the full Record and database APIs to build more complex queries 302 + - [Lexicons](../guides/lexicons.md): Learn about network lexicons, the backfill flag, and target collections 303 + - [XRPC API](../reference/xrpc-api.md): Understand how the generated endpoints behave 304 304 - [Statusphere example app](https://github.com/bluesky-social/statusphere-example-app): See the full Statusphere frontend 305 305 - [ATProto Statusphere guide](https://atproto.com/guides/applications): Deep dive into how the app works at the protocol level