Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature request: offline-first (or tutorial) #255

Open
bionicles opened this issue Jan 3, 2021 · 3 comments
Open

Feature request: offline-first (or tutorial) #255

bionicles opened this issue Jan 3, 2021 · 3 comments

Comments

@bionicles
Copy link

bionicles commented Jan 3, 2021

Dear Team Userbase,

Thank you for making this platform!

One thing I hacked, and got stuck in the weeds with, was offline-first. I did manage to hack it, but it's not exactly an ideal solution -- we store the app state in a single entry in userbase, then use ramda.mergeDeepRight to merge the one with the later timestamp into the one with the older timestamp. It feels like a hack, and this could lose data, because it's Last Write Wins instead of some other option. Also, I doubt it's highly performant or scalable to stuff the whole user data blob into a single key.

Would it be possible for userbase to add some offline-first function, or perhaps a tutorial to figure that out? It would be incredibly cool to have a sort of CRDT / ORDT so userbase apps would 'just work' offline, and sync automatically. I dabbled with automerge but it didn't work with ramda.

What do you think?

Thanks in advance for continued development of userbase!

Bion

@bionicles bionicles changed the title Feature request: offline-first functionality (or tutorial) Feature request: offline-first (or tutorial) Jan 3, 2021
@j-berman
Copy link
Collaborator

j-berman commented Jan 4, 2021

Native offline-first functionality is definitely something we're thinking about for the future! That being said, the underlying Userbase data structure is quite powerful, and what you're trying to do with Automerge should be possible to do in a decently performant way!

A deeper explanation of how Userbase works under the hood, and why I believe it can be relied on to work with operation-based CRDTs: when a client calls any of the database write operations (insertItem, updateItem, etc.), the client pushes the operation up to the server, the server then assigns a sequence number to the transaction based on the order it receives it, and then broadcasts the transaction out to all clients. Clients therefore receive transactions in a guaranteed, durable order, and never receive duplicate transactions. Which I believe is a requirement for operation-based CRDTs (edit: the no duplicates part). This process is described further here.

What the above all means is that you can rely on insertItem exclusively to insert snippets of Automerge changes into a database, in a way that all clients will receive the changes without any duplicates. So, you can do something like this:

Writing

let localCollection = Automerge.change(syncedCollection, doc => {
  // make arbitrary changes to the collection locally
})
let changes = Automerge.getChanges(syncedCollection, localCollection)

// now push changes up to Userbase
Promise.all(changes.map((change) => userbase.insertItem({ databaseName, item: change })))

Reading

let syncedCollection = Automerge.init()
const databaseName = 'conflict-resolving-db'
const changeHandler = (changes) => {
  // syncedCollection will automatically remain in sync with the server relying on Automerge's conflict resolution
  syncedCollection = Automerge.applyChanges(syncedCollection, changes.map(change => change.item))
}
await userbase.openDatabase({ databaseName, changeHandler })

Note that all the changes would need to fit in to the client's memory for this to work, but I believe if you're working with Automerge, you make that assumption anyway since each item holds all historical state.

All the building blocks are there!

@j-berman
Copy link
Collaborator

j-berman commented Feb 6, 2021

I made an offline-first Google Docs alternative to show how to do this! It's a bit complex - planning to provide a simple tutorial on how to create a super basic version of that^ here. In the meantime, here are the relevant areas of the code:

@bionicles
Copy link
Author

bionicles commented Feb 15, 2021

@j-berman epic! hey if you want somebody to proofread your tutorial i'm happy to help out - bion at bitpharma dot com

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants