Storez :floppy_disk:
Safe, statically-typed, store-agnostic key-value storage
## 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.