+2
deno.json
+2
deno.json
+10
deno.lock
+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
-2
src/components/RecentSearches.tsx
+1
-1
src/components/SearchPage.tsx
+1
-1
src/components/SearchPage.tsx
+1
-3
src/components/SearchResults.tsx
+1
-3
src/components/SearchResults.tsx
+347
-802
src/generated_client.ts
+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
+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) => {