npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

redux-yjs-bindings

v0.3.1

Published

Use Yjs to sync your Redux store with other peers!

Downloads

83

Readme

redux-yjs-bindings

npm package Downloads Issues

Use Yjs to sync your Redux store with other peers!

This very small (roughly 1kB) bridge to connect Redux with a Yjs, allows you to use the synchronization features of Yjs with the data management capabilities of Redux. It works with any Redux store, whether you use Redux Toolkit or not, and even supports initial values. Values that are transmitted over the network can be any JSON serializable value, from primitives to deeply nested object structures.

Install

npm i redux-yjs-bindings

Prerequisites

To use this library you will need a Yjs document as well as a Redux store. Detailed steps on how to set both up can be taken form their respective documentation. You will need to configure the reducer of the state slice that you want to have synced to accept an action dispatched by this library, however helper functions are exported to hide this away. Check below for example usage:

General Usage

import { bind, enhanceReducer } from 'redux-yjs-bindings';
import { Doc } from 'yjs';
import { createStore } from 'redux';
import { mySharedStateReducer } from './store/reducers';

// Create Yjs document, or use existing one.
const yDoc = new Doc();

const rootReducer = {
  // Set up reducer to handle action dispatched by `redux-yjs-bindings` when remote changes come in.
  mySharedState: enhanceReducer(mySharedStateReducer),
};
const store = createStore(rootReducer);

// Start synchronisation of Redux and Yjs
bind(yDoc, store, 'mySharedState');

Internally the bind function creates a new Y.Map() on the provided document. This map then contains a property with the provided sliceName, whose value should then always match the one of your store slice.

Limitations

Despite best efforts to make this library as compatible with existing projects as possible, there are some things to keep in mind.

Referential Equality

For now referential equality is not kept when changes from a remote peer are applied to the local Redux state. This may change in the future, however for now you can simply provide a custom equality function to the useSelector hook if performance becomes a problem.

Undefined in Array ([undefined]) Not Supported

As per Redux guidelines values in the store should be JSON serializable. While undefined values are mostly supported by this library, Yjs currently doesn't support undefined in arrays. However, undefined as a primitive or as object values works just fine, even though they are technically not a part of JSON.

Setter Action Must Be Synchronous

When remote changes come in through Yjs changes must be applied to the store immediately. Since reducers are already required to be synchronous by Redux and the action is dispatched by the library itself, this should not be a problem in practice. However, in theory it would be possible for a Redux middleware to delay all dispatched actions, which could potentially lead to data loss.

Intention Preservation on Array Changes

Internally redux-yjs-bindings subscribes to changes on the Redux store in order to patch the Yjs document. By calculating the difference between the previous and the next state, intentions behind changes can be retrieved even though Redux forces immutability. Unfortunately diffing arrays is not trivial and the currently used algorithm "recursive-diff" by @cosmicanant does not preserve intention for insertions or deletions that are not at the end of the array. States will still be synced correctly, but sometimes with more work than necessary. See this issue for more details.

Holey Arrays ([1, , 'a']) Not Supported

Well this obscure "feature" of JavaScript is simply not supported and will fail almost immediately.

API


bind(yDoc: Y.Doc, store: Store, sliceName: string): unbind()

yDoc

The yjs document changes should be synced with.

store

The redux store changes should be synced with.

sliceName

The name of the state slice (subtree) that should be synced.

unbind()

Returns a function which unbinds the state sync between the YJS doc and redux store


enhanceReducer(currentReducer: Reducer): Reducer

currentReducer

The reducer for the slice of the store that is supposed to be synced.


Constants

ROOT_MAP_NAME Property name of the map on the provided yDoc. Can be used to persist the yjs values on a server.

SET_STATE_FROM_YJS_ACTION Used by enhanceYjsReducer. Action type that is dispatched whenever changes to yjs from other peers come in.


Development

Setup

  • clone repo git clone https://github.com/lscheibel/redux-yjs-bindings
  • install dependencies and start microbundle in watch mode npm i && npm dev
  • open new terminal and navigate to example you want to test on (e.g., cd examples/todo-mvc)
  • follow instructions on how to start example from README.md but usually includes npm i && npm start plus starting the yjs-webrtc signaling server.

Inner Workings

Internally this library simply subscribes to changes on both the Redux store and the Yjs document, while making sure not to react to notifications triggered by itself. The subscribe method on the Redux store can be used to keep track of a previous and current state, from which a difference is calculated (currently using "recursive-diff" by @cosmicanant). The returned list of patch operations is then iterated over to apply all changes to the Yjs document. In the other direction, incoming changes from remote peers through Yjs are handled synchronously in order to prevent local Redux state updates while currently handling remote changes. When remote changes are detected, the Yjs state instance is simply turned into a JavaScript object, which replaces the entire store slice. This comes with the upside, that Yjs is always treated as the source of truth, however referential equality is lost, potentially impacting performance by effectively circumventing Redux selector's caching system, that would usually prevent unnecessary rerenders.

Acknowledgements and Prior Art

Special thanks to Joseph Miles whose zustand-middleware-yjs library was an inspiration. Also look out for Sana Labs upcoming "collaboration-kit".