Storez :floppy_disk:

Safe, statically-typed, store-agnostic key-value storage

Version CI Swift Platforms

## Highlights + __Fully Customizable:__
Customize the persistence store, the `KeyType` class, post-commit actions .. Make this framework yours! + __Batteries Included__:
In case you just want to use stuff, the framework is shipped with pre-configured basic set of classes that you can just use. + __Portability, Check!:__
If you're looking to share code between your app and extensions, you're covered! Use `UserDefaultsStore` with a shared container suite name. + __Swift 6 Ready:__
Full Swift 6 language mode with strict concurrency. All stores and keys are `Sendable`. **Example:** ```swift final class WeatherService { private enum Keys: Namespace { static let id = "weather-service" static let temperature = Key(id: "temp", defaultValue: 0.0) } private let store: UserDefaultsStore var temperature: Double { return store.get(Keys.temperature) } init(store: UserDefaultsStore) { self.store = store } func weatherUpdate(temperature: Double) { store.set(Keys.temperature, temperature) } } ``` ## Features __Available Stores__ | Store | Backend | |-------|---------| | `UserDefaultsStore` | `UserDefaults` | | `CacheStore` | `NSCache` | __Type-safe, store-agnostic, nestable Key definitions__ ```swift // Entries must belong to a "group", for namespacing struct Animals: Namespace { static let id = "animals" } let kingdom = Key(id: "mammals", defaultValue: nil) kingdom.stringValue // "animals:mammals" // Nesting struct Cats: Namespace { typealias parent = Animals static let id = "cats" // Namespaces also have pre and post commit hooks func preCommitHook() { /* custom code */ } func postCommitHook() { /* custom code */ } } let cat = Key(id: "lion", defaultValue: nil) cat.stringValue // "animals:cats:lion" ``` __Initialize the store you want__ ```swift // Use UserDefaultsStore for this example let store = UserDefaultsStore(suite: "io.kitz.testing") let key = Key(id: "key", defaultValue: nil) // With three simple functions store.set(key, value: 8) store.get(key) // 8 store.clear() // Start fresh every time for testing ``` __Optionality is honored throughout__ ```swift let nullable = Key(id: "nullable", defaultValue: nil) store.get(nullable)?.isEmpty // nil store.set(nullable, value: "") store.get(nullable)?.isEmpty // true let nonnull = Key(id: "nonnull", defaultValue: "!") store.get(nonnull).isEmpty // false store.set(nonnull, value: "") store.get(nonnull).isEmpty // true ``` __Custom objects easily supported via `Codable`__ ```swift struct CustomObject: Codable { var strings: [String] } let customObject = CustomObject( strings: ["fill", "in", "the"] ) // Add a processing block to transform values on set let customKey = Key(id: "custom", defaultValue: nil) { var processedValue = $0 processedValue?.strings.append("blank!") return processedValue } store.set(customKey, value: customObject) store.get(customKey)?.strings.joined(separator: " ") // fill in the blank! ``` You can also use `UserDefaultsConvertible` for custom serialization. __Make your own `KeyType`__ ```swift // For example, make a key that posts notifications on change struct MyKey: KeyType { typealias NamespaceType = G typealias ValueType = V var stringValue: String var defaultValue: ValueType func didChange(_ oldValue: ValueType, newValue: ValueType) { NotificationCenter.default.post(name: .init(stringValue), object: nil) } } ``` __Migrate from legacy keys__ ```swift let migration = UserDefaultsStore.Migration( to: Key(id: "city", defaultValue: .default), from: "OldCityKey" ) { oldValue in // Convert the old raw value to the new type City(rawValue: oldValue as! String) } store.migrate([migration]) ``` ## Getting Started ### Swift Package Manager #### You can add Storez to an Xcode project by adding it as a package dependency. - In Xcode, from the File menu, select Add Packages... - Enter "https://github.com/SwiftKitz/Storez" into the package repository URL text field Depending on how your project is structured: - If you have a single application target that needs access to the library, then add Storez directly to your application. - If you want to use this library from multiple Xcode targets, or mixing Xcode targets and SPM targets, you likely want to create a shared framework that depends on Storez and then depend on that framework in all of your targets. #### To use Storez in a Package.swift file, add this to the `dependencies:` section. ```swift .package(url: "https://github.com/SwiftKitz/Storez.git", from: "5.0.0"), ``` ## Motivation I've seen a lot of great attempts at statically-typed data stores, but they all build a tightly coupled design that limits the end-developer's freedom. With this framework, you can start prototyping right away with the shipped features, then replace the persistence store and `KeyType` functionality with your heart's content __and__ keep your code the way it is! ## Author Mazyod ([@Mazyod](http://twitter.com/mazyod)) ## License Storez is released under the MIT license. See LICENSE for details.