UNPKG

@syncedstore/core

Version:

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.

104 lines (93 loc) 2.75 kB
import { $reactive, $reactiveproxy } from "@reactivedata/reactive"; import * as Y from "yjs"; import { getYjsValue, INTERNAL_SYMBOL } from "."; import { CRDTArray } from "./array"; import { Box } from "./boxed"; import { crdtValue, parseYjsReturnValue, yToWrappedCache, ObjectSchemaType } from "./internal"; export type CRDTObject<T extends ObjectSchemaType> = { [P in keyof T]?: T[P] extends Box<infer A> ? Box<A> : T[P] extends Array<infer A> ? CRDTArray<A> : T[P] extends ObjectSchemaType ? CRDTObject<T[P]> : T[P]; } & { [INTERNAL_SYMBOL]?: Y.Map<T>; }; export function crdtObject<T extends ObjectSchemaType>(initializer: T, map = new Y.Map<any>()) { if (map[$reactive]) { throw new Error("unexpected"); // map = map[$reactive].raw; } const proxy = new Proxy({} as any as CRDTObject<T>, { set: (target, p, value) => { if (typeof p !== "string") { throw new Error(); } const wrapped = crdtValue(value); // TODO: maybe set cache let valueToSet = getYjsValue(wrapped) || wrapped; if (valueToSet instanceof Box) { valueToSet = valueToSet.value; } if (valueToSet instanceof Y.AbstractType && valueToSet.parent) { throw new Error("Not supported: reassigning object that already occurs in the tree."); } map.set(p, valueToSet); return true; }, get: (target, p, receiver) => { if (p === INTERNAL_SYMBOL) { return map; } if (typeof p !== "string") { return Reflect.get(target, p); // throw new Error("get non string parameter"); } let ic: any; if (receiver && receiver[$reactiveproxy]) { ic = receiver[$reactiveproxy]?.implicitObserver; (map as any)._implicitObserver = ic; } else { // console.warn("no receiver getting property", p); } let ret = map.get(p); ret = parseYjsReturnValue(ret, ic); return ret; }, deleteProperty: (target, p) => { if (typeof p !== "string") { throw new Error(); } if (map.has(p)) { map.delete(p); return true; } else { return false; } }, has: (target, p) => { if (typeof p === "string" && map.has(p)) { return true; } return false; }, getOwnPropertyDescriptor(target, p) { if (typeof p === "string" && map.has(p)) { return { enumerable: true, configurable: true, }; } return undefined; }, ownKeys: (target) => { return Array.from(map.keys()); }, }); yToWrappedCache.set(map, proxy); for (let key in initializer) { proxy[key] = initializer[key] as any; } return proxy; }