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:
import { AtprotoClient } from "./generated_client.ts";
import { OAuthClient } from "@slices/oauth";
Basic Setup#
Without Authentication (Read-Only)#
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)#
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:
// 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:
// 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#
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#
// 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#
// 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#
const rkey = "3jklmno456";
await client.com.recordcollector.album.deleteRecord(rkey);
Working with External Collections#
Access synced external collections like Bluesky profiles:
// 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#
// 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#
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:
// 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:
// 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:
// 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:
// 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:
// 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:
// 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#
// Sync current user's data (requires auth)
const syncResult = await client.syncUserCollections();
console.log(`Synced ${syncResult.recordsSynced} records`);
Error Handling#
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#
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#
const authResult = await oauthClient.authorize({
loginHint: "user.bsky.social",
});
// Redirect user to authorization URL
window.location.href = authResult.authorizationUrl;
3. Handle Callback#
// 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#
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 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#
// 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 - Complete endpoint documentation
- Concepts - Understand the architecture
- Getting Started - Initial setup guide