Typed key-value storage solution to store Codable types in various persistence layers with few lines of code!

Overview

🗂 Stores

Stores codecov License

A typed key-value storage solution to store Codable types in various persistence layers like User Defaults, File System, Core Data, Keychain, and more with a few lines of code!

Features

  • macOS Catalina+, iOS 13+, tvOS 13+, watchOS 6+, any Linux supporting Swift 5.4+.
  • Store any Codable type.
  • Single API with implementations using User Default, file system, Core Data, Keychain, and fakes for testing.
  • Thread-safe implementations.
  • Swappable implementations with a type-erased store.
  • Modular library, add all, or only what you need.

Motivation

When working on an app that uses the composable architecture, I fell in love with how reducers use an environment type that holds any dependencies the feature needs, such as API clients, analytics clients, and more.

Stores tries to abstract the concept of a store and provide various implementations that can be injected in such environment and swapped easily when running tests or based on a remote flag.

It all boils down to the two protocols SingleObjectStore and MultiObjectStore defined in the Blueprints layer, which provide the abstract concepts of stores that can store a single or multiple objects of a generic Codable type.

The two protocols are then implemented in the different modules as explained in the chart below:

Modules chart Modules chart


Usage

Let's say you have a User struct defined as below:

struct User: Codable {
    let id: Int
    let name: String
}

Here's how you store it using Stores:

1. Conform to Identifiable

This is required to make the store associate an object with its id.

extension User: Identifiable {}

The property id can be on any Hashable type. Read more.

2. Create a store

Stores comes pre-equipped with the following stores:

  • UserDefaults
    // Store for multiple objects
    let store = MultiUserDefaultsStore<User>(identifier: "users")
    
    // Store for a single object
    let store = SingleUserDefaultsStore<User>(identifier: "users")
  • FileSystem
    // Store for multiple objects
    let store = MultiFileSystemStore<User>(identifier: "users")
    
    // Store for a single object
    let store = SingleFileSystemStore<User>(identifier: "users")
  • CoreData
    // Store for multiple objects
    let store = MultiCoreDataStore<User>(identifier: "users")
    
    // Store for a single object
    let store = SingleCoreDataStore<User>(identifier: "users")
  • Keychain
    // Store for multiple objects
    let store = MultiKeychainStore<User>(identifier: "users")
    
    // Store for a single object
    let store = SingleKeychainStore<User>(identifier: "users")
  • Fakes (for testing)
    // Store for multiple objects
    let store = MultiObjectStoreFake<User>()
    
    // Store for a single object
    let store = SingleObjectStoreFake<User>()

You can create a custom store by implementing the protocols in Blueprints

  • Realm
    // Store for multiple objects
    final class MultiRealmStore<Object: Codable & Identifiable>: MultiObjectStore {
        // ...
    }
    
    // Store for a single object
    final class SingleRealmStore<Object: Codable>: SingleObjectStore {
        // ...
    }
  • SQLite
    // Store for multiple objects
    final class MultiSQLiteStore<Object: Codable & Identifiable>: MultiObjectStore {
        // ...
    }
    
    // Store for a single object
    final class SingleSQLiteStore<Object: Codable>: SingleObjectStore {
        // ...
    }
3. Inject the store

Assuming we have a view model that uses a store to fetch data:

struct UsersViewModel {
    let store: AnyMultiObjectStore<User>
}

Inject the appropriate store implementation:

let coreDataStore = MultiCoreDataStore<User>(databaseName: "users")
let prodViewModel = UsersViewModel(store: coreDataStore.eraseToAnyStore())

or:

let fakeStore = MultiObjectStoreFake<User>()
let testViewModel = UsersViewModel(store: fakeStore.eraseToAnyStore())
4. Save, retrieve, update, or remove objects
let john = User(id: 1, name: "John Appleseed")

// Save an object to a store
try store.save(john)

// Save an array of objects to a store
try store.save([jane, steve, jessica])

// Get an object from store
let user = store.object(withId: 1)

// Get an array of object in store
let users = store.objects(withIds: [1, 2, 3])

// Get an array of all objects in store
let allUsers = store.allObjects()

// Check if store has an object
print(store.containsObject(withId: 10)) // false

// Remove an object from a store
try store.remove(withId: 1)

// Remove multiple objects from a store
try store.remove(withIds: [1, 2, 3])

// Remove all objects in a store
try store.removeAll()

Documentation

Read the full documentation at Swift Package Index.


Installation

You can add Stores to an Xcode project by adding it as a package dependency.

  1. From the File menu, select Add Packages...
  2. Enter "https://github.com/omaralbeik/Stores" into the package repository URL text field
  3. Depending on what you want to use Stores for, add the following target(s) to your app:
    • Stores: the entire library with all stores.
    • UserDefaultsStore: use User Defaults to persist data.
    • FileSystemStore: persist data by saving it to the file system.
    • CoreDataStore: use a Core Data database to persist data.
    • KeychainStore: persist data securely in the Keychain.
    • Blueprints: protocols only, this is a good option if you do not want to use any of the provided stores and build yours.
    • StoresTestUtils to use the fakes in your tests target.

Credits and thanks


License

Stores is released under the MIT license. See LICENSE for more information.

Comments
  • Fix/SPI Framework documentation blank page

    Fix/SPI Framework documentation blank page

    First attempt to fixing #9

    Probable problem: (😅) The Store root file don't have any documentation.

    My proposed solution:

    Since the root level Store file conditionally imports the other framework, i am thinking adding a root level documentation will fix the issue of the docs landing page being empty. I have added a short summary of the framework description for testing purposes.

    🚨 But for this to work, it's necessary to update the swift-version to 5.5.

    DocC archive: Stores.doccarchive.zip

    NOTE: - If this solution works, then i'll proceed to improve the rest of the documentation to ensure uniformity and at most leverage of the interesting features that DocC offers.

    To test release, we might need to do a preview-release so that SPI can update the change.

    @omaralbeik let me know what you think

    opened by DevAgani 1
  • Documentation Edits

    Documentation Edits

    Description

    This adds a few documentation edits

    Resolution/What did you add?

    • replaced Wether with Whether
    • Added a few articles that we missing

    Additional Release Steps

    None

    Checklist

    • [x] I have performed a self-review of my own code
    • [x] My changes generate no new errors
    • [x] Is my code backwards compatible
    opened by DevAgani 1
  • Add core data db url, fix a bug in any stores, add more tests

    Add core data db url, fix a bug in any stores, add more tests

    • Add databaseURL property to core data stores to return SQLite database url.
    • Fix a bug where calling eraseToAnyStore on an any store is wrapping it again in a new any store.
    • Add more tests for code data stores
    opened by omaralbeik 1
  • Deprecations and core data stores customization

    Deprecations and core data stores customization

    Changes

    • MultiUserDefaultsStore.identifier and SingleUserDefaultsStore.identifier properties has been renamed to suiteName to reflect the usage in UserDefaults.
    • MultiFileSystemStore.identifier and SingleFileSystemStore.identifier properties has been renamed to path to reflect the usage in file system.
    • MultiCoreDataStore.identifier and SingleCoreDataStore.identifier properties has been renamed to databaseName to reflect the usage in Core Data.
    • MultiCoreDataStore and SingleCoreDataStore initializers has a new containerProvider argument that allows providing custom NSPersistentContainer, closes #15
    • Add better documentation in all stores.
    opened by omaralbeik 0
  • Thread safety and misc fixes

    Thread safety and misc fixes

    • Fix a bug where thread locks where not locked back after performing a throwing code (thanks to Markus Englund for pointing this out!)
    • Fix a bug where SingleCoreDataStore was thread safe
    • Add import checked to make the library one step closer to work on Linux
    opened by omaralbeik 0
  • Can't delete from KeyChainStore

    Can't delete from KeyChainStore

    Whenever I want to delete a value from the MultiKeyChainStore I get the error: KeyChainStore.KeyChainError-Error 0.

    This is the code im using:

    do {
        try keyChainStore.removeAll()
    } catch {
        print(error.localizedDescription)
    }
    
    opened by justTreme 7
Releases(0.4.1)
  • 0.4.1(Nov 16, 2022)

    What's Changed

    • Add error description in KeychainError in https://github.com/omaralbeik/Stores/pull/21

    Full Changelog: https://github.com/omaralbeik/Stores/compare/0.4.0...0.4.1

    Source code(tar.gz)
    Source code(zip)
  • 0.4.0(Nov 8, 2022)

    What's Changed

    UserDefaults

    • identifier property has been renamed to suiteName to reflect the usage in UserDefaults.

    FileSystem

    • identifier property has been renamed to path to reflect the usage in file system.

    CoreData

    • identifier property has been renamed to databaseName to reflect the usage in Core Data.
    • Initializers have a new containerProvider argument that allows providing custom NSPersistentContainer, closes #15

    Full Changelog: https://github.com/omaralbeik/Stores/compare/0.3.4...0.4.0

    Source code(tar.gz)
    Source code(zip)
  • 0.3.4(Oct 30, 2022)

    What's Changed

    • Fix/SPI Framework documentation blank page by @DevAgani in https://github.com/omaralbeik/Stores/pull/16
    • Add DocC documentation by @omaralbeik in https://github.com/omaralbeik/Stores/pull/18

    Full Changelog: https://github.com/omaralbeik/Stores/compare/0.3.3...0.3.4

    Source code(tar.gz)
    Source code(zip)
  • 0.3.3(Sep 17, 2022)

    What's Changed

    • Documentation typo fixes by @DevAgani in https://github.com/omaralbeik/Stores/pull/13
    • Add KeychainStore dependency to Stores by @jordanekay in https://github.com/omaralbeik/Stores/pull/14

    New Contributors

    • @DevAgani made their first contribution in https://github.com/omaralbeik/Stores/pull/13
    • @jordanekay made their first contribution in https://github.com/omaralbeik/Stores/pull/14

    Full Changelog: https://github.com/omaralbeik/Stores/compare/0.3.2...0.3.3

    Source code(tar.gz)
    Source code(zip)
  • 0.3.2(Aug 27, 2022)

    What's Changed

    • Improve docs and increase test coverage
    • Add databaseURL property to core data stores to return SQLite database url.
    • Fix a bug where calling eraseToAnyStore on an any store is wrapping it again in a new any store.
    • Add more tests for code data stores

    Full Changelog: https://github.com/omaralbeik/Stores/compare/0.3.1...0.3.2

    Source code(tar.gz)
    Source code(zip)
  • 0.3.1(Aug 25, 2022)

    What's Changed

    • Add missing documentation https://github.com/omaralbeik/Stores/pull/8

    Full Changelog: https://github.com/omaralbeik/Stores/compare/0.3.0...0.3.1

    Source code(tar.gz)
    Source code(zip)
  • 0.3.0(Aug 22, 2022)

    What's Changed

    • Fix a bug where thread locks where not locked back after performing a throwing code (thanks to Markus Englund for pointing this out!)
    • Fix a bug where SingleCoreDataStore was thread safe
    • Add import checked to make the library one step closer to work on Linux

    Full Changelog: https://github.com/omaralbeik/Stores/compare/0.2.2...0.3.0

    Source code(tar.gz)
    Source code(zip)
  • 0.2.2(Aug 21, 2022)

    What's Changed

    • Add package index badges in https://github.com/omaralbeik/Stores/pull/5
    • Add SPI manifest file in https://github.com/omaralbeik/Stores/pull/6

    Full Changelog: https://github.com/omaralbeik/Stores/compare/0.2.1...0.2.2

    Source code(tar.gz)
    Source code(zip)
  • 0.2.1(Aug 21, 2022)

  • 0.2.0(Aug 21, 2022)

Owner
Omar Albeik
It’s not a bug – it’s an undocumented feature!
Omar Albeik
💾 Safe, statically-typed, store-agnostic key-value storage written in Swift!

Storez ?? Safe, statically-typed, store-agnostic key-value storage Highlights Fully Customizable: Customize the persistence store, the KeyType class,

Kitz 67 Aug 7, 2022
An Objective-C wrapper for RocksDB - A Persistent Key-Value Store for Flash and RAM Storage.

ObjectiveRocks ObjectiveRocks is an Objective-C wrapper of Facebook's RocksDB - A Persistent Key-Value Store for Flash and RAM Storage. Current RocksD

Iskandar Abudiab 56 Nov 5, 2022
An efficient, small mobile key-value storage framework developed by WeChat. Works on Android, iOS, macOS, Windows, and POSIX.

中文版本请参看这里 MMKV is an efficient, small, easy-to-use mobile key-value storage framework used in the WeChat application. It's currently available on Andr

Tencent 15.4k Jan 6, 2023
An elegant, fast, thread-safe, multipurpose key-value storage, compatible with all Apple platforms.

KeyValueStorage An elegant, fast, thread-safe, multipurpose key-value storage, compatible with all Apple platforms. Supported Platforms iOS macOS watc

null 3 Aug 21, 2022
CloudKit, Apple’s remote data storage service, provides a possibility to store app data using users’ iCloud accounts as a back-end storage service.

CloudKit, Apple’s remote data storage service, provides a possibility to store app data using users’ iCloud accounts as a back-end storage service. He

Yalantis 252 Nov 4, 2022
YapDB is a collection/key/value store with a plugin architecture. It's built atop sqlite, for Swift & objective-c developers.

YapDatabase is a collection/key/value store and so much more. It's built atop sqlite, for Swift & Objective-C developers, targeting macOS, iOS, tvOS &

Yap Studios 3.3k Dec 29, 2022
Key-Value store for Swift backed by LevelDB

SwiftStore Key/Value store for Swift backed by LevelDB. Usage Create instances of store import SwiftStore // Create a store. let store = SwiftStore(s

Hemanta Sapkota 119 Dec 21, 2022
🛶Shallows is a generic abstraction layer over lightweight data storage and persistence.

Shallows Shallows is a generic abstraction layer over lightweight data storage and persistence. It provides a Storage<Key, Value> type, instances of w

Oleg Dreyman 620 Dec 3, 2022
A protocol-centric, type and queue safe key-value workflow.

Light-weight, strict protocol-first styled PropertyKit helps you to easily and safely handle guaranteed values, keys or types on various situations of

gitmerge 12 Feb 26, 2021
Why not use UserDefaults to store Codable objects 😉

tl;dr You love Swift's Codable protocol and use it everywhere, who doesn't! Here is an easy and very light way to store and retrieve -reasonable amoun

Omar Albeik 452 Oct 17, 2022
StorageManager - FileManager framework that handels Store, fetch, delete and update files in local storage

StorageManager - FileManager framework that handels Store, fetch, delete and update files in local storage. Requirements iOS 8.0+ / macOS 10.10+ / tvOS

Amr Salman 47 Nov 3, 2022
⚙️ A tiny property wrapper for UserDefaults. Only 60 lines of code.

⚙️ A tiny property wrapper for UserDefaults. Only 60 lines of code. import Persistent extension UserDefaults { // Optional property @Per

Mezhevikin Alexey 6 Sep 28, 2022
A type-safe, protocol-based, pure Swift database offering effortless persistence of any object

There are many libraries out there that aims to help developers easily create and use SQLite databases. Unfortunately developers still have to get bogged down in simple tasks such as writing table definitions and SQL queries. SwiftyDB automatically handles everything you don't want to spend your time doing.

Øyvind Grimnes 489 Sep 9, 2022
KeyPathKit is a library that provides the standard functions to manipulate data along with a call-syntax that relies on typed keypaths to make the call sites as short and clean as possible.

KeyPathKit Context Swift 4 has introduced a new type called KeyPath, with allows to access the properties of an object with a very nice syntax. For in

Vincent Pradeilles 406 Dec 25, 2022
Simple, Strongly Typed UserDefaults for iOS, macOS and tvOS

简体中文 DefaultsKit leverages Swift 4's powerful Codable capabilities to provide a Simple and Strongly Typed wrapper on top of UserDefaults. It uses less

Nuno Dias 1.4k Dec 26, 2022
Add Strictly Typed NIO Channel Builders for Swift 5.7

⚠️ Requires Swift 5.7 Omnibus is a set of helpers for SwiftNIO that allow you to leverage Swift's generics type system to create NIO Channels. It depe

Orlandos 6 Jun 10, 2022
Modern interface to UserDefaults + Codable support

Default Modern interface to UserDefaults + Codable support What is Default? Default is a library that extends what UserDefaults can do by providing ex

Nicholas Maccharoli 475 Dec 20, 2022
CodableFiles - Save and load Codable objects from DocumentDirectory on iOS Devices.

Welcome to CodableFiles, a simple library that provides an easier way to save, load or delete Codable objects in Documents directory. It’s primarily a

Egzon Pllana 36 Dec 20, 2022
AppCodableStorage - Extends `@AppStorage` in SwiftUI to support any Codable object

AppCodableStorage Extends @AppStorage in SwiftUI to support any Codable object.

Andrew Pouliot 19 Nov 25, 2022