Swiftpack.co - mergesort/Bodega as Swift Package

Swiftpack.co is a collection of thousands of indexed Swift packages. Search packages.
See all packages published by mergesort.
mergesort/Bodega 2.0.0-rc-1
A simple store for all your basic needs
⭐️ 143
🕓 2 weeks ago
iOS macOS
.package(url: "https://github.com/mergesort/Bodega.git", from: "2.0.0-rc-1")

Bodega Logo

A simple store for all your basic needs. 🐱

If you find Bodega valuable I would really appreciate it if you would consider helping sponsor my open source work, so I can continue to work on projects like Bodega to help developers like yourself.


If you're getting started with Bodega today, it is highly recommended that you use the 2.0 release candidate. The update is feature complete with only minor or likely no changes expected. It will be officially released when I finish adding documentation to provide tutorials, best practices, and updated sample projects. While v2 is a huge upgrade, the API changes from 1.0 are minimal enough that it shouldn't be difficult to get started without updated docs in the mean time.

As a born and raised New Yorker I can attest that bodegas act as humble infrastructure for our city, and Bodega aims to do that as well. We appreciate what bodegas do for us, yet it's their simplicity and prevalence that almost makes us forget they're here.

Bodega is an actor-based library that started as a simple cache based on reading and writing files to/from disk with an incredibly simple API. The DiskStorageEngine still provides that functionality, but Bodega has also blossomed into so much more, offering a form of infrastructure that any app's data can use.

Bodega's StorageEngine is at the heart of what's possible. Conforming any database, persistence layer, or even an API server, to the StorageEngine protocol automatically provides an incredibly simple data layer for your app thanks to Bodega's ObjectStorage. Rather than Data and databases developers interact with their app's Swift types no matter what those may be, have a unified API, and concurrency handled out of the box.

Bodega is fully usable and useful on its own, but it's also the foundation of Boutique. You can find a reference implementation of an app built atop Boutique in the Boutique Demo folder, showing you how to make an offline-ready realtime updating SwiftUI app in only a few lines of code. You can read more about the thinking behind the architecture in this blog post exploring Boutique and the Model View Controller Store architecture.



Getting Started

Bodega provides two kinds of storage primitives for you, StorageEngine and ObjectStorage. A StorageEngine is for writing Data to a persistence layer, whether it's files on disk, SQLite, or your own database. An ObjectStorage offers a unified layer over StorageEngines, providing a single API for saving Codable objects to any StorageEngine you choose. Bodega offers DiskStorageEngine and SQLiteStorageEngine by default, or you can even build a StorageEngine based on your app's API server if you want a simple way to interface with your API. You can even compose storage engines to create a complex data pipeline that hits your API and saves items into a database, all in one API call. The possibilities are endless.


DiskStorage

// Initialize a DiskStorage object
let storage = DiskStorage(
    directory: .documents(appendingPath: "Quotes")
)

// CacheKeys can be generated from a String or URL.
// URLs will be reformatted into a file system safe format before writing to disk.
let url = URL(string: "https://redpanda.club/dope-quotes/dolly-parton")
let cacheKey = CacheKey(url: url)
let data = Data("Find out who you are. And do it on purpose. - Dolly Parton".utf8)

// Write data to disk
try await storage.write(data, key: cacheKey)

// Read data from disk
let readData = await storage.read(key: cacheKey)

// Remove data from disk
try await storage.remove(key: Self.testCacheKey)

ObjectStorage

ObjectStorage has a very similar API to DiskStorage, but with slight naming deviations to be more explicit that you're working with objects and not data.

// Initialize an ObjectStorage object
let storage = ObjectStorage(
    directory: .documents(appendingPath: "Quotes")
)

let cacheKey = CacheKey("churchill-optimisim")

let quote = Quote(
    id: "winston-churchill-1",
    text: "I am an optimist. It does not seem too much use being anything else.",
    author: "Winston Churchill",
    url: URL(string: "https://redpanda.club/dope-quotes/winston-churchill")
)

// Store an object
try await storage.store(quote, forKey: cacheKey)

// Read an object
let readObject: Quote? = await storage.object(forKey: cacheKey)

// Grab all the keys, which at this point will be one key, `cacheKey`.
let allKeys = await storage.allKeys()

// Verify by calling `keyCount`, both key-related methods are also available on `DiskStorage`.
await storage.keyCount()

// Remove an object
try await storage.removeObject(forKey: cacheKey)

Further Exploration

Bodega is very useful on its own for saving files and objects to disk, but it's made even more powerful by using Boutique. Boutique is a Store and serves as the foundation of a Model View Controller Store architecture I've developed. MVCS brings together the familiarity and simplicity of the MVC architecture you know and love with the power of a Store, to give your app a simple but well-defined state management and data architecture.

If you'd like to learn more about how it works you can read about the philosophy in a blog post where I explore MVCS for SwiftUI, and you can find a reference implementation of an offline-ready realtime updating MVCS app powered by Boutique in this repo.


Feedback

This project provides multiple forms of delivering feedback to maintainers.

  • If you have a question about Bodega, we ask that you first consult the documentation to see if your question has been answered there.

  • If you still have a question, enhancement, or a way to improve Bodega, this project leverages GitHub's Discussions feature.

  • If you find a bug and wish to report an issue would be appreciated.


Requirements

  • iOS 13.0+
  • macOS 11.0
  • Xcode 13.2+

Installation

Swift Package Manager

The Swift Package Manager is a tool for automating the distribution of Swift code and is integrated into the Swift build system.

Once you have your Swift package set up, adding Bodega as a dependency is as easy as adding it to the dependencies value of your Package.swift.

dependencies: [
    .package(url: "https://github.com/mergesort/Bodega.git", .upToNextMajor(from: "1.0.0"))
]

Manually

If you prefer not to use SPM, you can integrate Bodega into your project manually by copying the files in.


About me

Hi, I'm Joe everywhere on the web, but especially on Twitter.

License

See the license for more information about how you can use Bodega.

Sponsorship

Bodega is a labor of love to help developers build better apps, making it easier for you to unlock your creativity and make something amazing for your yourself and your users. If you find Bodega valuable I would really appreciate it if you'd consider helping sponsor my open source work, so I can continue to work on projects like Bodega to help developers like yourself.

GitHub

link
Stars: 143
Last commit: 3 days ago
jonrohan Something's broken? Yell at me @ptrpavlik. Praise and feedback (and money) is also welcome.

Release Notes

Version 2.0 RC 1: Bring Your Own Database
2 weeks ago

Welcome to the future! Bodega v2 allows you to extend Bodega far and wide by giving you the power to bring your own database to Bodega in one easy step. Out of the box Bodega v2 is 5-10x faster because the default database is now powered by the blazing fast SQLiteStorageEngine. But if SQLite or the DiskStorageEngine aren't your jam, you can build your own StorageEngine by conforming to a simple protocol. Using Bodega is still as simple as it ever was, taking very little code, and no special objects, with simple defaults.

As a bonus if you do that it will automatically with any Boutique-powered app, providing you the same single source of truth, realtime updates, and a fully offline-capable app you've come to know and love, in only a couple of lines of code.


Warning This version contains breaking changes


In the Version 1.x series of Bodega the DiskStorage type was responsible for persisting data to disk. ObjectStorage was a Codable layer built on top of DiskStorage, letting you work with Swift types rather than Data. Like the name implies DiskStorage was backed by the file system, but what if you don't want to save Data to disk? Saving data to disk is a simple and effective starting point, but can get slow when working with large data sets. One of Bodega's goals is to work with every app without causing developers to make tradeoffs, so version 2.0 is focused on eliminating those tradeoffs without ruining the streamlined simplicity Bodega brings.

In the spirit of not making tradeoffs here's how Bodega works with any database you want, say hello to the new StorageEngine protocol.

public protocol StorageEngine: Actor {
    func write(_ data: Data, key: CacheKey) async throws
    func write(_ dataAndKeys: [(key: CacheKey, data: Data)]) async throws

    func read(key: CacheKey) async -> Data?
    func read(keys: [CacheKey]) async -> [Data]
    func readDataAndKeys(keys: [CacheKey]) async -> [(key: CacheKey, data: Data)]
    func readAllData() async -> [Data]
    func readAllDataAndKeys() async -> [(key: CacheKey, data: Data)]

    func remove(key: CacheKey) async throws
    func remove(keys: [CacheKey]) async throws
    func removeAllData() async throws

    func keyExists(_ key: CacheKey) async -> Bool
    func keyCount() async -> Int
    func allKeys() async -> [CacheKey]

    func createdAt(key: CacheKey) async -> Date?
    func updatedAt(key: CacheKey) async -> Date?
}

By providing your own write, read, remove, key, and timestamp related functions, you can make any persistence layer compatible with ObjectStorage. Whether your app is backed by Realm, Core Data, or even CloudKit, when you create a new StorageEngine it automatically becomes usable by ObjectStorage, with one drop dead simple API.

The first StorageEngine to be implemented is an SQLiteStorageEngine, bundled with Bodega. I'll explain all the possibilities below, but first let's take a second to see how much faster your apps using Bodega and Boutique will be.

StorageEngine Read Performance StorageEngine Write Performance


If it's not obvious, a SQLite foundation for Bodega is incredibly faster than using the file system. The DiskStorageStorageEngine is still available, but if you use the SQLiteStorageEngine loading 10,000 objects into memory will be more than 400% faster, and writing 5,000 objects is more than 500% faster. With this release I feel confident that you should be able to use Bodega and Boutique in the largest of apps, while counterintuitively becoming a more flexible framework.

Breaking

There's only one change to your code, ObjectStorage now has a type constraint. This isn't required for ObjectStorage but was an oversight in the 1.x versions, and a big version bump felt like the right time to fix this mistake.

Before you would initialize a new ObjectStorage like this.

let storage = ObjectStorage(
    storage: SQLiteStorageEngine(directory: .documents(appendingPath: "Animals"))!
)

And now it looks like this, one small tweak!

let storage = ObjectStorage<Animal>(
    storage: SQLiteStorageEngine(directory: .documents(appendingPath: "Animals"))!
)

And of course the same thing is true if you use DiskStorage or any StorageEngine you may build.


P.S. If you build something useful to others, by all means file a pull request so I can add it to Bodega!

Swiftpack is being maintained by Petr Pavlik | @ptrpavlik | @swiftpackco | API | Analytics