Swiftpack.co -  reddavis/Papyrus as Swift Package
Swiftpack.co is a collection of thousands of indexed Swift packages. Search packages.
reddavis/Papyrus
Papyrus aims to hit the sweet spot between saving raw API responses to the file system and a fully fledged database like Realm.
.package(url: "https://github.com/reddavis/Papyrus.git", from: "v1.1")

Papyrus

Papyrus aims to hit the sweet spot between saving raw API responses to the file system and a fully fledged database like Realm.

struct Car: Papyrus
{
    let id: String
    let model: String
    let manufacturer: String
}

let car = Car(id: "abc...", model: "Model S", manufacturer: "Tesla")
let store = PapyrusStore()
store.save(car)

Requirements

  • iOS 14.0+
  • macOS 11.0+

Installation

Swift Package Manager

dependencies: [
    .package(url: "https://github.com/reddavis/Papryrus", from: "0.9.0")
]

Note

Worth noting Papyrus is still in very early days and API's are expected to change dramatically. Saying that, SEMVER will be kept.

Apps using Papyrus

Documentation

API Reference

Usage

All write functions are synchronous and have asynchonous counterparts. Variety is the spice of life after all.

Saving

Anything that conforms to the Papyrus protocol can be stored.

The Papyrus protocol is simply an umbrella of these three protocols:

  • Codable
  • Equatable
  • Identifiable where ID: LosslessStringConvertible

Example A - Basic Saving

struct Car: Papyrus
{
    let id: String
    let model: String
    let manufacturer: String
}

let car = Car(id: "abc...", model: "Model S", manufacturer: "Tesla")
let store = PapyrusStore()
store.save(car)

Example B - Eventual Saving

struct Car: Papyrus
{
    let id: String
    let model: String
    let manufacturer: String
}

let car = Car(id: "abc...", model: "Model S", manufacturer: "Tesla")
let store = PapyrusStore()
store.saveEventually(car)

Example C - Relationships

Papyrus also understands relationships. If we continue with our Car modelling...Let's imagine we have an app that fetches a list of car manufacturers and their cars.

Our models could look like:

struct Manufacturer: Papyrus
{
    let id: String
    let name: String
    @HasMany let cars: [Car]
    @HasOne let address: Address
}

struct Car: Papyrus
{
    let id: String
    let model: String
}

struct Address: Papyrus
{
    let id: UUID
    let lineOne: String
    let lineTwo: String?
}

let modelS = Car(id: "abc...", model: "Model S")
let address = Address(id: UUID(), lineOne: "blah blah", lineTwo: nil)
let tesla = Manufacturer(
    id: "abc...", 
    name: "Tesla", 
    cars: [modelS], 
    address: address
)

let store = PapyrusStore()
store.save(tesla)

Because Car and Address also conforms to Papyrus and the @HasMany and @HasOne property wrappers have been used, PapyrusStore will also persist the cars and the address when it saves the manufacturer.

Example D - Merge

A common use case when dealing with API's is to fetch a collection of objects and the merge the results into your local collection.

Merge also has a async counterpart mergeEventually(objects:).

Papyrus provides a function for this:

let carA = Car(id: "abc...", model: "Model S", manufacturer: "Tesla")
let carB = Car(id: "def...", model: "Model 3", manufacturer: "Tesla")
let carC = Car(id: "ghi...", model: "Model X", manufacturer: "Tesla")

let store = PapyrusStore()
store.save(objects: [carA, carB])

store.merge(with: [carA, carC])
store
    .objects(type: Car.self)
    .execute()
// #=> [carA, carC]

Fetching by ID

Fetching objects has two forms:

  • Fetch by id.
  • Fetch collection.

Example A

let store = PapyrusStore()
let tesla = try store.object(id: "abc...", of: Manufacturer.self).execute()

Example B

You also have the option of a Publisher that will fire an event on first fetch and then when the object changes or is deleted.

When the object doesn't exist a PapyrusStore.QueryError error is sent.

let store = PapyrusStore()
let cancellable = store.object(id: "abc...", of: Manufacturer.self)
    .publisher()
    .sink(
        receiveCompletion: { ... },
        receiveValue: { ... }
    )

Fetching collections

Papryrus gives you the ability to fetch, filter and observe colletions of objects.

Example A - Simple fetch

let manufacturers = self.store
                        .objects(type: Manufacturer.self)
                        .execute()

Example B - Filtering

let manufacturers = self.store
                        .objects(type: Manufacturer.self)
                        .filter { $0.name == "Tesla" }
                        .execute()

Example C - Sorting

let manufacturers = self.store
                        .objects(type: Manufacturer.self)
                        .sort { $0.name < $1.name }
                        .execute()

Calling publisher() on a PapryrusStore.CollectionQuery object will return a Combine publisher which will emit the collection of objects. Unless specified the publisher will continue to emit a collection objects whenever a change is detected.

A change constitutes of:

  • Addition of an object.
  • Deletion of an object.
  • Update of an object.

Example D - Observing changes

self.store
    .objects(type: Manufacturer.self)
    .publisher()
    .subscribe(on: DispatchQueue.global())
    .receive(on: DispatchQueue.main)
    .sink { self.updateUI(with: $0) }
    .store(in: &self.cancellables)

Example E - All together

self.store
    .objects(type: Manufacturer.self)
    .filter { $0.name == "Tesla" }
    .sort { $0.name < $1.name }
    .publisher()
    .subscribe(on: DispatchQueue.global())
    .receive(on: DispatchQueue.main)
    .sink { self.updateUI(with: $0) }
    .store(in: &self.cancellables)

Deleting

There are several methods for deleting objects.

Example A

let store = PapyrusStore()
let tesla = store.object(id: "abc...", of: Manufacturer.self)
store.delete(tesla)

Example B

let store = PapyrusStore()
let tesla = store.object(id: "abc...", of: Manufacturer.self)
store.deleteEventually(tesla)

Example C

let store = PapyrusStore()
store.delete(id: "abc...", of: Manufacturer.self)

Example D

let store = PapyrusStore()
let tesla = store.object(id: "abc...", of: Manufacturer.self)
let ford = store.object(id: "xyz...", of: Manufacturer.self)
store.delete(objects: [tesla, ford])

Migrations (experimental)

If the wish is to keep existing data when introducing schema changes you can register a migration.

Example A

struct Car: Papyrus
{
    let id: String
    let model: String
    let manufacturer: String
}

struct CarV2: Papyrus
{
    let id: String
    let model: String
    let manufacturer: String
    let year: Int
}

let migration = Migration<Car, CarV2> { oldObject in
    CarV2(
        id: oldObject.id,
        model: oldObject.model,
        manufacturer: oldObject.manufacturer,
        year: 0
    )
}
self.store.register(migration: migration)

License

Whatevs.

GitHub

link
Stars: 8
Last commit: 4 days ago

Ad: Job Offers

iOS Software Engineer @ Perry Street Software
Perry Street Software is Jack’d and SCRUFF. We are two of the world’s largest gay, bi, trans and queer social dating apps on iOS and Android. Our brands reach more than 20 million members worldwide so members can connect, meet and express themselves on a platform that prioritizes privacy and security. We invest heavily into SwiftUI and using Swift Packages to modularize the codebase.

Release Notes

v1.1
4 weeks ago

Added

  • Experimental migrations

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