LocalData/README.md
Matt Bruce 4228fbc849 Update Models, Services + docs
Summary:
- Sources: Models, Services
- Docs: Proposal, README
- Added symbols: extension StorageKeys, struct UserTokenKey, typealias Value, enum KeychainAccessControl, enum KeychainAccessibility, actor EncryptionHelper (+38 more)
- Removed symbols: enum KeychainAccessControl, enum KeychainAccessibility, enum EncryptionConstants, func serialize, func deserialize, func applySecurity (+17 more)

Stats:
- 10 files changed, 1089 insertions(+), 348 deletions(-)
2026-01-18 14:53:25 -06:00

131 lines
4.0 KiB
Markdown

# LocalData
LocalData provides a typed, discoverable namespace for persisted app data across UserDefaults, Keychain, and file storage with optional encryption.
## Architecture
The package uses a clean, modular architecture with isolated actors for thread safety:
```
StorageRouter (main entry point)
├── UserDefaultsHelper
├── KeychainHelper
├── FileStorageHelper
├── EncryptionHelper
└── SyncHelper
```
## What Ships in the Package
### Protocols
- **StorageKey** - Define storage configuration for each data type
- **StorageProviding** - Abstraction for storage operations
### Services (Actors)
- **StorageRouter** - Main entry point for all storage operations
- **KeychainHelper** - Secure keychain storage
- **EncryptionHelper** - AES-256-GCM encryption with PBKDF2
- **FileStorageHelper** - File system operations
- **UserDefaultsHelper** - UserDefaults with suite support
- **SyncHelper** - WatchConnectivity sync
### Models
- **StorageDomain** - userDefaults, keychain, fileSystem, encryptedFileSystem
- **SecurityPolicy** - none, keychain, encrypted (AES-256)
- **Serializer** - JSON, plist, Data, or custom
- **PlatformAvailability** - all, phoneOnly, watchOnly, phoneWithWatchSync
- **SyncPolicy** - never, manual, automaticSmall
- **KeychainAccessibility** - All 7 iOS accessibility options
- **KeychainAccessControl** - All 6 access control options (biometry, passcode, etc.)
- **FileDirectory** - documents, caches, custom URL
- **StorageError** - Comprehensive error types
- **AnyCodable** - Type-erased Codable for mixed-type payloads
## Usage
### 1. Define Keys
Extend `StorageKeys` with your own key types:
```swift
import LocalData
extension StorageKeys {
struct UserTokenKey: StorageKey {
typealias Value = String
let name = "user_token"
let domain: StorageDomain = .keychain(service: "com.myapp")
let security: SecurityPolicy = .keychain(
accessibility: .afterFirstUnlock,
accessControl: .biometryAny
)
let serializer: Serializer<String> = .json
let owner = "AuthService"
let availability: PlatformAvailability = .phoneOnly
let syncPolicy: SyncPolicy = .never
}
}
```
### 2. Use StorageRouter
```swift
// Save
let key = StorageKeys.UserTokenKey()
try await StorageRouter.shared.set("token123", for: key)
// Retrieve
let token = try await StorageRouter.shared.get(key)
// Remove
try await StorageRouter.shared.remove(key)
```
## Storage Domains
| Domain | Use Case |
|--------|----------|
| `userDefaults` | Preferences, small settings |
| `keychain` | Credentials, tokens, sensitive data |
| `fileSystem` | Documents, cached data, large files |
| `encryptedFileSystem` | Sensitive files with AES-256 encryption |
## Security Options
### Keychain Accessibility
- `whenUnlocked` - Only when device unlocked
- `afterFirstUnlock` - After first unlock until restart
- `whenUnlockedThisDeviceOnly` - No migration to new device
- `afterFirstUnlockThisDeviceOnly` - No migration
- `always` - Always accessible (least secure)
- `alwaysThisDeviceOnly` - Always, no migration
- `whenPasscodeSetThisDeviceOnly` - Requires passcode
### Access Control
- `userPresence` - Any authentication
- `biometryAny` - Face ID or Touch ID
- `biometryCurrentSet` - Current enrolled biometric only
- `devicePasscode` - Passcode only
- `biometryAnyOrDevicePasscode` - Biometric preferred, passcode fallback
- `biometryCurrentSetOrDevicePasscode` - Current biometric or passcode
### Encryption
- AES-256-GCM with PBKDF2-SHA256 key derivation
- Configurable iteration count
- Master key stored securely in keychain
## Sync Behavior
StorageRouter can sync data to Apple Watch via WCSession when:
- `availability` is `.all` or `.phoneWithWatchSync`
- `syncPolicy` is `.manual` or `.automaticSmall` (≤100KB)
- WCSession is activated and watch is paired
The app owns WCSession activation and handling incoming updates.
## Platforms
- iOS 17+
- watchOS 10+
## Sample App
See `SecureStorgageSample` for working examples of all storage domains and security options.