Statusphere running on a slice 馃崟
1// Generated TypeScript client for AT Protocol records
2// Generated at: 2025-08-28 00:22:31 UTC
3// Lexicons: 2
4
5/**
6 * @example Usage
7 * ```ts
8 * import { AtProtoClient } from "./generated_client.ts";
9 *
10 * const client = new AtProtoClient(
11 * 'https://slices-api.fly.dev',
12 * 'at://did:plc:bcgltzqazw5tb6k2g3ttenbj/social.slices.slice/3lx5y476bws2q'
13 * );
14 *
15 * // List records from the app.bsky.actor.profile collection
16 * const records = await client.app.bsky.actor.profile.listRecords();
17 *
18 * // Get a specific record
19 * const record = await client.app.bsky.actor.profile.getRecord({
20 * uri: 'at://did:plc:example/app.bsky.actor.profile/3abc123'
21 * });
22 *
23 * // Search records in the collection
24 * const searchResults = await client.app.bsky.actor.profile.searchRecords({
25 * query: "example search term"
26 * });
27 *
28 * // Search specific field
29 * const fieldSearch = await client.app.bsky.actor.profile.searchRecords({
30 * query: "blog",
31 * field: "title"
32 * });
33 *
34 * // Serve the records as JSON
35 * Deno.serve(async () => new Response(JSON.stringify(records.records.map(r => r.value))));
36 * ```
37 */
38
39import { OAuthClient } from "@slices/oauth";
40
41export interface RecordResponse<T> {
42 uri: string;
43 cid: string;
44 did: string;
45 collection: string;
46 value: T;
47 indexedAt: string;
48}
49
50export interface ListRecordsResponse<T> {
51 records: RecordResponse<T>[];
52 cursor?: string;
53}
54
55export interface GetActorsResponse {
56 actors: Actor[];
57 cursor?: string;
58}
59
60export interface ListRecordsParams<TSortField extends string = string> {
61 author?: string;
62 authors?: string[];
63 limit?: number;
64 cursor?: string;
65 sort?:
66 | `${TSortField}:${"asc" | "desc"}`
67 | `${TSortField}:${"asc" | "desc"},${TSortField}:${"asc" | "desc"}`;
68}
69
70export interface GetRecordParams {
71 uri: string;
72}
73
74export interface GetActorsParams {
75 search?: string;
76 dids?: string[];
77 limit?: number;
78 cursor?: string;
79}
80
81export interface SearchRecordsParams<TSortField extends string = string> {
82 query: string;
83 field?: string;
84 limit?: number;
85 cursor?: string;
86 sort?:
87 | `${TSortField}:${"asc" | "desc"}`
88 | `${TSortField}:${"asc" | "desc"},${TSortField}:${"asc" | "desc"}`;
89}
90
91export interface IndexedRecord {
92 uri: string;
93 cid: string;
94 did: string;
95 collection: string;
96 value: Record<string, unknown>;
97 indexedAt: string;
98}
99
100export interface Actor {
101 did: string;
102 handle?: string;
103 sliceUri: string;
104 indexedAt: string;
105}
106
107export interface CodegenXrpcRequest {
108 target: string;
109 slice: string;
110}
111
112export interface CodegenXrpcResponse {
113 success: boolean;
114 generatedCode?: string;
115 error?: string;
116}
117
118export interface BulkSyncParams {
119 collections?: string[];
120 externalCollections?: string[];
121 repos?: string[];
122 limitPerRepo?: number;
123}
124
125export interface BulkSyncOutput {
126 success: boolean;
127 totalRecords: number;
128 collectionsSynced: string[];
129 reposProcessed: number;
130 message: string;
131}
132
133export interface SyncJobResponse {
134 success: boolean;
135 jobId?: string;
136 message: string;
137}
138
139export interface SyncJobResult {
140 success: boolean;
141 totalRecords: number;
142 collectionsSynced: string[];
143 reposProcessed: number;
144 message: string;
145}
146
147export interface JobStatus {
148 jobId: string;
149 status: string;
150 createdAt: string;
151 startedAt?: string;
152 completedAt?: string;
153 result?: SyncJobResult;
154 error?: string;
155 retryCount: number;
156}
157
158export interface GetJobStatusParams {
159 jobId: string;
160}
161
162export interface GetJobHistoryParams {
163 userDid: string;
164 sliceUri: string;
165 limit?: number;
166}
167
168export type GetJobHistoryResponse = JobStatus[];
169
170export interface CollectionStats {
171 collection: string;
172 recordCount: number;
173 uniqueActors: number;
174}
175
176export interface SliceStatsParams {
177 slice: string;
178}
179
180export interface SliceStatsOutput {
181 success: boolean;
182 collections: string[];
183 collectionStats: CollectionStats[];
184 totalLexicons: number;
185 totalRecords: number;
186 totalActors: number;
187 message?: string;
188}
189
190export interface SliceRecordsParams {
191 slice: string;
192 collection: string;
193 repo?: string;
194 limit?: number;
195 cursor?: string;
196}
197
198export interface SliceRecordsOutput {
199 success: boolean;
200 records: IndexedRecord[];
201 cursor?: string;
202 message?: string;
203}
204
205export interface UploadBlobRequest {
206 data: ArrayBuffer | Uint8Array;
207 mimeType: string;
208}
209
210export interface BlobRef {
211 $type: string;
212 ref: string;
213 mimeType: string;
214 size: number;
215}
216
217export interface UploadBlobResponse {
218 blob: BlobRef;
219}
220
221export interface CollectionOperations<T> {
222 listRecords(params?: ListRecordsParams): Promise<ListRecordsResponse<T>>;
223 getRecord(params: GetRecordParams): Promise<RecordResponse<T>>;
224 searchRecords(params: SearchRecordsParams): Promise<ListRecordsResponse<T>>;
225}
226
227export interface AppBskyActorProfileRecord {
228 /** Small image to be displayed next to posts from account. AKA, 'profile picture' */
229 avatar?: BlobRef;
230 /** Larger horizontal image to display behind profile view. */
231 banner?: BlobRef;
232 createdAt?: string;
233 /** Free-form profile description text. */
234 description?: string;
235 displayName?: string;
236}
237
238export type AppBskyActorProfileRecordSortFields =
239 | "createdAt"
240 | "description"
241 | "displayName";
242
243export interface XyzStatusphereStatusRecord {
244 createdAt: string;
245 status: string;
246}
247
248export type XyzStatusphereStatusRecordSortFields = "createdAt" | "status";
249
250class BaseClient {
251 protected readonly baseUrl: string;
252 protected oauthClient?: OAuthClient;
253
254 constructor(baseUrl: string, oauthClient?: OAuthClient) {
255 this.baseUrl = baseUrl;
256 this.oauthClient = oauthClient;
257 }
258
259 protected async ensureValidToken(): Promise<void> {
260 if (!this.oauthClient) {
261 throw new Error("OAuth client not configured");
262 }
263
264 await this.oauthClient.ensureValidToken();
265 }
266
267 protected async makeRequest<T = unknown>(
268 endpoint: string,
269 method?: "GET" | "POST" | "PUT" | "DELETE",
270 params?: Record<string, unknown> | unknown
271 ): Promise<T> {
272 return this.makeRequestWithRetry(endpoint, method, params, false);
273 }
274
275 private async makeRequestWithRetry<T = unknown>(
276 endpoint: string,
277 method?: "GET" | "POST" | "PUT" | "DELETE",
278 params?: Record<string, unknown> | unknown,
279 isRetry?: boolean
280 ): Promise<T> {
281 isRetry = isRetry ?? false;
282 const httpMethod = method || "GET";
283 let url = `${this.baseUrl}/xrpc/${endpoint}`;
284
285 const requestInit: RequestInit = {
286 method: httpMethod,
287 headers: {},
288 };
289
290 // Add authorization header if OAuth client is available
291 if (this.oauthClient) {
292 try {
293 const tokens = await this.oauthClient.ensureValidToken();
294 if (tokens.accessToken) {
295 (requestInit.headers as Record<string, string>)[
296 "Authorization"
297 ] = `${tokens.tokenType} ${tokens.accessToken}`;
298 }
299 } catch (tokenError) {
300 // For write operations, OAuth tokens are required
301 if (httpMethod !== "GET") {
302 throw new Error(
303 `Authentication required: OAuth tokens are invalid or expired. Please log in again.`
304 );
305 }
306
307 // For read operations, continue without auth (allow read-only operations)
308 }
309 }
310
311 if (httpMethod === "GET" && params) {
312 const searchParams = new URLSearchParams();
313 Object.entries(params).forEach(([key, value]) => {
314 if (value !== undefined && value !== null) {
315 searchParams.append(key, String(value));
316 }
317 });
318 const queryString = searchParams.toString();
319 if (queryString) {
320 url += "?" + queryString;
321 }
322 } else if (httpMethod !== "GET" && params) {
323 // Regular API endpoints expect JSON
324 (requestInit.headers as Record<string, string>)["Content-Type"] =
325 "application/json";
326 requestInit.body = JSON.stringify(params);
327 }
328
329 const response = await fetch(url, requestInit);
330 if (!response.ok) {
331 // Handle 404 gracefully for GET requests
332 if (response.status === 404 && httpMethod === "GET") {
333 return null as T;
334 }
335
336 // Handle 401 Unauthorized - attempt token refresh and retry once
337 if (
338 response.status === 401 &&
339 !isRetry &&
340 this.oauthClient &&
341 httpMethod !== "GET"
342 ) {
343 try {
344 // Force token refresh by calling ensureValidToken again
345 await this.oauthClient.ensureValidToken();
346 // Retry the request once with refreshed tokens
347 return this.makeRequestWithRetry(endpoint, method, params, true);
348 } catch (_refreshError) {
349 throw new Error(
350 `Authentication required: OAuth tokens are invalid or expired. Please log in again.`
351 );
352 }
353 }
354
355 throw new Error(
356 `Request failed: ${response.status} ${response.statusText}`
357 );
358 }
359
360 return (await response.json()) as T;
361 }
362}
363
364class ProfileActorBskyAppClient extends BaseClient {
365 private readonly sliceUri: string;
366
367 constructor(baseUrl: string, sliceUri: string, oauthClient?: OAuthClient) {
368 super(baseUrl, oauthClient);
369 this.sliceUri = sliceUri;
370 }
371
372 async listRecords(
373 params?: ListRecordsParams<AppBskyActorProfileRecordSortFields>
374 ): Promise<ListRecordsResponse<AppBskyActorProfileRecord>> {
375 const requestParams = { ...params, slice: this.sliceUri };
376 return await this.makeRequest<
377 ListRecordsResponse<AppBskyActorProfileRecord>
378 >("app.bsky.actor.profile.list", "GET", requestParams);
379 }
380
381 async getRecord(
382 params: GetRecordParams
383 ): Promise<RecordResponse<AppBskyActorProfileRecord>> {
384 const requestParams = { ...params, slice: this.sliceUri };
385 return await this.makeRequest<RecordResponse<AppBskyActorProfileRecord>>(
386 "app.bsky.actor.profile.get",
387 "GET",
388 requestParams
389 );
390 }
391
392 async searchRecords(
393 params: SearchRecordsParams<AppBskyActorProfileRecordSortFields>
394 ): Promise<ListRecordsResponse<AppBskyActorProfileRecord>> {
395 const requestParams = { ...params, slice: this.sliceUri };
396 return await this.makeRequest<
397 ListRecordsResponse<AppBskyActorProfileRecord>
398 >("app.bsky.actor.profile.searchRecords", "GET", requestParams);
399 }
400
401 async createRecord(
402 record: AppBskyActorProfileRecord,
403 useSelfRkey?: boolean
404 ): Promise<{ uri: string; cid: string }> {
405 const recordWithType = { $type: "app.bsky.actor.profile", ...record };
406 const payload = useSelfRkey
407 ? { ...recordWithType, rkey: "self" }
408 : recordWithType;
409 return await this.makeRequest<{ uri: string; cid: string }>(
410 "app.bsky.actor.profile.create",
411 "POST",
412 payload
413 );
414 }
415
416 async updateRecord(
417 rkey: string,
418 record: AppBskyActorProfileRecord
419 ): Promise<{ uri: string; cid: string }> {
420 const recordWithType = { $type: "app.bsky.actor.profile", ...record };
421 return await this.makeRequest<{ uri: string; cid: string }>(
422 "app.bsky.actor.profile.update",
423 "POST",
424 { rkey, record: recordWithType }
425 );
426 }
427
428 async deleteRecord(rkey: string): Promise<void> {
429 return await this.makeRequest<void>(
430 "app.bsky.actor.profile.delete",
431 "POST",
432 { rkey }
433 );
434 }
435}
436
437class ActorBskyAppClient extends BaseClient {
438 readonly profile: ProfileActorBskyAppClient;
439 private readonly sliceUri: string;
440
441 constructor(baseUrl: string, sliceUri: string, oauthClient?: OAuthClient) {
442 super(baseUrl, oauthClient);
443 this.sliceUri = sliceUri;
444 this.profile = new ProfileActorBskyAppClient(
445 baseUrl,
446 sliceUri,
447 oauthClient
448 );
449 }
450}
451
452class BskyAppClient extends BaseClient {
453 readonly actor: ActorBskyAppClient;
454 private readonly sliceUri: string;
455
456 constructor(baseUrl: string, sliceUri: string, oauthClient?: OAuthClient) {
457 super(baseUrl, oauthClient);
458 this.sliceUri = sliceUri;
459 this.actor = new ActorBskyAppClient(baseUrl, sliceUri, oauthClient);
460 }
461}
462
463class AppClient extends BaseClient {
464 readonly bsky: BskyAppClient;
465 private readonly sliceUri: string;
466
467 constructor(baseUrl: string, sliceUri: string, oauthClient?: OAuthClient) {
468 super(baseUrl, oauthClient);
469 this.sliceUri = sliceUri;
470 this.bsky = new BskyAppClient(baseUrl, sliceUri, oauthClient);
471 }
472}
473
474class StatusStatusphereXyzClient extends BaseClient {
475 private readonly sliceUri: string;
476
477 constructor(baseUrl: string, sliceUri: string, oauthClient?: OAuthClient) {
478 super(baseUrl, oauthClient);
479 this.sliceUri = sliceUri;
480 }
481
482 async listRecords(
483 params?: ListRecordsParams<XyzStatusphereStatusRecordSortFields>
484 ): Promise<ListRecordsResponse<XyzStatusphereStatusRecord>> {
485 const requestParams = { ...params, slice: this.sliceUri };
486 return await this.makeRequest<
487 ListRecordsResponse<XyzStatusphereStatusRecord>
488 >("xyz.statusphere.status.list", "GET", requestParams);
489 }
490
491 async getRecord(
492 params: GetRecordParams
493 ): Promise<RecordResponse<XyzStatusphereStatusRecord>> {
494 const requestParams = { ...params, slice: this.sliceUri };
495 return await this.makeRequest<RecordResponse<XyzStatusphereStatusRecord>>(
496 "xyz.statusphere.status.get",
497 "GET",
498 requestParams
499 );
500 }
501
502 async searchRecords(
503 params: SearchRecordsParams<XyzStatusphereStatusRecordSortFields>
504 ): Promise<ListRecordsResponse<XyzStatusphereStatusRecord>> {
505 const requestParams = { ...params, slice: this.sliceUri };
506 return await this.makeRequest<
507 ListRecordsResponse<XyzStatusphereStatusRecord>
508 >("xyz.statusphere.status.searchRecords", "GET", requestParams);
509 }
510
511 async createRecord(
512 record: XyzStatusphereStatusRecord,
513 useSelfRkey?: boolean
514 ): Promise<{ uri: string; cid: string }> {
515 const recordWithType = { $type: "xyz.statusphere.status", ...record };
516 const payload = useSelfRkey
517 ? { ...recordWithType, rkey: "self" }
518 : recordWithType;
519 return await this.makeRequest<{ uri: string; cid: string }>(
520 "xyz.statusphere.status.create",
521 "POST",
522 payload
523 );
524 }
525
526 async updateRecord(
527 rkey: string,
528 record: XyzStatusphereStatusRecord
529 ): Promise<{ uri: string; cid: string }> {
530 const recordWithType = { $type: "xyz.statusphere.status", ...record };
531 return await this.makeRequest<{ uri: string; cid: string }>(
532 "xyz.statusphere.status.update",
533 "POST",
534 { rkey, record: recordWithType }
535 );
536 }
537
538 async deleteRecord(rkey: string): Promise<void> {
539 return await this.makeRequest<void>(
540 "xyz.statusphere.status.delete",
541 "POST",
542 { rkey }
543 );
544 }
545}
546
547class StatusphereXyzClient extends BaseClient {
548 readonly status: StatusStatusphereXyzClient;
549 private readonly sliceUri: string;
550
551 constructor(baseUrl: string, sliceUri: string, oauthClient?: OAuthClient) {
552 super(baseUrl, oauthClient);
553 this.sliceUri = sliceUri;
554 this.status = new StatusStatusphereXyzClient(
555 baseUrl,
556 sliceUri,
557 oauthClient
558 );
559 }
560}
561
562class XyzClient extends BaseClient {
563 readonly statusphere: StatusphereXyzClient;
564 private readonly sliceUri: string;
565
566 constructor(baseUrl: string, sliceUri: string, oauthClient?: OAuthClient) {
567 super(baseUrl, oauthClient);
568 this.sliceUri = sliceUri;
569 this.statusphere = new StatusphereXyzClient(baseUrl, sliceUri, oauthClient);
570 }
571}
572
573export class AtProtoClient extends BaseClient {
574 readonly app: AppClient;
575 readonly xyz: XyzClient;
576 readonly oauth?: OAuthClient;
577 private readonly sliceUri: string;
578
579 constructor(baseUrl: string, sliceUri: string, oauthClient?: OAuthClient) {
580 super(baseUrl, oauthClient);
581 this.sliceUri = sliceUri;
582 this.app = new AppClient(baseUrl, sliceUri, oauthClient);
583 this.xyz = new XyzClient(baseUrl, sliceUri, oauthClient);
584 this.oauth = this.oauthClient;
585 }
586
587 async getActors(params: GetActorsParams): Promise<GetActorsResponse> {
588 const requestParams = { ...params, slice: this.sliceUri };
589 return await this.makeRequest<GetActorsResponse>(
590 "social.slices.slice.getActors",
591 "GET",
592 requestParams
593 );
594 }
595
596 uploadBlob(request: UploadBlobRequest): Promise<UploadBlobResponse> {
597 return this.uploadBlobWithRetry(request, false);
598 }
599
600 private async uploadBlobWithRetry(
601 request: UploadBlobRequest,
602 isRetry?: boolean
603 ): Promise<UploadBlobResponse> {
604 isRetry = isRetry ?? false;
605 // Special handling for blob upload with binary data
606 const httpMethod = "POST";
607 const url = `${this.baseUrl}/xrpc/com.atproto.repo.uploadBlob`;
608
609 if (!this.oauthClient) {
610 throw new Error("OAuth client not configured");
611 }
612
613 const tokens = await this.oauthClient.ensureValidToken();
614
615 const requestInit: RequestInit = {
616 method: httpMethod,
617 headers: {
618 "Content-Type": request.mimeType,
619 Authorization: `${tokens.tokenType} ${tokens.accessToken}`,
620 },
621 body: request.data,
622 };
623
624 const response = await fetch(url, requestInit);
625 if (!response.ok) {
626 // Handle 401 Unauthorized - attempt token refresh and retry once
627 if (response.status === 401 && !isRetry && this.oauthClient) {
628 try {
629 // Force token refresh by calling ensureValidToken again
630 await this.oauthClient.ensureValidToken();
631 // Retry the request once with refreshed tokens
632 return this.uploadBlobWithRetry(request, true);
633 } catch (_refreshError) {
634 throw new Error(
635 `Authentication required: OAuth tokens are invalid or expired. Please log in again.`
636 );
637 }
638 }
639
640 throw new Error(
641 `Blob upload failed: ${response.status} ${response.statusText}`
642 );
643 }
644
645 return await response.json();
646 }
647}