import { Client, XrpcResponseError, type DidString } from '@atproto/lex'; /** * Which PDS was rate limiting. `source` is the old PDS (getBlob), `destination` * is the new PDS (uploadBlob). */ export interface RateLimitInfo { side: 'source' | 'destination'; limit?: number; remaining?: number; /** * Parsed from the `ratelimit-reset` header. May be absent: browsers can only * read the header when the PDS CORS-exposes it — bsky.social does not, so a * missing `resetAt` is the expected case in production browsers, not a bug. * All UI copy needs a "usually resets within 24 hours" fallback. */ resetAt?: Date; /** * True when transferBlobs stopped itself before any 429, because * `ratelimit-remaining` dropped to the `uploadHeadroom` threshold. Lets UI * copy distinguish a deliberate early stop from an actual rate-limit hit. */ headroom?: boolean; } export interface BlobTransferResult { outcome: 'completed' | 'rate-limited'; /** How many blobs were uploaded during this call. */ uploaded: number; /** Non-429 failures; the loop logged them and continued past. */ failedCids: string[]; /** Listed but never attempted because a 429 stopped the run (lower bound). */ remainingCids: string[]; rateLimit?: RateLimitInfo; } /** * Thrown by callers (e.g. Migrator) when a 429 should abort the flow. Not thrown * by transferBlobs itself, which returns a rate-limited result instead. */ export declare class BlobRateLimitError extends Error { readonly info: RateLimitInfo; readonly uploaded: number; readonly remaining: number; constructor(info: RateLimitInfo, uploaded: number, remaining: number); } /** True for an XRPC 429 (RateLimitExceeded). */ export declare function isRateLimitError(e: unknown): e is XrpcResponseError; /** * Reads the `ratelimit-*` headers off a 429. See {@link readRateLimitHeaders} * for why every field is optional (bsky.social never CORS-exposes them). */ export declare function parseRateLimitInfo(e: XrpcResponseError, side: 'source' | 'destination'): RateLimitInfo; /** * Copies blobs from a source PDS to a destination PDS. Pages of CIDs are pulled * via `listPage(cursor)` until the cursor is undefined; each CID is fetched from * the source and uploaded to the destination. * * On a 429 from either side it stops immediately (no more attempts, no more * pages) and returns `outcome: 'rate-limited'` with the parsed rate-limit info * and the CIDs that were never attempted. Any other per-blob error is logged and * the loop continues past it. * * It also stops itself early, before any 429, once a successful upload's * `ratelimit-remaining` drops to `uploadHeadroom` — this leaves the destination * enough quota to still serve other requests (e.g. posting pictures/videos from * the app) while the limit window is active. Reported the same way as a real * 429 (`outcome: 'rate-limited'`), with `rateLimit.headroom` set to `true`. */ export declare function transferBlobs(options: { sourceClient: Client; destClient: Client; did: DidString; /** One page of CIDs; called until it returns an undefined cursor. */ listPage: (cursor: string | undefined) => Promise<{ cids: string[]; cursor?: string; }>; onProgress?: (p: { uploaded: number; listed: number; }) => void; /** * Stop uploading once `ratelimit-remaining` on a successful upload drops to * this many requests left, instead of waiting for an actual 429. Ignored * when the destination doesn't expose the header (e.g. bsky.social in a * browser). * @default 5 */ uploadHeadroom?: number; }): Promise; //# sourceMappingURL=blobTransfer.d.ts.map