Skip to content

Instantly share code, notes, and snippets.

@robertvunabandi
Created November 18, 2025 05:03
Show Gist options
  • Select an option

  • Save robertvunabandi/a9edd11763fe0b8be890d7a2907ee29d to your computer and use it in GitHub Desktop.

Select an option

Save robertvunabandi/a9edd11763fe0b8be890d7a2907ee29d to your computer and use it in GitHub Desktop.
//
// DataStore.swift
// Reading Journal
//
//
import Foundation
/// The job of a ``DataStore`` is simply to store the data that's been fetched
/// and organize it. The data store's data is managed using a Singleton pattern,
/// so there's only ever one of these in the entire app.
///
/// A few possible cases where this is especially useful:
/// - If the same data is fetched twice, a data store will know to not store duplicates
/// - When users log out (or when there's an inconsistency in the data), the datastore
/// will know to delete invalid data.
///
/// **IMPORTANT:** A decision decision has been made, from the server side,
/// that is very important to the way things are expected to work here. Namely: it is
/// not possible to make an edit to a model that will cascade another edit into
/// another model. Here's an example of a classic violation case: Deleting an
/// ``MAuthor`` would result in the deletion of all of that authors' sources.
/// However, we make it impossible to delete an author that still has sources in
/// one's library. We will ensure this constraint applies to all types throughout
/// all server calls. What this decision enables is that we can independently
/// update each data store without needing to update other data stores.
protocol DataStore: ObservableObject {
/// An associated type for this ``DataStore``, which helps us ensure that
/// we always also have an associated ``DataFetcher`` and that all data
/// stores are flushed.
static var Enum: DataType { get }
/// Erase all the data that is currently stored in this data store
static func flush()
/// The type of fetcher that's associated to this ``DataStore``
associatedtype Fetcher: DataFetcher where Fetcher.Store == Self
/// Returns an instance of the data fetcher for this data store. This should be the
/// only way to get a data fetcher throughout the app. Do not instantiate data
/// fetchers directly.
func fetcher() -> Fetcher
/// For debugging purposes, we may want the datastore to be given preliminary
/// starting data (i.e., "seed" data). The following method is a way to seed this
/// data into the data store. It is up to each individual data store to seed itself
/// with the particular type of ``DataStoreSeeding`` data that works for
/// it.
func seed(_ seeding: DataStoreSeeding)
}
extension DataStore {
func fetcher() -> Fetcher {
Fetcher(store: self)
}
func seedings(_ seedings: [DataStoreSeeding]) -> Self {
for seeding in seedings {
seed(seeding)
}
return self
}
}
/// This is a data store that only has a single instance.
protocol SingleInstanceDataStore: DataStore {
/// Access the singleton for this datastore.
static func shared(_ user: MUser) -> Self
}
/// This is a data store that can have multiple instances.
protocol MultipleInstanceDataStore: DataStore {
/// A defined ``Key`` that will allow to fetch the instance for that key.
associatedtype Key: Codable, Hashable
/// We want to be able to access the key for this data store so that the fetcher
/// can use it when it's fetching data (we need to ensure that the data stored
/// from the fetcher actually belongs to this data store, and this enables that)
var key: Key { get }
/// This is the way to access the various "singleton" instances. We want to
/// make sure to create them only when they're needed and such.
static func shared(_ user: MUser, key: Key) -> Self
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment