# SDK Usage Guide This guide covers how to use the generated TypeScript SDK for your slice. ## Installation After generating your TypeScript client, you can use it directly in your project: ```typescript import { AtprotoClient } from "./generated_client.ts"; import { OAuthClient } from "@slices/oauth"; ``` ## Basic Setup ### Without Authentication (Read-Only) ```typescript const client = new AtprotoClient({ baseUrl: "https://api.slices.network", sliceUri: "at://did:plc:abc/network.slices.slice/your-slice-rkey", }); // Read operations work without auth const albums = await client.com.recordcollector.album.getRecords(); ``` ### With Authentication (Full Access) ```typescript import { OAuthClient } from "@slices/oauth"; // Set up OAuth client const oauthClient = new OAuthClient({ clientId: "your-client-id", clientSecret: "your-client-secret", authBaseUrl: "https://aip.your-domain.com", redirectUri: "https://your-app.com/oauth/callback", scopes: ["atproto", "transition:generic"], }); // Initialize API client with OAuth const client = new AtprotoClient({ baseUrl: "https://api.slices.network", sliceUri: "at://did:plc:abc/network.slices.slice/your-slice-rkey", auth: oauthClient, }); ``` ## CRUD Operations ### Getting Records The SDK uses `getRecords` for retrieving records: ```typescript // Get all vinyl records const albums = await client.com.recordcollector.album.getRecords(); // With pagination const page1 = await client.com.recordcollector.album.getRecords({ limit: 20 }); const page2 = await client.com.recordcollector.album.getRecords({ limit: 20, cursor: page1.cursor, }); // With filtering using where clause const nirvanaAlbums = await client.com.recordcollector.album.getRecords({ where: { artist: { eq: "Nirvana" }, }, }); // Text search in specific fields const searchResults = await client.com.recordcollector.album.getRecords({ where: { title: { contains: "nevermind" }, }, }); // Global text search across ALL fields using 'json' const globalSearch = await client.com.recordcollector.album.getRecords({ where: { json: { contains: "grunge" }, }, }); // Combine multiple filters const seattleGrunge = await client.com.recordcollector.album.getRecords({ where: { city: { eq: "Seattle" }, genre: { contains: "grunge" }, }, limit: 50, }); // Advanced filtering with multiple conditions const complexFilter = await client.com.recordcollector.album.getRecords({ where: { artist: { contains: "alice" }, releaseDate: { gte: "1990-01-01" }, condition: { in: ["Mint", "Near Mint"] }, }, limit: 25, }); // Filtering with exact matches const exactMatch = await client.com.recordcollector.album.getRecords({ where: { artist: { eq: "Soundgarden" }, genre: { contains: "grunge" }, }, }); // Date range filtering const nineties = await client.com.recordcollector.album.getRecords({ where: { releaseDate: { gte: "1990-01-01", lte: "1999-12-31", }, }, }); // With sorting const recentAlbums = await client.com.recordcollector.album.getRecords({ sortBy: [{ field: "releaseDate", direction: "desc" }], }); // Multiple sort fields const sortedAlbums = await client.com.recordcollector.album.getRecords({ sortBy: [ { field: "releaseDate", direction: "desc" }, { field: "title", direction: "asc" }, ], }); ``` ### Counting Records The `countRecords` method allows you to count records without fetching them, using the same filtering parameters as `getRecords`: ```typescript // Count all records const total = await client.com.recordcollector.album.countRecords(); console.log(`Total albums: ${total.count}`); // Count with filtering const nirvanaCount = await client.com.recordcollector.album.countRecords({ where: { artist: { eq: "Nirvana" }, }, }); // Count with text search const searchCount = await client.com.recordcollector.album.countRecords({ where: { title: { contains: "nevermind" }, }, }); // Count with multiple filters const filteredCount = await client.com.recordcollector.album.countRecords({ where: { artist: { eq: "Alice in Chains" }, json: { contains: "grunge" }, }, }); // Count with OR conditions const orCount = await client.com.recordcollector.album.countRecords({ where: { releaseDate: { eq: "1991-09-24" }, }, orWhere: { title: { contains: "nevermind" }, artist: { eq: "Pearl Jam" }, }, }); console.log(`Found ${filteredCount.count} matching albums`); console.log(`Found ${orCount.count} albums with OR conditions`); ``` ### Getting a Single Record ```typescript const album = await client.com.recordcollector.album.getRecord({ uri: "at://did:plc:abc/com.recordcollector.album/3jklmno456", }); console.log(album.value.title); console.log(album.value.artist); ``` ### Creating Records ```typescript // Create with auto-generated key const newAlbum = await client.com.recordcollector.album.createRecord({ title: "In Utero", artist: "Nirvana", releaseDate: "1993-09-21", genre: ["grunge", "alternative rock"], }); console.log(`Created: ${newAlbum.uri}`); // Create with custom key const customAlbum = await client.com.recordcollector.album.createRecord( { title: "Badmotorfinger", artist: "Soundgarden", releaseDate: "1991-10-08", }, true, // useSelfRkey for singleton records like profiles ); ``` ### Updating Records ```typescript // Get the record key from the URI const uri = "at://did:plc:abc/com.recordcollector.album/3jklmno456"; const rkey = uri.split("/").pop(); // '3jklmno456' const updated = await client.com.recordcollector.album.updateRecord( rkey, { title: "Nevermind (Remastered)", artist: "Nirvana", releaseDate: "1991-09-24", updatedAt: new Date().toISOString(), }, ); console.log(`Updated: ${updated.cid}`); ``` ### Deleting Records ```typescript const rkey = "3jklmno456"; await client.com.recordcollector.album.deleteRecord(rkey); ``` ## Working with External Collections Access synced external collections like Bluesky profiles: ```typescript // Get Bluesky profiles in your slice const profiles = await client.app.bsky.actor.profile.getRecords(); // Get a specific profile const profile = await client.app.bsky.actor.profile.getRecord({ uri: "at://did:plc:user/app.bsky.actor.profile/self", }); // Access profile data console.log(profile.value.displayName); console.log(profile.value.description); ``` ## Blob Handling ### Uploading Blobs ```typescript // Read file as ArrayBuffer const file = await Deno.readFile("./nevermind-cover.jpg"); // Upload blob const blobResponse = await client.uploadBlob({ data: file, mimeType: "image/jpeg", }); // Use blob in a record const albumWithArt = await client.com.recordcollector.album.createRecord({ title: "Nevermind", artist: "Nirvana", releaseDate: "1991-09-24", genre: ["grunge", "alternative rock"], condition: "Near Mint", albumArt: blobResponse.blob, }); ``` ### Converting Blobs to CDN URLs ```typescript import { recordBlobToCdnUrl } from "./generated-client.ts"; // Get a record with a blob const profile = await client.app.bsky.actor.profile.getRecord({ uri: "at://did:plc:user/app.bsky.actor.profile/self", }); // Convert avatar blob to CDN URL if (profile.value.avatar) { const avatarUrl = recordBlobToCdnUrl( profile, profile.value.avatar, "avatar", // Size preset ); console.log(`Avatar URL: ${avatarUrl}`); } // Available presets: // - 'avatar': Small square images // - 'banner': Wide header images // - 'feed_thumbnail': Small feed previews // - 'feed_fullsize': Full resolution images ``` ## Slice Operations ### Get Actors The `getActors` method retrieves actors (users) within a slice with powerful filtering and sorting capabilities: ```typescript // Get all actors in the slice const actors = await client.getActors(); // With pagination const page1 = await client.getActors({ limit: 20, }); const page2 = await client.getActors({ limit: 20, cursor: page1.cursor, }); // Filter by specific DIDs const specificActors = await client.getActors({ where: { did: { in: ["did:plc:user1", "did:plc:user2"] }, }, }); // Search by handle const searchByHandle = await client.getActors({ where: { handle: { contains: "alice" }, }, }); // Filter by exact handle const exactHandle = await client.getActors({ where: { handle: { eq: "user.bsky.social" }, }, }); // Available where fields for actors: // - did: Filter by decentralized identifier // - handle: Filter by handle/username // - indexed_at: Filter by when the actor was indexed ``` ### Browse Slice Records The `getSliceRecords` method retrieves records across multiple collections: ```typescript // Get records from specific collections const records = await client.getSliceRecords({ where: { collection: { eq: "com.recordcollector.album" }, did: { eq: "did:plc:specific-author" }, // optional }, limit: 50, }); records.records.forEach((record) => { console.log(`${record.uri}: ${JSON.stringify(record.value)}`); }); // Search across collections using specific fields const searchResults = await client.getSliceRecords({ where: { collection: { eq: "com.recordcollector.album" }, title: { contains: "nevermind" }, did: { eq: "did:plc:specific-author" }, // optional }, limit: 50, }); // Global search across ALL fields in records const globalSearchResults = await client.getSliceRecords({ where: { collection: { eq: "com.recordcollector.album" }, json: { contains: "grunge" }, // Searches entire record content did: { eq: "did:plc:specific-author" }, // optional }, limit: 50, }); searchResults.records.forEach((record) => { console.log(`Found: ${record.uri} - ${JSON.stringify(record.value)}`); }); // Get records from any collection with global text search const allCollectionSearch = await client.getSliceRecords({ where: { json: { contains: "seattle" }, // Searches ALL fields in ALL collections }, limit: 20, }); ``` ## Search Capabilities The SDK provides flexible search options for finding records: ### Field-Specific Search Search within specific fields of your records: ```typescript // Search in title field only const titleSearch = await client.com.recordcollector.album.getRecords({ where: { title: { contains: "nevermind" }, }, }); // Search in notes field const notesSearch = await client.com.recordcollector.album.getRecords({ where: { notes: { contains: "original pressing" }, }, }); ``` ### Global JSON Search Use the special `json` field to search across **all fields** in a record: ```typescript // Finds records containing "grunge" anywhere in their data const globalSearch = await client.com.recordcollector.album.getRecords({ where: { json: { contains: "grunge" }, }, }); // This will match records where "grunge" appears in: // - title: "Nevermind" // - artist: "Nirvana" // - genre: ["grunge", "alternative rock"] // - notes: "Classic grunge album from Seattle" // - or any other field in the record ``` ### Cross-Collection Search When using `getSliceRecords`, you can search across multiple collections: ```typescript // Search for "seattle" across all collections const crossCollectionSearch = await client.getSliceRecords({ where: { json: { contains: "seattle" }, }, }); // Limit to specific collections const specificSearch = await client.getSliceRecords({ where: { collection: { in: ["com.recordcollector.album", "com.recordcollector.review"], }, json: { contains: "grunge" }, }, }); ``` ### OR Query Support You can use OR queries to find records that match any of multiple conditions using the separate `orWhere` parameter. This provides clean type safety and autocomplete for field names: ```typescript // Find albums by either Nirvana OR Alice in Chains const albums = await client.com.recordcollector.album.getRecords({ orWhere: { artist: { in: ["Nirvana", "Alice in Chains"] }, }, }); // Find albums that either have "nevermind" in title OR are by Soundgarden const albums = await client.com.recordcollector.album.getRecords({ orWhere: { title: { contains: "nevermind" }, artist: { eq: "Soundgarden" }, }, }); // Combining OR with regular AND conditions const albums = await client.com.recordcollector.album.getRecords({ where: { releaseDate: { eq: "1991-09-24" }, // AND conditions }, orWhere: { // OR conditions artist: { contains: "nirvana" }, genre: { contains: "grunge" }, }, }); // SQL: WHERE release_date = '1991-09-24' AND (artist LIKE '%nirvana%' OR genre LIKE '%grunge%') // OR queries work with cross-collection searches too const crossCollectionOrSearch = await client.getSliceRecords({ where: { collection: { eq: "com.recordcollector.album" }, }, orWhere: { artist: { contains: "pearl jam" }, genre: { contains: "alternative rock" }, }, }); // You get full autocomplete and type safety for field names in both where and orWhere const typedSearch = await client.com.recordcollector.album.getRecords({ where: { // TypeScript autocompletes valid field names here condition: { contains: "mint" }, }, orWhere: { // And also provides autocomplete here artist: { contains: "soundgarden" }, genre: { contains: "grunge" }, }, }); ``` ### Sync User Collections ```typescript // Sync current user's data (requires auth) const syncResult = await client.syncUserCollections(); console.log(`Synced ${syncResult.recordsSynced} records`); ``` ## Error Handling ```typescript try { const post = await client.com.example.post.getRecord({ uri: "at://invalid-uri", }); } catch (error) { if (error.message.includes("404")) { console.log("Record not found"); } else if (error.message.includes("401")) { console.log("Authentication required"); } else { console.error("Unexpected error:", error); } } ``` ## OAuth Authentication Flow ### 1. Initialize OAuth ```typescript const oauthClient = new OAuthClient({ clientId: process.env.OAUTH_CLIENT_ID, clientSecret: process.env.OAUTH_CLIENT_SECRET, authBaseUrl: process.env.OAUTH_AIP_BASE_URL, redirectUri: "https://your-app.com/oauth/callback", }); ``` ### 2. Start Authorization ```typescript const authResult = await oauthClient.authorize({ loginHint: "user.bsky.social", }); // Redirect user to authorization URL window.location.href = authResult.authorizationUrl; ``` ### 3. Handle Callback ```typescript // In your callback handler const urlParams = new URLSearchParams(window.location.search); const code = urlParams.get("code"); const state = urlParams.get("state"); await oauthClient.handleCallback({ code, state }); ``` ### 4. Use Authenticated Client ```typescript const client = new AtprotoClient({ baseUrl: "https://api.slices.network", sliceUri: "at://did:plc:abc/network.slices.slice/your-slice-rkey", auth: oauthClient, }); // OAuth tokens are automatically managed const album = await client.com.recordcollector.album.createRecord({ title: "Ten", artist: "Pearl Jam", releaseDate: "1991-08-27", genre: ["grunge", "alternative rock"], condition: "Mint", }); ``` ## Type Safety The generated SDK provides full TypeScript type safety: ```typescript // TypeScript knows the shape of your records const album = await client.com.recordcollector.album.getRecord({ uri }); // Type error: property 'unknownField' does not exist // album.value.unknownField // Autocomplete works for all fields album.value.title; // string album.value.artist; // string album.value.genre; // string[] album.value.releaseDate; // string // Creating records is type-checked await client.com.recordcollector.album.createRecord({ title: "Dirt", artist: "Alice in Chains", releaseDate: "1992-09-29", genre: ["grunge", "alternative metal"], condition: "Very Good Plus", // Type error: 'invalidField' is not assignable // invalidField: "This will error" }); ``` ## Advanced Patterns ### Batch Operations ```typescript // Process records in batches async function* getAllAlbums() { let cursor: string | undefined; do { const batch = await client.com.recordcollector.album.getRecords({ limit: 100, cursor, }); yield* batch.records; cursor = batch.cursor; } while (cursor); } // Use the generator for await (const album of getAllAlbums()) { console.log(`${album.value.artist} - ${album.value.title}`); } ``` ## Next Steps - [API Reference](./api-reference.md) - Complete endpoint documentation - [Concepts](./concepts.md) - Understand the architecture - [Getting Started](./getting-started.md) - Initial setup guide