import type { ComAtprotoRepoApplyWrites, ComAtprotoRepoStrongRef } from '@atcute/atproto';
import { type Client, ok } from '@atcute/client';
import type { ActorIdentifier, Did } from '@atcute/lexicons';
import * as TID from '@atcute/tid';
import { chunked } from '@mary/array-fns';
import { until } from '#/lib/utils/until';
/**
* Creates follow records in chunks and waits until at least one follow indexes.
*
* @param clients the appview and pds clients plus the repo did.
* @param dids actor dids to follow.
* @param via optional starter-pack reference attached to each follow.
* @returns a map of followed dids to created follow uris.
*/
export async function bulkWriteFollows(
{ appview, did, pds }: { appview: Client; did: Did; pds: Client },
dids: Did[],
via?: ComAtprotoRepoStrongRef.Main,
) {
const items = dids.map((d) => ({ did: d, rkey: TID.now() }));
const followWrites: ComAtprotoRepoApplyWrites.$input['writes'] = items.map((item) => ({
$type: 'com.atproto.repo.applyWrites#create',
collection: 'app.bsky.graph.follow',
rkey: item.rkey,
value: {
$type: 'app.bsky.graph.follow',
createdAt: new Date().toISOString(),
subject: item.did,
via,
},
}));
const chunks = chunked(followWrites, 50);
for (const batch of chunks) {
await ok(pds.post('com.atproto.repo.applyWrites', { input: { repo: did, writes: batch } }));
}
await whenFollowsIndexed(appview, did, (res) => !!res.follows.length);
const followUris = new Map<string, string>();
for (const item of items) {
followUris.set(item.did, `at://${did}/app.bsky.graph.follow/${item.rkey}`);
}
return followUris;
}
async function whenFollowsIndexed(
appview: Client,
actor: ActorIdentifier,
fn: (res: { follows: unknown[] }) => boolean,
) {
await until(5, 1e3, fn, () =>
ok(
appview.get('app.bsky.graph.getFollows', {
params: { actor, limit: 1 },
}),
),
);
}