Localstorage projects using Y.js #632
Draft
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
This builds on the persistence module developed for the python editor, for the basic overview look here.
microbit-foundation/python-editor-v3#1236
Challenges using a CRDT-backed persistent storage in CreateAI
Zustand persistence
Having a Zustand store with interchangeable project data is a challenge, because it does not wait for React's events, but connects to the persistence store on the javascript module load. This means it's ready to go out of the box, but if you are managing multiple projects then loading and switching projects break the assumption that you know where your project is before React is ready.
I addressed this by writing my own persistent storage singleton (persist just needs a get and set that can pack json) that exists on module load, and by turning off automatic hydration of the Zustand store, instead hydrating once a database is connected. This happens in
src/store-persistence.tsandstore-persistence-hooks.tshas the logic to change the backing store.An alternate pattern would be to have a Zustand store without persist middleware, and write to it from outside the store (as opposed to inside, in the
persiststorage), replicating the data whenever you load or save from persistence.I have not exposed the app itself to changes in persistence state (e.g. what do you do if you try reading the store while it's loading?)
Y.js datatypes in Zustand
It made sense to me to use Actions as a spike, so that multiple users could, e.g. add actions and recordings to the same project. However, Zustand treats states as immutable wavefronts, while Y.js expects to be able to create deltas from
insertanddeleteoperations on a persistent object. I created auseActionshook that exposes the Y.js actions.The Zustand store contains logic that manages updates to the MakeCode project, and so this needs to be able to see the actions. I have, therefore, plumbed the actions into the state provided by the custom persist store mentioned above. Zustand itself is datatype-agnostic and doesn't care that it's not a serialisable type, but it rather breaks Zustand's paradigm, and if you ever wanted to use
immeron it, it might cause issues.This would be simplified if the MakeCode project was updated from an observer, rather than inside the Zustand store, as it could then pull in the actions from other sources without being run inside a Zustand action. However this was somewhat out of scope for this project.
MakeCode projects
As discussed elsewhere, the structure of a MakeCode project is currently very difficult to align with CRDTs, and I have not had any successful attempts.