Giter Club home page Giter Club logo

syncedstore's Introduction

SyncedStore CRDT

npm version Coverage Status

Discord

(This library was previously called "Reactive-CRDT")

SyncedStore is an easy-to-use library for building collaborative applications that sync automatically. It's built on top of Yjs, a proven, high performance CRDT implementation.

TL;DR

Create apps like this:

SyncedStore CRDT screencapture

Play with this example

Using an API as simple as this:

// add a todo
store.todos.push({ completed: false, title: "Get groceries" });

// set todo to completed
store.todos[0].completed = true;

Documentation

You can find the SyncedStore documentation on the website.

Examples

We have several examples on the website (React, Vue, Svelte) as part of the documentation.

In this repository, there are also more complex examples based on TodoMVC (examples/todo-react, examples/todo-vue, examples/todo-svelte).

example app screencapture

  • Open live demo: React or Vue (Of course, open multiple times to test multiplayer)
  • Edit / view on Codesandbox React / Vue

Motivation

SyncedStore makes it easy to develop applications that:

  • 👨‍👩‍👧‍👦 Are collaborative: create multi-user and multi-device experiences without the need to handle complex conflict resolution management yourself.
  • 🚀 Are fast: operations are handled locally, and data synchronization with other users and devices happens quietly in the background. 0 Latency!
  • 🔗 Work offline: cloud apps typically don’t work while offline. Supporting both data sync and offline used to be difficult, SyncedStore aims to simplify this.

Perhaps most importantly, it makes it easy to build decentralized applications. This has a lot of security & privacy benefits compared to always relying on central (expensive) servers to keep track of all our data.

Read more about the benefits of Local-first software in this essay

In short, with some technological magic of so-called CRDTs (Conflict-free Replicated Data Types), we can build cross-device apps that are more collaborative, faster, work offline AND put the user back in control of their data.

Feedback

I'd always love to hear how you're using SyncedStore. Definitely open an issue if you need help, get in touch via Twitter, or join the discussion in the Yjs forums.

Credits ❤️

SyncedStore builds directly on Yjs and Reactive. It's also inspired by and builds upon the amazing work by MobX and NX Observe.

SyncedStore is built as part of TypeCell. TypeCell is proudly sponsored by the renowned NLNet foundation who are on a mission to support an open internet, and protect the privacy and security of internet users. Check them out!

NLNet

syncedstore's People

Contributors

aubergene avatar bogobogo avatar getflourish avatar ismail-codar avatar jasonm avatar oysteinsigholt avatar rhaenni avatar skaterdad avatar websiddu avatar yousefed avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

syncedstore's Issues

Filter/query data store

Hi there,

Firstly, this library is great.

Apologies if this is an obvious question- I’d like to filter the data before it’s sent to the store

For example, if I only wanted “completed” todos.

FWIW: I tired using “y-socket” to decode and then return filter but had no success. My thinking was to decode the entire store, then only send back specific data (or even indexes if a different data structure where data is in different “rooms” ie ws://…/key)

How I can filter the data before it arrives to the client? Can you offer any suggestions?

thanks

The console is spammed with "property not found on root doc __v_isRef"

The warning doc.ts:46 property not found on root doc __v_isRef and similar warnings about other internal Vue properties are spammed on the console. In certain situations, it also prints properties that I'm working with myself as a warning. It would be good if this library could filter out these warnings for internal Vue properties as there doesn't seem to be a way around it. The Vue3 examples in the documentation also have this issue with the warnings.

References kept to objects within the synced store become empty on their own

An error occurs in the following reproduction sandbox (adapted from the one in the documentation for syncedstore and vue). There are two input fields, the first one uses the reactive object contained within the store for getting the model property. The second one uses the full reference path to the store. The first input field errors when it's touched by the user (the value changed) since the object becomes empty because SyncedStore modifies it in a weird way.

https://codesandbox.io/s/sandpack-project-forked-p3fkw6?file=/src/App.vue

<template>
  <main id="app">
    baz.title.foo.content
    <input
      autocomplete="off"
      v-model="baz.title.foo.content"
    />
    store.documents.baz.title.foo.content
    <input
      autocomplete="off"
      v-model="store.documents.baz.title.foo.content"
    />
  </main>
</template>

<script>
import { store } from "./store";
import { ref } from "vue";
import * as Vue from "vue";
import { enableVueBindings } from "@syncedstore/core";

// make SyncedStore use Vuejs internally
enableVueBindings(Vue);

export default {
  name: "App",
  setup() {
    store.documents.baz = {
      completed: false,
      title: {
        foo: {
          content: "foo",
          user: null
        }
      }
    };
    const baz = store.documents.baz;
    return {
      store, // Put the store on the data() of the component
      baz
    };
  }
};
</script>

...
import { syncedStore, getYjsValue } from "@syncedstore/core";
import { WebrtcProvider } from "y-webrtc";

type Todo = {
  completed: boolean
  title: {
    foo: {
      content: string,
      user: null
    }
  }
}

export const store = syncedStore({
  documents: {} as Record<string, Todo>
})

...

Error with null/undefined values?

Hello!

I'm running into an error with null or undefined values inside a SyncedStore using @syncedstore/[email protected]:

  TypeError: Cannot read properties of undefined (reading 'Symbol(INTERNAL_SYMBOL)')
      at getYjsValue (/...myapp.../node_modules/@syncedstore/core/src/index.ts:65:21)
      at crdtValue (/...myapp.../node_modules/@syncedstore/core/src/internal.ts:48:12)
      at Object.set (/...myapp.../node_modules/@syncedstore/core/src/object.ts:31:23)
      at crdtObject (/...myapp.../node_modules/@syncedstore/core/src/object.ts:99:5)
      at crdtValue (/...myapp.../node_modules/@syncedstore/core/src/internal.ts:70:14)
      at Object.set (/...myapp.../node_modules/@syncedstore/core/src/object.ts:31:23)
      at crdtObject (/...myapp.../node_modules/@syncedstore/core/src/object.ts:99:5)
      at crdtValue (/...myapp.../node_modules/@syncedstore/core/src/internal.ts:70:14)
      at Object.set (/...myapp.../node_modules/@syncedstore/core/src/object.ts:31:23)
      at crdtObject (/...myapp.../node_modules/@syncedstore/core/src/object.ts:99:5)

My particular use-case is inserting a modest JSON document from an API response into a boxed value, and that response happens to have some undefined values in it. Something like this:

const store = syncedStore<StoreShape>({/* ... */});
const apiResp = fetchThing(/*...*/).json();
store.key1.key2 = boxed(apiResp);

I don't see boxing directly related in the traceback, but I'm mentioning it in case it's helpful to understand the use case here.

Here's a small repro case based off the React sandbox from the docs. It adds field to the Todo type called description which may be undefined, and will set the description to undefined if you add a new todo named "bad".

WDYT about storing null/undefined in SyncedStore stores?

How to initialize value or use existing Yjs doc stored in server

First off, this is an amazing project and I'm incredibly impressed with how wide a range of reactive bindings and library integrations you were able to provide. Thank you for your work on this!

If it is important to the solution, I am using the Vue3 bindings.

The problem I'm running into is being able to initialize a document using a value stored in the server. When i try to provide a document with initial values to syncedStore like so:

const doc = syncedStore({
  items: ['a', 'b', 'c'],
})

I get the error: Root Array initializer must always be empty array.

I can technically initialize everything to an empty state and then add the data, but if there is a race condition or users have slow connections/offline mode this will cause duplication of data when they re-sync. Furthermore I want the flow of my document to be as such:

New document creation:

  1. User navigates to /list/:listID
  2. If database has a list by that ID load and initialize syncedStore to existing list.
  3. If no list exists by that ID then initialize to empty list.
  4. Any users viewing the same list can make edits over WebRTC provider
  5. Document is autosaved to database periodically by the last user to make an update
  6. All users eventually leave but may come back periodically to make edits, sometimes collaboratively

In your opinion what is the best way to allow for a document to be persisted and picked up again allowing for initialization of a template document or an existing databased stored document?

Any help greatly appreciated!!!

Yjs transact

Hi,

is there a way in SyncedStore to do doc transact as in Yjs, to reduce the number of update calls?

Overwriting an array re-patches array methods, eventually resulting in stack overflow

First let me say – thanks for an amazing library! It's been really useful for our project 🙌

I've tracked down an issue related to array usage within SyncedStore objects, which eventually results in stack overflow. Here's the shortest example I could create that demonstrates the issue:

import { syncedStore } from "@syncedstore/core";

type StoreRoot = {
  foo: Foo;
};

type Foo = {
  bar?: number[];
};

const store = syncedStore<StoreRoot>({ foo: {} });

const count = 100000;

for (let i = 0; i < count; ++i) {
  if (i % 1000 === 0) {
    console.log(`${i} of ${count}`);
  }

  // Results in patchGetter('length') being called on the array, even though it's already been patched
  store.foo.bar = [42];

  // Once the loop has executed enough times, reading the length will result in stack overflow
  store.foo.bar.length;
}

Output:

0 of 100000
1000 of 100000
2000 of 100000
3000 of 100000
4000 of 100000
5000 of 100000
6000 of 100000
@reactivedata/reactive:372
  get: function get(target, key, receiver) {
                   ^

RangeError: Maximum call stack size exceeded
    at Object.get (@reactivedata/reactive:372:20)
    at observable (@reactivedata/reactive:287:13)
    at Atom.reportObserved (@reactivedata/reactive:561:12)
    at reportSelfAtom (@syncedstore/yjs-reactive-bindings:111:14)
    at YArray.descriptor.get (@syncedstore/yjs-reactive-bindings:185:9)
    at YArray.descriptor.get (@syncedstore/yjs-reactive-bindings:188:25)
    at YArray.descriptor.get (@syncedstore/yjs-reactive-bindings:188:25)
    at YArray.descriptor.get (@syncedstore/yjs-reactive-bindings:188:25)
    at YArray.descriptor.get (@syncedstore/yjs-reactive-bindings:188:25)
    at YArray.descriptor.get (@syncedstore/yjs-reactive-bindings:188:25)

In our application we run into this quite regularly during any periods of extended usage. Please let me know if I can be of any further help!

Observe changes to object in array

Hello! I am using SyncedStore in a React application and have come across an issue that I cannot figure out.

Context

It appears that if I have an array of objects in a store, and I change a property of one object, then a component whose render depends on that object does not get updated.

The state data looks like this:

const sections = [
  {
    id: 1,
    todos: [
      { completed: true, title: "Write some code" },
      { completed: false, title: "Write a test" }
    ]
  },
  {
    id: 2,
    todos: [
      { completed: false, title: "Read a paper" }
    ]
  },
];

Repro CSB and video

I have made a minimal repro as a fork of the documentation’s CSB react app and a brief video describing the behavior that I see:

Modifying the data structure to use an object instead of an array seems to work like I’d expect:

Failing test PR

I have tried to write a good failing test in this PR YousefED/reactive#4 but I have not read that code carefully enough to know if this is a good repro case.

If it is more helpful to try to write a test against this repo, I'm happy to try that.

Thanks!

Feat: add Vue 3 composition api usage

Hi,thanks for this useful project. I found that there is only old option api usage in Vue example. Because Vue 3 has the powerful composition api just look like react hook, maybe it's a good idea to add composition features for Vue's users. If you agree with this proposal, I'd be glad to open a pr for it;)

Discussion: valtio-yjs binding

Hi, nice work!
I'm the author of valtio, and thinking to develop a library valtio-yjs (no repo yet), which binds those two worlds.
My plan is to use both public APIs, and create two-way bindings. After I spent a couple of hours, I noticed it's not that trivial. (And then got busy with other stuff.)
There might be something I can learn from your work and here's the start of discussion.

I'd be more than happy if we could collaborate on it, but as I see reactive and valtio are different API-wise and maybe for some base concepts. So, not sure what is feasible.

Consider Svelte?

Has anyone looked at Svelte integration? It seems like a natural fit. I wrote https://svelt-yjs.dev/ which is also an attempt at making yjs easier to use for Svelte, but I like this approach better.

SyncedText is not a valid synced object

SyncedText does throw an exception as show in the example below:

I would except that SyncedText is a valid synced object.

import { SyncedText, syncedStore, observeDeep } from "@syncedstore/core";

export const store = syncedStore({ myObject: {} });
store.myObject.myText = new SyncedText("hello");

observeDeep(store.myObject.myText, () => { // Does throw an exception
  store.myObject.myText.toString();
});

store.myObject.myText.insert(0, "My name is Bob, ");

I did create a codesandbox example, so it can be reproduced easily:
https://codesandbox.io/s/syncedstore-syncedtext-issue-kpomzt?file=/src/index.js:0-386

This part has been taken from the docs:
https://syncedstore.org/docs/advanced/text#syncedtext-objects

Angular support

Hi, you have a very impressive library. Are you planning to add Angular support?

What do you think about awareness?

(Please let me know if there's a better place for discussions like this. Do you prefer e.g. https://discuss.yjs.dev?)

I'm curious how you think about exposing awareness through SyncedStore.

A caveat — as I'm fairly new to Yjs, the answer here may very well be "don't use awareness like that." 😄

In this discuss.yjs.dev thread about Yjs Redux bindings I shared a bit code I've written that I use to provide some facilities reminiscent of redux and react-redux on top of SyncedStore: making the state available throughout the component tree with React context and hooks, and a way of organizing accessors and mutations with names drawn from redux, "selectors" and "actions". I shared a hasty gist here: https://gist.github.com/jasonm/8d81f233a6a4f853ddbd981a8784bc41

There's another piece that I added about awareness, to provide access to the awareness throughout the React component tree. I couldn't quite figure out how to wrap the awareness crdt in reactive/syncedstate (though I admit I haven't yet tried very hard 🙈 ) and ended up subscribing to awareness changes in a useEffect hook and mirroring them into React state that is provided by my context provider. This all happens in this part of this gist: https://gist.github.com/jasonm/8d81f233a6a4f853ddbd981a8784bc41#file-synccontext-tsx-L51-L74

However (back to my original caveat) this seems to rely on the provider being connected, which isn't great for an offline-friendly/offline-first approach. Perhaps I should not rely on storing the user identity state (currentUserId / JWT claims / etc) into the awareness crdt, and instead store it elsewhere (plain react state?) and then push the interesting parts into provider.awareness purely for, well, awareness (cursors, etc.)

@YousefED I wonder if you've thought about this?

Sorting an array in Synced Store

I have an array in synced store and I wanted to sort

I'm using

const [removed] = store.nodes.splice(srcIndex, 1);
store.nodes.splice(destIndex, 0, removed)

I keep getting an error Not supported: reassigning object that already occurs in the tree.

array is not boxed before yjs sync but returns as boxed after sync

thanks for this library, been playing with it but stumbled upon an issue when trying to nest an array inside a top level map, when I set the array it appears to be not-boxed (e.g. I can get crdt.myarray.length) but as soon as it gets synced back again from yjs it returns as boxed (crdt.myarray.length doesnt work anymore and instead need to use crdt.myarray.value.length)

is this expected?

Circular dependencies

After running npm build, it seems like some instances and functions are duplicated.

For example, there are two INTERNAL_SYMBOL in packages\core\dist\syncedstore.js

var INTERNAL_SYMBOL$1 = Symbol("INTERNAL_SYMBOL");
var INTERNAL_SYMBOL = Symbol("INTERNAL_SYMBOL");

"Cannot read property 'forEach' of null" when trying to set property to object returned by reactive-crdt previously

The vue todo example included in this repo has this problem when testing the "Clear Completed" functionality. It filters the todo's array by completed todo objects and then tries to set the todos array to the filtered array leading to this error TypeError: Cannot read property 'forEach' of null

simplified code to reproduce:

import { crdt, Y } from "./packages/reactive-crdt/dist/reactive-crdt.js";

const doc1 = new Y.Doc();
let store1 = crdt(doc1);
store1.arr = [];
store1.arr.push({ title: "Todo 1", completed: true });
store1.arr.push({ title: "Todo 2", completed: false });
let filtered_array = store1.arr.filter(x => !x.completed);
store1.testit = filtered_array[0]; // this will throw error

stack trace

    ;/** @type {Map<string, any>} */ (this._prelimContent).forEach((value, key) => {
                                                           ^

TypeError: Cannot read property 'forEach' of null
    at YMap._integrate (C:\reactive-crdt\node_modules\yjs\dist\yjs.cjs:5438:60)
    at ContentType.integrate (C:\reactive-crdt\node_modules\yjs\dist\yjs.cjs:8714:15)
    at Item.integrate (C:\reactive-crdt\node_modules\yjs\dist\yjs.cjs:9263:20)
    at C:\reactive-crdt\node_modules\yjs\dist\yjs.cjs:4904:20
    at Array.forEach (<anonymous>)
    at typeListInsertGenericsAfter (C:\reactive-crdt\node_modules\yjs\dist\yjs.cjs:4880:11)
    at typeListInsertGenerics (C:\reactive-crdt\node_modules\yjs\dist\yjs.cjs:4933:12)
    at C:\reactive-crdt\node_modules\yjs\dist\yjs.cjs:5258:9
    at transact (C:\reactive-crdt\node_modules\yjs\dist\yjs.cjs:3201:5)
    at YArray.insert (C:\reactive-crdt\node_modules\yjs\dist\yjs.cjs:5257:7)
    at YArray._integrate (C:\reactive-crdt\node_modules\yjs\dist\yjs.cjs:5205:10)
    at ContentType.integrate (C:\reactive-crdt\node_modules\yjs\dist\yjs.cjs:8714:15)
    at Item.integrate (C:\reactive-crdt\node_modules\yjs\dist\yjs.cjs:9263:20)
    at typeMapSet (C:\reactive-crdt\node_modules\yjs\dist\yjs.cjs:5067:130)
    at C:\reactive-crdt\node_modules\yjs\dist\yjs.cjs:5573:9
    at transact (C:\reactive-crdt\node_modules\yjs\dist\yjs.cjs:3201:5)
    at YMap.set (C:\reactive-crdt\node_modules\yjs\dist\yjs.cjs:5572:7)
    at Object.set (C:\reactive-crdt\packages\reactive-crdt\dist\reactive-crdt.js:91:13)
    at file:///C:/reactive-crdt/test.mjs:22:12

tried to debug and found that the object proxied by reactive-crdt is not "unwrapped" before passing it to yjs but not sure why not or if that's really the problem

Array indexing

It seems like array assignment by index is not supported currently. For example, if I have a store

export const store = syncedStore({
  todos: [] as Todo[],
  myText: {} as { text?: any },
  arr: [],
  fragment: "xml"
});

Then I type:

store.arr.push(1);
store.arr[0] = 1;

It gives me an error says array assignment is not supported. Is there any other way I can do to accomplish equivalent operation?

sync multiple documents

I'm investigating SyncedStore.

My use case is allowing users to select multiple documents (projects, i.e., JSON) they can edit. I want to each document synced separately using y-indexeddb (for now) and y-webrtc (goal).

My current approach is to create the store:

import { syncedStore } from '@syncedstore/core'
import { svelteSyncedStore } from '@syncedstore/svelte'

const projects = syncedStore({projects: []})
const store = svelteSyncedStore(projects)

export default store

And then, when the user inputs the id of the document in a form, attempting the following:

const createProject = () => {
    const doc = getYjsValue(store)
    new IndexeddbPersistence(projectId, doc);
    const project = {name, id: projectId, ...description && {description}}
    $store.projects.push(project)
    clear()
  }

Doing that creates a IndexedDB database named as the projectId, but causes the following console errors:

Uncaught TypeError: Cannot read properties of undefined (reading 'on')
    at new IndexeddbPersistence (y-indexeddb.js:113:9)
    at HTMLButtonElement.createProject (CreateProjectModal.svelte:27:41)

encoding.js:506 Uncaught (in promise) TypeError: Cannot read properties of undefined (reading 'store')
    at writeStateAsUpdate (encoding.js:506:36)
    at encodeStateAsUpdateV2 (encoding.js:525:3)
    at encodeStateAsUpdate (encoding.js:556:71)
    at y-indexeddb.js:77:27

Can I do what I'm wanting to do with SyncedStore?

Required version of node/npm for examples

Thanks for sharing SyncedStore! I'm digging in and starting by playing with the examples.

It looks like the package.json for the todo-react example works with npm 6.14.13 but not npm 8.1.4 (latest stable). Not sure exactly what version started the issue but the message is:

npm ERR! code EWORKSPACESCONFIG
npm ERR! workspaces config expects an Array

Gist of a shell session trying it out:
https://gist.github.com/jasonm/33e2d7a7501d3c4f6a8f7df150ed34ec

This seems to fix it:

diff --git a/examples/todo-react/package.json b/examples/todo-react/package.json
index 3ac21ec..4d3cece 100644
--- a/examples/todo-react/package.json
+++ b/examples/todo-react/package.json
@@ -44,10 +44,5 @@
       "last 1 firefox version",
       "last 1 safari version"
     ]
-  },
-  "workspaces": {
-    "nohoist": [
-      "**"
-    ]
   }
 }

set whole object on useSyncedStore

hi! if I have "const params = useSyncedStore(store.data.params)" and I have a store that has { data: { params: {} } as ISomeInterface }
imagine I want to set params to a new object, I can't do params = newParams;. I need to do useSyncedStore(store.data) and then do data.params = newParams

the problem is that because I'm observing store.data, it will refresh on any update. if I observe store.data.params, how do I set params to newParams?

TIA

Document Updates for Persistence

Hi Yousef,

Good day,

Is it possible for you to give me some pointers around document updates in SyncedStore for persistence?

Currently, I am thinking of computing the the diff of YDoc by getting the previous state before editing and current state after editing. The diff will then be persisted to database for future retrieval.

    const doc = getYjsValue(store);
    const prevState = Y.encodeStateAsUpdate(doc);
	
	// editing happens here
	
    const newState = Y.encodeStateAsUpdate(doc);
    const diff = Y.diffUpdate(prevState, newState);
        // the diff will then be persisted to database

However, the diff have to happen in every functions that are going to be used for editing.

The second option is to listen for the update event on YDoc:

doc.on("update", (update, origin) => {
  console.log("update from doc: ", update, origin);
  // however, origin is null thus couldnt differentiate whether the updates happens locally or from remote peers
});

FYI, the connection provider is WebRTCProvider.
Thus, if I am able to determine origin, I could persist the updates to database if the updates happens locally.
Is there a way to get to know the origin to differentiate oneself from other peers?

The document updates would then be retrieved from database and merged locally in each of the clients if needed.

Can I know whether my understanding and ideas is correct?

Thanks,
Vincent

rxjs binding

Thanks to SyncedStore for the results.
Are there any plans to bundle SyncStore with rxjs?

How are IDs handled?

This looks like a great project - I'm looking at using it for a collaborative app I'm working on.

My model has deeply nested structures, but in the client side store and in the database these are flattened out using unique IDs. If an object contains nested objects, I store IDs instead. In the store everything becomes top level.

It seems like this approach would work well with SyncedStore, but I'm wondering how allocating new IDs would be handled, to ensure they are unique across devices.

One solution would be to use UUIDs, but I find it a pain to work with these huge long IDs.

My current solution is to generate client-specific temp-IDs, and when objects are persisted to the server, the server allocates a proper unique ID and sends a mapping (temp-ID -> perm-ID) back to the client, so the client-side store can be updated accordingly. It's working but I don't love this approach. It's easy to make mistakes and introduce bugs.

It seems this must be a common issue with the kinds of apps SyncedStore is intended for. Is there a built-in solution?

Thanks very much.

Can I use SyncedStore solely between tabs in one browser? (No signaling servers)

It seems like it's possible to use SyncedStore in multiple tabs in a browser, just locally. Just BroadcastChannel and y-indexeddb, no signaling servers, and I don't think it would need any WebRTC activity.

I can share some code I've tried but first I just wanted to ask the question... Is there an official way to use it purely locally?

Documentation on creating your own provider

This looks fantastic, but I'm interested in creating my own provider, EG hasura or supabase.

Is there any guide to implementing your own provider? If so I would love to contribute by creating some open source ones.

Example from documentation doesn't typecheck

The minimal example from the documentation:

import { syncedStore, getYjsValue } from "@syncedstore/core";
import { WebrtcProvider } from "y-webrtc";

// (optional, define types for TypeScript)
type Vehicle = { color: string; brand: string };

// Create your SyncedStore store
export const store = syncedStore({ vehicles: [] as Vehicle[] });

// Get the Yjs document and sync automatically using y-webrtc
const doc = getYjsValue(store);
const webrtcProvider = new WebrtcProvider("my-document-id", doc);

Doesn't typecheck on line 12, because Argument of type 'Doc | AbstractType<any> | undefined' is not assignable to parameter of type 'Doc'. Type 'undefined' is not assignable to type 'Doc'.

only refresh when there is a change

Hello! if I have the sample todo list, and I do a "todo.completed = false;" always false
if the value is already false, it triggers a change in the store.. and the page refreshes. is it possible to trigger a change only if the set value is different from the current one? of course, I can check if completed is false, but would like a way to do this automatically if possible. Thanks!

How to use with mobx class stores?

Is it allowed to pass mobx store instance into syncedStore fn? My store has a bunch of methods and I am worried this will not work. In this case, how should I integrate with mobx? Separate actions/getters somehow?

Example store:

class TodoStore {
  todos = [];

  get completedTodosCount() {
    return this.todos.filter(
      todo => todo.completed === true
    ).length;
  }

  addTodo(task) {
    this.todos.push({
      task: task,
      completed: false,
      assignee: null
    });
  }
}

const todoStore = new TodoStore();

Thanks!

"observe" doesn't work immediately after filling SyncedMap

Hi, I stumbled on this problem that seems very simple, but I can't find info about the solution in the docs of either Yjs or SyncedStore.

"observe" doesn't work immediately after filling SyncedMap:

const store = new syncedStore({ obj: {} })

store.obj.array = []

getYjsValue(store.obj.array).observe(event => {
  console.log(event)
})

// Inserting into store.obj.array doesn't trigger console.log

"observe" works after sufficient delay:

const store = new syncedStore({ obj: {} })

store.obj.array = []

setTimeout(() => {
  getYjsValue(store.obj.array).observe(event => {
    console.log(event)
  })
}, 2000)

// Inserting into store.obj.array triggers console.log

There seems to be a delay after which I'm able to use observe.
So I'm wondering: is there a hook in which I can initialize my observes?

Thanks

Btw, I'm using SyncedStore with Vue

Vue usage without TypeScript?

This project looks awesome. I was reading about y.js and tried it with Vue which worked, but you seem to have taken it a lot further and almost make the whole magic "invisible".

I’ve tried the example for Vue, but can’t seem to get it to work. How do I need to set up the data without TypeScript?

sharedData: crdt<{
  vehicles: Vehicle[];
}>(doc),

I’ve tried:

sharedData: crdt(doc)

But changing arbitrary keys on sharedData (like sharedData.vehicles) wasn’t noticed.

Errors with Vue example

I tried installing the Vue example with npm and it failed - I try not to use npm. Then did yarn install and all went well. However when I go yarn run serve I get:

yarn run v1.22.10
$ vue-cli-service serve
 INFO  Starting development server...
98% after emitting CopyPlugin

 ERROR  Failed to compile with 4 errors                                                                                             6:49:29 pm
This dependency was not found:

* yjs in ./node_modules/@reactivedata/reactive-crdt/dist/reactive-crdt.module.js, ./node_modules/@reactivedata/yjs-reactive-bindings/dist/yjs-reactive-bindings.module.js and 2 others

To install it, you can run: npm install --save yjs
Issues checking in progress...
Error from chokidar (C:\): Error: EBUSY: resource busy or locked, lstat 'C:\DumpStack.log.tmp'
No issues found.

I just wanted to have a quick play, so don't go to any trouble. I'm new to yjs and saw your post: https://discuss.yjs.dev/t/reactive-crdt-easy-to-use-api-to-use-yjs-and-build-collaborative-apps/507/7

I understand you are using a fork of yjs.

FYI For managing state I was using NX Observe, then moved to onChange and more recently the reactive code in Acebase which is the best fit so far.

I am now looking seriously at yjs which is how I discovered your lib. The ability to use plain JS objects and arrays is very appealing. I don't use Vue or React 😀

PS. I'm using Node v16.1.0 on Windows 10

svelteSyncedStore not synced across tabs

Maybe I'm missing something, but I tried to follow the examples and it's just not working. I tried a brand new Svelte kit app, installed dependencies, and set up a bare bones store

npm create svelte@latest ss-svelte && cd ss-svelte
npm i --save @syncedstore/core yjs @syncedstore/svelte
npm run dev

Then just src/routes/+page.svelte and src/lib/stores.js as follows:

<script>
    import {store} from '$lib/stores.js'

    function setVal(val) {
        return () => {
            $store.data.val = val
        }
    }
</script>

<p>Current value of $store.data.val is {$store.data.val}</p> 
<p>$store.todos.length is {$store.todos.length}</p> 

<h1 on:click="{setVal(1)}">1</h1>
<h1 on:click="{setVal(2)}">2</h1>
<h1 on:click="{setVal(3)}">3</h1>
import { syncedStore } from "@syncedstore/core";
import { svelteSyncedStore } from "@syncedstore/svelte";

// Create your SyncedStore store
const sStore = syncedStore({ data: {}, todos: [] });

// Create Svelte Store for use in your components.
// You can treat this like any other store, including `bind`.
export const store = svelteSyncedStore(sStore);

The store is not kept up to date between tabs:
image

Is there a way to replace a store with a known good value?

Hi, apologies if this question is answered elsewhere, I looked through all the docs and other issues and couldn't see anything though.

I have a simple chat app, and I would like to use this library to get instant updates. However, I also want each message to be persisted in my database, and when data is fetched from that db I want to replace the syncedstore with that new data.

So really I just want to do this:

// when adding a comment
const handleAddComment = () => {
   // update store to trigger real time updates for others
    state.comments.push({
      comment,
    })
   // persist to db, might take a second or so and won't trigger any updates
    addCommentHook.mutate({
      comment
    })
  }

// data is from the server, I don't know exactly when it will change, 
// but if it does I want to fully replace the contents of my store with it
  useEffect(() => {
    state.comments = data.comments
  }, [data])

However, this doesn't work because state.comments is read only. Is there a way to do this? Or am I going down the wrong path entirely?

Installation fails

Hi, trying to install the library, I get a dependency failure due to an apparent incompatibility with yjs

npm ERR! code ELIFECYCLE
npm ERR! errno 2
npm ERR! [email protected] dist: `rm -rf dist && rollup -c && tsc`
npm ERR! Exit status 2
npm ERR!
npm ERR! Failed at the [email protected] dist script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

Support binary data

I have been using yjs for some time now. And I usually store binary data in my yjs documents using Uint8Array objects. I wonder if you could add the option to store Uint8Array objects without using the boxed function.

Example react app appears to be broken across browser windows

Hello! I was investigating using yjs in a react project, and started designing my own abstraction when I came across this project. It looks really cool although I haven't been able to get state to sync across two browser windows. Oddly enough, the state syncs when I have my app open in two tabs in the same browser window. I haven't pushed my code anywhere as I'm still playing around with different approaches before incorporating a solution into my main project, but I was able to repro the issue in the react example app: https://3xemy.csb.app/, if you open that link in a chrome window, and then open it again in a second incognito window, the state doesn't get synced across the two windows. In my local playground app, I use y-webrtc as my provider and determined that the two windows are indeed connecting to each other, but haven't investigated much beyond that.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.