a simple Yjs storage provider using localForage for persistence
Yjs provides a complete ecosystem for (persisting and) sharing "Conflict-free replicated data types" (CRDT) among multiple clients using a variety of persistence and communication providers. LocalForage is a simple storage library for JavaScript which wraps IndexedDB, WebSQL and other storage technologies in a common, localStorage
-like API.
This module implements a simple Yjs storage provider for browser-based applications which uses an arbitrary localForage
store for persistance - this means: if there is a localForage
"driver" for a storage of your choice, you can use this module to immediately instantiate a Yjs provider for it.
In addition to other database providers y-localforage
- contains an
isSynced
property which reflects the main document's own current synchronization status, - and an
isFullySynced
property which includes the synchronization state of any subdocs, - emits additional events (
sync-started
,sync-continued
,sync-finished
andsync-aborted
) which inform about synchronization progress for the main document, - and
subdoc-synced
which informs about a given subdoc being successfully synchronized, - sends a
load
event to the main doc and any subdoc as soon as thatY.Doc
has been fully loaded from persistence, - automatically persists any subdocs as well, and
- includes rudimentary error handling which breaks down the provider upon failure (which means that you have to re-incarnate the provider after the cause for this failure has been removed).
y-localforage
always tries to keep your data safe and not to overwrite or even delete previously written updates. Even a failure normally only means that the last update could not be written but all the previous ones are still safe.
Important: do not use the "copy" feature for
Y.Doc
s, i.e., do not create aY.Doc
instance with the same GUID as another one -Y.Doc
copies do not "synchronize" as described in the docs anyway.
NPM users: please consider the Github README for the latest description of this package (as updating the docs would otherwise always require a new NPM package version)
Just a small note: if you like this work and plan to use it, consider "starring" this repository (you will find the "Star" button on the top right of this page), so that I know which of my repositories to take most care of.
y-localforage
may be used as an ECMAScript module (ESM), a CommonJS or AMD module or from a global variable.
You may either install the package into your build environment using NPM with the command
npm install y-localforage localforage
or load the plain script files directly
<script src="https://unpkg.com/localforage"></script>
<script src="https://unpkg.com/y-localforage"></script>
How to access the package depends on the type of module you prefer
- ESM (or Svelte):
import { LocalForageProvider } from 'y-localforage'
- CommonJS:
const LocalForageProvider = require('y-localforage')
- AMD:
require(['y-localforage'], (LocalForageProvider) => {...})
Alternatively, you may access the global variable LocalForageProvider
directly.
Note for ECMAScript module users: all module functions and values are exported individually, thus allowing your bundler to perform some "tree-shaking" in order to include actually used functions or values (together with their dependencies) only.
For Svelte, it is recommended to import the package in a module context. From then on, its exports may be used as usual:
<script context="module">
import * as Y from 'yjs'
import { LocalForageProvider } from 'y-localforage'
</script>
<script>
localforage.config({
driver: [localforage.INDEXEDDB, localforage.WEBSQL]
})
localforage.ready(function () {
const DocStore = localforage.createInstance({
name:'Yjs-Persistence'
})
const sharedDoc = new Y.Doc()
const Persistence = new LocalForageProvider(DocStore, sharedDoc)
...
})
</script>
Let's assume that you already "required" or "imported" (or simply loaded) the module according to your local environment. In that case, you may use it as follows:
...
localforage.config({
driver: [localforage.INDEXEDDB, localforage.WEBSQL]
})
localforage.ready(function () {
const DocStore = localforage.createInstance({
name:'Yjs-Persistence'
})
const sharedDoc = new Y.Doc()
const Persistence = new LocalForageProvider(DocStore, sharedDoc)
...
})
The following documentation shows method signatures as used by TypeScript - if you prefer plain JavaScript, just ignore the type annotations.
LocalForageProvider (Store:any, sharedDoc:Y.Doc, UpdateLimit:number = 500)
creates a new instance ofLocalForageProvider
which synchronizes the givensharedDoc
on the given localForageStore
.UpdateLimit
indicates how many updates should be appended to theStore
before they will be compacted into a single one
isSynced
returnstrue
while the initially givenY.Doc
and this provider are in-sync - orfalse
otherwise. Please note, thatisSynced
does not inform about the synchronization status of any "subdocs"isFullySynced
returnstrue
while the initially givenY.Doc
and all its "subdocs" are in-sync - orfalse
otherwise
SubDocIsSynced (SubDoc:Y.Doc):boolean
returnstrue
while the givenSubDoc
(of this provider's sharedY.Doc
) and its provider are in-sync - orfalse
otherwise.SubDocIsSynced
also returnsfalse
ifSubDoc
is not a subdoc of this provider's sharedY.Doc
async destroy ():Promise<void>
stops any activities of this provider and deletes any persistence entries of this provider's sharedY.Doc
and its subdocs. Warning: this method completely destroys any written data and cannot be undone!
on('sync-started', Handler:(Provider:LocalForageProvider, Progress:number) => void)
thesync-started
event is fired whenever a synchronization between this provider and its associatedY.Doc
has begun.Provider
contains a reference to this provider andProgress
is always0.0
on('sync-continued', Handler:(Provider:LocalForageProvider, Progress:number) => void)
thesync-continued
event may be fired several times while a synchronization between this provider and its associatedY.Doc
is in progress if this synchronization can not be completed instantaneously.Provider
contains a reference to this provider andProgress
is a number between0.0
and1.0
indicating how much has already been synchronized. Please note: depending on how many new updates are generated (in contrast to how many have been synchronized during that time) the reportedProgress
may not always increase but may even decrease sometimeson('sync-finished', Handler:(Provider:LocalForageProvider, Progress:number) => void)
thesync-finished
event is fired whenever a synchronization between this provider and its associatedY.Doc
has finished.Provider
contains a reference to this provider andProgress
is always1.0
on('sync-aborted', Handler:(Provider:LocalForageProvider, Progress:number) => void)
thesync-aborted
event is fired when a synchronization between this provider and its associatedY.Doc
has been aborted (e.g., because the space on localStorage was exhausted or the provider was destroyed).Provider
contains a reference to this provider andProgress
is always1.0
. After such an event, theProvider
remains unusable and has to be created againon('synced', Handler:(Provider:LocalForageProvider) => void
thesynced
event works like in any other Yjs provider and is fired whenever (initially or after an update to the associatedY.Doc
) this provider gets in-sync againon('subdoc-synced', Handler:(Provider:LocalForageProvider, SubDoc:Y.Doc) => void
thesubdoc-synced
event is fired whenever any "subdoc" of this provider's mainY.Doc
has been successfully synchronized.Provider
contains a reference to this provider andSubDoc
a reference to the synchronized subdoc
You may easily build this package yourself.
Just install NPM according to the instructions for your platform and follow these steps:
- either clone this repository using git or download a ZIP archive with its contents to your disk and unpack it there
- open a shell and navigate to the root directory of this repository
- run
npm install
in order to install the complete build environment - execute
npm run build
to create a new build
You may also look into the author's build-configuration-study for a general description of his build environment.