import Foundation /// Why a store operation failed. The three cases are kept apart because each /// one asks for a different reaction, and folding them together is how a /// locked phone ends up deleting the changes it was keeping. public enum BlobStoreError: Error, Sendable, Equatable { /// The device is locked, file protection refused the read, or the Keychain /// would not answer. Retry later (on `.active` or unlock). Never read this /// as "empty", and never delete anything because of it. case unavailable /// The account's key is gone (`errSecItemNotFound`) while its directory /// still held sealed files. The store has already removed that directory, /// because nothing in it can ever be read again; the caller reports the /// loss once and carries on with an empty store. case keyLost /// One file failed its authentication tag. The store has removed that /// file, and only that file; the path is given so it can be reported once. case corrupt(String) } /// Durable bytes by relative path, for one account. `NativeSealedStore` is the /// real one; tests use `MemoryBlobStore` and `FaultingBlobStore`. /// /// Paths are relative, `/`-separated, and never renamed once written: a /// sealed file is bound to its path. public protocol BlobStore: Sendable { /// Atomic: after a crash the path holds either the old bytes or the new /// ones. Durable on return means it survives a process kill, not a power /// loss. func write(_ data: Data, to path: String) throws /// nil when there is no such file. func read(_ path: String) throws -> Data? /// The names (not paths) of the files directly in `directory`, sorted. An /// absent directory lists as empty. func list(_ directory: String) throws -> [String] /// Removes a file or a directory. Removing what is not there succeeds. func remove(_ path: String) throws }