Highly ambitious ATProtocol AppView service and sdks

docs and more docs

+65 -36
README.md
··· 1 1 # Slices 2 2 3 - An open-source platform for building AT Protocol appviews with custom data 3 + An open-source platform for building AT Protocol AppViews with custom data 4 4 schemas, automatic SDK generation, and built-in sync capabilities. 5 5 6 + ⚠️ **Alpha Release** Version 0.x - This project is in active development 7 + approaching stability. Core features are implemented and functional, though APIs 8 + may undergo refinements. Suitable for early adoption and development use. 9 + Production deployment is possible with thorough testing for your specific use 10 + case. 11 + 6 12 ## Overview 7 13 8 14 Slices enables developers to create "slices" - custom appviews within the AT ··· 20 26 - **OAuth Integration**: Built-in AT Protocol authentication 21 27 - **Multi-tenant Architecture**: Each slice operates independently with its own 22 28 data validated against its lexicons 23 - - **Dynamic API Endpoints**: CRUD operations automatically created for each 24 - lexicon record type (collection) 29 + - **Dynamic API Endpoints**: CRUD operations with SQL-like queries automatically 30 + created for each lexicon record type (collection) 25 31 26 32 ## Documentation 27 33 ··· 32 38 - [API Reference](./docs/api-reference.md) - Complete API documentation 33 39 - [SDK Usage](./docs/sdk-usage.md) - Using generated TypeScript clients 34 40 35 - ## Quick Start 41 + ## Development Quick Start 36 42 37 43 ### Prerequisites 38 44 ··· 46 52 1. Clone the repository: 47 53 48 54 ```bash 49 - git clone https://tangled.sh/justslices.net/core 55 + git clone https://tangled.sh/slices.network/slices 50 56 cd core 51 57 ``` 52 58 53 59 2. Set up environment variables: 54 60 55 - Create `.env` files in both `/api` and `/frontend` directories (see 56 - [Getting Started](./docs/getting-started.md) for details). 61 + Create `.env` files in both `/api` and `/frontend` directories (see environment 62 + variables section below). 63 + 64 + 3. Start the required infrastructure services: 65 + 66 + > **Note**: This section is a work in progress. Currently using Cloudflare 67 + > tunnels, but support for alternative solutions like ngrok or Tailscale would 68 + > be welcome contributions. 69 + 70 + ```bash 71 + # Start PostgreSQL, AIP OAuth service, Cloudflare tunnel, and Redis 72 + CLOUDFLARE_TUNNEL_TOKEN=<your-token> AIP_EXTERNAL_BASE=<your-tunnel-url> docker compose up -d 73 + ``` 57 74 58 - 3. Start the services: 75 + You'll need to provide: 76 + 77 + - `CLOUDFLARE_TUNNEL_TOKEN`: Your Cloudflare tunnel token for secure access 78 + - `AIP_EXTERNAL_BASE`: The external URL for the AIP OAuth service (e.g., 79 + `https://tunnel.example.com`) 80 + 81 + 4. Start the application services: 59 82 60 83 ```bash 61 84 # Start the API ··· 67 90 deno task dev 68 91 ``` 69 92 70 - 4. Visit `http://localhost:8000` to access the web interface. 93 + 5. Visit `http://localhost:8080` to access the web interface. 71 94 72 95 ## Project Structure 73 96 ··· 78 101 79 102 - **AT Protocol XRPC Handlers**: Dynamic endpoints for slice-specific 80 103 collections with full CRUD operations 104 + - **Lexicon Validation**: Validates all records against defined lexicon schemas 81 105 - **Sync Engine**: Bulk synchronization from AT Protocol repositories 82 106 - **Jetstream Integration**: Real-time data streaming from AT Protocol firehose 83 107 - **Database Layer**: PostgreSQL integration with slice-aware queries 84 - - **SDK Generation**: Automatically generates type-safe TypeScript clients and 85 - OpenAPI specifications 86 108 - **OAuth Integration**: Handles AT Protocol OAuth flows and token management 87 109 88 110 ### Frontend (`/frontend`) ··· 168 190 169 191 ### API Configuration 170 192 171 - | Variable | Description | Required | 172 - | --------------- | ---------------------------- | -------- | 173 - | `DATABASE_URL` | PostgreSQL connection string | Yes | 174 - | `AUTH_BASE_URL` | AIP OAuth service URL | Yes | 175 - | `PORT` | Server port (default: 3000) | No | 193 + | Variable | Description | Required | Default | 194 + | -------------------------------------- | -------------------------------------------------------------- | -------- | ------------------------------------- | 195 + | `DATABASE_URL` | PostgreSQL connection string | Yes | - | 196 + | `AUTH_BASE_URL` | Authentication service base URL | No | `http://localhost:8081` | 197 + | `PORT` | Server port | No | `3000` | 198 + | `PROCESS_TYPE` | Process type: `all` (HTTP + Jetstream), `app`, or `worker` | No | `all` | 199 + | `RELAY_ENDPOINT` | AT Protocol relay endpoint for backfill | No | `https://relay1.us-west.bsky.network` | 200 + | `JETSTREAM_HOSTNAME` | AT Protocol Jetstream hostname | No | - | 201 + | `SYSTEM_SLICE_URI` | System slice URI | No | Default slice URI | 202 + | `DEFAULT_MAX_SYNC_REPOS` | Maximum repositories per sync operation | No | `5000` | 203 + | `REDIS_URL` | Redis connection URL (optional, falls back to in-memory cache) | No | - | 204 + | `REDIS_TTL_SECONDS` | Redis cache TTL in seconds | No | `3600` | 205 + | `JETSTREAM_CURSOR_WRITE_INTERVAL_SECS` | Interval for writing Jetstream cursor position | No | `30` | 206 + | `RUST_LOG` | Rust logging level | No | `debug` | 176 207 177 208 ### Frontend Configuration 178 209 179 - | Variable | Description | Required | 180 - | --------------------- | ------------------------------- | -------- | 181 - | `OAUTH_CLIENT_ID` | OAuth application client ID | Yes | 182 - | `OAUTH_CLIENT_SECRET` | OAuth application client secret | Yes | 183 - | `OAUTH_REDIRECT_URI` | OAuth callback URL | Yes | 184 - | `OAUTH_AIP_BASE_URL` | AIP OAuth service URL | Yes | 185 - | `API_URL` | Backend API base URL | Yes | 186 - | `SLICE_URI` | Default slice URI for queries | Yes | 210 + | Variable | Description | Required | Default | 211 + | --------------------- | --------------------------------------------- | -------- | ----------- | 212 + | `OAUTH_CLIENT_ID` | OAuth application client ID | Yes | - | 213 + | `OAUTH_CLIENT_SECRET` | OAuth application client secret | Yes | - | 214 + | `OAUTH_REDIRECT_URI` | OAuth callback URL | Yes | - | 215 + | `OAUTH_AIP_BASE_URL` | AIP OAuth service URL | Yes | - | 216 + | `API_URL` | Backend API base URL | Yes | - | 217 + | `SLICE_URI` | Default slice URI for queries | Yes | - | 218 + | `ADMIN_DID` | Admin DID for privileged operations | No | - | 219 + | `DATABASE_URL` | SQLite database path (frontend session store) | No | `slices.db` | 187 220 188 221 ## Roadmap 189 222 ··· 192 225 - Documentation 193 226 - Frontend UX improvements/social features 194 227 - Support more search and filtering params in collection xrpx handlers and SDK 195 - - Surface jetstream and sync logs in the UI 196 - - Improve sync and jetstream reliability 228 + - SDK error handling improvements (i.e. fuzzy search, date ranges, geo?? etc) 229 + - Improve sync and jetstream performace, logging, error handling, and 230 + reliability 197 231 - Monitor api container performance and resource usage 198 232 199 233 ### Planned Features 200 234 201 235 - Labeler service integration 202 - - CLI tool 203 - - API rate limiting 204 236 - Enhanced lexicon management UI 205 237 - Lexicon discovery and sharing 238 + - More cli templates (React, Expo, Astro, etc) and examples 206 239 207 240 ## Community 208 241 209 - - **Bluesky**: [@justslices.net](https://bsky.app/profile/justslices.net) 210 - - **Discord**: [Join our server](https://discord.gg/your-invite) 242 + - **Bluesky**: [@slices.network](https://bsky.app/profile/slices.network) 243 + - **Discord**: [Join our server](https://discord.gg/NqSd3eW8S8) 211 244 212 245 ## Support 213 246 214 247 - [Documentation](./docs/) 215 - - [Discord Community](https://discord.gg/your-invite) 248 + - [Discord Community](https://discord.gg/NqSd3eW8S8) 216 249 217 250 ## License 218 251 ··· 223 256 224 257 - Built on the [AT Protocol](https://atproto.com) 225 258 - Inspired by the AT Protocol community 226 - - Thanks to all contributors 227 - 228 - ## Status 229 - 230 - This project is in active development. APIs may change as we approach v1.0. 259 + - Thanks to all contributors and early adopters 231 260 232 261 --- 233 262
+340 -483
docs/api-reference.md
··· 1 1 # API Reference 2 2 3 - Complete reference for Slices API endpoints. 4 - 5 3 ## Base URL 6 4 7 - ```bash 5 + ``` 8 6 https://api.slices.network/xrpc/ 9 7 ``` 10 8 11 - ## Authentication 12 - 13 - Most write operations require OAuth 2.0 authentication. Include the access token 14 - in the Authorization header: 15 - 9 + For local development: 16 10 ``` 17 - Authorization: Bearer YOUR_ACCESS_TOKEN 11 + http://localhost:3000/xrpc/ 18 12 ``` 19 13 20 - Read operations typically work without authentication. 21 - 22 - ## Dynamic Collection Endpoints 23 - 24 - For each collection in your slice, the following endpoints are automatically 25 - generated: 26 - 27 - ### `[collection].getRecords` 28 - 29 - Get records in a collection. 30 - 31 - **Method**: GET 32 - 33 - **Parameters**: 34 - 35 - - `slice` (string, required): Slice URI 36 - - `limit` (number, optional): Maximum records (default: 50) 37 - - `cursor` (string, optional): Pagination cursor 38 - - `where` (object, optional): Filter conditions using field-specific queries 39 - - `sortBy` (array, optional): Sort specification with field and direction 40 - objects 41 - 42 - ### `[collection].getRecord` 43 - 44 - Get a single record. 45 - 46 - **Method**: GET 14 + ## Authentication 47 15 48 - **Parameters**: 16 + Write operations require OAuth 2.0 authentication with a Bearer token: 49 17 50 - - `slice` (string, required): Slice URI 51 - - `uri` (string, required): Record URI 52 - 53 - ### `[collection].countRecords` 54 - 55 - Count records in a collection. 56 - 57 - **Method**: GET 58 - 59 - **Parameters**: 60 - 61 - - `slice` (string, required): Slice URI 62 - - `where` (object, optional): Filter conditions using field-specific queries 63 - - Other filter parameters (no limit/cursor) 64 - 65 - **Response**: 66 - 67 - ```json Code 68 - { 69 - "count": 150 70 - } 71 18 ``` 72 - 73 - ### `[collection].createRecord` 74 - 75 - Create a new record. 76 - 77 - **Method**: POST 78 - 79 - **Authentication**: Required 80 - 81 - **Body**: 82 - 83 - ```json Code 84 - { 85 - "slice": "at://your-slice-uri", 86 - "record": { 87 - "$type": "com.recordcollector.album", 88 - "title": "Superunknown", 89 - "artist": "Soundgarden", 90 - "releaseDate": "1994-03-08", 91 - "condition": "Near Mint", 92 - "genre": ["grunge", "alternative metal"] 93 - }, 94 - "rkey": "3jklmno456" 95 - } 19 + Authorization: Bearer YOUR_ACCESS_TOKEN 96 20 ``` 97 21 98 - ### `[collection].updateRecord` 99 - 100 - Update an existing record. 101 - 102 - **Method**: POST 103 - 104 - **Authentication**: Required 105 - 106 - **Body**: 107 - 108 - ```json Code 109 - { 110 - "slice": "at://your-slice-uri", 111 - "rkey": "3xyz789abc", 112 - "record": { 113 - "$type": "com.recordcollector.album", 114 - "title": "Dirt", 115 - "artist": "Alice in Chains", 116 - "releaseDate": "1992-09-29", 117 - "condition": "Very Good Plus", 118 - "notes": "Minor sleeve wear, vinyl plays perfectly" 119 - } 120 - } 121 - ``` 22 + Read operations are public by default. 122 23 123 - ### `[collection].deleteRecord` 24 + ## Collection Endpoints 124 25 125 - Delete a record. 26 + For each collection in your slice (e.g., `com.recordcollector.album`), the following endpoints are automatically generated: 126 27 127 - **Method**: POST 28 + # **{collection}.getRecords** 128 29 129 - **Authentication**: Required 30 + > List records with filtering, sorting, and pagination. 130 31 131 - **Body**: 32 + **Method:** `POST` 33 + **Endpoint:** `/xrpc/{collection}.getRecords` 34 + **Example:** `/xrpc/com.recordcollector.album.getRecords` 132 35 133 - ```json Code 36 + **Request Body:** 37 + ```json 134 38 { 135 - "rkey": "3abc123xyz" 39 + "slice": "at://did:plc:abc123/network.slices.slice/xyz789", 40 + "limit": 20, 41 + "cursor": "optional-pagination-cursor", 42 + "where": { 43 + "genre": { "contains": "grunge" }, 44 + "condition": { "in": ["Mint", "Near Mint"] } 45 + }, 46 + "sortBy": [ 47 + { "field": "releaseDate", "direction": "desc" } 48 + ] 136 49 } 137 50 ``` 138 51 139 - ## Core Endpoints 140 - 141 - ### Slice Management 52 + **Parameters:** 53 + - `slice` (string, required): The slice URI to query 54 + - `limit` (number, optional): Maximum records to return (default: 50, max: 100) 55 + - `cursor` (string, optional): Pagination cursor from previous response 56 + - `where` (object, optional): Filter conditions. Field filters support operators: `eq`, `contains`, `in`. Special field `json` searches across all fields 57 + - `sortBy` (array, optional): Sort specification. Each item has `field` and `direction` ("asc" or "desc") 142 58 143 - ### `network.slices.slice.getRecords` 144 - 145 - Get all slices. 146 - 147 - **Method**: GET 148 - 149 - **Parameters**: 150 - 151 - - `limit` (number, optional): Maximum records to return (default: 50) 152 - - `cursor` (string, optional): Pagination cursor 153 - - `where` (object, optional): Filter conditions using field-specific queries 154 - - `sortBy` (array, optional): Sort specification with field and direction 155 - objects 156 - 157 - **Response**: 158 - 159 - ```json Code 59 + **Response:** 60 + ```json 160 61 { 161 62 "records": [ 162 63 { 163 - "uri": "at://did:plc:abc/network.slices.slice/xyz", 164 - "cid": "bafyrei...", 165 - "did": "did:plc:abc", 166 - "collection": "network.slices.slice", 64 + "uri": "at://did:plc:user123/com.recordcollector.album/3l2w4x5y6z", 65 + "cid": "bafyreigbtj4x7ip5legnfznufuopl4sg4knzc2cof6duas4b3q2fy6swua", 66 + "did": "did:plc:user123", 67 + "collection": "com.recordcollector.album", 167 68 "value": { 168 - "name": "My Slice", 169 - "domain": "com.example", 170 - "createdAt": "2024-01-01T00:00:00Z" 69 + "$type": "com.recordcollector.album", 70 + "title": "Nevermind", 71 + "artist": "Nirvana", 72 + "releaseDate": "1991-09-24T00:00:00.000Z", 73 + "genre": ["grunge", "alternative rock"], 74 + "condition": "Near Mint", 75 + "notes": "Original pressing, includes poster" 171 76 }, 172 - "indexedAt": "2024-01-01T00:00:00Z" 77 + "indexedAt": "2024-03-15T10:30:15.123Z" 173 78 } 174 79 ], 175 - "cursor": "next-page-cursor" 80 + "cursor": "next-page-cursor-xyz789" 176 81 } 177 82 ``` 178 83 179 - ### `network.slices.slice.getRecord` 180 - 181 - Get a specific slice by URI. 84 + # **{collection}.getRecord** 182 85 183 - **Method**: GET 184 - 185 - **Parameters**: 186 - 187 - - `uri` (string, required): AT Protocol URI of the slice 188 - 189 - **Response**: Single record object (same structure as getRecords item) 190 - 191 - ### `network.slices.slice.createRecord` 192 - 193 - Create a new slice. 194 - 195 - **Method**: POST 86 + > Get a single record by URI. 196 87 197 - **Authentication**: Required 88 + **Method:** `GET` 89 + **Endpoint:** `/xrpc/{collection}.getRecord` 90 + **Example:** `/xrpc/com.recordcollector.album.getRecord` 198 91 199 - **Body**: 92 + **Query Parameters:** 93 + - `slice` (string, required): The slice URI 94 + - `uri` (string, required): The AT Protocol URI of the record 200 95 201 - ```json Code 202 - { 203 - "slice": "at://your-slice-uri", 204 - "record": { 205 - "$type": "network.slices.slice", 206 - "name": "My New Slice", 207 - "domain": "com.example", 208 - "createdAt": "2024-01-01T00:00:00Z" 209 - }, 210 - "rkey": "optional-record-key" 211 - } 96 + **Example Request:** 97 + ``` 98 + GET /xrpc/com.recordcollector.album.getRecord?slice=at://did:plc:abc123/network.slices.slice/xyz789&uri=at://did:plc:user123/com.recordcollector.album/3l2w4x5y6z 212 99 ``` 213 100 214 - **Response**: 215 - 216 - ```json Code 101 + **Response:** 102 + ```json 217 103 { 218 - "uri": "at://did:plc:abc/network.slices.slice/xyz", 219 - "cid": "bafyrei..." 104 + "uri": "at://did:plc:user123/com.recordcollector.album/3l2w4x5y6z", 105 + "cid": "bafyreigbtj4x7ip5legnfznufuopl4sg4knzc2cof6duas4b3q2fy6swua", 106 + "did": "did:plc:user123", 107 + "collection": "com.recordcollector.album", 108 + "value": { 109 + "$type": "com.recordcollector.album", 110 + "title": "Nevermind", 111 + "artist": "Nirvana", 112 + "releaseDate": "1991-09-24T00:00:00.000Z", 113 + "genre": ["grunge", "alternative rock"], 114 + "condition": "Near Mint", 115 + "notes": "Original pressing, includes poster" 116 + }, 117 + "indexedAt": "2024-03-15T10:30:15.123Z" 220 118 } 221 119 ``` 222 120 223 - ### Slice Operations 121 + # **{collection}.countRecords** 224 122 225 - ### `network.slices.slice.stats` 123 + > Count records matching filter criteria. 226 124 227 - Get statistics for a slice. 125 + **Method:** `POST` 126 + **Endpoint:** `/xrpc/{collection}.countRecords` 127 + **Example:** `/xrpc/com.recordcollector.album.countRecords` 228 128 229 - **Method**: POST 230 - 231 - **Body**: 232 - 233 - ```json Code 129 + **Request Body:** 130 + ```json 234 131 { 235 - "slice": "at://your-slice-uri" 132 + "slice": "at://did:plc:abc123/network.slices.slice/xyz789", 133 + "where": { 134 + "condition": { "in": ["Mint", "Near Mint"] }, 135 + "genre": { "contains": "grunge" } 136 + } 236 137 } 237 138 ``` 238 139 239 - **Response**: 240 - 241 - ```json Code 140 + **Response:** 141 + ```json 242 142 { 243 143 "success": true, 244 - "collections": ["com.recordcollector.album", "com.recordcollector.review"], 245 - "collectionStats": [ 246 - { 247 - "collection": "com.recordcollector.album", 248 - "recordCount": 427, 249 - "uniqueActors": 23 250 - } 251 - ], 252 - "totalLexicons": 5, 253 - "totalRecords": 500, 254 - "totalActors": 25, 255 - "message": "Statistics retrieved successfully" 144 + "count": 42, 145 + "message": "Count retrieved successfully" 256 146 } 257 147 ``` 258 148 259 - ### `network.slices.slice.listSliceRecords` 149 + # **{collection}.createRecord** 260 150 261 - List records across multiple collections in a slice. 151 + > Create a new record. 262 152 263 - **Method**: POST 264 - 265 - **Body**: 153 + **Method:** `POST` 154 + **Endpoint:** `/xrpc/{collection}.createRecord` 155 + **Example:** `/xrpc/com.recordcollector.album.createRecord` 156 + **Authentication:** Required 266 157 267 - ```json Code 158 + **Request Body:** 159 + ```json 268 160 { 269 - "slice": "at://your-slice-uri", 270 - "collections": ["com.recordcollector.album", "com.recordcollector.review"], 271 - "authors": ["did:plc:optional-filter"], 272 - "limit": 20, 273 - "cursor": "pagination-cursor" 161 + "slice": "at://did:plc:abc123/network.slices.slice/xyz789", 162 + "rkey": "optional-custom-key", 163 + "record": { 164 + "$type": "com.recordcollector.album", 165 + "title": "In Utero", 166 + "artist": "Nirvana", 167 + "releaseDate": "1993-09-21T00:00:00.000Z", 168 + "genre": ["grunge", "alternative rock"], 169 + "condition": "Very Good Plus", 170 + "notes": "Some light wear on sleeve" 171 + } 274 172 } 275 173 ``` 276 174 277 - **Response**: 175 + **Parameters:** 176 + - `slice` (string, required): The slice URI 177 + - `rkey` (string, optional): Custom record key (auto-generated if omitted) 178 + - `record` (object, required): The record data matching your lexicon schema 278 179 279 - ```json Code 180 + **Response:** 181 + ```json 280 182 { 281 - "success": true, 282 - "records": [ 283 - { 284 - "uri": "at://did:plc:abc/com.recordcollector.album/xyz", 285 - "cid": "bafyrei...", 286 - "did": "did:plc:abc", 287 - "collection": "com.recordcollector.album", 288 - "value": {/* record data */}, 289 - "indexedAt": "2024-01-01T00:00:00Z" 290 - } 291 - ], 292 - "cursor": "next-page-cursor" 183 + "uri": "at://did:plc:user123/com.recordcollector.album/3abc456def", 184 + "cid": "bafyreihj7x5legnfznufuopl4sg4knzc2cof6duas4b3q2fy6swua" 293 185 } 294 186 ``` 295 187 296 - ### `network.slices.slice.searchSliceRecords` 297 - 298 - Search records across multiple collections in a slice by content. 188 + # **{collection}.updateRecord** 299 189 300 - **Method**: POST 190 + > Update an existing record. 301 191 302 - **Body**: 192 + **Method:** `POST` 193 + **Endpoint:** `/xrpc/{collection}.updateRecord` 194 + **Example:** `/xrpc/com.recordcollector.album.updateRecord` 195 + **Authentication:** Required 303 196 304 - ```json Code 197 + **Request Body:** 198 + ```json 305 199 { 306 - "slice": "at://your-slice-uri", 307 - "collections": ["com.recordcollector.album", "com.recordcollector.review"], 308 - "search": "search term", 309 - "authors": ["did:plc:optional-filter"], 310 - "limit": 20, 311 - "cursor": "pagination-cursor" 200 + "slice": "at://did:plc:abc123/network.slices.slice/xyz789", 201 + "rkey": "3abc456def", 202 + "record": { 203 + "$type": "com.recordcollector.album", 204 + "title": "In Utero", 205 + "artist": "Nirvana", 206 + "releaseDate": "1993-09-21T00:00:00.000Z", 207 + "genre": ["grunge", "alternative rock", "noise rock"], 208 + "condition": "Very Good", 209 + "notes": "Updated: slight ring wear visible, plays perfectly" 210 + } 312 211 } 313 212 ``` 314 213 315 - **Response**: 214 + **Parameters:** 215 + - `slice` (string, required): The slice URI 216 + - `rkey` (string, required): The record key to update 217 + - `record` (object, required): The complete updated record data 316 218 317 - ```json Code 219 + **Response:** 220 + ```json 318 221 { 319 - "success": true, 320 - "records": [ 321 - { 322 - "uri": "at://did:plc:abc/com.recordcollector.album/xyz", 323 - "cid": "bafyrei...", 324 - "did": "did:plc:abc", 325 - "collection": "com.recordcollector.album", 326 - "value": {/* record data */}, 327 - "indexedAt": "2024-01-01T00:00:00Z" 328 - } 329 - ], 330 - "cursor": "next-page-cursor" 222 + "uri": "at://did:plc:user123/com.recordcollector.album/3abc456def", 223 + "cid": "bafyreiabc123legnfznufuopl4sg4knzc2cof6duas4b3q2fy6swua" 331 224 } 332 225 ``` 333 226 334 - ### `network.slices.slice.syncUserCollections` 227 + # **{collection}.deleteRecord** 335 228 336 - Synchronously sync collections for the authenticated user. 229 + > Delete a record. 337 230 338 - **Method**: POST 339 - 340 - **Authentication**: Required 341 - 342 - **Body**: 231 + **Method:** `POST` 232 + **Endpoint:** `/xrpc/{collection}.deleteRecord` 233 + **Example:** `/xrpc/com.recordcollector.album.deleteRecord` 234 + **Authentication:** Required 343 235 344 - ```json Code 236 + **Request Body:** 237 + ```json 345 238 { 346 - "slice": "at://your-slice-uri", 347 - "timeoutSeconds": 30 239 + "rkey": "3abc456def" 348 240 } 349 241 ``` 350 242 351 - **Response**: 352 - 353 - ```json Code 354 - { 355 - "success": true, 356 - "reposProcessed": 1, 357 - "recordsSynced": 45, 358 - "timedOut": false, 359 - "message": "Sync completed successfully" 360 - } 243 + **Response:** 244 + ```json 245 + {} 361 246 ``` 362 247 363 - ### `network.slices.slice.startSync` 248 + ## Filtering 364 249 365 - Start an asynchronous bulk sync job. 250 + The `where` parameter supports powerful filtering: 366 251 367 - **Method**: POST 252 + ### Filter Operators 368 253 369 - **Authentication**: Required 370 - 371 - **Body**: 372 - 373 - ```json Code 374 - { 375 - "slice": "at://your-slice-uri", 376 - "collections": ["com.recordcollector.album"], 377 - "externalCollections": ["app.bsky.actor.profile"], 378 - "repos": ["did:plc:abc", "did:plc:xyz"], 379 - "limitPerRepo": 100 380 - } 254 + - **`eq`**: Exact match 255 + ```json 256 + { "condition": { "eq": "Mint" } } 381 257 ``` 382 258 383 - **Response**: 259 + - **`contains`**: Partial text match (case-insensitive) 260 + ```json 261 + { "artist": { "contains": "pearl jam" } } 262 + ``` 384 263 385 - ```json Code 386 - { 387 - "success": true, 388 - "jobId": "job-uuid", 389 - "message": "Sync job started" 390 - } 264 + - **`in`**: Match any value in array 265 + ```json 266 + { "condition": { "in": ["Mint", "Near Mint", "Very Good Plus"] } } 391 267 ``` 392 268 393 - ### `network.slices.slice.codegen` 269 + ### Special Fields 394 270 395 - Generate TypeScript client code. 396 - 397 - **Method**: POST 398 - 399 - **Body**: 400 - 401 - ```json Code 402 - { 403 - "target": "typescript", 404 - "slice": "at://your-slice-uri" 405 - } 271 + - **`json`**: Search across all fields 272 + ```json 273 + { "json": { "contains": "nirvana" } } 406 274 ``` 407 275 408 - **Response**: 409 - 410 - ```json Code 276 + - **System fields**: Filter by record metadata 277 + ```json 411 278 { 412 - "success": true, 413 - "generatedCode": "// Generated TypeScript client code..." 279 + "did": { "eq": "did:plc:user123" }, 280 + "collection": { "eq": "com.recordcollector.album" }, 281 + "indexedAt": { "contains": "2024-03" } 414 282 } 415 283 ``` 416 284 417 - ## Lexicon Management 285 + ### Complex Filtering Examples 418 286 419 - ### `network.slices.lexicon.getRecords` 420 - 421 - Get lexicons in a slice. 422 - 423 - **Method**: GET 424 - 425 - **Parameters**: Same as collection.getRecords 426 - 427 - ### `network.slices.lexicon.countRecords` 428 - 429 - Count lexicons in a slice. 430 - 431 - **Method**: GET 432 - 433 - **Parameters**: Same as collection.getRecords (except limit and cursor) 434 - 435 - **Response**: 436 - 437 - ```json Code 287 + **Multiple conditions (AND logic):** 288 + ```json 438 289 { 439 - "count": 10 290 + "where": { 291 + "genre": { "contains": "grunge" }, 292 + "condition": { "in": ["Mint", "Near Mint"] }, 293 + "releaseDate": { "contains": "1991" } 294 + } 440 295 } 441 296 ``` 442 297 443 - ### `network.slices.lexicon.createRecord` 444 - 445 - Add a lexicon to a slice. 446 - 447 - **Method**: POST 448 - 449 - **Authentication**: Required 450 - 451 - **Body**: 452 - 453 - ```json Code 298 + **Array field filtering:** 299 + ```json 454 300 { 455 - "slice": "at://your-slice-uri", 456 - "record": { 457 - "$type": "network.slices.lexicon", 458 - "nsid": "com.recordcollector.album", 459 - "definitions": "{\"lexicon\": 1, ...}", 460 - "createdAt": "2024-01-01T00:00:00Z", 461 - "slice": "at://your-slice-uri" 301 + "where": { 302 + "genre": { "contains": "alternative" } 462 303 } 463 304 } 464 305 ``` 465 306 466 - ## Actor Management 467 - 468 - ### `network.slices.slice.getActors` 469 - 470 - Get actors (users) in a slice. 471 - 472 - **Method**: GET 473 - 474 - **Parameters**: 475 - 476 - - `slice` (string, required): Slice URI 477 - - `search` (string, optional): Search query 478 - - `dids` (string[], optional): Filter by DIDs 479 - - `limit` (number, optional): Maximum results 480 - - `cursor` (string, optional): Pagination cursor 307 + ## Sorting 481 308 482 - **Response**: 309 + Sort results using the `sortBy` parameter: 483 310 484 - ```json Code 311 + ```json 485 312 { 486 - "actors": [ 487 - { 488 - "did": "did:plc:abc", 489 - "handle": "user.bsky.social", 490 - "sliceUri": "at://slice-uri", 491 - "indexedAt": "2024-01-01T00:00:00Z" 492 - } 493 - ], 494 - "cursor": "next-page" 313 + "sortBy": [ 314 + { "field": "releaseDate", "direction": "desc" }, 315 + { "field": "artist", "direction": "asc" } 316 + ] 495 317 } 496 318 ``` 497 319 498 - ## Blob Upload 320 + **Common sort patterns:** 321 + - Newest releases first: `[{ "field": "releaseDate", "direction": "desc" }]` 322 + - Alphabetical by artist: `[{ "field": "artist", "direction": "asc" }]` 323 + - By condition (best first): `[{ "field": "condition", "direction": "asc" }]` 324 + - Recently indexed: `[{ "field": "indexedAt", "direction": "desc" }]` 499 325 500 - ### `com.atproto.repo.uploadBlob` 326 + ## Pagination 501 327 502 - Upload a blob (image, file). 328 + Use cursor-based pagination for large result sets: 503 329 504 - **Method**: POST 330 + ```javascript 331 + // First request 332 + const page1 = await fetch('/xrpc/com.recordcollector.album.getRecords', { 333 + method: 'POST', 334 + body: JSON.stringify({ 335 + slice: 'at://your-slice-uri', 336 + limit: 20 337 + }) 338 + }); 505 339 506 - **Authentication**: Required 507 - 508 - **Headers**: 509 - 510 - - `Content-Type`: MIME type of the blob 511 - 512 - **Body**: Raw binary data 513 - 514 - **Response**: 515 - 516 - ```json Code 517 - { 518 - "blob": { 519 - "$type": "blob", 520 - "ref": { "$link": "bafkrei..." }, 521 - "mimeType": "image/jpeg", 522 - "size": 127198 523 - } 524 - } 340 + // Next page using cursor 341 + const page2 = await fetch('/xrpc/com.recordcollector.album.getRecords', { 342 + method: 'POST', 343 + body: JSON.stringify({ 344 + slice: 'at://your-slice-uri', 345 + limit: 20, 346 + cursor: page1.cursor 347 + }) 348 + }); 525 349 ``` 526 350 527 351 ## Error Responses 528 352 529 - All endpoints may return error responses: 353 + All endpoints return consistent error format: 530 354 531 - ```json Code 355 + ```json 532 356 { 533 357 "error": "InvalidRequest", 534 - "message": "Detailed error message" 358 + "message": "Missing required parameter: slice" 535 359 } 536 360 ``` 537 361 538 - Common HTTP status codes: 539 - 362 + **Common HTTP Status Codes:** 540 363 - `200`: Success 541 - - `400`: Bad request 542 - - `401`: Authentication required 543 - - `403`: Forbidden 544 - - `404`: Not found 364 + - `400`: Bad request (invalid parameters) 365 + - `401`: Unauthorized (missing/invalid auth token) 366 + - `403`: Forbidden (insufficient permissions) 367 + - `404`: Not found (record/collection doesn't exist) 545 368 - `500`: Internal server error 546 369 547 - ## Pagination 370 + ## OpenAPI Specification 548 371 549 - List endpoints support cursor-based pagination: 550 - 551 - 1. Make initial request without cursor 552 - 2. Use returned cursor for next page 553 - 3. Continue until no cursor returned 372 + Get the OpenAPI spec for your slice: 554 373 555 - Example: 374 + **Method:** `GET` 375 + **Endpoint:** `/xrpc/network.slices.slice.openapi` 556 376 557 - ```javascript 558 - let cursor = undefined; 559 - do { 560 - const response = await fetch(`/xrpc/collection.getRecords?cursor=${cursor}`); 561 - const data = await response.json(); 562 - // Process records 563 - cursor = data.cursor; 564 - } while (cursor); 565 - ``` 377 + **Query Parameters:** 378 + - `slice` (string, required): The slice URI 566 379 567 - ## Filtering 380 + **Response:** OpenAPI 3.0 specification with all available endpoints 568 381 569 - List endpoints support filtering using the `where` parameter with field-specific 570 - query operators: 382 + ## Example: Record Collector Application 571 383 572 - ### Filter Operators 384 + Here's a complete example using the record collector lexicon: 573 385 574 - - `eq`: Exact match 575 - - `contains`: Partial text match (case-insensitive) 576 - - `in`: Match any value in array 577 - 578 - ### Examples 579 - 580 - **Exact match filtering:** 581 - 582 - ```json Code 386 + ### Lexicon Definition 387 + ```json 583 388 { 584 - "where": { 585 - "artist": { "eq": "Nirvana" }, 586 - "condition": { "eq": "Mint" } 389 + "lexicon": 1, 390 + "id": "com.recordcollector.album", 391 + "defs": { 392 + "main": { 393 + "type": "record", 394 + "record": { 395 + "type": "object", 396 + "required": ["title", "artist", "releaseDate"], 397 + "properties": { 398 + "title": { "type": "string", "description": "Album title" }, 399 + "artist": { "type": "string", "description": "Artist or band name" }, 400 + "releaseDate": { 401 + "type": "string", 402 + "format": "datetime", 403 + "description": "Original release date" 404 + }, 405 + "genre": { 406 + "type": "array", 407 + "items": { "type": "string" }, 408 + "description": "Music genres" 409 + }, 410 + "condition": { 411 + "type": "string", 412 + "description": "Vinyl condition (Mint, Near Mint, etc.)" 413 + }, 414 + "notes": { 415 + "type": "string", 416 + "description": "Collector notes" 417 + } 418 + } 419 + } 420 + } 587 421 } 588 422 } 589 423 ``` 590 424 591 - **Text search filtering:** 592 - 593 - ```json Code 594 - { 595 - "where": { 596 - "title": { "contains": "nevermind" }, 597 - "genre": { "contains": "grunge" } 598 - } 599 - } 425 + ### List Albums by Genre 426 + ```bash 427 + curl -X POST "https://api.slices.network/xrpc/com.recordcollector.album.getRecords" \ 428 + -H "Content-Type: application/json" \ 429 + -d '{ 430 + "slice": "at://did:plc:abc123/network.slices.slice/xyz789", 431 + "where": { "genre": { "contains": "grunge" } }, 432 + "sortBy": [{ "field": "releaseDate", "direction": "desc" }], 433 + "limit": 10 434 + }' 600 435 ``` 601 436 602 - **Array filtering:** 603 - 604 - ```json Code 605 - { 606 - "where": { 607 - "condition": { "in": ["Mint", "Near Mint", "Very Good Plus"] }, 608 - "artist": { "in": ["Nirvana", "Pearl Jam", "Soundgarden"] } 609 - } 610 - } 437 + ### Add New Album to Collection 438 + ```bash 439 + curl -X POST "https://api.slices.network/xrpc/com.recordcollector.album.createRecord" \ 440 + -H "Authorization: Bearer YOUR_TOKEN" \ 441 + -H "Content-Type: application/json" \ 442 + -d '{ 443 + "slice": "at://did:plc:abc123/network.slices.slice/xyz789", 444 + "record": { 445 + "$type": "com.recordcollector.album", 446 + "title": "Superunknown", 447 + "artist": "Soundgarden", 448 + "releaseDate": "1994-03-08T00:00:00.000Z", 449 + "genre": ["grunge", "alternative metal"], 450 + "condition": "Near Mint", 451 + "notes": "Limited edition orange vinyl" 452 + } 453 + }' 611 454 ``` 612 455 613 - **Global search across all fields:** 614 - 615 - ```json Code 616 - { 617 - "where": { 618 - "json": { "contains": "grunge" } 619 - } 620 - } 456 + ### Search Collection by Condition 457 + ```bash 458 + curl -X POST "https://api.slices.network/xrpc/com.recordcollector.album.getRecords" \ 459 + -H "Content-Type: application/json" \ 460 + -d '{ 461 + "slice": "at://did:plc:abc123/network.slices.slice/xyz789", 462 + "where": { 463 + "condition": { "in": ["Mint", "Near Mint"] }, 464 + "releaseDate": { "contains": "199" } 465 + }, 466 + "sortBy": [{ "field": "artist", "direction": "asc" }] 467 + }' 621 468 ``` 622 469 623 - ## Sorting 470 + ### Count Albums in Collection 471 + ```bash 472 + curl -X POST "https://api.slices.network/xrpc/com.recordcollector.album.countRecords" \ 473 + -H "Content-Type: application/json" \ 474 + -d '{ 475 + "slice": "at://did:plc:abc123/network.slices.slice/xyz789", 476 + "where": { "condition": { "eq": "Mint" } } 477 + }' 478 + ``` 624 479 625 - Sort parameter uses an array format with field and direction: 626 - 627 - ```json Code 628 - { 629 - "sortBy": [ 630 - { "field": "releaseDate", "direction": "desc" }, 631 - { "field": "title", "direction": "asc" } 632 - ] 633 - } 480 + ### Update Album Condition 481 + ```bash 482 + curl -X POST "https://api.slices.network/xrpc/com.recordcollector.album.updateRecord" \ 483 + -H "Authorization: Bearer YOUR_TOKEN" \ 484 + -H "Content-Type: application/json" \ 485 + -d '{ 486 + "slice": "at://did:plc:abc123/network.slices.slice/xyz789", 487 + "rkey": "3abc456def", 488 + "record": { 489 + "$type": "com.recordcollector.album", 490 + "title": "Ten", 491 + "artist": "Pearl Jam", 492 + "releaseDate": "1991-08-27T00:00:00.000Z", 493 + "genre": ["grunge", "alternative rock"], 494 + "condition": "Very Good", 495 + "notes": "Updated after closer inspection - slight scuffs on Side B" 496 + } 497 + }' 634 498 ``` 635 499 636 - Examples: 637 - 638 - - `[{ "field": "releaseDate", "direction": "desc" }]` - Newest releases first 639 - - `[{ "field": "artist", "direction": "asc" }]` - Alphabetical by artist 640 - - `[{ "field": "releaseDate", "direction": "desc" }, { "field": "title", "direction": "asc" }]` - 641 - Newest first, then alphabetical by title 642 - 643 500 ## Next Steps 644 501 645 502 - [SDK Usage](./sdk-usage.md) - Using generated TypeScript clients 646 503 - [Getting Started](./getting-started.md) - Build your first application 647 - - [Concepts](./concepts.md) - Understand the architecture 504 + - [Concepts](./concepts.md) - Understand the architecture
+285 -216
docs/concepts.md
··· 1 1 # Core Concepts 2 2 3 - Understanding these core concepts will help you effectively use Slices. 3 + Slices is built on four fundamental concepts that work together to create a 4 + powerful AT Protocol development platform. 5 + 6 + ``` 7 + Slices → Lexicons → Sync → APIs 8 + (Container) → (Schema) → (Data) → (Access) 9 + ``` 10 + 11 + ## **Slices**: Your Data Universe 12 + 13 + ### What is a Slice? 14 + 15 + A slice is your own independent data space within the AT Protocol network. Think 16 + of it as a database with built-in APIs, authentication, and real-time sync, all 17 + isolated from other slices. 4 18 5 - ## Slices 19 + ### Why Slices Matter 20 + 21 + In the AT Protocol ecosystem, data lives on Personal Data Servers (PDS) 22 + scattered across the network. Slices acts as an **AppView** that: 23 + 24 + - Aggregates data from across the network 25 + - Applies your custom schemas 26 + - Provides instant APIs for your data 27 + - Maintains complete data isolation 28 + 29 + ### Creating Your Slice 30 + 31 + When you create a slice for `com.recordcollector`: 32 + 33 + ``` 34 + Slice Name: My Vinyl Collection 35 + Domain: com.recordcollector 36 + URI: at://did:plc:abc123/network.slices.slice/xyz789 37 + ``` 6 38 7 - A slice is an independent appview within the AT Protocol ecosystem. Think of it 8 - as your own data universe with custom schemas and records. 39 + This slice becomes the container for all your vinyl collection data, completely 40 + isolated from other slices. 9 41 10 42 ### Key Properties 11 43 12 - - **URI**: Unique AT Protocol URI (e.g., 13 - `at://did:plc:abc123/network.slices.slice/3xyz`) 14 - - **Name**: Human-readable identifier 15 - - **Domain**: Namespace for lexicons (e.g., `com.example`, `social.grain`) 16 - - **Creation Date**: When the slice was created 44 + - **URI**: Unique AT Protocol identifier 45 + - **Domain**: Your namespace (e.g., `com.recordcollector`) 46 + - **Isolation**: Complete data separation between slices 47 + - **Multi-tenancy**: Multiple users can have data in the same slice 17 48 18 - ### Slice Isolation 49 + ### Example: Record Collector Slice 19 50 20 - Each slice maintains complete data isolation: 51 + ```json 52 + { 53 + "uri": "at://did:plc:abc123/network.slices.slice/xyz789", 54 + "name": "Vinyl Collection Manager", 55 + "domain": "com.recordcollector", 56 + "createdAt": "2024-01-15T10:00:00Z" 57 + } 58 + ``` 21 59 22 - - Records are filtered by slice URI in all queries 23 - - Sync operations respect slice boundaries 24 - - Statistics are calculated per-slice 25 - - Users can have different data in different slices 60 + ## **Lexicons**: Data Schemas 26 61 27 - ## Lexicons 62 + ### What are Lexicons? 28 63 29 - Lexicons are JSON schemas that define record types in AT Protocol. They specify 30 - the structure, validation rules, and metadata for records. 64 + Lexicons are JSON schemas that define the structure of your data in AT Protocol. 65 + They're like database tables with built-in validation. In Slices, these lexicons 66 + automatically generate APIs for you. 31 67 32 - ### Lexicon Structure 68 + ### Defining Your Data 33 69 34 - ```json Code 70 + For a record collector app, you might define an album lexicon: 71 + 72 + ```json 35 73 { 36 74 "lexicon": 1, 37 75 "id": "com.recordcollector.album", 38 76 "defs": { 39 77 "main": { 40 78 "type": "record", 41 - "description": "A vinyl album record", 79 + "description": "A vinyl album in the collection", 42 80 "record": { 43 81 "type": "object", 82 + "required": ["title", "artist", "releaseDate"], 44 83 "properties": { 45 - "title": { "type": "string" }, 46 - "artist": { "type": "string" }, 47 - "releaseDate": { "type": "string", "format": "datetime" }, 48 - "condition": { "type": "string" } 49 - }, 50 - "required": ["title", "artist"] 84 + "title": { 85 + "type": "string", 86 + "description": "Album title" 87 + }, 88 + "artist": { 89 + "type": "string", 90 + "description": "Artist or band name" 91 + }, 92 + "releaseDate": { 93 + "type": "string", 94 + "format": "datetime", 95 + "description": "Original release date" 96 + }, 97 + "genre": { 98 + "type": "array", 99 + "items": { "type": "string" }, 100 + "description": "Musical genres" 101 + }, 102 + "condition": { 103 + "type": "string", 104 + "enum": [ 105 + "Mint", 106 + "Near Mint", 107 + "Very Good Plus", 108 + "Very Good", 109 + "Good", 110 + "Fair", 111 + "Poor" 112 + ], 113 + "description": "Vinyl condition grading" 114 + }, 115 + "notes": { 116 + "type": "string", 117 + "maxLength": 1000, 118 + "description": "Collector's notes" 119 + } 120 + } 51 121 } 52 122 } 53 123 } 54 124 } 55 125 ``` 56 126 57 - ### Supported Types 127 + ### Primary vs External Lexicons 58 128 59 - - **Primitives**: string, number, integer, boolean 60 - - **Complex**: object, array, union, ref 61 - - **Special**: blob (for media), cid-link, at-uri 62 - - **Formats**: datetime, at-identifier, did, handle 129 + **Primary Lexicons**: Match your slice domain: 63 130 64 - ### Lexicon Namespacing 131 + - `com.recordcollector.album` 132 + - `com.recordcollector.review` 133 + - `com.recordcollector.wishlist` 65 134 66 - Lexicons follow reverse domain naming: 135 + **External Lexicons**: From other namespaces: 67 136 68 - - `com.recordcollector.album` - An album in the recordcollector.com namespace 69 - - `com.recordcollector.review` - A vinyl review record 70 - - `network.slices.slice` - Core slice record type 71 - - `app.bsky.actor.profile` - Bluesky profile (external) 137 + - `app.bsky.actor.profile` (Bluesky profiles) 138 + - `sh.tangled.repo` (Tangled Repo) 139 + - `social.grain.photo` (Grain Photos) 140 + - `com.cassettecollector.tape` (Cassette Collector) 72 141 73 - ## Collections 142 + ### Automatic API Generation 74 143 75 - Collections are groups of records with the same lexicon type. They map directly 76 - to XRPC endpoints. 144 + Each lexicon with `type: "record"` automatically creates endpoints. Record types 145 + are the only lexicons that generate CRUD APIs: 77 146 78 - ### Primary Collections 147 + ``` 148 + com.recordcollector.album (type: "record") → 149 + /xrpc/com.recordcollector.album.getRecords 150 + /xrpc/com.recordcollector.album.createRecord 151 + /xrpc/com.recordcollector.album.updateRecord 152 + /xrpc/com.recordcollector.album.deleteRecord 153 + ``` 79 154 80 - Collections that match your slice's domain namespace. For example, if your slice 81 - domain is `com.recordcollector`, then `com.recordcollector.album` would be a 82 - primary collection. 155 + Note: Other lexicon types (like `query`, `procedure`, or `subscription`) serve 156 + different purposes and don't create these standard CRUD endpoints. 83 157 84 - ### External Collections 158 + ## **Sync**: Data Flow 85 159 86 - Collections from other namespaces that you've synced into your slice. For 87 - example: 160 + ### How Data Enters Your Slice 88 161 89 - - Bluesky profiles (`app.bsky.actor.profile`) 90 - - Bluesky posts (`app.bsky.feed.post`) 91 - - Collections from other slices 162 + The sync engine manages how data flows into your slice from the AT Protocol 163 + network. 92 164 93 - ### Collection Operations 165 + ### Three Sync Strategies 94 166 95 - Both primary and external collections support the same operations: 167 + #### 1. Bulk Sync: Historical Import 96 168 97 - - `*.getRecords` - Get records with pagination, filtering, and search 98 - - `*.getRecord` - Get single record by URI 99 - - `*.createRecord` - Create new record 100 - - `*.updateRecord` - Update existing record 101 - - `*.deleteRecord` - Remove record 102 - - `*.countRecords` - Count records with filtering 169 + Perfect for initial data loading or periodic updates. When you're first starting 170 + out, you might only be syncing your own records from your PDS. But as more 171 + people adopt your app and write records to their own PDSs, you can sync from 172 + their repositories too, growing your network. 103 173 104 - The key difference is conceptual: primary collections are "native" to your 105 - slice's domain, while external collections are imported from other namespaces. 174 + Specify: 106 175 107 - ## Records 176 + - Collections to sync (e.g., `com.recordcollector.album`) 177 + - External collections (e.g., `app.bsky.actor.profile` for user profiles) 178 + - Specific repositories (DIDs) to import from 108 179 109 - Records are individual data items stored in collections. 180 + #### 2. User Sync: On-Demand 110 181 111 - ### Record Properties 182 + Automatically syncs when users log in. This is primarily for discovering and 183 + syncing external collections. When a new user authenticates for the first time, 184 + they become an actor in your slice, allowing you to discover what external 185 + collections they have (like Bluesky profiles or posts) and sync that data from 186 + their PDS. 112 187 113 - - **URI**: Unique AT Protocol URI 114 - - **CID**: Content identifier (hash) 115 - - **DID**: Owner's decentralized identifier 116 - - **Collection**: Lexicon type 117 - - **Value**: Actual record data 118 - - **IndexedAt**: When record was indexed 188 + #### 3. Jetstream: Real-Time Updates 119 189 120 - ### Record Keys (rkeys) 190 + Connects to the AT Protocol firehose for live updates. This tracks create, 191 + update, and delete events as they happen across the network. 121 192 122 - Records use keys for identification: 193 + ``` 194 + Firehose Event → Filter by Collection → Validate → Store in Slice 195 + ``` 123 196 124 - - **Self**: Special key for singleton records (e.g., profiles) 125 - - **TID**: Timestamp-based identifiers 126 - - **Custom**: User-defined keys 197 + ### Syncing External Data 127 198 128 - ### Record Lifecycle 199 + Import Bluesky profiles to show who owns each album and their Bluesky avatar. 200 + You can sync external collections like `app.bsky.actor.profile` to enrich your 201 + slice with user information from the broader AT Protocol network. 129 202 130 - 1. **Creation**: Via API or sync 131 - 2. **Indexing**: Stored in PostgreSQL 132 - 3. **Updates**: New versions with new CIDs 133 - 4. **Deletion**: Soft or hard delete 203 + ### Performance Features 134 204 135 - ## Sync Engine 205 + - **CID Deduplication**: Skips unchanged records 206 + - **Bulk Operations**: Processes thousands of records efficiently 207 + - **Actor Caching**: Reduces database lookups 208 + - **Auto-Recovery**: Handles network interruptions 136 209 137 - The sync engine imports AT Protocol data into your slice using multiple 138 - strategies for optimal performance and reliability. 210 + ### Example: Syncing a Vinyl Community 139 211 140 - ### Sync Types 212 + To sync all albums from a vinyl collecting community, you would: 141 213 142 - **Bulk Sync**: One-time import of historical data 214 + - List primary collections (`com.recordcollector.album`, 215 + `com.recordcollector.review`) 216 + - Include external collections (`app.bsky.actor.profile`) to show collector 217 + information 143 218 144 - - Specify collections to sync 145 - - Filter by repositories (DIDs) 146 - - Set limits per repository 147 - - Uses optimized bulk database operations 219 + ## **Code Generation & XRPC Endpoints**: APIs & SDKs 148 220 149 - **User Sync**: Sync data for authenticated user 221 + ### Dynamic API Creation 150 222 151 - - Automatic on login 152 - - Timeout protection (30 seconds default) 153 - - External collection discovery 154 - - Synchronous operation for immediate feedback 223 + Every record-type lexicon automatically generates REST-like XRPC endpoints. 155 224 156 - **Jetstream Sync**: Real-time updates via WebSocket 225 + ### Generated Endpoints 157 226 158 - - Subscribe to AT Protocol firehose 159 - - Filter relevant events by slice collections 160 - - Automatic record updates and deletions 161 - - Built-in reconnection with exponential backoff 227 + For `com.recordcollector.album`: 162 228 163 - ### Sync Process 229 + #### List Albums 164 230 165 - 1. **Discovery**: Find available records via AT Protocol relay 166 - 2. **Filtering**: Apply slice and collection filters 167 - 3. **Validation**: Check lexicon compliance against slice schemas 168 - 4. **Storage**: Index in database using bulk operations 169 - 5. **Deduplication**: Skip existing records (by CID comparison) 231 + ```http 232 + POST /xrpc/com.recordcollector.album.getRecords 233 + { 234 + "slice": "at://your-slice-uri", 235 + "where": { "genre": { "contains": "jazz" } }, 236 + "sortBy": [{ "field": "releaseDate", "direction": "desc" }], 237 + "limit": 20 238 + } 239 + ``` 170 240 171 - ### Performance Optimizations 241 + #### Add Album 172 242 173 - **CID-Based Deduplication** 243 + ```http 244 + POST /xrpc/com.recordcollector.album.createRecord 245 + Authorization: Bearer YOUR_TOKEN 246 + { 247 + "slice": "at://your-slice-uri", 248 + "record": { 249 + "title": "Kind of Blue", 250 + "artist": "Miles Davis", 251 + "releaseDate": "1959-08-17T00:00:00Z", 252 + "genre": ["jazz", "modal jazz"], 253 + "condition": "Very Good Plus" 254 + } 255 + } 256 + ``` 174 257 175 - - Compare Content Identifiers (CIDs) before processing 176 - - Skip records that haven't changed since last sync 177 - - Reduces unnecessary database operations and validation overhead 258 + ### TypeScript SDK Generation 178 259 179 - **Actor Caching** 260 + Slices generates a fully-typed TypeScript client: 180 261 181 - - Pre-load actor lookup cache to avoid database hits during Jetstream processing 182 - - Cache (DID, slice_uri) mappings for external collection filtering 183 - - Periodic cache refresh every 5 minutes 262 + ```typescript 263 + // Generated SDK with full type safety 264 + import { AtprotoClient } from "./generated_client.ts"; 184 265 185 - ### Jetstream Reliability 266 + const client = new AtprotoClient({ 267 + baseUrl: "https://api.slices.network", 268 + sliceUri: "at://your-slice-uri", 269 + auth: oauthClient, 270 + }); 186 271 187 - **Automatic Recovery** 188 - 189 - - Infinite retry loop with exponential backoff (5 seconds → 5 minutes max) 190 - - Fresh consumer instance creation on each retry 191 - - Database connectivity monitoring and recovery 192 - - Connection status tracking via atomic flags 193 - 194 - **Error Handling** 195 - 196 - - Graceful degradation when database connections fail 197 - - Validation fallback with fresh lexicon loading from database 198 - - Separate error handling for primary vs external collections 199 - 200 - **Configuration Reloading** 201 - 202 - - Automatic slice configuration refresh every 5 minutes 203 - - Dynamic collection filtering based on slice lexicons 204 - - Actor cache updates to reflect new slice membership 205 - 206 - ## XRPC Handlers 207 - 208 - XRPC (Cross-Protocol Remote Procedure Call) handlers provide the API layer. 209 - 210 - ### Dynamic Handlers 211 - 212 - Automatically generated from lexicons: 213 - 214 - - No manual endpoint creation 215 - - Type-safe request/response 216 - - Automatic validation 217 - - OAuth integration 272 + // Fully typed operations 273 + const albums = await client.com.recordcollector.album.getRecords({ 274 + where: { 275 + condition: { in: ["Mint", "Near Mint"] }, 276 + genre: { contains: "jazz" }, 277 + }, 278 + sortBy: [{ field: "artist", direction: "asc" }], 279 + limit: 50, 280 + }); 218 281 219 - ### Core Handlers 220 - 221 - Built-in endpoints for slice management: 282 + // Type-safe record creation 283 + const newAlbum = await client.com.recordcollector.album.createRecord({ 284 + title: "Blue Train", 285 + artist: "John Coltrane", 286 + releaseDate: "1958-01-01T00:00:00Z", 287 + genre: ["jazz", "hard bop"], 288 + condition: "Near Mint", 289 + notes: "Original Blue Note pressing", 290 + }); 291 + ``` 222 292 223 - - `network.slices.slice.stats` - Slice statistics 224 - - `network.slices.slice.records` - Browse records 225 - - `network.slices.slice.codegen` - Generate SDKs 226 - - `network.slices.slice.sync` - Trigger sync 227 - 228 - ### Handler Authentication 229 - 230 - - **Read Operations**: Optional auth (public by default) 231 - - **Write Operations**: Require OAuth tokens 232 - - **Admin Operations**: Require slice ownership 293 + ### OAuth Integration 233 294 234 - ## Generated SDKs 295 + Built-in OAuth 2.0 with PKCE: 235 296 236 - Type-safe client libraries generated from lexicons. 297 + - Read operations: Public by default 298 + - Write operations: Require authentication 299 + - Automatic token refresh 300 + - Secure session management 237 301 238 302 ### SDK Features 239 303 240 - - **Type Safety**: Full TypeScript types 241 - - **Nested Structure**: Matches lexicon namespacing 242 - - **OAuth Integration**: Automatic token handling 243 - - **Error Handling**: Retry logic and graceful failures 304 + - **Type Safety**: Full TypeScript types from lexicons 305 + - **Nested APIs**: `client.com.recordcollector.album.*` 306 + - **Error Handling**: Automatic retries and graceful failures 307 + - **Blob Support**: Handle images and media files 244 308 245 - ### SDK Generation Process 309 + ## How It All Works Together 246 310 247 - 1. Parse slice lexicons 248 - 2. Generate TypeScript interfaces 249 - 3. Create client classes 250 - 4. Add utility functions 251 - 5. Format and validate 311 + ``` 312 + 1. Create Slice → Define namespace (com.recordcollector) 313 + 314 + 2. Add Lexicons → Define data structure (album, review, wishlist) 315 + 316 + 3. Sync Data → Import existing vinyl collections 317 + → Subscribe to real-time updates 318 + 319 + 4. Use APIs → Generated endpoints for all operations 320 + → Type-safe SDK for your app 321 + ``` 252 322 253 - ### Using Generated SDKs 323 + ## Practical Example: Building a Vinyl Collector App 254 324 255 - ```typescript Code 256 - // Initialize client 257 - const client = new AtProtoClient(apiUrl, sliceUri, oauthClient); 325 + ### Step 1: Create Your Slice 258 326 259 - // Use nested structure matching lexicons 260 - await client.com.recordcollector.album.getRecords(); 261 - await client.app.bsky.actor.profile.getRecord({ uri }); 262 - ``` 327 + Create a slice using the Slices CLI or web interface: 263 328 264 - ## Authentication 329 + - Name: "Vintage Vinyl Collectors" 330 + - Domain: "com.recordcollector" 265 331 266 - OAuth 2.0 with PKCE for secure authentication. 332 + ### Step 2: Define Lexicons 267 333 268 - ### OAuth Flow 334 + Upload your lexicon definitions through the web UI or CLI: 269 335 270 - 1. **Authorization**: Redirect to AT Protocol provider 271 - 2. **Callback**: Exchange code for tokens 272 - 3. **Token Storage**: Secure client-side storage 273 - 4. **Refresh**: Automatic token renewal 336 + - Album schema (`com.recordcollector.album`) 337 + - Review schema (`com.recordcollector.review`) 338 + - Wishlist schema (`com.recordcollector.wishlist`) 274 339 275 - ### Session Management 340 + ### Step 3: Sync Existing Data 276 341 277 - - Encrypted cookies for web sessions 278 - - Token refresh before expiration 279 - - Graceful degradation for read-only access 342 + Start a sync job to import your existing collection: 280 343 281 - ## Blob Handling 344 + - Use the Sync tab in the web UI 345 + - Select collections to sync 346 + - Specify repositories (or leave empty for all) 282 347 283 - Media files use blob references with CDN URLs. 348 + ### Step 4: Build Your App 284 349 285 - ### Blob Structure 350 + ```javascript 351 + // Use the generated SDK 352 + const client = new AtprotoClient(slice.uri); 286 353 287 - ```json Code 288 - { 289 - "$type": "blob", 290 - "ref": { "$link": "bafkreig5bcb..." }, 291 - "mimeType": "image/jpeg", 292 - "size": 127198 293 - } 294 - ``` 295 - 296 - ### CDN URL Generation 297 - 298 - Convert blob references to CDN URLs using Bluesky's CDN: 354 + // List all jazz albums in mint condition 355 + const jazzMint = await client.com.recordcollector.album.getRecords({ 356 + where: { 357 + genre: { contains: "jazz" }, 358 + condition: { eq: "Mint" }, 359 + }, 360 + }); 299 361 300 - ```typescript Code 301 - recordBlobToCdnUrl(record, blobRef, "avatar"); 302 - // -> https://cdn.bsky.app/img/avatar/plain/did:plc:abc/bafkrei...@jpeg 362 + // Add a new album to the collection 363 + await client.com.recordcollector.album.createRecord({ 364 + title: "A Love Supreme", 365 + artist: "John Coltrane", 366 + releaseDate: "1965-02-01T00:00:00Z", 367 + genre: ["jazz", "spiritual jazz"], 368 + condition: "Near Mint", 369 + }); 303 370 ``` 304 371 305 - ### Bluesky CDN Presets 372 + ## Summary 306 373 307 - - `avatar` - Profile pictures 308 - - `banner` - Cover images 309 - - `feed_thumbnail` - Small previews 310 - - `feed_fullsize` - Full resolution 374 + | Concept | Purpose | Record Collector Example | 375 + | ------------ | ------------------------------- | ----------------------------------------------------------- | 376 + | **Slices** | Isolated data container | `com.recordcollector` namespace with all your vinyl data | 377 + | **Lexicons** | Schema definitions | `album`, `review`, `wishlist` record types | 378 + | **Sync** | Data import & real-time updates | Import collections from network, live updates via Jetstream | 379 + | **Code Gen** | Auto-generated APIs & SDKs | TypeScript client with `getRecords`, `createRecord`, etc. | 311 380 312 381 ## Next Steps 313 382 314 - - [API Reference](./api-reference.md) - Detailed endpoint documentation 315 - - [SDK Usage](./sdk-usage.md) - Advanced client patterns 316 - - [Getting Started](./getting-started.md) - Build your first slice 383 + - [Getting Started](./getting-started.md): Create your first slice 384 + - [API Reference](./api-reference.md): Detailed endpoint documentation 385 + - [SDK Usage](./sdk-usage.md): Advanced SDK patterns
-23
docs/deployment.md
··· 1 - # Deployment Guide 2 - 3 - Documentation for deploying Slices to production is coming soon. 4 - 5 - ## Basic Requirements 6 - 7 - - [AIP server](https://github.com/graze-social/aip) running and accessible 8 - - Registered OAuth client with AIP 9 - - PostgreSQL database 10 - 11 - ## Environment Setup 12 - 13 - See the [environment variables](../README.md#environment-variables) section in the README for required configuration. 14 - 15 - ## Docker 16 - 17 - Docker images can be built using the provided Dockerfile in each directory. 18 - 19 - ## More Information 20 - 21 - For now, refer to: 22 - - [Getting Started](./getting-started.md) for local setup 23 - - [README](../README.md) for environment configuration
+379 -69
docs/getting-started.md
··· 1 - # Getting Started with Slices 1 + # Getting Started 2 2 3 - This guide will help you set up Slices and create your first slice. 3 + Build your first AT Protocol app in minutes with the Slices CLI. 4 4 5 - ## Creating Your First Slice 5 + ## Quick Start 6 6 7 - ### 1. Log In 7 + ### Install the CLI 8 8 9 - Click "Login" and authenticate with your AT Protocol account. 9 + You'll need Deno installed first. Get it at [deno.com](https://deno.com/). 10 10 11 - ### 2. Create a Slice 11 + ```bash 12 + # Install from JSR 13 + deno install -g jsr:@slices/cli --name slices 14 + ``` 12 15 13 - Click "Create Slice" and provide: 16 + ### Create Your Project 14 17 15 - - **Name**: A friendly name for your slice 16 - - **Domain**: Your namespace (e.g., `com.recordcollector`) 18 + ```bash 19 + # Create a new project with automatic setup 20 + slices init my-vinyl-app 17 21 18 - ### 3. Define a Lexicon 22 + # Or let us generate a name/domain for you 23 + slices init 24 + ``` 19 25 20 - Navigate to your slice and go to the Lexicon tab. Create a lexicon for your 21 - first record type: 26 + The `slices init` command does everything for you: 22 27 23 - ```json Code 28 + - Creates a full-stack Deno app with OAuth authentication 29 + - Automatically creates a slice on the network 30 + - Sets up OAuth credentials 31 + - Pulls standard lexicons (including Bluesky profiles) 32 + - Generates a TypeScript SDK 33 + - Initializes a git repository 34 + 35 + ### Start Developing 36 + 37 + ```bash 38 + cd my-vinyl-app 39 + deno task dev 40 + ``` 41 + 42 + Visit http://localhost:8080 and you're live! 43 + 44 + ## What You Get 45 + 46 + The `slices init` command creates a production-ready app with: 47 + 48 + > More templates/examples coming soon (i.e. React, Expo, Astro, etc) 49 + 50 + ### Full-Stack Deno Application 51 + 52 + - **Server-side rendering** with Preact and JSX 53 + - **OAuth authentication** with PKCE flow and automatic token refresh 54 + - **HTMX integration** for dynamic UI without complex JavaScript 55 + - **Tailwind CSS** for beautiful, responsive styling 56 + - **SQLite sessions** for secure session management 57 + - **Feature-based architecture** for scalable code organization 58 + 59 + ### AT Protocol Integration 60 + 61 + - **Configured slice** with your own namespace 62 + - **Generated TypeScript SDK** from your lexicons 63 + - **Automatic sync** capabilities 64 + - **Real-time updates** via Jetstream 65 + 66 + ### Development Experience 67 + 68 + - **Hot reload** in development 69 + - **Type safety** throughout 70 + - **Environment variables** pre-configured 71 + - **Git repository** initialized 72 + 73 + ## Project Structure 74 + 75 + Here's what the generated project structure looks like: 76 + 77 + ``` 78 + my-vinyl-app/ 79 + ├── slices.json # Slice configuration 80 + ├── .env # Your credentials (auto-generated) 81 + ├── deno.json # Deno configuration 82 + ├── lexicons/ # AT Protocol schemas 83 + │ ├── com/ 84 + │ │ └── recordcollector/ 85 + │ │ └── album.json 86 + │ └── app/ 87 + │ └── bsky/ 88 + │ └── actor/ 89 + │ └── profile.json 90 + └── src/ 91 + ├── main.ts # Server entry point 92 + ├── config.ts # App configuration 93 + ├── generated_client.ts # Your TypeScript SDK 94 + ├── routes/ # HTTP routes 95 + ├── features/ # Feature modules 96 + │ ├── auth/ # OAuth implementation 97 + │ └── dashboard/ # Main app UI 98 + ├── shared/ # Reusable components 99 + └── utils/ # Helper functions 100 + ``` 101 + 102 + ## CLI Commands 103 + 104 + The Slices CLI is your command center: 105 + 106 + ### Project Management 107 + 108 + ```bash 109 + # Create a new project 110 + slices init my-app 111 + 112 + # Check your authentication status 113 + slices status 114 + 115 + # Authenticate with Slices network 116 + slices login 117 + ``` 118 + 119 + ### Lexicon Management 120 + 121 + ```bash 122 + # Pull lexicons from your slice 123 + slices lexicon pull 124 + 125 + # Push local lexicons to your slice 126 + slices lexicon push 127 + 128 + # List lexicons in your slice 129 + slices lexicon list 130 + ``` 131 + 132 + ### Code Generation 133 + 134 + ```bash 135 + # Generate TypeScript SDK from lexicons 136 + slices codegen 137 + 138 + # The SDK is created at src/generated_client.ts 139 + ``` 140 + 141 + ### Monitoring 142 + 143 + ```bash 144 + # View real-time Jetstream logs 145 + slices logs 146 + 147 + # See sync activity and data flow 148 + slices logs --verbose 149 + ``` 150 + 151 + ## Working with Lexicons 152 + 153 + Lexicons define your data structure. The init command includes Bluesky profile 154 + lexicons to get you started, but you'll want to add your own custom lexicons for 155 + your app. 156 + 157 + ### Modify an Existing Lexicon 158 + 159 + Edit `lexicons/com/recordcollector/album.json`: 160 + 161 + ```json 24 162 { 25 163 "lexicon": 1, 26 164 "id": "com.recordcollector.album", 27 165 "defs": { 28 166 "main": { 29 167 "type": "record", 30 - "description": "A vinyl album record", 168 + "description": "A vinyl album in my collection", 31 169 "record": { 32 170 "type": "object", 171 + "required": ["title", "artist", "releaseDate"], 33 172 "properties": { 34 - "title": { 35 - "type": "string", 36 - "description": "Album title" 37 - }, 38 - "artist": { 39 - "type": "string", 40 - "description": "Artist or band name" 41 - }, 173 + "title": { "type": "string" }, 174 + "artist": { "type": "string" }, 42 175 "releaseDate": { 43 176 "type": "string", 44 - "format": "datetime", 45 - "description": "Original release date" 177 + "format": "datetime" 46 178 }, 47 179 "genre": { 48 180 "type": "array", 49 - "items": { 50 - "type": "string" 51 - }, 52 - "description": "Music genres" 181 + "items": { "type": "string" } 53 182 }, 54 183 "condition": { 55 184 "type": "string", 56 - "description": "Vinyl condition (Mint, Near Mint, Very Good, etc.)" 185 + "enum": [ 186 + "Mint", 187 + "Near Mint", 188 + "Very Good Plus", 189 + "Very Good", 190 + "Good", 191 + "Fair", 192 + "Poor" 193 + ] 57 194 }, 58 - "notes": { 195 + "notes": { "type": "string" } 196 + } 197 + } 198 + } 199 + } 200 + } 201 + ``` 202 + 203 + ### Create a New Lexicon 204 + 205 + Create `lexicons/com/recordcollector/review.json`: 206 + 207 + ```json 208 + { 209 + "lexicon": 1, 210 + "id": "com.recordcollector.review", 211 + "defs": { 212 + "main": { 213 + "type": "record", 214 + "description": "Album review", 215 + "record": { 216 + "type": "object", 217 + "required": ["albumUri", "rating", "content"], 218 + "properties": { 219 + "albumUri": { 59 220 "type": "string", 60 - "description": "Collector notes" 221 + "format": "at-uri" 222 + }, 223 + "rating": { 224 + "type": "integer", 225 + "minimum": 1, 226 + "maximum": 5 227 + }, 228 + "content": { "type": "string" }, 229 + "createdAt": { 230 + "type": "string", 231 + "format": "datetime" 61 232 } 62 - }, 63 - "required": ["title", "artist", "releaseDate"] 233 + } 64 234 } 65 235 } 66 236 } 67 237 } 68 238 ``` 69 239 70 - ### 4. Generate TypeScript Client 240 + ### Update Your Slice 71 241 72 - Navigate to the Code Generation tab and click "Generate TypeScript Client". This 73 - creates a type-safe client library for your slice. 242 + After modifying lexicons: 74 243 75 - ### 5. Use the Generated Client 244 + ```bash 245 + # Push changes to your slice 246 + slices lexicon push 76 247 77 - In your application: 248 + # Regenerate the TypeScript SDK 249 + slices codegen 250 + ``` 78 251 79 - ```typescript Code 80 - import { AtProtoClient } from "./generated-client.ts"; 252 + Your SDK at `src/generated_client.ts` now includes the new types and methods! 81 253 82 - const client = new AtProtoClient( 83 - "http://localhost:3000", 84 - "at://did:plc:your-did/network.slices.slice/your-slice-id", 85 - ); 254 + **Important:** You must push your lexicons to the slice before you can create 255 + records. The slice needs to know about your schema in order to validate and 256 + store records. 86 257 87 - // Get albums 88 - const albums = await client.com.recordcollector.album.getRecords(); 258 + ## Using the Generated SDK 259 + 260 + The generated SDK provides a type-safe client for your slice: 89 261 90 - // Add a new album to your collection 262 + ```typescript 263 + import { AtprotoClient } from "./generated_client.ts"; 264 + 265 + // Initialize the client 266 + const client = new AtprotoClient({ 267 + baseUrl: "https://api.slices.network", 268 + sliceUri: Deno.env.get("SLICE_URI")!, 269 + }); 270 + 271 + // List albums with filtering and sorting 272 + const albums = await client.com.recordcollector.album.getRecords({ 273 + where: { 274 + genre: { contains: "jazz" }, 275 + }, 276 + sortBy: [{ field: "releaseDate", direction: "desc" }], 277 + limit: 20, 278 + }); 279 + 280 + // Add a new album 91 281 const newAlbum = await client.com.recordcollector.album.createRecord({ 92 - title: "Nevermind", 93 - artist: "Nirvana", 94 - releaseDate: "1991-09-24", 95 - genre: ["grunge", "alternative rock"], 282 + title: "Kind of Blue", 283 + artist: "Miles Davis", 284 + releaseDate: "1959-08-17T00:00:00Z", 285 + genre: ["jazz", "modal jazz"], 96 286 condition: "Near Mint", 97 - notes: "Original pressing, includes poster", 287 + notes: "Original Columbia pressing", 98 288 }); 99 289 100 290 // Get a specific album 101 291 const album = await client.com.recordcollector.album.getRecord({ 102 292 uri: newAlbum.uri, 293 + }); 294 + 295 + // Update an album 296 + await client.com.recordcollector.album.updateRecord({ 297 + uri: album.uri, 298 + record: { 299 + ...album.value, 300 + notes: "Verified as first pressing!", 301 + }, 302 + }); 303 + 304 + // Delete an album 305 + await client.com.recordcollector.album.deleteRecord({ 306 + uri: album.uri, 103 307 }); 104 308 ``` 105 309 106 - ## Syncing External Data 310 + ### External Collections 107 311 108 - To import data from other AT Protocol repositories: 312 + Since the init command included Bluesky profile lexicons, your SDK has methods 313 + for querying them: 109 314 110 - ### 1. Navigate to Sync 315 + ```typescript 316 + // Query users by display name (from included Bluesky lexicons) 317 + const profiles = await client.app.bsky.actor.profile.getRecords({ 318 + where: { 319 + displayName: { contains: "vinyl collector" }, 320 + }, 321 + }); 322 + ``` 111 323 112 - Go to your slice and click the Sync tab. 324 + Any record-type lexicon you add to your slice will generate corresponding SDK 325 + methods when you run `slices codegen`. 113 326 114 - ### 2. Configure Sync 327 + ## Syncing Data 115 328 116 - Choose collections to sync: 329 + Once your app is running, you can sync data from the AT Protocol network. 117 330 118 - - **Primary Collections**: Your slice's lexicons 119 - - **External Collections**: Bluesky or other AT Protocol collections 331 + ### User Authentication Sync 120 332 121 - ### 3. Start Sync 333 + When users log in via OAuth, you can sync their data using the 334 + `syncUserCollections` method. This discovers and imports their external 335 + collections (like Bluesky profiles and posts). 122 336 123 - Specify repositories (DIDs) to sync from, or leave empty to sync all available 124 - data. 337 + ```typescript 338 + // After user logs in 339 + await client.network.slices.slice.syncUserCollections(); 340 + ``` 125 341 126 - ### 4. Monitor Progress 342 + ### Manual Bulk Sync 127 343 128 - The sync will run in the background. Check the status in the UI or via API. 344 + Use the web interface at https://slices.network to start a bulk sync job. 345 + Navigate to your slice's Sync tab to configure which collections and 346 + repositories to sync. 347 + 348 + **Note:** If you created new lexicons, you'll be the only one with records 349 + initially. As more users adopt your app and write records to their own PDSs, you 350 + can sync from their repositories to grow your network. 351 + 352 + ### Real-time Updates 353 + 354 + Jetstream automatically tracks creates, updates, and deletes across the network: 355 + 356 + ```bash 357 + # Monitor real-time sync 358 + slices logs --slice $SLICE_URI 359 + ``` 360 + 361 + ## Deployment 362 + 363 + Your app is ready for production deployment. 364 + 365 + ### Deno Deploy 366 + 367 + Create a free account at [deno.com/deploy](https://deno.com/deploy). Push your code to GitHub, then connect your repository through the Deno Deploy dashboard to deploy your app. 368 + 369 + For production use with Deno Deploy, switch from SQLite to Deno KV for OAuth and session storage: 370 + 371 + ```typescript 372 + import { DenoKVOAuthStorage } from "@slices/oauth"; 373 + import { DenoKVAdapter } from "@slices/session"; 374 + 375 + // Configure OAuth with Deno KV storage 376 + const oauthClient = new OAuthClient({ 377 + clientId: Deno.env.get("OAUTH_CLIENT_ID")!, 378 + clientSecret: Deno.env.get("OAUTH_CLIENT_SECRET")!, 379 + authBaseUrl: Deno.env.get("OAUTH_AIP_BASE_URL")!, 380 + redirectUri: Deno.env.get("OAUTH_REDIRECT_URI")!, 381 + storage: new DenoKVOAuthStorage(), // Uses Deno KV 382 + }); 383 + 384 + // Configure sessions with Deno KV adapter 385 + const sessionStore = new SessionStore({ 386 + adapter: new DenoKVAdapter(), // Uses Deno KV 387 + cookieOptions: { 388 + secure: true, 389 + httpOnly: true, 390 + }, 391 + }); 392 + ``` 393 + 394 + Deno KV provides serverless-compatible storage that scales automatically with your deployment. 395 + 396 + ## Manual Setup (Advanced) 397 + 398 + If you prefer to set things up manually or need custom configuration: 399 + 400 + ### 1. Create a Slice via Web UI 401 + 402 + Visit https://slices.network and: 403 + 404 + 1. Log in with your AT Protocol account 405 + 2. Click "Create Slice" 406 + 3. Choose your namespace (e.g., `com.recordcollector`) 407 + 408 + ### 2. Create OAuth Credentials 409 + 410 + In your slice dashboard: 411 + 412 + 1. Go to Settings → OAuth Clients 413 + 2. Create a new client 414 + 3. Set redirect URI: `http://localhost:8080/oauth/callback` 415 + 4. Copy the Client ID and Secret 416 + 417 + ### 3. Set Up Your Project 418 + 419 + Use any framework you prefer. You can use the generated TypeScript SDK (works 420 + with any JavaScript/TypeScript environment) or call the XRPC endpoints directly 421 + from any language: 422 + 423 + ```bash 424 + # Configure environment 425 + cp .env.example .env 426 + # Edit .env with your credentials 427 + 428 + # Start your project 429 + # (commands depend on your framework choice) 430 + ``` 129 431 130 432 ## Next Steps 131 433 132 - - [Core Concepts](./concepts.md) - Understand slices, lexicons, and collections 133 - - [API Reference](./api-reference.md) - Explore available endpoints 134 - - [SDK Usage](./sdk-usage.md) - Advanced SDK patterns 135 - - [Examples](./examples/) - Sample applications 434 + - [Core Concepts](./concepts.md): Understand slices, lexicons, sync, and code 435 + generation 436 + - [API Reference](./api-reference.md): Detailed endpoint documentation 437 + - [SDK Usage](./sdk-usage.md): Advanced SDK patterns and examples 438 + - [Examples](./examples/): Sample applications and use cases 439 + 440 + ## Need Help? 441 + 442 + - Join our [Discord community](https://discord.gg/slices) 443 + - Check out [example apps](https://github.com/slices/examples) 444 + - Read the [AT Protocol docs](https://atproto.com/) 445 + - Report issues on [Tangled](https://tangled.sh/slices.network/slices/issues)
+98 -64
docs/intro.md
··· 1 - # Introduction to Slices 1 + # Introduction 2 2 3 - Slices is an AT Protocol appview platform that enables developers to create 4 - custom data slices (appviews) with their own lexicons, sync AT Protocol data, 5 - and generate type-safe SDKs. 3 + Slices is an open source platform for building structured data applications on 4 + the AT Protocol network. 6 5 7 - ## What is a Slice? 6 + ## What is Slices? 8 7 9 - A slice is a custom appview within the AT Protocol ecosystem. Each slice: 8 + Slices lets you define custom data schemas and build applications that store, 9 + query, and sync structured records across the decentralized AT Protocol network. 10 + Think of it as a schema-first backend that automatically handles data 11 + validation, indexing, and cross-network synchronization. 10 12 11 - - Has its own domain namespace (e.g., `social.grain`, `xyz.statusphere`) 12 - - Defines custom lexicons (schemas) for record types 13 - - Can sync both internal and external AT Protocol collections 14 - - Provides automatically generated type-safe SDKs 13 + ## How Slices Works on AT Protocol 15 14 16 - ## Why Slices? 15 + ```mermaid 16 + flowchart LR 17 + Users[Users<br/>Create/Update Records] --> PDS[PDS Nodes<br/>Store user data] 18 + PDS --> Firehose[Firehose<br/>Stream of all events] 19 + Firehose --> SlicesNetwork[Slices Network - AppView<br/>• Monitors all AT Protocol data<br/>• Routes to relevant slices] 20 + SlicesNetwork --> SliceA[Slice A<br/>• Blog lexicons<br/>• Post records<br/>• Comment queries] 21 + SlicesNetwork --> SliceB[Slice B<br/>• Music lexicons<br/>• Album records<br/>• Playlist queries] 22 + SliceA --> ClientA[Application<br/>Client<br/>• Read/write data] 23 + SliceB --> ClientB[Application<br/>Client<br/>• Read/write data] 24 + ``` 17 25 18 - Building AT Protocol applications typically requires: 26 + **Flow:** 19 27 20 - - Setting up infrastructure to index and query AT Protocol data 21 - - Managing OAuth authentication flows 22 - - Implementing XRPC handlers for CRUD operations 23 - - Creating client libraries for frontend integration 28 + 1. Users create records on their Personal Data Server (PDS) 29 + 2. The Firehose streams all network events in real-time 30 + 3. The Slices Network monitors the firehose and routes data to relevant slices 31 + 4. Each slice indexes only records matching its specific lexicons 32 + 5. Application clients connect to specific slices to read/write data 24 33 25 - Slices provides all of this infrastructure out of the box, letting you focus on 26 - your application logic. 34 + ## Quick Start 27 35 28 - ## Key Features 36 + Get started in under a minute: 29 37 30 - ### Dynamic API Generation 38 + ```bash 39 + # Install the CLI globally 40 + deno install -g jsr:@slices/cli --name slices 31 41 32 - Define a lexicon, and Slices automatically creates REST endpoints for: 42 + # Initialize a new slice project 43 + slices init my-app 33 44 34 - - Listing records with filtering and sorting 35 - - Getting individual records by URI 36 - - Creating new records with OAuth authentication 37 - - Updating existing records 38 - - Deleting records 39 - - Searching within collections 45 + # Start developing 46 + cd my-app 47 + deno task dev 48 + ``` 40 49 41 - ### Data Synchronization 50 + The `slices init` command creates a full-stack Deno app with OAuth authentication, automatically creates your slice on the network, and generates a type-safe TypeScript SDK. 42 51 43 - Sync AT Protocol data into your slice: 52 + ## Simple Example 44 53 45 - - Import external collections (e.g., Bluesky profiles, posts) 46 - - Filter data by repository or collection 47 - - Maintain slice-specific data isolation 48 - - Real-time sync via Jetstream (coming soon) 54 + Define a schema for vinyl albums: 49 55 50 - ### Type-Safe SDK Generation 56 + ```json lexicons/com/recordcollector/album.json 57 + { 58 + "lexicon": 1, 59 + "id": "com.recordcollector.album", 60 + "defs": { 61 + "main": { 62 + "type": "record", 63 + "record": { 64 + "type": "object", 65 + "required": ["title", "artist", "releaseDate"], 66 + "properties": { 67 + "title": { "type": "string" }, 68 + "artist": { "type": "string" }, 69 + "releaseDate": { "type": "string", "format": "datetime" }, 70 + "genre": { "type": "array", "items": { "type": "string" } }, 71 + "condition": { "type": "string" } 72 + } 73 + } 74 + } 75 + } 76 + } 77 + ``` 51 78 52 - Automatically generate TypeScript clients with: 79 + Push your lexicon and regenerate the SDK: 53 80 54 - - Full type safety for all record types 55 - - OAuth authentication integration 56 - - Nested API structure matching your lexicons 57 - - Blob/CDN URL utilities for media handling 81 + ```bash 82 + # Push your lexicon to the slice 83 + slices lexicon push 84 + 85 + # Regenerate TypeScript SDK 86 + slices codegen 87 + ``` 58 88 59 - ### Multi-Tenant Architecture 89 + Use the auto-generated, type-safe client: 60 90 61 - Each slice operates independently: 91 + ```typescript 92 + import { AtprotoClient } from "./generated_client.ts"; 62 93 63 - - Isolated data storage 64 - - Custom lexicon definitions 65 - - Separate sync configurations 66 - - Per-slice statistics and analytics 94 + const client = new AtprotoClient({ 95 + baseUrl: "https://api.slices.network", 96 + sliceUri: "at://your-slice-uri", 97 + }); 67 98 68 - ## Use Cases 99 + // Get all grunge albums 100 + const albums = await client.com.recordcollector.album.getRecords({ 101 + where: { genre: { contains: "grunge" } }, 102 + sortBy: [{ field: "releaseDate", direction: "desc" }], 103 + }); 104 + ``` 69 105 70 - - **Custom Social Apps**: Build specialized social networks with custom record 71 - types 72 - - **Data Aggregators**: Collect and organize AT Protocol data for analysis 73 - - **Specialized Feeds**: Create curated views of AT Protocol content 74 - - **Research Tools**: Index and query specific subsets of AT Protocol data 75 - - **Creative Applications**: Blogs, galleries, portfolios built on AT Protocol 106 + ## Key Features 76 107 77 - ## Architecture Overview 108 + - **Schema Validation**: Define lexicons that enforce data structure and constraints 109 + - **Auto-generated APIs**: REST endpoints created automatically from your schemas 110 + - **TypeScript SDKs**: Type-safe clients generated from your lexicons 111 + - **Real-time Sync**: Automatic synchronization across the AT Protocol network 112 + - **Advanced Querying**: Filter, sort, and paginate records with a powerful query API 113 + - **OAuth Built-in**: Authentication with any AT Protocol account 78 114 79 - Slices consists of two main components: 115 + ## When to Use Slices 80 116 81 - - **API Server** (Rust): Handles AT Protocol integration, database operations, 82 - and API endpoints 83 - - **Frontend** (Deno): Provides web UI for slice management and user 84 - authentication 117 + Slices is ideal for: 85 118 86 - Both components work together to provide a complete AT Protocol appview 87 - platform. The API server can also be run standalone as a headless service and 88 - interactive with via it's XRPC apis. 119 + - **Social Applications**: Build specialized communities, forums, or social features 120 + - **Content Platforms**: Create blogs, documentation sites, or media libraries 121 + - **SaaS Products**: Develop collaborative tools with structured data needs 122 + - **Web APIs**: Design REST APIs with automatic validation and documentation 123 + - **Decentralized Apps**: Build on AT Protocol without managing infrastructure 89 124 90 125 ## Next Steps 91 126 92 127 - [Getting Started](./getting-started.md) - Set up your first slice 93 - - [Core Concepts](./concepts.md) - Understand the key concepts 94 - - [API Reference](./api-reference.md) - Explore the API endpoints 95 - - [SDK Usage](./sdk-usage.md) - Learn to use generated clients 128 + - [Core Concepts](./concepts.md) - Understand lexicons and collections 129 + - [API Reference](./api-reference.md) - Explore the full API
-118
docs/introduction.md
··· 1 - # Introduction 2 - 3 - Slices is an open source platform for building structured data applications on 4 - the AT Protocol network. 5 - 6 - ## What is Slices? 7 - 8 - Slices lets you define custom data schemas and build applications that store, 9 - query, and sync structured records across the decentralized AT Protocol network. 10 - Think of it as a schema-first backend that automatically handles data 11 - validation, indexing, and cross-network synchronization. 12 - 13 - ## How Slices Works on AT Protocol 14 - 15 - ```mermaid 16 - flowchart LR 17 - Users[Users<br/>Create/Update Records] --> PDS[PDS Nodes<br/>Store user data] 18 - PDS --> Firehose[Firehose<br/>Stream of all events] 19 - Firehose --> SlicesNetwork[Slices Network - AppView<br/>• Monitors all AT Protocol data<br/>• Routes to relevant slices] 20 - SlicesNetwork --> SliceA[Slice A<br/>• Blog lexicons<br/>• Post records<br/>• Comment queries] 21 - SlicesNetwork --> SliceB[Slice B<br/>• Music lexicons<br/>• Album records<br/>• Playlist queries] 22 - SliceA --> ClientA[Application<br/>Client<br/>• Read/write data] 23 - SliceB --> ClientB[Application<br/>Client<br/>• Read/write data] 24 - ``` 25 - 26 - **Flow:** 27 - 28 - 1. Users create records on their Personal Data Server (PDS) 29 - 2. The Firehose streams all network events in real-time 30 - 3. The Slices Network monitors the firehose and routes data to relevant slices 31 - 4. Each slice indexes only records matching its specific lexicons 32 - 5. Application clients connect to specific slices to read/write data 33 - 34 - ## Quick Start 35 - 36 - Get started in under a minute: 37 - 38 - ```bash 39 - # Initialize a new slice project 40 - deno run -A jsr:@slices/cli init 41 - 42 - # Follow the prompts to: 43 - # 1. Name your slice 44 - # 2. Define your first lexicon 45 - # 3. Deploy to the network 46 - ``` 47 - 48 - ## Simple Example 49 - 50 - Define a schema for a blog post: 51 - 52 - ```json lexicons/com/myblog/post.json 53 - { 54 - "lexicon": 1, 55 - "id": "com.myblog.post", 56 - "defs": { 57 - "main": { 58 - "type": "record", 59 - "record": { 60 - "type": "object", 61 - "required": ["title", "content", "createdAt"], 62 - "properties": { 63 - "title": { "type": "string", "maxLength": 200 }, 64 - "content": { "type": "string", "maxLength": 10000 }, 65 - "tags": { "type": "array", "items": { "type": "string" } }, 66 - "createdAt": { "type": "string", "format": "datetime" } 67 - } 68 - } 69 - } 70 - } 71 - } 72 - ``` 73 - 74 - Deploy your lexicon and generate the TypeScript client: 75 - 76 - ```bash 77 - # Push your lexicon to the slice 78 - deno run -A jsr:@slices/cli lexicon push 79 - 80 - # Generate TypeScript SDK from your lexicons 81 - deno run -A jsr:@slices/cli codegen 82 - ``` 83 - 84 - Query your data using the auto-generated API: 85 - 86 - ```typescript 87 - // Get all posts with a specific tag 88 - const posts = await client.com.myblog.post.getRecords({ 89 - slice: "at://your-slice-uri", 90 - where: { tags: { contains: "javascript" } }, 91 - sortBy: [{ field: "createdAt", direction: "desc" }], 92 - }); 93 - ``` 94 - 95 - ## Key Features 96 - 97 - - **Schema Validation**: Define lexicons that enforce data structure and constraints 98 - - **Auto-generated APIs**: REST endpoints created automatically from your schemas 99 - - **TypeScript SDKs**: Type-safe clients generated from your lexicons 100 - - **Real-time Sync**: Automatic synchronization across the AT Protocol network 101 - - **Advanced Querying**: Filter, sort, and paginate records with a powerful query API 102 - - **OAuth Built-in**: Authentication with any AT Protocol account 103 - 104 - ## When to Use Slices 105 - 106 - Slices is ideal for: 107 - 108 - - **Social Applications**: Build specialized communities, forums, or social features 109 - - **Content Platforms**: Create blogs, documentation sites, or media libraries 110 - - **SaaS Products**: Develop collaborative tools with structured data needs 111 - - **Web APIs**: Design REST APIs with automatic validation and documentation 112 - - **Decentralized Apps**: Build on AT Protocol without managing infrastructure 113 - 114 - ## Next Steps 115 - 116 - - [Getting Started](./getting-started.md) - Set up your first slice 117 - - [Core Concepts](./concepts.md) - Understand lexicons and collections 118 - - [API Reference](./api-reference.md) - Explore the full API
+81 -95
docs/sdk-usage.md
··· 7 7 After generating your TypeScript client, you can use it directly in your 8 8 project: 9 9 10 - ```typescript Code 11 - import { AtProtoClient } from "./generated_client.ts"; 10 + ```typescript 11 + import { AtprotoClient } from "./generated_client.ts"; 12 12 import { OAuthClient } from "@slices/oauth"; 13 13 ``` 14 14 ··· 16 16 17 17 ### Without Authentication (Read-Only) 18 18 19 - ```typescript Code 20 - const client = new AtProtoClient( 21 - "https://api.your-domain.com", 22 - "at://did:plc:abc/network.slices.slice/your-slice-rkey", 23 - ); 19 + ```typescript 20 + const client = new AtprotoClient({ 21 + baseUrl: "https://api.slices.network", 22 + sliceUri: "at://did:plc:abc/network.slices.slice/your-slice-rkey", 23 + }); 24 24 25 25 // Read operations work without auth 26 26 const albums = await client.com.recordcollector.album.getRecords(); ··· 28 28 29 29 ### With Authentication (Full Access) 30 30 31 - ```typescript Code 31 + ```typescript 32 32 import { OAuthClient } from "@slices/oauth"; 33 33 34 34 // Set up OAuth client ··· 41 41 }); 42 42 43 43 // Initialize API client with OAuth 44 - const client = new AtProtoClient( 45 - "https://api.your-domain.com", 46 - "at://did:plc:abc/network.slices.slice/your-slice-rekey", 47 - oauthClient, 48 - ); 44 + const client = new AtprotoClient({ 45 + baseUrl: "https://api.slices.network", 46 + sliceUri: "at://did:plc:abc/network.slices.slice/your-slice-rkey", 47 + auth: oauthClient, 48 + }); 49 49 ``` 50 50 51 51 ## CRUD Operations ··· 54 54 55 55 The SDK uses `getRecords` for retrieving records: 56 56 57 - ```typescript Code 57 + ```typescript 58 58 // Get all vinyl records 59 59 const albums = await client.com.recordcollector.album.getRecords(); 60 60 ··· 142 142 The `countRecords` method allows you to count records without fetching them, 143 143 using the same filtering parameters as `getRecords`: 144 144 145 - ```typescript Code 145 + ```typescript 146 146 // Count all records 147 147 const total = await client.com.recordcollector.album.countRecords(); 148 148 console.log(`Total albums: ${total.count}`); ··· 186 186 187 187 ### Getting a Single Record 188 188 189 - ```typescript Code 189 + ```typescript 190 190 const album = await client.com.recordcollector.album.getRecord({ 191 191 uri: "at://did:plc:abc/com.recordcollector.album/3jklmno456", 192 192 }); ··· 197 197 198 198 ### Creating Records 199 199 200 - ```typescript Code 200 + ```typescript 201 201 // Create with auto-generated key 202 202 const newAlbum = await client.com.recordcollector.album.createRecord({ 203 203 title: "In Utero", ··· 221 221 222 222 ### Updating Records 223 223 224 - ```typescript Code 224 + ```typescript 225 225 // Get the record key from the URI 226 226 const uri = "at://did:plc:abc/com.recordcollector.album/3jklmno456"; 227 227 const rkey = uri.split("/").pop(); // '3jklmno456' ··· 241 241 242 242 ### Deleting Records 243 243 244 - ```typescript Code 244 + ```typescript 245 245 const rkey = "3jklmno456"; 246 246 await client.com.recordcollector.album.deleteRecord(rkey); 247 247 ``` ··· 250 250 251 251 Access synced external collections like Bluesky profiles: 252 252 253 - ```typescript Code 253 + ```typescript 254 254 // Get Bluesky profiles in your slice 255 255 const profiles = await client.app.bsky.actor.profile.getRecords(); 256 256 ··· 268 268 269 269 ### Uploading Blobs 270 270 271 - ```typescript Code 271 + ```typescript 272 272 // Read file as ArrayBuffer 273 273 const file = await Deno.readFile("./nevermind-cover.jpg"); 274 274 ··· 291 291 292 292 ### Converting Blobs to CDN URLs 293 293 294 - ```typescript Code 294 + ```typescript 295 295 import { recordBlobToCdnUrl } from "./generated-client.ts"; 296 296 297 297 // Get a record with a blob ··· 318 318 319 319 ## Slice Operations 320 320 321 - ### Get Slice Statistics 322 - 323 - ```typescript Code 324 - const stats = await client.network.slices.slice.stats({ 325 - slice: "at://your-slice-uri", 326 - }); 327 - 328 - console.log(`Total records: ${stats.totalRecords}`); 329 - console.log(`Total actors: ${stats.totalActors}`); 330 - 331 - stats.collectionStats.forEach((stat) => { 332 - console.log(`${stat.collection}: ${stat.recordCount} records`); 333 - }); 334 - ``` 335 - 336 321 ### Get Actors 337 322 338 323 The `getActors` method retrieves actors (users) within a slice with powerful 339 324 filtering and sorting capabilities: 340 325 341 - ```typescript Code 326 + ```typescript 342 327 // Get all actors in the slice 343 - const actors = await client.network.slices.slice.getActors(); 328 + const actors = await client.getActors(); 344 329 345 330 // With pagination 346 - const page1 = await client.network.slices.slice.getActors({ 331 + const page1 = await client.getActors({ 347 332 limit: 20, 348 333 }); 349 - const page2 = await client.network.slices.slice.getActors({ 334 + const page2 = await client.getActors({ 350 335 limit: 20, 351 336 cursor: page1.cursor, 352 337 }); 353 338 354 339 // Filter by specific DIDs 355 - const specificActors = await client.network.slices.slice.getActors({ 340 + const specificActors = await client.getActors({ 356 341 where: { 357 342 did: { in: ["did:plc:user1", "did:plc:user2"] }, 358 343 }, 359 344 }); 360 345 361 346 // Search by handle 362 - const searchByHandle = await client.network.slices.slice.getActors({ 347 + const searchByHandle = await client.getActors({ 363 348 where: { 364 349 handle: { contains: "alice" }, 365 350 }, 366 351 }); 367 352 368 353 // Filter by exact handle 369 - const exactHandle = await client.network.slices.slice.getActors({ 354 + const exactHandle = await client.getActors({ 370 355 where: { 371 356 handle: { eq: "user.bsky.social" }, 372 357 }, ··· 380 365 381 366 ### Browse Slice Records 382 367 383 - #### Get Records from Multiple Collections 384 - 385 - The `getSliceRecords` method uses the same `where` clause approach: 368 + The `getSliceRecords` method retrieves records across multiple collections: 386 369 387 - ```typescript Code 370 + ```typescript 388 371 // Get records from specific collections 389 - const records = await client.network.slices.slice.getSliceRecords({ 372 + const records = await client.getSliceRecords({ 390 373 where: { 391 - collection: { eq: "com.example.post" }, 374 + collection: { eq: "com.recordcollector.album" }, 392 375 did: { eq: "did:plc:specific-author" }, // optional 393 376 }, 394 377 limit: 50, ··· 399 382 }); 400 383 401 384 // Search across collections using specific fields 402 - const searchResults = await client.network.slices.slice.getSliceRecords({ 385 + const searchResults = await client.getSliceRecords({ 403 386 where: { 404 - collection: { eq: "com.example.post" }, 405 - title: { contains: "hello world" }, 387 + collection: { eq: "com.recordcollector.album" }, 388 + title: { contains: "nevermind" }, 406 389 did: { eq: "did:plc:specific-author" }, // optional 407 390 }, 408 391 limit: 50, 409 392 }); 410 393 411 394 // Global search across ALL fields in records 412 - const globalSearchResults = await client.network.slices.slice.getSliceRecords({ 395 + const globalSearchResults = await client.getSliceRecords({ 413 396 where: { 414 - collection: { eq: "com.example.post" }, 415 - json: { contains: "hello world" }, // Searches entire record content 397 + collection: { eq: "com.recordcollector.album" }, 398 + json: { contains: "grunge" }, // Searches entire record content 416 399 did: { eq: "did:plc:specific-author" }, // optional 417 400 }, 418 401 limit: 50, ··· 423 406 }); 424 407 425 408 // Get records from any collection with global text search 426 - const allCollectionSearch = await client.network.slices.slice.getSliceRecords({ 409 + const allCollectionSearch = await client.getSliceRecords({ 427 410 where: { 428 - json: { contains: "important content" }, // Searches ALL fields in ALL collections 411 + json: { contains: "seattle" }, // Searches ALL fields in ALL collections 429 412 }, 430 413 limit: 20, 431 414 }); ··· 439 422 440 423 Search within specific fields of your records: 441 424 442 - ```typescript Code 425 + ```typescript 443 426 // Search in title field only 444 427 const titleSearch = await client.com.recordcollector.album.getRecords({ 445 428 where: { ··· 459 442 460 443 Use the special `json` field to search across **all fields** in a record: 461 444 462 - ```typescript Code 445 + ```typescript 463 446 // Finds records containing "grunge" anywhere in their data 464 447 const globalSearch = await client.com.recordcollector.album.getRecords({ 465 448 where: { ··· 479 462 480 463 When using `getSliceRecords`, you can search across multiple collections: 481 464 482 - ```typescript Code 465 + ```typescript 483 466 // Search for "seattle" across all collections 484 - const crossCollectionSearch = await client.network.slices.slice.getSliceRecords( 485 - { 486 - where: { 487 - json: { contains: "seattle" }, 488 - }, 467 + const crossCollectionSearch = await client.getSliceRecords({ 468 + where: { 469 + json: { contains: "seattle" }, 489 470 }, 490 - ); 471 + }); 491 472 492 473 // Limit to specific collections 493 - const specificSearch = await client.network.slices.slice.getSliceRecords({ 474 + const specificSearch = await client.getSliceRecords({ 494 475 where: { 495 476 collection: { 496 477 in: ["com.recordcollector.album", "com.recordcollector.review"], ··· 506 487 using the separate `orWhere` parameter. This provides clean type safety and 507 488 autocomplete for field names: 508 489 509 - ```typescript Code 490 + ```typescript 510 491 // Find albums by either Nirvana OR Alice in Chains 511 492 const albums = await client.com.recordcollector.album.getRecords({ 512 493 orWhere: { ··· 535 516 // SQL: WHERE release_date = '1991-09-24' AND (artist LIKE '%nirvana%' OR genre LIKE '%grunge%') 536 517 537 518 // OR queries work with cross-collection searches too 538 - const crossCollectionOrSearch = await client.network.slices.slice 539 - .getSliceRecords({ 540 - where: { 541 - collection: { eq: "com.recordcollector.album" }, 542 - }, 543 - orWhere: { 544 - artist: { contains: "pearl jam" }, 545 - genre: { contains: "alternative rock" }, 546 - }, 547 - }); 519 + const crossCollectionOrSearch = await client.getSliceRecords({ 520 + where: { 521 + collection: { eq: "com.recordcollector.album" }, 522 + }, 523 + orWhere: { 524 + artist: { contains: "pearl jam" }, 525 + genre: { contains: "alternative rock" }, 526 + }, 527 + }); 548 528 549 529 // You get full autocomplete and type safety for field names in both where and orWhere 550 530 const typedSearch = await client.com.recordcollector.album.getRecords({ ··· 562 542 563 543 ### Sync User Collections 564 544 565 - ```typescript Code 545 + ```typescript 566 546 // Sync current user's data (requires auth) 567 - const syncResult = await client.network.slices.slice.syncUserCollections({ 568 - timeoutSeconds: 30, 569 - }); 547 + const syncResult = await client.syncUserCollections(); 570 548 571 549 console.log(`Synced ${syncResult.recordsSynced} records`); 572 550 ``` 573 551 574 552 ## Error Handling 575 553 576 - ```typescript Code 554 + ```typescript 577 555 try { 578 556 const post = await client.com.example.post.getRecord({ 579 557 uri: "at://invalid-uri", ··· 593 571 594 572 ### 1. Initialize OAuth 595 573 596 - ```typescript Code 574 + ```typescript 597 575 const oauthClient = new OAuthClient({ 598 576 clientId: process.env.OAUTH_CLIENT_ID, 599 577 clientSecret: process.env.OAUTH_CLIENT_SECRET, ··· 604 582 605 583 ### 2. Start Authorization 606 584 607 - ```typescript Code 585 + ```typescript 608 586 const authResult = await oauthClient.authorize({ 609 587 loginHint: "user.bsky.social", 610 588 }); ··· 615 593 616 594 ### 3. Handle Callback 617 595 618 - ```typescript Code 596 + ```typescript 619 597 // In your callback handler 620 598 const urlParams = new URLSearchParams(window.location.search); 621 599 const code = urlParams.get("code"); ··· 626 604 627 605 ### 4. Use Authenticated Client 628 606 629 - ```typescript Code 630 - const client = new AtProtoClient(apiUrl, sliceUri, oauthClient); 607 + ```typescript 608 + const client = new AtprotoClient({ 609 + baseUrl: "https://api.slices.network", 610 + sliceUri: "at://did:plc:abc/network.slices.slice/your-slice-rkey", 611 + auth: oauthClient, 612 + }); 631 613 632 614 // OAuth tokens are automatically managed 633 - const profile = await client.network.slices.actor.profile.createRecord({ 634 - displayName: "New User", 635 - description: "My profile", 636 - }, true); // useSelfRkey for profile 615 + const album = await client.com.recordcollector.album.createRecord({ 616 + title: "Ten", 617 + artist: "Pearl Jam", 618 + releaseDate: "1991-08-27", 619 + genre: ["grunge", "alternative rock"], 620 + condition: "Mint", 621 + }); 637 622 ``` 638 623 639 624 ## Type Safety 640 625 641 626 The generated SDK provides full TypeScript type safety: 642 627 643 - ```typescript Code 628 + ```typescript 644 629 // TypeScript knows the shape of your records 645 630 const album = await client.com.recordcollector.album.getRecord({ uri }); 646 631 ··· 669 654 670 655 ### Batch Operations 671 656 672 - ```typescript Code 657 + ```typescript 673 658 // Process records in batches 659 + 674 660 async function* getAllAlbums() { 675 661 let cursor: string | undefined; 676 662
+1 -84
docs/self-hosting.md
··· 2 2 3 3 This guide covers how to set up and run your own Slices instance. 4 4 5 - ## Prerequisites 6 - 7 - - Docker and Docker Compose 8 - - PostgreSQL (or use Docker) 9 - - Deno (for frontend) 10 - - Rust and Cargo (for API development) 11 - - An AT Protocol account (for OAuth) 12 - 13 - ## Initial Setup 14 - 15 - ### 1. Clone the Repository 16 - 17 - ```bash 18 - git clone https://tangled.sh/@slices.network/slices 19 - cd slice 20 - ``` 21 - 22 - ### 2. Set Up the Database 23 - 24 - Start PostgreSQL using Docker: 25 - 26 - ```bash 27 - docker-compose up -d postgres 28 - ``` 29 - 30 - Or use an existing PostgreSQL instance and create a database: 31 - 32 - ```sql 33 - CREATE DATABASE slices; 34 - ``` 35 - 36 - ### 3. Configure Environment Variables 37 - 38 - Create `.env` files for both API and frontend: 39 - 40 - **API (`/api/.env`)**: 41 - 42 - ```bash 43 - DATABASE_URL=postgres://user:password@localhost:5432/slices 44 - AUTH_BASE_URL=https://aip.your-domain.com 45 - PORT=3000 46 - ``` 47 - 48 - **Frontend (`/frontend/.env`)**: 49 - 50 - ```bash 51 - OAUTH_CLIENT_ID=your-client-id 52 - OAUTH_CLIENT_SECRET=your-client-secret 53 - OAUTH_REDIRECT_URI=http://localhost:8000/oauth/callback 54 - OAUTH_AIP_BASE_URL=https://aip.your-domain.com 55 - SESSION_ENCRYPTION_KEY=your-32-char-key 56 - API_URL=http://localhost:3000 57 - SLICE_URI=at://did:plc:your-did/network.slices.slice/your-slice-id 58 - DATABASE_URL=slices.db 59 - ``` 60 - 61 - ### 4. Register OAuth Client 62 - 63 - Register your application with the AIP server: 64 - 65 - ```bash 66 - cd frontend 67 - ./scripts/register-oauth-client.sh 68 - ``` 69 - 70 - Save the client ID and secret to your `.env` file. 71 - 72 - ### 5. Start the Services 73 - 74 - Start the API server: 75 - 76 - ```bash 77 - cd api 78 - cargo run 79 - ``` 80 - 81 - Start the frontend: 82 - 83 - ```bash 84 - cd frontend 85 - deno task dev 86 - ``` 87 - 88 - Visit `http://localhost:8000` to access the web interface. 5 + WIP - coming soon
+40 -1
frontend/src/features/docs/handlers.tsx
··· 16 16 category: "Getting Started", 17 17 docs: [ 18 18 { 19 - slug: "introduction", 19 + slug: "intro", 20 20 title: "Introduction", 21 21 description: "Overview of Slices platform", 22 22 }, ··· 238 238 // Custom code block renderer - return content as-is since marked-highlight handles it 239 239 renderer.code = function (token: Tokens.Code) { 240 240 return token.text; // marked-highlight has already processed this 241 + }; 242 + 243 + // Custom table renderer 244 + renderer.table = function (token: Tokens.Table) { 245 + const header = token.header 246 + .map((cell: Tokens.TableCell, i: number) => { 247 + const text = this.parser.parseInline(cell.tokens); 248 + const align = token.align[i] 249 + ? ` style="text-align: ${token.align[i]}"` 250 + : ""; 251 + return `<th class="px-4 py-2 text-left text-sm font-semibold text-zinc-900 dark:text-white border-b-2 border-zinc-200 dark:border-zinc-700"${align}>${text}</th>`; 252 + }) 253 + .join(""); 254 + 255 + const rows = token.rows 256 + .map((row: Tokens.TableCell[]) => { 257 + const cells = row 258 + .map((cell: Tokens.TableCell, i: number) => { 259 + const text = this.parser.parseInline(cell.tokens); 260 + const align = token.align[i] 261 + ? ` style="text-align: ${token.align[i]}"` 262 + : ""; 263 + return `<td class="px-4 py-2 text-sm text-zinc-700 dark:text-zinc-300 border-b border-zinc-100 dark:border-zinc-800"${align}>${text}</td>`; 264 + }) 265 + .join(""); 266 + return `<tr class="hover:bg-zinc-50 dark:hover:bg-zinc-800/50">${cells}</tr>`; 267 + }) 268 + .join(""); 269 + 270 + return `<div class="overflow-x-auto my-6"> 271 + <table class="min-w-full divide-y divide-zinc-200 dark:divide-zinc-700"> 272 + <thead class="bg-zinc-50 dark:bg-zinc-800/50"> 273 + <tr>${header}</tr> 274 + </thead> 275 + <tbody class="bg-white dark:bg-zinc-900 divide-y divide-zinc-100 dark:divide-zinc-800"> 276 + ${rows} 277 + </tbody> 278 + </table> 279 + </div>`; 241 280 }; 242 281 243 282 // Set options and use the custom renderer