@syncedstore/yjs-reactive-bindings
Version:
A bridge between Reactive programming libraries (reactive, Vue or MobX) and Yjs
109 lines (96 loc) • 3.5 kB
text/typescript
import * as Y from "yjs";
import { observeArray } from "./types/array";
import { observeDoc } from "./types/doc";
import { observeMap } from "./types/map";
import { observeText } from "./types/text";
import { observeXml } from "./types/xml";
export function isYType(element: any) {
return element instanceof Y.AbstractType || Object.prototype.hasOwnProperty.call(element, "autoLoad"); // detect subdocs. Is there a better way for this?
}
export function observeYJS(element: Y.AbstractType<any> | Y.Doc) {
if (element instanceof Y.XmlText) {
return observeText(element);
} else if (element instanceof Y.Text) {
return observeText(element);
} else if (element instanceof Y.Array) {
return observeArray(element);
} else if (element instanceof Y.Map) {
return observeMap(element);
} else if (element instanceof Y.Doc || Object.prototype.hasOwnProperty.call(element, "autoLoad")) {
// subdoc. Ok way to detect this?
return observeDoc(element as any as Y.Doc);
} else if (element instanceof Y.XmlFragment) {
return observeXml(element);
} else if (element instanceof Y.XmlElement) {
return observeXml(element);
} else {
if (element._item === null && element._start === null) {
// console.warn("edge case");
} else {
// throw new Error("not yet supported");
}
}
return element;
}
function makeYDocRootLevelTypesObservable(doc: Y.Doc) {
doc.share.forEach((type) => {
// the explicit check is necessary because we sometimes initialize "anonymous" types that the user can't (and shouldn't) access.
if (type.constructor !== Y.AbstractType) {
// console.log("root", type)
observeYJS(type);
}
});
}
function makeStructsObservable(structs: (Y.Item | Y.GC)[], startPos: number) {
for (let i = structs.length - 1; i >= startPos; i--) {
let struct = structs[i];
if (!struct.deleted) {
if (struct instanceof Y.GC) {
continue;
}
struct.content?.getContent().forEach((content) => {
if (content instanceof Y.AbstractType) {
// console.log("struct", content)
observeYJS(content);
// console.log(content, "is a created type type");
}
});
}
}
}
const docsObserved = new WeakSet<Y.Doc>();
export function makeYDocObservable(doc: Y.Doc) {
if (docsObserved.has(doc)) {
return;
}
docsObserved.add(doc);
// based on https://github.com/yjs/yjs/pull/298#issuecomment-937636849
// hook new root type creations (when calling getMap() or getArray(), etc)
observeYJS(doc);
// observe all structs already in the document
doc.store.clients.forEach((entry) => {
if (entry) {
makeStructsObservable(entry, 0);
}
});
// observe all root-types
makeYDocRootLevelTypesObservable(doc);
// observe newly created types from now on
doc.on("beforeObserverCalls", (tr: Y.Transaction) => {
// observe new root types
makeYDocRootLevelTypesObservable(doc);
// observe new structs
tr.afterState.forEach((clock, client) => {
const beforeClock = tr.beforeState.get(client) || 0;
if (beforeClock !== clock) {
const structs = tr.doc.store.clients.get(client);
if (!structs) {
return;
}
const firstChangePos = Y.findIndexSS(structs, beforeClock);
makeStructsObservable(structs, firstChangePos);
}
});
});
}
export { enableMobxBindings, enableReactiveBindings, enableVueBindings } from "./observableProvider";