import type { ModuleStorageBody, ModuleStorageListing, ModuleStorageListOptions, ModuleStorageObject, ModuleStorageObjectMetadata, ModuleStoragePutOptions, ModuleStorageReadUrl, } from "@ekklesia/contracts"; /** * The seam between Core's module-facing storage facade and whatever actually stores bytes. * * This is the **portability boundary made executable**. Everything above it — the scoped facade, * binding resolution, lifecycle eligibility, diagnostics — is provider-agnostic and would be * identical in a standalone host. Everything below it is one implementation * (`createS3StorageProvider`) that could be replaced without any module changing a line. * * It deliberately speaks in **physical buckets**: resolving a module's logical bucket to a physical * one has already happened by the time anything reaches here (`./binding.js`), so a provider never * has to know what a module is. That split is what keeps scoping enforcement in exactly one place * rather than duplicated into every provider. * * Also deliberately absent: any operation that *destroys* a bucket. Core never deletes a bucket, so * there is no method to call by mistake, and disabling or removing a module cannot take data with it * (ADR 0030). */ export interface StorageProvider { /** * Makes sure a physical bucket exists, idempotently. * * Called lazily on first use of a binding rather than at startup, so a storage outage can never * prevent the platform from booting. */ ensureBucket(physicalBucket: string): Promise; list(physicalBucket: string, options: ModuleStorageListOptions): Promise; head(physicalBucket: string, key: string): Promise; get(physicalBucket: string, key: string): Promise; put( physicalBucket: string, key: string, body: ModuleStorageBody, options: ModuleStoragePutOptions, ): Promise; delete(physicalBucket: string, key: string): Promise; /** * A URL a browser can read one object from directly, valid for `expiresIn` seconds. * * The seconds and the download filename arrive already validated against the contract's bounds — * a provider enforces neither, so two providers can never disagree about what a module is allowed * to ask for. * * A provider that cannot issue such a URL has no business implementing this interface: the * facility is part of the storage contract, not an optional extra a module has to feel around for. * An implementation with no notion of signing could satisfy it by minting its own opaque token. */ createReadUrl( physicalBucket: string, key: string, options: { readonly expiresInSeconds: number; readonly downloadFilename?: string | undefined; }, ): Promise; /** A cheap reachability probe for diagnostics only. Its result is **never** persisted and never * influences a module's lifecycle state — a provider being briefly unreachable is an operational * condition, not an administrative decision (ADR 0030). */ probe(): Promise; /** Releases provider connections. Called once, during API shutdown. */ close(): Promise; }