{"id":15041096,"url":"https://github.com/omaralbeik/stores","last_synced_at":"2025-04-13T18:20:34.255Z","repository":{"id":50692313,"uuid":"518538250","full_name":"omaralbeik/Stores","owner":"omaralbeik","description":"Typed key-value storage solution to store Codable types in various persistence layers with few lines of code!","archived":false,"fork":false,"pushed_at":"2023-07-30T16:43:39.000Z","size":492,"stargazers_count":136,"open_issues_count":2,"forks_count":12,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-04T14:53:56.436Z","etag":null,"topics":["cache","codable","coredata","database","db","filesystem","identifiable","ios","keychain","keyvaluestore","macos","spm","store","swift","tvos","userdefaults","watchos"],"latest_commit_sha":null,"homepage":"https://swiftpackageindex.com/omaralbeik/Stores","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/omaralbeik.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2022-07-27T16:42:39.000Z","updated_at":"2025-02-15T14:29:07.000Z","dependencies_parsed_at":"2022-08-14T08:50:09.401Z","dependency_job_id":"53721bd3-e9fb-4b8e-8e16-852ac126df00","html_url":"https://github.com/omaralbeik/Stores","commit_stats":{"total_commits":29,"total_committers":4,"mean_commits":7.25,"dds":0.4482758620689655,"last_synced_commit":"638291752db347e40ea4932be424585d47447d32"},"previous_names":[],"tags_count":12,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/omaralbeik%2FStores","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/omaralbeik%2FStores/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/omaralbeik%2FStores/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/omaralbeik%2FStores/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/omaralbeik","download_url":"https://codeload.github.com/omaralbeik/Stores/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248758756,"owners_count":21157024,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cache","codable","coredata","database","db","filesystem","identifiable","ios","keychain","keyvaluestore","macos","spm","store","swift","tvos","userdefaults","watchos"],"created_at":"2024-09-24T20:45:32.467Z","updated_at":"2025-04-13T18:20:34.234Z","avatar_url":"https://github.com/omaralbeik.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 🗂 Stores\n\n[![Stores](https://github.com/omaralbeik/Stores/actions/workflows/CI.yml/badge.svg)](https://github.com/omaralbeik/Stores/actions/workflows/CI.yml)\n[![codecov](https://codecov.io/gh/omaralbeik/Stores/branch/main/graph/badge.svg?token=iga0JA6Mwo)](https://codecov.io/gh/omaralbeik/Stores)\n[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fomaralbeik%2FStores%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/omaralbeik/Stores)\n[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fomaralbeik%2FStores%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/omaralbeik/Stores)\n[![License](https://img.shields.io/badge/License-MIT-red.svg)](https://opensource.org/licenses/MIT)\n\nA 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!\n\n## Features\n\n- [x] macOS Catalina+, iOS 13+, tvOS 13+, watchOS 6+, any Linux supporting Swift 5.4+.\n- [x] Store any `Codable` type.\n- [x] Single API with implementations using User Default, file system, Core Data, Keychain, and fakes for testing.\n- [x] Thread-safe implementations.\n- [x] Swappable implementations with a type-erased store.\n- [x] [Modular library](#installation), add all, or only what you need.\n\n---\n\n## Motivation\n\nWhen working on an app that uses the [composable architecture](https://github.com/pointfreeco/swift-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.\n\n**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.\n\nIt all boils down to the two protocols [`SingleObjectStore`](https://github.com/omaralbeik/Stores/blob/main/Sources/Blueprints/SingleObjectStore.swift) and [`MultiObjectStore`](https://github.com/omaralbeik/Stores/blob/main/Sources/Blueprints/MultiObjectStore.swift) defined in the [**Blueprints**](https://github.com/omaralbeik/Stores/tree/main/Sources/Blueprints) layer, which provide the abstract concepts of stores that can store a single or multiple objects of a generic `Codable` type.\n\nThe two protocols are then implemented in the different modules as explained in the chart below:\n\n![Modules chart](https://raw.githubusercontent.com/omaralbeik/Stores/main/Assets/stores-light.png#gh-light-mode-only)\n![Modules chart](https://raw.githubusercontent.com/omaralbeik/Stores/main/Assets/stores-dark.png#gh-dark-mode-only)\n\n---\n\n## Usage\n\nLet's say you have a `User` struct defined as below:\n\n```swift\nstruct User: Codable {\n    let id: Int\n    let name: String\n}\n```\n\nHere's how you store it using Stores:\n\n\u003cdetails\u003e\n\u003csummary\u003e1. Conform to \u003ccode\u003eIdentifiable\u003c/code\u003e\u003c/summary\u003e\n\nThis is required to make the store associate an object with its id.\n\n```swift\nextension User: Identifiable {}\n```\n\nThe property `id` can be on any `Hashable` type. [Read more](https://developer.apple.com/documentation/swift/identifiable).\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e2. Create a store\u003c/summary\u003e\n\nStores comes pre-equipped with the following stores:\n\n\u003cul\u003e\n\n\u003cli\u003e\n\u003cdetails\u003e\n\u003csummary\u003eUserDefaults\u003c/summary\u003e\n\n```swift\n// Store for multiple objects\nlet store = MultiUserDefaultsStore\u003cUser\u003e(suiteName: \"users\")\n\n// Store for a single object\nlet store = SingleUserDefaultsStore\u003cUser\u003e(suiteName: \"users\")\n```\n\u003c/details\u003e\n\u003c/li\u003e\n\n\u003cli\u003e\n\u003cdetails\u003e\n\u003csummary\u003eFileSystem\u003c/summary\u003e\n\n```swift\n// Store for multiple objects\nlet store = MultiFileSystemStore\u003cUser\u003e(path: \"users\")\n\n// Store for a single object\nlet store = SingleFileSystemStore\u003cUser\u003e(path: \"users\")\n```\n\u003c/details\u003e\n\u003c/li\u003e\n\n\u003cli\u003e\n\u003cdetails\u003e\n\u003csummary\u003eCoreData\u003c/summary\u003e\n\n```swift\n// Store for multiple objects\nlet store = MultiCoreDataStore\u003cUser\u003e(databaseName: \"users\")\n\n// Store for a single object\nlet store = SingleCoreDataStore\u003cUser\u003e(databaseName: \"users\")\n```\n\u003c/details\u003e\n\u003c/li\u003e\n\n\u003cli\u003e\n\u003cdetails\u003e\n\u003csummary\u003eKeychain\u003c/summary\u003e\n\n```swift\n// Store for multiple objects\nlet store = MultiKeychainStore\u003cUser\u003e(identifier: \"users\")\n\n// Store for a single object\nlet store = SingleKeychainStore\u003cUser\u003e(identifier: \"users\")\n```\n\u003c/details\u003e\n\u003c/li\u003e\n\n\u003cli\u003e\n\u003cdetails\u003e\n\u003csummary\u003eFakes (for testing)\u003c/summary\u003e\n\n```swift\n// Store for multiple objects\nlet store = MultiObjectStoreFake\u003cUser\u003e()\n\n// Store for a single object\nlet store = SingleObjectStoreFake\u003cUser\u003e()\n```\n\u003c/details\u003e\n\u003c/li\u003e\n\n\u003c/ul\u003e\n\nYou can create a custom store by implementing the protocols in [`Blueprints`](https://github.com/omaralbeik/Stores/tree/main/Sources/Blueprints)\n\n\u003cul\u003e\n\u003cli\u003e\n\u003cdetails\u003e\n\u003csummary\u003eRealm\u003c/summary\u003e\n\n```swift\n// Store for multiple objects\nfinal class MultiRealmStore\u003cObject: Codable \u0026 Identifiable\u003e: MultiObjectStore {\n    // ...\n}\n\n// Store for a single object\nfinal class SingleRealmStore\u003cObject: Codable\u003e: SingleObjectStore {\n    // ...\n}\n```\n\u003c/details\u003e\n\u003c/li\u003e\n\n\u003cli\u003e\n\u003cdetails\u003e\n\u003csummary\u003eSQLite\u003c/summary\u003e\n\n```swift\n// Store for multiple objects\nfinal class MultiSQLiteStore\u003cObject: Codable \u0026 Identifiable\u003e: MultiObjectStore {\n    // ...\n}\n\n// Store for a single object\nfinal class SingleSQLiteStore\u003cObject: Codable\u003e: SingleObjectStore {\n    // ...\n}\n```\n\u003c/details\u003e\n\u003c/li\u003e\n\n\u003c/ul\u003e\n\u003c/details\u003e\n\n\n\u003cdetails\u003e\n\u003csummary\u003e3. Inject the store\u003c/summary\u003e\n\nAssuming we have a view model that uses a store to fetch data:\n\n```swift\nstruct UsersViewModel {\n    let store: AnyMultiObjectStore\u003cUser\u003e\n}\n```\n\nInject the appropriate store implementation:\n\n```swift\nlet coreDataStore = MultiCoreDataStore\u003cUser\u003e(databaseName: \"users\")\nlet prodViewModel = UsersViewModel(store: coreDataStore.eraseToAnyStore())\n```\n\nor:\n\n```swift\nlet fakeStore = MultiObjectStoreFake\u003cUser\u003e()\nlet testViewModel = UsersViewModel(store: fakeStore.eraseToAnyStore())\n```\n\n\u003c/details\u003e\n\n\u003cdetails\u003e\n\u003csummary\u003e4. Save, retrieve, update, or remove objects\u003c/summary\u003e\n\n```swift\nlet john = User(id: 1, name: \"John Appleseed\")\n\n// Save an object to a store\ntry store.save(john)\n\n// Save an array of objects to a store\ntry store.save([jane, steve, jessica])\n\n// Get an object from store\nlet user = store.object(withId: 1)\n\n// Get an array of object in store\nlet users = store.objects(withIds: [1, 2, 3])\n\n// Get an array of all objects in store\nlet allUsers = store.allObjects()\n\n// Check if store has an object\nprint(store.containsObject(withId: 10)) // false\n\n// Remove an object from a store\ntry store.remove(withId: 1)\n\n// Remove multiple objects from a store\ntry store.remove(withIds: [1, 2, 3])\n\n// Remove all objects in a store\ntry store.removeAll()\n```\n\n\u003c/details\u003e\n    \n---\n    \n## Documentation\n\nRead the full documentation at [Swift Package Index](https://swiftpackageindex.com/omaralbeik/Stores/documentation).\n\n---\n\n## Installation\n\nYou can add Stores to an Xcode project by adding it as a package dependency.\n\n1. From the **File** menu, select **Add Packages...**\n2. Enter \"https://github.com/omaralbeik/Stores\" into the package repository URL text field\n3. Depending on what you want to use Stores for, add the following target(s) to your app:\n    - `Stores`: the entire library with all stores.\n    - `UserDefaultsStore`: use User Defaults to persist data.\n    - `FileSystemStore`: persist data by saving it to the file system.\n    - `CoreDataStore`: use a Core Data database to persist data.\n    - `KeychainStore`: persist data securely in the Keychain.\n    - `Blueprints`: protocols only, this is a good option if you do not want to use any of the provided stores and build yours.\n    - `StoresTestUtils` to use the fakes in your tests target.\n\n---\n\n## Credits and thanks\n\n- [Tom Harrington](https://twitter.com/atomicbird) for writing [\"Core Data Using Only Code\"](https://www.atomicbird.com/blog/core-data-code-only/).\n- [Keith Harrison](https://twitter.com/kharrison) for writing [\"Testing Core Data In A Swift Package\"](https://useyourloaf.com/blog/testing-core-data-in-a-swift-package/).\n- [Riccardo Cipolleschi](https://twitter.com/cipolleschir) for writing [Retrieve multiple values from Keychain](https://medium.com/macoclock/retrieve-multiple-values-from-keychain-77641248f4a1).\n---\n\n## License\n\nStores is released under the MIT license. See [LICENSE](https://github.com/omaralbeik/Stores/blob/main/LICENSE) for more information.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fomaralbeik%2Fstores","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fomaralbeik%2Fstores","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fomaralbeik%2Fstores/lists"}