Search interface for Tangled running on a Slice

update to latest slices apis

+2
deno.json
··· 1 1 { 2 2 "imports": { 3 + "@slices/client": "jsr:@slices/client@~0.1.0-alpha.2", 4 + "@slices/oauth": "jsr:@slices/oauth@^0.4.1", 3 5 "github-colors": "npm:github-colors@^2.2.21", 4 6 "preact": "npm:preact@^10.27.1", 5 7 "preact-render-to-string": "npm:preact-render-to-string@^6.6.1",
+10
deno.lock
··· 1 1 { 2 2 "version": "5", 3 3 "specifiers": { 4 + "jsr:@slices/client@~0.1.0-alpha.2": "0.1.0-alpha.2", 4 5 "jsr:@slices/oauth@~0.3.2": "0.3.2", 6 + "jsr:@slices/oauth@~0.4.1": "0.4.1", 5 7 "npm:@resvg/resvg-js@^2.6.2": "2.6.2", 6 8 "npm:@resvg/resvg-wasm@^2.6.2": "2.6.2", 7 9 "npm:@takumi-rs/core@~0.29.8": "0.29.8", ··· 16 18 "npm:satori@~0.18.2": "0.18.2" 17 19 }, 18 20 "jsr": { 21 + "@slices/client@0.1.0-alpha.2": { 22 + "integrity": "d3c591e89ab5b7ed7988faf9428bb7b3539484c6b90005a7c66f2188cc60fe19" 23 + }, 19 24 "@slices/oauth@0.3.2": { 20 25 "integrity": "51feaa6be538a61a3278ee7f1d264ed937187d09da2be1f0a2a837128df82526" 26 + }, 27 + "@slices/oauth@0.4.1": { 28 + "integrity": "15f20df2ba81e9d1764291c8b4f6e3eb38cfc953750eeb3815872b7e22475492" 21 29 } 22 30 }, 23 31 "npm": { ··· 584 592 }, 585 593 "workspace": { 586 594 "dependencies": [ 595 + "jsr:@slices/client@~0.1.0-alpha.2", 596 + "jsr:@slices/oauth@~0.4.1", 587 597 "npm:@takumi-rs/core@~0.29.8", 588 598 "npm:@takumi-rs/helpers@~0.29.8", 589 599 "npm:github-colors@^2.2.21",
+1 -2
src/components/RecentSearches.tsx
··· 1 1 import { 2 2 type ShTangledRepo, 3 3 type AppBskyActorProfile, 4 - type Actor, 5 - type IndexedRecord, 6 4 } from "../generated_client.ts"; 5 + import { type Actor, type IndexedRecord } from "@slices/client"; 7 6 8 7 const RecentSearches = ({ 9 8 recentRecords,
+1 -1
src/components/SearchPage.tsx
··· 1 1 import Layout from "./Layout.tsx"; 2 2 import RecentSearches from "./RecentSearches.tsx"; 3 - import { type Actor, type IndexedRecord } from "../generated_client.ts"; 3 + import { type Actor, type IndexedRecord } from "@slices/client"; 4 4 5 5 const SearchPage = ({ 6 6 recentRecords,
+1 -3
src/components/SearchResults.tsx
··· 1 1 import { 2 2 type ShTangledRepo, 3 3 type AppBskyActorProfile, 4 - type Actor, 5 - type IndexedRecord, 6 - recordBlobToCdnUrl, 7 4 } from "../generated_client.ts"; 5 + import { IndexedRecord, Actor, recordBlobToCdnUrl } from "@slices/client"; 8 6 9 7 const SearchResults = ({ 10 8 results,
+347 -802
src/generated_client.ts
··· 1 1 // Generated TypeScript client for AT Protocol records 2 - // Generated at: 2025-09-03 18:04:36 UTC 3 - // Lexicons: 5 2 + // Generated at: 2025-09-14 18:49:29 UTC 3 + // Lexicons: 7 4 4 5 5 /** 6 6 * @example Usage ··· 9 9 * 10 10 * const client = new AtProtoClient( 11 11 * 'https://slices-api.fly.dev', 12 - * 'at://did:plc:bcgltzqazw5tb6k2g3ttenbj/social.slices.slice/3lx6lhk7ibk2q' 12 + * 'at://did:plc:bcgltzqazw5tb6k2g3ttenbj/network.slices.slice/3lymhhbxald2z' 13 13 * ); 14 14 * 15 - * // Get records from the sh.tangled.repo collection 16 - * const records = await client.sh.tangled.repo.getRecords(); 15 + * // Get records from the sh.tangled.feed.star collection 16 + * const records = await client.sh.tangled.feed.star.getRecords(); 17 17 * 18 18 * // Get a specific record 19 - * const record = await client.sh.tangled.repo.getRecord({ 20 - * uri: 'at://did:plc:example/sh.tangled.repo/3abc123' 19 + * const record = await client.sh.tangled.feed.star.getRecord({ 20 + * uri: 'at://did:plc:example/sh.tangled.feed.star/3abc123' 21 21 * }); 22 22 * 23 23 * // Get records with filtering and search 24 - * const filteredRecords = await client.sh.tangled.repo.getRecords({ 24 + * const filteredRecords = await client.sh.tangled.feed.star.getRecords({ 25 25 * where: { 26 26 * text: { contains: "example search term" } 27 27 * } 28 28 * }); 29 29 * 30 30 * // Use slice-level methods for cross-collection queries with type safety 31 - * const sliceRecords = await client.social.slices.slice.getSliceRecords<ShTangledRepo>({ 31 + * const sliceRecords = await client.network.slices.slice.getSliceRecords<ShTangledFeedStar>({ 32 32 * where: { 33 - * collection: { eq: 'sh.tangled.repo' } 33 + * collection: { eq: 'sh.tangled.feed.star' } 34 34 * } 35 35 * }); 36 36 * 37 37 * // Search across multiple collections using union types 38 - * const multiCollectionRecords = await client.social.slices.slice.getSliceRecords<ShTangledRepo | AppBskyActorProfile>({ 38 + * const multiCollectionRecords = await client.network.slices.slice.getSliceRecords<ShTangledFeedStar | AppBskyActorProfile>({ 39 39 * where: { 40 - * collection: { in: ['sh.tangled.repo', 'app.bsky.actor.profile'] }, 40 + * collection: { in: ['sh.tangled.feed.star', 'app.bsky.actor.profile'] }, 41 41 * text: { contains: 'example search term' }, 42 42 * did: { in: ['did:plc:user1', 'did:plc:user2'] } 43 43 * }, ··· 49 49 * ``` 50 50 */ 51 51 52 - import { OAuthClient } from "jsr:@slices/oauth@^0.3.2"; 53 - 54 - export interface RecordResponse<T> { 55 - uri: string; 56 - cid: string; 57 - did: string; 58 - collection: string; 59 - value: T; 60 - indexedAt: string; 61 - } 62 - 63 - export interface GetActorsResponse { 64 - actors: Actor[]; 65 - cursor?: string; 66 - } 67 - 68 - export interface GetRecordsResponse<T> { 69 - records: RecordResponse<T>[]; 70 - cursor?: string; 71 - } 72 - 73 - export interface CountRecordsResponse { 74 - success: boolean; 75 - count: number; 76 - message?: string; 77 - } 78 - 79 - export interface GetRecordParams { 80 - uri: string; 81 - } 82 - 83 - export type ActorWhereConditions = Partial< 84 - Record<"did" | "handle" | "indexed_at", WhereCondition> 85 - >; 86 - 87 - export interface GetActorsParams { 88 - limit?: number; 89 - cursor?: string; 90 - where?: ActorWhereConditions; 91 - } 92 - 93 - export interface IndexedRecord<T = Record<string, unknown>> { 94 - uri: string; 95 - cid: string; 96 - did: string; 97 - collection: string; 98 - value: T; 99 - indexedAt: string; 100 - } 101 - 102 - export interface Actor { 103 - did: string; 104 - handle?: string; 105 - sliceUri: string; 106 - indexedAt: string; 107 - } 108 - 109 - export interface CodegenXrpcRequest { 110 - target: string; 111 - slice: string; 112 - } 113 - 114 - export interface CodegenXrpcResponse { 115 - success: boolean; 116 - generatedCode?: string; 117 - error?: string; 118 - } 119 - 120 - export interface BulkSyncParams { 121 - collections?: string[]; 122 - externalCollections?: string[]; 123 - repos?: string[]; 124 - limitPerRepo?: number; 125 - } 126 - 127 - export interface BulkSyncOutput { 128 - success: boolean; 129 - totalRecords: number; 130 - collectionsSynced: string[]; 131 - reposProcessed: number; 132 - message: string; 133 - } 134 - 135 - export interface SyncJobResponse { 136 - success: boolean; 137 - jobId?: string; 138 - message: string; 139 - } 140 - 141 - export interface SyncJobResult { 142 - success: boolean; 143 - totalRecords: number; 144 - collectionsSynced: string[]; 145 - reposProcessed: number; 146 - message: string; 147 - } 148 - 149 - export interface JobStatus { 150 - jobId: string; 151 - status: string; 152 - createdAt: string; 153 - startedAt?: string; 154 - completedAt?: string; 155 - result?: SyncJobResult; 156 - error?: string; 157 - retryCount: number; 158 - } 159 - 160 - export interface GetJobStatusParams { 161 - jobId: string; 162 - } 163 - 164 - export interface GetJobHistoryParams { 165 - userDid: string; 166 - sliceUri: string; 167 - limit?: number; 168 - } 169 - 170 - export type GetJobHistoryResponse = JobStatus[]; 171 - 172 - export interface SyncUserCollectionsRequest { 173 - slice: string; 174 - timeoutSeconds?: number; 175 - } 176 - 177 - export interface SyncUserCollectionsResult { 178 - success: boolean; 179 - reposProcessed: number; 180 - recordsSynced: number; 181 - timedOut: boolean; 182 - message: string; 183 - } 184 - 185 - export interface JetstreamStatusResponse { 186 - connected: boolean; 187 - status: string; 188 - error?: string; 189 - } 190 - 191 - export interface CollectionStats { 192 - collection: string; 193 - recordCount: number; 194 - uniqueActors: number; 195 - } 196 - 197 - export interface SliceStatsParams { 198 - slice: string; 199 - } 200 - 201 - export interface SliceStatsOutput { 202 - success: boolean; 203 - collections: string[]; 204 - collectionStats: CollectionStats[]; 205 - totalLexicons: number; 206 - totalRecords: number; 207 - totalActors: number; 208 - message?: string; 209 - } 210 - 211 - export interface WhereCondition { 212 - eq?: string; 213 - in?: string[]; 214 - contains?: string; 215 - } 216 - 217 - export type WhereClause<T extends string = string> = { 218 - [K in T]?: WhereCondition; 219 - }; 220 - export type IndexedRecordFields = 221 - | "did" 222 - | "collection" 223 - | "uri" 224 - | "cid" 225 - | "indexedAt"; 226 - 227 - export interface SortField<TField extends string = string> { 228 - field: TField; 229 - direction: "asc" | "desc"; 230 - } 231 - 232 - export interface SliceRecordsParams<TSortField extends string = string> { 233 - slice: string; 234 - limit?: number; 235 - cursor?: string; 236 - where?: { [K in TSortField | IndexedRecordFields]?: WhereCondition }; 237 - orWhere?: { [K in TSortField | IndexedRecordFields]?: WhereCondition }; 238 - sortBy?: SortField<TSortField>[]; 239 - } 240 - 241 - export interface SliceLevelRecordsParams<TRecord = Record<string, unknown>> { 242 - slice: string; 243 - limit?: number; 244 - cursor?: string; 245 - where?: { [K in keyof TRecord | IndexedRecordFields]?: WhereCondition }; 246 - orWhere?: { [K in keyof TRecord | IndexedRecordFields]?: WhereCondition }; 247 - sortBy?: SortField[]; 248 - } 249 - 250 - export interface SliceRecordsOutput<T = Record<string, unknown>> { 251 - success: boolean; 252 - records: IndexedRecord<T>[]; 253 - cursor?: string; 254 - message?: string; 255 - } 256 - 257 - export interface UploadBlobRequest { 258 - data: ArrayBuffer | Uint8Array; 259 - mimeType: string; 260 - } 261 - 262 - export interface BlobRef { 263 - $type: string; 264 - ref: { $link: string }; 265 - mimeType: string; 266 - size: number; 267 - } 268 - 269 - export interface UploadBlobResponse { 270 - blob: BlobRef; 271 - } 272 - 273 - export interface CollectionOperations<T, TSortField extends string = string> { 274 - getRecords( 275 - params?: Omit<SliceRecordsParams<TSortField>, "slice"> 276 - ): Promise<GetRecordsResponse<T>>; 277 - getRecord(params: GetRecordParams): Promise<RecordResponse<T>>; 278 - } 279 - 280 - export interface ShTangledRepo { 281 - createdAt: string; 282 - description?: string; 283 - /** knot where the repo was created */ 284 - knot: string; 285 - /** name of the repo */ 286 - name: string; 287 - owner: string; 288 - /** source of the repo */ 289 - source?: string; 290 - /** CI runner to send jobs to and receive results from */ 291 - spindle?: string; 292 - } 293 - 294 - export type ShTangledRepoSortFields = 295 - | "createdAt" 296 - | "description" 297 - | "knot" 298 - | "name" 299 - | "owner" 300 - | "source" 301 - | "spindle"; 302 - 303 - export interface AppBskyActorProfile { 304 - /** Small image to be displayed next to posts from account. AKA, 'profile picture' */ 305 - avatar?: BlobRef; 306 - /** Larger horizontal image to display behind profile view. */ 307 - banner?: BlobRef; 308 - createdAt?: string; 309 - /** Free-form profile description text. */ 310 - description?: string; 311 - displayName?: string; 312 - joinedViaStarterPack?: ComAtprotoRepoStrongRef; 313 - /** Self-label values, specific to the Bluesky application, on the overall account. */ 314 - labels?: 315 - | ComAtprotoLabelDefs["SelfLabels"] 316 - | { 317 - $type: string; 318 - [key: string]: unknown; 319 - }; 320 - pinnedPost?: ComAtprotoRepoStrongRef; 321 - } 322 - 323 - export type AppBskyActorProfileSortFields = 324 - | "createdAt" 325 - | "description" 326 - | "displayName"; 52 + import { 53 + type BlobRef, 54 + type CountRecordsResponse, 55 + type GetRecordParams, 56 + type GetRecordsResponse, 57 + type IndexedRecordFields, 58 + type RecordResponse, 59 + SlicesClient, 60 + type SortField, 61 + type WhereCondition, 62 + } from "jsr:@slices/client@^0.1.0-alpha.2"; 63 + import { OAuthClient } from "jsr:@slices/oauth@^0.4.1"; 327 64 328 65 export interface ShTangledFeedStar { 329 66 createdAt: string; ··· 331 68 } 332 69 333 70 export type ShTangledFeedStarSortFields = "createdAt" | "subject"; 71 + 72 + export interface ComAtprotoRepoStrongRef { 73 + cid: string; 74 + uri: string; 75 + } 334 76 335 77 export interface ComAtprotoLabelDefsLabel { 336 78 /** Optionally, CID specifying the specific version of 'uri' resource this label applies to. */ ··· 385 127 values: ComAtprotoLabelDefs["SelfLabel"][]; 386 128 } 387 129 388 - export interface ComAtprotoRepoStrongRef { 389 - cid: string; 390 - uri: string; 130 + export interface AppBskyActorProfile { 131 + /** Small image to be displayed next to posts from account. AKA, 'profile picture' */ 132 + avatar?: BlobRef; 133 + /** Larger horizontal image to display behind profile view. */ 134 + banner?: BlobRef; 135 + createdAt?: string; 136 + /** Free-form profile description text. */ 137 + description?: string; 138 + displayName?: string; 139 + joinedViaStarterPack?: ComAtprotoRepoStrongRef; 140 + /** Self-label values, specific to the Bluesky application, on the overall account. */ 141 + labels?: 142 + | ComAtprotoLabelDefs["SelfLabels"] 143 + | { 144 + $type: string; 145 + [key: string]: unknown; 146 + }; 147 + pinnedPost?: ComAtprotoRepoStrongRef; 391 148 } 392 149 150 + export type AppBskyActorProfileSortFields = 151 + | "createdAt" 152 + | "description" 153 + | "displayName"; 154 + 155 + export interface ShTangledActorProfile { 156 + /** Include link to this account on Bluesky. */ 157 + bluesky: boolean; 158 + /** Free-form profile description text. */ 159 + description?: string; 160 + links?: string[]; 161 + /** Free-form location text. */ 162 + location?: string; 163 + /** Any ATURI, it is up to appviews to validate these fields. */ 164 + pinnedRepositories?: string[]; 165 + stats?: string[]; 166 + } 167 + 168 + export type ShTangledActorProfileSortFields = "description" | "location"; 169 + 170 + export interface ShTangledRepo { 171 + createdAt: string; 172 + description?: string; 173 + /** knot where the repo was created */ 174 + knot: string; 175 + /** name of the repo */ 176 + name: string; 177 + owner: string; 178 + /** source of the repo */ 179 + source?: string; 180 + /** CI runner to send jobs to and receive results from */ 181 + spindle?: string; 182 + } 183 + 184 + export type ShTangledRepoSortFields = 185 + | "createdAt" 186 + | "description" 187 + | "knot" 188 + | "name" 189 + | "owner" 190 + | "source" 191 + | "spindle"; 192 + 193 + export interface ShTangledRepoIssue { 194 + body?: string; 195 + createdAt: string; 196 + repo: string; 197 + title: string; 198 + } 199 + 200 + export type ShTangledRepoIssueSortFields = 201 + | "body" 202 + | "createdAt" 203 + | "repo" 204 + | "title"; 205 + 393 206 export interface ComAtprotoLabelDefs { 394 207 readonly Label: ComAtprotoLabelDefsLabel; 395 208 readonly LabelValueDefinition: ComAtprotoLabelDefsLabelValueDefinition; ··· 398 211 readonly SelfLabels: ComAtprotoLabelDefsSelfLabels; 399 212 } 400 213 401 - class BaseClient { 402 - protected readonly baseUrl: string; 403 - protected oauthClient?: OAuthClient; 214 + class StarFeedTangledShClient { 215 + private readonly client: SlicesClient; 404 216 405 - constructor(baseUrl: string, oauthClient?: OAuthClient) { 406 - this.baseUrl = baseUrl; 407 - this.oauthClient = oauthClient; 217 + constructor(client: SlicesClient) { 218 + this.client = client; 408 219 } 409 220 410 - protected async ensureValidToken(): Promise<void> { 411 - if (!this.oauthClient) { 412 - throw new Error("OAuth client not configured"); 413 - } 414 - 415 - await this.oauthClient.ensureValidToken(); 221 + async getRecords(params?: { 222 + limit?: number; 223 + cursor?: string; 224 + where?: { 225 + [K in ShTangledFeedStarSortFields | IndexedRecordFields]?: WhereCondition; 226 + }; 227 + orWhere?: { 228 + [K in ShTangledFeedStarSortFields | IndexedRecordFields]?: WhereCondition; 229 + }; 230 + sortBy?: SortField<ShTangledFeedStarSortFields>[]; 231 + }): Promise<GetRecordsResponse<ShTangledFeedStar>> { 232 + return await this.client.getRecords("sh.tangled.feed.star", params); 416 233 } 417 234 418 - protected async makeRequest<T = unknown>( 419 - endpoint: string, 420 - method?: "GET" | "POST" | "PUT" | "DELETE", 421 - params?: Record<string, unknown> | unknown 422 - ): Promise<T> { 423 - return this.makeRequestWithRetry(endpoint, method, params, false); 235 + async getRecord( 236 + params: GetRecordParams 237 + ): Promise<RecordResponse<ShTangledFeedStar>> { 238 + return await this.client.getRecord("sh.tangled.feed.star", params); 424 239 } 425 240 426 - private async makeRequestWithRetry<T = unknown>( 427 - endpoint: string, 428 - method?: "GET" | "POST" | "PUT" | "DELETE", 429 - params?: Record<string, unknown> | unknown, 430 - isRetry?: boolean 431 - ): Promise<T> { 432 - isRetry = isRetry ?? false; 433 - const httpMethod = method || "GET"; 434 - let url = `${this.baseUrl}/xrpc/${endpoint}`; 435 - 436 - const requestInit: RequestInit = { 437 - method: httpMethod, 438 - headers: {}, 241 + async countRecords(params?: { 242 + limit?: number; 243 + cursor?: string; 244 + where?: { 245 + [K in ShTangledFeedStarSortFields | IndexedRecordFields]?: WhereCondition; 439 246 }; 247 + orWhere?: { 248 + [K in ShTangledFeedStarSortFields | IndexedRecordFields]?: WhereCondition; 249 + }; 250 + sortBy?: SortField<ShTangledFeedStarSortFields>[]; 251 + }): Promise<CountRecordsResponse> { 252 + return await this.client.countRecords("sh.tangled.feed.star", params); 253 + } 440 254 441 - // Add authorization header if OAuth client is available 442 - if (this.oauthClient) { 443 - try { 444 - const tokens = await this.oauthClient.ensureValidToken(); 445 - if (tokens.accessToken) { 446 - (requestInit.headers as Record<string, string>)[ 447 - "Authorization" 448 - ] = `${tokens.tokenType} ${tokens.accessToken}`; 449 - } 450 - } catch (tokenError) { 451 - // For write operations, OAuth tokens are required (excluding read endpoints that use POST) 452 - const isReadEndpoint = 453 - endpoint.includes(".getRecords") || 454 - endpoint.includes(".getSliceRecords") || 455 - endpoint.includes(".stats"); 456 - if (httpMethod !== "GET" && !isReadEndpoint) { 457 - throw new Error( 458 - `Authentication required: OAuth tokens are invalid or expired. Please log in again.` 459 - ); 460 - } 255 + async createRecord( 256 + record: ShTangledFeedStar, 257 + useSelfRkey?: boolean 258 + ): Promise<{ uri: string; cid: string }> { 259 + return await this.client.createRecord( 260 + "sh.tangled.feed.star", 261 + record, 262 + useSelfRkey 263 + ); 264 + } 461 265 462 - // For read operations, continue without auth (allow read-only operations) 463 - } 464 - } 266 + async updateRecord( 267 + rkey: string, 268 + record: ShTangledFeedStar 269 + ): Promise<{ uri: string; cid: string }> { 270 + return await this.client.updateRecord("sh.tangled.feed.star", rkey, record); 271 + } 465 272 466 - if (httpMethod === "GET" && params) { 467 - const searchParams = new URLSearchParams(); 468 - Object.entries(params).forEach(([key, value]) => { 469 - if (value !== undefined && value !== null) { 470 - searchParams.append(key, String(value)); 471 - } 472 - }); 473 - const queryString = searchParams.toString(); 474 - if (queryString) { 475 - url += "?" + queryString; 476 - } 477 - } else if (httpMethod !== "GET" && params) { 478 - // Regular API endpoints expect JSON 479 - (requestInit.headers as Record<string, string>)["Content-Type"] = 480 - "application/json"; 481 - requestInit.body = JSON.stringify(params); 482 - } 273 + async deleteRecord(rkey: string): Promise<void> { 274 + return await this.client.deleteRecord("sh.tangled.feed.star", rkey); 275 + } 276 + } 483 277 484 - const response = await fetch(url, requestInit); 485 - if (!response.ok) { 486 - // Handle 404 gracefully for GET requests 487 - if (response.status === 404 && httpMethod === "GET") { 488 - return null as T; 489 - } 278 + class FeedTangledShClient { 279 + readonly star: StarFeedTangledShClient; 280 + private readonly client: SlicesClient; 490 281 491 - // Handle 401 Unauthorized - attempt token refresh and retry once 492 - const isReadEndpoint = 493 - endpoint.includes(".getRecords") || 494 - endpoint.includes(".getSliceRecords") || 495 - endpoint.includes(".stats"); 496 - if ( 497 - response.status === 401 && 498 - !isRetry && 499 - this.oauthClient && 500 - httpMethod !== "GET" && 501 - !isReadEndpoint 502 - ) { 503 - try { 504 - // Force token refresh by calling ensureValidToken again 505 - await this.oauthClient.ensureValidToken(); 506 - // Retry the request once with refreshed tokens 507 - return this.makeRequestWithRetry(endpoint, method, params, true); 508 - } catch (_refreshError) { 509 - throw new Error( 510 - `Authentication required: OAuth tokens are invalid or expired. Please log in again.` 511 - ); 512 - } 513 - } 514 - 515 - throw new Error( 516 - `Request failed: ${response.status} ${response.statusText}` 517 - ); 518 - } 519 - 520 - return (await response.json()) as T; 282 + constructor(client: SlicesClient) { 283 + this.client = client; 284 + this.star = new StarFeedTangledShClient(client); 521 285 } 522 286 } 523 287 524 - class RepoTangledShClient extends BaseClient { 525 - private readonly sliceUri: string; 288 + class ProfileActorTangledShClient { 289 + private readonly client: SlicesClient; 526 290 527 - constructor(baseUrl: string, sliceUri: string, oauthClient?: OAuthClient) { 528 - super(baseUrl, oauthClient); 529 - this.sliceUri = sliceUri; 291 + constructor(client: SlicesClient) { 292 + this.client = client; 530 293 } 531 294 532 295 async getRecords(params?: { 533 296 limit?: number; 534 297 cursor?: string; 535 298 where?: { 536 - [K in ShTangledRepoSortFields | IndexedRecordFields]?: WhereCondition; 299 + [K in 300 + | ShTangledActorProfileSortFields 301 + | IndexedRecordFields]?: WhereCondition; 537 302 }; 538 303 orWhere?: { 539 - [K in ShTangledRepoSortFields | IndexedRecordFields]?: WhereCondition; 304 + [K in 305 + | ShTangledActorProfileSortFields 306 + | IndexedRecordFields]?: WhereCondition; 540 307 }; 541 - sortBy?: SortField<ShTangledRepoSortFields>[]; 542 - }): Promise<GetRecordsResponse<ShTangledRepo>> { 543 - // Combine where and orWhere into the expected backend format 544 - const whereClause: any = params?.where ? { ...params.where } : {}; 545 - if (params?.orWhere) { 546 - whereClause.$or = params.orWhere; 547 - } 548 - 549 - const requestParams = { 550 - ...params, 551 - where: Object.keys(whereClause).length > 0 ? whereClause : undefined, 552 - orWhere: undefined, // Remove orWhere as it's now in where.$or 553 - slice: this.sliceUri, 554 - }; 555 - const result = await this.makeRequest<SliceRecordsOutput>( 556 - "sh.tangled.repo.getRecords", 557 - "POST", 558 - requestParams 559 - ); 560 - return { 561 - records: result.records.map((record) => ({ 562 - uri: record.uri, 563 - cid: record.cid, 564 - did: record.did, 565 - collection: record.collection, 566 - value: record.value as unknown as ShTangledRepo, 567 - indexedAt: record.indexedAt, 568 - })), 569 - cursor: result.cursor, 570 - }; 308 + sortBy?: SortField<ShTangledActorProfileSortFields>[]; 309 + }): Promise<GetRecordsResponse<ShTangledActorProfile>> { 310 + return await this.client.getRecords("sh.tangled.actor.profile", params); 571 311 } 572 312 573 313 async getRecord( 574 314 params: GetRecordParams 575 - ): Promise<RecordResponse<ShTangledRepo>> { 576 - const requestParams = { ...params, slice: this.sliceUri }; 577 - return await this.makeRequest<RecordResponse<ShTangledRepo>>( 578 - "sh.tangled.repo.getRecord", 579 - "GET", 580 - requestParams 581 - ); 315 + ): Promise<RecordResponse<ShTangledActorProfile>> { 316 + return await this.client.getRecord("sh.tangled.actor.profile", params); 582 317 } 583 318 584 319 async countRecords(params?: { 585 320 limit?: number; 586 321 cursor?: string; 587 322 where?: { 588 - [K in ShTangledRepoSortFields | IndexedRecordFields]?: WhereCondition; 323 + [K in 324 + | ShTangledActorProfileSortFields 325 + | IndexedRecordFields]?: WhereCondition; 589 326 }; 590 327 orWhere?: { 591 - [K in ShTangledRepoSortFields | IndexedRecordFields]?: WhereCondition; 328 + [K in 329 + | ShTangledActorProfileSortFields 330 + | IndexedRecordFields]?: WhereCondition; 592 331 }; 593 - sortBy?: SortField<ShTangledRepoSortFields>[]; 332 + sortBy?: SortField<ShTangledActorProfileSortFields>[]; 594 333 }): Promise<CountRecordsResponse> { 595 - // Combine where and orWhere into the expected backend format 596 - const whereClause: any = params?.where ? { ...params.where } : {}; 597 - if (params?.orWhere) { 598 - whereClause.$or = params.orWhere; 599 - } 600 - 601 - const requestParams = { 602 - ...params, 603 - where: Object.keys(whereClause).length > 0 ? whereClause : undefined, 604 - orWhere: undefined, // Remove orWhere as it's now in where.$or 605 - slice: this.sliceUri, 606 - }; 607 - return await this.makeRequest<CountRecordsResponse>( 608 - "sh.tangled.repo.countRecords", 609 - "POST", 610 - requestParams 611 - ); 334 + return await this.client.countRecords("sh.tangled.actor.profile", params); 612 335 } 613 336 614 337 async createRecord( 615 - record: ShTangledRepo, 338 + record: ShTangledActorProfile, 616 339 useSelfRkey?: boolean 617 340 ): Promise<{ uri: string; cid: string }> { 618 - const recordValue = { $type: "sh.tangled.repo", ...record }; 619 - const payload = { 620 - slice: this.sliceUri, 621 - ...(useSelfRkey ? { rkey: "self" } : {}), 622 - record: recordValue, 623 - }; 624 - return await this.makeRequest<{ uri: string; cid: string }>( 625 - "sh.tangled.repo.createRecord", 626 - "POST", 627 - payload 341 + return await this.client.createRecord( 342 + "sh.tangled.actor.profile", 343 + record, 344 + useSelfRkey 628 345 ); 629 346 } 630 347 631 348 async updateRecord( 632 349 rkey: string, 633 - record: ShTangledRepo 350 + record: ShTangledActorProfile 634 351 ): Promise<{ uri: string; cid: string }> { 635 - const recordValue = { $type: "sh.tangled.repo", ...record }; 636 - const payload = { 637 - slice: this.sliceUri, 352 + return await this.client.updateRecord( 353 + "sh.tangled.actor.profile", 638 354 rkey, 639 - record: recordValue, 640 - }; 641 - return await this.makeRequest<{ uri: string; cid: string }>( 642 - "sh.tangled.repo.updateRecord", 643 - "POST", 644 - payload 355 + record 645 356 ); 646 357 } 647 358 648 359 async deleteRecord(rkey: string): Promise<void> { 649 - return await this.makeRequest<void>( 650 - "sh.tangled.repo.deleteRecord", 651 - "POST", 652 - { rkey } 653 - ); 360 + return await this.client.deleteRecord("sh.tangled.actor.profile", rkey); 654 361 } 655 362 } 656 363 657 - class StarFeedTangledShClient extends BaseClient { 658 - private readonly sliceUri: string; 364 + class ActorTangledShClient { 365 + readonly profile: ProfileActorTangledShClient; 366 + private readonly client: SlicesClient; 659 367 660 - constructor(baseUrl: string, sliceUri: string, oauthClient?: OAuthClient) { 661 - super(baseUrl, oauthClient); 662 - this.sliceUri = sliceUri; 368 + constructor(client: SlicesClient) { 369 + this.client = client; 370 + this.profile = new ProfileActorTangledShClient(client); 371 + } 372 + } 373 + 374 + class IssueRepoTangledShClient { 375 + private readonly client: SlicesClient; 376 + 377 + constructor(client: SlicesClient) { 378 + this.client = client; 663 379 } 664 380 665 381 async getRecords(params?: { 666 382 limit?: number; 667 383 cursor?: string; 668 384 where?: { 669 - [K in ShTangledFeedStarSortFields | IndexedRecordFields]?: WhereCondition; 385 + [K in 386 + | ShTangledRepoIssueSortFields 387 + | IndexedRecordFields]?: WhereCondition; 670 388 }; 671 389 orWhere?: { 672 - [K in ShTangledFeedStarSortFields | IndexedRecordFields]?: WhereCondition; 390 + [K in 391 + | ShTangledRepoIssueSortFields 392 + | IndexedRecordFields]?: WhereCondition; 673 393 }; 674 - sortBy?: SortField<ShTangledFeedStarSortFields>[]; 675 - }): Promise<GetRecordsResponse<ShTangledFeedStar>> { 676 - // Combine where and orWhere into the expected backend format 677 - const whereClause: any = params?.where ? { ...params.where } : {}; 678 - if (params?.orWhere) { 679 - whereClause.$or = params.orWhere; 680 - } 681 - 682 - const requestParams = { 683 - ...params, 684 - where: Object.keys(whereClause).length > 0 ? whereClause : undefined, 685 - orWhere: undefined, // Remove orWhere as it's now in where.$or 686 - slice: this.sliceUri, 687 - }; 688 - const result = await this.makeRequest<SliceRecordsOutput>( 689 - "sh.tangled.feed.star.getRecords", 690 - "POST", 691 - requestParams 692 - ); 693 - return { 694 - records: result.records.map((record) => ({ 695 - uri: record.uri, 696 - cid: record.cid, 697 - did: record.did, 698 - collection: record.collection, 699 - value: record.value as unknown as ShTangledFeedStar, 700 - indexedAt: record.indexedAt, 701 - })), 702 - cursor: result.cursor, 703 - }; 394 + sortBy?: SortField<ShTangledRepoIssueSortFields>[]; 395 + }): Promise<GetRecordsResponse<ShTangledRepoIssue>> { 396 + return await this.client.getRecords("sh.tangled.repo.issue", params); 704 397 } 705 398 706 399 async getRecord( 707 400 params: GetRecordParams 708 - ): Promise<RecordResponse<ShTangledFeedStar>> { 709 - const requestParams = { ...params, slice: this.sliceUri }; 710 - return await this.makeRequest<RecordResponse<ShTangledFeedStar>>( 711 - "sh.tangled.feed.star.getRecord", 712 - "GET", 713 - requestParams 714 - ); 401 + ): Promise<RecordResponse<ShTangledRepoIssue>> { 402 + return await this.client.getRecord("sh.tangled.repo.issue", params); 715 403 } 716 404 717 405 async countRecords(params?: { 718 406 limit?: number; 719 407 cursor?: string; 720 408 where?: { 721 - [K in ShTangledFeedStarSortFields | IndexedRecordFields]?: WhereCondition; 409 + [K in 410 + | ShTangledRepoIssueSortFields 411 + | IndexedRecordFields]?: WhereCondition; 722 412 }; 723 413 orWhere?: { 724 - [K in ShTangledFeedStarSortFields | IndexedRecordFields]?: WhereCondition; 414 + [K in 415 + | ShTangledRepoIssueSortFields 416 + | IndexedRecordFields]?: WhereCondition; 725 417 }; 726 - sortBy?: SortField<ShTangledFeedStarSortFields>[]; 418 + sortBy?: SortField<ShTangledRepoIssueSortFields>[]; 727 419 }): Promise<CountRecordsResponse> { 728 - // Combine where and orWhere into the expected backend format 729 - const whereClause: any = params?.where ? { ...params.where } : {}; 730 - if (params?.orWhere) { 731 - whereClause.$or = params.orWhere; 732 - } 733 - 734 - const requestParams = { 735 - ...params, 736 - where: Object.keys(whereClause).length > 0 ? whereClause : undefined, 737 - orWhere: undefined, // Remove orWhere as it's now in where.$or 738 - slice: this.sliceUri, 739 - }; 740 - return await this.makeRequest<CountRecordsResponse>( 741 - "sh.tangled.feed.star.countRecords", 742 - "POST", 743 - requestParams 744 - ); 420 + return await this.client.countRecords("sh.tangled.repo.issue", params); 745 421 } 746 422 747 423 async createRecord( 748 - record: ShTangledFeedStar, 424 + record: ShTangledRepoIssue, 749 425 useSelfRkey?: boolean 750 426 ): Promise<{ uri: string; cid: string }> { 751 - const recordValue = { $type: "sh.tangled.feed.star", ...record }; 752 - const payload = { 753 - slice: this.sliceUri, 754 - ...(useSelfRkey ? { rkey: "self" } : {}), 755 - record: recordValue, 756 - }; 757 - return await this.makeRequest<{ uri: string; cid: string }>( 758 - "sh.tangled.feed.star.createRecord", 759 - "POST", 760 - payload 427 + return await this.client.createRecord( 428 + "sh.tangled.repo.issue", 429 + record, 430 + useSelfRkey 761 431 ); 762 432 } 763 433 764 434 async updateRecord( 765 435 rkey: string, 766 - record: ShTangledFeedStar 436 + record: ShTangledRepoIssue 767 437 ): Promise<{ uri: string; cid: string }> { 768 - const recordValue = { $type: "sh.tangled.feed.star", ...record }; 769 - const payload = { 770 - slice: this.sliceUri, 438 + return await this.client.updateRecord( 439 + "sh.tangled.repo.issue", 771 440 rkey, 772 - record: recordValue, 773 - }; 774 - return await this.makeRequest<{ uri: string; cid: string }>( 775 - "sh.tangled.feed.star.updateRecord", 776 - "POST", 777 - payload 441 + record 778 442 ); 779 443 } 780 444 781 445 async deleteRecord(rkey: string): Promise<void> { 782 - return await this.makeRequest<void>( 783 - "sh.tangled.feed.star.deleteRecord", 784 - "POST", 785 - { rkey } 786 - ); 446 + return await this.client.deleteRecord("sh.tangled.repo.issue", rkey); 787 447 } 788 448 } 789 449 790 - class FeedTangledShClient extends BaseClient { 791 - readonly star: StarFeedTangledShClient; 792 - private readonly sliceUri: string; 450 + class RepoTangledShClient { 451 + readonly issue: IssueRepoTangledShClient; 452 + private readonly client: SlicesClient; 453 + 454 + constructor(client: SlicesClient) { 455 + this.client = client; 456 + this.issue = new IssueRepoTangledShClient(client); 457 + } 458 + 459 + async getRecords(params?: { 460 + limit?: number; 461 + cursor?: string; 462 + where?: { 463 + [K in ShTangledRepoSortFields | IndexedRecordFields]?: WhereCondition; 464 + }; 465 + orWhere?: { 466 + [K in ShTangledRepoSortFields | IndexedRecordFields]?: WhereCondition; 467 + }; 468 + sortBy?: SortField<ShTangledRepoSortFields>[]; 469 + }): Promise<GetRecordsResponse<ShTangledRepo>> { 470 + return await this.client.getRecords("sh.tangled.repo", params); 471 + } 472 + 473 + async getRecord( 474 + params: GetRecordParams 475 + ): Promise<RecordResponse<ShTangledRepo>> { 476 + return await this.client.getRecord("sh.tangled.repo", params); 477 + } 793 478 794 - constructor(baseUrl: string, sliceUri: string, oauthClient?: OAuthClient) { 795 - super(baseUrl, oauthClient); 796 - this.sliceUri = sliceUri; 797 - this.star = new StarFeedTangledShClient(baseUrl, sliceUri, oauthClient); 479 + async countRecords(params?: { 480 + limit?: number; 481 + cursor?: string; 482 + where?: { 483 + [K in ShTangledRepoSortFields | IndexedRecordFields]?: WhereCondition; 484 + }; 485 + orWhere?: { 486 + [K in ShTangledRepoSortFields | IndexedRecordFields]?: WhereCondition; 487 + }; 488 + sortBy?: SortField<ShTangledRepoSortFields>[]; 489 + }): Promise<CountRecordsResponse> { 490 + return await this.client.countRecords("sh.tangled.repo", params); 491 + } 492 + 493 + async createRecord( 494 + record: ShTangledRepo, 495 + useSelfRkey?: boolean 496 + ): Promise<{ uri: string; cid: string }> { 497 + return await this.client.createRecord( 498 + "sh.tangled.repo", 499 + record, 500 + useSelfRkey 501 + ); 502 + } 503 + 504 + async updateRecord( 505 + rkey: string, 506 + record: ShTangledRepo 507 + ): Promise<{ uri: string; cid: string }> { 508 + return await this.client.updateRecord("sh.tangled.repo", rkey, record); 509 + } 510 + 511 + async deleteRecord(rkey: string): Promise<void> { 512 + return await this.client.deleteRecord("sh.tangled.repo", rkey); 798 513 } 799 514 } 800 515 801 - class TangledShClient extends BaseClient { 516 + class TangledShClient { 517 + readonly feed: FeedTangledShClient; 518 + readonly actor: ActorTangledShClient; 802 519 readonly repo: RepoTangledShClient; 803 - readonly feed: FeedTangledShClient; 804 - private readonly sliceUri: string; 520 + private readonly client: SlicesClient; 805 521 806 - constructor(baseUrl: string, sliceUri: string, oauthClient?: OAuthClient) { 807 - super(baseUrl, oauthClient); 808 - this.sliceUri = sliceUri; 809 - this.repo = new RepoTangledShClient(baseUrl, sliceUri, oauthClient); 810 - this.feed = new FeedTangledShClient(baseUrl, sliceUri, oauthClient); 522 + constructor(client: SlicesClient) { 523 + this.client = client; 524 + this.feed = new FeedTangledShClient(client); 525 + this.actor = new ActorTangledShClient(client); 526 + this.repo = new RepoTangledShClient(client); 811 527 } 812 528 } 813 529 814 - class ShClient extends BaseClient { 530 + class ShClient { 815 531 readonly tangled: TangledShClient; 816 - private readonly sliceUri: string; 532 + private readonly client: SlicesClient; 817 533 818 - constructor(baseUrl: string, sliceUri: string, oauthClient?: OAuthClient) { 819 - super(baseUrl, oauthClient); 820 - this.sliceUri = sliceUri; 821 - this.tangled = new TangledShClient(baseUrl, sliceUri, oauthClient); 534 + constructor(client: SlicesClient) { 535 + this.client = client; 536 + this.tangled = new TangledShClient(client); 822 537 } 823 538 } 824 539 825 - class ProfileActorBskyAppClient extends BaseClient { 826 - private readonly sliceUri: string; 540 + class ProfileActorBskyAppClient { 541 + private readonly client: SlicesClient; 827 542 828 - constructor(baseUrl: string, sliceUri: string, oauthClient?: OAuthClient) { 829 - super(baseUrl, oauthClient); 830 - this.sliceUri = sliceUri; 543 + constructor(client: SlicesClient) { 544 + this.client = client; 831 545 } 832 546 833 547 async getRecords(params?: { ··· 845 559 }; 846 560 sortBy?: SortField<AppBskyActorProfileSortFields>[]; 847 561 }): Promise<GetRecordsResponse<AppBskyActorProfile>> { 848 - // Combine where and orWhere into the expected backend format 849 - const whereClause: any = params?.where ? { ...params.where } : {}; 850 - if (params?.orWhere) { 851 - whereClause.$or = params.orWhere; 852 - } 853 - 854 - const requestParams = { 855 - ...params, 856 - where: Object.keys(whereClause).length > 0 ? whereClause : undefined, 857 - orWhere: undefined, // Remove orWhere as it's now in where.$or 858 - slice: this.sliceUri, 859 - }; 860 - const result = await this.makeRequest<SliceRecordsOutput>( 861 - "app.bsky.actor.profile.getRecords", 862 - "POST", 863 - requestParams 864 - ); 865 - return { 866 - records: result.records.map((record) => ({ 867 - uri: record.uri, 868 - cid: record.cid, 869 - did: record.did, 870 - collection: record.collection, 871 - value: record.value as unknown as AppBskyActorProfile, 872 - indexedAt: record.indexedAt, 873 - })), 874 - cursor: result.cursor, 875 - }; 562 + return await this.client.getRecords("app.bsky.actor.profile", params); 876 563 } 877 564 878 565 async getRecord( 879 566 params: GetRecordParams 880 567 ): Promise<RecordResponse<AppBskyActorProfile>> { 881 - const requestParams = { ...params, slice: this.sliceUri }; 882 - return await this.makeRequest<RecordResponse<AppBskyActorProfile>>( 883 - "app.bsky.actor.profile.getRecord", 884 - "GET", 885 - requestParams 886 - ); 568 + return await this.client.getRecord("app.bsky.actor.profile", params); 887 569 } 888 570 889 571 async countRecords(params?: { ··· 901 583 }; 902 584 sortBy?: SortField<AppBskyActorProfileSortFields>[]; 903 585 }): Promise<CountRecordsResponse> { 904 - // Combine where and orWhere into the expected backend format 905 - const whereClause: any = params?.where ? { ...params.where } : {}; 906 - if (params?.orWhere) { 907 - whereClause.$or = params.orWhere; 908 - } 909 - 910 - const requestParams = { 911 - ...params, 912 - where: Object.keys(whereClause).length > 0 ? whereClause : undefined, 913 - orWhere: undefined, // Remove orWhere as it's now in where.$or 914 - slice: this.sliceUri, 915 - }; 916 - return await this.makeRequest<CountRecordsResponse>( 917 - "app.bsky.actor.profile.countRecords", 918 - "POST", 919 - requestParams 920 - ); 586 + return await this.client.countRecords("app.bsky.actor.profile", params); 921 587 } 922 588 923 589 async createRecord( 924 590 record: AppBskyActorProfile, 925 591 useSelfRkey?: boolean 926 592 ): Promise<{ uri: string; cid: string }> { 927 - const recordValue = { $type: "app.bsky.actor.profile", ...record }; 928 - const payload = { 929 - slice: this.sliceUri, 930 - ...(useSelfRkey ? { rkey: "self" } : {}), 931 - record: recordValue, 932 - }; 933 - return await this.makeRequest<{ uri: string; cid: string }>( 934 - "app.bsky.actor.profile.createRecord", 935 - "POST", 936 - payload 593 + return await this.client.createRecord( 594 + "app.bsky.actor.profile", 595 + record, 596 + useSelfRkey 937 597 ); 938 598 } 939 599 ··· 941 601 rkey: string, 942 602 record: AppBskyActorProfile 943 603 ): Promise<{ uri: string; cid: string }> { 944 - const recordValue = { $type: "app.bsky.actor.profile", ...record }; 945 - const payload = { 946 - slice: this.sliceUri, 604 + return await this.client.updateRecord( 605 + "app.bsky.actor.profile", 947 606 rkey, 948 - record: recordValue, 949 - }; 950 - return await this.makeRequest<{ uri: string; cid: string }>( 951 - "app.bsky.actor.profile.updateRecord", 952 - "POST", 953 - payload 607 + record 954 608 ); 955 609 } 956 610 957 611 async deleteRecord(rkey: string): Promise<void> { 958 - return await this.makeRequest<void>( 959 - "app.bsky.actor.profile.deleteRecord", 960 - "POST", 961 - { rkey } 962 - ); 612 + return await this.client.deleteRecord("app.bsky.actor.profile", rkey); 963 613 } 964 614 } 965 615 966 - class ActorBskyAppClient extends BaseClient { 616 + class ActorBskyAppClient { 967 617 readonly profile: ProfileActorBskyAppClient; 968 - private readonly sliceUri: string; 618 + private readonly client: SlicesClient; 969 619 970 - constructor(baseUrl: string, sliceUri: string, oauthClient?: OAuthClient) { 971 - super(baseUrl, oauthClient); 972 - this.sliceUri = sliceUri; 973 - this.profile = new ProfileActorBskyAppClient( 974 - baseUrl, 975 - sliceUri, 976 - oauthClient 977 - ); 620 + constructor(client: SlicesClient) { 621 + this.client = client; 622 + this.profile = new ProfileActorBskyAppClient(client); 978 623 } 979 624 } 980 625 981 - class BskyAppClient extends BaseClient { 626 + class BskyAppClient { 982 627 readonly actor: ActorBskyAppClient; 983 - private readonly sliceUri: string; 628 + private readonly client: SlicesClient; 984 629 985 - constructor(baseUrl: string, sliceUri: string, oauthClient?: OAuthClient) { 986 - super(baseUrl, oauthClient); 987 - this.sliceUri = sliceUri; 988 - this.actor = new ActorBskyAppClient(baseUrl, sliceUri, oauthClient); 630 + constructor(client: SlicesClient) { 631 + this.client = client; 632 + this.actor = new ActorBskyAppClient(client); 989 633 } 990 634 } 991 635 992 - class AppClient extends BaseClient { 636 + class AppClient { 993 637 readonly bsky: BskyAppClient; 994 - private readonly sliceUri: string; 638 + private readonly client: SlicesClient; 995 639 996 - constructor(baseUrl: string, sliceUri: string, oauthClient?: OAuthClient) { 997 - super(baseUrl, oauthClient); 998 - this.sliceUri = sliceUri; 999 - this.bsky = new BskyAppClient(baseUrl, sliceUri, oauthClient); 640 + constructor(client: SlicesClient) { 641 + this.client = client; 642 + this.bsky = new BskyAppClient(client); 1000 643 } 1001 644 } 1002 645 1003 - export class AtProtoClient extends BaseClient { 646 + export class AtProtoClient extends SlicesClient { 1004 647 readonly sh: ShClient; 1005 648 readonly app: AppClient; 1006 649 readonly oauth?: OAuthClient; 1007 - private readonly sliceUri: string; 1008 650 1009 651 constructor(baseUrl: string, sliceUri: string, oauthClient?: OAuthClient) { 1010 - super(baseUrl, oauthClient); 1011 - this.sliceUri = sliceUri; 1012 - this.sh = new ShClient(baseUrl, sliceUri, oauthClient); 1013 - this.app = new AppClient(baseUrl, sliceUri, oauthClient); 652 + super(baseUrl, sliceUri, oauthClient); 653 + this.sh = new ShClient(this); 654 + this.app = new AppClient(this); 1014 655 this.oauth = this.oauthClient; 1015 656 } 1016 - 1017 - async getActors(params: GetActorsParams): Promise<GetActorsResponse> { 1018 - const requestParams = { ...params, slice: this.sliceUri }; 1019 - return await this.makeRequest<GetActorsResponse>( 1020 - "social.slices.slice.getActors", 1021 - "POST", 1022 - requestParams 1023 - ); 1024 - } 1025 - 1026 - async getSliceRecords<T = Record<string, unknown>>( 1027 - params: Omit<SliceLevelRecordsParams<T>, "slice"> 1028 - ): Promise<SliceRecordsOutput<T>> { 1029 - // Combine where and orWhere into the expected backend format 1030 - const whereClause: any = params?.where ? { ...params.where } : {}; 1031 - if (params?.orWhere) { 1032 - whereClause.$or = params.orWhere; 1033 - } 1034 - 1035 - const requestParams = { 1036 - ...params, 1037 - where: Object.keys(whereClause).length > 0 ? whereClause : undefined, 1038 - orWhere: undefined, // Remove orWhere as it's now in where.$or 1039 - slice: this.sliceUri, 1040 - }; 1041 - return await this.makeRequest<SliceRecordsOutput<T>>( 1042 - "social.slices.slice.getSliceRecords", 1043 - "POST", 1044 - requestParams 1045 - ); 1046 - } 1047 - 1048 - uploadBlob(request: UploadBlobRequest): Promise<UploadBlobResponse> { 1049 - return this.uploadBlobWithRetry(request, false); 1050 - } 1051 - 1052 - private async uploadBlobWithRetry( 1053 - request: UploadBlobRequest, 1054 - isRetry?: boolean 1055 - ): Promise<UploadBlobResponse> { 1056 - isRetry = isRetry ?? false; 1057 - // Special handling for blob upload with binary data 1058 - const httpMethod = "POST"; 1059 - const url = `${this.baseUrl}/xrpc/com.atproto.repo.uploadBlob`; 1060 - 1061 - if (!this.oauthClient) { 1062 - throw new Error("OAuth client not configured"); 1063 - } 1064 - 1065 - const tokens = await this.oauthClient.ensureValidToken(); 1066 - 1067 - const requestInit: RequestInit = { 1068 - method: httpMethod, 1069 - headers: { 1070 - "Content-Type": request.mimeType, 1071 - Authorization: `${tokens.tokenType} ${tokens.accessToken}`, 1072 - }, 1073 - body: request.data, 1074 - }; 1075 - 1076 - const response = await fetch(url, requestInit); 1077 - if (!response.ok) { 1078 - // Handle 401 Unauthorized - attempt token refresh and retry once 1079 - if (response.status === 401 && !isRetry && this.oauthClient) { 1080 - try { 1081 - // Force token refresh by calling ensureValidToken again 1082 - await this.oauthClient.ensureValidToken(); 1083 - // Retry the request once with refreshed tokens 1084 - return this.uploadBlobWithRetry(request, true); 1085 - } catch (_refreshError) { 1086 - throw new Error( 1087 - `Authentication required: OAuth tokens are invalid or expired. Please log in again.` 1088 - ); 1089 - } 1090 - } 1091 - 1092 - throw new Error( 1093 - `Blob upload failed: ${response.status} ${response.statusText}` 1094 - ); 1095 - } 1096 - 1097 - return await response.json(); 1098 - } 1099 - } 1100 - 1101 - // Utility function to convert BlobRef to CDN URL using record context 1102 - export function recordBlobToCdnUrl<T>( 1103 - record: RecordResponse<T>, 1104 - blobRef: BlobRef, 1105 - preset?: "avatar" | "banner" | "feed_thumbnail" | "feed_fullsize", 1106 - cdnBaseUrl?: string 1107 - ): string { 1108 - const cdnBase = cdnBaseUrl || "https://cdn.bsky.app/img"; 1109 - const sizePreset = preset || "feed_fullsize"; 1110 - const cid = blobRef.ref.$link; 1111 - return `${cdnBase}/${sizePreset}/plain/${record.did}/${cid}@jpeg`; 1112 657 }
+8 -9
src/main.tsx
··· 1 1 import { renderToString } from "preact-render-to-string"; 2 2 import { fromJsx } from "@takumi-rs/helpers/jsx"; 3 3 import { Renderer } from "@takumi-rs/core"; 4 + import { AtProtoClient, type AppBskyActorProfile } from "./generated_client.ts"; 5 + import SharePage from "./components/SharePage.tsx"; 6 + import SearchPage from "./components/SearchPage.tsx"; 7 + import SearchResults from "./components/SearchResults.tsx"; 8 + import RepoCard from "./components/RepoCard.tsx"; 4 9 import { 5 - AtProtoClient, 6 - type AppBskyActorProfile, 7 10 type Actor, 8 11 type IndexedRecord, 9 12 type WhereCondition, 10 13 recordBlobToCdnUrl, 11 - } from "./generated_client.ts"; 12 - import SharePage from "./components/SharePage.tsx"; 13 - import SearchPage from "./components/SearchPage.tsx"; 14 - import SearchResults from "./components/SearchResults.tsx"; 15 - import RepoCard from "./components/RepoCard.tsx"; 14 + } from "@slices/client"; 16 15 17 16 const client = new AtProtoClient( 18 - "https://slices-api.fly.dev", 19 - "at://did:plc:bcgltzqazw5tb6k2g3ttenbj/social.slices.slice/3lx6lhk7ibk2q" 17 + "https://api.slices.network", 18 + "at://did:plc:bcgltzqazw5tb6k2g3ttenbj/network.slices.slice/3lymhhbxald2z" 20 19 ); 21 20 22 21 Deno.serve(async (req) => {