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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,20 +1,16 @@
import Foundation import Foundation
import LocalData import LocalData
extension StorageKeys { extension StorageKey where Value == [String] {
/// Stores session logs with encryption using external key material. /// Stores session logs with encryption using external key material.
struct ExternalSessionLogsKey: StorageKey { nonisolated static let externalSessionLogs = StorageKey(
typealias Value = [String] name: "external_session_logs.json",
domain: .encryptedFileSystem(directory: .caches),
let name = "external_session_logs.json" security: .encrypted(.external(source: SampleKeyMaterialSources.external)),
let domain: StorageDomain = .encryptedFileSystem(directory: .caches) serializer: .json,
let security: SecurityPolicy = .encrypted( owner: "SampleApp",
.external(source: SampleKeyMaterialSources.external) 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 Foundation
import LocalData import LocalData
extension StorageKeys { extension StorageKey where Value == String {
/// Stores private notes with encryption. /// Stores private notes with encryption.
struct PrivateNotesKey: StorageKey { nonisolated static let privateNotes = StorageKey(
typealias Value = String name: "private_notes.enc",
domain: .encryptedFileSystem(directory: .documents),
let name = "private_notes.enc" security: .encrypted(.aes256(keyDerivation: .pbkdf2(iterations: 50_000))),
let domain: StorageDomain = .encryptedFileSystem(directory: .documents) serializer: .json,
let security: SecurityPolicy = .encrypted( owner: "SampleApp",
.aes256(keyDerivation: .pbkdf2(iterations: 50_000)) 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 Foundation
import LocalData import LocalData
extension StorageKeys { extension StorageKey where Value == [String] {
/// Stores session logs with full encryption. /// Stores session logs with full encryption.
/// Configurable PBKDF2 iterations. /// Configurable PBKDF2 iterations.
struct SessionLogsKey: StorageKey { nonisolated static let sessionLogs = StorageKey(
typealias Value = [String] 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" nonisolated static func sessionLogsKey(iterations: Int) -> StorageKey {
let domain: StorageDomain = .encryptedFileSystem(directory: .caches) StorageKey(
let security: SecurityPolicy name: "session_logs.json",
let serializer: Serializer<[String]> = .json domain: .encryptedFileSystem(directory: .caches),
let owner = "SampleApp" security: .encrypted(.aes256(keyDerivation: .pbkdf2(iterations: iterations))),
let description = "Stores session logs encrypted with PBKDF2-derived keys." serializer: .json,
let availability: PlatformAvailability = .phoneOnly owner: "SampleApp",
let syncPolicy: SyncPolicy = .never description: "Stores session logs encrypted with PBKDF2-derived keys.",
availability: .phoneOnly,
init(iterations: Int = 10_000) { syncPolicy: .never
// NOTE: PBKDF2 iterations must remain stable for existing data; changing this breaks decryption. )
self.security = .encrypted(.aes256(keyDerivation: .pbkdf2(iterations: iterations)))
}
} }
} }

View File

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

View File

@ -1,15 +1,13 @@
import Foundation import Foundation
import LocalData import LocalData
extension StorageKeys { extension StorageKey where Value == String {
/// Example using custom serializer for specialized encoding. /// Example using custom serializer for specialized encoding.
struct CustomEncodedKey: StorageKey { nonisolated static let customEncoded = StorageKey(
typealias Value = String name: "custom_encoded",
domain: .fileSystem(directory: .documents),
let name = "custom_encoded" security: .none,
let domain: StorageDomain = .fileSystem(directory: .documents) serializer: .custom(
let security: SecurityPolicy = .none
let serializer: Serializer<String> = .custom(
encode: { value in encode: { value in
Data(value.utf8).base64EncodedData() Data(value.utf8).base64EncodedData()
}, },
@ -20,10 +18,10 @@ extension StorageKeys {
} }
return string 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 Foundation
import LocalData import LocalData
extension StorageKeys { extension StorageKey where Value == [String: AnyCodable] {
/// Stores settings as property list. /// Stores settings as property list.
struct SettingsPlistKey: StorageKey { nonisolated static let settingsPlist = StorageKey(
typealias Value = [String: AnyCodable] name: "settings.plist",
domain: .fileSystem(directory: .documents),
let name = "settings.plist" security: .none,
let domain: StorageDomain = .fileSystem(directory: .documents) serializer: .plist,
let security: SecurityPolicy = .none owner: "SampleApp",
let serializer: Serializer<[String: AnyCodable]> = .plist description: "Stores app settings exported as a property list.",
let owner = "SampleApp" availability: .all,
let description = "Stores app settings exported as a property list." syncPolicy: .never
let availability: PlatformAvailability = .all )
let syncPolicy: SyncPolicy = .never
}
} }

View File

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

View File

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

View File

@ -2,21 +2,19 @@ import Foundation
import LocalData import LocalData
import SharedKit import SharedKit
extension StorageKeys { extension StorageKey where Value == Data {
/// Stores external key material used for encryption policies. /// Stores external key material used for encryption policies.
struct ExternalKeyMaterialKey: StorageKey { nonisolated static let externalKeyMaterial = StorageKey(
typealias Value = Data name: "external_key_material",
domain: .keychain(service: StorageServiceIdentifiers.keychainExternalKeyMaterial),
let name = "external_key_material" security: .keychain(
let domain: StorageDomain = .keychain(service: StorageServiceIdentifiers.keychainExternalKeyMaterial)
let security: SecurityPolicy = .keychain(
accessibility: .afterFirstUnlock, accessibility: .afterFirstUnlock,
accessControl: nil 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 LocalData
import SharedKit import SharedKit
extension StorageKeys { extension StorageKey where Value == SampleLocationData {
/// Stores sensitive location data in keychain with biometric protection. /// Stores sensitive location data in keychain with biometric protection.
struct LastLocationKey: StorageKey { nonisolated static let lastLocation = StorageKey(
typealias Value = SampleLocationData name: "last_known_location",
domain: .keychain(service: StorageServiceIdentifiers.keychainLocation),
let name = "last_known_location" security: .keychain(
let domain: StorageDomain = .keychain(service: StorageServiceIdentifiers.keychainLocation)
let security: SecurityPolicy = .keychain(
accessibility: .afterFirstUnlock, accessibility: .afterFirstUnlock,
accessControl: .userPresence 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 Foundation
import LocalData import LocalData
extension StorageKeys { extension StorageKey where Value == Bool {
struct LegacyNotificationSettingKey: StorageKey { nonisolated static let legacyNotificationSetting = StorageKey(
typealias Value = Bool name: "legacy_notification_setting",
domain: .userDefaults(suite: nil),
let name = "legacy_notification_setting" security: .none,
let domain: StorageDomain = .userDefaults(suite: nil) serializer: .json,
let security: SecurityPolicy = .none owner: "MigrationDemo",
let serializer: Serializer<Bool> = .json description: "Legacy notification setting stored as Bool.",
let owner = "MigrationDemo" availability: .all,
let description = "Legacy notification setting stored as Bool." syncPolicy: .never
let availability: PlatformAvailability = .all )
let syncPolicy: SyncPolicy = .never
} }
struct LegacyThemeSettingKey: StorageKey { extension StorageKey where Value == String {
typealias Value = String nonisolated static let legacyThemeSetting = StorageKey(
name: "legacy_theme_setting",
let name = "legacy_theme_setting" domain: .userDefaults(suite: nil),
let domain: StorageDomain = .userDefaults(suite: nil) security: .none,
let security: SecurityPolicy = .none serializer: .json,
let serializer: Serializer<String> = .json owner: "MigrationDemo",
let owner = "MigrationDemo" description: "Legacy theme setting stored as a string.",
let description = "Legacy theme setting stored as a string." availability: .all,
let availability: PlatformAvailability = .all syncPolicy: .never
let syncPolicy: SyncPolicy = .never )
} }
struct ModernUnifiedSettingsKey: StorageKey { extension StorageKey where Value == UnifiedSettings {
typealias Value = UnifiedSettings nonisolated static let modernUnifiedSettings = StorageKey(
name: "modern_unified_settings",
let name = "modern_unified_settings" domain: .fileSystem(directory: .documents),
let domain: StorageDomain = .fileSystem(directory: .documents) security: .none,
let security: SecurityPolicy = .none serializer: .json,
let serializer: Serializer<UnifiedSettings> = .json owner: "MigrationDemo",
let owner = "MigrationDemo" description: "Modern unified settings aggregated from legacy keys.",
let description = "Modern unified settings aggregated from legacy keys." availability: .all,
let availability: PlatformAvailability = .all syncPolicy: .never,
let syncPolicy: SyncPolicy = .never migration: { key in
var migration: AnyStorageMigration? {
let sources: [AnyStorageKey] = [ let sources: [AnyStorageKey] = [
.key(StorageKeys.LegacyNotificationSettingKey()), .key(StorageKey<Bool>.legacyNotificationSetting),
.key(StorageKeys.LegacyThemeSettingKey()) .key(StorageKey<String>.legacyThemeSetting)
] ]
return AnyStorageMigration( return AnyStorageMigration(
DefaultAggregatingMigration( DefaultAggregatingMigration(
destinationKey: self, destinationKey: key,
sourceKeys: sources sourceKeys: sources
) { sources in ) { sources in
var notificationsEnabled = false var notificationsEnabled = false
@ -69,5 +66,5 @@ extension StorageKeys {
} }
) )
} }
} )
} }

View File

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

View File

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

View File

@ -1,40 +1,37 @@
import Foundation import Foundation
import LocalData import LocalData
extension StorageKeys { extension StorageKey where Value == String {
struct LegacyProfileNameKey: StorageKey { nonisolated static let legacyProfileName = StorageKey(
typealias Value = String name: "legacy_profile_name",
domain: .userDefaults(suite: nil),
let name = "legacy_profile_name" security: .none,
let domain: StorageDomain = .userDefaults(suite: nil) serializer: .json,
let security: SecurityPolicy = .none owner: "MigrationDemo",
let serializer: Serializer<String> = .json description: "Legacy profile name stored as a single string.",
let owner = "MigrationDemo" availability: .all,
let description = "Legacy profile name stored as a single string." syncPolicy: .never
let availability: PlatformAvailability = .all )
let syncPolicy: SyncPolicy = .never
} }
struct ModernProfileNameKey: StorageKey { extension StorageKey where Value == ProfileName {
typealias Value = ProfileName nonisolated static let modernProfileName = StorageKey(
name: "modern_profile_name",
let name = "modern_profile_name" domain: .keychain(service: "com.mbrucedogs.securestorage"),
let domain: StorageDomain = .keychain(service: "com.mbrucedogs.securestorage") security: .keychain(
let security: SecurityPolicy = .keychain(
accessibility: .afterFirstUnlock, accessibility: .afterFirstUnlock,
accessControl: .userPresence accessControl: .userPresence
) ),
let serializer: Serializer<ProfileName> = .json serializer: .json,
let owner = "MigrationDemo" owner: "MigrationDemo",
let description = "Modern profile name stored as structured data." description: "Modern profile name stored as structured data.",
let availability: PlatformAvailability = .all availability: .all,
let syncPolicy: SyncPolicy = .never syncPolicy: .never,
migration: { key in
var migration: AnyStorageMigration? { let sourceKey = StorageKey<String>.legacyProfileName
let sourceKey = StorageKeys.LegacyProfileNameKey()
return AnyStorageMigration( return AnyStorageMigration(
DefaultTransformingMigration( DefaultTransformingMigration(
destinationKey: self, destinationKey: key,
sourceKey: sourceKey sourceKey: sourceKey
) { value in ) { value in
let parts = value.split(separator: " ", maxSplits: 1).map(String.init) 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 Foundation
import LocalData import LocalData
extension StorageKeys { extension StorageKey where Value == String {
/// Syncable setting with configurable platform and sync policy. /// Syncable setting with configurable platform and sync policy.
/// Grouped under Platform to highlight availability/sync behavior. /// Grouped under Platform to highlight availability/sync behavior.
struct SyncableSettingKey: StorageKey { nonisolated static let syncableSetting = StorageKey(
typealias Value = String 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" nonisolated static func syncableSettingKey(
let domain: StorageDomain = .userDefaults(suite: nil) availability: PlatformAvailability = .all,
let security: SecurityPolicy = .none syncPolicy: SyncPolicy = .never
let serializer: Serializer<String> = .json ) -> StorageKey {
let owner = "SampleApp" StorageKey(
let description = "Stores a setting that can be synced to watch." name: "syncable_setting",
let availability: PlatformAvailability domain: .userDefaults(suite: nil),
let syncPolicy: SyncPolicy security: .none,
serializer: .json,
init(availability: PlatformAvailability = .all, syncPolicy: SyncPolicy = .never) { owner: "SampleApp",
self.availability = availability description: "Stores a setting that can be synced to watch.",
self.syncPolicy = syncPolicy availability: availability,
} syncPolicy: syncPolicy
)
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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