Try   HackMD

Vue Sync documentation

What is Vue Sync

Vue Sync is a library that brings a unified syntax for reading and writing data and allows you to easily work with this data in your app like a "global" store.

You can power Vue Sync with "store plugins" that work in the background to keep this data automatically in sync with whatever service/database or local store you use.

Benefits of Vue Sync

  • an easy to use global store
  • unified syntax for reading and writing data
  • integration of cloud services and databases (like Firebase, MongoDB, )
  • integration of local state libraries (like Vuex, Redux, )
  • your data automatically stays in sync with external services
  • you do not need to learn each service's SDK
  • API calls are optimised and handled for you (to be as economic as possible)
  • optimistic UI built in by default (can be opt out of)
  • dynamic modules for maximum flexibility in your data structure
  • small footprint by using only the store plugins you need
  • (TS users) type safety and helpers when reading/writing data

It's like graphQL but much simpler with CRUD-like functions

Store plugins

In most cases you use Vue Sync with two store plugins installed:

  • A "local" store plugin, for the data you need stored in memory while using the app. (like Vuex or a simple global object)
  • A "remote" store plugin, for the remote data stored. (like Firestore or any other database)

When reading data: the remote store will fetch the data; the local store will then add that data for you, so you can easily access and use it in your app.

When writing data: both the local and remote stores will update the data for you. You can choose if you want to show updates immidiately and sync remote store in the background (optimistic UI) or wait for the remote store to complete before its reflected locally.

Modules

Will be organised and accessible through modules. A module could be a single object (a "document") or an map of objects (a "collection").

Modules are dynamically created and destroyed when you read/write data! JavaScript will automatically garbage-collect modules that are not referenced anymore.

Collection vs document modules

A "module" is something that points to a certain chunk of data in your database.

A module instance has these four things: (1) functions to read data (2) functions to write data (3) the data itself (4) other meta data.

Every module can be one of two types:

  • document: has a single object as data
  • collection: has an map of objects as data

Collections can hold several documents, and documents can have sub-collections with more documents. Like so:

collection/document/collection/document

As an example: pokedex/001/moves/leafAttack

  • pokedex & moves are "collections"
  • 001 & leafAttack are "documents"
Further reading

Collections and documents was inspired by Google Firebase's database called Firestore.

If you are still new to this concept you should also read their documentation on the "data model".

Access a module and its data

You can access modules with a simple collection(id) & doc(id) syntax.

// access the collection:
vueSync.collection('pokedex')

// access a sub-doc via its collection:
vueSync.collection('pokedex').doc('001')

// access a sub-doc via its path:
vueSync.doc('pokedex/001')

Access the module data

With the collection or document instance, you can then access the data, id and several functions on of this "module". Eg.

// a collection module
const pokedex = vueSync.collection('pokedex')

pokedex.id // === 'pokedex'
pokedex.data // === Map<'001': { name: 'bulbasaur' }, ... >

// a document module
const bulbasaur = vueSync.doc('pokedex/001')

bulbasaur.id // === '001'
bulbasaur.data // === { name: 'bulbasaur' }
The data is also accessible directly like so: (TBD)
const pokedex = vueSync.collection('pokedex')

pokedex['001'] // === { name: 'bulbasaur' }
pokedex['001'].name // === 'bulbasaur'


To be decided

As an added layer of protection, any data accesses is readonly! To "mutate" the data, you'll have to use the functions that are provided (which we explain down below).

This is good because any mutation is supposed to go through a function so it can be synced to all your stores.

Add & edit data

In the following examples we use this collection instance:

export const pokedex = vueSync.collection('pokedex')

You can instanciate modules and then export them for to use in other files. (more on this here) ← add link

Insert a new document

Insert with random ID

When you insert a new document without specifying an ID, you can do so by calling insert directly on the collection:

const data = { name: 'squirtle' }

// insert a new document with random ID
const newDoc = await pokedex.insert(data)

insert returns a newly created instance of a document-module. You can then retrieve both the generated id and the data from this.

Insert with pre-set ID

If you want to provide an ID yourself, you can do so by calling insert on the doc instance of that preset id:

await pokedex.doc(presetId).insert(data)

In the case of using a presetId, it will return Promise<void>.

How can I batch insert documents?

You can make a simple loop. Vue Sync will automatically optimise so that only a single "batch" API call is made!

const newPokemon = [
  { name: 'Flareon' },
  { name: 'Vaporeon' },
  { name: 'Jolteon' },
]
for (const data of newPokemon) {
  pokedex.insert(data)
}

Delete a document

await pokedex.doc('001').delete()
How can I batch delete documents?

You can make a simple loop. Vue Sync will automatically optimise so that only a single "batch" API call is made!

const idsToDelete = ['001', '004', '007']
for (const id of idsToDelete) {
  vueSync.doc(id).delete()
}

Delete a document prop

You can delete a single prop or an array of props of a document:

await pokedex.doc('001').deleteProp('name')
// or
await pokedex.doc('001').deleteProp(['name', ...])

You can also delete nested props by using the "dot" notation:

await pokedex.doc('001').deleteProp('moves.tackle')
// will remove 'tackle' from an object called 'moves'

Modify a document

There are 3 ways to modify documents:

  • merge
  • assign
  • replace

Merge

Merge will update your document and deep-merge any nested properties.

const bulbasaur = pokedex.doc('001')

// bulbasaur.data === { name: 'bulbasaur', types: { grass: true } }

await bulbasaur.merge({ types: { poison: true } })

// bulbasaur.data === { name: 'bulbasaur', types: { grass: true, poison: true } }

In the example above, Bulbasaur kept grass: true when { poison: true } was merged onto it.

Assign

Assign will update your document and shallow-merge any nested properties. That means that nested objects are replaced by what ever you pass.

const bulbasaur = pokedex.doc('001')

// bulbasaur.data === { name: 'bulbasaur', types: { grass: true } }

await bulbasaur.merge({ types: { poison: true } })

// bulbasaur.data === { name: 'bulbasaur', types: { poison: true } }

In the example above, Bulbasaur lost grass: true when { poison: true } was assigned onto it.

Replace

Replace will replace the entire object of your document with whatever you pass.

const bulbasaur = pokedex.doc('001')

// bulbasaur.data === { name: 'bulbasaur', types: { grass: true } }

await bulbasaur.replace({ level: 16 })

// bulbasaur.data === { level: 16 }

In the example above, Bulbasaur lost all of its data and it was replaced with whatever was passed.

Read Data

There are two ways to retrieve data from your remote stores. Either of these methods can be used with documents and collections.

  • Execute a function to get data once
  • Set up a "stream" to receive realtime updates

Get data once

When you get data by executing get(), the data will be fetched from a server by your "remote" store plugin and then added to your module's data by your "local" store plugin.

Get a single document

When you create a module instance of a document, but the data is still on the server, you can retrieve it with get() like so:

const bulbasaur = vueSync.doc('pokedex/001')

// bulbasaur.data === {} // empty!

await pokedex.get()

// now it is available locally:

const bulbasaurData = bulbasaur.data
// bulbasaurData === { name: 'Bulbasaur' }

Get multiple documents in a collection

const pokedex = vueSync.collection('pokedex')

// pokedex.data === Map<> // empty!
// pokedex.data.values() === [] // empty!

await pokedex.get()

// now they are available locally:
// pokedex.data === Map<'001': { name: 'Bulbasaur' }, ...>

const allPokemon = pokedex.data.values()
// allPokemon === [{ name: 'Bulbasaur' }, ...]

Stream realtime updates

When you set up a "stream" for a document or collection, just like get(), your the data will be fetched from a server by your "remote" store plugin and then added to your module's data by your "local" store plugin.

Afterwards, if there are any changes to this document, they will automatically be updated in your "local" data while the stream is open.

Stream a collection

const pokedex = vueSync.collection('pokedex')
// open the stream
pokedex.stream()

// data that comes in from the remote store will be added to your data and stays in sync

// now they are available locally:
// pokedex.data === Map<'001': { name: 'Bulbasaur' }, ...>

const allPokemon = pokedex.data.values()
// allPokemon === [{ name: 'Bulbasaur' }, ...]

Stream a single document

const bulbasaur = pokedex.doc('001')
// open the stream
bulbasaur.stream()

// data that comes in from the remote store will be added to your data and stays in sync

const data = bulbasaur.data
// data === { name: 'Bulbasaur' }

:::hint
The promise returned from a stream is only resolved if the stream channel was closed. Either by the dev or because of an error (in which case it will reject). As long as the stream is open, the promise returned will not resolve.
:::

Closing a stream

You can list all open streams like so:

vueSync.collection('pokedex').openStreams.entries()

// or for a doc:
vueSync.doc('pokedex/001').openStreams.entries()

openStreams is a [Map](link to mdn) with the payload you passed to start the stream as key, and the "close" function as value.

Here is a full example of opening and closing a stream:

// open the stream:
const streamOptions = {}
vueSync.collection('pokedex').stream(streamOptions)


// close the stream:
const streamCloseId = streamOptions
const closeFn = vueSync.collection('pokedex').openStreams.get(streamCloseId)
closeFn()

Note if you didn't pass any options when you opened the stream, like so: stream() then it's "id" in the openStreams map will be undefined.

Using clauses when reading (filters, limits, order)

You might only want to get or stream some documents, filtered by a field; limited to X docs; or in a certain order.

MORE DOCUMENTATION COMING ON THIS SOON

You can pass an object with options to get() and stream() like so:

const clauses = { /* some options */ }

await vueSync.collection('pokedex').get(clauses)

More information on what kind of options you can pass can be found in the documentation of your store plugin.