Add StorageKey, updatedLogs; Remove StorageKeys, AppGroupUserDefaultsKey, Value, AppGroupUserProfileKey (+25 more)

This commit is contained in:
Matt Bruce 2026-01-16 21:06:01 -06:00
parent e025e435bc
commit 6d5c916f95
32 changed files with 443 additions and 444 deletions

View File

@ -6,31 +6,31 @@ import SharedKit
struct AppStorageCatalog: StorageKeyCatalog {
var allKeys: [AnyStorageKey] {
[
.key(StorageKeys.AppVersionKey()),
.key(StorageKeys.UserPreferencesKey()),
.key(StorageKeys.CredentialsKey()),
.key(StorageKeys.LastLocationKey()),
.key(StorageKeys.APITokenKey()),
.key(StorageKeys.UserProfileFileKey()),
.key(StorageKeys.CachedDataKey()),
.key(StorageKeys.SettingsPlistKey()),
.key(StorageKeys.SessionLogsKey()),
.key(StorageKeys.PrivateNotesKey()),
.key(StorageKeys.ExternalSessionLogsKey()),
.key(StorageKeys.WatchVibrationKey()),
.key(StorageKeys.SyncableSettingKey()),
.key(StorageKeys.ExternalKeyMaterialKey()),
.key(StorageKeys.AppGroupUserDefaultsKey()),
.key(StorageKeys.AppGroupUserProfileKey()),
.key(StorageKeys.LegacyMigrationSourceKey()),
.key(StorageKeys.ModernMigrationDestinationKey()),
.key(StorageKeys.LegacyProfileNameKey()),
.key(StorageKeys.ModernProfileNameKey()),
.key(StorageKeys.LegacyNotificationSettingKey()),
.key(StorageKeys.LegacyThemeSettingKey()),
.key(StorageKeys.ModernUnifiedSettingsKey()),
.key(StorageKeys.LegacyAppModeKey()),
.key(StorageKeys.ModernAppModeKey())
.key(StorageKey.appVersion),
.key(StorageKey.userPreferences),
.key(StorageKey.credentials),
.key(StorageKey.lastLocation),
.key(StorageKey.apiToken),
.key(StorageKey.userProfileFile),
.key(StorageKey.cachedData),
.key(StorageKey.settingsPlist),
.key(StorageKey.sessionLogs),
.key(StorageKey.privateNotes),
.key(StorageKey.externalSessionLogs),
.key(StorageKey.watchVibration),
.key(StorageKey.syncableSetting),
.key(StorageKey.externalKeyMaterial),
.key(StorageKey.appGroupUserDefaults),
.key(StorageKey.appGroupUserProfile),
.key(StorageKey.legacyMigrationSource),
.key(StorageKey.modernMigrationDestination),
.key(StorageKey.legacyProfileName),
.key(StorageKey.modernProfileName),
.key(StorageKey.legacyNotificationSetting),
.key(StorageKey.legacyThemeSetting),
.key(StorageKey.modernUnifiedSettings),
.key(StorageKey.legacyAppMode),
.key(StorageKey.modernAppMode)
]
}
}

View File

@ -9,8 +9,8 @@ struct ExternalKeyMaterialProvider: KeyMaterialProviding {
}
func keyMaterial(for keyName: String) async throws -> Data {
let key = StorageKeys.ExternalKeyMaterialKey()
if let existing = try await StorageRouter.shared.get(key) as Data? {
let key = StorageKey.externalKeyMaterial
if let existing = try? await StorageRouter.shared.get(key) {
return existing
}

View File

@ -1,21 +1,20 @@
import Foundation
import LocalData
import SharedKit
extension StorageKeys {
extension StorageKey where Value == String {
/// Stores a shared setting in App Group UserDefaults.
/// - Domain: App Group UserDefaults
/// - Security: None
/// - Sync: Never
struct AppGroupUserDefaultsKey: StorageKey {
typealias Value = String
let name = "app_group_setting"
let domain: StorageDomain = .appGroupUserDefaults(identifier: StorageServiceIdentifiers.appGroupIdentifier)
let security: SecurityPolicy = .none
let serializer: Serializer<String> = .json
let owner = "SampleApp"
let description = "Stores a shared setting readable by app extensions."
let availability: PlatformAvailability = .phoneOnly
let syncPolicy: SyncPolicy = .never
}
nonisolated static let appGroupUserDefaults = StorageKey(
name: "app_group_setting",
domain: .appGroupUserDefaults(identifier: StorageServiceIdentifiers.appGroupIdentifier),
security: .none,
serializer: .json,
owner: "SampleApp",
description: "Stores a shared setting readable by app extensions.",
availability: .phoneOnly,
syncPolicy: .never
)
}

View File

@ -2,29 +2,40 @@ import Foundation
import LocalData
import SharedKit
extension StorageKeys {
extension StorageKey where Value == UserProfile {
/// Stores a shared user profile in the App Group container.
/// - Domain: App Group File System
/// - Security: None
/// - Sync: Never
struct AppGroupUserProfileKey: StorageKey {
typealias Value = UserProfile
nonisolated static let appGroupUserProfile = StorageKey(
name: "app_group_user_profile.json",
domain: .appGroupFileSystem(
identifier: StorageServiceIdentifiers.appGroupIdentifier,
directory: .documents
),
security: .none,
serializer: .json,
owner: "SampleApp",
description: "Stores a profile shared between the app and extensions.",
availability: .phoneOnly,
syncPolicy: .never
)
let directory: FileDirectory
let name = "app_group_user_profile.json"
let security: SecurityPolicy = .none
let serializer: Serializer<UserProfile> = .json
let owner = "SampleApp"
let description = "Stores a profile shared between the app and extensions."
let availability: PlatformAvailability = .phoneOnly
let syncPolicy: SyncPolicy = .never
init(directory: FileDirectory = .documents) {
self.directory = directory
}
var domain: StorageDomain {
.appGroupFileSystem(identifier: StorageServiceIdentifiers.appGroupIdentifier, directory: directory)
}
nonisolated static func appGroupUserProfileKey(
directory: FileDirectory = .documents
) -> StorageKey {
StorageKey(
name: "app_group_user_profile.json",
domain: .appGroupFileSystem(
identifier: StorageServiceIdentifiers.appGroupIdentifier,
directory: directory
),
security: .none,
serializer: .json,
owner: "SampleApp",
description: "Stores a profile shared between the app and extensions.",
availability: .phoneOnly,
syncPolicy: .never
)
}
}

View File

@ -2,21 +2,19 @@ import Foundation
import LocalData
import SharedKit
extension StorageKeys {
extension StorageKey where Value == [String: AnyCodable] {
/// Stores user preferences in App Group UserDefaults.
/// - Domain: App Group UserDefaults
/// - Security: None
/// - Sync: Never
struct UserPreferencesKey: StorageKey {
typealias Value = [String: AnyCodable]
let name = "user_preferences"
let domain: StorageDomain = .appGroupUserDefaults(identifier: StorageServiceIdentifiers.appGroupIdentifier)
let security: SecurityPolicy = .none
let serializer: Serializer<[String: AnyCodable]> = .json
let owner = "SampleApp"
let description = "Stores shared user preferences for app configuration screens."
let availability: PlatformAvailability = .all
let syncPolicy: SyncPolicy = .never
}
nonisolated static let userPreferences = StorageKey(
name: "user_preferences",
domain: .appGroupUserDefaults(identifier: StorageServiceIdentifiers.appGroupIdentifier),
security: .none,
serializer: .json,
owner: "SampleApp",
description: "Stores shared user preferences for app configuration screens.",
availability: .all,
syncPolicy: .never
)
}

View File

@ -1,20 +1,16 @@
import Foundation
import LocalData
extension StorageKeys {
extension StorageKey where Value == [String] {
/// Stores session logs with encryption using external key material.
struct ExternalSessionLogsKey: StorageKey {
typealias Value = [String]
let name = "external_session_logs.json"
let domain: StorageDomain = .encryptedFileSystem(directory: .caches)
let security: SecurityPolicy = .encrypted(
.external(source: SampleKeyMaterialSources.external)
nonisolated static let externalSessionLogs = StorageKey(
name: "external_session_logs.json",
domain: .encryptedFileSystem(directory: .caches),
security: .encrypted(.external(source: SampleKeyMaterialSources.external)),
serializer: .json,
owner: "SampleApp",
description: "Stores session logs encrypted with external key material.",
availability: .phoneOnly,
syncPolicy: .never
)
let serializer: Serializer<[String]> = .json
let owner = "SampleApp"
let description = "Stores session logs encrypted with external key material."
let availability: PlatformAvailability = .phoneOnly
let syncPolicy: SyncPolicy = .never
}
}

View File

@ -1,20 +1,16 @@
import Foundation
import LocalData
extension StorageKeys {
extension StorageKey where Value == String {
/// Stores private notes with encryption.
struct PrivateNotesKey: StorageKey {
typealias Value = String
let name = "private_notes.enc"
let domain: StorageDomain = .encryptedFileSystem(directory: .documents)
let security: SecurityPolicy = .encrypted(
.aes256(keyDerivation: .pbkdf2(iterations: 50_000))
nonisolated static let privateNotes = StorageKey(
name: "private_notes.enc",
domain: .encryptedFileSystem(directory: .documents),
security: .encrypted(.aes256(keyDerivation: .pbkdf2(iterations: 50_000))),
serializer: .json,
owner: "SampleApp",
description: "Stores private notes encrypted at rest.",
availability: .phoneOnly,
syncPolicy: .never
)
let serializer: Serializer<String> = .json
let owner = "SampleApp"
let description = "Stores private notes encrypted at rest."
let availability: PlatformAvailability = .phoneOnly
let syncPolicy: SyncPolicy = .never
}
}

View File

@ -1,24 +1,30 @@
import Foundation
import LocalData
extension StorageKeys {
extension StorageKey where Value == [String] {
/// Stores session logs with full encryption.
/// Configurable PBKDF2 iterations.
struct SessionLogsKey: StorageKey {
typealias Value = [String]
nonisolated static let sessionLogs = StorageKey(
name: "session_logs.json",
domain: .encryptedFileSystem(directory: .caches),
security: .encrypted(.aes256(keyDerivation: .pbkdf2(iterations: 10_000))),
serializer: .json,
owner: "SampleApp",
description: "Stores session logs encrypted with PBKDF2-derived keys.",
availability: .phoneOnly,
syncPolicy: .never
)
let name = "session_logs.json"
let domain: StorageDomain = .encryptedFileSystem(directory: .caches)
let security: SecurityPolicy
let serializer: Serializer<[String]> = .json
let owner = "SampleApp"
let description = "Stores session logs encrypted with PBKDF2-derived keys."
let availability: PlatformAvailability = .phoneOnly
let syncPolicy: SyncPolicy = .never
init(iterations: Int = 10_000) {
// NOTE: PBKDF2 iterations must remain stable for existing data; changing this breaks decryption.
self.security = .encrypted(.aes256(keyDerivation: .pbkdf2(iterations: iterations)))
}
nonisolated static func sessionLogsKey(iterations: Int) -> StorageKey {
StorageKey(
name: "session_logs.json",
domain: .encryptedFileSystem(directory: .caches),
security: .encrypted(.aes256(keyDerivation: .pbkdf2(iterations: iterations))),
serializer: .json,
owner: "SampleApp",
description: "Stores session logs encrypted with PBKDF2-derived keys.",
availability: .phoneOnly,
syncPolicy: .never
)
}
}

View File

@ -1,18 +1,16 @@
import Foundation
import LocalData
extension StorageKeys {
extension StorageKey where Value == Data {
/// Stores cached data files.
struct CachedDataKey: StorageKey {
typealias Value = Data
let name = "cached_data.bin"
let domain: StorageDomain = .fileSystem(directory: .caches)
let security: SecurityPolicy = .none
let serializer: Serializer<Data> = .data
let owner = "SampleApp"
let description = "Stores cached binary data that can be regenerated."
let availability: PlatformAvailability = .all
let syncPolicy: SyncPolicy = .never
}
nonisolated static let cachedData = StorageKey(
name: "cached_data.bin",
domain: .fileSystem(directory: .caches),
security: .none,
serializer: .data,
owner: "SampleApp",
description: "Stores cached binary data that can be regenerated.",
availability: .all,
syncPolicy: .never
)
}

View File

@ -1,15 +1,13 @@
import Foundation
import LocalData
extension StorageKeys {
extension StorageKey where Value == String {
/// Example using custom serializer for specialized encoding.
struct CustomEncodedKey: StorageKey {
typealias Value = String
let name = "custom_encoded"
let domain: StorageDomain = .fileSystem(directory: .documents)
let security: SecurityPolicy = .none
let serializer: Serializer<String> = .custom(
nonisolated static let customEncoded = StorageKey(
name: "custom_encoded",
domain: .fileSystem(directory: .documents),
security: .none,
serializer: .custom(
encode: { value in
Data(value.utf8).base64EncodedData()
},
@ -20,10 +18,10 @@ extension StorageKeys {
}
return string
}
),
owner: "SampleApp",
description: "Stores custom-encoded string data (Base64 example).",
availability: .all,
syncPolicy: .never
)
let owner = "SampleApp"
let description = "Stores custom-encoded string data (Base64 example)."
let availability: PlatformAvailability = .all
let syncPolicy: SyncPolicy = .never
}
}

View File

@ -1,18 +1,16 @@
import Foundation
import LocalData
extension StorageKeys {
extension StorageKey where Value == [String: AnyCodable] {
/// Stores settings as property list.
struct SettingsPlistKey: StorageKey {
typealias Value = [String: AnyCodable]
let name = "settings.plist"
let domain: StorageDomain = .fileSystem(directory: .documents)
let security: SecurityPolicy = .none
let serializer: Serializer<[String: AnyCodable]> = .plist
let owner = "SampleApp"
let description = "Stores app settings exported as a property list."
let availability: PlatformAvailability = .all
let syncPolicy: SyncPolicy = .never
}
nonisolated static let settingsPlist = StorageKey(
name: "settings.plist",
domain: .fileSystem(directory: .documents),
security: .none,
serializer: .plist,
owner: "SampleApp",
description: "Stores app settings exported as a property list.",
availability: .all,
syncPolicy: .never
)
}

View File

@ -2,22 +2,29 @@ import Foundation
import LocalData
import SharedKit
extension StorageKeys {
extension StorageKey where Value == UserProfile {
/// Stores user profile as JSON file in documents.
struct UserProfileFileKey: StorageKey {
typealias Value = UserProfile
nonisolated static let userProfileFile = StorageKey(
name: UserProfile.storageKeyName,
domain: .fileSystem(directory: .documents),
security: .none,
serializer: .json,
owner: "SampleApp",
description: "Stores a lightweight user profile for file storage and watch sync.",
availability: .phoneWithWatchSync,
syncPolicy: .automaticSmall
)
let name = UserProfile.storageKeyName
let domain: StorageDomain
let security: SecurityPolicy = .none
let serializer: Serializer<UserProfile> = .json
let owner = "SampleApp"
let description = "Stores a lightweight user profile for file storage and watch sync."
let availability: PlatformAvailability = .phoneWithWatchSync
let syncPolicy: SyncPolicy = .automaticSmall
init(directory: FileDirectory = .documents) {
self.domain = .fileSystem(directory: directory)
}
nonisolated static func userProfileFileKey(directory: FileDirectory = .documents) -> StorageKey {
StorageKey(
name: UserProfile.storageKeyName,
domain: .fileSystem(directory: directory),
security: .none,
serializer: .json,
owner: "SampleApp",
description: "Stores a lightweight user profile for file storage and watch sync.",
availability: .phoneWithWatchSync,
syncPolicy: .automaticSmall
)
}
}

View File

@ -2,21 +2,19 @@ import Foundation
import LocalData
import SharedKit
extension StorageKeys {
extension StorageKey where Value == String {
/// Stores API token in keychain.
struct APITokenKey: StorageKey {
typealias Value = String
let name = "api_token"
let domain: StorageDomain = .keychain(service: StorageServiceIdentifiers.keychainAPIToken)
let security: SecurityPolicy = .keychain(
nonisolated static let apiToken = StorageKey(
name: "api_token",
domain: .keychain(service: StorageServiceIdentifiers.keychainAPIToken),
security: .keychain(
accessibility: .whenUnlockedThisDeviceOnly,
accessControl: nil
),
serializer: .json,
owner: "SampleApp",
description: "Stores API auth token for network requests.",
availability: .phoneOnly,
syncPolicy: .never
)
let serializer: Serializer<String> = .json
let owner = "SampleApp"
let description = "Stores API auth token for network requests."
let availability: PlatformAvailability = .phoneOnly
let syncPolicy: SyncPolicy = .never
}
}

View File

@ -2,23 +2,33 @@ import Foundation
import LocalData
import SharedKit
extension StorageKeys {
extension StorageKey where Value == Credential {
/// Stores user credentials securely in keychain.
/// Configurable accessibility and access control.
struct CredentialsKey: StorageKey {
typealias Value = Credential
nonisolated static let credentials = StorageKey(
name: "user_credentials",
domain: .keychain(service: StorageServiceIdentifiers.keychainCredentials),
security: .keychain(accessibility: .afterFirstUnlock, accessControl: nil),
serializer: .json,
owner: "SampleApp",
description: "Stores user credentials for sign-in flows.",
availability: .phoneOnly,
syncPolicy: .never
)
let name = "user_credentials"
let domain: StorageDomain = .keychain(service: StorageServiceIdentifiers.keychainCredentials)
let security: SecurityPolicy
let serializer: Serializer<Credential> = .json
let owner = "SampleApp"
let description = "Stores user credentials for sign-in flows."
let availability: PlatformAvailability = .phoneOnly
let syncPolicy: SyncPolicy = .never
init(accessibility: KeychainAccessibility = .afterFirstUnlock, accessControl: KeychainAccessControl? = nil) {
self.security = .keychain(accessibility: accessibility, accessControl: accessControl)
}
nonisolated static func credentialsKey(
accessibility: KeychainAccessibility = .afterFirstUnlock,
accessControl: KeychainAccessControl? = nil
) -> StorageKey {
StorageKey(
name: "user_credentials",
domain: .keychain(service: StorageServiceIdentifiers.keychainCredentials),
security: .keychain(accessibility: accessibility, accessControl: accessControl),
serializer: .json,
owner: "SampleApp",
description: "Stores user credentials for sign-in flows.",
availability: .phoneOnly,
syncPolicy: .never
)
}
}

View File

@ -2,21 +2,19 @@ import Foundation
import LocalData
import SharedKit
extension StorageKeys {
extension StorageKey where Value == Data {
/// Stores external key material used for encryption policies.
struct ExternalKeyMaterialKey: StorageKey {
typealias Value = Data
let name = "external_key_material"
let domain: StorageDomain = .keychain(service: StorageServiceIdentifiers.keychainExternalKeyMaterial)
let security: SecurityPolicy = .keychain(
nonisolated static let externalKeyMaterial = StorageKey(
name: "external_key_material",
domain: .keychain(service: StorageServiceIdentifiers.keychainExternalKeyMaterial),
security: .keychain(
accessibility: .afterFirstUnlock,
accessControl: nil
),
serializer: .data,
owner: "SampleApp",
description: "Stores external key material used by encryption policies.",
availability: .phoneOnly,
syncPolicy: .never
)
let serializer: Serializer<Data> = .data
let owner = "SampleApp"
let description = "Stores external key material used by encryption policies."
let availability: PlatformAvailability = .phoneOnly
let syncPolicy: SyncPolicy = .never
}
}

View File

@ -2,21 +2,19 @@ import Foundation
import LocalData
import SharedKit
extension StorageKeys {
extension StorageKey where Value == SampleLocationData {
/// Stores sensitive location data in keychain with biometric protection.
struct LastLocationKey: StorageKey {
typealias Value = SampleLocationData
let name = "last_known_location"
let domain: StorageDomain = .keychain(service: StorageServiceIdentifiers.keychainLocation)
let security: SecurityPolicy = .keychain(
nonisolated static let lastLocation = StorageKey(
name: "last_known_location",
domain: .keychain(service: StorageServiceIdentifiers.keychainLocation),
security: .keychain(
accessibility: .afterFirstUnlock,
accessControl: .userPresence
),
serializer: .json,
owner: "SampleApp",
description: "Stores last known location for location-aware features.",
availability: .phoneOnly,
syncPolicy: .never
)
let serializer: Serializer<SampleLocationData> = .json
let owner = "SampleApp"
let description = "Stores last known location for location-aware features."
let availability: PlatformAvailability = .phoneOnly
let syncPolicy: SyncPolicy = .never
}
}

View File

@ -1,54 +1,51 @@
import Foundation
import LocalData
extension StorageKeys {
struct LegacyNotificationSettingKey: StorageKey {
typealias Value = Bool
extension StorageKey where Value == Bool {
nonisolated static let legacyNotificationSetting = StorageKey(
name: "legacy_notification_setting",
domain: .userDefaults(suite: nil),
security: .none,
serializer: .json,
owner: "MigrationDemo",
description: "Legacy notification setting stored as Bool.",
availability: .all,
syncPolicy: .never
)
}
let name = "legacy_notification_setting"
let domain: StorageDomain = .userDefaults(suite: nil)
let security: SecurityPolicy = .none
let serializer: Serializer<Bool> = .json
let owner = "MigrationDemo"
let description = "Legacy notification setting stored as Bool."
let availability: PlatformAvailability = .all
let syncPolicy: SyncPolicy = .never
}
extension StorageKey where Value == String {
nonisolated static let legacyThemeSetting = StorageKey(
name: "legacy_theme_setting",
domain: .userDefaults(suite: nil),
security: .none,
serializer: .json,
owner: "MigrationDemo",
description: "Legacy theme setting stored as a string.",
availability: .all,
syncPolicy: .never
)
}
struct LegacyThemeSettingKey: StorageKey {
typealias Value = String
let name = "legacy_theme_setting"
let domain: StorageDomain = .userDefaults(suite: nil)
let security: SecurityPolicy = .none
let serializer: Serializer<String> = .json
let owner = "MigrationDemo"
let description = "Legacy theme setting stored as a string."
let availability: PlatformAvailability = .all
let syncPolicy: SyncPolicy = .never
}
struct ModernUnifiedSettingsKey: StorageKey {
typealias Value = UnifiedSettings
let name = "modern_unified_settings"
let domain: StorageDomain = .fileSystem(directory: .documents)
let security: SecurityPolicy = .none
let serializer: Serializer<UnifiedSettings> = .json
let owner = "MigrationDemo"
let description = "Modern unified settings aggregated from legacy keys."
let availability: PlatformAvailability = .all
let syncPolicy: SyncPolicy = .never
var migration: AnyStorageMigration? {
extension StorageKey where Value == UnifiedSettings {
nonisolated static let modernUnifiedSettings = StorageKey(
name: "modern_unified_settings",
domain: .fileSystem(directory: .documents),
security: .none,
serializer: .json,
owner: "MigrationDemo",
description: "Modern unified settings aggregated from legacy keys.",
availability: .all,
syncPolicy: .never,
migration: { key in
let sources: [AnyStorageKey] = [
.key(StorageKeys.LegacyNotificationSettingKey()),
.key(StorageKeys.LegacyThemeSettingKey())
.key(StorageKey<Bool>.legacyNotificationSetting),
.key(StorageKey<String>.legacyThemeSetting)
]
return AnyStorageMigration(
DefaultAggregatingMigration(
destinationKey: self,
destinationKey: key,
sourceKeys: sources
) { sources in
var notificationsEnabled = false
@ -69,5 +66,5 @@ extension StorageKeys {
}
)
}
}
)
}

View File

@ -1,49 +1,43 @@
import Foundation
import LocalData
extension StorageKeys {
struct LegacyAppModeKey: StorageKey {
typealias Value = String
extension StorageKey where Value == String {
nonisolated static let legacyAppMode = StorageKey(
name: "legacy_app_mode",
domain: .userDefaults(suite: nil),
security: .none,
serializer: .json,
owner: "MigrationDemo",
description: "Legacy app mode stored in UserDefaults.",
availability: .all,
syncPolicy: .never
)
let name = "legacy_app_mode"
let domain: StorageDomain = .userDefaults(suite: nil)
let security: SecurityPolicy = .none
let serializer: Serializer<String> = .json
let owner = "MigrationDemo"
let description = "Legacy app mode stored in UserDefaults."
let availability: PlatformAvailability = .all
let syncPolicy: SyncPolicy = .never
}
struct ModernAppModeKey: StorageKey {
typealias Value = String
let name = "modern_app_mode"
let domain: StorageDomain = .keychain(service: "com.mbrucedogs.securestorage")
let security: SecurityPolicy = .keychain(
nonisolated static let modernAppMode = StorageKey(
name: "modern_app_mode",
domain: .keychain(service: "com.mbrucedogs.securestorage"),
security: .keychain(
accessibility: .afterFirstUnlock,
accessControl: .userPresence
)
let serializer: Serializer<String> = .json
let owner = "MigrationDemo"
let description = "Modern app mode with conditional migration."
let availability: PlatformAvailability = .all
let syncPolicy: SyncPolicy = .never
var migration: AnyStorageMigration? {
let destination = self
let legacy = StorageKeys.LegacyAppModeKey()
),
serializer: .json,
owner: "MigrationDemo",
description: "Modern app mode with conditional migration.",
availability: .all,
syncPolicy: .never,
migration: { key in
let legacy = StorageKey.legacyAppMode
let fallback = AnyStorageMigration(
SimpleLegacyMigration(destinationKey: destination, sourceKey: .key(legacy))
SimpleLegacyMigration(destinationKey: key, sourceKey: .key(legacy))
)
return AnyStorageMigration(
AppVersionConditionalMigration(
destinationKey: destination,
destinationKey: key,
minAppVersion: "99.0",
fallbackMigration: fallback
)
)
}
}
)
}

View File

@ -1,44 +1,39 @@
import Foundation
import LocalData
extension StorageKeys {
extension StorageKey where Value == String {
/// The legacy key where data starts (in UserDefaults)
struct LegacyMigrationSourceKey: StorageKey {
typealias Value = String
let name = "legacy_user_id"
let domain: StorageDomain = .userDefaults(suite: nil)
let security: SecurityPolicy = .none
let serializer: Serializer<String> = .json
let owner = "MigrationDemo"
let description = "Legacy key in UserDefaults from an older version of the app."
let availability: PlatformAvailability = .all
let syncPolicy: SyncPolicy = .never
}
nonisolated static let legacyMigrationSource = StorageKey(
name: "legacy_user_id",
domain: .userDefaults(suite: nil),
security: .none,
serializer: .json,
owner: "MigrationDemo",
description: "Legacy key in UserDefaults from an older version of the app.",
availability: .all,
syncPolicy: .never
)
/// The modern key where data should end up (in Keychain)
struct ModernMigrationDestinationKey: StorageKey {
typealias Value = String
let name = "secure_user_id"
let domain: StorageDomain = .keychain(service: "com.mbrucedogs.securestorage")
let security: SecurityPolicy = .keychain(
nonisolated static let modernMigrationDestination = StorageKey(
name: "secure_user_id",
domain: .keychain(service: "com.mbrucedogs.securestorage"),
security: .keychain(
accessibility: .afterFirstUnlock,
accessControl: .userPresence
)
let serializer: Serializer<String> = .json
let owner = "MigrationDemo"
let description = "Modern key in Keychain with biometric security."
let availability: PlatformAvailability = .all
let syncPolicy: SyncPolicy = .never
var migration: AnyStorageMigration? {
),
serializer: .json,
owner: "MigrationDemo",
description: "Modern key in Keychain with biometric security.",
availability: .all,
syncPolicy: .never,
migration: { key in
AnyStorageMigration(
SimpleLegacyMigration(
destinationKey: self,
sourceKey: .key(LegacyMigrationSourceKey())
destinationKey: key,
sourceKey: .key(StorageKey.legacyMigrationSource)
)
)
}
}
)
}

View File

@ -1,40 +1,37 @@
import Foundation
import LocalData
extension StorageKeys {
struct LegacyProfileNameKey: StorageKey {
typealias Value = String
extension StorageKey where Value == String {
nonisolated static let legacyProfileName = StorageKey(
name: "legacy_profile_name",
domain: .userDefaults(suite: nil),
security: .none,
serializer: .json,
owner: "MigrationDemo",
description: "Legacy profile name stored as a single string.",
availability: .all,
syncPolicy: .never
)
}
let name = "legacy_profile_name"
let domain: StorageDomain = .userDefaults(suite: nil)
let security: SecurityPolicy = .none
let serializer: Serializer<String> = .json
let owner = "MigrationDemo"
let description = "Legacy profile name stored as a single string."
let availability: PlatformAvailability = .all
let syncPolicy: SyncPolicy = .never
}
struct ModernProfileNameKey: StorageKey {
typealias Value = ProfileName
let name = "modern_profile_name"
let domain: StorageDomain = .keychain(service: "com.mbrucedogs.securestorage")
let security: SecurityPolicy = .keychain(
extension StorageKey where Value == ProfileName {
nonisolated static let modernProfileName = StorageKey(
name: "modern_profile_name",
domain: .keychain(service: "com.mbrucedogs.securestorage"),
security: .keychain(
accessibility: .afterFirstUnlock,
accessControl: .userPresence
)
let serializer: Serializer<ProfileName> = .json
let owner = "MigrationDemo"
let description = "Modern profile name stored as structured data."
let availability: PlatformAvailability = .all
let syncPolicy: SyncPolicy = .never
var migration: AnyStorageMigration? {
let sourceKey = StorageKeys.LegacyProfileNameKey()
),
serializer: .json,
owner: "MigrationDemo",
description: "Modern profile name stored as structured data.",
availability: .all,
syncPolicy: .never,
migration: { key in
let sourceKey = StorageKey<String>.legacyProfileName
return AnyStorageMigration(
DefaultTransformingMigration(
destinationKey: self,
destinationKey: key,
sourceKey: sourceKey
) { value in
let parts = value.split(separator: " ", maxSplits: 1).map(String.init)
@ -44,5 +41,5 @@ extension StorageKeys {
}
)
}
}
)
}

View File

@ -1,24 +1,33 @@
import Foundation
import LocalData
extension StorageKeys {
extension StorageKey where Value == String {
/// Syncable setting with configurable platform and sync policy.
/// Grouped under Platform to highlight availability/sync behavior.
struct SyncableSettingKey: StorageKey {
typealias Value = String
nonisolated static let syncableSetting = StorageKey(
name: "syncable_setting",
domain: .userDefaults(suite: nil),
security: .none,
serializer: .json,
owner: "SampleApp",
description: "Stores a setting that can be synced to watch.",
availability: .all,
syncPolicy: .never
)
let name = "syncable_setting"
let domain: StorageDomain = .userDefaults(suite: nil)
let security: SecurityPolicy = .none
let serializer: Serializer<String> = .json
let owner = "SampleApp"
let description = "Stores a setting that can be synced to watch."
let availability: PlatformAvailability
let syncPolicy: SyncPolicy
init(availability: PlatformAvailability = .all, syncPolicy: SyncPolicy = .never) {
self.availability = availability
self.syncPolicy = syncPolicy
}
nonisolated static func syncableSettingKey(
availability: PlatformAvailability = .all,
syncPolicy: SyncPolicy = .never
) -> StorageKey {
StorageKey(
name: "syncable_setting",
domain: .userDefaults(suite: nil),
security: .none,
serializer: .json,
owner: "SampleApp",
description: "Stores a setting that can be synced to watch.",
availability: availability,
syncPolicy: syncPolicy
)
}
}

View File

@ -1,19 +1,17 @@
import Foundation
import LocalData
extension StorageKeys {
extension StorageKey where Value == Bool {
/// Watch-only setting for vibration.
/// Grouped under Platform to highlight watch-only availability.
struct WatchVibrationKey: StorageKey {
typealias Value = Bool
let name = "watch_vibration_enabled"
let domain: StorageDomain = .userDefaults(suite: nil)
let security: SecurityPolicy = .none
let serializer: Serializer<Bool> = .json
let owner = "SampleApp"
let description = "Controls haptic feedback on watch-only experiences."
let availability: PlatformAvailability = .watchOnly
let syncPolicy: SyncPolicy = .never
}
nonisolated static let watchVibration = StorageKey(
name: "watch_vibration_enabled",
domain: .userDefaults(suite: nil),
security: .none,
serializer: .json,
owner: "SampleApp",
description: "Controls haptic feedback on watch-only experiences.",
availability: .watchOnly,
syncPolicy: .never
)
}

View File

@ -1,21 +1,19 @@
import Foundation
import LocalData
extension StorageKeys {
extension StorageKey where Value == String {
/// Stores the app version in standard UserDefaults.
/// - Domain: UserDefaults (standard)
/// - Security: None
/// - Sync: Automatic for small data
struct AppVersionKey: StorageKey {
typealias Value = String
let name = "last_app_version"
let domain: StorageDomain = .userDefaults(suite: nil)
let security: SecurityPolicy = .none
let serializer: Serializer<String> = .json
let owner = "SampleApp"
let description = "Tracks the last app version for migration and UI messaging."
let availability: PlatformAvailability = .all
let syncPolicy: SyncPolicy = .automaticSmall
}
nonisolated static let appVersion = StorageKey(
name: "last_app_version",
domain: .userDefaults(suite: nil),
security: .none,
serializer: .json,
owner: "SampleApp",
description: "Tracks the last app version for migration and UI messaging.",
availability: .all,
syncPolicy: .automaticSmall
)
}

View File

@ -64,11 +64,11 @@ struct AggregatingMigrationDemo: View {
do {
try await StorageRouter.shared.set(
notificationsEnabled,
for: StorageKeys.LegacyNotificationSettingKey()
for: StorageKey.legacyNotificationSetting
)
try await StorageRouter.shared.set(
theme,
for: StorageKeys.LegacyThemeSettingKey()
for: StorageKey.legacyThemeSetting
)
statusMessage = "✓ Saved legacy settings"
} catch {
@ -83,7 +83,7 @@ struct AggregatingMigrationDemo: View {
statusMessage = "Loading unified settings..."
Task {
do {
let value = try await StorageRouter.shared.get(StorageKeys.ModernUnifiedSettingsKey())
let value = try await StorageRouter.shared.get(StorageKey.modernUnifiedSettings)
modernValue = format(value)
statusMessage = "✓ Migration complete."
} catch StorageError.notFound {

View File

@ -63,7 +63,7 @@ struct ConditionalMigrationDemo: View {
isLoading = true
Task {
do {
let key = StorageKeys.LegacyAppModeKey()
let key = StorageKey.legacyAppMode
try await StorageRouter.shared.set(legacyValue, for: key)
statusMessage = "✓ Saved legacy app mode"
} catch {
@ -78,7 +78,7 @@ struct ConditionalMigrationDemo: View {
statusMessage = "Loading modern mode..."
Task {
do {
let key = StorageKeys.ModernAppModeKey()
let key = StorageKey.modernAppMode
let value = try await StorageRouter.shared.get(key)
modernValue = value
statusMessage = "✓ Conditional migration complete."

View File

@ -132,12 +132,12 @@ struct EncryptedStorageDemo: View {
Task {
do {
if useExternalKeyProvider {
let key = StorageKeys.ExternalSessionLogsKey()
let key = StorageKey.externalSessionLogs
let logs = try await updatedLogs(for: key)
try await StorageRouter.shared.set(logs, for: key)
storedLogs = logs
} else {
let key = StorageKeys.SessionLogsKey(iterations: iterations)
let key = StorageKey.sessionLogsKey(iterations: iterations)
let logs = try await updatedLogs(for: key)
try await StorageRouter.shared.set(logs, for: key)
storedLogs = logs
@ -157,10 +157,10 @@ struct EncryptedStorageDemo: View {
Task {
do {
if useExternalKeyProvider {
let key = StorageKeys.ExternalSessionLogsKey()
let key = StorageKey.externalSessionLogs
storedLogs = try await StorageRouter.shared.get(key)
} else {
let key = StorageKeys.SessionLogsKey(iterations: iterations)
let key = StorageKey.sessionLogsKey(iterations: iterations)
storedLogs = try await StorageRouter.shared.get(key)
}
statusMessage = "✓ Decrypted \(storedLogs.count) log entries"
@ -179,10 +179,10 @@ struct EncryptedStorageDemo: View {
Task {
do {
if useExternalKeyProvider {
let key = StorageKeys.ExternalSessionLogsKey()
let key = StorageKey.externalSessionLogs
try await StorageRouter.shared.remove(key)
} else {
let key = StorageKeys.SessionLogsKey(iterations: iterations)
let key = StorageKey.sessionLogsKey(iterations: iterations)
try await StorageRouter.shared.remove(key)
}
storedLogs = []
@ -194,7 +194,7 @@ struct EncryptedStorageDemo: View {
}
}
private func updatedLogs<Key: StorageKey>(for key: Key) async throws -> [String] where Key.Value == [String] {
private func updatedLogs(for key: StorageKey<[String]>) async throws -> [String] {
var logs: [String]
do {
logs = try await StorageRouter.shared.get(key)

View File

@ -172,7 +172,7 @@ struct FileSystemDemo: View {
isLoading = true
Task {
do {
let key = StorageKeys.UserProfileFileKey(directory: selectedDirectory)
let key = StorageKey.userProfileFileKey(directory: selectedDirectory)
let profile = UserProfile(
name: profileName,
email: profileEmail,
@ -192,7 +192,7 @@ struct FileSystemDemo: View {
isLoading = true
Task {
do {
let key = StorageKeys.UserProfileFileKey(directory: selectedDirectory)
let key = StorageKey.userProfileFileKey(directory: selectedDirectory)
let profile = try await StorageRouter.shared.get(key)
storedProfile = profile
@ -216,7 +216,7 @@ struct FileSystemDemo: View {
isLoading = true
Task {
do {
let key = StorageKeys.UserProfileFileKey(directory: selectedDirectory)
let key = StorageKey.userProfileFileKey(directory: selectedDirectory)
try await StorageRouter.shared.remove(key)
storedProfile = nil
statusMessage = "✓ File deleted"
@ -231,7 +231,7 @@ struct FileSystemDemo: View {
isLoading = true
Task {
do {
let key = StorageKeys.AppGroupUserProfileKey(directory: selectedDirectory)
let key = StorageKey.appGroupUserProfileKey(directory: selectedDirectory)
let profile = UserProfile(
name: profileName,
email: profileEmail,
@ -251,7 +251,7 @@ struct FileSystemDemo: View {
isLoading = true
Task {
do {
let key = StorageKeys.AppGroupUserProfileKey(directory: selectedDirectory)
let key = StorageKey.appGroupUserProfileKey(directory: selectedDirectory)
let profile = try await StorageRouter.shared.get(key)
appGroupProfile = profile
@ -275,7 +275,7 @@ struct FileSystemDemo: View {
isLoading = true
Task {
do {
let key = StorageKeys.AppGroupUserProfileKey(directory: selectedDirectory)
let key = StorageKey.appGroupUserProfileKey(directory: selectedDirectory)
try await StorageRouter.shared.remove(key)
appGroupProfile = nil
appGroupStatusMessage = "✓ App Group file deleted"

View File

@ -117,7 +117,7 @@ struct KeychainDemo: View {
isLoading = true
Task {
do {
let key = StorageKeys.CredentialsKey(
let key = StorageKey.credentialsKey(
accessibility: selectedAccessibility,
accessControl: selectedAccessControl
)
@ -135,7 +135,7 @@ struct KeychainDemo: View {
isLoading = true
Task {
do {
let key = StorageKeys.CredentialsKey(
let key = StorageKey.credentialsKey(
accessibility: selectedAccessibility,
accessControl: selectedAccessControl
)
@ -161,7 +161,7 @@ struct KeychainDemo: View {
isLoading = true
Task {
do {
let key = StorageKeys.CredentialsKey(
let key = StorageKey.credentialsKey(
accessibility: selectedAccessibility,
accessControl: selectedAccessControl
)

View File

@ -87,7 +87,7 @@ struct MigrationDemo: View {
isLoading = true
Task {
do {
let key = StorageKeys.LegacyMigrationSourceKey()
let key = StorageKey.legacyMigrationSource
try await StorageRouter.shared.set(legacyValue, for: key)
statusMessage = "✓ Saved '\(legacyValue)' to legacy UserDefaults"
} catch {
@ -102,7 +102,7 @@ struct MigrationDemo: View {
statusMessage = "Retrieving from Modern..."
Task {
do {
let key = StorageKeys.ModernMigrationDestinationKey()
let key = StorageKey.modernMigrationDestination
let value = try await StorageRouter.shared.get(key)
modernValue = value
statusMessage = "✓ Success! Data migrated from UserDefaults to Keychain."
@ -120,7 +120,7 @@ struct MigrationDemo: View {
statusMessage = "Running manual migration..."
Task {
do {
let key = StorageKeys.ModernMigrationDestinationKey()
let key = StorageKey.modernMigrationDestination
_ = try await StorageRouter.shared.forceMigration(for: key)
// Refresh modern value display
modernValue = try await StorageRouter.shared.get(key)
@ -136,7 +136,7 @@ struct MigrationDemo: View {
isLoading = true
Task {
do {
let key = StorageKeys.LegacyMigrationSourceKey()
let key = StorageKey.legacyMigrationSource
let exists = try await StorageRouter.shared.exists(key)
statusMessage = exists ? "⚠️ Legacy data STILL EXISTS in UserDefaults!" : "✅ Legacy data was successfully DELETED."
} catch {

View File

@ -114,7 +114,7 @@ struct PlatformSyncDemo: View {
LabeledContent("Platform", value: selectedPlatform.displayName)
LabeledContent("Sync", value: selectedSync.displayName)
LabeledContent("Max Auto-Sync Size", value: "50 KB")
LabeledContent("Key Name", value: StorageKeys.SyncableSettingKey().name)
LabeledContent("Key Name", value: StorageKey.syncableSetting.name)
}
Section("Watch Notes") {
@ -141,7 +141,7 @@ struct PlatformSyncDemo: View {
isLoading = true
Task {
do {
let key = StorageKeys.SyncableSettingKey(
let key = StorageKey.syncableSettingKey(
availability: selectedPlatform,
syncPolicy: selectedSync
)
@ -161,7 +161,7 @@ struct PlatformSyncDemo: View {
isLoading = true
Task {
do {
let key = StorageKeys.SyncableSettingKey(
let key = StorageKey.syncableSettingKey(
availability: selectedPlatform,
syncPolicy: selectedSync
)
@ -184,7 +184,7 @@ struct PlatformSyncDemo: View {
isLoading = true
Task {
do {
let key = StorageKeys.SyncableSettingKey(
let key = StorageKey.syncableSettingKey(
availability: selectedPlatform,
syncPolicy: selectedSync
)

View File

@ -63,7 +63,7 @@ struct TransformingMigrationDemo: View {
isLoading = true
Task {
do {
let key = StorageKeys.LegacyProfileNameKey()
let key = StorageKey.legacyProfileName
try await StorageRouter.shared.set(legacyValue, for: key)
statusMessage = "✓ Saved legacy name \(legacyValue)"
} catch {
@ -78,7 +78,7 @@ struct TransformingMigrationDemo: View {
statusMessage = "Loading modern profile..."
Task {
do {
let key = StorageKeys.ModernProfileNameKey()
let key = StorageKey.modernProfileName
let value = try await StorageRouter.shared.get(key)
modernValue = format(value)
statusMessage = "✓ Migration complete."

View File

@ -140,7 +140,7 @@ struct UserDefaultsDemo: View {
isLoading = true
Task {
do {
let key = StorageKeys.AppVersionKey()
let key = StorageKey.appVersion
try await StorageRouter.shared.set(inputText, for: key)
statusMessage = "✓ Saved successfully"
} catch {
@ -154,7 +154,7 @@ struct UserDefaultsDemo: View {
isLoading = true
Task {
do {
let key = StorageKeys.AppVersionKey()
let key = StorageKey.appVersion
let value = try await StorageRouter.shared.get(key)
storedValue = value
inputText = value // Sync to field
@ -173,7 +173,7 @@ struct UserDefaultsDemo: View {
isLoading = true
Task {
do {
let key = StorageKeys.AppVersionKey()
let key = StorageKey.appVersion
try await StorageRouter.shared.remove(key)
storedValue = ""
statusMessage = "✓ Removed successfully"
@ -188,7 +188,7 @@ struct UserDefaultsDemo: View {
isLoading = true
Task {
do {
let key = StorageKeys.AppGroupUserDefaultsKey()
let key = StorageKey.appGroupUserDefaults
try await StorageRouter.shared.set(inputText, for: key)
statusMessage = "✓ App Group saved successfully"
} catch {
@ -202,7 +202,7 @@ struct UserDefaultsDemo: View {
isLoading = true
Task {
do {
let key = StorageKeys.AppGroupUserDefaultsKey()
let key = StorageKey.appGroupUserDefaults
let value = try await StorageRouter.shared.get(key)
appGroupStoredValue = value
inputText = value // Sync to field
@ -221,7 +221,7 @@ struct UserDefaultsDemo: View {
isLoading = true
Task {
do {
let key = StorageKeys.AppGroupUserDefaultsKey()
let key = StorageKey.appGroupUserDefaults
try await StorageRouter.shared.remove(key)
appGroupStoredValue = ""
statusMessage = "✓ App Group value removed"