/** A stored object's metadata (no body). */
interface StorageObjectHead {
    key: string;
    size: number;
    etag: string;
    /** Content-Type as stored (set at put; a HOST that validated bytes stores the DETECTED type). */
    contentType?: string;
    customMetadata?: Record<string, string>;
    /** Last-modified epoch ms, when the backend reports it. */
    uploadedAt?: number;
}
/** A stored object WITH its body. */
interface StorageObject extends StorageObjectHead {
    body: ReadableStream<Uint8Array>;
}
interface PutOptions {
    contentType?: string;
    cacheControl?: string;
    customMetadata?: Record<string, string>;
}
interface PutResult {
    key: string;
    size: number;
    etag: string;
}
interface ListOptions {
    prefix?: string;
    cursor?: string;
    limit?: number;
}
interface ListResult {
    objects: StorageObjectHead[];
    /** Present iff more pages remain; pass back as ListOptions.cursor. */
    cursor?: string;
}
interface PresignOptions {
    /** REQUIRED, bounded — a presigned URL is a time-boxed capability, never standing. */
    expiresIn: number;
    contentType?: string;
}
interface PresignedUrl {
    url: string;
    method: 'PUT' | 'GET';
    headers: Record<string, string>;
    expiresIn: number;
}
/** Body shapes a put accepts — matches the r2-binding leaf so the R2 adapter wraps it directly. */
type StorageBody = ReadableStream | ArrayBuffer | ArrayBufferView | Blob | Uint8Array;
/**
 * Host-agnostic object store. R2 is the FIRST reference adapter, not the lock
 * (CLAUDE.md §5). A new host's adapter slots in here without touching any consumer.
 */
interface StorageAdapter {
    put(key: string, body: StorageBody, opts?: PutOptions): Promise<PutResult>;
    /** Body + metadata. null when the key is absent (NEVER throw on absence — no enumeration oracle). */
    get(key: string): Promise<StorageObject | null>;
    /** Metadata only. null when absent. */
    head(key: string): Promise<StorageObjectHead | null>;
    /** Idempotent — deleting an absent key resolves, never throws. */
    delete(key: string): Promise<void>;
    list(opts?: ListOptions): Promise<ListResult>;
    /** Mint a browser-direct PUT capability for ONE exact key (never prefix/wildcard). */
    presignPut(key: string, opts: PresignOptions): Promise<PresignedUrl>;
    /** OPTIONAL — signed read for private buckets. Public-bucket hosts omit it. */
    presignGet?(key: string, opts: PresignOptions): Promise<PresignedUrl>;
}
type StorageErrorCode = 'invalid_key' | 'not_found' | 'backend';
interface StorageErrorShape {
    readonly isStorageError: true;
    readonly code: StorageErrorCode;
    readonly message: string;
}
declare class StorageError extends Error implements StorageErrorShape {
    readonly isStorageError: true;
    readonly code: StorageErrorCode;
    constructor(code: StorageErrorCode, message: string);
}
/** Cross-package-safe guard — use this, NOT instanceof. */
declare function isStorageError(e: unknown): e is StorageErrorShape;
/**
 * Shared exact-key guard. Throws StorageError('invalid_key') on prefix/wildcard/empty/trailing-slash.
 * Both adapters call this so presignPut enforces the single-exact-key floor identically.
 */
declare function assertExactStorageKey(key: string): void;
/** Shared expiry guard. Throws RangeError when expiresIn is not a positive finite number of seconds. */
declare function assertPresignExpiry(expiresIn: number): void;

export { type ListOptions, type ListResult, type PresignOptions, type PresignedUrl, type PutOptions, type PutResult, type StorageAdapter, type StorageBody, StorageError, type StorageErrorCode, type StorageErrorShape, type StorageObject, type StorageObjectHead, assertExactStorageKey, assertPresignExpiry, isStorageError };
