@liveblocks/client
Advanced tools
Comparing version 0.12.0-beta.4 to 0.12.0-beta.6
import { Op, SerializedCrdtWithId, SerializedList } from "./live"; | ||
declare const INTERNAL: unique symbol; | ||
declare type Dispatch = (ops: Op[]) => void; | ||
interface ICrdt { | ||
readonly [INTERNAL]: { | ||
getId(): string | undefined; | ||
getParentId(): string | undefined; | ||
attach(id: string, doc: Doc, parentId?: string, parentKey?: string): Op[]; | ||
attachChild(key: any, child: ICrdt): void; | ||
detach(): void; | ||
detachChild(child: ICrdt): void; | ||
}; | ||
} | ||
export declare class Doc<T extends Record<string, any> = Record<string, any>> { | ||
@@ -24,4 +13,5 @@ private _root; | ||
dispatch(ops: Op[]): void; | ||
addItem(id: string, item: ICrdt): void; | ||
addItem(id: string, item: AbstractCrdt): void; | ||
deleteItem(id: string): void; | ||
getItem(id: string): AbstractCrdt | undefined; | ||
apply(op: Op): void; | ||
@@ -40,67 +30,54 @@ private applyCreateRegister; | ||
} | ||
export declare class LiveObject<T extends Record<string, any> = Record<string, any>> { | ||
declare class AbstractCrdt { | ||
#private; | ||
private _listeners; | ||
private _deepListeners; | ||
protected get doc(): Doc<Record<string, any>> | undefined; | ||
get id(): string | undefined; | ||
get parent(): AbstractCrdt | undefined; | ||
setParent(parent: AbstractCrdt): void; | ||
attach(id: string, doc: Doc): void; | ||
attachChild(id: string, key: string, crdt: AbstractCrdt): void; | ||
detach(): void; | ||
detachChild(crdt: AbstractCrdt): void; | ||
subscribe(listener: () => void): void; | ||
subscribeDeep(listener: () => void): void; | ||
unsubscribe(listener: () => void): void; | ||
unsubscribeDeep(listener: () => void): void; | ||
notify(onlyDeep?: boolean): void; | ||
serialize(parentId: string, parentKey: string): Op[]; | ||
} | ||
export declare class LiveObject<T extends Record<string, any> = Record<string, any>> extends AbstractCrdt { | ||
private _map; | ||
private _listeners; | ||
private _ctx?; | ||
constructor(object?: T); | ||
/** | ||
* INTERNAL | ||
*/ | ||
serialize(parentId?: string, parentKey?: string): Op[]; | ||
static deserialize([id, item]: SerializedCrdtWithId, parentToChildren: Map<string, SerializedCrdtWithId[]>, doc: Doc): LiveObject<{ | ||
[key: string]: any; | ||
}>; | ||
get [INTERNAL](): { | ||
ctx: { | ||
id: string; | ||
doc: Doc<Record<string, any>>; | ||
parentId?: string | undefined; | ||
} | undefined; | ||
attachChild: (key: keyof T, child: ICrdt) => void; | ||
detachChild: (child: ICrdt) => void; | ||
detach: () => void; | ||
attach: (id: string, doc: Doc<Record<string, any>>, parentId?: string | undefined, parentKey?: string | undefined) => Op[]; | ||
apply: (op: Op) => void; | ||
getParentId: () => string | undefined; | ||
getId: () => string | undefined; | ||
}; | ||
private _getParentId; | ||
private _getId; | ||
private attach; | ||
private attachChild; | ||
private detachChild; | ||
private detach; | ||
private apply; | ||
private notify; | ||
attach(id: string, doc: Doc): void; | ||
attachChild(id: string, key: keyof T, child: AbstractCrdt): void; | ||
detachChild(child: AbstractCrdt): void; | ||
detach(): void; | ||
/** | ||
* INTERNAL | ||
*/ | ||
apply(op: Op): void; | ||
toObject(): T; | ||
set<TKey extends keyof T>(key: TKey, value: T[TKey]): void; | ||
get<TKey extends keyof T>(key: TKey): T[TKey]; | ||
delete<TKey extends keyof T>(key: TKey): void; | ||
update(overrides: Partial<T>): void; | ||
subscribe(listener: () => void): void; | ||
unsubscribe(listener: () => void): void; | ||
} | ||
export declare class LiveMap<TKey extends string, TValue> implements ICrdt { | ||
private _listeners; | ||
export declare class LiveMap<TKey extends string, TValue> extends AbstractCrdt { | ||
private _map; | ||
private _ctx?; | ||
constructor(entries?: readonly (readonly [TKey, TValue])[] | null | undefined); | ||
serialize(parentId?: string, parentKey?: string): Op[]; | ||
static deserialize([id, item]: SerializedCrdtWithId, parentToChildren: Map<string, SerializedCrdtWithId[]>, doc: Doc): LiveMap<string, unknown>; | ||
get [INTERNAL](): { | ||
ctx: { | ||
id: string; | ||
doc: Doc<Record<string, any>>; | ||
parentId?: string | undefined; | ||
} | undefined; | ||
apply: (op: Op) => void; | ||
attachChild: (key: TKey, child: ICrdt) => void; | ||
attach: (id: string, doc: Doc<Record<string, any>>, parentId: string, parentKey: string) => Op[]; | ||
detach: () => void; | ||
detachChild: (child: ICrdt) => void; | ||
getParentId: () => string | undefined; | ||
getId: () => string | undefined; | ||
}; | ||
private _getParentId; | ||
private _getId; | ||
private apply; | ||
private attach; | ||
private attachChild; | ||
private detach; | ||
private detachChild; | ||
attach(id: string, doc: Doc): void; | ||
attachChild(id: string, key: TKey, child: AbstractCrdt): void; | ||
detach(): void; | ||
detachChild(child: AbstractCrdt): void; | ||
get(key: TKey): TValue | undefined; | ||
@@ -116,36 +93,14 @@ set(key: TKey, value: TValue): void; | ||
forEach(callback: (value: TValue, key: TKey, map: LiveMap<TKey, TValue>) => void): void; | ||
subscribe(listener: () => void): void; | ||
unsubscribe(listener: () => void): void; | ||
private notify; | ||
} | ||
export declare class LiveList<T> implements ICrdt { | ||
private _listeners; | ||
private _ctx?; | ||
export declare class LiveList<T> extends AbstractCrdt { | ||
private _items; | ||
constructor(items?: T[]); | ||
static deserialize([id, item]: [id: string, item: SerializedList], parentToChildren: Map<string, SerializedCrdtWithId[]>, doc: Doc): LiveList<never>; | ||
get [INTERNAL](): { | ||
ctx: { | ||
id: string; | ||
parentId: string; | ||
doc: Doc<Record<string, any>>; | ||
} | undefined; | ||
attachChild: (key: string, child: ICrdt) => void; | ||
detachChild: (child: ICrdt) => void; | ||
attach: (id: string, doc: Doc<Record<string, any>>, parentId: string, parentKey: string) => Op[]; | ||
detach: () => void; | ||
apply: (op: Op) => void; | ||
setChildKey: (key: string, child: ICrdt) => void; | ||
getParentId: () => string | undefined; | ||
getId: () => string | undefined; | ||
}; | ||
private _getParentId; | ||
private _getId; | ||
private attach; | ||
private detach; | ||
private attachChild; | ||
private detachChild; | ||
private setChildKey; | ||
serialize(parentId?: string, parentKey?: string): Op[]; | ||
attach(id: string, doc: Doc): void; | ||
detach(): void; | ||
attachChild(id: string, key: string, child: AbstractCrdt): void; | ||
detachChild(child: AbstractCrdt): void; | ||
setChildKey(key: string, child: AbstractCrdt): void; | ||
private apply; | ||
private notify; | ||
push(item: T): void; | ||
@@ -157,5 +112,3 @@ insert(item: T, index: number): void; | ||
get(index: number): T; | ||
subscribe(listener: () => void): void; | ||
unsubscribe(listener: () => void): void; | ||
} | ||
export {}; |
"use strict"; | ||
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, privateMap) { | ||
if (!privateMap.has(receiver)) { | ||
throw new TypeError("attempted to get private field on non-instance"); | ||
} | ||
return privateMap.get(receiver); | ||
}; | ||
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, privateMap, value) { | ||
if (!privateMap.has(receiver)) { | ||
throw new TypeError("attempted to set private field on non-instance"); | ||
} | ||
privateMap.set(receiver, value); | ||
return value; | ||
}; | ||
var _parent, _doc, _id; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
@@ -20,4 +34,4 @@ exports.LiveList = exports.LiveMap = exports.LiveObject = exports.Doc = void 0; | ||
const storage = new Doc(rootRecord, actor, dispatch); | ||
const ops = rootRecord[INTERNAL].attach(storage.generateId(), storage); | ||
storage.dispatch(ops); | ||
rootRecord.attach(storage.generateId(), storage); | ||
storage.dispatch(rootRecord.serialize()); | ||
return storage; | ||
@@ -62,2 +76,5 @@ } | ||
} | ||
getItem(id) { | ||
return this._items.get(id); | ||
} | ||
apply(op) { | ||
@@ -100,4 +117,2 @@ switch (op.type) { | ||
applyCreateRegister(op) { | ||
const newRegister = new LiveRegister(op.data); | ||
newRegister[INTERNAL].attach(op.id, this, op.parentId, op.parentKey); | ||
const parent = this._items.get(op.parentId); | ||
@@ -110,3 +125,4 @@ if (parent == null) { | ||
} | ||
parent[INTERNAL].attachChild(op.parentKey, newRegister); | ||
const newRegister = new LiveRegister(op.data); | ||
parent.attachChild(op.id, op.parentKey, newRegister); | ||
} | ||
@@ -116,3 +132,3 @@ applyDeleteRecordKey(op) { | ||
if (item && item instanceof LiveObject) { | ||
item[INTERNAL].apply(op); | ||
item.apply(op); | ||
} | ||
@@ -123,3 +139,3 @@ } | ||
if (item && item instanceof LiveObject) { | ||
item[INTERNAL].apply(op); | ||
item.apply(op); | ||
} | ||
@@ -133,4 +149,3 @@ } | ||
const newMap = new LiveMap(); | ||
newMap[INTERNAL].attach(op.id, this, op.parentId, op.parentKey); | ||
parent[INTERNAL].attachChild(op.parentKey, newMap); | ||
parent.attachChild(op.id, op.parentKey, newMap); | ||
} | ||
@@ -142,9 +157,6 @@ applyCreateList(op) { | ||
} | ||
const newMap = new LiveList(); | ||
newMap[INTERNAL].attach(op.id, this, op.parentId, op.parentKey); | ||
parent[INTERNAL].attachChild(op.parentKey, newMap); | ||
const list = new LiveList(); | ||
parent.attachChild(op.id, op.parentKey, list); | ||
} | ||
applyCreateObject(op) { | ||
const newObj = new LiveObject(op.data); | ||
newObj[INTERNAL].attach(op.id, this, op.parentId, op.parentKey); | ||
if (op.parentId && op.parentKey) { | ||
@@ -155,3 +167,4 @@ const parent = this._items.get(op.parentId); | ||
} | ||
parent[INTERNAL].attachChild(op.parentKey, newObj); | ||
const newObj = new LiveObject(op.data); | ||
parent.attachChild(op.id, op.parentKey, newObj); | ||
} | ||
@@ -164,9 +177,8 @@ } | ||
} | ||
const parentId = item[INTERNAL].getParentId(); | ||
if (parentId == null) { | ||
const parent = item.parent; | ||
if (parent == null) { | ||
return; | ||
} | ||
const parent = this._items.get(parentId); | ||
if (parent) { | ||
parent[INTERNAL].detachChild(item); | ||
parent.detachChild(item); | ||
} | ||
@@ -179,9 +191,7 @@ } | ||
} | ||
const parentId = item[INTERNAL].getParentId(); | ||
if (parentId == null) { | ||
if (item.parent == null) { | ||
return; | ||
} | ||
const parent = this._items.get(parentId); | ||
if (parent && parent instanceof LiveList) { | ||
parent[INTERNAL].setChildKey(op.parentKey, item); | ||
if (item.parent instanceof LiveList) { | ||
item.parent.setChildKey(op.parentKey, item); | ||
} | ||
@@ -200,72 +210,109 @@ } | ||
exports.Doc = Doc; | ||
class LiveObject { | ||
constructor(object = {}) { | ||
class AbstractCrdt { | ||
constructor() { | ||
this._listeners = []; | ||
this._map = new Map(Object.entries(object)); | ||
this._deepListeners = []; | ||
_parent.set(this, void 0); | ||
_doc.set(this, void 0); | ||
_id.set(this, void 0); | ||
} | ||
static deserialize([id, item], parentToChildren, doc) { | ||
if (item.type !== live_1.CrdtType.Object) { | ||
throw new Error(`Tried to deserialize a record but item type is "${item.type}"`); | ||
get doc() { | ||
return __classPrivateFieldGet(this, _doc); | ||
} | ||
get id() { | ||
return __classPrivateFieldGet(this, _id); | ||
} | ||
get parent() { | ||
return __classPrivateFieldGet(this, _parent); | ||
} | ||
setParent(parent) { | ||
if (__classPrivateFieldGet(this, _parent)) { | ||
throw new Error("Cannot attach parent if it already exist"); | ||
} | ||
const record = new LiveObject(item.data); | ||
record.attach(id, doc, item.parentId, item.parentKey); | ||
const children = parentToChildren.get(id); | ||
if (children == null) { | ||
return record; | ||
__classPrivateFieldSet(this, _parent, parent); | ||
} | ||
attach(id, doc) { | ||
if (__classPrivateFieldGet(this, _id) || __classPrivateFieldGet(this, _doc)) { | ||
throw new Error("Cannot attach if CRDT is already attached"); | ||
} | ||
for (const entry of children) { | ||
const crdt = entry[1]; | ||
if (crdt.parentKey == null) { | ||
throw new Error("Tried to deserialize a crdt but it does not have a parentKey and is not the root"); | ||
} | ||
const child = deserialize(entry, parentToChildren, doc); | ||
record._map.set(crdt.parentKey, child); | ||
doc.addItem(id, this); | ||
__classPrivateFieldSet(this, _id, id); | ||
__classPrivateFieldSet(this, _doc, doc); | ||
} | ||
attachChild(id, key, crdt) { | ||
throw new Error("attachChild should be implement by a non abstract CRDT"); | ||
} | ||
detach() { | ||
if (__classPrivateFieldGet(this, _doc) && __classPrivateFieldGet(this, _id)) { | ||
__classPrivateFieldGet(this, _doc).deleteItem(__classPrivateFieldGet(this, _id)); | ||
} | ||
return record; | ||
__classPrivateFieldSet(this, _parent, undefined); | ||
__classPrivateFieldSet(this, _doc, undefined); | ||
} | ||
get [INTERNAL]() { | ||
return { | ||
ctx: this._ctx, | ||
attachChild: this.attachChild.bind(this), | ||
detachChild: this.detachChild.bind(this), | ||
detach: this.detach.bind(this), | ||
attach: this.attach.bind(this), | ||
apply: this.apply.bind(this), | ||
getParentId: this._getParentId.bind(this), | ||
getId: this._getId.bind(this), | ||
}; | ||
detachChild(crdt) { | ||
throw new Error("detach child should be implement by a non abstract CRDT"); | ||
} | ||
_getParentId() { | ||
var _a; | ||
return (_a = this._ctx) === null || _a === void 0 ? void 0 : _a.parentId; | ||
subscribe(listener) { | ||
this._listeners.push(listener); | ||
} | ||
_getId() { | ||
var _a; | ||
return (_a = this._ctx) === null || _a === void 0 ? void 0 : _a.id; | ||
subscribeDeep(listener) { | ||
this._deepListeners.push(listener); | ||
} | ||
attach(id, doc, parentId, parentKey) { | ||
if (this._ctx) { | ||
throw new Error("LiveObject is already part of the storage"); | ||
unsubscribe(listener) { | ||
utils_1.remove(this._listeners, listener); | ||
} | ||
unsubscribeDeep(listener) { | ||
utils_1.remove(this._deepListeners, listener); | ||
} | ||
notify(onlyDeep = false) { | ||
if (onlyDeep === false) { | ||
for (const listener of this._listeners) { | ||
listener(); | ||
} | ||
} | ||
doc.addItem(id, this); | ||
this._ctx = { | ||
id, | ||
doc: doc, | ||
parentId, | ||
}; | ||
for (const listener of this._deepListeners) { | ||
listener(); | ||
} | ||
if (this.parent) { | ||
this.parent.notify(true); | ||
} | ||
} | ||
serialize(parentId, parentKey) { | ||
throw new Error("serialize should be implement by a non abstract CRDT"); | ||
} | ||
} | ||
_parent = new WeakMap(), _doc = new WeakMap(), _id = new WeakMap(); | ||
class LiveObject extends AbstractCrdt { | ||
constructor(object = {}) { | ||
super(); | ||
for (const key in object) { | ||
const value = object[key]; | ||
if (value instanceof AbstractCrdt) { | ||
value.setParent(this); | ||
} | ||
} | ||
this._map = new Map(Object.entries(object)); | ||
} | ||
/** | ||
* INTERNAL | ||
*/ | ||
serialize(parentId, parentKey) { | ||
if (this.id == null) { | ||
throw new Error("Cannot serialize item is not attached"); | ||
} | ||
const ops = []; | ||
const createOp = { | ||
id: this._ctx.id, | ||
const op = { | ||
id: this.id, | ||
type: live_1.OpType.CreateObject, | ||
parentId, | ||
parentKey, | ||
data: {}, | ||
data: {} | ||
}; | ||
ops.push(createOp); | ||
ops.push(op); | ||
for (const [key, value] of this._map) { | ||
if (isCrdt(value)) { | ||
ops.push(...value[INTERNAL].attach(doc.generateId(), doc, this._ctx.id, key)); | ||
if (value instanceof AbstractCrdt) { | ||
ops.push(...value.serialize(this.id, key)); | ||
} | ||
else { | ||
createOp.data[key] = value; | ||
op.data[key] = value; | ||
} | ||
@@ -275,8 +322,42 @@ } | ||
} | ||
attachChild(key, child) { | ||
static deserialize([id, item], parentToChildren, doc) { | ||
if (item.type !== live_1.CrdtType.Object) { | ||
throw new Error(`Tried to deserialize a record but item type is "${item.type}"`); | ||
} | ||
const object = new LiveObject(item.data); | ||
object.attach(id, doc); | ||
const children = parentToChildren.get(id); | ||
if (children == null) { | ||
return object; | ||
} | ||
for (const entry of children) { | ||
const crdt = entry[1]; | ||
if (crdt.parentKey == null) { | ||
throw new Error("Tried to deserialize a crdt but it does not have a parentKey and is not the root"); | ||
} | ||
const child = deserialize(entry, parentToChildren, doc); | ||
child.setParent(object); | ||
object._map.set(crdt.parentKey, child); | ||
} | ||
return object; | ||
} | ||
attach(id, doc) { | ||
super.attach(id, doc); | ||
for (const [key, value] of this._map) { | ||
if (value instanceof AbstractCrdt) { | ||
value.attach(doc.generateId(), doc); | ||
} | ||
} | ||
} | ||
attachChild(id, key, child) { | ||
if (this.doc == null) { | ||
throw new Error("Can't attach child if doc is not present"); | ||
} | ||
const previousValue = this._map.get(key); | ||
if (isCrdt(previousValue)) { | ||
previousValue[INTERNAL].detach(); | ||
previousValue.detach(); | ||
} | ||
this._map.set(key, child); | ||
child.setParent(this); | ||
child.attach(id, this.doc); | ||
this.notify(); | ||
@@ -291,3 +372,3 @@ } | ||
if (child) { | ||
child[INTERNAL].detach(); | ||
child.detach(); | ||
} | ||
@@ -297,12 +378,12 @@ this.notify(); | ||
detach() { | ||
if (this._ctx == null) { | ||
return; | ||
} | ||
this._ctx.doc.deleteItem(this._ctx.id); | ||
super.detach(); | ||
for (const value of this._map.values()) { | ||
if (isCrdt(value)) { | ||
value[INTERNAL].detach(); | ||
value.detach(); | ||
} | ||
} | ||
} | ||
/** | ||
* INTERNAL | ||
*/ | ||
apply(op) { | ||
@@ -313,3 +394,3 @@ if (op.type === live_1.OpType.UpdateObject) { | ||
if (isCrdt(oldValue)) { | ||
oldValue[INTERNAL].detach(); | ||
oldValue.detach(); | ||
} | ||
@@ -325,3 +406,3 @@ const value = op.data[key]; | ||
if (isCrdt(oldValue)) { | ||
oldValue[INTERNAL].detach(); | ||
oldValue.detach(); | ||
} | ||
@@ -332,7 +413,2 @@ this._map.delete(key); | ||
} | ||
notify() { | ||
for (const listener of this._listeners) { | ||
listener(); | ||
} | ||
} | ||
toObject() { | ||
@@ -348,63 +424,70 @@ return Object.fromEntries(this._map); | ||
} | ||
delete(key) { | ||
if (this._ctx) { | ||
const ops = []; | ||
const item = this._map.get(key); | ||
if (isCrdt(item)) { | ||
item[INTERNAL].detach(); | ||
} | ||
this._ctx.doc.dispatch([ | ||
{ type: live_1.OpType.DeleteObjectKey, id: this._ctx.id, key: key }, | ||
]); | ||
} | ||
this._map.delete(key); | ||
this.notify(); | ||
} | ||
// delete<TKey extends keyof T>(key: TKey) { | ||
// if (this.doc && this.id) { | ||
// const item = this._map.get(key as string); | ||
// if (isCrdt(item)) { | ||
// item.detach(); | ||
// } | ||
// this.doc.dispatch([ | ||
// { type: OpType.DeleteObjectKey, id: this.id, key: key as string }, | ||
// ]); | ||
// } | ||
// this._map.delete(key as string); | ||
// this.notify(); | ||
// } | ||
update(overrides) { | ||
if (this._ctx) { | ||
if (this.doc && this.id) { | ||
const ops = []; | ||
const updateOperation = { | ||
id: this._ctx.id, | ||
const updateOp = { | ||
id: this.id, | ||
type: live_1.OpType.UpdateObject, | ||
data: {}, | ||
data: {} | ||
}; | ||
ops.push(updateOperation); | ||
ops.push(updateOp); | ||
for (const key in overrides) { | ||
const oldValue = this._map.get(key); | ||
if (isCrdt(oldValue)) { | ||
oldValue[INTERNAL].detach(); | ||
if (oldValue instanceof LiveObject) { | ||
oldValue.detach(); | ||
} | ||
const value = overrides[key]; | ||
if (isCrdt(value)) { | ||
ops.push(...value[INTERNAL].attach(this._ctx.doc.generateId(), this._ctx.doc, this._ctx.id, key)); | ||
const newValue = overrides[key]; | ||
if (newValue instanceof AbstractCrdt) { | ||
newValue.setParent(this); | ||
newValue.attach(this.doc.generateId(), this.doc); | ||
ops.push(...newValue.serialize(this.id, key)); | ||
} | ||
else { | ||
updateOperation.data[key] = value; | ||
updateOp.data[key] = newValue; | ||
} | ||
this._map.set(key, value); | ||
this._map.set(key, newValue); | ||
} | ||
this._ctx.doc.dispatch(ops); | ||
this.doc.dispatch(ops); | ||
this.notify(); | ||
return; | ||
} | ||
else { | ||
for (const key in overrides) { | ||
const value = overrides[key]; | ||
this._map.set(key, value); | ||
for (const key in overrides) { | ||
const oldValue = this._map.get(key); | ||
if (oldValue instanceof AbstractCrdt) { | ||
oldValue.detach(); | ||
} | ||
this.notify(); | ||
const newValue = overrides[key]; | ||
if (newValue instanceof AbstractCrdt) { | ||
newValue.setParent(this); | ||
} | ||
this._map.set(key, newValue); | ||
} | ||
this.notify(); | ||
} | ||
subscribe(listener) { | ||
this._listeners.push(listener); | ||
} | ||
unsubscribe(listener) { | ||
utils_1.remove(this._listeners, listener); | ||
} | ||
} | ||
exports.LiveObject = LiveObject; | ||
class LiveMap { | ||
class LiveMap extends AbstractCrdt { | ||
constructor(entries) { | ||
this._listeners = []; | ||
super(); | ||
if (entries) { | ||
this._map = new Map(entries.map((entry) => [entry[0], selfOrRegister(entry[1])])); | ||
const mappedEntries = []; | ||
for (const entry of entries) { | ||
const value = selfOrRegister(entry[1]); | ||
value.setParent(this); | ||
mappedEntries.push([entry[0], value]); | ||
} | ||
this._map = new Map(mappedEntries); | ||
} | ||
@@ -415,2 +498,22 @@ else { | ||
} | ||
serialize(parentId, parentKey) { | ||
if (this.id == null) { | ||
throw new Error("Cannot serialize item is not attached"); | ||
} | ||
if (parentId == null || parentKey == null) { | ||
throw new Error("Cannot serialize map if parentId or parentKey is undefined"); | ||
} | ||
const ops = []; | ||
const op = { | ||
id: this.id, | ||
type: live_1.OpType.CreateMap, | ||
parentId, | ||
parentKey, | ||
}; | ||
ops.push(op); | ||
for (const [key, value] of this._map) { | ||
ops.push(...value.serialize(this.id, key)); | ||
} | ||
return ops; | ||
} | ||
static deserialize([id, item], parentToChildren, doc) { | ||
@@ -421,3 +524,3 @@ if (item.type !== live_1.CrdtType.Map) { | ||
const map = new LiveMap(); | ||
map.attach(id, doc, item.parentId, item.parentKey); | ||
map.attach(id, doc); | ||
const children = parentToChildren.get(id); | ||
@@ -433,2 +536,3 @@ if (children == null) { | ||
const child = deserialize(entry, parentToChildren, doc); | ||
child.setParent(map); | ||
map._map.set(crdt.parentKey, child); | ||
@@ -438,53 +542,21 @@ } | ||
} | ||
get [INTERNAL]() { | ||
return { | ||
ctx: this._ctx, | ||
apply: this.apply.bind(this), | ||
attachChild: this.attachChild.bind(this), | ||
attach: this.attach.bind(this), | ||
detach: this.detach.bind(this), | ||
detachChild: this.detachChild.bind(this), | ||
getParentId: this._getParentId.bind(this), | ||
getId: this._getId.bind(this), | ||
}; | ||
} | ||
_getParentId() { | ||
var _a; | ||
return (_a = this._ctx) === null || _a === void 0 ? void 0 : _a.parentId; | ||
} | ||
_getId() { | ||
var _a; | ||
return (_a = this._ctx) === null || _a === void 0 ? void 0 : _a.id; | ||
} | ||
apply(op) { } | ||
attach(id, doc, parentId, parentKey) { | ||
if (this._ctx) { | ||
throw new Error("LiveMap is already part of the storage"); | ||
} | ||
doc.addItem(id, this); | ||
this._ctx = { | ||
id, | ||
doc: doc, | ||
parentId, | ||
}; | ||
const ops = []; | ||
const createOp = { | ||
id: this._ctx.id, | ||
type: live_1.OpType.CreateMap, | ||
parentId, | ||
parentKey, | ||
}; | ||
ops.push(createOp); | ||
attach(id, doc) { | ||
super.attach(id, doc); | ||
for (const [key, value] of this._map) { | ||
if (isCrdt(value)) { | ||
ops.push(...value[INTERNAL].attach(doc.generateId(), doc, this._ctx.id, key)); | ||
value.attach(doc.generateId(), doc); | ||
} | ||
} | ||
return ops; | ||
} | ||
attachChild(key, child) { | ||
attachChild(id, key, child) { | ||
if (this.doc == null) { | ||
throw new Error("Can't attach child if doc is not present"); | ||
} | ||
const previousValue = this._map.get(key); | ||
if (previousValue) { | ||
previousValue[INTERNAL].detach(); | ||
previousValue.detach(); | ||
} | ||
child.setParent(this); | ||
child.attach(id, this.doc); | ||
this._map.set(key, child); | ||
@@ -494,9 +566,6 @@ this.notify(); | ||
detach() { | ||
if (this._ctx == null) { | ||
return; | ||
} | ||
super.detach(); | ||
for (const item of this._map.values()) { | ||
item[INTERNAL].detach(); | ||
item.detach(); | ||
} | ||
this._ctx.doc.deleteItem(this._ctx.id); | ||
} | ||
@@ -509,3 +578,3 @@ detachChild(child) { | ||
} | ||
child[INTERNAL].detach(); | ||
child.detach(); | ||
this.notify(); | ||
@@ -521,19 +590,15 @@ } | ||
set(key, value) { | ||
if (this._ctx) { | ||
const ops = []; | ||
const oldValue = this._map.get(key); | ||
if (oldValue) { | ||
oldValue[INTERNAL].detach(); | ||
} | ||
const item = selfOrRegister(value); | ||
ops.push(...item[INTERNAL].attach(this._ctx.doc.generateId(), this._ctx.doc, this._ctx.id, key)); | ||
this._map.set(key, item); | ||
this._ctx.doc.dispatch(ops); | ||
this.notify(); | ||
const oldValue = this._map.get(key); | ||
if (oldValue) { | ||
oldValue.detach(); | ||
} | ||
else { | ||
const item = selfOrRegister(value); | ||
this._map.set(key, item); | ||
this.notify(); | ||
const item = selfOrRegister(value); | ||
item.setParent(this); | ||
this._map.set(key, item); | ||
if (this.doc && this.id) { | ||
item.attach(this.doc.generateId(), this.doc); | ||
const ops = item.serialize(this.id, key); | ||
this.doc.dispatch(ops); | ||
} | ||
this.notify(); | ||
} | ||
@@ -547,17 +612,13 @@ get size() { | ||
delete(key) { | ||
if (this._ctx) { | ||
const item = this._map.get(key); | ||
if (item) { | ||
const itemId = item[INTERNAL].getId(); | ||
if (itemId != null) { | ||
item[INTERNAL].detach(); | ||
this._ctx.doc.dispatch([{ type: live_1.OpType.DeleteCrdt, id: itemId }]); | ||
} | ||
} | ||
const item = this._map.get(key); | ||
if (item == null) { | ||
return false; | ||
} | ||
const isDeleted = this._map.delete(key); | ||
if (isDeleted) { | ||
this.notify(); | ||
item.detach(); | ||
if (this.doc && item.id) { | ||
this.doc.dispatch([{ type: live_1.OpType.DeleteCrdt, id: item.id }]); | ||
} | ||
return isDeleted; | ||
this._map.delete(key); | ||
this.notify(); | ||
return true; | ||
} | ||
@@ -616,36 +677,7 @@ entries() { | ||
} | ||
subscribe(listener) { | ||
this._listeners.push(listener); | ||
} | ||
unsubscribe(listener) { | ||
utils_1.remove(this._listeners, listener); | ||
} | ||
notify() { | ||
for (const listener of this._listeners) { | ||
listener(); | ||
} | ||
} | ||
} | ||
exports.LiveMap = LiveMap; | ||
function selfOrRegisterValue(obj) { | ||
if (obj instanceof LiveRegister) { | ||
return obj.data; | ||
} | ||
return obj; | ||
} | ||
function selfOrRegister(obj) { | ||
if (obj instanceof LiveObject || | ||
obj instanceof LiveMap || | ||
obj instanceof LiveList) { | ||
return obj; | ||
} | ||
else if (obj instanceof LiveRegister) { | ||
throw new Error("Internal error. LiveRegister should not be created from LiveRegister"); | ||
} | ||
else { | ||
return new LiveRegister(obj); | ||
} | ||
} | ||
class LiveRegister { | ||
class LiveRegister extends AbstractCrdt { | ||
constructor(data) { | ||
super(); | ||
this._data = data; | ||
@@ -661,60 +693,21 @@ } | ||
const register = new LiveRegister(item.data); | ||
register.attach(id, doc, item.parentId, item.parentKey); | ||
register.attach(id, doc); | ||
return register; | ||
} | ||
get [INTERNAL]() { | ||
return { | ||
ctx: this._ctx, | ||
attach: this.attach.bind(this), | ||
detach: this.detach.bind(this), | ||
attachChild: this.attachChild.bind(this), | ||
detachChild: this.detachChild.bind(this), | ||
getParentId: this._getParentId.bind(this), | ||
getId: this._getId.bind(this), | ||
}; | ||
} | ||
_getParentId() { | ||
var _a; | ||
return (_a = this._ctx) === null || _a === void 0 ? void 0 : _a.parentId; | ||
} | ||
_getId() { | ||
var _a; | ||
return (_a = this._ctx) === null || _a === void 0 ? void 0 : _a.id; | ||
} | ||
detachChild(crdt) { | ||
throw new Error("Internal error: cannot detach CRDT on register"); | ||
} | ||
detach() { | ||
if (this._ctx) { | ||
this._ctx.doc.deleteItem(this._ctx.id); | ||
serialize(parentId, parentKey) { | ||
if (this.id == null || parentId == null || parentKey == null) { | ||
throw new Error("Cannot serialize register if parentId or parentKey is undefined"); | ||
} | ||
return [{ | ||
type: live_1.OpType.CreateRegister, | ||
id: this.id, | ||
parentId, | ||
parentKey, | ||
data: this.data | ||
}]; | ||
} | ||
attach(id, doc, parentId, parentKey) { | ||
if (this._ctx) { | ||
throw new Error("LiveRegister is already part of the storage"); | ||
} | ||
doc.addItem(id, this); | ||
this._ctx = { | ||
id, | ||
doc: doc, | ||
parentId, | ||
}; | ||
const ops = []; | ||
const createOp = { | ||
id, | ||
type: live_1.OpType.CreateRegister, | ||
parentId, | ||
parentKey, | ||
data: this._data, | ||
}; | ||
ops.push(createOp); | ||
return ops; | ||
} | ||
attachChild(key, child) { | ||
throw new Error("Cannot attach child to register"); | ||
} | ||
} | ||
class LiveList { | ||
class LiveList extends AbstractCrdt { | ||
constructor(items = []) { | ||
this._listeners = []; | ||
super(); | ||
// TODO: Naive array at first, find a better data structure | ||
@@ -732,3 +725,3 @@ this._items = []; | ||
const list = new LiveList([]); | ||
list.attach(id, doc, item.parentId, item.parentKey); | ||
list.attach(id, doc); | ||
const children = parentToChildren.get(id); | ||
@@ -740,40 +733,18 @@ if (children == null) { | ||
const child = deserialize(entry, parentToChildren, doc); | ||
list.attachChild(entry[1].parentKey, child); | ||
child.setParent(list); | ||
list._items.push([child, entry[1].parentKey]); | ||
list._items.sort((itemA, itemB) => position_1.compare({ position: itemA[1] }, { position: itemB[1] })); | ||
} | ||
return list; | ||
} | ||
get [INTERNAL]() { | ||
return { | ||
ctx: this._ctx, | ||
attachChild: this.attachChild.bind(this), | ||
detachChild: this.detachChild.bind(this), | ||
attach: this.attach.bind(this), | ||
detach: this.detach.bind(this), | ||
apply: this.apply.bind(this), | ||
setChildKey: this.setChildKey.bind(this), | ||
getParentId: this._getParentId.bind(this), | ||
getId: this._getId.bind(this), | ||
}; | ||
} | ||
_getParentId() { | ||
var _a; | ||
return (_a = this._ctx) === null || _a === void 0 ? void 0 : _a.parentId; | ||
} | ||
_getId() { | ||
var _a; | ||
return (_a = this._ctx) === null || _a === void 0 ? void 0 : _a.id; | ||
} | ||
attach(id, doc, parentId, parentKey) { | ||
if (this._ctx) { | ||
throw new Error("LiveList is already part of the storage"); | ||
serialize(parentId, parentKey) { | ||
if (this.id == null) { | ||
throw new Error("Cannot serialize item is not attached"); | ||
} | ||
doc.addItem(id, this); | ||
this._ctx = { | ||
doc: doc, | ||
id: id, | ||
parentId: parentId, | ||
}; | ||
if (parentId == null || parentKey == null) { | ||
throw new Error("Cannot serialize list if parentId or parentKey is undefined"); | ||
} | ||
const ops = []; | ||
const createOp = { | ||
id: this._ctx.id, | ||
const op = { | ||
id: this.id, | ||
type: live_1.OpType.CreateList, | ||
@@ -783,18 +754,26 @@ parentId, | ||
}; | ||
ops.push(createOp); | ||
for (const [item, position] of this._items) { | ||
ops.push(...item[INTERNAL].attach(doc.generateId(), doc, this._ctx.id, position)); | ||
ops.push(op); | ||
for (const [value, key] of this._items) { | ||
ops.push(...value.serialize(this.id, key)); | ||
} | ||
return ops; | ||
} | ||
attach(id, doc) { | ||
super.attach(id, doc); | ||
for (const [item, position] of this._items) { | ||
item.attach(doc.generateId(), doc); | ||
} | ||
} | ||
detach() { | ||
if (this._ctx == null) { | ||
return; | ||
} | ||
super.detach(); | ||
for (const [value] of this._items) { | ||
value[INTERNAL].detach(); | ||
value.detach(); | ||
} | ||
this._ctx.doc.deleteItem(this._ctx.id); | ||
} | ||
attachChild(key, child) { | ||
attachChild(id, key, child) { | ||
if (this.doc == null) { | ||
throw new Error("Can't attach child if doc is not present"); | ||
} | ||
child.attach(id, this.doc); | ||
child.setParent(this); | ||
// TODO: Handle list conflict | ||
@@ -809,3 +788,3 @@ this._items.push([child, key]); | ||
if (child) { | ||
child[INTERNAL].detach(); | ||
child.detach(); | ||
} | ||
@@ -823,7 +802,2 @@ this.notify(); | ||
apply(op) { } | ||
notify() { | ||
for (const listener of this._listeners) { | ||
listener(); | ||
} | ||
} | ||
push(item) { | ||
@@ -834,7 +808,8 @@ const position = this._items.length === 0 | ||
const value = selfOrRegister(item); | ||
value.setParent(this); | ||
this._items.push([value, position]); | ||
this.notify(); | ||
if (this._ctx) { | ||
const ops = value[INTERNAL].attach(this._ctx.doc.generateId(), this._ctx.doc, this._ctx.id, position); | ||
this._ctx.doc.dispatch(ops); | ||
if (this.doc && this.id) { | ||
value.attach(this.doc.generateId(), this.doc); | ||
this.doc.dispatch(value.serialize(this.id, position)); | ||
} | ||
@@ -850,8 +825,9 @@ } | ||
const value = selfOrRegister(item); | ||
value.setParent(this); | ||
this._items.push([value, position]); | ||
this._items.sort((itemA, itemB) => position_1.compare({ position: itemA[1] }, { position: itemB[1] })); | ||
this.notify(); | ||
if (this._ctx) { | ||
const ops = value[INTERNAL].attach(this._ctx.doc.generateId(), this._ctx.doc, this._ctx.id, position); | ||
this._ctx.doc.dispatch(ops); | ||
if (this.doc && this.id) { | ||
value.attach(this.doc.generateId(), this.doc); | ||
this.doc.dispatch(value.serialize(this.id, position)); | ||
} | ||
@@ -891,14 +867,8 @@ } | ||
this.notify(); | ||
if (this._ctx) { | ||
const id = item[0][INTERNAL].getId(); | ||
if (id == null) { | ||
throw new Error("Internal error. Cannot set parent key from "); | ||
} | ||
this._ctx.doc.dispatch([ | ||
{ | ||
if (this.doc && this.id) { | ||
this.doc.dispatch([{ | ||
type: live_1.OpType.SetParentKey, | ||
id: id, | ||
id: item[0].id, | ||
parentKey: position, | ||
}, | ||
]); | ||
},]); | ||
} | ||
@@ -911,12 +881,14 @@ } | ||
const item = this._items[index]; | ||
item[0].detach(); | ||
this._items.splice(index, 1); | ||
if (this._ctx) { | ||
const childRecord = item[0]; | ||
this._ctx.doc.dispatch([ | ||
{ | ||
id: childRecord[INTERNAL].ctx.id, | ||
type: live_1.OpType.DeleteCrdt, | ||
}, | ||
]); | ||
childRecord[INTERNAL].detach(); | ||
if (this.doc) { | ||
const childRecordId = item[0].id; | ||
if (childRecordId) { | ||
this.doc.dispatch([ | ||
{ | ||
id: childRecordId, | ||
type: live_1.OpType.DeleteCrdt, | ||
}, | ||
]); | ||
} | ||
} | ||
@@ -931,8 +903,2 @@ this.notify(); | ||
} | ||
subscribe(listener) { | ||
this._listeners.push(listener); | ||
} | ||
unsubscribe(listener) { | ||
utils_1.remove(this._listeners, listener); | ||
} | ||
} | ||
@@ -965,1 +931,20 @@ exports.LiveList = LiveList; | ||
} | ||
function selfOrRegisterValue(obj) { | ||
if (obj instanceof LiveRegister) { | ||
return obj.data; | ||
} | ||
return obj; | ||
} | ||
function selfOrRegister(obj) { | ||
if (obj instanceof LiveObject || | ||
obj instanceof LiveMap || | ||
obj instanceof LiveList) { | ||
return obj; | ||
} | ||
else if (obj instanceof LiveRegister) { | ||
throw new Error("Internal error. LiveRegister should not be created from selfOrRegister"); | ||
} | ||
else { | ||
return new LiveRegister(obj); | ||
} | ||
} |
import { Op, SerializedCrdtWithId, SerializedList } from "./live"; | ||
declare const INTERNAL: unique symbol; | ||
declare type Dispatch = (ops: Op[]) => void; | ||
interface ICrdt { | ||
readonly [INTERNAL]: { | ||
getId(): string | undefined; | ||
getParentId(): string | undefined; | ||
attach(id: string, doc: Doc, parentId?: string, parentKey?: string): Op[]; | ||
attachChild(key: any, child: ICrdt): void; | ||
detach(): void; | ||
detachChild(child: ICrdt): void; | ||
}; | ||
} | ||
export declare class Doc<T extends Record<string, any> = Record<string, any>> { | ||
@@ -24,4 +13,5 @@ private _root; | ||
dispatch(ops: Op[]): void; | ||
addItem(id: string, item: ICrdt): void; | ||
addItem(id: string, item: AbstractCrdt): void; | ||
deleteItem(id: string): void; | ||
getItem(id: string): AbstractCrdt | undefined; | ||
apply(op: Op): void; | ||
@@ -40,67 +30,54 @@ private applyCreateRegister; | ||
} | ||
export declare class LiveObject<T extends Record<string, any> = Record<string, any>> { | ||
declare class AbstractCrdt { | ||
#private; | ||
private _listeners; | ||
private _deepListeners; | ||
protected get doc(): Doc<Record<string, any>> | undefined; | ||
get id(): string | undefined; | ||
get parent(): AbstractCrdt | undefined; | ||
setParent(parent: AbstractCrdt): void; | ||
attach(id: string, doc: Doc): void; | ||
attachChild(id: string, key: string, crdt: AbstractCrdt): void; | ||
detach(): void; | ||
detachChild(crdt: AbstractCrdt): void; | ||
subscribe(listener: () => void): void; | ||
subscribeDeep(listener: () => void): void; | ||
unsubscribe(listener: () => void): void; | ||
unsubscribeDeep(listener: () => void): void; | ||
notify(onlyDeep?: boolean): void; | ||
serialize(parentId: string, parentKey: string): Op[]; | ||
} | ||
export declare class LiveObject<T extends Record<string, any> = Record<string, any>> extends AbstractCrdt { | ||
private _map; | ||
private _listeners; | ||
private _ctx?; | ||
constructor(object?: T); | ||
/** | ||
* INTERNAL | ||
*/ | ||
serialize(parentId?: string, parentKey?: string): Op[]; | ||
static deserialize([id, item]: SerializedCrdtWithId, parentToChildren: Map<string, SerializedCrdtWithId[]>, doc: Doc): LiveObject<{ | ||
[key: string]: any; | ||
}>; | ||
get [INTERNAL](): { | ||
ctx: { | ||
id: string; | ||
doc: Doc<Record<string, any>>; | ||
parentId?: string | undefined; | ||
} | undefined; | ||
attachChild: (key: keyof T, child: ICrdt) => void; | ||
detachChild: (child: ICrdt) => void; | ||
detach: () => void; | ||
attach: (id: string, doc: Doc<Record<string, any>>, parentId?: string | undefined, parentKey?: string | undefined) => Op[]; | ||
apply: (op: Op) => void; | ||
getParentId: () => string | undefined; | ||
getId: () => string | undefined; | ||
}; | ||
private _getParentId; | ||
private _getId; | ||
private attach; | ||
private attachChild; | ||
private detachChild; | ||
private detach; | ||
private apply; | ||
private notify; | ||
attach(id: string, doc: Doc): void; | ||
attachChild(id: string, key: keyof T, child: AbstractCrdt): void; | ||
detachChild(child: AbstractCrdt): void; | ||
detach(): void; | ||
/** | ||
* INTERNAL | ||
*/ | ||
apply(op: Op): void; | ||
toObject(): T; | ||
set<TKey extends keyof T>(key: TKey, value: T[TKey]): void; | ||
get<TKey extends keyof T>(key: TKey): T[TKey]; | ||
delete<TKey extends keyof T>(key: TKey): void; | ||
update(overrides: Partial<T>): void; | ||
subscribe(listener: () => void): void; | ||
unsubscribe(listener: () => void): void; | ||
} | ||
export declare class LiveMap<TKey extends string, TValue> implements ICrdt { | ||
private _listeners; | ||
export declare class LiveMap<TKey extends string, TValue> extends AbstractCrdt { | ||
private _map; | ||
private _ctx?; | ||
constructor(entries?: readonly (readonly [TKey, TValue])[] | null | undefined); | ||
serialize(parentId?: string, parentKey?: string): Op[]; | ||
static deserialize([id, item]: SerializedCrdtWithId, parentToChildren: Map<string, SerializedCrdtWithId[]>, doc: Doc): LiveMap<string, unknown>; | ||
get [INTERNAL](): { | ||
ctx: { | ||
id: string; | ||
doc: Doc<Record<string, any>>; | ||
parentId?: string | undefined; | ||
} | undefined; | ||
apply: (op: Op) => void; | ||
attachChild: (key: TKey, child: ICrdt) => void; | ||
attach: (id: string, doc: Doc<Record<string, any>>, parentId: string, parentKey: string) => Op[]; | ||
detach: () => void; | ||
detachChild: (child: ICrdt) => void; | ||
getParentId: () => string | undefined; | ||
getId: () => string | undefined; | ||
}; | ||
private _getParentId; | ||
private _getId; | ||
private apply; | ||
private attach; | ||
private attachChild; | ||
private detach; | ||
private detachChild; | ||
attach(id: string, doc: Doc): void; | ||
attachChild(id: string, key: TKey, child: AbstractCrdt): void; | ||
detach(): void; | ||
detachChild(child: AbstractCrdt): void; | ||
get(key: TKey): TValue | undefined; | ||
@@ -116,36 +93,14 @@ set(key: TKey, value: TValue): void; | ||
forEach(callback: (value: TValue, key: TKey, map: LiveMap<TKey, TValue>) => void): void; | ||
subscribe(listener: () => void): void; | ||
unsubscribe(listener: () => void): void; | ||
private notify; | ||
} | ||
export declare class LiveList<T> implements ICrdt { | ||
private _listeners; | ||
private _ctx?; | ||
export declare class LiveList<T> extends AbstractCrdt { | ||
private _items; | ||
constructor(items?: T[]); | ||
static deserialize([id, item]: [id: string, item: SerializedList], parentToChildren: Map<string, SerializedCrdtWithId[]>, doc: Doc): LiveList<never>; | ||
get [INTERNAL](): { | ||
ctx: { | ||
id: string; | ||
parentId: string; | ||
doc: Doc<Record<string, any>>; | ||
} | undefined; | ||
attachChild: (key: string, child: ICrdt) => void; | ||
detachChild: (child: ICrdt) => void; | ||
attach: (id: string, doc: Doc<Record<string, any>>, parentId: string, parentKey: string) => Op[]; | ||
detach: () => void; | ||
apply: (op: Op) => void; | ||
setChildKey: (key: string, child: ICrdt) => void; | ||
getParentId: () => string | undefined; | ||
getId: () => string | undefined; | ||
}; | ||
private _getParentId; | ||
private _getId; | ||
private attach; | ||
private detach; | ||
private attachChild; | ||
private detachChild; | ||
private setChildKey; | ||
serialize(parentId?: string, parentKey?: string): Op[]; | ||
attach(id: string, doc: Doc): void; | ||
detach(): void; | ||
attachChild(id: string, key: string, child: AbstractCrdt): void; | ||
detachChild(child: AbstractCrdt): void; | ||
setChildKey(key: string, child: AbstractCrdt): void; | ||
private apply; | ||
private notify; | ||
push(item: T): void; | ||
@@ -157,5 +112,3 @@ insert(item: T, index: number): void; | ||
get(index: number): T; | ||
subscribe(listener: () => void): void; | ||
unsubscribe(listener: () => void): void; | ||
} | ||
export {}; |
@@ -0,1 +1,15 @@ | ||
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, privateMap) { | ||
if (!privateMap.has(receiver)) { | ||
throw new TypeError("attempted to get private field on non-instance"); | ||
} | ||
return privateMap.get(receiver); | ||
}; | ||
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, privateMap, value) { | ||
if (!privateMap.has(receiver)) { | ||
throw new TypeError("attempted to set private field on non-instance"); | ||
} | ||
privateMap.set(receiver, value); | ||
return value; | ||
}; | ||
var _parent, _doc, _id; | ||
import { remove } from "./utils"; | ||
@@ -17,4 +31,4 @@ import { CrdtType, OpType, } from "./live"; | ||
const storage = new Doc(rootRecord, actor, dispatch); | ||
const ops = rootRecord[INTERNAL].attach(storage.generateId(), storage); | ||
storage.dispatch(ops); | ||
rootRecord.attach(storage.generateId(), storage); | ||
storage.dispatch(rootRecord.serialize()); | ||
return storage; | ||
@@ -59,2 +73,5 @@ } | ||
} | ||
getItem(id) { | ||
return this._items.get(id); | ||
} | ||
apply(op) { | ||
@@ -97,4 +114,2 @@ switch (op.type) { | ||
applyCreateRegister(op) { | ||
const newRegister = new LiveRegister(op.data); | ||
newRegister[INTERNAL].attach(op.id, this, op.parentId, op.parentKey); | ||
const parent = this._items.get(op.parentId); | ||
@@ -107,3 +122,4 @@ if (parent == null) { | ||
} | ||
parent[INTERNAL].attachChild(op.parentKey, newRegister); | ||
const newRegister = new LiveRegister(op.data); | ||
parent.attachChild(op.id, op.parentKey, newRegister); | ||
} | ||
@@ -113,3 +129,3 @@ applyDeleteRecordKey(op) { | ||
if (item && item instanceof LiveObject) { | ||
item[INTERNAL].apply(op); | ||
item.apply(op); | ||
} | ||
@@ -120,3 +136,3 @@ } | ||
if (item && item instanceof LiveObject) { | ||
item[INTERNAL].apply(op); | ||
item.apply(op); | ||
} | ||
@@ -130,4 +146,3 @@ } | ||
const newMap = new LiveMap(); | ||
newMap[INTERNAL].attach(op.id, this, op.parentId, op.parentKey); | ||
parent[INTERNAL].attachChild(op.parentKey, newMap); | ||
parent.attachChild(op.id, op.parentKey, newMap); | ||
} | ||
@@ -139,9 +154,6 @@ applyCreateList(op) { | ||
} | ||
const newMap = new LiveList(); | ||
newMap[INTERNAL].attach(op.id, this, op.parentId, op.parentKey); | ||
parent[INTERNAL].attachChild(op.parentKey, newMap); | ||
const list = new LiveList(); | ||
parent.attachChild(op.id, op.parentKey, list); | ||
} | ||
applyCreateObject(op) { | ||
const newObj = new LiveObject(op.data); | ||
newObj[INTERNAL].attach(op.id, this, op.parentId, op.parentKey); | ||
if (op.parentId && op.parentKey) { | ||
@@ -152,3 +164,4 @@ const parent = this._items.get(op.parentId); | ||
} | ||
parent[INTERNAL].attachChild(op.parentKey, newObj); | ||
const newObj = new LiveObject(op.data); | ||
parent.attachChild(op.id, op.parentKey, newObj); | ||
} | ||
@@ -161,9 +174,8 @@ } | ||
} | ||
const parentId = item[INTERNAL].getParentId(); | ||
if (parentId == null) { | ||
const parent = item.parent; | ||
if (parent == null) { | ||
return; | ||
} | ||
const parent = this._items.get(parentId); | ||
if (parent) { | ||
parent[INTERNAL].detachChild(item); | ||
parent.detachChild(item); | ||
} | ||
@@ -176,9 +188,7 @@ } | ||
} | ||
const parentId = item[INTERNAL].getParentId(); | ||
if (parentId == null) { | ||
if (item.parent == null) { | ||
return; | ||
} | ||
const parent = this._items.get(parentId); | ||
if (parent && parent instanceof LiveList) { | ||
parent[INTERNAL].setChildKey(op.parentKey, item); | ||
if (item.parent instanceof LiveList) { | ||
item.parent.setChildKey(op.parentKey, item); | ||
} | ||
@@ -196,72 +206,109 @@ } | ||
} | ||
export class LiveObject { | ||
constructor(object = {}) { | ||
class AbstractCrdt { | ||
constructor() { | ||
this._listeners = []; | ||
this._map = new Map(Object.entries(object)); | ||
this._deepListeners = []; | ||
_parent.set(this, void 0); | ||
_doc.set(this, void 0); | ||
_id.set(this, void 0); | ||
} | ||
static deserialize([id, item], parentToChildren, doc) { | ||
if (item.type !== CrdtType.Object) { | ||
throw new Error(`Tried to deserialize a record but item type is "${item.type}"`); | ||
get doc() { | ||
return __classPrivateFieldGet(this, _doc); | ||
} | ||
get id() { | ||
return __classPrivateFieldGet(this, _id); | ||
} | ||
get parent() { | ||
return __classPrivateFieldGet(this, _parent); | ||
} | ||
setParent(parent) { | ||
if (__classPrivateFieldGet(this, _parent)) { | ||
throw new Error("Cannot attach parent if it already exist"); | ||
} | ||
const record = new LiveObject(item.data); | ||
record.attach(id, doc, item.parentId, item.parentKey); | ||
const children = parentToChildren.get(id); | ||
if (children == null) { | ||
return record; | ||
__classPrivateFieldSet(this, _parent, parent); | ||
} | ||
attach(id, doc) { | ||
if (__classPrivateFieldGet(this, _id) || __classPrivateFieldGet(this, _doc)) { | ||
throw new Error("Cannot attach if CRDT is already attached"); | ||
} | ||
for (const entry of children) { | ||
const crdt = entry[1]; | ||
if (crdt.parentKey == null) { | ||
throw new Error("Tried to deserialize a crdt but it does not have a parentKey and is not the root"); | ||
} | ||
const child = deserialize(entry, parentToChildren, doc); | ||
record._map.set(crdt.parentKey, child); | ||
doc.addItem(id, this); | ||
__classPrivateFieldSet(this, _id, id); | ||
__classPrivateFieldSet(this, _doc, doc); | ||
} | ||
attachChild(id, key, crdt) { | ||
throw new Error("attachChild should be implement by a non abstract CRDT"); | ||
} | ||
detach() { | ||
if (__classPrivateFieldGet(this, _doc) && __classPrivateFieldGet(this, _id)) { | ||
__classPrivateFieldGet(this, _doc).deleteItem(__classPrivateFieldGet(this, _id)); | ||
} | ||
return record; | ||
__classPrivateFieldSet(this, _parent, undefined); | ||
__classPrivateFieldSet(this, _doc, undefined); | ||
} | ||
get [INTERNAL]() { | ||
return { | ||
ctx: this._ctx, | ||
attachChild: this.attachChild.bind(this), | ||
detachChild: this.detachChild.bind(this), | ||
detach: this.detach.bind(this), | ||
attach: this.attach.bind(this), | ||
apply: this.apply.bind(this), | ||
getParentId: this._getParentId.bind(this), | ||
getId: this._getId.bind(this), | ||
}; | ||
detachChild(crdt) { | ||
throw new Error("detach child should be implement by a non abstract CRDT"); | ||
} | ||
_getParentId() { | ||
var _a; | ||
return (_a = this._ctx) === null || _a === void 0 ? void 0 : _a.parentId; | ||
subscribe(listener) { | ||
this._listeners.push(listener); | ||
} | ||
_getId() { | ||
var _a; | ||
return (_a = this._ctx) === null || _a === void 0 ? void 0 : _a.id; | ||
subscribeDeep(listener) { | ||
this._deepListeners.push(listener); | ||
} | ||
attach(id, doc, parentId, parentKey) { | ||
if (this._ctx) { | ||
throw new Error("LiveObject is already part of the storage"); | ||
unsubscribe(listener) { | ||
remove(this._listeners, listener); | ||
} | ||
unsubscribeDeep(listener) { | ||
remove(this._deepListeners, listener); | ||
} | ||
notify(onlyDeep = false) { | ||
if (onlyDeep === false) { | ||
for (const listener of this._listeners) { | ||
listener(); | ||
} | ||
} | ||
doc.addItem(id, this); | ||
this._ctx = { | ||
id, | ||
doc: doc, | ||
parentId, | ||
}; | ||
for (const listener of this._deepListeners) { | ||
listener(); | ||
} | ||
if (this.parent) { | ||
this.parent.notify(true); | ||
} | ||
} | ||
serialize(parentId, parentKey) { | ||
throw new Error("serialize should be implement by a non abstract CRDT"); | ||
} | ||
} | ||
_parent = new WeakMap(), _doc = new WeakMap(), _id = new WeakMap(); | ||
export class LiveObject extends AbstractCrdt { | ||
constructor(object = {}) { | ||
super(); | ||
for (const key in object) { | ||
const value = object[key]; | ||
if (value instanceof AbstractCrdt) { | ||
value.setParent(this); | ||
} | ||
} | ||
this._map = new Map(Object.entries(object)); | ||
} | ||
/** | ||
* INTERNAL | ||
*/ | ||
serialize(parentId, parentKey) { | ||
if (this.id == null) { | ||
throw new Error("Cannot serialize item is not attached"); | ||
} | ||
const ops = []; | ||
const createOp = { | ||
id: this._ctx.id, | ||
const op = { | ||
id: this.id, | ||
type: OpType.CreateObject, | ||
parentId, | ||
parentKey, | ||
data: {}, | ||
data: {} | ||
}; | ||
ops.push(createOp); | ||
ops.push(op); | ||
for (const [key, value] of this._map) { | ||
if (isCrdt(value)) { | ||
ops.push(...value[INTERNAL].attach(doc.generateId(), doc, this._ctx.id, key)); | ||
if (value instanceof AbstractCrdt) { | ||
ops.push(...value.serialize(this.id, key)); | ||
} | ||
else { | ||
createOp.data[key] = value; | ||
op.data[key] = value; | ||
} | ||
@@ -271,8 +318,42 @@ } | ||
} | ||
attachChild(key, child) { | ||
static deserialize([id, item], parentToChildren, doc) { | ||
if (item.type !== CrdtType.Object) { | ||
throw new Error(`Tried to deserialize a record but item type is "${item.type}"`); | ||
} | ||
const object = new LiveObject(item.data); | ||
object.attach(id, doc); | ||
const children = parentToChildren.get(id); | ||
if (children == null) { | ||
return object; | ||
} | ||
for (const entry of children) { | ||
const crdt = entry[1]; | ||
if (crdt.parentKey == null) { | ||
throw new Error("Tried to deserialize a crdt but it does not have a parentKey and is not the root"); | ||
} | ||
const child = deserialize(entry, parentToChildren, doc); | ||
child.setParent(object); | ||
object._map.set(crdt.parentKey, child); | ||
} | ||
return object; | ||
} | ||
attach(id, doc) { | ||
super.attach(id, doc); | ||
for (const [key, value] of this._map) { | ||
if (value instanceof AbstractCrdt) { | ||
value.attach(doc.generateId(), doc); | ||
} | ||
} | ||
} | ||
attachChild(id, key, child) { | ||
if (this.doc == null) { | ||
throw new Error("Can't attach child if doc is not present"); | ||
} | ||
const previousValue = this._map.get(key); | ||
if (isCrdt(previousValue)) { | ||
previousValue[INTERNAL].detach(); | ||
previousValue.detach(); | ||
} | ||
this._map.set(key, child); | ||
child.setParent(this); | ||
child.attach(id, this.doc); | ||
this.notify(); | ||
@@ -287,3 +368,3 @@ } | ||
if (child) { | ||
child[INTERNAL].detach(); | ||
child.detach(); | ||
} | ||
@@ -293,12 +374,12 @@ this.notify(); | ||
detach() { | ||
if (this._ctx == null) { | ||
return; | ||
} | ||
this._ctx.doc.deleteItem(this._ctx.id); | ||
super.detach(); | ||
for (const value of this._map.values()) { | ||
if (isCrdt(value)) { | ||
value[INTERNAL].detach(); | ||
value.detach(); | ||
} | ||
} | ||
} | ||
/** | ||
* INTERNAL | ||
*/ | ||
apply(op) { | ||
@@ -309,3 +390,3 @@ if (op.type === OpType.UpdateObject) { | ||
if (isCrdt(oldValue)) { | ||
oldValue[INTERNAL].detach(); | ||
oldValue.detach(); | ||
} | ||
@@ -321,3 +402,3 @@ const value = op.data[key]; | ||
if (isCrdt(oldValue)) { | ||
oldValue[INTERNAL].detach(); | ||
oldValue.detach(); | ||
} | ||
@@ -328,7 +409,2 @@ this._map.delete(key); | ||
} | ||
notify() { | ||
for (const listener of this._listeners) { | ||
listener(); | ||
} | ||
} | ||
toObject() { | ||
@@ -344,62 +420,69 @@ return Object.fromEntries(this._map); | ||
} | ||
delete(key) { | ||
if (this._ctx) { | ||
const ops = []; | ||
const item = this._map.get(key); | ||
if (isCrdt(item)) { | ||
item[INTERNAL].detach(); | ||
} | ||
this._ctx.doc.dispatch([ | ||
{ type: OpType.DeleteObjectKey, id: this._ctx.id, key: key }, | ||
]); | ||
} | ||
this._map.delete(key); | ||
this.notify(); | ||
} | ||
// delete<TKey extends keyof T>(key: TKey) { | ||
// if (this.doc && this.id) { | ||
// const item = this._map.get(key as string); | ||
// if (isCrdt(item)) { | ||
// item.detach(); | ||
// } | ||
// this.doc.dispatch([ | ||
// { type: OpType.DeleteObjectKey, id: this.id, key: key as string }, | ||
// ]); | ||
// } | ||
// this._map.delete(key as string); | ||
// this.notify(); | ||
// } | ||
update(overrides) { | ||
if (this._ctx) { | ||
if (this.doc && this.id) { | ||
const ops = []; | ||
const updateOperation = { | ||
id: this._ctx.id, | ||
const updateOp = { | ||
id: this.id, | ||
type: OpType.UpdateObject, | ||
data: {}, | ||
data: {} | ||
}; | ||
ops.push(updateOperation); | ||
ops.push(updateOp); | ||
for (const key in overrides) { | ||
const oldValue = this._map.get(key); | ||
if (isCrdt(oldValue)) { | ||
oldValue[INTERNAL].detach(); | ||
if (oldValue instanceof LiveObject) { | ||
oldValue.detach(); | ||
} | ||
const value = overrides[key]; | ||
if (isCrdt(value)) { | ||
ops.push(...value[INTERNAL].attach(this._ctx.doc.generateId(), this._ctx.doc, this._ctx.id, key)); | ||
const newValue = overrides[key]; | ||
if (newValue instanceof AbstractCrdt) { | ||
newValue.setParent(this); | ||
newValue.attach(this.doc.generateId(), this.doc); | ||
ops.push(...newValue.serialize(this.id, key)); | ||
} | ||
else { | ||
updateOperation.data[key] = value; | ||
updateOp.data[key] = newValue; | ||
} | ||
this._map.set(key, value); | ||
this._map.set(key, newValue); | ||
} | ||
this._ctx.doc.dispatch(ops); | ||
this.doc.dispatch(ops); | ||
this.notify(); | ||
return; | ||
} | ||
else { | ||
for (const key in overrides) { | ||
const value = overrides[key]; | ||
this._map.set(key, value); | ||
for (const key in overrides) { | ||
const oldValue = this._map.get(key); | ||
if (oldValue instanceof AbstractCrdt) { | ||
oldValue.detach(); | ||
} | ||
this.notify(); | ||
const newValue = overrides[key]; | ||
if (newValue instanceof AbstractCrdt) { | ||
newValue.setParent(this); | ||
} | ||
this._map.set(key, newValue); | ||
} | ||
this.notify(); | ||
} | ||
subscribe(listener) { | ||
this._listeners.push(listener); | ||
} | ||
unsubscribe(listener) { | ||
remove(this._listeners, listener); | ||
} | ||
} | ||
export class LiveMap { | ||
export class LiveMap extends AbstractCrdt { | ||
constructor(entries) { | ||
this._listeners = []; | ||
super(); | ||
if (entries) { | ||
this._map = new Map(entries.map((entry) => [entry[0], selfOrRegister(entry[1])])); | ||
const mappedEntries = []; | ||
for (const entry of entries) { | ||
const value = selfOrRegister(entry[1]); | ||
value.setParent(this); | ||
mappedEntries.push([entry[0], value]); | ||
} | ||
this._map = new Map(mappedEntries); | ||
} | ||
@@ -410,2 +493,22 @@ else { | ||
} | ||
serialize(parentId, parentKey) { | ||
if (this.id == null) { | ||
throw new Error("Cannot serialize item is not attached"); | ||
} | ||
if (parentId == null || parentKey == null) { | ||
throw new Error("Cannot serialize map if parentId or parentKey is undefined"); | ||
} | ||
const ops = []; | ||
const op = { | ||
id: this.id, | ||
type: OpType.CreateMap, | ||
parentId, | ||
parentKey, | ||
}; | ||
ops.push(op); | ||
for (const [key, value] of this._map) { | ||
ops.push(...value.serialize(this.id, key)); | ||
} | ||
return ops; | ||
} | ||
static deserialize([id, item], parentToChildren, doc) { | ||
@@ -416,3 +519,3 @@ if (item.type !== CrdtType.Map) { | ||
const map = new LiveMap(); | ||
map.attach(id, doc, item.parentId, item.parentKey); | ||
map.attach(id, doc); | ||
const children = parentToChildren.get(id); | ||
@@ -428,2 +531,3 @@ if (children == null) { | ||
const child = deserialize(entry, parentToChildren, doc); | ||
child.setParent(map); | ||
map._map.set(crdt.parentKey, child); | ||
@@ -433,53 +537,21 @@ } | ||
} | ||
get [INTERNAL]() { | ||
return { | ||
ctx: this._ctx, | ||
apply: this.apply.bind(this), | ||
attachChild: this.attachChild.bind(this), | ||
attach: this.attach.bind(this), | ||
detach: this.detach.bind(this), | ||
detachChild: this.detachChild.bind(this), | ||
getParentId: this._getParentId.bind(this), | ||
getId: this._getId.bind(this), | ||
}; | ||
} | ||
_getParentId() { | ||
var _a; | ||
return (_a = this._ctx) === null || _a === void 0 ? void 0 : _a.parentId; | ||
} | ||
_getId() { | ||
var _a; | ||
return (_a = this._ctx) === null || _a === void 0 ? void 0 : _a.id; | ||
} | ||
apply(op) { } | ||
attach(id, doc, parentId, parentKey) { | ||
if (this._ctx) { | ||
throw new Error("LiveMap is already part of the storage"); | ||
} | ||
doc.addItem(id, this); | ||
this._ctx = { | ||
id, | ||
doc: doc, | ||
parentId, | ||
}; | ||
const ops = []; | ||
const createOp = { | ||
id: this._ctx.id, | ||
type: OpType.CreateMap, | ||
parentId, | ||
parentKey, | ||
}; | ||
ops.push(createOp); | ||
attach(id, doc) { | ||
super.attach(id, doc); | ||
for (const [key, value] of this._map) { | ||
if (isCrdt(value)) { | ||
ops.push(...value[INTERNAL].attach(doc.generateId(), doc, this._ctx.id, key)); | ||
value.attach(doc.generateId(), doc); | ||
} | ||
} | ||
return ops; | ||
} | ||
attachChild(key, child) { | ||
attachChild(id, key, child) { | ||
if (this.doc == null) { | ||
throw new Error("Can't attach child if doc is not present"); | ||
} | ||
const previousValue = this._map.get(key); | ||
if (previousValue) { | ||
previousValue[INTERNAL].detach(); | ||
previousValue.detach(); | ||
} | ||
child.setParent(this); | ||
child.attach(id, this.doc); | ||
this._map.set(key, child); | ||
@@ -489,9 +561,6 @@ this.notify(); | ||
detach() { | ||
if (this._ctx == null) { | ||
return; | ||
} | ||
super.detach(); | ||
for (const item of this._map.values()) { | ||
item[INTERNAL].detach(); | ||
item.detach(); | ||
} | ||
this._ctx.doc.deleteItem(this._ctx.id); | ||
} | ||
@@ -504,3 +573,3 @@ detachChild(child) { | ||
} | ||
child[INTERNAL].detach(); | ||
child.detach(); | ||
this.notify(); | ||
@@ -516,19 +585,15 @@ } | ||
set(key, value) { | ||
if (this._ctx) { | ||
const ops = []; | ||
const oldValue = this._map.get(key); | ||
if (oldValue) { | ||
oldValue[INTERNAL].detach(); | ||
} | ||
const item = selfOrRegister(value); | ||
ops.push(...item[INTERNAL].attach(this._ctx.doc.generateId(), this._ctx.doc, this._ctx.id, key)); | ||
this._map.set(key, item); | ||
this._ctx.doc.dispatch(ops); | ||
this.notify(); | ||
const oldValue = this._map.get(key); | ||
if (oldValue) { | ||
oldValue.detach(); | ||
} | ||
else { | ||
const item = selfOrRegister(value); | ||
this._map.set(key, item); | ||
this.notify(); | ||
const item = selfOrRegister(value); | ||
item.setParent(this); | ||
this._map.set(key, item); | ||
if (this.doc && this.id) { | ||
item.attach(this.doc.generateId(), this.doc); | ||
const ops = item.serialize(this.id, key); | ||
this.doc.dispatch(ops); | ||
} | ||
this.notify(); | ||
} | ||
@@ -542,17 +607,13 @@ get size() { | ||
delete(key) { | ||
if (this._ctx) { | ||
const item = this._map.get(key); | ||
if (item) { | ||
const itemId = item[INTERNAL].getId(); | ||
if (itemId != null) { | ||
item[INTERNAL].detach(); | ||
this._ctx.doc.dispatch([{ type: OpType.DeleteCrdt, id: itemId }]); | ||
} | ||
} | ||
const item = this._map.get(key); | ||
if (item == null) { | ||
return false; | ||
} | ||
const isDeleted = this._map.delete(key); | ||
if (isDeleted) { | ||
this.notify(); | ||
item.detach(); | ||
if (this.doc && item.id) { | ||
this.doc.dispatch([{ type: OpType.DeleteCrdt, id: item.id }]); | ||
} | ||
return isDeleted; | ||
this._map.delete(key); | ||
this.notify(); | ||
return true; | ||
} | ||
@@ -611,35 +672,6 @@ entries() { | ||
} | ||
subscribe(listener) { | ||
this._listeners.push(listener); | ||
} | ||
unsubscribe(listener) { | ||
remove(this._listeners, listener); | ||
} | ||
notify() { | ||
for (const listener of this._listeners) { | ||
listener(); | ||
} | ||
} | ||
} | ||
function selfOrRegisterValue(obj) { | ||
if (obj instanceof LiveRegister) { | ||
return obj.data; | ||
} | ||
return obj; | ||
} | ||
function selfOrRegister(obj) { | ||
if (obj instanceof LiveObject || | ||
obj instanceof LiveMap || | ||
obj instanceof LiveList) { | ||
return obj; | ||
} | ||
else if (obj instanceof LiveRegister) { | ||
throw new Error("Internal error. LiveRegister should not be created from LiveRegister"); | ||
} | ||
else { | ||
return new LiveRegister(obj); | ||
} | ||
} | ||
class LiveRegister { | ||
class LiveRegister extends AbstractCrdt { | ||
constructor(data) { | ||
super(); | ||
this._data = data; | ||
@@ -655,60 +687,21 @@ } | ||
const register = new LiveRegister(item.data); | ||
register.attach(id, doc, item.parentId, item.parentKey); | ||
register.attach(id, doc); | ||
return register; | ||
} | ||
get [INTERNAL]() { | ||
return { | ||
ctx: this._ctx, | ||
attach: this.attach.bind(this), | ||
detach: this.detach.bind(this), | ||
attachChild: this.attachChild.bind(this), | ||
detachChild: this.detachChild.bind(this), | ||
getParentId: this._getParentId.bind(this), | ||
getId: this._getId.bind(this), | ||
}; | ||
} | ||
_getParentId() { | ||
var _a; | ||
return (_a = this._ctx) === null || _a === void 0 ? void 0 : _a.parentId; | ||
} | ||
_getId() { | ||
var _a; | ||
return (_a = this._ctx) === null || _a === void 0 ? void 0 : _a.id; | ||
} | ||
detachChild(crdt) { | ||
throw new Error("Internal error: cannot detach CRDT on register"); | ||
} | ||
detach() { | ||
if (this._ctx) { | ||
this._ctx.doc.deleteItem(this._ctx.id); | ||
serialize(parentId, parentKey) { | ||
if (this.id == null || parentId == null || parentKey == null) { | ||
throw new Error("Cannot serialize register if parentId or parentKey is undefined"); | ||
} | ||
return [{ | ||
type: OpType.CreateRegister, | ||
id: this.id, | ||
parentId, | ||
parentKey, | ||
data: this.data | ||
}]; | ||
} | ||
attach(id, doc, parentId, parentKey) { | ||
if (this._ctx) { | ||
throw new Error("LiveRegister is already part of the storage"); | ||
} | ||
doc.addItem(id, this); | ||
this._ctx = { | ||
id, | ||
doc: doc, | ||
parentId, | ||
}; | ||
const ops = []; | ||
const createOp = { | ||
id, | ||
type: OpType.CreateRegister, | ||
parentId, | ||
parentKey, | ||
data: this._data, | ||
}; | ||
ops.push(createOp); | ||
return ops; | ||
} | ||
attachChild(key, child) { | ||
throw new Error("Cannot attach child to register"); | ||
} | ||
} | ||
export class LiveList { | ||
export class LiveList extends AbstractCrdt { | ||
constructor(items = []) { | ||
this._listeners = []; | ||
super(); | ||
// TODO: Naive array at first, find a better data structure | ||
@@ -726,3 +719,3 @@ this._items = []; | ||
const list = new LiveList([]); | ||
list.attach(id, doc, item.parentId, item.parentKey); | ||
list.attach(id, doc); | ||
const children = parentToChildren.get(id); | ||
@@ -734,40 +727,18 @@ if (children == null) { | ||
const child = deserialize(entry, parentToChildren, doc); | ||
list.attachChild(entry[1].parentKey, child); | ||
child.setParent(list); | ||
list._items.push([child, entry[1].parentKey]); | ||
list._items.sort((itemA, itemB) => compare({ position: itemA[1] }, { position: itemB[1] })); | ||
} | ||
return list; | ||
} | ||
get [INTERNAL]() { | ||
return { | ||
ctx: this._ctx, | ||
attachChild: this.attachChild.bind(this), | ||
detachChild: this.detachChild.bind(this), | ||
attach: this.attach.bind(this), | ||
detach: this.detach.bind(this), | ||
apply: this.apply.bind(this), | ||
setChildKey: this.setChildKey.bind(this), | ||
getParentId: this._getParentId.bind(this), | ||
getId: this._getId.bind(this), | ||
}; | ||
} | ||
_getParentId() { | ||
var _a; | ||
return (_a = this._ctx) === null || _a === void 0 ? void 0 : _a.parentId; | ||
} | ||
_getId() { | ||
var _a; | ||
return (_a = this._ctx) === null || _a === void 0 ? void 0 : _a.id; | ||
} | ||
attach(id, doc, parentId, parentKey) { | ||
if (this._ctx) { | ||
throw new Error("LiveList is already part of the storage"); | ||
serialize(parentId, parentKey) { | ||
if (this.id == null) { | ||
throw new Error("Cannot serialize item is not attached"); | ||
} | ||
doc.addItem(id, this); | ||
this._ctx = { | ||
doc: doc, | ||
id: id, | ||
parentId: parentId, | ||
}; | ||
if (parentId == null || parentKey == null) { | ||
throw new Error("Cannot serialize list if parentId or parentKey is undefined"); | ||
} | ||
const ops = []; | ||
const createOp = { | ||
id: this._ctx.id, | ||
const op = { | ||
id: this.id, | ||
type: OpType.CreateList, | ||
@@ -777,18 +748,26 @@ parentId, | ||
}; | ||
ops.push(createOp); | ||
for (const [item, position] of this._items) { | ||
ops.push(...item[INTERNAL].attach(doc.generateId(), doc, this._ctx.id, position)); | ||
ops.push(op); | ||
for (const [value, key] of this._items) { | ||
ops.push(...value.serialize(this.id, key)); | ||
} | ||
return ops; | ||
} | ||
attach(id, doc) { | ||
super.attach(id, doc); | ||
for (const [item, position] of this._items) { | ||
item.attach(doc.generateId(), doc); | ||
} | ||
} | ||
detach() { | ||
if (this._ctx == null) { | ||
return; | ||
} | ||
super.detach(); | ||
for (const [value] of this._items) { | ||
value[INTERNAL].detach(); | ||
value.detach(); | ||
} | ||
this._ctx.doc.deleteItem(this._ctx.id); | ||
} | ||
attachChild(key, child) { | ||
attachChild(id, key, child) { | ||
if (this.doc == null) { | ||
throw new Error("Can't attach child if doc is not present"); | ||
} | ||
child.attach(id, this.doc); | ||
child.setParent(this); | ||
// TODO: Handle list conflict | ||
@@ -803,3 +782,3 @@ this._items.push([child, key]); | ||
if (child) { | ||
child[INTERNAL].detach(); | ||
child.detach(); | ||
} | ||
@@ -817,7 +796,2 @@ this.notify(); | ||
apply(op) { } | ||
notify() { | ||
for (const listener of this._listeners) { | ||
listener(); | ||
} | ||
} | ||
push(item) { | ||
@@ -828,7 +802,8 @@ const position = this._items.length === 0 | ||
const value = selfOrRegister(item); | ||
value.setParent(this); | ||
this._items.push([value, position]); | ||
this.notify(); | ||
if (this._ctx) { | ||
const ops = value[INTERNAL].attach(this._ctx.doc.generateId(), this._ctx.doc, this._ctx.id, position); | ||
this._ctx.doc.dispatch(ops); | ||
if (this.doc && this.id) { | ||
value.attach(this.doc.generateId(), this.doc); | ||
this.doc.dispatch(value.serialize(this.id, position)); | ||
} | ||
@@ -844,8 +819,9 @@ } | ||
const value = selfOrRegister(item); | ||
value.setParent(this); | ||
this._items.push([value, position]); | ||
this._items.sort((itemA, itemB) => compare({ position: itemA[1] }, { position: itemB[1] })); | ||
this.notify(); | ||
if (this._ctx) { | ||
const ops = value[INTERNAL].attach(this._ctx.doc.generateId(), this._ctx.doc, this._ctx.id, position); | ||
this._ctx.doc.dispatch(ops); | ||
if (this.doc && this.id) { | ||
value.attach(this.doc.generateId(), this.doc); | ||
this.doc.dispatch(value.serialize(this.id, position)); | ||
} | ||
@@ -885,14 +861,8 @@ } | ||
this.notify(); | ||
if (this._ctx) { | ||
const id = item[0][INTERNAL].getId(); | ||
if (id == null) { | ||
throw new Error("Internal error. Cannot set parent key from "); | ||
} | ||
this._ctx.doc.dispatch([ | ||
{ | ||
if (this.doc && this.id) { | ||
this.doc.dispatch([{ | ||
type: OpType.SetParentKey, | ||
id: id, | ||
id: item[0].id, | ||
parentKey: position, | ||
}, | ||
]); | ||
},]); | ||
} | ||
@@ -905,12 +875,14 @@ } | ||
const item = this._items[index]; | ||
item[0].detach(); | ||
this._items.splice(index, 1); | ||
if (this._ctx) { | ||
const childRecord = item[0]; | ||
this._ctx.doc.dispatch([ | ||
{ | ||
id: childRecord[INTERNAL].ctx.id, | ||
type: OpType.DeleteCrdt, | ||
}, | ||
]); | ||
childRecord[INTERNAL].detach(); | ||
if (this.doc) { | ||
const childRecordId = item[0].id; | ||
if (childRecordId) { | ||
this.doc.dispatch([ | ||
{ | ||
id: childRecordId, | ||
type: OpType.DeleteCrdt, | ||
}, | ||
]); | ||
} | ||
} | ||
@@ -925,8 +897,2 @@ this.notify(); | ||
} | ||
subscribe(listener) { | ||
this._listeners.push(listener); | ||
} | ||
unsubscribe(listener) { | ||
remove(this._listeners, listener); | ||
} | ||
} | ||
@@ -958,1 +924,20 @@ function deserialize(entry, parentToChildren, doc) { | ||
} | ||
function selfOrRegisterValue(obj) { | ||
if (obj instanceof LiveRegister) { | ||
return obj.data; | ||
} | ||
return obj; | ||
} | ||
function selfOrRegister(obj) { | ||
if (obj instanceof LiveObject || | ||
obj instanceof LiveMap || | ||
obj instanceof LiveList) { | ||
return obj; | ||
} | ||
else if (obj instanceof LiveRegister) { | ||
throw new Error("Internal error. LiveRegister should not be created from selfOrRegister"); | ||
} | ||
else { | ||
return new LiveRegister(obj); | ||
} | ||
} |
{ | ||
"name": "@liveblocks/client", | ||
"version": "0.12.0-beta.4", | ||
"version": "0.12.0-beta.6", | ||
"description": "", | ||
@@ -5,0 +5,0 @@ "main": "./lib/cjs/index.js", |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
325066
9407