• Stars
    star
    252
  • Rank 161,312 (Top 4 %)
  • Language
    Swift
  • License
    MIT License
  • Created over 2 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A simple store for all your basic needs, and a foundational data layer primitive for iOS and Mac apps. 🐱

Bodega Logo

An actor-based data layer, helping you build simple or complex stores for any iOS or Mac app. πŸͺ

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.


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. Today Bodega offers a form of infrastructure that any app's data layer can use. Whether you want to store Codable objects with ease, build caches, or interface with your API or services like CloudKit, it all works in just a few lines of code.

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 demo 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 types of storage primitives for you, StorageEngine and ObjectStorage. A StorageEngine writes Data to a persistence layer, while ObjectStorage works with Swift types that conform to Codable. A StorageEngine can save items to disk, SQLite, or even your own database, while ObjectStorage offers a unified layer over StorageEngines, providing a single API for saving 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 server or a service like CloudKit 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.


StorageEngines

// Initialize a SQLiteStorageEngine to save data to an SQLite database.
let storage = SQLiteStorageEngine(
    directory: .documents(appendingPath: "Quotes")
)

// Alternatively Bodega provides a DiskStorageEngine out of the box too.
// It has the same API but uses the file system to store objects. ΒΉ
let storage = DiskStorageEngine(
    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)

ΒΉ The tradeoffs of SQLiteStorageEngine vs. DiskStorageEngine are discussed in the StorageEngine documentation, but SQLiteStorageEngine is the suggested default because of it's far superior performance, using the same simple API.

Bodega provides two different instances of StorageEngine out of the box, but if you want to build your own all you have to do is conform to the StorageEngine protocol. This will allow you to create a StorageEngine for any data layer, whether you want to build a CoreDataStorageEngine, a RealmStorageEngine, a KeychainStorageEngine, or even a StorageEngine that maps to your API. If you can read, write, or delete data, you can conform to StorageEngine.

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?
}

ObjectStorage

Bodega's most common usage is in Boutique, but you can also use it as a standalone cache. Any StorageEngine can read or write Data from your persistence layer, but ObjectStorage provides the ability to work with Swift types, as long as they conform to Codable. ObjectStorage has a very similar API to DiskStorage, but with slightly different function names to be more explicit that you're working with objects and not Data.

// Initialize an ObjectStorage object
let storage = ObjectStorage(
    storage: SQLiteStorageEngine(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 as a primitive for interacting with and persisting data, but it's even more powerful when integrated into 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.

Feedback


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 Mastodon.

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.


Now that you're up to speed, let's take this offline πŸ“­

More Repositories

1

Boutique

✨ A magical persistence library (and so much more) for state-driven iOS and Mac apps ✨
Swift
774
star
2

TableFlip

A simpler way to do cool UITableView animations! (β•―Β°β–‘Β°οΌ‰β•―οΈ΅ ┻━┻
Swift
552
star
3

Public-Extension

πŸ”§ A weekly log of handy Swift extensions
Swift
298
star
4

Slope

A simpler way to implement gradients on iOS.
Swift
238
star
5

FeedbackEffect

A library for playing sounds and providing haptic feedback with ease.
Swift
225
star
6

GenericCells

Creating generic UITableViewCells and UICollectionViewCells instead of subclasses.
Swift
82
star
7

Anchorman

An autolayout library for the damn fine citizens of San Diego.
Swift
79
star
8

TypedNotifications

A mechanism for sending typed notifications with payloads across your iOS app.
Swift
76
star
9

MVCS

Swift
51
star
10

UILabel-ContentSize

Get the content size of a UILabel with text, because I always forget how to and want to have it in one god damn place.
Objective-C
44
star
11

CrossPromoter

A control which allows you to display an app to cross promote within your own app.
Objective-C
36
star
12

Communicado

A simpler way to share on iOS.
Swift
26
star
13

Shortcat

Navigate UITableViews using a keyboard with cat-like agility 🐱
Swift
12
star
14

Modem

A routing, serialization, and deep link handling framework all wrapped up in one
Swift
9
star
15

slack-themes

Slack Themes I've made and use
8
star
16

UIView-BezierCurve

Round individual corners of a UIView
Objective-C
7
star
17

UIViewController-StoreKit

A category on UIViewController allowing you to pull up an iTunes item with just one method
Objective-C
7
star
18

UIViewController-Sharing

A category on UIViewController for adding sharing options
Objective-C
6
star
19

UIControl-Notifications

Make your UIControls respond to notifications and blocks instead of the old fashioned target+selector approach
Objective-C
5
star
20

Launchpad

A better way to handle SwiftUI app launch
Swift
5
star
21

QuickNote

A scratch pad notification center widget for jailbroken iOS 5/6
Objective-C
4
star
22

iMessageFormatter

An Applescript to autoformat sentences with a capital letter and period at the end
AppleScript
4
star
23

UIDevice-Hardware

Category on UIDevice for accessing hardware information
Ruby
3
star
24

RequestBuilder

A library to help you build network requests atop URLSession
Swift
3
star
25

bot-of-conduct

JavaScript
3
star
26

JFTextFieldTableCell

A UITableViewCell subclass which supports inline editing, block handlers, and other niceties.
Objective-C
3
star
27

Podcatcher

A library for interacting with iOS Podcast apps
Objective-C
2
star
28

is-ci-busted

Is Ci Busted? Who even knows.
JavaScript
2
star
29

NSObject-Builder

Use the builder pattern with a category on NSObject
Ruby
2
star
30

QuickTweet

Add a quick way to send tweets from iOS's Notification Center.
2
star
31

UIImage-Transforms

Transforms on UIImage
Objective-C
2
star
32

JFFileManager

Class methods which make dealing with files on iOS easier.
Objective-C
2
star
33

NSString-Validation

A category on NSString for checking validity and transforming strings
Objective-C
2
star
34

SML

Random code I've done in SML for class
Standard ML
1
star
35

dropcaster-feed-seeder

Ruby
1
star
36

Google-Trends-Scraper

Get the list of google trends for every day from a certain point in time.
PHP
1
star
37

liftoff

Templates and Configuration for Liftoff
Swift
1
star
38

Project-Euler

My Objective-C implementation of the Project Euler solutions.
Objective-C
1
star
39

iosfolks.com

1
star
40

WebViewController

A simple web view controller, because everyone has their own version, but this one is simple with minimal chrome, and supports sharing out the box.
Objective-C
1
star
41

Response

An interface for creating a JSON response simply from a string
Go
1
star
42

NSDictionary-Networking

A category on NSDictionary for adding functionality when you're interacting with networking
Objective-C
1
star
43

Rerelease

A What's New screen, and more
Swift
1
star