···11# Slices
2233-An open-source platform for building AT Protocol appviews with custom data
33+An open-source platform for building AT Protocol AppViews with custom data
44schemas, automatic SDK generation, and built-in sync capabilities.
5566+⚠️ **Alpha Release** Version 0.x - This project is in active development
77+approaching stability. Core features are implemented and functional, though APIs
88+may undergo refinements. Suitable for early adoption and development use.
99+Production deployment is possible with thorough testing for your specific use
1010+case.
1111+612## Overview
713814Slices enables developers to create "slices" - custom appviews within the AT
···2026- **OAuth Integration**: Built-in AT Protocol authentication
2127- **Multi-tenant Architecture**: Each slice operates independently with its own
2228 data validated against its lexicons
2323-- **Dynamic API Endpoints**: CRUD operations automatically created for each
2424- lexicon record type (collection)
2929+- **Dynamic API Endpoints**: CRUD operations with SQL-like queries automatically
3030+ created for each lexicon record type (collection)
25312632## Documentation
2733···3238- [API Reference](./docs/api-reference.md) - Complete API documentation
3339- [SDK Usage](./docs/sdk-usage.md) - Using generated TypeScript clients
34403535-## Quick Start
4141+## Development Quick Start
36423743### Prerequisites
3844···46521. Clone the repository:
47534854```bash
4949-git clone https://tangled.sh/justslices.net/core
5555+git clone https://tangled.sh/slices.network/slices
5056cd core
5157```
525853592. Set up environment variables:
54605555-Create `.env` files in both `/api` and `/frontend` directories (see
5656-[Getting Started](./docs/getting-started.md) for details).
6161+Create `.env` files in both `/api` and `/frontend` directories (see environment
6262+variables section below).
6363+6464+3. Start the required infrastructure services:
6565+6666+> **Note**: This section is a work in progress. Currently using Cloudflare
6767+> tunnels, but support for alternative solutions like ngrok or Tailscale would
6868+> be welcome contributions.
6969+7070+```bash
7171+# Start PostgreSQL, AIP OAuth service, Cloudflare tunnel, and Redis
7272+CLOUDFLARE_TUNNEL_TOKEN=<your-token> AIP_EXTERNAL_BASE=<your-tunnel-url> docker compose up -d
7373+```
57745858-3. Start the services:
7575+You'll need to provide:
7676+7777+- `CLOUDFLARE_TUNNEL_TOKEN`: Your Cloudflare tunnel token for secure access
7878+- `AIP_EXTERNAL_BASE`: The external URL for the AIP OAuth service (e.g.,
7979+ `https://tunnel.example.com`)
8080+8181+4. Start the application services:
59826083```bash
6184# Start the API
···6790deno task dev
6891```
69927070-4. Visit `http://localhost:8000` to access the web interface.
9393+5. Visit `http://localhost:8080` to access the web interface.
71947295## Project Structure
7396···7810179102- **AT Protocol XRPC Handlers**: Dynamic endpoints for slice-specific
80103 collections with full CRUD operations
104104+- **Lexicon Validation**: Validates all records against defined lexicon schemas
81105- **Sync Engine**: Bulk synchronization from AT Protocol repositories
82106- **Jetstream Integration**: Real-time data streaming from AT Protocol firehose
83107- **Database Layer**: PostgreSQL integration with slice-aware queries
8484-- **SDK Generation**: Automatically generates type-safe TypeScript clients and
8585- OpenAPI specifications
86108- **OAuth Integration**: Handles AT Protocol OAuth flows and token management
8710988110### Frontend (`/frontend`)
···168190169191### API Configuration
170192171171-| Variable | Description | Required |
172172-| --------------- | ---------------------------- | -------- |
173173-| `DATABASE_URL` | PostgreSQL connection string | Yes |
174174-| `AUTH_BASE_URL` | AIP OAuth service URL | Yes |
175175-| `PORT` | Server port (default: 3000) | No |
193193+| Variable | Description | Required | Default |
194194+| -------------------------------------- | -------------------------------------------------------------- | -------- | ------------------------------------- |
195195+| `DATABASE_URL` | PostgreSQL connection string | Yes | - |
196196+| `AUTH_BASE_URL` | Authentication service base URL | No | `http://localhost:8081` |
197197+| `PORT` | Server port | No | `3000` |
198198+| `PROCESS_TYPE` | Process type: `all` (HTTP + Jetstream), `app`, or `worker` | No | `all` |
199199+| `RELAY_ENDPOINT` | AT Protocol relay endpoint for backfill | No | `https://relay1.us-west.bsky.network` |
200200+| `JETSTREAM_HOSTNAME` | AT Protocol Jetstream hostname | No | - |
201201+| `SYSTEM_SLICE_URI` | System slice URI | No | Default slice URI |
202202+| `DEFAULT_MAX_SYNC_REPOS` | Maximum repositories per sync operation | No | `5000` |
203203+| `REDIS_URL` | Redis connection URL (optional, falls back to in-memory cache) | No | - |
204204+| `REDIS_TTL_SECONDS` | Redis cache TTL in seconds | No | `3600` |
205205+| `JETSTREAM_CURSOR_WRITE_INTERVAL_SECS` | Interval for writing Jetstream cursor position | No | `30` |
206206+| `RUST_LOG` | Rust logging level | No | `debug` |
176207177208### Frontend Configuration
178209179179-| Variable | Description | Required |
180180-| --------------------- | ------------------------------- | -------- |
181181-| `OAUTH_CLIENT_ID` | OAuth application client ID | Yes |
182182-| `OAUTH_CLIENT_SECRET` | OAuth application client secret | Yes |
183183-| `OAUTH_REDIRECT_URI` | OAuth callback URL | Yes |
184184-| `OAUTH_AIP_BASE_URL` | AIP OAuth service URL | Yes |
185185-| `API_URL` | Backend API base URL | Yes |
186186-| `SLICE_URI` | Default slice URI for queries | Yes |
210210+| Variable | Description | Required | Default |
211211+| --------------------- | --------------------------------------------- | -------- | ----------- |
212212+| `OAUTH_CLIENT_ID` | OAuth application client ID | Yes | - |
213213+| `OAUTH_CLIENT_SECRET` | OAuth application client secret | Yes | - |
214214+| `OAUTH_REDIRECT_URI` | OAuth callback URL | Yes | - |
215215+| `OAUTH_AIP_BASE_URL` | AIP OAuth service URL | Yes | - |
216216+| `API_URL` | Backend API base URL | Yes | - |
217217+| `SLICE_URI` | Default slice URI for queries | Yes | - |
218218+| `ADMIN_DID` | Admin DID for privileged operations | No | - |
219219+| `DATABASE_URL` | SQLite database path (frontend session store) | No | `slices.db` |
187220188221## Roadmap
189222···192225- Documentation
193226- Frontend UX improvements/social features
194227- Support more search and filtering params in collection xrpx handlers and SDK
195195-- Surface jetstream and sync logs in the UI
196196-- Improve sync and jetstream reliability
228228+- SDK error handling improvements (i.e. fuzzy search, date ranges, geo?? etc)
229229+- Improve sync and jetstream performace, logging, error handling, and
230230+ reliability
197231- Monitor api container performance and resource usage
198232199233### Planned Features
200234201235- Labeler service integration
202202-- CLI tool
203203-- API rate limiting
204236- Enhanced lexicon management UI
205237- Lexicon discovery and sharing
238238+- More cli templates (React, Expo, Astro, etc) and examples
206239207240## Community
208241209209-- **Bluesky**: [@justslices.net](https://bsky.app/profile/justslices.net)
210210-- **Discord**: [Join our server](https://discord.gg/your-invite)
242242+- **Bluesky**: [@slices.network](https://bsky.app/profile/slices.network)
243243+- **Discord**: [Join our server](https://discord.gg/NqSd3eW8S8)
211244212245## Support
213246214247- [Documentation](./docs/)
215215-- [Discord Community](https://discord.gg/your-invite)
248248+- [Discord Community](https://discord.gg/NqSd3eW8S8)
216249217250## License
218251···223256224257- Built on the [AT Protocol](https://atproto.com)
225258- Inspired by the AT Protocol community
226226-- Thanks to all contributors
227227-228228-## Status
229229-230230-This project is in active development. APIs may change as we approach v1.0.
259259+- Thanks to all contributors and early adopters
231260232261---
233262
+340-483
docs/api-reference.md
···11# API Reference
2233-Complete reference for Slices API endpoints.
44-53## Base URL
6477-```bash
55+```
86https://api.slices.network/xrpc/
97```
1081111-## Authentication
1212-1313-Most write operations require OAuth 2.0 authentication. Include the access token
1414-in the Authorization header:
1515-99+For local development:
1610```
1717-Authorization: Bearer YOUR_ACCESS_TOKEN
1111+http://localhost:3000/xrpc/
1812```
19132020-Read operations typically work without authentication.
2121-2222-## Dynamic Collection Endpoints
2323-2424-For each collection in your slice, the following endpoints are automatically
2525-generated:
2626-2727-### `[collection].getRecords`
2828-2929-Get records in a collection.
3030-3131-**Method**: GET
3232-3333-**Parameters**:
3434-3535-- `slice` (string, required): Slice URI
3636-- `limit` (number, optional): Maximum records (default: 50)
3737-- `cursor` (string, optional): Pagination cursor
3838-- `where` (object, optional): Filter conditions using field-specific queries
3939-- `sortBy` (array, optional): Sort specification with field and direction
4040- objects
4141-4242-### `[collection].getRecord`
4343-4444-Get a single record.
4545-4646-**Method**: GET
1414+## Authentication
47154848-**Parameters**:
1616+Write operations require OAuth 2.0 authentication with a Bearer token:
49175050-- `slice` (string, required): Slice URI
5151-- `uri` (string, required): Record URI
5252-5353-### `[collection].countRecords`
5454-5555-Count records in a collection.
5656-5757-**Method**: GET
5858-5959-**Parameters**:
6060-6161-- `slice` (string, required): Slice URI
6262-- `where` (object, optional): Filter conditions using field-specific queries
6363-- Other filter parameters (no limit/cursor)
6464-6565-**Response**:
6666-6767-```json Code
6868-{
6969- "count": 150
7070-}
7118```
7272-7373-### `[collection].createRecord`
7474-7575-Create a new record.
7676-7777-**Method**: POST
7878-7979-**Authentication**: Required
8080-8181-**Body**:
8282-8383-```json Code
8484-{
8585- "slice": "at://your-slice-uri",
8686- "record": {
8787- "$type": "com.recordcollector.album",
8888- "title": "Superunknown",
8989- "artist": "Soundgarden",
9090- "releaseDate": "1994-03-08",
9191- "condition": "Near Mint",
9292- "genre": ["grunge", "alternative metal"]
9393- },
9494- "rkey": "3jklmno456"
9595-}
1919+Authorization: Bearer YOUR_ACCESS_TOKEN
9620```
97219898-### `[collection].updateRecord`
9999-100100-Update an existing record.
101101-102102-**Method**: POST
103103-104104-**Authentication**: Required
105105-106106-**Body**:
107107-108108-```json Code
109109-{
110110- "slice": "at://your-slice-uri",
111111- "rkey": "3xyz789abc",
112112- "record": {
113113- "$type": "com.recordcollector.album",
114114- "title": "Dirt",
115115- "artist": "Alice in Chains",
116116- "releaseDate": "1992-09-29",
117117- "condition": "Very Good Plus",
118118- "notes": "Minor sleeve wear, vinyl plays perfectly"
119119- }
120120-}
121121-```
2222+Read operations are public by default.
12223123123-### `[collection].deleteRecord`
2424+## Collection Endpoints
12425125125-Delete a record.
2626+For each collection in your slice (e.g., `com.recordcollector.album`), the following endpoints are automatically generated:
12627127127-**Method**: POST
2828+# **{collection}.getRecords**
12829129129-**Authentication**: Required
3030+> List records with filtering, sorting, and pagination.
13031131131-**Body**:
3232+**Method:** `POST`
3333+**Endpoint:** `/xrpc/{collection}.getRecords`
3434+**Example:** `/xrpc/com.recordcollector.album.getRecords`
13235133133-```json Code
3636+**Request Body:**
3737+```json
13438{
135135- "rkey": "3abc123xyz"
3939+ "slice": "at://did:plc:abc123/network.slices.slice/xyz789",
4040+ "limit": 20,
4141+ "cursor": "optional-pagination-cursor",
4242+ "where": {
4343+ "genre": { "contains": "grunge" },
4444+ "condition": { "in": ["Mint", "Near Mint"] }
4545+ },
4646+ "sortBy": [
4747+ { "field": "releaseDate", "direction": "desc" }
4848+ ]
13649}
13750```
13851139139-## Core Endpoints
140140-141141-### Slice Management
5252+**Parameters:**
5353+- `slice` (string, required): The slice URI to query
5454+- `limit` (number, optional): Maximum records to return (default: 50, max: 100)
5555+- `cursor` (string, optional): Pagination cursor from previous response
5656+- `where` (object, optional): Filter conditions. Field filters support operators: `eq`, `contains`, `in`. Special field `json` searches across all fields
5757+- `sortBy` (array, optional): Sort specification. Each item has `field` and `direction` ("asc" or "desc")
14258143143-### `network.slices.slice.getRecords`
144144-145145-Get all slices.
146146-147147-**Method**: GET
148148-149149-**Parameters**:
150150-151151-- `limit` (number, optional): Maximum records to return (default: 50)
152152-- `cursor` (string, optional): Pagination cursor
153153-- `where` (object, optional): Filter conditions using field-specific queries
154154-- `sortBy` (array, optional): Sort specification with field and direction
155155- objects
156156-157157-**Response**:
158158-159159-```json Code
5959+**Response:**
6060+```json
16061{
16162 "records": [
16263 {
163163- "uri": "at://did:plc:abc/network.slices.slice/xyz",
164164- "cid": "bafyrei...",
165165- "did": "did:plc:abc",
166166- "collection": "network.slices.slice",
6464+ "uri": "at://did:plc:user123/com.recordcollector.album/3l2w4x5y6z",
6565+ "cid": "bafyreigbtj4x7ip5legnfznufuopl4sg4knzc2cof6duas4b3q2fy6swua",
6666+ "did": "did:plc:user123",
6767+ "collection": "com.recordcollector.album",
16768 "value": {
168168- "name": "My Slice",
169169- "domain": "com.example",
170170- "createdAt": "2024-01-01T00:00:00Z"
6969+ "$type": "com.recordcollector.album",
7070+ "title": "Nevermind",
7171+ "artist": "Nirvana",
7272+ "releaseDate": "1991-09-24T00:00:00.000Z",
7373+ "genre": ["grunge", "alternative rock"],
7474+ "condition": "Near Mint",
7575+ "notes": "Original pressing, includes poster"
17176 },
172172- "indexedAt": "2024-01-01T00:00:00Z"
7777+ "indexedAt": "2024-03-15T10:30:15.123Z"
17378 }
17479 ],
175175- "cursor": "next-page-cursor"
8080+ "cursor": "next-page-cursor-xyz789"
17681}
17782```
17883179179-### `network.slices.slice.getRecord`
180180-181181-Get a specific slice by URI.
8484+# **{collection}.getRecord**
18285183183-**Method**: GET
184184-185185-**Parameters**:
186186-187187-- `uri` (string, required): AT Protocol URI of the slice
188188-189189-**Response**: Single record object (same structure as getRecords item)
190190-191191-### `network.slices.slice.createRecord`
192192-193193-Create a new slice.
194194-195195-**Method**: POST
8686+> Get a single record by URI.
19687197197-**Authentication**: Required
8888+**Method:** `GET`
8989+**Endpoint:** `/xrpc/{collection}.getRecord`
9090+**Example:** `/xrpc/com.recordcollector.album.getRecord`
19891199199-**Body**:
9292+**Query Parameters:**
9393+- `slice` (string, required): The slice URI
9494+- `uri` (string, required): The AT Protocol URI of the record
20095201201-```json Code
202202-{
203203- "slice": "at://your-slice-uri",
204204- "record": {
205205- "$type": "network.slices.slice",
206206- "name": "My New Slice",
207207- "domain": "com.example",
208208- "createdAt": "2024-01-01T00:00:00Z"
209209- },
210210- "rkey": "optional-record-key"
211211-}
9696+**Example Request:**
9797+```
9898+GET /xrpc/com.recordcollector.album.getRecord?slice=at://did:plc:abc123/network.slices.slice/xyz789&uri=at://did:plc:user123/com.recordcollector.album/3l2w4x5y6z
21299```
213100214214-**Response**:
215215-216216-```json Code
101101+**Response:**
102102+```json
217103{
218218- "uri": "at://did:plc:abc/network.slices.slice/xyz",
219219- "cid": "bafyrei..."
104104+ "uri": "at://did:plc:user123/com.recordcollector.album/3l2w4x5y6z",
105105+ "cid": "bafyreigbtj4x7ip5legnfznufuopl4sg4knzc2cof6duas4b3q2fy6swua",
106106+ "did": "did:plc:user123",
107107+ "collection": "com.recordcollector.album",
108108+ "value": {
109109+ "$type": "com.recordcollector.album",
110110+ "title": "Nevermind",
111111+ "artist": "Nirvana",
112112+ "releaseDate": "1991-09-24T00:00:00.000Z",
113113+ "genre": ["grunge", "alternative rock"],
114114+ "condition": "Near Mint",
115115+ "notes": "Original pressing, includes poster"
116116+ },
117117+ "indexedAt": "2024-03-15T10:30:15.123Z"
220118}
221119```
222120223223-### Slice Operations
121121+# **{collection}.countRecords**
224122225225-### `network.slices.slice.stats`
123123+> Count records matching filter criteria.
226124227227-Get statistics for a slice.
125125+**Method:** `POST`
126126+**Endpoint:** `/xrpc/{collection}.countRecords`
127127+**Example:** `/xrpc/com.recordcollector.album.countRecords`
228128229229-**Method**: POST
230230-231231-**Body**:
232232-233233-```json Code
129129+**Request Body:**
130130+```json
234131{
235235- "slice": "at://your-slice-uri"
132132+ "slice": "at://did:plc:abc123/network.slices.slice/xyz789",
133133+ "where": {
134134+ "condition": { "in": ["Mint", "Near Mint"] },
135135+ "genre": { "contains": "grunge" }
136136+ }
236137}
237138```
238139239239-**Response**:
240240-241241-```json Code
140140+**Response:**
141141+```json
242142{
243143 "success": true,
244244- "collections": ["com.recordcollector.album", "com.recordcollector.review"],
245245- "collectionStats": [
246246- {
247247- "collection": "com.recordcollector.album",
248248- "recordCount": 427,
249249- "uniqueActors": 23
250250- }
251251- ],
252252- "totalLexicons": 5,
253253- "totalRecords": 500,
254254- "totalActors": 25,
255255- "message": "Statistics retrieved successfully"
144144+ "count": 42,
145145+ "message": "Count retrieved successfully"
256146}
257147```
258148259259-### `network.slices.slice.listSliceRecords`
149149+# **{collection}.createRecord**
260150261261-List records across multiple collections in a slice.
151151+> Create a new record.
262152263263-**Method**: POST
264264-265265-**Body**:
153153+**Method:** `POST`
154154+**Endpoint:** `/xrpc/{collection}.createRecord`
155155+**Example:** `/xrpc/com.recordcollector.album.createRecord`
156156+**Authentication:** Required
266157267267-```json Code
158158+**Request Body:**
159159+```json
268160{
269269- "slice": "at://your-slice-uri",
270270- "collections": ["com.recordcollector.album", "com.recordcollector.review"],
271271- "authors": ["did:plc:optional-filter"],
272272- "limit": 20,
273273- "cursor": "pagination-cursor"
161161+ "slice": "at://did:plc:abc123/network.slices.slice/xyz789",
162162+ "rkey": "optional-custom-key",
163163+ "record": {
164164+ "$type": "com.recordcollector.album",
165165+ "title": "In Utero",
166166+ "artist": "Nirvana",
167167+ "releaseDate": "1993-09-21T00:00:00.000Z",
168168+ "genre": ["grunge", "alternative rock"],
169169+ "condition": "Very Good Plus",
170170+ "notes": "Some light wear on sleeve"
171171+ }
274172}
275173```
276174277277-**Response**:
175175+**Parameters:**
176176+- `slice` (string, required): The slice URI
177177+- `rkey` (string, optional): Custom record key (auto-generated if omitted)
178178+- `record` (object, required): The record data matching your lexicon schema
278179279279-```json Code
180180+**Response:**
181181+```json
280182{
281281- "success": true,
282282- "records": [
283283- {
284284- "uri": "at://did:plc:abc/com.recordcollector.album/xyz",
285285- "cid": "bafyrei...",
286286- "did": "did:plc:abc",
287287- "collection": "com.recordcollector.album",
288288- "value": {/* record data */},
289289- "indexedAt": "2024-01-01T00:00:00Z"
290290- }
291291- ],
292292- "cursor": "next-page-cursor"
183183+ "uri": "at://did:plc:user123/com.recordcollector.album/3abc456def",
184184+ "cid": "bafyreihj7x5legnfznufuopl4sg4knzc2cof6duas4b3q2fy6swua"
293185}
294186```
295187296296-### `network.slices.slice.searchSliceRecords`
297297-298298-Search records across multiple collections in a slice by content.
188188+# **{collection}.updateRecord**
299189300300-**Method**: POST
190190+> Update an existing record.
301191302302-**Body**:
192192+**Method:** `POST`
193193+**Endpoint:** `/xrpc/{collection}.updateRecord`
194194+**Example:** `/xrpc/com.recordcollector.album.updateRecord`
195195+**Authentication:** Required
303196304304-```json Code
197197+**Request Body:**
198198+```json
305199{
306306- "slice": "at://your-slice-uri",
307307- "collections": ["com.recordcollector.album", "com.recordcollector.review"],
308308- "search": "search term",
309309- "authors": ["did:plc:optional-filter"],
310310- "limit": 20,
311311- "cursor": "pagination-cursor"
200200+ "slice": "at://did:plc:abc123/network.slices.slice/xyz789",
201201+ "rkey": "3abc456def",
202202+ "record": {
203203+ "$type": "com.recordcollector.album",
204204+ "title": "In Utero",
205205+ "artist": "Nirvana",
206206+ "releaseDate": "1993-09-21T00:00:00.000Z",
207207+ "genre": ["grunge", "alternative rock", "noise rock"],
208208+ "condition": "Very Good",
209209+ "notes": "Updated: slight ring wear visible, plays perfectly"
210210+ }
312211}
313212```
314213315315-**Response**:
214214+**Parameters:**
215215+- `slice` (string, required): The slice URI
216216+- `rkey` (string, required): The record key to update
217217+- `record` (object, required): The complete updated record data
316218317317-```json Code
219219+**Response:**
220220+```json
318221{
319319- "success": true,
320320- "records": [
321321- {
322322- "uri": "at://did:plc:abc/com.recordcollector.album/xyz",
323323- "cid": "bafyrei...",
324324- "did": "did:plc:abc",
325325- "collection": "com.recordcollector.album",
326326- "value": {/* record data */},
327327- "indexedAt": "2024-01-01T00:00:00Z"
328328- }
329329- ],
330330- "cursor": "next-page-cursor"
222222+ "uri": "at://did:plc:user123/com.recordcollector.album/3abc456def",
223223+ "cid": "bafyreiabc123legnfznufuopl4sg4knzc2cof6duas4b3q2fy6swua"
331224}
332225```
333226334334-### `network.slices.slice.syncUserCollections`
227227+# **{collection}.deleteRecord**
335228336336-Synchronously sync collections for the authenticated user.
229229+> Delete a record.
337230338338-**Method**: POST
339339-340340-**Authentication**: Required
341341-342342-**Body**:
231231+**Method:** `POST`
232232+**Endpoint:** `/xrpc/{collection}.deleteRecord`
233233+**Example:** `/xrpc/com.recordcollector.album.deleteRecord`
234234+**Authentication:** Required
343235344344-```json Code
236236+**Request Body:**
237237+```json
345238{
346346- "slice": "at://your-slice-uri",
347347- "timeoutSeconds": 30
239239+ "rkey": "3abc456def"
348240}
349241```
350242351351-**Response**:
352352-353353-```json Code
354354-{
355355- "success": true,
356356- "reposProcessed": 1,
357357- "recordsSynced": 45,
358358- "timedOut": false,
359359- "message": "Sync completed successfully"
360360-}
243243+**Response:**
244244+```json
245245+{}
361246```
362247363363-### `network.slices.slice.startSync`
248248+## Filtering
364249365365-Start an asynchronous bulk sync job.
250250+The `where` parameter supports powerful filtering:
366251367367-**Method**: POST
252252+### Filter Operators
368253369369-**Authentication**: Required
370370-371371-**Body**:
372372-373373-```json Code
374374-{
375375- "slice": "at://your-slice-uri",
376376- "collections": ["com.recordcollector.album"],
377377- "externalCollections": ["app.bsky.actor.profile"],
378378- "repos": ["did:plc:abc", "did:plc:xyz"],
379379- "limitPerRepo": 100
380380-}
254254+- **`eq`**: Exact match
255255+```json
256256+{ "condition": { "eq": "Mint" } }
381257```
382258383383-**Response**:
259259+- **`contains`**: Partial text match (case-insensitive)
260260+```json
261261+{ "artist": { "contains": "pearl jam" } }
262262+```
384263385385-```json Code
386386-{
387387- "success": true,
388388- "jobId": "job-uuid",
389389- "message": "Sync job started"
390390-}
264264+- **`in`**: Match any value in array
265265+```json
266266+{ "condition": { "in": ["Mint", "Near Mint", "Very Good Plus"] } }
391267```
392268393393-### `network.slices.slice.codegen`
269269+### Special Fields
394270395395-Generate TypeScript client code.
396396-397397-**Method**: POST
398398-399399-**Body**:
400400-401401-```json Code
402402-{
403403- "target": "typescript",
404404- "slice": "at://your-slice-uri"
405405-}
271271+- **`json`**: Search across all fields
272272+```json
273273+{ "json": { "contains": "nirvana" } }
406274```
407275408408-**Response**:
409409-410410-```json Code
276276+- **System fields**: Filter by record metadata
277277+```json
411278{
412412- "success": true,
413413- "generatedCode": "// Generated TypeScript client code..."
279279+ "did": { "eq": "did:plc:user123" },
280280+ "collection": { "eq": "com.recordcollector.album" },
281281+ "indexedAt": { "contains": "2024-03" }
414282}
415283```
416284417417-## Lexicon Management
285285+### Complex Filtering Examples
418286419419-### `network.slices.lexicon.getRecords`
420420-421421-Get lexicons in a slice.
422422-423423-**Method**: GET
424424-425425-**Parameters**: Same as collection.getRecords
426426-427427-### `network.slices.lexicon.countRecords`
428428-429429-Count lexicons in a slice.
430430-431431-**Method**: GET
432432-433433-**Parameters**: Same as collection.getRecords (except limit and cursor)
434434-435435-**Response**:
436436-437437-```json Code
287287+**Multiple conditions (AND logic):**
288288+```json
438289{
439439- "count": 10
290290+ "where": {
291291+ "genre": { "contains": "grunge" },
292292+ "condition": { "in": ["Mint", "Near Mint"] },
293293+ "releaseDate": { "contains": "1991" }
294294+ }
440295}
441296```
442297443443-### `network.slices.lexicon.createRecord`
444444-445445-Add a lexicon to a slice.
446446-447447-**Method**: POST
448448-449449-**Authentication**: Required
450450-451451-**Body**:
452452-453453-```json Code
298298+**Array field filtering:**
299299+```json
454300{
455455- "slice": "at://your-slice-uri",
456456- "record": {
457457- "$type": "network.slices.lexicon",
458458- "nsid": "com.recordcollector.album",
459459- "definitions": "{\"lexicon\": 1, ...}",
460460- "createdAt": "2024-01-01T00:00:00Z",
461461- "slice": "at://your-slice-uri"
301301+ "where": {
302302+ "genre": { "contains": "alternative" }
462303 }
463304}
464305```
465306466466-## Actor Management
467467-468468-### `network.slices.slice.getActors`
469469-470470-Get actors (users) in a slice.
471471-472472-**Method**: GET
473473-474474-**Parameters**:
475475-476476-- `slice` (string, required): Slice URI
477477-- `search` (string, optional): Search query
478478-- `dids` (string[], optional): Filter by DIDs
479479-- `limit` (number, optional): Maximum results
480480-- `cursor` (string, optional): Pagination cursor
307307+## Sorting
481308482482-**Response**:
309309+Sort results using the `sortBy` parameter:
483310484484-```json Code
311311+```json
485312{
486486- "actors": [
487487- {
488488- "did": "did:plc:abc",
489489- "handle": "user.bsky.social",
490490- "sliceUri": "at://slice-uri",
491491- "indexedAt": "2024-01-01T00:00:00Z"
492492- }
493493- ],
494494- "cursor": "next-page"
313313+ "sortBy": [
314314+ { "field": "releaseDate", "direction": "desc" },
315315+ { "field": "artist", "direction": "asc" }
316316+ ]
495317}
496318```
497319498498-## Blob Upload
320320+**Common sort patterns:**
321321+- Newest releases first: `[{ "field": "releaseDate", "direction": "desc" }]`
322322+- Alphabetical by artist: `[{ "field": "artist", "direction": "asc" }]`
323323+- By condition (best first): `[{ "field": "condition", "direction": "asc" }]`
324324+- Recently indexed: `[{ "field": "indexedAt", "direction": "desc" }]`
499325500500-### `com.atproto.repo.uploadBlob`
326326+## Pagination
501327502502-Upload a blob (image, file).
328328+Use cursor-based pagination for large result sets:
503329504504-**Method**: POST
330330+```javascript
331331+// First request
332332+const page1 = await fetch('/xrpc/com.recordcollector.album.getRecords', {
333333+ method: 'POST',
334334+ body: JSON.stringify({
335335+ slice: 'at://your-slice-uri',
336336+ limit: 20
337337+ })
338338+});
505339506506-**Authentication**: Required
507507-508508-**Headers**:
509509-510510-- `Content-Type`: MIME type of the blob
511511-512512-**Body**: Raw binary data
513513-514514-**Response**:
515515-516516-```json Code
517517-{
518518- "blob": {
519519- "$type": "blob",
520520- "ref": { "$link": "bafkrei..." },
521521- "mimeType": "image/jpeg",
522522- "size": 127198
523523- }
524524-}
340340+// Next page using cursor
341341+const page2 = await fetch('/xrpc/com.recordcollector.album.getRecords', {
342342+ method: 'POST',
343343+ body: JSON.stringify({
344344+ slice: 'at://your-slice-uri',
345345+ limit: 20,
346346+ cursor: page1.cursor
347347+ })
348348+});
525349```
526350527351## Error Responses
528352529529-All endpoints may return error responses:
353353+All endpoints return consistent error format:
530354531531-```json Code
355355+```json
532356{
533357 "error": "InvalidRequest",
534534- "message": "Detailed error message"
358358+ "message": "Missing required parameter: slice"
535359}
536360```
537361538538-Common HTTP status codes:
539539-362362+**Common HTTP Status Codes:**
540363- `200`: Success
541541-- `400`: Bad request
542542-- `401`: Authentication required
543543-- `403`: Forbidden
544544-- `404`: Not found
364364+- `400`: Bad request (invalid parameters)
365365+- `401`: Unauthorized (missing/invalid auth token)
366366+- `403`: Forbidden (insufficient permissions)
367367+- `404`: Not found (record/collection doesn't exist)
545368- `500`: Internal server error
546369547547-## Pagination
370370+## OpenAPI Specification
548371549549-List endpoints support cursor-based pagination:
550550-551551-1. Make initial request without cursor
552552-2. Use returned cursor for next page
553553-3. Continue until no cursor returned
372372+Get the OpenAPI spec for your slice:
554373555555-Example:
374374+**Method:** `GET`
375375+**Endpoint:** `/xrpc/network.slices.slice.openapi`
556376557557-```javascript
558558-let cursor = undefined;
559559-do {
560560- const response = await fetch(`/xrpc/collection.getRecords?cursor=${cursor}`);
561561- const data = await response.json();
562562- // Process records
563563- cursor = data.cursor;
564564-} while (cursor);
565565-```
377377+**Query Parameters:**
378378+- `slice` (string, required): The slice URI
566379567567-## Filtering
380380+**Response:** OpenAPI 3.0 specification with all available endpoints
568381569569-List endpoints support filtering using the `where` parameter with field-specific
570570-query operators:
382382+## Example: Record Collector Application
571383572572-### Filter Operators
384384+Here's a complete example using the record collector lexicon:
573385574574-- `eq`: Exact match
575575-- `contains`: Partial text match (case-insensitive)
576576-- `in`: Match any value in array
577577-578578-### Examples
579579-580580-**Exact match filtering:**
581581-582582-```json Code
386386+### Lexicon Definition
387387+```json
583388{
584584- "where": {
585585- "artist": { "eq": "Nirvana" },
586586- "condition": { "eq": "Mint" }
389389+ "lexicon": 1,
390390+ "id": "com.recordcollector.album",
391391+ "defs": {
392392+ "main": {
393393+ "type": "record",
394394+ "record": {
395395+ "type": "object",
396396+ "required": ["title", "artist", "releaseDate"],
397397+ "properties": {
398398+ "title": { "type": "string", "description": "Album title" },
399399+ "artist": { "type": "string", "description": "Artist or band name" },
400400+ "releaseDate": {
401401+ "type": "string",
402402+ "format": "datetime",
403403+ "description": "Original release date"
404404+ },
405405+ "genre": {
406406+ "type": "array",
407407+ "items": { "type": "string" },
408408+ "description": "Music genres"
409409+ },
410410+ "condition": {
411411+ "type": "string",
412412+ "description": "Vinyl condition (Mint, Near Mint, etc.)"
413413+ },
414414+ "notes": {
415415+ "type": "string",
416416+ "description": "Collector notes"
417417+ }
418418+ }
419419+ }
420420+ }
587421 }
588422}
589423```
590424591591-**Text search filtering:**
592592-593593-```json Code
594594-{
595595- "where": {
596596- "title": { "contains": "nevermind" },
597597- "genre": { "contains": "grunge" }
598598- }
599599-}
425425+### List Albums by Genre
426426+```bash
427427+curl -X POST "https://api.slices.network/xrpc/com.recordcollector.album.getRecords" \
428428+ -H "Content-Type: application/json" \
429429+ -d '{
430430+ "slice": "at://did:plc:abc123/network.slices.slice/xyz789",
431431+ "where": { "genre": { "contains": "grunge" } },
432432+ "sortBy": [{ "field": "releaseDate", "direction": "desc" }],
433433+ "limit": 10
434434+ }'
600435```
601436602602-**Array filtering:**
603603-604604-```json Code
605605-{
606606- "where": {
607607- "condition": { "in": ["Mint", "Near Mint", "Very Good Plus"] },
608608- "artist": { "in": ["Nirvana", "Pearl Jam", "Soundgarden"] }
609609- }
610610-}
437437+### Add New Album to Collection
438438+```bash
439439+curl -X POST "https://api.slices.network/xrpc/com.recordcollector.album.createRecord" \
440440+ -H "Authorization: Bearer YOUR_TOKEN" \
441441+ -H "Content-Type: application/json" \
442442+ -d '{
443443+ "slice": "at://did:plc:abc123/network.slices.slice/xyz789",
444444+ "record": {
445445+ "$type": "com.recordcollector.album",
446446+ "title": "Superunknown",
447447+ "artist": "Soundgarden",
448448+ "releaseDate": "1994-03-08T00:00:00.000Z",
449449+ "genre": ["grunge", "alternative metal"],
450450+ "condition": "Near Mint",
451451+ "notes": "Limited edition orange vinyl"
452452+ }
453453+ }'
611454```
612455613613-**Global search across all fields:**
614614-615615-```json Code
616616-{
617617- "where": {
618618- "json": { "contains": "grunge" }
619619- }
620620-}
456456+### Search Collection by Condition
457457+```bash
458458+curl -X POST "https://api.slices.network/xrpc/com.recordcollector.album.getRecords" \
459459+ -H "Content-Type: application/json" \
460460+ -d '{
461461+ "slice": "at://did:plc:abc123/network.slices.slice/xyz789",
462462+ "where": {
463463+ "condition": { "in": ["Mint", "Near Mint"] },
464464+ "releaseDate": { "contains": "199" }
465465+ },
466466+ "sortBy": [{ "field": "artist", "direction": "asc" }]
467467+ }'
621468```
622469623623-## Sorting
470470+### Count Albums in Collection
471471+```bash
472472+curl -X POST "https://api.slices.network/xrpc/com.recordcollector.album.countRecords" \
473473+ -H "Content-Type: application/json" \
474474+ -d '{
475475+ "slice": "at://did:plc:abc123/network.slices.slice/xyz789",
476476+ "where": { "condition": { "eq": "Mint" } }
477477+ }'
478478+```
624479625625-Sort parameter uses an array format with field and direction:
626626-627627-```json Code
628628-{
629629- "sortBy": [
630630- { "field": "releaseDate", "direction": "desc" },
631631- { "field": "title", "direction": "asc" }
632632- ]
633633-}
480480+### Update Album Condition
481481+```bash
482482+curl -X POST "https://api.slices.network/xrpc/com.recordcollector.album.updateRecord" \
483483+ -H "Authorization: Bearer YOUR_TOKEN" \
484484+ -H "Content-Type: application/json" \
485485+ -d '{
486486+ "slice": "at://did:plc:abc123/network.slices.slice/xyz789",
487487+ "rkey": "3abc456def",
488488+ "record": {
489489+ "$type": "com.recordcollector.album",
490490+ "title": "Ten",
491491+ "artist": "Pearl Jam",
492492+ "releaseDate": "1991-08-27T00:00:00.000Z",
493493+ "genre": ["grunge", "alternative rock"],
494494+ "condition": "Very Good",
495495+ "notes": "Updated after closer inspection - slight scuffs on Side B"
496496+ }
497497+ }'
634498```
635499636636-Examples:
637637-638638-- `[{ "field": "releaseDate", "direction": "desc" }]` - Newest releases first
639639-- `[{ "field": "artist", "direction": "asc" }]` - Alphabetical by artist
640640-- `[{ "field": "releaseDate", "direction": "desc" }, { "field": "title", "direction": "asc" }]` -
641641- Newest first, then alphabetical by title
642642-643500## Next Steps
644501645502- [SDK Usage](./sdk-usage.md) - Using generated TypeScript clients
646503- [Getting Started](./getting-started.md) - Build your first application
647647-- [Concepts](./concepts.md) - Understand the architecture
504504+- [Concepts](./concepts.md) - Understand the architecture
+285-216
docs/concepts.md
···11# Core Concepts
2233-Understanding these core concepts will help you effectively use Slices.
33+Slices is built on four fundamental concepts that work together to create a
44+powerful AT Protocol development platform.
55+66+```
77+Slices → Lexicons → Sync → APIs
88+(Container) → (Schema) → (Data) → (Access)
99+```
1010+1111+## **Slices**: Your Data Universe
1212+1313+### What is a Slice?
1414+1515+A slice is your own independent data space within the AT Protocol network. Think
1616+of it as a database with built-in APIs, authentication, and real-time sync, all
1717+isolated from other slices.
41855-## Slices
1919+### Why Slices Matter
2020+2121+In the AT Protocol ecosystem, data lives on Personal Data Servers (PDS)
2222+scattered across the network. Slices acts as an **AppView** that:
2323+2424+- Aggregates data from across the network
2525+- Applies your custom schemas
2626+- Provides instant APIs for your data
2727+- Maintains complete data isolation
2828+2929+### Creating Your Slice
3030+3131+When you create a slice for `com.recordcollector`:
3232+3333+```
3434+Slice Name: My Vinyl Collection
3535+Domain: com.recordcollector
3636+URI: at://did:plc:abc123/network.slices.slice/xyz789
3737+```
63877-A slice is an independent appview within the AT Protocol ecosystem. Think of it
88-as your own data universe with custom schemas and records.
3939+This slice becomes the container for all your vinyl collection data, completely
4040+isolated from other slices.
9411042### Key Properties
11431212-- **URI**: Unique AT Protocol URI (e.g.,
1313- `at://did:plc:abc123/network.slices.slice/3xyz`)
1414-- **Name**: Human-readable identifier
1515-- **Domain**: Namespace for lexicons (e.g., `com.example`, `social.grain`)
1616-- **Creation Date**: When the slice was created
4444+- **URI**: Unique AT Protocol identifier
4545+- **Domain**: Your namespace (e.g., `com.recordcollector`)
4646+- **Isolation**: Complete data separation between slices
4747+- **Multi-tenancy**: Multiple users can have data in the same slice
17481818-### Slice Isolation
4949+### Example: Record Collector Slice
19502020-Each slice maintains complete data isolation:
5151+```json
5252+{
5353+ "uri": "at://did:plc:abc123/network.slices.slice/xyz789",
5454+ "name": "Vinyl Collection Manager",
5555+ "domain": "com.recordcollector",
5656+ "createdAt": "2024-01-15T10:00:00Z"
5757+}
5858+```
21592222-- Records are filtered by slice URI in all queries
2323-- Sync operations respect slice boundaries
2424-- Statistics are calculated per-slice
2525-- Users can have different data in different slices
6060+## **Lexicons**: Data Schemas
26612727-## Lexicons
6262+### What are Lexicons?
28632929-Lexicons are JSON schemas that define record types in AT Protocol. They specify
3030-the structure, validation rules, and metadata for records.
6464+Lexicons are JSON schemas that define the structure of your data in AT Protocol.
6565+They're like database tables with built-in validation. In Slices, these lexicons
6666+automatically generate APIs for you.
31673232-### Lexicon Structure
6868+### Defining Your Data
33693434-```json Code
7070+For a record collector app, you might define an album lexicon:
7171+7272+```json
3573{
3674 "lexicon": 1,
3775 "id": "com.recordcollector.album",
3876 "defs": {
3977 "main": {
4078 "type": "record",
4141- "description": "A vinyl album record",
7979+ "description": "A vinyl album in the collection",
4280 "record": {
4381 "type": "object",
8282+ "required": ["title", "artist", "releaseDate"],
4483 "properties": {
4545- "title": { "type": "string" },
4646- "artist": { "type": "string" },
4747- "releaseDate": { "type": "string", "format": "datetime" },
4848- "condition": { "type": "string" }
4949- },
5050- "required": ["title", "artist"]
8484+ "title": {
8585+ "type": "string",
8686+ "description": "Album title"
8787+ },
8888+ "artist": {
8989+ "type": "string",
9090+ "description": "Artist or band name"
9191+ },
9292+ "releaseDate": {
9393+ "type": "string",
9494+ "format": "datetime",
9595+ "description": "Original release date"
9696+ },
9797+ "genre": {
9898+ "type": "array",
9999+ "items": { "type": "string" },
100100+ "description": "Musical genres"
101101+ },
102102+ "condition": {
103103+ "type": "string",
104104+ "enum": [
105105+ "Mint",
106106+ "Near Mint",
107107+ "Very Good Plus",
108108+ "Very Good",
109109+ "Good",
110110+ "Fair",
111111+ "Poor"
112112+ ],
113113+ "description": "Vinyl condition grading"
114114+ },
115115+ "notes": {
116116+ "type": "string",
117117+ "maxLength": 1000,
118118+ "description": "Collector's notes"
119119+ }
120120+ }
51121 }
52122 }
53123 }
54124}
55125```
561265757-### Supported Types
127127+### Primary vs External Lexicons
581285959-- **Primitives**: string, number, integer, boolean
6060-- **Complex**: object, array, union, ref
6161-- **Special**: blob (for media), cid-link, at-uri
6262-- **Formats**: datetime, at-identifier, did, handle
129129+**Primary Lexicons**: Match your slice domain:
631306464-### Lexicon Namespacing
131131+- `com.recordcollector.album`
132132+- `com.recordcollector.review`
133133+- `com.recordcollector.wishlist`
651346666-Lexicons follow reverse domain naming:
135135+**External Lexicons**: From other namespaces:
671366868-- `com.recordcollector.album` - An album in the recordcollector.com namespace
6969-- `com.recordcollector.review` - A vinyl review record
7070-- `network.slices.slice` - Core slice record type
7171-- `app.bsky.actor.profile` - Bluesky profile (external)
137137+- `app.bsky.actor.profile` (Bluesky profiles)
138138+- `sh.tangled.repo` (Tangled Repo)
139139+- `social.grain.photo` (Grain Photos)
140140+- `com.cassettecollector.tape` (Cassette Collector)
721417373-## Collections
142142+### Automatic API Generation
741437575-Collections are groups of records with the same lexicon type. They map directly
7676-to XRPC endpoints.
144144+Each lexicon with `type: "record"` automatically creates endpoints. Record types
145145+are the only lexicons that generate CRUD APIs:
771467878-### Primary Collections
147147+```
148148+com.recordcollector.album (type: "record") →
149149+ /xrpc/com.recordcollector.album.getRecords
150150+ /xrpc/com.recordcollector.album.createRecord
151151+ /xrpc/com.recordcollector.album.updateRecord
152152+ /xrpc/com.recordcollector.album.deleteRecord
153153+```
791548080-Collections that match your slice's domain namespace. For example, if your slice
8181-domain is `com.recordcollector`, then `com.recordcollector.album` would be a
8282-primary collection.
155155+Note: Other lexicon types (like `query`, `procedure`, or `subscription`) serve
156156+different purposes and don't create these standard CRUD endpoints.
831578484-### External Collections
158158+## **Sync**: Data Flow
851598686-Collections from other namespaces that you've synced into your slice. For
8787-example:
160160+### How Data Enters Your Slice
881618989-- Bluesky profiles (`app.bsky.actor.profile`)
9090-- Bluesky posts (`app.bsky.feed.post`)
9191-- Collections from other slices
162162+The sync engine manages how data flows into your slice from the AT Protocol
163163+network.
921649393-### Collection Operations
165165+### Three Sync Strategies
941669595-Both primary and external collections support the same operations:
167167+#### 1. Bulk Sync: Historical Import
961689797-- `*.getRecords` - Get records with pagination, filtering, and search
9898-- `*.getRecord` - Get single record by URI
9999-- `*.createRecord` - Create new record
100100-- `*.updateRecord` - Update existing record
101101-- `*.deleteRecord` - Remove record
102102-- `*.countRecords` - Count records with filtering
169169+Perfect for initial data loading or periodic updates. When you're first starting
170170+out, you might only be syncing your own records from your PDS. But as more
171171+people adopt your app and write records to their own PDSs, you can sync from
172172+their repositories too, growing your network.
103173104104-The key difference is conceptual: primary collections are "native" to your
105105-slice's domain, while external collections are imported from other namespaces.
174174+Specify:
106175107107-## Records
176176+- Collections to sync (e.g., `com.recordcollector.album`)
177177+- External collections (e.g., `app.bsky.actor.profile` for user profiles)
178178+- Specific repositories (DIDs) to import from
108179109109-Records are individual data items stored in collections.
180180+#### 2. User Sync: On-Demand
110181111111-### Record Properties
182182+Automatically syncs when users log in. This is primarily for discovering and
183183+syncing external collections. When a new user authenticates for the first time,
184184+they become an actor in your slice, allowing you to discover what external
185185+collections they have (like Bluesky profiles or posts) and sync that data from
186186+their PDS.
112187113113-- **URI**: Unique AT Protocol URI
114114-- **CID**: Content identifier (hash)
115115-- **DID**: Owner's decentralized identifier
116116-- **Collection**: Lexicon type
117117-- **Value**: Actual record data
118118-- **IndexedAt**: When record was indexed
188188+#### 3. Jetstream: Real-Time Updates
119189120120-### Record Keys (rkeys)
190190+Connects to the AT Protocol firehose for live updates. This tracks create,
191191+update, and delete events as they happen across the network.
121192122122-Records use keys for identification:
193193+```
194194+Firehose Event → Filter by Collection → Validate → Store in Slice
195195+```
123196124124-- **Self**: Special key for singleton records (e.g., profiles)
125125-- **TID**: Timestamp-based identifiers
126126-- **Custom**: User-defined keys
197197+### Syncing External Data
127198128128-### Record Lifecycle
199199+Import Bluesky profiles to show who owns each album and their Bluesky avatar.
200200+You can sync external collections like `app.bsky.actor.profile` to enrich your
201201+slice with user information from the broader AT Protocol network.
129202130130-1. **Creation**: Via API or sync
131131-2. **Indexing**: Stored in PostgreSQL
132132-3. **Updates**: New versions with new CIDs
133133-4. **Deletion**: Soft or hard delete
203203+### Performance Features
134204135135-## Sync Engine
205205+- **CID Deduplication**: Skips unchanged records
206206+- **Bulk Operations**: Processes thousands of records efficiently
207207+- **Actor Caching**: Reduces database lookups
208208+- **Auto-Recovery**: Handles network interruptions
136209137137-The sync engine imports AT Protocol data into your slice using multiple
138138-strategies for optimal performance and reliability.
210210+### Example: Syncing a Vinyl Community
139211140140-### Sync Types
212212+To sync all albums from a vinyl collecting community, you would:
141213142142-**Bulk Sync**: One-time import of historical data
214214+- List primary collections (`com.recordcollector.album`,
215215+ `com.recordcollector.review`)
216216+- Include external collections (`app.bsky.actor.profile`) to show collector
217217+ information
143218144144-- Specify collections to sync
145145-- Filter by repositories (DIDs)
146146-- Set limits per repository
147147-- Uses optimized bulk database operations
219219+## **Code Generation & XRPC Endpoints**: APIs & SDKs
148220149149-**User Sync**: Sync data for authenticated user
221221+### Dynamic API Creation
150222151151-- Automatic on login
152152-- Timeout protection (30 seconds default)
153153-- External collection discovery
154154-- Synchronous operation for immediate feedback
223223+Every record-type lexicon automatically generates REST-like XRPC endpoints.
155224156156-**Jetstream Sync**: Real-time updates via WebSocket
225225+### Generated Endpoints
157226158158-- Subscribe to AT Protocol firehose
159159-- Filter relevant events by slice collections
160160-- Automatic record updates and deletions
161161-- Built-in reconnection with exponential backoff
227227+For `com.recordcollector.album`:
162228163163-### Sync Process
229229+#### List Albums
164230165165-1. **Discovery**: Find available records via AT Protocol relay
166166-2. **Filtering**: Apply slice and collection filters
167167-3. **Validation**: Check lexicon compliance against slice schemas
168168-4. **Storage**: Index in database using bulk operations
169169-5. **Deduplication**: Skip existing records (by CID comparison)
231231+```http
232232+POST /xrpc/com.recordcollector.album.getRecords
233233+{
234234+ "slice": "at://your-slice-uri",
235235+ "where": { "genre": { "contains": "jazz" } },
236236+ "sortBy": [{ "field": "releaseDate", "direction": "desc" }],
237237+ "limit": 20
238238+}
239239+```
170240171171-### Performance Optimizations
241241+#### Add Album
172242173173-**CID-Based Deduplication**
243243+```http
244244+POST /xrpc/com.recordcollector.album.createRecord
245245+Authorization: Bearer YOUR_TOKEN
246246+{
247247+ "slice": "at://your-slice-uri",
248248+ "record": {
249249+ "title": "Kind of Blue",
250250+ "artist": "Miles Davis",
251251+ "releaseDate": "1959-08-17T00:00:00Z",
252252+ "genre": ["jazz", "modal jazz"],
253253+ "condition": "Very Good Plus"
254254+ }
255255+}
256256+```
174257175175-- Compare Content Identifiers (CIDs) before processing
176176-- Skip records that haven't changed since last sync
177177-- Reduces unnecessary database operations and validation overhead
258258+### TypeScript SDK Generation
178259179179-**Actor Caching**
260260+Slices generates a fully-typed TypeScript client:
180261181181-- Pre-load actor lookup cache to avoid database hits during Jetstream processing
182182-- Cache (DID, slice_uri) mappings for external collection filtering
183183-- Periodic cache refresh every 5 minutes
262262+```typescript
263263+// Generated SDK with full type safety
264264+import { AtprotoClient } from "./generated_client.ts";
184265185185-### Jetstream Reliability
266266+const client = new AtprotoClient({
267267+ baseUrl: "https://api.slices.network",
268268+ sliceUri: "at://your-slice-uri",
269269+ auth: oauthClient,
270270+});
186271187187-**Automatic Recovery**
188188-189189-- Infinite retry loop with exponential backoff (5 seconds → 5 minutes max)
190190-- Fresh consumer instance creation on each retry
191191-- Database connectivity monitoring and recovery
192192-- Connection status tracking via atomic flags
193193-194194-**Error Handling**
195195-196196-- Graceful degradation when database connections fail
197197-- Validation fallback with fresh lexicon loading from database
198198-- Separate error handling for primary vs external collections
199199-200200-**Configuration Reloading**
201201-202202-- Automatic slice configuration refresh every 5 minutes
203203-- Dynamic collection filtering based on slice lexicons
204204-- Actor cache updates to reflect new slice membership
205205-206206-## XRPC Handlers
207207-208208-XRPC (Cross-Protocol Remote Procedure Call) handlers provide the API layer.
209209-210210-### Dynamic Handlers
211211-212212-Automatically generated from lexicons:
213213-214214-- No manual endpoint creation
215215-- Type-safe request/response
216216-- Automatic validation
217217-- OAuth integration
272272+// Fully typed operations
273273+const albums = await client.com.recordcollector.album.getRecords({
274274+ where: {
275275+ condition: { in: ["Mint", "Near Mint"] },
276276+ genre: { contains: "jazz" },
277277+ },
278278+ sortBy: [{ field: "artist", direction: "asc" }],
279279+ limit: 50,
280280+});
218281219219-### Core Handlers
220220-221221-Built-in endpoints for slice management:
282282+// Type-safe record creation
283283+const newAlbum = await client.com.recordcollector.album.createRecord({
284284+ title: "Blue Train",
285285+ artist: "John Coltrane",
286286+ releaseDate: "1958-01-01T00:00:00Z",
287287+ genre: ["jazz", "hard bop"],
288288+ condition: "Near Mint",
289289+ notes: "Original Blue Note pressing",
290290+});
291291+```
222292223223-- `network.slices.slice.stats` - Slice statistics
224224-- `network.slices.slice.records` - Browse records
225225-- `network.slices.slice.codegen` - Generate SDKs
226226-- `network.slices.slice.sync` - Trigger sync
227227-228228-### Handler Authentication
229229-230230-- **Read Operations**: Optional auth (public by default)
231231-- **Write Operations**: Require OAuth tokens
232232-- **Admin Operations**: Require slice ownership
293293+### OAuth Integration
233294234234-## Generated SDKs
295295+Built-in OAuth 2.0 with PKCE:
235296236236-Type-safe client libraries generated from lexicons.
297297+- Read operations: Public by default
298298+- Write operations: Require authentication
299299+- Automatic token refresh
300300+- Secure session management
237301238302### SDK Features
239303240240-- **Type Safety**: Full TypeScript types
241241-- **Nested Structure**: Matches lexicon namespacing
242242-- **OAuth Integration**: Automatic token handling
243243-- **Error Handling**: Retry logic and graceful failures
304304+- **Type Safety**: Full TypeScript types from lexicons
305305+- **Nested APIs**: `client.com.recordcollector.album.*`
306306+- **Error Handling**: Automatic retries and graceful failures
307307+- **Blob Support**: Handle images and media files
244308245245-### SDK Generation Process
309309+## How It All Works Together
246310247247-1. Parse slice lexicons
248248-2. Generate TypeScript interfaces
249249-3. Create client classes
250250-4. Add utility functions
251251-5. Format and validate
311311+```
312312+1. Create Slice → Define namespace (com.recordcollector)
313313+ ↓
314314+2. Add Lexicons → Define data structure (album, review, wishlist)
315315+ ↓
316316+3. Sync Data → Import existing vinyl collections
317317+ → Subscribe to real-time updates
318318+ ↓
319319+4. Use APIs → Generated endpoints for all operations
320320+ → Type-safe SDK for your app
321321+```
252322253253-### Using Generated SDKs
323323+## Practical Example: Building a Vinyl Collector App
254324255255-```typescript Code
256256-// Initialize client
257257-const client = new AtProtoClient(apiUrl, sliceUri, oauthClient);
325325+### Step 1: Create Your Slice
258326259259-// Use nested structure matching lexicons
260260-await client.com.recordcollector.album.getRecords();
261261-await client.app.bsky.actor.profile.getRecord({ uri });
262262-```
327327+Create a slice using the Slices CLI or web interface:
263328264264-## Authentication
329329+- Name: "Vintage Vinyl Collectors"
330330+- Domain: "com.recordcollector"
265331266266-OAuth 2.0 with PKCE for secure authentication.
332332+### Step 2: Define Lexicons
267333268268-### OAuth Flow
334334+Upload your lexicon definitions through the web UI or CLI:
269335270270-1. **Authorization**: Redirect to AT Protocol provider
271271-2. **Callback**: Exchange code for tokens
272272-3. **Token Storage**: Secure client-side storage
273273-4. **Refresh**: Automatic token renewal
336336+- Album schema (`com.recordcollector.album`)
337337+- Review schema (`com.recordcollector.review`)
338338+- Wishlist schema (`com.recordcollector.wishlist`)
274339275275-### Session Management
340340+### Step 3: Sync Existing Data
276341277277-- Encrypted cookies for web sessions
278278-- Token refresh before expiration
279279-- Graceful degradation for read-only access
342342+Start a sync job to import your existing collection:
280343281281-## Blob Handling
344344+- Use the Sync tab in the web UI
345345+- Select collections to sync
346346+- Specify repositories (or leave empty for all)
282347283283-Media files use blob references with CDN URLs.
348348+### Step 4: Build Your App
284349285285-### Blob Structure
350350+```javascript
351351+// Use the generated SDK
352352+const client = new AtprotoClient(slice.uri);
286353287287-```json Code
288288-{
289289- "$type": "blob",
290290- "ref": { "$link": "bafkreig5bcb..." },
291291- "mimeType": "image/jpeg",
292292- "size": 127198
293293-}
294294-```
295295-296296-### CDN URL Generation
297297-298298-Convert blob references to CDN URLs using Bluesky's CDN:
354354+// List all jazz albums in mint condition
355355+const jazzMint = await client.com.recordcollector.album.getRecords({
356356+ where: {
357357+ genre: { contains: "jazz" },
358358+ condition: { eq: "Mint" },
359359+ },
360360+});
299361300300-```typescript Code
301301-recordBlobToCdnUrl(record, blobRef, "avatar");
302302-// -> https://cdn.bsky.app/img/avatar/plain/did:plc:abc/bafkrei...@jpeg
362362+// Add a new album to the collection
363363+await client.com.recordcollector.album.createRecord({
364364+ title: "A Love Supreme",
365365+ artist: "John Coltrane",
366366+ releaseDate: "1965-02-01T00:00:00Z",
367367+ genre: ["jazz", "spiritual jazz"],
368368+ condition: "Near Mint",
369369+});
303370```
304371305305-### Bluesky CDN Presets
372372+## Summary
306373307307-- `avatar` - Profile pictures
308308-- `banner` - Cover images
309309-- `feed_thumbnail` - Small previews
310310-- `feed_fullsize` - Full resolution
374374+| Concept | Purpose | Record Collector Example |
375375+| ------------ | ------------------------------- | ----------------------------------------------------------- |
376376+| **Slices** | Isolated data container | `com.recordcollector` namespace with all your vinyl data |
377377+| **Lexicons** | Schema definitions | `album`, `review`, `wishlist` record types |
378378+| **Sync** | Data import & real-time updates | Import collections from network, live updates via Jetstream |
379379+| **Code Gen** | Auto-generated APIs & SDKs | TypeScript client with `getRecords`, `createRecord`, etc. |
311380312381## Next Steps
313382314314-- [API Reference](./api-reference.md) - Detailed endpoint documentation
315315-- [SDK Usage](./sdk-usage.md) - Advanced client patterns
316316-- [Getting Started](./getting-started.md) - Build your first slice
383383+- [Getting Started](./getting-started.md): Create your first slice
384384+- [API Reference](./api-reference.md): Detailed endpoint documentation
385385+- [SDK Usage](./sdk-usage.md): Advanced SDK patterns
-23
docs/deployment.md
···11-# Deployment Guide
22-33-Documentation for deploying Slices to production is coming soon.
44-55-## Basic Requirements
66-77-- [AIP server](https://github.com/graze-social/aip) running and accessible
88-- Registered OAuth client with AIP
99-- PostgreSQL database
1010-1111-## Environment Setup
1212-1313-See the [environment variables](../README.md#environment-variables) section in the README for required configuration.
1414-1515-## Docker
1616-1717-Docker images can be built using the provided Dockerfile in each directory.
1818-1919-## More Information
2020-2121-For now, refer to:
2222-- [Getting Started](./getting-started.md) for local setup
2323-- [README](../README.md) for environment configuration
+379-69
docs/getting-started.md
···11-# Getting Started with Slices
11+# Getting Started
2233-This guide will help you set up Slices and create your first slice.
33+Build your first AT Protocol app in minutes with the Slices CLI.
4455-## Creating Your First Slice
55+## Quick Start
6677-### 1. Log In
77+### Install the CLI
8899-Click "Login" and authenticate with your AT Protocol account.
99+You'll need Deno installed first. Get it at [deno.com](https://deno.com/).
10101111-### 2. Create a Slice
1111+```bash
1212+# Install from JSR
1313+deno install -g jsr:@slices/cli --name slices
1414+```
12151313-Click "Create Slice" and provide:
1616+### Create Your Project
14171515-- **Name**: A friendly name for your slice
1616-- **Domain**: Your namespace (e.g., `com.recordcollector`)
1818+```bash
1919+# Create a new project with automatic setup
2020+slices init my-vinyl-app
17211818-### 3. Define a Lexicon
2222+# Or let us generate a name/domain for you
2323+slices init
2424+```
19252020-Navigate to your slice and go to the Lexicon tab. Create a lexicon for your
2121-first record type:
2626+The `slices init` command does everything for you:
22272323-```json Code
2828+- Creates a full-stack Deno app with OAuth authentication
2929+- Automatically creates a slice on the network
3030+- Sets up OAuth credentials
3131+- Pulls standard lexicons (including Bluesky profiles)
3232+- Generates a TypeScript SDK
3333+- Initializes a git repository
3434+3535+### Start Developing
3636+3737+```bash
3838+cd my-vinyl-app
3939+deno task dev
4040+```
4141+4242+Visit http://localhost:8080 and you're live!
4343+4444+## What You Get
4545+4646+The `slices init` command creates a production-ready app with:
4747+4848+> More templates/examples coming soon (i.e. React, Expo, Astro, etc)
4949+5050+### Full-Stack Deno Application
5151+5252+- **Server-side rendering** with Preact and JSX
5353+- **OAuth authentication** with PKCE flow and automatic token refresh
5454+- **HTMX integration** for dynamic UI without complex JavaScript
5555+- **Tailwind CSS** for beautiful, responsive styling
5656+- **SQLite sessions** for secure session management
5757+- **Feature-based architecture** for scalable code organization
5858+5959+### AT Protocol Integration
6060+6161+- **Configured slice** with your own namespace
6262+- **Generated TypeScript SDK** from your lexicons
6363+- **Automatic sync** capabilities
6464+- **Real-time updates** via Jetstream
6565+6666+### Development Experience
6767+6868+- **Hot reload** in development
6969+- **Type safety** throughout
7070+- **Environment variables** pre-configured
7171+- **Git repository** initialized
7272+7373+## Project Structure
7474+7575+Here's what the generated project structure looks like:
7676+7777+```
7878+my-vinyl-app/
7979+├── slices.json # Slice configuration
8080+├── .env # Your credentials (auto-generated)
8181+├── deno.json # Deno configuration
8282+├── lexicons/ # AT Protocol schemas
8383+│ ├── com/
8484+│ │ └── recordcollector/
8585+│ │ └── album.json
8686+│ └── app/
8787+│ └── bsky/
8888+│ └── actor/
8989+│ └── profile.json
9090+└── src/
9191+ ├── main.ts # Server entry point
9292+ ├── config.ts # App configuration
9393+ ├── generated_client.ts # Your TypeScript SDK
9494+ ├── routes/ # HTTP routes
9595+ ├── features/ # Feature modules
9696+ │ ├── auth/ # OAuth implementation
9797+ │ └── dashboard/ # Main app UI
9898+ ├── shared/ # Reusable components
9999+ └── utils/ # Helper functions
100100+```
101101+102102+## CLI Commands
103103+104104+The Slices CLI is your command center:
105105+106106+### Project Management
107107+108108+```bash
109109+# Create a new project
110110+slices init my-app
111111+112112+# Check your authentication status
113113+slices status
114114+115115+# Authenticate with Slices network
116116+slices login
117117+```
118118+119119+### Lexicon Management
120120+121121+```bash
122122+# Pull lexicons from your slice
123123+slices lexicon pull
124124+125125+# Push local lexicons to your slice
126126+slices lexicon push
127127+128128+# List lexicons in your slice
129129+slices lexicon list
130130+```
131131+132132+### Code Generation
133133+134134+```bash
135135+# Generate TypeScript SDK from lexicons
136136+slices codegen
137137+138138+# The SDK is created at src/generated_client.ts
139139+```
140140+141141+### Monitoring
142142+143143+```bash
144144+# View real-time Jetstream logs
145145+slices logs
146146+147147+# See sync activity and data flow
148148+slices logs --verbose
149149+```
150150+151151+## Working with Lexicons
152152+153153+Lexicons define your data structure. The init command includes Bluesky profile
154154+lexicons to get you started, but you'll want to add your own custom lexicons for
155155+your app.
156156+157157+### Modify an Existing Lexicon
158158+159159+Edit `lexicons/com/recordcollector/album.json`:
160160+161161+```json
24162{
25163 "lexicon": 1,
26164 "id": "com.recordcollector.album",
27165 "defs": {
28166 "main": {
29167 "type": "record",
3030- "description": "A vinyl album record",
168168+ "description": "A vinyl album in my collection",
31169 "record": {
32170 "type": "object",
171171+ "required": ["title", "artist", "releaseDate"],
33172 "properties": {
3434- "title": {
3535- "type": "string",
3636- "description": "Album title"
3737- },
3838- "artist": {
3939- "type": "string",
4040- "description": "Artist or band name"
4141- },
173173+ "title": { "type": "string" },
174174+ "artist": { "type": "string" },
42175 "releaseDate": {
43176 "type": "string",
4444- "format": "datetime",
4545- "description": "Original release date"
177177+ "format": "datetime"
46178 },
47179 "genre": {
48180 "type": "array",
4949- "items": {
5050- "type": "string"
5151- },
5252- "description": "Music genres"
181181+ "items": { "type": "string" }
53182 },
54183 "condition": {
55184 "type": "string",
5656- "description": "Vinyl condition (Mint, Near Mint, Very Good, etc.)"
185185+ "enum": [
186186+ "Mint",
187187+ "Near Mint",
188188+ "Very Good Plus",
189189+ "Very Good",
190190+ "Good",
191191+ "Fair",
192192+ "Poor"
193193+ ]
57194 },
5858- "notes": {
195195+ "notes": { "type": "string" }
196196+ }
197197+ }
198198+ }
199199+ }
200200+}
201201+```
202202+203203+### Create a New Lexicon
204204+205205+Create `lexicons/com/recordcollector/review.json`:
206206+207207+```json
208208+{
209209+ "lexicon": 1,
210210+ "id": "com.recordcollector.review",
211211+ "defs": {
212212+ "main": {
213213+ "type": "record",
214214+ "description": "Album review",
215215+ "record": {
216216+ "type": "object",
217217+ "required": ["albumUri", "rating", "content"],
218218+ "properties": {
219219+ "albumUri": {
59220 "type": "string",
6060- "description": "Collector notes"
221221+ "format": "at-uri"
222222+ },
223223+ "rating": {
224224+ "type": "integer",
225225+ "minimum": 1,
226226+ "maximum": 5
227227+ },
228228+ "content": { "type": "string" },
229229+ "createdAt": {
230230+ "type": "string",
231231+ "format": "datetime"
61232 }
6262- },
6363- "required": ["title", "artist", "releaseDate"]
233233+ }
64234 }
65235 }
66236 }
67237}
68238```
692397070-### 4. Generate TypeScript Client
240240+### Update Your Slice
712417272-Navigate to the Code Generation tab and click "Generate TypeScript Client". This
7373-creates a type-safe client library for your slice.
242242+After modifying lexicons:
742437575-### 5. Use the Generated Client
244244+```bash
245245+# Push changes to your slice
246246+slices lexicon push
762477777-In your application:
248248+# Regenerate the TypeScript SDK
249249+slices codegen
250250+```
782517979-```typescript Code
8080-import { AtProtoClient } from "./generated-client.ts";
252252+Your SDK at `src/generated_client.ts` now includes the new types and methods!
812538282-const client = new AtProtoClient(
8383- "http://localhost:3000",
8484- "at://did:plc:your-did/network.slices.slice/your-slice-id",
8585-);
254254+**Important:** You must push your lexicons to the slice before you can create
255255+records. The slice needs to know about your schema in order to validate and
256256+store records.
862578787-// Get albums
8888-const albums = await client.com.recordcollector.album.getRecords();
258258+## Using the Generated SDK
259259+260260+The generated SDK provides a type-safe client for your slice:
892619090-// Add a new album to your collection
262262+```typescript
263263+import { AtprotoClient } from "./generated_client.ts";
264264+265265+// Initialize the client
266266+const client = new AtprotoClient({
267267+ baseUrl: "https://api.slices.network",
268268+ sliceUri: Deno.env.get("SLICE_URI")!,
269269+});
270270+271271+// List albums with filtering and sorting
272272+const albums = await client.com.recordcollector.album.getRecords({
273273+ where: {
274274+ genre: { contains: "jazz" },
275275+ },
276276+ sortBy: [{ field: "releaseDate", direction: "desc" }],
277277+ limit: 20,
278278+});
279279+280280+// Add a new album
91281const newAlbum = await client.com.recordcollector.album.createRecord({
9292- title: "Nevermind",
9393- artist: "Nirvana",
9494- releaseDate: "1991-09-24",
9595- genre: ["grunge", "alternative rock"],
282282+ title: "Kind of Blue",
283283+ artist: "Miles Davis",
284284+ releaseDate: "1959-08-17T00:00:00Z",
285285+ genre: ["jazz", "modal jazz"],
96286 condition: "Near Mint",
9797- notes: "Original pressing, includes poster",
287287+ notes: "Original Columbia pressing",
98288});
99289100290// Get a specific album
101291const album = await client.com.recordcollector.album.getRecord({
102292 uri: newAlbum.uri,
293293+});
294294+295295+// Update an album
296296+await client.com.recordcollector.album.updateRecord({
297297+ uri: album.uri,
298298+ record: {
299299+ ...album.value,
300300+ notes: "Verified as first pressing!",
301301+ },
302302+});
303303+304304+// Delete an album
305305+await client.com.recordcollector.album.deleteRecord({
306306+ uri: album.uri,
103307});
104308```
105309106106-## Syncing External Data
310310+### External Collections
107311108108-To import data from other AT Protocol repositories:
312312+Since the init command included Bluesky profile lexicons, your SDK has methods
313313+for querying them:
109314110110-### 1. Navigate to Sync
315315+```typescript
316316+// Query users by display name (from included Bluesky lexicons)
317317+const profiles = await client.app.bsky.actor.profile.getRecords({
318318+ where: {
319319+ displayName: { contains: "vinyl collector" },
320320+ },
321321+});
322322+```
111323112112-Go to your slice and click the Sync tab.
324324+Any record-type lexicon you add to your slice will generate corresponding SDK
325325+methods when you run `slices codegen`.
113326114114-### 2. Configure Sync
327327+## Syncing Data
115328116116-Choose collections to sync:
329329+Once your app is running, you can sync data from the AT Protocol network.
117330118118-- **Primary Collections**: Your slice's lexicons
119119-- **External Collections**: Bluesky or other AT Protocol collections
331331+### User Authentication Sync
120332121121-### 3. Start Sync
333333+When users log in via OAuth, you can sync their data using the
334334+`syncUserCollections` method. This discovers and imports their external
335335+collections (like Bluesky profiles and posts).
122336123123-Specify repositories (DIDs) to sync from, or leave empty to sync all available
124124-data.
337337+```typescript
338338+// After user logs in
339339+await client.network.slices.slice.syncUserCollections();
340340+```
125341126126-### 4. Monitor Progress
342342+### Manual Bulk Sync
127343128128-The sync will run in the background. Check the status in the UI or via API.
344344+Use the web interface at https://slices.network to start a bulk sync job.
345345+Navigate to your slice's Sync tab to configure which collections and
346346+repositories to sync.
347347+348348+**Note:** If you created new lexicons, you'll be the only one with records
349349+initially. As more users adopt your app and write records to their own PDSs, you
350350+can sync from their repositories to grow your network.
351351+352352+### Real-time Updates
353353+354354+Jetstream automatically tracks creates, updates, and deletes across the network:
355355+356356+```bash
357357+# Monitor real-time sync
358358+slices logs --slice $SLICE_URI
359359+```
360360+361361+## Deployment
362362+363363+Your app is ready for production deployment.
364364+365365+### Deno Deploy
366366+367367+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.
368368+369369+For production use with Deno Deploy, switch from SQLite to Deno KV for OAuth and session storage:
370370+371371+```typescript
372372+import { DenoKVOAuthStorage } from "@slices/oauth";
373373+import { DenoKVAdapter } from "@slices/session";
374374+375375+// Configure OAuth with Deno KV storage
376376+const oauthClient = new OAuthClient({
377377+ clientId: Deno.env.get("OAUTH_CLIENT_ID")!,
378378+ clientSecret: Deno.env.get("OAUTH_CLIENT_SECRET")!,
379379+ authBaseUrl: Deno.env.get("OAUTH_AIP_BASE_URL")!,
380380+ redirectUri: Deno.env.get("OAUTH_REDIRECT_URI")!,
381381+ storage: new DenoKVOAuthStorage(), // Uses Deno KV
382382+});
383383+384384+// Configure sessions with Deno KV adapter
385385+const sessionStore = new SessionStore({
386386+ adapter: new DenoKVAdapter(), // Uses Deno KV
387387+ cookieOptions: {
388388+ secure: true,
389389+ httpOnly: true,
390390+ },
391391+});
392392+```
393393+394394+Deno KV provides serverless-compatible storage that scales automatically with your deployment.
395395+396396+## Manual Setup (Advanced)
397397+398398+If you prefer to set things up manually or need custom configuration:
399399+400400+### 1. Create a Slice via Web UI
401401+402402+Visit https://slices.network and:
403403+404404+1. Log in with your AT Protocol account
405405+2. Click "Create Slice"
406406+3. Choose your namespace (e.g., `com.recordcollector`)
407407+408408+### 2. Create OAuth Credentials
409409+410410+In your slice dashboard:
411411+412412+1. Go to Settings → OAuth Clients
413413+2. Create a new client
414414+3. Set redirect URI: `http://localhost:8080/oauth/callback`
415415+4. Copy the Client ID and Secret
416416+417417+### 3. Set Up Your Project
418418+419419+Use any framework you prefer. You can use the generated TypeScript SDK (works
420420+with any JavaScript/TypeScript environment) or call the XRPC endpoints directly
421421+from any language:
422422+423423+```bash
424424+# Configure environment
425425+cp .env.example .env
426426+# Edit .env with your credentials
427427+428428+# Start your project
429429+# (commands depend on your framework choice)
430430+```
129431130432## Next Steps
131433132132-- [Core Concepts](./concepts.md) - Understand slices, lexicons, and collections
133133-- [API Reference](./api-reference.md) - Explore available endpoints
134134-- [SDK Usage](./sdk-usage.md) - Advanced SDK patterns
135135-- [Examples](./examples/) - Sample applications
434434+- [Core Concepts](./concepts.md): Understand slices, lexicons, sync, and code
435435+ generation
436436+- [API Reference](./api-reference.md): Detailed endpoint documentation
437437+- [SDK Usage](./sdk-usage.md): Advanced SDK patterns and examples
438438+- [Examples](./examples/): Sample applications and use cases
439439+440440+## Need Help?
441441+442442+- Join our [Discord community](https://discord.gg/slices)
443443+- Check out [example apps](https://github.com/slices/examples)
444444+- Read the [AT Protocol docs](https://atproto.com/)
445445+- Report issues on [Tangled](https://tangled.sh/slices.network/slices/issues)
+98-64
docs/intro.md
···11-# Introduction to Slices
11+# Introduction
2233-Slices is an AT Protocol appview platform that enables developers to create
44-custom data slices (appviews) with their own lexicons, sync AT Protocol data,
55-and generate type-safe SDKs.
33+Slices is an open source platform for building structured data applications on
44+the AT Protocol network.
6577-## What is a Slice?
66+## What is Slices?
8799-A slice is a custom appview within the AT Protocol ecosystem. Each slice:
88+Slices lets you define custom data schemas and build applications that store,
99+query, and sync structured records across the decentralized AT Protocol network.
1010+Think of it as a schema-first backend that automatically handles data
1111+validation, indexing, and cross-network synchronization.
10121111-- Has its own domain namespace (e.g., `social.grain`, `xyz.statusphere`)
1212-- Defines custom lexicons (schemas) for record types
1313-- Can sync both internal and external AT Protocol collections
1414-- Provides automatically generated type-safe SDKs
1313+## How Slices Works on AT Protocol
15141616-## Why Slices?
1515+```mermaid
1616+flowchart LR
1717+ Users[Users<br/>Create/Update Records] --> PDS[PDS Nodes<br/>Store user data]
1818+ PDS --> Firehose[Firehose<br/>Stream of all events]
1919+ Firehose --> SlicesNetwork[Slices Network - AppView<br/>• Monitors all AT Protocol data<br/>• Routes to relevant slices]
2020+ SlicesNetwork --> SliceA[Slice A<br/>• Blog lexicons<br/>• Post records<br/>• Comment queries]
2121+ SlicesNetwork --> SliceB[Slice B<br/>• Music lexicons<br/>• Album records<br/>• Playlist queries]
2222+ SliceA --> ClientA[Application<br/>Client<br/>• Read/write data]
2323+ SliceB --> ClientB[Application<br/>Client<br/>• Read/write data]
2424+```
17251818-Building AT Protocol applications typically requires:
2626+**Flow:**
19272020-- Setting up infrastructure to index and query AT Protocol data
2121-- Managing OAuth authentication flows
2222-- Implementing XRPC handlers for CRUD operations
2323-- Creating client libraries for frontend integration
2828+1. Users create records on their Personal Data Server (PDS)
2929+2. The Firehose streams all network events in real-time
3030+3. The Slices Network monitors the firehose and routes data to relevant slices
3131+4. Each slice indexes only records matching its specific lexicons
3232+5. Application clients connect to specific slices to read/write data
24332525-Slices provides all of this infrastructure out of the box, letting you focus on
2626-your application logic.
3434+## Quick Start
27352828-## Key Features
3636+Get started in under a minute:
29373030-### Dynamic API Generation
3838+```bash
3939+# Install the CLI globally
4040+deno install -g jsr:@slices/cli --name slices
31413232-Define a lexicon, and Slices automatically creates REST endpoints for:
4242+# Initialize a new slice project
4343+slices init my-app
33443434-- Listing records with filtering and sorting
3535-- Getting individual records by URI
3636-- Creating new records with OAuth authentication
3737-- Updating existing records
3838-- Deleting records
3939-- Searching within collections
4545+# Start developing
4646+cd my-app
4747+deno task dev
4848+```
40494141-### Data Synchronization
5050+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.
42514343-Sync AT Protocol data into your slice:
5252+## Simple Example
44534545-- Import external collections (e.g., Bluesky profiles, posts)
4646-- Filter data by repository or collection
4747-- Maintain slice-specific data isolation
4848-- Real-time sync via Jetstream (coming soon)
5454+Define a schema for vinyl albums:
49555050-### Type-Safe SDK Generation
5656+```json lexicons/com/recordcollector/album.json
5757+{
5858+ "lexicon": 1,
5959+ "id": "com.recordcollector.album",
6060+ "defs": {
6161+ "main": {
6262+ "type": "record",
6363+ "record": {
6464+ "type": "object",
6565+ "required": ["title", "artist", "releaseDate"],
6666+ "properties": {
6767+ "title": { "type": "string" },
6868+ "artist": { "type": "string" },
6969+ "releaseDate": { "type": "string", "format": "datetime" },
7070+ "genre": { "type": "array", "items": { "type": "string" } },
7171+ "condition": { "type": "string" }
7272+ }
7373+ }
7474+ }
7575+ }
7676+}
7777+```
51785252-Automatically generate TypeScript clients with:
7979+Push your lexicon and regenerate the SDK:
53805454-- Full type safety for all record types
5555-- OAuth authentication integration
5656-- Nested API structure matching your lexicons
5757-- Blob/CDN URL utilities for media handling
8181+```bash
8282+# Push your lexicon to the slice
8383+slices lexicon push
8484+8585+# Regenerate TypeScript SDK
8686+slices codegen
8787+```
58885959-### Multi-Tenant Architecture
8989+Use the auto-generated, type-safe client:
60906161-Each slice operates independently:
9191+```typescript
9292+import { AtprotoClient } from "./generated_client.ts";
62936363-- Isolated data storage
6464-- Custom lexicon definitions
6565-- Separate sync configurations
6666-- Per-slice statistics and analytics
9494+const client = new AtprotoClient({
9595+ baseUrl: "https://api.slices.network",
9696+ sliceUri: "at://your-slice-uri",
9797+});
67986868-## Use Cases
9999+// Get all grunge albums
100100+const albums = await client.com.recordcollector.album.getRecords({
101101+ where: { genre: { contains: "grunge" } },
102102+ sortBy: [{ field: "releaseDate", direction: "desc" }],
103103+});
104104+```
691057070-- **Custom Social Apps**: Build specialized social networks with custom record
7171- types
7272-- **Data Aggregators**: Collect and organize AT Protocol data for analysis
7373-- **Specialized Feeds**: Create curated views of AT Protocol content
7474-- **Research Tools**: Index and query specific subsets of AT Protocol data
7575-- **Creative Applications**: Blogs, galleries, portfolios built on AT Protocol
106106+## Key Features
761077777-## Architecture Overview
108108+- **Schema Validation**: Define lexicons that enforce data structure and constraints
109109+- **Auto-generated APIs**: REST endpoints created automatically from your schemas
110110+- **TypeScript SDKs**: Type-safe clients generated from your lexicons
111111+- **Real-time Sync**: Automatic synchronization across the AT Protocol network
112112+- **Advanced Querying**: Filter, sort, and paginate records with a powerful query API
113113+- **OAuth Built-in**: Authentication with any AT Protocol account
781147979-Slices consists of two main components:
115115+## When to Use Slices
801168181-- **API Server** (Rust): Handles AT Protocol integration, database operations,
8282- and API endpoints
8383-- **Frontend** (Deno): Provides web UI for slice management and user
8484- authentication
117117+Slices is ideal for:
851188686-Both components work together to provide a complete AT Protocol appview
8787-platform. The API server can also be run standalone as a headless service and
8888-interactive with via it's XRPC apis.
119119+- **Social Applications**: Build specialized communities, forums, or social features
120120+- **Content Platforms**: Create blogs, documentation sites, or media libraries
121121+- **SaaS Products**: Develop collaborative tools with structured data needs
122122+- **Web APIs**: Design REST APIs with automatic validation and documentation
123123+- **Decentralized Apps**: Build on AT Protocol without managing infrastructure
8912490125## Next Steps
9112692127- [Getting Started](./getting-started.md) - Set up your first slice
9393-- [Core Concepts](./concepts.md) - Understand the key concepts
9494-- [API Reference](./api-reference.md) - Explore the API endpoints
9595-- [SDK Usage](./sdk-usage.md) - Learn to use generated clients
128128+- [Core Concepts](./concepts.md) - Understand lexicons and collections
129129+- [API Reference](./api-reference.md) - Explore the full API
-118
docs/introduction.md
···11-# Introduction
22-33-Slices is an open source platform for building structured data applications on
44-the AT Protocol network.
55-66-## What is Slices?
77-88-Slices lets you define custom data schemas and build applications that store,
99-query, and sync structured records across the decentralized AT Protocol network.
1010-Think of it as a schema-first backend that automatically handles data
1111-validation, indexing, and cross-network synchronization.
1212-1313-## How Slices Works on AT Protocol
1414-1515-```mermaid
1616-flowchart LR
1717- Users[Users<br/>Create/Update Records] --> PDS[PDS Nodes<br/>Store user data]
1818- PDS --> Firehose[Firehose<br/>Stream of all events]
1919- Firehose --> SlicesNetwork[Slices Network - AppView<br/>• Monitors all AT Protocol data<br/>• Routes to relevant slices]
2020- SlicesNetwork --> SliceA[Slice A<br/>• Blog lexicons<br/>• Post records<br/>• Comment queries]
2121- SlicesNetwork --> SliceB[Slice B<br/>• Music lexicons<br/>• Album records<br/>• Playlist queries]
2222- SliceA --> ClientA[Application<br/>Client<br/>• Read/write data]
2323- SliceB --> ClientB[Application<br/>Client<br/>• Read/write data]
2424-```
2525-2626-**Flow:**
2727-2828-1. Users create records on their Personal Data Server (PDS)
2929-2. The Firehose streams all network events in real-time
3030-3. The Slices Network monitors the firehose and routes data to relevant slices
3131-4. Each slice indexes only records matching its specific lexicons
3232-5. Application clients connect to specific slices to read/write data
3333-3434-## Quick Start
3535-3636-Get started in under a minute:
3737-3838-```bash
3939-# Initialize a new slice project
4040-deno run -A jsr:@slices/cli init
4141-4242-# Follow the prompts to:
4343-# 1. Name your slice
4444-# 2. Define your first lexicon
4545-# 3. Deploy to the network
4646-```
4747-4848-## Simple Example
4949-5050-Define a schema for a blog post:
5151-5252-```json lexicons/com/myblog/post.json
5353-{
5454- "lexicon": 1,
5555- "id": "com.myblog.post",
5656- "defs": {
5757- "main": {
5858- "type": "record",
5959- "record": {
6060- "type": "object",
6161- "required": ["title", "content", "createdAt"],
6262- "properties": {
6363- "title": { "type": "string", "maxLength": 200 },
6464- "content": { "type": "string", "maxLength": 10000 },
6565- "tags": { "type": "array", "items": { "type": "string" } },
6666- "createdAt": { "type": "string", "format": "datetime" }
6767- }
6868- }
6969- }
7070- }
7171-}
7272-```
7373-7474-Deploy your lexicon and generate the TypeScript client:
7575-7676-```bash
7777-# Push your lexicon to the slice
7878-deno run -A jsr:@slices/cli lexicon push
7979-8080-# Generate TypeScript SDK from your lexicons
8181-deno run -A jsr:@slices/cli codegen
8282-```
8383-8484-Query your data using the auto-generated API:
8585-8686-```typescript
8787-// Get all posts with a specific tag
8888-const posts = await client.com.myblog.post.getRecords({
8989- slice: "at://your-slice-uri",
9090- where: { tags: { contains: "javascript" } },
9191- sortBy: [{ field: "createdAt", direction: "desc" }],
9292-});
9393-```
9494-9595-## Key Features
9696-9797-- **Schema Validation**: Define lexicons that enforce data structure and constraints
9898-- **Auto-generated APIs**: REST endpoints created automatically from your schemas
9999-- **TypeScript SDKs**: Type-safe clients generated from your lexicons
100100-- **Real-time Sync**: Automatic synchronization across the AT Protocol network
101101-- **Advanced Querying**: Filter, sort, and paginate records with a powerful query API
102102-- **OAuth Built-in**: Authentication with any AT Protocol account
103103-104104-## When to Use Slices
105105-106106-Slices is ideal for:
107107-108108-- **Social Applications**: Build specialized communities, forums, or social features
109109-- **Content Platforms**: Create blogs, documentation sites, or media libraries
110110-- **SaaS Products**: Develop collaborative tools with structured data needs
111111-- **Web APIs**: Design REST APIs with automatic validation and documentation
112112-- **Decentralized Apps**: Build on AT Protocol without managing infrastructure
113113-114114-## Next Steps
115115-116116-- [Getting Started](./getting-started.md) - Set up your first slice
117117-- [Core Concepts](./concepts.md) - Understand lexicons and collections
118118-- [API Reference](./api-reference.md) - Explore the full API
+81-95
docs/sdk-usage.md
···77After generating your TypeScript client, you can use it directly in your
88project:
991010-```typescript Code
1111-import { AtProtoClient } from "./generated_client.ts";
1010+```typescript
1111+import { AtprotoClient } from "./generated_client.ts";
1212import { OAuthClient } from "@slices/oauth";
1313```
1414···16161717### Without Authentication (Read-Only)
18181919-```typescript Code
2020-const client = new AtProtoClient(
2121- "https://api.your-domain.com",
2222- "at://did:plc:abc/network.slices.slice/your-slice-rkey",
2323-);
1919+```typescript
2020+const client = new AtprotoClient({
2121+ baseUrl: "https://api.slices.network",
2222+ sliceUri: "at://did:plc:abc/network.slices.slice/your-slice-rkey",
2323+});
24242525// Read operations work without auth
2626const albums = await client.com.recordcollector.album.getRecords();
···28282929### With Authentication (Full Access)
30303131-```typescript Code
3131+```typescript
3232import { OAuthClient } from "@slices/oauth";
33333434// Set up OAuth client
···4141});
42424343// Initialize API client with OAuth
4444-const client = new AtProtoClient(
4545- "https://api.your-domain.com",
4646- "at://did:plc:abc/network.slices.slice/your-slice-rekey",
4747- oauthClient,
4848-);
4444+const client = new AtprotoClient({
4545+ baseUrl: "https://api.slices.network",
4646+ sliceUri: "at://did:plc:abc/network.slices.slice/your-slice-rkey",
4747+ auth: oauthClient,
4848+});
4949```
50505151## CRUD Operations
···54545555The SDK uses `getRecords` for retrieving records:
56565757-```typescript Code
5757+```typescript
5858// Get all vinyl records
5959const albums = await client.com.recordcollector.album.getRecords();
6060···142142The `countRecords` method allows you to count records without fetching them,
143143using the same filtering parameters as `getRecords`:
144144145145-```typescript Code
145145+```typescript
146146// Count all records
147147const total = await client.com.recordcollector.album.countRecords();
148148console.log(`Total albums: ${total.count}`);
···186186187187### Getting a Single Record
188188189189-```typescript Code
189189+```typescript
190190const album = await client.com.recordcollector.album.getRecord({
191191 uri: "at://did:plc:abc/com.recordcollector.album/3jklmno456",
192192});
···197197198198### Creating Records
199199200200-```typescript Code
200200+```typescript
201201// Create with auto-generated key
202202const newAlbum = await client.com.recordcollector.album.createRecord({
203203 title: "In Utero",
···221221222222### Updating Records
223223224224-```typescript Code
224224+```typescript
225225// Get the record key from the URI
226226const uri = "at://did:plc:abc/com.recordcollector.album/3jklmno456";
227227const rkey = uri.split("/").pop(); // '3jklmno456'
···241241242242### Deleting Records
243243244244-```typescript Code
244244+```typescript
245245const rkey = "3jklmno456";
246246await client.com.recordcollector.album.deleteRecord(rkey);
247247```
···250250251251Access synced external collections like Bluesky profiles:
252252253253-```typescript Code
253253+```typescript
254254// Get Bluesky profiles in your slice
255255const profiles = await client.app.bsky.actor.profile.getRecords();
256256···268268269269### Uploading Blobs
270270271271-```typescript Code
271271+```typescript
272272// Read file as ArrayBuffer
273273const file = await Deno.readFile("./nevermind-cover.jpg");
274274···291291292292### Converting Blobs to CDN URLs
293293294294-```typescript Code
294294+```typescript
295295import { recordBlobToCdnUrl } from "./generated-client.ts";
296296297297// Get a record with a blob
···318318319319## Slice Operations
320320321321-### Get Slice Statistics
322322-323323-```typescript Code
324324-const stats = await client.network.slices.slice.stats({
325325- slice: "at://your-slice-uri",
326326-});
327327-328328-console.log(`Total records: ${stats.totalRecords}`);
329329-console.log(`Total actors: ${stats.totalActors}`);
330330-331331-stats.collectionStats.forEach((stat) => {
332332- console.log(`${stat.collection}: ${stat.recordCount} records`);
333333-});
334334-```
335335-336321### Get Actors
337322338323The `getActors` method retrieves actors (users) within a slice with powerful
339324filtering and sorting capabilities:
340325341341-```typescript Code
326326+```typescript
342327// Get all actors in the slice
343343-const actors = await client.network.slices.slice.getActors();
328328+const actors = await client.getActors();
344329345330// With pagination
346346-const page1 = await client.network.slices.slice.getActors({
331331+const page1 = await client.getActors({
347332 limit: 20,
348333});
349349-const page2 = await client.network.slices.slice.getActors({
334334+const page2 = await client.getActors({
350335 limit: 20,
351336 cursor: page1.cursor,
352337});
353338354339// Filter by specific DIDs
355355-const specificActors = await client.network.slices.slice.getActors({
340340+const specificActors = await client.getActors({
356341 where: {
357342 did: { in: ["did:plc:user1", "did:plc:user2"] },
358343 },
359344});
360345361346// Search by handle
362362-const searchByHandle = await client.network.slices.slice.getActors({
347347+const searchByHandle = await client.getActors({
363348 where: {
364349 handle: { contains: "alice" },
365350 },
366351});
367352368353// Filter by exact handle
369369-const exactHandle = await client.network.slices.slice.getActors({
354354+const exactHandle = await client.getActors({
370355 where: {
371356 handle: { eq: "user.bsky.social" },
372357 },
···380365381366### Browse Slice Records
382367383383-#### Get Records from Multiple Collections
384384-385385-The `getSliceRecords` method uses the same `where` clause approach:
368368+The `getSliceRecords` method retrieves records across multiple collections:
386369387387-```typescript Code
370370+```typescript
388371// Get records from specific collections
389389-const records = await client.network.slices.slice.getSliceRecords({
372372+const records = await client.getSliceRecords({
390373 where: {
391391- collection: { eq: "com.example.post" },
374374+ collection: { eq: "com.recordcollector.album" },
392375 did: { eq: "did:plc:specific-author" }, // optional
393376 },
394377 limit: 50,
···399382});
400383401384// Search across collections using specific fields
402402-const searchResults = await client.network.slices.slice.getSliceRecords({
385385+const searchResults = await client.getSliceRecords({
403386 where: {
404404- collection: { eq: "com.example.post" },
405405- title: { contains: "hello world" },
387387+ collection: { eq: "com.recordcollector.album" },
388388+ title: { contains: "nevermind" },
406389 did: { eq: "did:plc:specific-author" }, // optional
407390 },
408391 limit: 50,
409392});
410393411394// Global search across ALL fields in records
412412-const globalSearchResults = await client.network.slices.slice.getSliceRecords({
395395+const globalSearchResults = await client.getSliceRecords({
413396 where: {
414414- collection: { eq: "com.example.post" },
415415- json: { contains: "hello world" }, // Searches entire record content
397397+ collection: { eq: "com.recordcollector.album" },
398398+ json: { contains: "grunge" }, // Searches entire record content
416399 did: { eq: "did:plc:specific-author" }, // optional
417400 },
418401 limit: 50,
···423406});
424407425408// Get records from any collection with global text search
426426-const allCollectionSearch = await client.network.slices.slice.getSliceRecords({
409409+const allCollectionSearch = await client.getSliceRecords({
427410 where: {
428428- json: { contains: "important content" }, // Searches ALL fields in ALL collections
411411+ json: { contains: "seattle" }, // Searches ALL fields in ALL collections
429412 },
430413 limit: 20,
431414});
···439422440423Search within specific fields of your records:
441424442442-```typescript Code
425425+```typescript
443426// Search in title field only
444427const titleSearch = await client.com.recordcollector.album.getRecords({
445428 where: {
···459442460443Use the special `json` field to search across **all fields** in a record:
461444462462-```typescript Code
445445+```typescript
463446// Finds records containing "grunge" anywhere in their data
464447const globalSearch = await client.com.recordcollector.album.getRecords({
465448 where: {
···479462480463When using `getSliceRecords`, you can search across multiple collections:
481464482482-```typescript Code
465465+```typescript
483466// Search for "seattle" across all collections
484484-const crossCollectionSearch = await client.network.slices.slice.getSliceRecords(
485485- {
486486- where: {
487487- json: { contains: "seattle" },
488488- },
467467+const crossCollectionSearch = await client.getSliceRecords({
468468+ where: {
469469+ json: { contains: "seattle" },
489470 },
490490-);
471471+});
491472492473// Limit to specific collections
493493-const specificSearch = await client.network.slices.slice.getSliceRecords({
474474+const specificSearch = await client.getSliceRecords({
494475 where: {
495476 collection: {
496477 in: ["com.recordcollector.album", "com.recordcollector.review"],
···506487using the separate `orWhere` parameter. This provides clean type safety and
507488autocomplete for field names:
508489509509-```typescript Code
490490+```typescript
510491// Find albums by either Nirvana OR Alice in Chains
511492const albums = await client.com.recordcollector.album.getRecords({
512493 orWhere: {
···535516// SQL: WHERE release_date = '1991-09-24' AND (artist LIKE '%nirvana%' OR genre LIKE '%grunge%')
536517537518// OR queries work with cross-collection searches too
538538-const crossCollectionOrSearch = await client.network.slices.slice
539539- .getSliceRecords({
540540- where: {
541541- collection: { eq: "com.recordcollector.album" },
542542- },
543543- orWhere: {
544544- artist: { contains: "pearl jam" },
545545- genre: { contains: "alternative rock" },
546546- },
547547- });
519519+const crossCollectionOrSearch = await client.getSliceRecords({
520520+ where: {
521521+ collection: { eq: "com.recordcollector.album" },
522522+ },
523523+ orWhere: {
524524+ artist: { contains: "pearl jam" },
525525+ genre: { contains: "alternative rock" },
526526+ },
527527+});
548528549529// You get full autocomplete and type safety for field names in both where and orWhere
550530const typedSearch = await client.com.recordcollector.album.getRecords({
···562542563543### Sync User Collections
564544565565-```typescript Code
545545+```typescript
566546// Sync current user's data (requires auth)
567567-const syncResult = await client.network.slices.slice.syncUserCollections({
568568- timeoutSeconds: 30,
569569-});
547547+const syncResult = await client.syncUserCollections();
570548571549console.log(`Synced ${syncResult.recordsSynced} records`);
572550```
573551574552## Error Handling
575553576576-```typescript Code
554554+```typescript
577555try {
578556 const post = await client.com.example.post.getRecord({
579557 uri: "at://invalid-uri",
···593571594572### 1. Initialize OAuth
595573596596-```typescript Code
574574+```typescript
597575const oauthClient = new OAuthClient({
598576 clientId: process.env.OAUTH_CLIENT_ID,
599577 clientSecret: process.env.OAUTH_CLIENT_SECRET,
···604582605583### 2. Start Authorization
606584607607-```typescript Code
585585+```typescript
608586const authResult = await oauthClient.authorize({
609587 loginHint: "user.bsky.social",
610588});
···615593616594### 3. Handle Callback
617595618618-```typescript Code
596596+```typescript
619597// In your callback handler
620598const urlParams = new URLSearchParams(window.location.search);
621599const code = urlParams.get("code");
···626604627605### 4. Use Authenticated Client
628606629629-```typescript Code
630630-const client = new AtProtoClient(apiUrl, sliceUri, oauthClient);
607607+```typescript
608608+const client = new AtprotoClient({
609609+ baseUrl: "https://api.slices.network",
610610+ sliceUri: "at://did:plc:abc/network.slices.slice/your-slice-rkey",
611611+ auth: oauthClient,
612612+});
631613632614// OAuth tokens are automatically managed
633633-const profile = await client.network.slices.actor.profile.createRecord({
634634- displayName: "New User",
635635- description: "My profile",
636636-}, true); // useSelfRkey for profile
615615+const album = await client.com.recordcollector.album.createRecord({
616616+ title: "Ten",
617617+ artist: "Pearl Jam",
618618+ releaseDate: "1991-08-27",
619619+ genre: ["grunge", "alternative rock"],
620620+ condition: "Mint",
621621+});
637622```
638623639624## Type Safety
640625641626The generated SDK provides full TypeScript type safety:
642627643643-```typescript Code
628628+```typescript
644629// TypeScript knows the shape of your records
645630const album = await client.com.recordcollector.album.getRecord({ uri });
646631···669654670655### Batch Operations
671656672672-```typescript Code
657657+```typescript
673658// Process records in batches
659659+674660async function* getAllAlbums() {
675661 let cursor: string | undefined;
676662
+1-84
docs/self-hosting.md
···2233This guide covers how to set up and run your own Slices instance.
4455-## Prerequisites
66-77-- Docker and Docker Compose
88-- PostgreSQL (or use Docker)
99-- Deno (for frontend)
1010-- Rust and Cargo (for API development)
1111-- An AT Protocol account (for OAuth)
1212-1313-## Initial Setup
1414-1515-### 1. Clone the Repository
1616-1717-```bash
1818-git clone https://tangled.sh/@slices.network/slices
1919-cd slice
2020-```
2121-2222-### 2. Set Up the Database
2323-2424-Start PostgreSQL using Docker:
2525-2626-```bash
2727-docker-compose up -d postgres
2828-```
2929-3030-Or use an existing PostgreSQL instance and create a database:
3131-3232-```sql
3333-CREATE DATABASE slices;
3434-```
3535-3636-### 3. Configure Environment Variables
3737-3838-Create `.env` files for both API and frontend:
3939-4040-**API (`/api/.env`)**:
4141-4242-```bash
4343-DATABASE_URL=postgres://user:password@localhost:5432/slices
4444-AUTH_BASE_URL=https://aip.your-domain.com
4545-PORT=3000
4646-```
4747-4848-**Frontend (`/frontend/.env`)**:
4949-5050-```bash
5151-OAUTH_CLIENT_ID=your-client-id
5252-OAUTH_CLIENT_SECRET=your-client-secret
5353-OAUTH_REDIRECT_URI=http://localhost:8000/oauth/callback
5454-OAUTH_AIP_BASE_URL=https://aip.your-domain.com
5555-SESSION_ENCRYPTION_KEY=your-32-char-key
5656-API_URL=http://localhost:3000
5757-SLICE_URI=at://did:plc:your-did/network.slices.slice/your-slice-id
5858-DATABASE_URL=slices.db
5959-```
6060-6161-### 4. Register OAuth Client
6262-6363-Register your application with the AIP server:
6464-6565-```bash
6666-cd frontend
6767-./scripts/register-oauth-client.sh
6868-```
6969-7070-Save the client ID and secret to your `.env` file.
7171-7272-### 5. Start the Services
7373-7474-Start the API server:
7575-7676-```bash
7777-cd api
7878-cargo run
7979-```
8080-8181-Start the frontend:
8282-8383-```bash
8484-cd frontend
8585-deno task dev
8686-```
8787-8888-Visit `http://localhost:8000` to access the web interface.55+WIP - coming soon