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