Search interface for Tangled running on a Slice
12
fork

Configure Feed

Select the types of activity you want to include in your feed.

update to latest slices apis

+370 -817
+2
deno.json
··· 1 { 2 "imports": { 3 "github-colors": "npm:github-colors@^2.2.21", 4 "preact": "npm:preact@^10.27.1", 5 "preact-render-to-string": "npm:preact-render-to-string@^6.6.1",
··· 1 { 2 "imports": { 3 + "@slices/client": "jsr:@slices/client@~0.1.0-alpha.2", 4 + "@slices/oauth": "jsr:@slices/oauth@^0.4.1", 5 "github-colors": "npm:github-colors@^2.2.21", 6 "preact": "npm:preact@^10.27.1", 7 "preact-render-to-string": "npm:preact-render-to-string@^6.6.1",
+10
deno.lock
··· 1 { 2 "version": "5", 3 "specifiers": { 4 "jsr:@slices/oauth@~0.3.2": "0.3.2", 5 "npm:@resvg/resvg-js@^2.6.2": "2.6.2", 6 "npm:@resvg/resvg-wasm@^2.6.2": "2.6.2", 7 "npm:@takumi-rs/core@~0.29.8": "0.29.8", ··· 16 "npm:satori@~0.18.2": "0.18.2" 17 }, 18 "jsr": { 19 "@slices/oauth@0.3.2": { 20 "integrity": "51feaa6be538a61a3278ee7f1d264ed937187d09da2be1f0a2a837128df82526" 21 } 22 }, 23 "npm": { ··· 584 }, 585 "workspace": { 586 "dependencies": [ 587 "npm:@takumi-rs/core@~0.29.8", 588 "npm:@takumi-rs/helpers@~0.29.8", 589 "npm:github-colors@^2.2.21",
··· 1 { 2 "version": "5", 3 "specifiers": { 4 + "jsr:@slices/client@~0.1.0-alpha.2": "0.1.0-alpha.2", 5 "jsr:@slices/oauth@~0.3.2": "0.3.2", 6 + "jsr:@slices/oauth@~0.4.1": "0.4.1", 7 "npm:@resvg/resvg-js@^2.6.2": "2.6.2", 8 "npm:@resvg/resvg-wasm@^2.6.2": "2.6.2", 9 "npm:@takumi-rs/core@~0.29.8": "0.29.8", ··· 18 "npm:satori@~0.18.2": "0.18.2" 19 }, 20 "jsr": { 21 + "@slices/client@0.1.0-alpha.2": { 22 + "integrity": "d3c591e89ab5b7ed7988faf9428bb7b3539484c6b90005a7c66f2188cc60fe19" 23 + }, 24 "@slices/oauth@0.3.2": { 25 "integrity": "51feaa6be538a61a3278ee7f1d264ed937187d09da2be1f0a2a837128df82526" 26 + }, 27 + "@slices/oauth@0.4.1": { 28 + "integrity": "15f20df2ba81e9d1764291c8b4f6e3eb38cfc953750eeb3815872b7e22475492" 29 } 30 }, 31 "npm": { ··· 592 }, 593 "workspace": { 594 "dependencies": [ 595 + "jsr:@slices/client@~0.1.0-alpha.2", 596 + "jsr:@slices/oauth@~0.4.1", 597 "npm:@takumi-rs/core@~0.29.8", 598 "npm:@takumi-rs/helpers@~0.29.8", 599 "npm:github-colors@^2.2.21",
+1 -2
src/components/RecentSearches.tsx
··· 1 import { 2 type ShTangledRepo, 3 type AppBskyActorProfile, 4 - type Actor, 5 - type IndexedRecord, 6 } from "../generated_client.ts"; 7 8 const RecentSearches = ({ 9 recentRecords,
··· 1 import { 2 type ShTangledRepo, 3 type AppBskyActorProfile, 4 } from "../generated_client.ts"; 5 + import { type Actor, type IndexedRecord } from "@slices/client"; 6 7 const RecentSearches = ({ 8 recentRecords,
+1 -1
src/components/SearchPage.tsx
··· 1 import Layout from "./Layout.tsx"; 2 import RecentSearches from "./RecentSearches.tsx"; 3 - import { type Actor, type IndexedRecord } from "../generated_client.ts"; 4 5 const SearchPage = ({ 6 recentRecords,
··· 1 import Layout from "./Layout.tsx"; 2 import RecentSearches from "./RecentSearches.tsx"; 3 + import { type Actor, type IndexedRecord } from "@slices/client"; 4 5 const SearchPage = ({ 6 recentRecords,
+1 -3
src/components/SearchResults.tsx
··· 1 import { 2 type ShTangledRepo, 3 type AppBskyActorProfile, 4 - type Actor, 5 - type IndexedRecord, 6 - recordBlobToCdnUrl, 7 } from "../generated_client.ts"; 8 9 const SearchResults = ({ 10 results,
··· 1 import { 2 type ShTangledRepo, 3 type AppBskyActorProfile, 4 } from "../generated_client.ts"; 5 + import { IndexedRecord, Actor, recordBlobToCdnUrl } from "@slices/client"; 6 7 const SearchResults = ({ 8 results,
+347 -802
src/generated_client.ts
··· 1 // Generated TypeScript client for AT Protocol records 2 - // Generated at: 2025-09-03 18:04:36 UTC 3 - // Lexicons: 5 4 5 /** 6 * @example Usage ··· 9 * 10 * const client = new AtProtoClient( 11 * 'https://slices-api.fly.dev', 12 - * 'at://did:plc:bcgltzqazw5tb6k2g3ttenbj/social.slices.slice/3lx6lhk7ibk2q' 13 * ); 14 * 15 - * // Get records from the sh.tangled.repo collection 16 - * const records = await client.sh.tangled.repo.getRecords(); 17 * 18 * // Get a specific record 19 - * const record = await client.sh.tangled.repo.getRecord({ 20 - * uri: 'at://did:plc:example/sh.tangled.repo/3abc123' 21 * }); 22 * 23 * // Get records with filtering and search 24 - * const filteredRecords = await client.sh.tangled.repo.getRecords({ 25 * where: { 26 * text: { contains: "example search term" } 27 * } 28 * }); 29 * 30 * // Use slice-level methods for cross-collection queries with type safety 31 - * const sliceRecords = await client.social.slices.slice.getSliceRecords<ShTangledRepo>({ 32 * where: { 33 - * collection: { eq: 'sh.tangled.repo' } 34 * } 35 * }); 36 * 37 * // Search across multiple collections using union types 38 - * const multiCollectionRecords = await client.social.slices.slice.getSliceRecords<ShTangledRepo | AppBskyActorProfile>({ 39 * where: { 40 - * collection: { in: ['sh.tangled.repo', 'app.bsky.actor.profile'] }, 41 * text: { contains: 'example search term' }, 42 * did: { in: ['did:plc:user1', 'did:plc:user2'] } 43 * }, ··· 49 * ``` 50 */ 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"; 327 328 export interface ShTangledFeedStar { 329 createdAt: string; ··· 331 } 332 333 export type ShTangledFeedStarSortFields = "createdAt" | "subject"; 334 335 export interface ComAtprotoLabelDefsLabel { 336 /** Optionally, CID specifying the specific version of 'uri' resource this label applies to. */ ··· 385 values: ComAtprotoLabelDefs["SelfLabel"][]; 386 } 387 388 - export interface ComAtprotoRepoStrongRef { 389 - cid: string; 390 - uri: string; 391 } 392 393 export interface ComAtprotoLabelDefs { 394 readonly Label: ComAtprotoLabelDefsLabel; 395 readonly LabelValueDefinition: ComAtprotoLabelDefsLabelValueDefinition; ··· 398 readonly SelfLabels: ComAtprotoLabelDefsSelfLabels; 399 } 400 401 - class BaseClient { 402 - protected readonly baseUrl: string; 403 - protected oauthClient?: OAuthClient; 404 405 - constructor(baseUrl: string, oauthClient?: OAuthClient) { 406 - this.baseUrl = baseUrl; 407 - this.oauthClient = oauthClient; 408 } 409 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(); 416 } 417 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); 424 } 425 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: {}, 439 }; 440 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 - } 461 462 - // For read operations, continue without auth (allow read-only operations) 463 - } 464 - } 465 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 - } 483 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 - } 490 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; 521 } 522 } 523 524 - class RepoTangledShClient extends BaseClient { 525 - private readonly sliceUri: string; 526 527 - constructor(baseUrl: string, sliceUri: string, oauthClient?: OAuthClient) { 528 - super(baseUrl, oauthClient); 529 - this.sliceUri = sliceUri; 530 } 531 532 async getRecords(params?: { 533 limit?: number; 534 cursor?: string; 535 where?: { 536 - [K in ShTangledRepoSortFields | IndexedRecordFields]?: WhereCondition; 537 }; 538 orWhere?: { 539 - [K in ShTangledRepoSortFields | IndexedRecordFields]?: WhereCondition; 540 }; 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 - }; 571 } 572 573 async getRecord( 574 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 - ); 582 } 583 584 async countRecords(params?: { 585 limit?: number; 586 cursor?: string; 587 where?: { 588 - [K in ShTangledRepoSortFields | IndexedRecordFields]?: WhereCondition; 589 }; 590 orWhere?: { 591 - [K in ShTangledRepoSortFields | IndexedRecordFields]?: WhereCondition; 592 }; 593 - sortBy?: SortField<ShTangledRepoSortFields>[]; 594 }): 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 - ); 612 } 613 614 async createRecord( 615 - record: ShTangledRepo, 616 useSelfRkey?: boolean 617 ): 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 628 ); 629 } 630 631 async updateRecord( 632 rkey: string, 633 - record: ShTangledRepo 634 ): Promise<{ uri: string; cid: string }> { 635 - const recordValue = { $type: "sh.tangled.repo", ...record }; 636 - const payload = { 637 - slice: this.sliceUri, 638 rkey, 639 - record: recordValue, 640 - }; 641 - return await this.makeRequest<{ uri: string; cid: string }>( 642 - "sh.tangled.repo.updateRecord", 643 - "POST", 644 - payload 645 ); 646 } 647 648 async deleteRecord(rkey: string): Promise<void> { 649 - return await this.makeRequest<void>( 650 - "sh.tangled.repo.deleteRecord", 651 - "POST", 652 - { rkey } 653 - ); 654 } 655 } 656 657 - class StarFeedTangledShClient extends BaseClient { 658 - private readonly sliceUri: string; 659 660 - constructor(baseUrl: string, sliceUri: string, oauthClient?: OAuthClient) { 661 - super(baseUrl, oauthClient); 662 - this.sliceUri = sliceUri; 663 } 664 665 async getRecords(params?: { 666 limit?: number; 667 cursor?: string; 668 where?: { 669 - [K in ShTangledFeedStarSortFields | IndexedRecordFields]?: WhereCondition; 670 }; 671 orWhere?: { 672 - [K in ShTangledFeedStarSortFields | IndexedRecordFields]?: WhereCondition; 673 }; 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 - }; 704 } 705 706 async getRecord( 707 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 - ); 715 } 716 717 async countRecords(params?: { 718 limit?: number; 719 cursor?: string; 720 where?: { 721 - [K in ShTangledFeedStarSortFields | IndexedRecordFields]?: WhereCondition; 722 }; 723 orWhere?: { 724 - [K in ShTangledFeedStarSortFields | IndexedRecordFields]?: WhereCondition; 725 }; 726 - sortBy?: SortField<ShTangledFeedStarSortFields>[]; 727 }): 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 - ); 745 } 746 747 async createRecord( 748 - record: ShTangledFeedStar, 749 useSelfRkey?: boolean 750 ): 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 761 ); 762 } 763 764 async updateRecord( 765 rkey: string, 766 - record: ShTangledFeedStar 767 ): Promise<{ uri: string; cid: string }> { 768 - const recordValue = { $type: "sh.tangled.feed.star", ...record }; 769 - const payload = { 770 - slice: this.sliceUri, 771 rkey, 772 - record: recordValue, 773 - }; 774 - return await this.makeRequest<{ uri: string; cid: string }>( 775 - "sh.tangled.feed.star.updateRecord", 776 - "POST", 777 - payload 778 ); 779 } 780 781 async deleteRecord(rkey: string): Promise<void> { 782 - return await this.makeRequest<void>( 783 - "sh.tangled.feed.star.deleteRecord", 784 - "POST", 785 - { rkey } 786 - ); 787 } 788 } 789 790 - class FeedTangledShClient extends BaseClient { 791 - readonly star: StarFeedTangledShClient; 792 - private readonly sliceUri: string; 793 794 - constructor(baseUrl: string, sliceUri: string, oauthClient?: OAuthClient) { 795 - super(baseUrl, oauthClient); 796 - this.sliceUri = sliceUri; 797 - this.star = new StarFeedTangledShClient(baseUrl, sliceUri, oauthClient); 798 } 799 } 800 801 - class TangledShClient extends BaseClient { 802 readonly repo: RepoTangledShClient; 803 - readonly feed: FeedTangledShClient; 804 - private readonly sliceUri: string; 805 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); 811 } 812 } 813 814 - class ShClient extends BaseClient { 815 readonly tangled: TangledShClient; 816 - private readonly sliceUri: string; 817 818 - constructor(baseUrl: string, sliceUri: string, oauthClient?: OAuthClient) { 819 - super(baseUrl, oauthClient); 820 - this.sliceUri = sliceUri; 821 - this.tangled = new TangledShClient(baseUrl, sliceUri, oauthClient); 822 } 823 } 824 825 - class ProfileActorBskyAppClient extends BaseClient { 826 - private readonly sliceUri: string; 827 828 - constructor(baseUrl: string, sliceUri: string, oauthClient?: OAuthClient) { 829 - super(baseUrl, oauthClient); 830 - this.sliceUri = sliceUri; 831 } 832 833 async getRecords(params?: { ··· 845 }; 846 sortBy?: SortField<AppBskyActorProfileSortFields>[]; 847 }): 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 - }; 876 } 877 878 async getRecord( 879 params: GetRecordParams 880 ): 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 - ); 887 } 888 889 async countRecords(params?: { ··· 901 }; 902 sortBy?: SortField<AppBskyActorProfileSortFields>[]; 903 }): 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 - ); 921 } 922 923 async createRecord( 924 record: AppBskyActorProfile, 925 useSelfRkey?: boolean 926 ): 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 937 ); 938 } 939 ··· 941 rkey: string, 942 record: AppBskyActorProfile 943 ): Promise<{ uri: string; cid: string }> { 944 - const recordValue = { $type: "app.bsky.actor.profile", ...record }; 945 - const payload = { 946 - slice: this.sliceUri, 947 rkey, 948 - record: recordValue, 949 - }; 950 - return await this.makeRequest<{ uri: string; cid: string }>( 951 - "app.bsky.actor.profile.updateRecord", 952 - "POST", 953 - payload 954 ); 955 } 956 957 async deleteRecord(rkey: string): Promise<void> { 958 - return await this.makeRequest<void>( 959 - "app.bsky.actor.profile.deleteRecord", 960 - "POST", 961 - { rkey } 962 - ); 963 } 964 } 965 966 - class ActorBskyAppClient extends BaseClient { 967 readonly profile: ProfileActorBskyAppClient; 968 - private readonly sliceUri: string; 969 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 - ); 978 } 979 } 980 981 - class BskyAppClient extends BaseClient { 982 readonly actor: ActorBskyAppClient; 983 - private readonly sliceUri: string; 984 985 - constructor(baseUrl: string, sliceUri: string, oauthClient?: OAuthClient) { 986 - super(baseUrl, oauthClient); 987 - this.sliceUri = sliceUri; 988 - this.actor = new ActorBskyAppClient(baseUrl, sliceUri, oauthClient); 989 } 990 } 991 992 - class AppClient extends BaseClient { 993 readonly bsky: BskyAppClient; 994 - private readonly sliceUri: string; 995 996 - constructor(baseUrl: string, sliceUri: string, oauthClient?: OAuthClient) { 997 - super(baseUrl, oauthClient); 998 - this.sliceUri = sliceUri; 999 - this.bsky = new BskyAppClient(baseUrl, sliceUri, oauthClient); 1000 } 1001 } 1002 1003 - export class AtProtoClient extends BaseClient { 1004 readonly sh: ShClient; 1005 readonly app: AppClient; 1006 readonly oauth?: OAuthClient; 1007 - private readonly sliceUri: string; 1008 1009 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); 1014 this.oauth = this.oauthClient; 1015 } 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 }
··· 1 // Generated TypeScript client for AT Protocol records 2 + // Generated at: 2025-09-14 18:49:29 UTC 3 + // Lexicons: 7 4 5 /** 6 * @example Usage ··· 9 * 10 * const client = new AtProtoClient( 11 * 'https://slices-api.fly.dev', 12 + * 'at://did:plc:bcgltzqazw5tb6k2g3ttenbj/network.slices.slice/3lymhhbxald2z' 13 * ); 14 * 15 + * // Get records from the sh.tangled.feed.star collection 16 + * const records = await client.sh.tangled.feed.star.getRecords(); 17 * 18 * // Get a specific record 19 + * const record = await client.sh.tangled.feed.star.getRecord({ 20 + * uri: 'at://did:plc:example/sh.tangled.feed.star/3abc123' 21 * }); 22 * 23 * // Get records with filtering and search 24 + * const filteredRecords = await client.sh.tangled.feed.star.getRecords({ 25 * where: { 26 * text: { contains: "example search term" } 27 * } 28 * }); 29 * 30 * // Use slice-level methods for cross-collection queries with type safety 31 + * const sliceRecords = await client.network.slices.slice.getSliceRecords<ShTangledFeedStar>({ 32 * where: { 33 + * collection: { eq: 'sh.tangled.feed.star' } 34 * } 35 * }); 36 * 37 * // Search across multiple collections using union types 38 + * const multiCollectionRecords = await client.network.slices.slice.getSliceRecords<ShTangledFeedStar | AppBskyActorProfile>({ 39 * where: { 40 + * collection: { in: ['sh.tangled.feed.star', 'app.bsky.actor.profile'] }, 41 * text: { contains: 'example search term' }, 42 * did: { in: ['did:plc:user1', 'did:plc:user2'] } 43 * }, ··· 49 * ``` 50 */ 51 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"; 64 65 export interface ShTangledFeedStar { 66 createdAt: string; ··· 68 } 69 70 export type ShTangledFeedStarSortFields = "createdAt" | "subject"; 71 + 72 + export interface ComAtprotoRepoStrongRef { 73 + cid: string; 74 + uri: string; 75 + } 76 77 export interface ComAtprotoLabelDefsLabel { 78 /** Optionally, CID specifying the specific version of 'uri' resource this label applies to. */ ··· 127 values: ComAtprotoLabelDefs["SelfLabel"][]; 128 } 129 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; 148 } 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 + 206 export interface ComAtprotoLabelDefs { 207 readonly Label: ComAtprotoLabelDefsLabel; 208 readonly LabelValueDefinition: ComAtprotoLabelDefsLabelValueDefinition; ··· 211 readonly SelfLabels: ComAtprotoLabelDefsSelfLabels; 212 } 213 214 + class StarFeedTangledShClient { 215 + private readonly client: SlicesClient; 216 217 + constructor(client: SlicesClient) { 218 + this.client = client; 219 } 220 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); 233 } 234 235 + async getRecord( 236 + params: GetRecordParams 237 + ): Promise<RecordResponse<ShTangledFeedStar>> { 238 + return await this.client.getRecord("sh.tangled.feed.star", params); 239 } 240 241 + async countRecords(params?: { 242 + limit?: number; 243 + cursor?: string; 244 + where?: { 245 + [K in ShTangledFeedStarSortFields | IndexedRecordFields]?: WhereCondition; 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 + } 254 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 + } 265 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 + } 272 273 + async deleteRecord(rkey: string): Promise<void> { 274 + return await this.client.deleteRecord("sh.tangled.feed.star", rkey); 275 + } 276 + } 277 278 + class FeedTangledShClient { 279 + readonly star: StarFeedTangledShClient; 280 + private readonly client: SlicesClient; 281 282 + constructor(client: SlicesClient) { 283 + this.client = client; 284 + this.star = new StarFeedTangledShClient(client); 285 } 286 } 287 288 + class ProfileActorTangledShClient { 289 + private readonly client: SlicesClient; 290 291 + constructor(client: SlicesClient) { 292 + this.client = client; 293 } 294 295 async getRecords(params?: { 296 limit?: number; 297 cursor?: string; 298 where?: { 299 + [K in 300 + | ShTangledActorProfileSortFields 301 + | IndexedRecordFields]?: WhereCondition; 302 }; 303 orWhere?: { 304 + [K in 305 + | ShTangledActorProfileSortFields 306 + | IndexedRecordFields]?: WhereCondition; 307 }; 308 + sortBy?: SortField<ShTangledActorProfileSortFields>[]; 309 + }): Promise<GetRecordsResponse<ShTangledActorProfile>> { 310 + return await this.client.getRecords("sh.tangled.actor.profile", params); 311 } 312 313 async getRecord( 314 params: GetRecordParams 315 + ): Promise<RecordResponse<ShTangledActorProfile>> { 316 + return await this.client.getRecord("sh.tangled.actor.profile", params); 317 } 318 319 async countRecords(params?: { 320 limit?: number; 321 cursor?: string; 322 where?: { 323 + [K in 324 + | ShTangledActorProfileSortFields 325 + | IndexedRecordFields]?: WhereCondition; 326 }; 327 orWhere?: { 328 + [K in 329 + | ShTangledActorProfileSortFields 330 + | IndexedRecordFields]?: WhereCondition; 331 }; 332 + sortBy?: SortField<ShTangledActorProfileSortFields>[]; 333 }): Promise<CountRecordsResponse> { 334 + return await this.client.countRecords("sh.tangled.actor.profile", params); 335 } 336 337 async createRecord( 338 + record: ShTangledActorProfile, 339 useSelfRkey?: boolean 340 ): Promise<{ uri: string; cid: string }> { 341 + return await this.client.createRecord( 342 + "sh.tangled.actor.profile", 343 + record, 344 + useSelfRkey 345 ); 346 } 347 348 async updateRecord( 349 rkey: string, 350 + record: ShTangledActorProfile 351 ): Promise<{ uri: string; cid: string }> { 352 + return await this.client.updateRecord( 353 + "sh.tangled.actor.profile", 354 rkey, 355 + record 356 ); 357 } 358 359 async deleteRecord(rkey: string): Promise<void> { 360 + return await this.client.deleteRecord("sh.tangled.actor.profile", rkey); 361 } 362 } 363 364 + class ActorTangledShClient { 365 + readonly profile: ProfileActorTangledShClient; 366 + private readonly client: SlicesClient; 367 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; 379 } 380 381 async getRecords(params?: { 382 limit?: number; 383 cursor?: string; 384 where?: { 385 + [K in 386 + | ShTangledRepoIssueSortFields 387 + | IndexedRecordFields]?: WhereCondition; 388 }; 389 orWhere?: { 390 + [K in 391 + | ShTangledRepoIssueSortFields 392 + | IndexedRecordFields]?: WhereCondition; 393 }; 394 + sortBy?: SortField<ShTangledRepoIssueSortFields>[]; 395 + }): Promise<GetRecordsResponse<ShTangledRepoIssue>> { 396 + return await this.client.getRecords("sh.tangled.repo.issue", params); 397 } 398 399 async getRecord( 400 params: GetRecordParams 401 + ): Promise<RecordResponse<ShTangledRepoIssue>> { 402 + return await this.client.getRecord("sh.tangled.repo.issue", params); 403 } 404 405 async countRecords(params?: { 406 limit?: number; 407 cursor?: string; 408 where?: { 409 + [K in 410 + | ShTangledRepoIssueSortFields 411 + | IndexedRecordFields]?: WhereCondition; 412 }; 413 orWhere?: { 414 + [K in 415 + | ShTangledRepoIssueSortFields 416 + | IndexedRecordFields]?: WhereCondition; 417 }; 418 + sortBy?: SortField<ShTangledRepoIssueSortFields>[]; 419 }): Promise<CountRecordsResponse> { 420 + return await this.client.countRecords("sh.tangled.repo.issue", params); 421 } 422 423 async createRecord( 424 + record: ShTangledRepoIssue, 425 useSelfRkey?: boolean 426 ): Promise<{ uri: string; cid: string }> { 427 + return await this.client.createRecord( 428 + "sh.tangled.repo.issue", 429 + record, 430 + useSelfRkey 431 ); 432 } 433 434 async updateRecord( 435 rkey: string, 436 + record: ShTangledRepoIssue 437 ): Promise<{ uri: string; cid: string }> { 438 + return await this.client.updateRecord( 439 + "sh.tangled.repo.issue", 440 rkey, 441 + record 442 ); 443 } 444 445 async deleteRecord(rkey: string): Promise<void> { 446 + return await this.client.deleteRecord("sh.tangled.repo.issue", rkey); 447 } 448 } 449 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 + } 478 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); 513 } 514 } 515 516 + class TangledShClient { 517 + readonly feed: FeedTangledShClient; 518 + readonly actor: ActorTangledShClient; 519 readonly repo: RepoTangledShClient; 520 + private readonly client: SlicesClient; 521 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); 527 } 528 } 529 530 + class ShClient { 531 readonly tangled: TangledShClient; 532 + private readonly client: SlicesClient; 533 534 + constructor(client: SlicesClient) { 535 + this.client = client; 536 + this.tangled = new TangledShClient(client); 537 } 538 } 539 540 + class ProfileActorBskyAppClient { 541 + private readonly client: SlicesClient; 542 543 + constructor(client: SlicesClient) { 544 + this.client = client; 545 } 546 547 async getRecords(params?: { ··· 559 }; 560 sortBy?: SortField<AppBskyActorProfileSortFields>[]; 561 }): Promise<GetRecordsResponse<AppBskyActorProfile>> { 562 + return await this.client.getRecords("app.bsky.actor.profile", params); 563 } 564 565 async getRecord( 566 params: GetRecordParams 567 ): Promise<RecordResponse<AppBskyActorProfile>> { 568 + return await this.client.getRecord("app.bsky.actor.profile", params); 569 } 570 571 async countRecords(params?: { ··· 583 }; 584 sortBy?: SortField<AppBskyActorProfileSortFields>[]; 585 }): Promise<CountRecordsResponse> { 586 + return await this.client.countRecords("app.bsky.actor.profile", params); 587 } 588 589 async createRecord( 590 record: AppBskyActorProfile, 591 useSelfRkey?: boolean 592 ): Promise<{ uri: string; cid: string }> { 593 + return await this.client.createRecord( 594 + "app.bsky.actor.profile", 595 + record, 596 + useSelfRkey 597 ); 598 } 599 ··· 601 rkey: string, 602 record: AppBskyActorProfile 603 ): Promise<{ uri: string; cid: string }> { 604 + return await this.client.updateRecord( 605 + "app.bsky.actor.profile", 606 rkey, 607 + record 608 ); 609 } 610 611 async deleteRecord(rkey: string): Promise<void> { 612 + return await this.client.deleteRecord("app.bsky.actor.profile", rkey); 613 } 614 } 615 616 + class ActorBskyAppClient { 617 readonly profile: ProfileActorBskyAppClient; 618 + private readonly client: SlicesClient; 619 620 + constructor(client: SlicesClient) { 621 + this.client = client; 622 + this.profile = new ProfileActorBskyAppClient(client); 623 } 624 } 625 626 + class BskyAppClient { 627 readonly actor: ActorBskyAppClient; 628 + private readonly client: SlicesClient; 629 630 + constructor(client: SlicesClient) { 631 + this.client = client; 632 + this.actor = new ActorBskyAppClient(client); 633 } 634 } 635 636 + class AppClient { 637 readonly bsky: BskyAppClient; 638 + private readonly client: SlicesClient; 639 640 + constructor(client: SlicesClient) { 641 + this.client = client; 642 + this.bsky = new BskyAppClient(client); 643 } 644 } 645 646 + export class AtProtoClient extends SlicesClient { 647 readonly sh: ShClient; 648 readonly app: AppClient; 649 readonly oauth?: OAuthClient; 650 651 constructor(baseUrl: string, sliceUri: string, oauthClient?: OAuthClient) { 652 + super(baseUrl, sliceUri, oauthClient); 653 + this.sh = new ShClient(this); 654 + this.app = new AppClient(this); 655 this.oauth = this.oauthClient; 656 } 657 }
+8 -9
src/main.tsx
··· 1 import { renderToString } from "preact-render-to-string"; 2 import { fromJsx } from "@takumi-rs/helpers/jsx"; 3 import { Renderer } from "@takumi-rs/core"; 4 import { 5 - AtProtoClient, 6 - type AppBskyActorProfile, 7 type Actor, 8 type IndexedRecord, 9 type WhereCondition, 10 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"; 16 17 const client = new AtProtoClient( 18 - "https://slices-api.fly.dev", 19 - "at://did:plc:bcgltzqazw5tb6k2g3ttenbj/social.slices.slice/3lx6lhk7ibk2q" 20 ); 21 22 Deno.serve(async (req) => {
··· 1 import { renderToString } from "preact-render-to-string"; 2 import { fromJsx } from "@takumi-rs/helpers/jsx"; 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"; 9 import { 10 type Actor, 11 type IndexedRecord, 12 type WhereCondition, 13 recordBlobToCdnUrl, 14 + } from "@slices/client"; 15 16 const client = new AtProtoClient( 17 + "https://api.slices.network", 18 + "at://did:plc:bcgltzqazw5tb6k2g3ttenbj/network.slices.slice/3lymhhbxald2z" 19 ); 20 21 Deno.serve(async (req) => {