/**
 * Cloudflare R2 asset backend
 *
 * S3-compatible object storage with zero egress fees
 */
import type { AssetBackend, AssetInfo, AssetRecord, R2Config } from './base.js';
export declare class R2AssetBackend implements AssetBackend {
    private client;
    private bucket;
    private publicUrl;
    constructor(config: R2Config);
    /**
     * Upload asset to R2
     */
    upload(key: string, buffer: Buffer, contentType: string): Promise<AssetInfo>;
    /**
     * Server-side copy via S3 CopyObjectCommand (R2 supports it) — no download.
     * CopySource must be URL-encoded and bucket-qualified. Overwrites the
     * destination, so migration re-runs are idempotent.
     */
    copy(srcKey: string, destKey: string): Promise<void>;
    /**
     * List assets with a given prefix
     */
    list(prefix: string): Promise<AssetInfo[]>;
    /**
     * List every asset in the bucket via paginated ListObjectsV2. Parses
     * `{type}/{id}/{filename}` out of each key and guesses content type
     * from the extension — no per-object HEAD calls.
     */
    listAll(): Promise<AssetRecord[]>;
    /**
     * Delete a single asset
     */
    delete(key: string): Promise<void>;
    /**
     * Delete all assets with a given prefix
     */
    deleteAll(prefix: string): Promise<number>;
    /**
     * Generate a short-lived pre-signed URL for a stored object.
     *
     * Stage 2 of the asset architecture: rather than handing browsers a
     * permanent public URL, the GET /api/assets/* route authorizes per-repo
     * and 302-redirects to one of these. The bucket can then be flipped to
     * private without breaking the workflow.
     *
     * Default expiry is 4 hours (14400s). Long enough that Range-request
     * continuation on multi-hour media doesn't get a SignatureExpired mid-
     * stream; short enough that out-of-band URL leakage has a bounded
     * exposure window. The leak math doesn't meaningfully shift between
     * 15 min and 4 hr — anything that exfiltrates a URL fast enough to
     * matter does so within either window.
     *
     * `method` selects the HTTP verb the URL is signed against — pre-signed
     * URLs are method-bound, so HEAD requests need a HEAD-signed URL.
     * Without this, link-unfurlers and OpenGraph crawlers that issue HEAD
     * after following a redirect get 403 SignatureDoesNotMatch from R2.
     */
    getSignedUrl(key: string, expiresIn?: number, method?: 'GET' | 'HEAD'): Promise<string>;
    /**
     * Check if an asset exists
     */
    exists(key: string): Promise<boolean>;
    /**
     * Guess content type from file extension
     */
    private guessContentType;
}
