{"id":20064261,"url":"https://github.com/mochidev/codabledatastore","last_synced_at":"2025-12-26T15:34:27.384Z","repository":{"id":170542506,"uuid":"646693836","full_name":"mochidev/CodableDatastore","owner":"mochidev","description":"An ACID-compliant database written in pure-swift enabling on-disk persistence for apps and services.","archived":false,"fork":false,"pushed_at":"2024-04-14T10:21:53.000Z","size":344,"stargazers_count":13,"open_issues_count":18,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-04-14T10:59:56.321Z","etag":null,"topics":["database","ios","linux","macos","swift","swift-async","swift-package-manager","swift-server","swift5-10","swift5-9","tvos","visionos","watchos"],"latest_commit_sha":null,"homepage":"","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/mochidev.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,"roadmap":null,"authors":null,"dei":null}},"created_at":"2023-05-29T06:04:43.000Z","updated_at":"2024-04-20T23:21:44.012Z","dependencies_parsed_at":null,"dependency_job_id":"fcc38b15-ab0e-4141-ac46-e34e83a6bce1","html_url":"https://github.com/mochidev/CodableDatastore","commit_stats":null,"previous_names":["mochidev/codabledatastore"],"tags_count":42,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mochidev%2FCodableDatastore","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mochidev%2FCodableDatastore/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mochidev%2FCodableDatastore/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mochidev%2FCodableDatastore/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mochidev","download_url":"https://codeload.github.com/mochidev/CodableDatastore/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":224458290,"owners_count":17314639,"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":["database","ios","linux","macos","swift","swift-async","swift-package-manager","swift-server","swift5-10","swift5-9","tvos","visionos","watchos"],"created_at":"2024-11-13T13:45:27.865Z","updated_at":"2025-12-26T15:34:27.371Z","avatar_url":"https://github.com/mochidev.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# CodableDatastore\n\n\u003cp align=\"center\"\u003e\n    \u003ca href=\"https://swiftpackageindex.com/mochidev/CodableDatastore\"\u003e\n        \u003cimg src=\"https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fmochidev%2FCodableDatastore%2Fbadge%3Ftype%3Dswift-versions\" /\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://swiftpackageindex.com/mochidev/CodableDatastore\"\u003e\n        \u003cimg src=\"https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fmochidev%2FCodableDatastore%2Fbadge%3Ftype%3Dplatforms\" /\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://github.com/mochidev/CodableDatastore/actions?query=workflow%3A%22Test+CodableDatastore%22\"\u003e\n        \u003cimg src=\"https://github.com/mochidev/CodableDatastore/workflows/Test%20CodableDatastore/badge.svg\" alt=\"Test Status\" /\u003e\n    \u003c/a\u003e\n\u003c/p\u003e\n\nA pure-Swift implementation of a database-like persistent store for use in apps and other single-write-multiple-read environments.\n\n## Quick Links\n\n- [Documentation](https://swiftpackageindex.com/mochidev/CodableDatastore/documentation)\n- [Updates on Mastodon](https://mastodon.social/tags/CodableDatastore)\n\n## Installation\n\nAdd `CodableDatastore` as a dependency in your `Package.swift` file to start using it. Then, add `import CodableDatastore` to any file you wish to use the library in.\n\nPlease check the [releases](https://github.com/mochidev/CodableDatastore/releases) for recommended versions.\n\n```swift\ndependencies: [\n    .package(\n        url: \"https://github.com/mochidev/CodableDatastore.git\", \n        .upToNextMinor(from: \"0.4.0\")\n    ),\n],\n...\ntargets: [\n    .target(\n        name: \"MyPackage\",\n        dependencies: [\n            \"CodableDatastore\",\n        ]\n    )\n]\n```\n\n## Usage\n\nThere are three basic steps to use `CodableDatastore`:\n- First, declare a _format_ for your datastore by conforming a struct to [`DatastoreFormat`](https://swiftpackageindex.com/mochidev/codabledatastore/main/documentation/codabledatastore/datastoreformat).\n- Then, instanciate the [persistence](https://swiftpackageindex.com/mochidev/codabledatastore/main/documentation/codabledatastore/diskpersistence) on disk where it will be stored.\n- Finally, [read](https://swiftpackageindex.com/mochidev/codabledatastore/main/documentation/codabledatastore/datastore/load%28_:%29-6uzkw) and [write](https://swiftpackageindex.com/mochidev/codabledatastore/main/documentation/codabledatastore/datastore/persist%28_:%29) to it as necessary.\n\n### Declaring a Format\n\nFirst, you must decide on the shape of your model. Each datastore can contain exactly one type, though a persistence can coordinate access across different types. Since `CodableDatastore` was designed with value types in mind, avoid using classes for your model objects.\n\nYou'll also need a corresponding _format_ for your datastore, that describes how your type is identified, versioning information, and any indexes you'd like to be automatically created on your behalf.\n\n`CodableDatastore` encourages a pattern where the _format_ you declare becomes the primary namespace for your type and its related meta types:\n\n```swift\nimport Foundation\nimport CodableDatastore\n\nstruct BookStore: DatastoreFormat {\n    // These are both required, and used by the initializer for `Datastore` below.\n    static let defaultKey: DatastoreKey = \"BooksStore\"\n    static let currentVersion: Version = .current\n\n    // A required description of your current and past versions you support\n    // decoding. Note that the current version should always have a sensible\n    // value, as it will get encoded to the persistence. \n    // Either `Int`s or `String`s are supported.\n    enum Version: String {\n        case v1 = \"2024-04-01\"\n        case current = \"2024-04-09\"\n    }\n\n    // A required \"pointer\" to the latest representation of your type.\n    typealias Instance = Book\n    // Optional if the Instance above is Identifiable, but required otherwise.\n    // typealias Identifier = UUID\n\n    // The first version we shipped with and need to support.\n    struct BookV1: Codable, Identifiable {\n        var id: UUID\n        var title: String\n        var author: String\n    }\n\n    // The current version we use in the app.\n    struct Book: Codable, Identifiable {\n        var id: UUID\n        var title: SortableTitle\n        var authors: [AuthorID]\n        var isbn: ISBN\n    }\n\n    // A non-required helper for instanciating a datastore. Note many\n    // parameters are inferred, since `DatastoreFormat` declares a specialized\n    // `Self.Datastore` with all the generic parameters filled out.\n    // `CodableDatastore` comes with initializers for JSON and Property list\n    // stores, though completely custom coders can easily be supported by using\n    // the default initializers on `Datastore`.\n    static func datastore(for persistence: DiskPersistence\u003cReadWrite\u003e) -\u003e Self.Datastore {\n        .JSONStore(\n            persistence: persistence,\n            // This is where all the migrations for different versions are\n            // defined; Simply decode the type you know if stored for a given \n            // version, and convert it to the modern type you use in the app.\n            // Conversions are typically only done at read time.\n            migrations: [\n                .v1: { try Book($0.decode(BookV1.self, from: $1)) },\n                .current: { try $0.decode(Book.self, from: $1) }\n            ]\n        )\n    }\n    \n    // Declare your indexes here by declaring stored properties in one of the\n    // provided `Index` types. The keypaths refer to the main Instance for the\n    // format you declared. Both stored and computed keypaths are supported.\n    // Indexes are automatically re-computed whenever the names or types of\n    // these declarations change, though the key path itself can change silently.\n    // Note: Manual migrations of these is not currently supported but is planned.\n    let title = Index(\\.title)\n    let author = ManyToManyIndex(\\.authors)\n    // A one to one index where every isbn points to exactly one book.\n    // Note the index is marked with `@Direct`, which optimizes reads by isbn by\n    // trading off the additional storage space needed to store full copies of\n    // the `Book` struct in that index.\n    @Direct var isbn = OneToOneIndex(\\.isbn)\n}\n\n// A convenience alias for the rest of the app to use.\ntypealias Book = BookStore.Book\n\n// Declare any necessary conversions to make old stored instances continue working.\nextension Book {\n    init(_ bookV1: BookStore.BookV1) {\n        self.init(\n            id: id,\n            title: SortableTitle(title),\n            authors: [AuthorID(authors)],\n            isbn: ISBN.generate()\n        )\n    }\n}\n```\n\n### Instanciating Your Persistence\n\nNext, setup an actor or other manager to \"own\" your persistence and datastores in a way that makes sense for your app. Note that persistences and datastores don't need an async or throwable context to be instantiated, and provide deferred methods to do this when you can optionally show UI around these actions. Keep a reference to the persistence and datastore actors you create and either pass them around in your app individually, or keep them abstracted away in a single manager with getters and setters for common operations.\n\n```swift\nimport Foundation\nimport CodableDatastore\n\nactor LibraryPersistence {\n    // Keep these around so we can access them as necessary\n    let persistence: DiskPersistence\u003cReadWrite\u003e\n    \n    let bookDatastore: BookStore.Datastore\n    let authorDatastore: AuthorStore.Datastore\n    let shelfDatastore: ShelfStore.Datastore\n\n    init() async throws {\n        // Initialize the persistence to Application Support, or pass in a readWriteURL\n        persistence = try DiskPersistence.defaultStore()\n        // Make sure we can write and access it\n        try await persistence.createPersistenceIfNecessary()\n        \n        // Initialize the datastores so we can refer back to them\n        bookDatastore = BookStore.datastore(for: persistence)\n        authorDatastore = AuthorStore.datastore(for: persistence)\n        shelfDatastore = ShelfStore.datastore(for: persistence)\n        \n        // Warm the datastores to re-build any indexes changed suring development.\n        // This is an excellend opportunity to show migration UI if the process takes longer than a second.\n        try await bookDatastore.warm { progress in\n            switch progress {\n            case .evaluating:\n                // Always called\n                print(\"Checking Books…\") \n            case .working(let current, let total):\n                // Only called if migrating. Signal some UI and log the values.\n                // `current` is 0-based.\n                print(\"  → Migrating \\(current+1)/\\(total) Books…\")\n            case .complete(let total):\n                // Always called\n                print(\"  ✓ Finished checking \\(total) Books!\") \n            }\n        }\n        try await authorDatastore.warm()\n        try await shelfDatastore.warm()\n    }\n}\n```\n\n### Access the Datastores\n\nOnce you have a datastore, you can read from it in any async throwing context:\n\n```swift\nlet bookByID = try await bookDatastore.load(bookID)\nlet bookByISBN = try await bookDatastore.load(isbn, from: \\.isbn)\n```\n\nNote that in the above examples, bookID is of type `BookStore.Identifier`, aka `Book.ID`, and `\\.isbn` is the keypath on `BookStore` that points to the ISBN index, a one-to-one index. These both return optionals, and thus `nil` if the instance for the given key \n\nA range of results can also be attained as an asynchronous sequence:\n\n```swift\nfor try await book in bookDatastore.load(\"A\"..\u003c\"B\", from: \\.title) {\n    print(\"Book that starts with A: \\(book.title)\")\n}\n\nguard let dimitri = authorDatastore.load(\"Dimitri Bouniol\", from: \\.fullname).firstInstance\nelse { throw NotFoundError() }\nfor try await book in bookDatastore.load(dimitri.id, from: \\.author) {\n    print(\"Book written by Dimitri: \\(book.title)\")\n}\n\nlet allShelves = try await shelfDatastore.load(...).collectInstances(upTo: .infinity)\n```\n\nWriting or deleteing is equally as straight-forward:\n```swift\nlet oldValue = try await bookDatastore.persist(newBook)\nlet oldValue = try await bookDatastore.delete(oldBookID)\nlet oldOptionalValue = try await bookDatastore.deleteIfPresent(oldBookID)\n\n// Passing an Identifiable instance also works:\nlet oldValue = try await bookDatastore.delete(oldBook)\n```\n\nIf you are reading and writing multiple things, you can wrap them in a transaction to ensure they all get written to the persistence together, ensuring that you either have all the data, or none of the data if an error occurs:\n```swift\ntry await persistence.perform {\n    try await authorDatastore.persist(newAuthor)\n    for newBook in newBooks {\n        guard let shelf = try await shelfDatastore.load(newBook.genre, from: \\.genre)\n        else { throw ShelfNotFoundError() }\n        \n        newBook.shelfID = shelf\n        try await authorDatastore.persist(newBook)\n    }\n}\n```\n\nNote that in the example above, even though the author is persisted first, if an error occurs fetching the shelf for the book, the author will _not_ be present in the datastore in future reads. Additionally, no two writes can occur simultaneously no matter the async context, as all individual operations are themselves full transactions.\n\n## What is `CodableDatastore`?\n\n`CodableDatastore` is a collection of types that make it easy to interface with large data stores of independent types without loading the entire data store in memory.\n\n\u003e **Warning**\n\u003e THINK CAREFULLY ABOUT USING THIS IN PRODUCTION PROJECTS. As this project only just entered its beta phase, I cannot stress how important it is to be very careful about shipping anything that relies on this code, as you may experience data loss migrating to a newer version. Although less likely, there is a chance the underlying model may change in an incompatible way that is not worth supporting with migrations.\n\u003e Until then, please enjoy the code as a spectator or play around with it in toy projects to submit feedback! If you would like to be notified when `CodableDatastore` enters a production-ready state, please follow [#CodableDatastore](https://mastodon.social/tags/CodableDatastore) on Mastodon.\n\n### Road to 1.0\n\nAs this project matures towards release, the project will focus on the functionality and work listed below:\n- Force migration methods\n- Composite indexes (via macros?)\n- Cleaning up old resources on disk\n- Ranged deletes\n- Controls for the edit history\n- Helper types to use with SwiftUI/Observability/Combine that can make data available on the main actor and filter and stay up to date\n- Comprehensive test coverage\n- Comprehensive usage guides\n- An example app\n- A memory persistence useful for testing apps with\n- A pre-configured data store tuned to storing pure Data, useful for types like Images\n- Cleaning up memory leaks\n\nThe above list will be kept up to date during development and will likely see additions during that process.\n\n### Beyond 1.0\n\nOnce the 1.0 release has been made, it'll be time to start working on additional features:\n- Snapshots and Backups\n- A companion app to open, inspect, and modify datastores\n- Other kinds of persistences, such as a distributed one for multi-server deployments\n- Compression and encryption on a per-datastore basis\n- External writes to a shared inbox\n\n### Original Goals\n\n\u003cdetails open\u003e\n\u003csummary\u003e\u003cstrong\u003eUse Codable and Identifiable to as the only requirements for types saved to the data store.\u003c/strong\u003e\u003c/summary\u003e\n\nHaving types conform to Codable and Identifiable as their only requirements means that many types won't need additional conformances or transformations to be used in other layers of the app, including at the view and network layers. Types must however conform to Identifiable so they can be updated when indexes require.\n\n\u003c/details\u003e\n\n\u003cdetails open\u003e\n\u003csummary\u003e\u003cstrong\u003eAllow the user to specify the Data-conforming Coder to use.\u003c/strong\u003e\u003c/summary\u003e\n\nSince `CodableDatastore` works with Codable types, it can flexibly support different types of coders. Out of the box, we plan on supporting both JSON and Property List coders as they provide an easy way for users to investigate the data saved to the store should they require doing so.\n\n\u003c/details\u003e\n\n\u003cdetails open\u003e\n\u003csummary\u003e\u003cstrong\u003eGuarantee consistency across writes, using the filesystem to snapshot and make operations atomic.\u003c/strong\u003e\u003c/summary\u003e\n\nAll file operations will operate on copies of the files being modified, ultimately being persisted by updating the root file with a pointer to the updated set of files, and deleting the old file references once they are no longer referenced. This means that if the process is interrupted for any reason, data integrity is maintained and consistent.\n\nAdditionally, if any unreferenced filed are identified, they could be placed in a Recovered Files directory allowing the developer of an app to help their users recover data should catastrophe arise.\n\n\u003c/details\u003e\n\n\u003cdetails open\u003e\n\u003csummary\u003e\u003cstrong\u003eEnable other processes to concurrently read from the data store.\u003c/strong\u003e\u003c/summary\u003e\n\nA common pattern is for App Extensions to need to read data from the main app, but not write to it. In this case, the data store can safely be opened as read only at the time of initialization, allowing the contents of that data store to be read by the app extension.\n\nFor cases where the Extension needs to write data for the app, it is suggested a separate persistence be used to communicate that flow of data, as persistences do not support multiple writing processes.\n\n\u003c/details\u003e\n\n\u003cdetails open\u003e\n\u003csummary\u003e\u003cstrong\u003eOffer an API than can make performance promises.\u003c/strong\u003e\u003c/summary\u003e\n\nAs the `CodableDatastore` is configured directly with indexes that the user specified, `CodableDatastore` can make performance guarantees without any hidden gotchas, as data can only be accessed via one of those indexes, and data cannot be loaded by a non-indexed key.\n\n\u003c/details\u003e\n\n\u003cdetails open\u003e\n\u003csummary\u003e\u003cstrong\u003eBuild on existing paradigms of the Swift language, using Swift concurrency to make operations async, and offer loading large amounts of data via Async Sequences.\u003c/strong\u003e\u003c/summary\u003e\n\n`CodableDatastore` makes liberal use of Swift's concurrency APIs, with all reads and writes being async operations that can fail in a way the user can do something about, and offers streams to data being loaded via AsyncSequences, allowing data to be loaded efficiently at the rate the consumer expects it.\n\n\u003c/details\u003e\n\n\u003cdetails open\u003e\n\u003csummary\u003e\u003cstrong\u003eAllow re-indexing at any time, even for an existing data store.\u003c/strong\u003e\u003c/summary\u003e\n\nApps change how they access data during development, and indexes evolve as a result of that. Since indexes are configured in code, they can change between builds or releases, so `CodableDatastore` supports re-indexing data should it determine that indexes have been re-configured. A method is provided to allow the app to await the re-indexing process with progress so a user interface can be shown to the user while this is happening.\n\n\u003c/details\u003e\n\n\u003cdetails open\u003e\n\u003csummary\u003e\u003cstrong\u003eAllow type-safe migrations for evolving datasets.\u003c/strong\u003e\u003c/summary\u003e\n\nAs apps evolve, the type of data they store evolves along with it. `CodableDatastore` provides no hassle migrations between older types and newer ones with typed versions to help you make sure you are covering all your bases. All you need to do is make sure to version older types and provide a translation between them and the type you expect.\n\nThis migration can even be done on save if desired, meaning the user doesn't need to wait to perform a migration so long as the types are supported and indexes don't need to be re-calculated.\n\nAdditionally, we aim to make sure that testing migrations against data snapshots is just as easy, allowing users to evolve their types with confidence.\n\n\u003c/details\u003e\n\n\u003cdetails open\u003e\n\u003csummary\u003e\u003cstrong\u003eAllow transactional reads and writes.\u003c/strong\u003e\u003c/summary\u003e\n\nSupporting atomic transactions is important when consistency between multiple data models is key to an app functioning correctly. A transaction being in progress means the objects updated by that transaction are locked for the duration of that transaction (other transactions will wait for this one to complete), and that all data is written to disk in a single final atomic write before returning that the transaction was complete. Importantly, this is done across data stores that share a common configuration, allowing the user to save independent types together. This also means that if a transaction fails, any updates it made will be reverted in the process.\n\n\u003c/details\u003e\n\n\u003cdetails open\u003e\n\u003csummary\u003e\u003cstrong\u003eHave all configuration be described in code.\u003c/strong\u003e\u003c/summary\u003e\n\nInstead of spreading the configuration across multiple different types or files, `CodableDatastore` aims to allow users of the library to have all configuration be defined in code, ideally in one place in an app.\n\n\u003c/details\u003e\n\n\u003cdetails open\u003e\n\u003csummary\u003e\u003cstrong\u003eEnable easy testing with out-of-the-box mocks.\u003c/strong\u003e\u003c/summary\u003e\n\nA configuration can describe either an on-disk persistence or an in-memory persistence, allowing app-based tests to be written against the in-memory version with little reconfiguration necessary. Additionally, since all access to the data store is made through a common actor, stubbing a new data store with compatible types should be easily attainable.\n\n\u003c/details\u003e\n\n### Future Goals\n\n\u003cdetails open\u003e\n\u003csummary\u003e\u003cstrong\u003eAllow indexing to be described using variadic generics.\u003c/strong\u003e\u003c/summary\u003e\n\nSwift 5.9 will introduce variadic generics allowing multiple indexes with different key paths to be described on the same data store. For now, we'll hard-code them as needed.\n\n\u003c/details\u003e\n\n\u003cdetails open\u003e\n\u003csummary\u003e\u003cstrong\u003eSnapshotting and backups.\u003c/strong\u003e\u003c/summary\u003e\n\nAlthough not planned for 1.0, this system should support light-weight snapshotting fairly easily by duplicating the file structure, making use of APFS snapshots to make sure data is not actually duplicated. Support for doing this via the API will be coming soon.\n\n\u003c/details\u003e\n\n\u003cdetails open\u003e\n\u003csummary\u003e\u003cstrong\u003eData integrity.\u003c/strong\u003e\u003c/summary\u003e\n\nAlthough `CodableDatastore` aims to maintain consistency for what is saved to the filesystem, it does nothing to maintain that the filesystem has not corrupted the data in the interim. This can be solved using additional Error-Correcting Codes saved along-side every file to correct bit errors should they ever occur.\n\n\u003c/details\u003e\n\n\u003cdetails open\u003e\n\u003csummary\u003e\u003cstrong\u003eEncryption.\u003c/strong\u003e\u003c/summary\u003e\n\nEncrypting the data store on disk could be supported in the future.\n\n\u003c/details\u003e\n\n### Non-goals\n\n\u003cdetails open\u003e\n\u003csummary\u003e\u003cstrong\u003eDescribe inter-dependencies between types.\u003c/strong\u003e\u003c/summary\u003e\n\nThis usually dramatically increases the complexity of index structures and allows users of the API to not understand the performance implications of creating inter-dependent relationships between disparate types.\n\nInstead, `CodableDatastore` aims to provide robust transactions between different data stores with the same configuration, allowing the user to build their own relationships by updating two or more data stores instead of these relationships being automatically built.\n\n\u003c/details\u003e\n\n\u003cdetails open\u003e\n\u003csummary\u003e\u003cstrong\u003eSafely allow multiple writing processes.\u003c/strong\u003e\u003c/summary\u003e\n\nAlthough multiple readers are supported, `CodableDatastore` intends a single process to write to disk persistence at once. This means that behavior is undefined if multiple writes happen from different processes. Ordinarily, this would be a problem in server-based deployments since server applications are traditionally run on multiple processes on a single machine, but most Swift-based server apps use a single process and multiple threads to achieve better performance, and would thus be compatible.\n\nIf you are designing a scalable system that runs multiple processes, consider setting up a single instance with the data store, or multiple instances with their own independent data stores to maintain these promises. Although not impossible, sharding and other strategies to keep multiple independent data stores in sync are left as an exercise to the user of this library.\n\n\u003c/details\u003e\n\n## Contributing\n\nContribution is welcome! Please take a look at the issues already available, or start a new discussion to propose a new feature. Although guarantees can't be made regarding feature requests, PRs that fit within the goals of the project and that have been discussed beforehand are more than welcome!\n\nPlease make sure that all submissions have clean commit histories, are well documented, and thoroughly tested. **Please rebase your PR** before submission rather than merge in `main`. Linear histories are required, so merge commits in PRs will not be accepted.\n\n## Support\n\nTo support this project, consider following [@dimitribouniol](https://mastodon.social/@dimitribouniol) on Mastodon, listening to Spencer and Dimitri on [Code Completion](https://mastodon.social/@codecompletion), or downloading Dimitri's wife Linh's app, [Not Phở](https://notpho.app/).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmochidev%2Fcodabledatastore","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmochidev%2Fcodabledatastore","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmochidev%2Fcodabledatastore/lists"}