Feature: Support path for migrating to NodeState with yjs
- Dominant language
- TypeScript
- Stars
- 23.9k
- Forks
- 2.2k
- Avg merge
- 1d 14h
- Merged PRs (30d)
- 57
Description
## Description
I'm trying to upgrade some custom nodes to using the new NodeState API. The goal is to be as minimally invasive to start. Currently I'm attempted to update my getters and setters:
```ts
// from something like:
getFoo(): string {
return this.getLatest().__foo;
}
setFoo(value: string) {
const self = this.getWritable();
self.__foo = value;
return self;
}
// to something like:
const fooState = createState('foo', { parse: (v) => typeof v === 'string' ? v : null });
getFoo(): string {
return $getState(this, fooState)
}
setFoo(value: string): this {
return $setState(this, fooState, value);
}
```
I'm trying to find a streamlined way to migrate `__foo` to `$.foo` when the document loads.
I think that adding the `updateFromJSON` overload can handle the case when loading the document from JSON.
```ts
// something like
static importJSON(serialized: SerializedFooNode): FooNode {
return $createFooNode().updateFromJSON(serialized);
}
updateFromJSON(serialized: SerializedFooNode): this {
const self = super.updateFromJSON(serialized);
if (serialized.version === 1) {
// old version
self.setFoo(serialized.foo); // calls $setState internally
} else {
// current version. "super" call above handles setting the node state
}
}
```
This doesn't work when opening up a document from a yjs server though since yjs applies changes directly to the nodes and doesn't go through JSON serialization.
**Is there a way to automatically migrate to node state when using yjs that I'm missing?**
If not, perhaps something like:
```ts
const fooState = createState('foo', {
parse: (v) => typeof v === 'string' ? v : null,
// new option: called only once when the node is loaded for the first time (not sure the heuristic for this)
// in other words, it gets called if the node is deserialized and node state doesn't exist or if it's loaded from
// a collab server and the node state doesn't exist yet.
migrate: (fromNode, toNode) => {
// probably want some more defensive code here.
toNode.setFoo(fromNode['__foo']);
},
```
Things I've tried:
- Adding the logic in the `getFoo` method checking the property and the result of `$getState` each time and calling `$setState` if they differ. This doesn't work if the editor is in read-only mode.
- I have mostly solved this using a mutation listener and manually upgrading each node type. Also a single update listener could probably solve the problem. But these solutions seem complex and brittle since you'd need to ensure that the update was the correct update (collab initial load, or document load), and each node would have to manually define it's own `migrate` method.
## Impact
This could increase adoption of the NodeState API for existing users if the migration API was clean and intuitive.
Contributor guide
Assessment
This issue has not been assessed yet.