import { ok } from "@atcute/client";
import { ClientResponseError, type BobbinContext } from "#lib/api/client.ts";
import type { RawCid, RawResourceUri } from "#lib/api/syntax.ts";
import { orNull } from "#lib/api/load.ts";
import { mainSchema as awaitRecordSchema } from "#lib/api/lexicons/types/org/tangled/bobbin/awaitRecord.ts";
export interface RecordRevision {
uri: RawResourceUri;
cid?: RawCid;
}
export const awaitRecord = async (ctx: BobbinContext, revision: RecordRevision) => {
return ok(
ctx.xrpc.call(awaitRecordSchema, {
params: {
uri: revision.uri,
...(revision.cid ? { cid: revision.cid } : {})
}
})
);
};
export const awaitIndexedRecord = async (
ctx: BobbinContext,
revision: RecordRevision
): Promise<void> => {
const result = await awaitRecord(ctx, revision);
if (result.status !== "indexed") {
throw new Error(result.detail || result.reason || "Bobbin rejected the record.");
}
};
export const awaitIndexedOrSaved = async (
ctx: BobbinContext,
revision: RecordRevision
): Promise<void> => {
try {
await awaitIndexedRecord(ctx, revision);
} catch (cause) {
if (cause instanceof ClientResponseError || cause instanceof TypeError) {
throw new Error("Saved. It can take a minute to show up.", { cause });
}
throw cause;
}
};
const DELETION_BUDGET_MS = 5000;
const DELETION_POLL_MS = 150;
export const awaitDeletedRecord = async (
ctx: BobbinContext,
uri: RawResourceUri,
{ budgetMs = DELETION_BUDGET_MS, pollMs = DELETION_POLL_MS } = {}
): Promise<boolean> => {
const deadline = Date.now() + budgetMs;
for (;;) {
const result = await orNull(awaitRecord(ctx, { uri }));
if (result === null) return false;
if (result.status !== "indexed") return true;
if (Date.now() >= deadline) return false;
const { promise, resolve } = Promise.withResolvers<void>();
setTimeout(resolve, pollMs);
await promise;
}
};