Socket
Socket
Sign inDemoInstall

@liveblocks/client

Package Overview
Dependencies
Maintainers
2
Versions
379
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@liveblocks/client - npm Package Compare versions

Comparing version 0.11.0 to 0.12.0-beta.1

lib/cjs/storage.d.ts

4

lib/cjs/client.d.ts

@@ -5,4 +5,3 @@ import { ClientOptions, Client } from "./types";

*
* ### Example
* ```
* @example
* const client = createClient({

@@ -28,4 +27,3 @@ * authEndpoint: "/api/auth"

* });
* ```
*/
export declare function createClient(options: ClientOptions): Client;

@@ -8,4 +8,3 @@ "use strict";

*
* ### Example
* ```
* @example
* const client = createClient({

@@ -31,7 +30,7 @@ * authEndpoint: "/api/auth"

* });
* ```
*/
function createClient(options) {
if (typeof options.throttle === "number") {
if (options.throttle < 80 || options.throttle > 1000) {
const clientOptions = options;
if (typeof clientOptions.throttle === "number") {
if (clientOptions.throttle < 80 || clientOptions.throttle > 1000) {
throw new Error("Liveblocks client throttle should be between 80 and 1000 ms");

@@ -45,3 +44,3 @@ }

}
function enter(roomId, initialPresence) {
function enter(roomId, options = {}) {
let internalRoom = rooms.get(roomId);

@@ -51,3 +50,3 @@ if (internalRoom) {

}
internalRoom = room_1.createRoom(roomId, Object.assign(Object.assign({}, options), { initialPresence }));
internalRoom = room_1.createRoom(roomId, Object.assign(Object.assign({}, clientOptions), options));
rooms.set(roomId, internalRoom);

@@ -54,0 +53,0 @@ internalRoom.connect();

@@ -1,51 +0,97 @@

import { Op, SerializedRecord } from "./live";
import { Serializable, SerializablePrimitive } from "./types";
declare type Link = {
parentId: string;
parentKey: string;
};
declare type Links = Map<string, Link>;
declare type ListCache = Map<string, Map<string, Crdt>>;
declare type Cache = {
links: Links;
listCache: ListCache;
};
declare type Crdt = Record | List<any>;
declare const RECORD: unique symbol;
declare const LIST: unique symbol;
export declare function createRecord<T extends RecordData>(id: string, data: T): Record<T>;
export declare function createList<T>(id: string, items?: T[]): List<T>;
export declare type RecordData = {
[key: string]: RecordValue;
};
declare type RecordValue = SerializablePrimitive | Array<SerializablePrimitive> | Serializable | Record<any> | List<any>;
export declare type Record<T extends RecordData = RecordData> = {
readonly id: string;
readonly $$type: typeof RECORD;
} & T;
export declare type List<T> = {
readonly id: string;
readonly $$type: typeof LIST;
toArray(): Array<T>;
map<U>(callback: (value: T, index: number) => U): U[];
readonly length: number;
};
declare type Emit = (op: Op) => void;
export declare class Doc<T extends RecordData> {
root: Record<T>;
private _cache;
private _emit;
constructor(root: Record<T>, _cache: Cache, _emit: Emit);
static empty<T extends RecordData>(id?: string, emit?: Emit): Doc<T>;
static createFromRoot<T extends RecordData>(data: T, id?: string, emit?: Emit): Doc<T>;
static load<T extends RecordData>(root: SerializedRecord, emit?: Emit): Doc<T>;
get data(): Record<T>;
dispatch(op: Op, shouldEmit?: boolean): Doc<T>;
private getChild;
updateRecord<TRecord>(id: string, overrides: Partial<TRecord>): Doc<T>;
pushItem<TItem>(id: string, item: TItem): Doc<T>;
moveItem(id: string, index: number, targetIndex: number): Doc<T>;
deleteItem(id: string, index: number): Doc<T>;
deleteItemById(id: string, itemId: string): Doc<T>;
import { Op, SerializedCrdtWithId, SerializedList } from "./live";
declare const INTERNAL: unique symbol;
declare type Dispatch = (ops: Op[]) => void;
declare type Crdt = LiveRecord | LiveList;
export declare type RecordData = Record<string, any>;
export declare class Doc<T extends RecordData = RecordData> {
private _root;
private actor;
private _dispatch;
private _clock;
private _items;
private constructor();
static from<T>(root: T, actor?: number, dispatch?: Dispatch): Doc<T>;
static load<T>(items: SerializedCrdtWithId[], actor: number, dispatch?: Dispatch): Doc<T>;
dispatch(ops: Op[]): void;
addItem(id: string, item: Crdt): void;
deleteItem(id: string): void;
apply(op: Op): void;
private applyDeleteRecordKey;
private applyUpdateRecord;
private applyCreateRecord;
private applyDeleteRecord;
private applySetParentKey;
get root(): LiveRecord<T>;
count(): number;
generateId(): string;
}
export declare class LiveRecord<T extends RecordData = RecordData> {
private _map;
private _listeners;
private _ctx?;
constructor(object?: T);
static deserialize([id, item]: SerializedCrdtWithId, parentToChildren: Map<string, SerializedCrdtWithId[]>, doc: Doc): LiveRecord<{
[key: string]: any;
}>;
get [INTERNAL](): {
ctx: {
id: string;
doc: Doc<Record<string, any>>;
parentId?: string | undefined;
} | undefined;
attachChild: (key: keyof T, child: Crdt) => void;
detachChild: (child: Crdt) => void;
detach: () => void;
attach: (id: string, doc: Doc<Record<string, any>>, parentId?: string | undefined, parentKey?: string | undefined) => Op[];
apply: (op: Op) => void;
};
private attach;
private attachChild;
private detachChild;
private detach;
private apply;
private notify;
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 LiveList<T extends LiveRecord = LiveRecord> {
private _listeners;
private _ctx?;
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: LiveRecord<Record<string, any>>) => void;
detachChild: (child: Crdt) => void;
attach: (id: string, doc: Doc<Record<string, any>>, parentId: string, parentKey: string) => Op[];
detach: () => void;
apply: (op: Op) => void;
setChildKey: (key: string, child: Crdt) => void;
};
private attach;
private detach;
private attachChild;
private detachChild;
private setChildKey;
private apply;
private notify;
push(item: T): void;
insert(item: T, index: number): void;
move(index: number, targetIndex: number): void;
delete(index: number): void;
toArray(): T[];
get(index: number): T;
subscribe(listener: () => void): void;
unsubscribe(listener: () => void): void;
}
export {};
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Doc = exports.createList = exports.createRecord = void 0;
exports.LiveList = exports.LiveRecord = exports.Doc = void 0;
const utils_1 = require("./utils");
const live_1 = require("./live");
const position_1 = require("./position");
const RECORD = Symbol("liveblocks.record");
const LIST = Symbol("liveblocks.list");
function createRecord(id, data) {
return Object.assign({ id, $$type: RECORD }, data);
}
exports.createRecord = createRecord;
function createList(id, items = []) {
return {
id,
$$type: LIST,
length: items.length,
toArray: () => items,
map: (callback) => items.map(callback),
};
}
exports.createList = createList;
function noop() { }
const INTERNAL = Symbol("liveblocks.internal");
function noOp() { }
class Doc {
constructor(root, _cache, _emit) {
this.root = root;
this._cache = _cache;
this._emit = _emit;
constructor(_root, actor = 0, _dispatch = noOp) {
this._root = _root;
this.actor = actor;
this._dispatch = _dispatch;
this._clock = 0;
this._items = new Map();
}
static empty(id = "root", emit = noop) {
const root = {
id,
$$type: RECORD,
};
return new Doc(root, { links: new Map(), listCache: new Map() }, emit);
static from(root, actor = 0, dispatch = noOp) {
const rootRecord = new LiveRecord(root);
const storage = new Doc(rootRecord, actor, dispatch);
const ops = rootRecord[INTERNAL].attach(storage.generateId(), storage);
storage.dispatch(ops);
return storage;
}
static createFromRoot(data, id = "root", emit = noop) {
let doc = Doc.empty(id, emit);
doc = doc.updateRecord(doc.root.id, data);
static load(items, actor, dispatch = noOp) {
if (items.length === 0) {
throw new Error("Internal error: cannot load storage without items");
}
const parentToChildren = new Map();
let root = null;
for (const tuple of items) {
const parentId = tuple[1].parentId;
if (parentId == null) {
root = tuple;
}
else {
const children = parentToChildren.get(parentId);
if (children != null) {
children.push(tuple);
}
else {
parentToChildren.set(parentId, [tuple]);
}
}
}
if (root == null) {
throw new Error("Root can't be null");
}
const doc = new Doc(null, actor, dispatch);
doc._root = LiveRecord.deserialize(root, parentToChildren, doc);
return doc;
}
static load(root, emit = noop) {
let doc = Doc.empty(root.id, emit);
return doc.dispatch({
type: live_1.OpType.RecordUpdate,
id: root.id,
data: root.data,
});
dispatch(ops) {
this._dispatch(ops);
}
get data() {
return this.root;
addItem(id, item) {
this._items.set(id, item);
}
dispatch(op, shouldEmit = false) {
if (shouldEmit) {
this._emit(op);
deleteItem(id) {
this._items.delete(id);
}
apply(op) {
switch (op.type) {
case live_1.OpType.UpdateRecord: {
this.applyUpdateRecord(op);
break;
}
case live_1.OpType.CreateRecord: {
this.applyCreateRecord(op);
break;
}
case live_1.OpType.DeleteRecord: {
this.applyDeleteRecord(op);
break;
}
case live_1.OpType.SetParentKey: {
this.applySetParentKey(op);
break;
}
case live_1.OpType.DeleteRecordKey: {
this.applyDeleteRecordKey(op);
break;
}
}
if (op.id === this.root.id) {
const node = dispatch(this.root, op, this._cache, []);
return new Doc(node, this._cache, this._emit);
}
applyDeleteRecordKey(op) {
const item = this._items.get(op.id);
if (item) {
item[INTERNAL].apply(op);
}
else {
const links = getAllLinks(op.id, this.root.id, this._cache.links);
const node = dispatch(this.root, op, this._cache, links);
return new Doc(node, this._cache, this._emit);
}
applyUpdateRecord(op) {
const item = this._items.get(op.id);
if (item) {
item[INTERNAL].apply(op);
}
}
getChild(id) {
if (id === this.root.id) {
return this.root;
applyCreateRecord(op) {
const newRecord = new LiveRecord(op.data);
newRecord[INTERNAL].attach(op.id, this, op.parentId, op.parentKey);
if (op.parentId && op.parentKey) {
const parent = this._items.get(op.parentId);
if (parent == null) {
throw new Error("Parent is missing");
}
parent[INTERNAL].attachChild(op.parentKey, newRecord);
}
const allLinks = getAllLinks(id, this.root.id, this._cache.links);
return getChildDeep(this.root, id, allLinks, this._cache);
}
updateRecord(id, overrides) {
const currentRecord = this.getChild(id);
if (currentRecord == null) {
throw new Error(`Record with id "${id}" does not exist`);
applyDeleteRecord(op) {
const item = this._items.get(op.id);
if (item == null) {
return;
}
let data = {};
for (const key in overrides) {
const value = overrides[key];
data[key] = serialize(value);
const parentId = item[INTERNAL].ctx.parentId;
if (parentId == null) {
return;
}
const op = {
id: currentRecord.id,
type: live_1.OpType.RecordUpdate,
data,
};
return this.dispatch(op, true);
const parent = this._items.get(parentId);
if (parent) {
parent[INTERNAL].detachChild(item);
}
}
pushItem(id, item) {
const list = this.getChild(id);
if (list == null) {
throw new Error(`List with id "${id}" does not exist`);
applySetParentKey(op) {
const item = this._items.get(op.id);
if (item == null) {
return;
}
if (list.$$type !== LIST) {
throw new Error(`Node with id "${id}" is not a list`);
const parentId = item[INTERNAL].ctx.parentId;
if (parentId == null) {
return;
}
if (!isRecord(item)) {
throw new Error("List can't only have Record as children");
const parent = this._items.get(parentId);
if (parent && parent instanceof LiveList) {
parent[INTERNAL].setChildKey(op.parentKey, item);
}
const data = serialize(item);
if (list.length === 0) {
return this.dispatch({
type: live_1.OpType.ListInsert,
id: list.id,
position: position_1.makePosition(),
data,
}, true);
}
const items = sortedListItems(getListItems(this._cache, id));
const [tailPosition] = items[items.length - 1];
const position = position_1.makePosition(tailPosition);
const operation = {
type: live_1.OpType.ListInsert,
id: list.id,
position,
data,
};
return this.dispatch(operation, true);
}
moveItem(id, index, targetIndex) {
const list = this.getChild(id);
if (list == null) {
throw new Error(`List with id "${id}" does not exist`);
get root() {
return this._root;
}
count() {
return this._items.size;
}
generateId() {
return `${this.actor}:${this._clock++}`;
}
}
exports.Doc = Doc;
class LiveRecord {
constructor(object = {}) {
this._listeners = [];
this._map = new Map(Object.entries(object));
}
static deserialize([id, item], parentToChildren, doc) {
if (item.type !== live_1.CrdtType.Record) {
throw new Error(`Tried to deserialize a record but item type is "${item.type}"`);
}
if (list.$$type !== LIST) {
throw new Error(`Node with id "${id}" is not a list`);
const record = new LiveRecord(item.data);
record.attach(id, doc, item.parentId, item.parentKey);
const children = parentToChildren.get(id);
if (children == null) {
return record;
}
const items = sortedListItems(getListItems(this._cache, id));
if (targetIndex < 0) {
throw new Error("targetIndex cannot be less than 0");
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);
}
if (targetIndex >= items.length) {
throw new Error("targetIndex cannot be greater or equal than the list length");
return record;
}
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),
};
}
attach(id, doc, parentId, parentKey) {
if (this._ctx) {
throw new Error("LiveRecord is already part of the storage!");
}
if (index < 0) {
throw new Error("index cannot be less than 0");
doc.addItem(id, this);
this._ctx = {
id,
doc: doc,
parentId,
};
const ops = [];
const createOp = {
id: this._ctx.id,
type: live_1.OpType.CreateRecord,
parentId,
parentKey,
data: {},
};
ops.push(createOp);
for (const [key, value] of this._map) {
if (value instanceof LiveRecord) {
ops.push(...value.attach(doc.generateId(), doc, this._ctx.id, key));
}
else if (value instanceof LiveList) {
ops.push(...value[INTERNAL].attach(doc.generateId(), doc, this._ctx.id, key));
}
else {
createOp.data[key] = value;
}
}
if (index >= items.length) {
throw new Error("index cannot be greater or equal than the list length");
return ops;
}
attachChild(key, child) {
this._map.set(key, child);
this.notify();
}
detachChild(child) {
for (const [key, value] of this._map) {
if (value === child) {
this._map.delete(key);
}
}
if (index === targetIndex) {
return this;
if (child instanceof LiveRecord) {
child.detach();
}
let beforePosition = null;
let afterPosition = null;
if (index < targetIndex) {
afterPosition =
targetIndex === items.length - 1
? undefined
: items[targetIndex + 1][0];
beforePosition = items[targetIndex][0];
this.notify();
}
detach() {
if (this._ctx == null) {
return;
}
else {
afterPosition = items[targetIndex][0];
beforePosition =
targetIndex === 0 ? undefined : items[targetIndex - 1][0];
this._ctx.doc.deleteItem(this._ctx.id);
for (const [, value] of this._map) {
if (value instanceof LiveRecord) {
value.detach();
}
}
const position = position_1.makePosition(beforePosition, afterPosition);
const [, item] = items[index];
return this.dispatch({
type: live_1.OpType.ListMove,
id: list.id,
itemId: item.id,
position,
}, true);
}
deleteItem(id, index) {
const list = this.getChild(id);
if (list == null) {
throw new Error(`List with id "${id}" does not exist`);
apply(op) {
if (op.type === live_1.OpType.UpdateRecord) {
for (const key in op.data) {
const oldValue = this._map.get(key);
if (oldValue instanceof LiveRecord) {
oldValue.detach();
}
const value = op.data[key];
this._map.set(key, value);
}
this.notify();
}
if (list.$$type !== LIST) {
throw new Error(`Node with id "${id}" is not a list`);
else if (op.type === live_1.OpType.DeleteRecordKey) {
const key = op.key;
const oldValue = this._map.get(key);
if (oldValue instanceof LiveRecord) {
oldValue.detach();
}
this._map.delete(key);
this.notify();
}
const items = sortedListItems(getListItems(this._cache, id));
const [, item] = items[index];
return this.dispatch({
type: live_1.OpType.ListRemove,
id: list.id,
itemId: item.id,
}, true);
}
deleteItemById(id, itemId) {
const list = this.getChild(id);
if (list == null) {
throw new Error(`List with id "${id}" does not exist`);
notify() {
for (const listener of this._listeners) {
listener();
}
if (list.$$type !== LIST) {
throw new Error(`Node with id "${id}" is not a list`);
}
const itemsMap = getListItems(this._cache, id);
let item = null;
for (const [, crdt] of itemsMap) {
if (crdt.id === itemId) {
item = crdt;
break;
}
toObject() {
return Object.fromEntries(this._map);
}
set(key, value) {
// TODO: Find out why typescript complains
this.update({ [key]: value });
}
get(key) {
return this._map.get(key);
}
delete(key) {
if (this._ctx) {
const ops = [];
const item = this._map.get(key);
if (item instanceof LiveRecord) {
item.detach();
}
this._ctx.doc.dispatch([
{ type: live_1.OpType.DeleteRecordKey, id: this._ctx.id, key: key },
]);
}
if (item == null) {
throw new Error(`List with id "${id}" does not have an item with id "${itemId}"`);
}
return this.dispatch({
type: live_1.OpType.ListRemove,
id: list.id,
itemId: item.id,
}, true);
this._map.delete(key);
this.notify();
}
}
exports.Doc = Doc;
function getAllLinks(id, rootId, links) {
let currentId = id;
const result = [];
do {
const link = links.get(currentId);
if (link == null) {
throw new Error(`Can't find link for id "${currentId}"`);
update(overrides) {
if (this._ctx) {
const ops = [];
const updateOperation = {
id: this._ctx.id,
type: live_1.OpType.UpdateRecord,
data: {},
};
ops.push(updateOperation);
for (const key in overrides) {
const oldValue = this._map.get(key);
if (oldValue instanceof LiveRecord) {
oldValue.detach();
}
const value = overrides[key];
if (value instanceof LiveRecord) {
ops.push(...value.attach(this._ctx.doc.generateId(), this._ctx.doc, this._ctx.id, key));
}
else {
updateOperation.data[key] = value;
}
this._map.set(key, value);
}
this._ctx.doc.dispatch(ops);
this.notify();
}
currentId = link.parentId;
result.push(link);
} while (currentId !== rootId);
return result;
}
function deserializeList(serialized, cache) {
const listItems = new Map();
for (const position in serialized.data) {
const item = deserialize(serialized.data[position], cache);
if (!isRecord(item)) {
throw new Error("TODO");
else {
for (const key in overrides) {
const value = overrides[key];
this._map.set(key, value);
}
this.notify();
}
listItems.set(position, item);
cache.links.set(item.id, { parentId: serialized.id, parentKey: position });
}
cache.listCache.set(serialized.id, listItems);
return createList(serialized.id, listItemsToArray(listItems));
}
function getListItems(cache, listId) {
const items = cache.listCache.get(listId);
if (items == null) {
throw new Error(`Can't find list cache for id "${listId}"`);
subscribe(listener) {
this._listeners.push(listener);
}
return items;
unsubscribe(listener) {
utils_1.remove(this._listeners, listener);
}
}
function deserializeRecord(serialized, cache) {
const result = {
id: serialized.id,
$$type: RECORD,
};
for (const key in serialized.data) {
const item = deserialize(serialized.data[key], cache);
if (isCrdt(item)) {
cache.links.set(item.id, {
parentId: serialized.id,
parentKey: key,
});
exports.LiveRecord = LiveRecord;
class LiveList {
constructor(items = []) {
this._listeners = [];
// TODO: Find a better data structure
this._items = [];
let position = undefined;
for (let i = 0; i < items.length; i++) {
const newPosition = position_1.makePosition(position);
this._items.push([items[i], newPosition]);
position = newPosition;
}
result[key] = item;
}
return result;
}
function deserialize(serialized, cache) {
switch (serialized.type) {
case live_1.CrdtType.Register: {
return serialized.data;
static deserialize([id, item], parentToChildren, doc) {
const list = new LiveList([]);
list.attach(id, doc, item.parentId, item.parentKey);
const children = parentToChildren.get(id);
if (children == null) {
return list;
}
case live_1.CrdtType.Record: {
return deserializeRecord(serialized, cache);
for (const entry of children) {
const child = LiveRecord.deserialize(entry, parentToChildren, doc);
list.attachChild(entry[1].parentKey, child);
}
case live_1.CrdtType.List: {
return deserializeList(serialized, cache);
}
default: {
throw new Error("TODO");
}
return list;
}
}
function dispatchOnRecord(record, op, cache, links) {
if (links.length === 0) {
if (record.id !== op.id) {
throw new Error("TODO");
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),
};
}
attach(id, doc, parentId, parentKey) {
if (this._ctx) {
throw new Error("LiveList is already part of the storage!");
}
switch (op.type) {
case live_1.OpType.RecordUpdate: {
return updateRecord(record, op, cache);
}
default: {
console.warn("Unsupported operation");
return record;
}
doc.addItem(id, this);
this._ctx = {
doc: doc,
id: id,
parentId: parentId,
};
const ops = [];
const createOp = {
id: this._ctx.id,
type: live_1.OpType.CreateList,
parentId,
parentKey,
};
ops.push(createOp);
for (const [item, position] of this._items) {
ops.push(...item[INTERNAL].attach(doc.generateId(), doc, this._ctx.id, position));
}
return ops;
}
const currentLink = links.pop();
const child = record[currentLink.parentKey];
const newNode = dispatch(child, op, cache, links);
return Object.assign(Object.assign({}, record), { [currentLink.parentKey]: newNode });
}
function dispatchOnList(list, op, cache, links) {
if (links.length === 0) {
if (list.id !== op.id) {
throw new Error("TODO");
detach() {
if (this._ctx == null) {
return;
}
switch (op.type) {
case live_1.OpType.ListInsert: {
return listInsert(list, op, cache);
}
case live_1.OpType.ListMove: {
return listMove(list, op, cache);
}
case live_1.OpType.ListRemove: {
return listDelete(list, op, cache);
}
default: {
console.warn("Unsupported operation");
return list;
}
}
this._ctx.doc.deleteItem(this._ctx.id);
}
const currentLink = links.pop();
const position = currentLink.parentKey;
const items = getListItems(cache, list.id);
const item = items.get(position);
if (item == null) {
throw new Error("TODO");
attachChild(key, child) {
this._items.push([child, key]);
this._items.sort((itemA, itemB) => position_1.compare({ position: itemA[1] }, { position: itemB[1] }));
this.notify();
}
const newItem = dispatch(item, op, cache, links);
items.set(position, newItem);
return createList(list.id, listItemsToArray(items));
}
function dispatch(node, op, cache, links) {
switch (node.$$type) {
case RECORD:
return dispatchOnRecord(node, op, cache, links);
case LIST:
return dispatchOnList(node, op, cache, links);
default: {
throw new Error("Unknown CRDT");
detachChild(child) {
const indexToDelete = this._items.findIndex((item) => item[0] === child);
this._items.splice(indexToDelete);
if (child instanceof LiveRecord) {
child[INTERNAL].detach();
}
this.notify();
}
}
function updateRecord(node, op, cache) {
const result = Object.assign({}, node);
for (const key in op.data) {
const value = op.data[key];
const item = deserialize(value, cache);
if (isCrdt(item)) {
cache.links.set(item.id, { parentId: node.id, parentKey: key });
setChildKey(key, child) {
const item = this._items.find((item) => item[0] === child);
if (item) {
item[1] = key;
}
result[key] = item;
this._items.sort((itemA, itemB) => position_1.compare({ position: itemA[1] }, { position: itemB[1] }));
this.notify();
}
return result;
}
function listInsert(list, op, cache) {
const items = getListItems(cache, list.id);
const item = deserialize(op.data, cache);
if (isCrdt(item)) {
items.set(op.position, item);
cache.links.set(item.id, { parentId: list.id, parentKey: op.position });
apply(op) { }
notify() {
for (const listener of this._listeners) {
listener();
}
}
return createList(list.id, listItemsToArray(items));
}
function listMove(list, op, cache) {
const items = getListItems(cache, list.id);
const link = getLinkOrThrow(cache, op.itemId);
const item = items.get(link.parentKey);
if (item == null) {
throw new Error("TODO");
push(item) {
const position = this._items.length === 0
? position_1.makePosition()
: position_1.makePosition(this._items[this._items.length - 1][1]);
this._items.push([item, position]);
this.notify();
if (this._ctx) {
const ops = item[INTERNAL].attach(this._ctx.doc.generateId(), this._ctx.doc, this._ctx.id, position);
this._ctx.doc.dispatch(ops);
}
}
// Delete old position cache entry
items.delete(link.parentKey);
// Insert new position in cache
items.set(op.position, item);
// Update link
cache.links.set(op.itemId, { parentId: list.id, parentKey: op.position });
return createList(list.id, listItemsToArray(items));
}
function getLinkOrThrow(cache, id) {
const link = cache.links.get(id);
if (link == null) {
throw new Error(`Can't find link with id "${id}"`);
insert(item, index) {
if (index < 0 || index > this._items.length) {
throw new Error(`Cannot delete list item at index "${index}". index should be between 0 and ${this._items.length}`);
}
let before = this._items[index - 1] ? this._items[index - 1][1] : undefined;
let after = this._items[index] ? this._items[index][1] : undefined;
const position = position_1.makePosition(before, after);
this._items.push([item, position]);
this._items.sort((itemA, itemB) => position_1.compare({ position: itemA[1] }, { position: itemB[1] }));
this.notify();
if (this._ctx) {
const ops = item[INTERNAL].attach(this._ctx.doc.generateId(), this._ctx.doc, this._ctx.id, position);
this._ctx.doc.dispatch(ops);
}
}
return link;
}
function listDelete(list, op, cache) {
const items = getListItems(cache, list.id);
const link = getLinkOrThrow(cache, op.itemId);
items.delete(link.parentKey);
cache.links.delete(op.itemId);
return createList(list.id, listItemsToArray(items));
}
function listItemsToArray(items) {
return sortedListItems(items).map((entry) => entry[1]);
}
function sortedListItems(items) {
return Array.from(items.entries()).sort((entryA, entryB) => position_1.compare({ position: entryA[0] }, { position: entryB[0] }));
}
function getChildDeep(node, id, links, cache) {
let currentNode = node;
while (currentNode.id !== id) {
const link = links.pop();
if (link == null || link.parentId !== currentNode.id) {
throw new Error("TODO");
move(index, targetIndex) {
if (targetIndex < 0) {
throw new Error("targetIndex cannot be less than 0");
}
if (currentNode.$$type === RECORD) {
currentNode = currentNode[link.parentKey];
if (targetIndex >= this._items.length) {
throw new Error("targetIndex cannot be greater or equal than the list length");
}
if (index < 0) {
throw new Error("index cannot be less than 0");
}
if (index >= this._items.length) {
throw new Error("index cannot be greater or equal than the list length");
}
let beforePosition = null;
let afterPosition = null;
if (index < targetIndex) {
afterPosition =
targetIndex === this._items.length - 1
? undefined
: this._items[targetIndex + 1][1];
beforePosition = this._items[targetIndex][1];
}
else {
const listItems = getListItems(cache, currentNode.id);
const item = listItems.get(link.parentKey);
if (item == null) {
throw new Error("TODO");
}
currentNode = item;
afterPosition = this._items[targetIndex][1];
beforePosition =
targetIndex === 0 ? undefined : this._items[targetIndex - 1][1];
}
const position = position_1.makePosition(beforePosition, afterPosition);
const item = this._items[index];
item[1] = position;
this._items.sort((itemA, itemB) => position_1.compare({ position: itemA[1] }, { position: itemB[1] }));
this.notify();
if (this._ctx) {
this._ctx.doc.dispatch([
{
type: live_1.OpType.SetParentKey,
id: item[0][INTERNAL].ctx.id,
parentKey: position,
},
]);
}
}
return currentNode;
}
function isRecord(value) {
return value != null && typeof value === "object" && value.$$type === RECORD;
}
function isList(value) {
return value != null && typeof value === "object" && value.$$type === LIST;
}
function isCrdt(value) {
return isRecord(value) || isList(value);
}
function serializeRecord(record) {
const serializedData = {};
for (const key in record) {
if (key !== "id" && key !== "$$type") {
const value = record[key]; // TODO: Find out why typescript does not like that
serializedData[key] = serialize(value);
delete(index) {
if (index < 0 || index >= this._items.length) {
throw new Error(`Cannot delete list item at index "${index}". index should be between 0 and ${this._items.length - 1}`);
}
const item = this._items[index];
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.DeleteRecord,
},
]);
childRecord[INTERNAL].detach();
}
this.notify();
}
return {
id: record.id,
type: live_1.CrdtType.Record,
data: serializedData,
};
}
function serializeList(list) {
return {
id: list.id,
type: live_1.CrdtType.List,
data: {},
};
}
function serialize(value) {
if (isRecord(value)) {
return serializeRecord(value);
toArray() {
return this._items.map((entry) => entry[0]);
}
else if (isList(value)) {
return serializeList(value);
get(index) {
return this._items[index][0];
}
else {
return { type: live_1.CrdtType.Register, data: value };
subscribe(listener) {
this._listeners.push(listener);
}
unsubscribe(listener) {
utils_1.remove(this._listeners, listener);
}
}
exports.LiveList = LiveList;
function deserialize(entry, parentToChildren, doc) {
switch (entry[1].type) {
case live_1.CrdtType.Record: {
return LiveRecord.deserialize(entry, parentToChildren, doc);
}
case live_1.CrdtType.List: {
return LiveList.deserialize(entry, parentToChildren, doc);
}
default: {
throw new Error("Unexpected CRDT type");
}
}
}

@@ -1,4 +0,3 @@

export type { Record, RecordData, List } from "./doc";
export { LiveRecord, LiveList, RecordData } from "./doc";
export type { Others, Presence, Room, Client, User } from "./types";
export { createClient } from "./client";
export { LiveStorageState } from "./types";
export type { Others, Presence, Room, InitialStorageFactory, Client, LiveStorage, User, } from "./types";
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.LiveStorageState = exports.createClient = void 0;
exports.createClient = exports.LiveList = exports.LiveRecord = void 0;
var doc_1 = require("./doc");
Object.defineProperty(exports, "LiveRecord", { enumerable: true, get: function () { return doc_1.LiveRecord; } });
Object.defineProperty(exports, "LiveList", { enumerable: true, get: function () { return doc_1.LiveList; } });
var client_1 = require("./client");
Object.defineProperty(exports, "createClient", { enumerable: true, get: function () { return client_1.createClient; } });
var types_1 = require("./types");
Object.defineProperty(exports, "LiveStorageState", { enumerable: true, get: function () { return types_1.LiveStorageState; } });

@@ -41,5 +41,6 @@ import { Presence } from "./types";

};
export declare type SerializedCrdtWithId = [id: string, crdt: SerializedCrdt];
export declare type InitialDocumentStateMessage = {
type: ServerMessageType.InitialStorageState;
root: SerializedRecord | null;
items: SerializedCrdtWithId[];
};

@@ -75,57 +76,64 @@ export declare type UpdateStorageMessage = {

Record = 0,
List = 1,
Register = 2
List = 1
}
export declare type SerializedRecord = {
id: string;
type: CrdtType.Record;
parentId?: string;
parentKey?: string;
data: {
[key: string]: SerializedCrdt;
[key: string]: any;
};
};
export declare type SerializedList = {
id: string;
type: CrdtType.List;
data: {
[position: string]: SerializedCrdt;
};
parentId: string;
parentKey: string;
};
export declare type SerializedRegister = {
id?: string;
type: CrdtType.Register;
data: any;
};
export declare type SerializedCrdt = SerializedRecord | SerializedList | SerializedRegister;
export declare type SerializedCrdt = SerializedRecord | SerializedList;
export declare enum OpType {
Init = 100,
ListInsert = 200,
ListMove = 201,
ListRemove = 202,
RecordUpdate = 300
Init = 0,
SetParentKey = 1,
CreateList = 2,
UpdateRecord = 3,
CreateRecord = 4,
DeleteRecord = 5,
DeleteRecordKey = 6
}
export declare type Op = RecordUpdateOp | ListInsertOp | ListDeleteOp | ListMoveOp;
export declare type Op = CreateRecordOp | RecordUpdateOp | DeleteRecordOp | CreateListOp | SetParentKeyOp | DeleteRecordKeyOp;
export declare type RecordUpdateOp = {
id: string;
type: OpType.RecordUpdate;
type: OpType.UpdateRecord;
data: {
[key: string]: SerializedCrdt;
[key: string]: any;
};
};
export declare type ListInsertOp = {
export declare type CreateRecordOp = {
id: string;
type: OpType.ListInsert;
position: string;
data: SerializedCrdt;
type: OpType.CreateRecord;
parentId?: string;
parentKey?: string;
data: {
[key: string]: any;
};
};
export declare type ListMoveOp = {
export declare type CreateListOp = {
id: string;
type: OpType.ListMove;
itemId: string;
position: string;
type: OpType.CreateList;
parentId: string;
parentKey: string;
};
export declare type ListDeleteOp = {
export declare type DeleteRecordOp = {
id: string;
type: OpType.ListRemove;
itemId: string;
type: OpType.DeleteRecord;
};
export declare type SetParentKeyOp = {
id: string;
type: OpType.SetParentKey;
parentKey: string;
};
export declare type DeleteRecordKeyOp = {
id: string;
type: OpType.DeleteRecordKey;
key: string;
};
export declare enum WebsocketCloseCodes {

@@ -132,0 +140,0 @@ CLOSE_ABNORMAL = 1006,

@@ -25,11 +25,12 @@ "use strict";

CrdtType[CrdtType["List"] = 1] = "List";
CrdtType[CrdtType["Register"] = 2] = "Register";
})(CrdtType = exports.CrdtType || (exports.CrdtType = {}));
var OpType;
(function (OpType) {
OpType[OpType["Init"] = 100] = "Init";
OpType[OpType["ListInsert"] = 200] = "ListInsert";
OpType[OpType["ListMove"] = 201] = "ListMove";
OpType[OpType["ListRemove"] = 202] = "ListRemove";
OpType[OpType["RecordUpdate"] = 300] = "RecordUpdate";
OpType[OpType["Init"] = 0] = "Init";
OpType[OpType["SetParentKey"] = 1] = "SetParentKey";
OpType[OpType["CreateList"] = 2] = "CreateList";
OpType[OpType["UpdateRecord"] = 3] = "UpdateRecord";
OpType[OpType["CreateRecord"] = 4] = "CreateRecord";
OpType[OpType["DeleteRecord"] = 5] = "DeleteRecord";
OpType[OpType["DeleteRecordKey"] = 6] = "DeleteRecordKey";
})(OpType = exports.OpType || (exports.OpType = {}));

@@ -36,0 +37,0 @@ var WebsocketCloseCodes;

@@ -1,4 +0,3 @@

import { RecordData, List } from ".";
import { Doc, Record } from "./doc";
import { Others, Presence, ClientOptions, Room, InitialStorageFactory, MyPresenceCallback, OthersEventCallback, StorageCallback, AuthEndpoint, LiveStorageState, LiveStorage, EventCallback, User, Connection, Serializable, ErrorCallback, AuthenticationToken, ConnectionCallback } from "./types";
import { RecordData } from "./doc";
import { Others, Presence, ClientOptions, Room, MyPresenceCallback, OthersEventCallback, AuthEndpoint, EventCallback, User, Connection, ErrorCallback, AuthenticationToken, ConnectionCallback } from "./types";
import { ClientMessage, Op } from "./live";

@@ -24,3 +23,2 @@ declare type IdFactory = () => string;

listeners: {
storage: StorageCallback[];
event: EventCallback[];

@@ -39,5 +37,5 @@ others: OthersEventCallback[];

numberOfRetry: number;
doc: Doc<any> | null;
storageState: LiveStorageState;
initialStorageFactory: InitialStorageFactory | null;
defaultStorageRoot?: {
[key: string]: any;
};
};

@@ -73,6 +71,5 @@ export declare type Effects = {

subscribe: {
<T extends Serializable>(type: "my-presence", listener: MyPresenceCallback<T>): void;
<T_1 extends Serializable>(type: "others", listener: OthersEventCallback<T_1>): void;
<T extends Presence>(type: "my-presence", listener: MyPresenceCallback<T>): void;
<T_1 extends Presence>(type: "others", listener: OthersEventCallback<T_1>): void;
(type: "event", listener: EventCallback): void;
<T_2 extends RecordData>(type: "storage", listener: StorageCallback<T_2>): void;
(type: "error", listener: ErrorCallback): void;

@@ -82,28 +79,23 @@ (type: "connection", listener: ConnectionCallback): void;

unsubscribe: {
<T_3 extends Serializable>(type: "my-presence", listener: MyPresenceCallback<T_3>): void;
<T_4 extends Serializable>(type: "others", listener: OthersEventCallback<T_4>): void;
<T_2 extends Presence>(type: "my-presence", listener: MyPresenceCallback<T_2>): void;
<T_3 extends Presence>(type: "others", listener: OthersEventCallback<T_3>): void;
(type: "event", listener: EventCallback): void;
<T_5 extends RecordData>(type: "storage", listener: StorageCallback<T_5>): void;
(type: "error", listener: ErrorCallback): void;
(type: "connection", listener: ConnectionCallback): void;
};
updatePresence: <T_6 extends Serializable>(overrides: Partial<T_6>) => void;
updatePresence: <T_4 extends Presence>(overrides: Partial<T_4>) => void;
broadcastEvent: (event: any) => void;
fetchStorage: (initialStorageFactory: InitialStorageFactory) => void;
createRecord: <T_7 extends RecordData>(data: any) => Record<T_7>;
updateRecord: <T_8 extends RecordData>(record: Record<T_8>, overrides: Partial<T_8>) => void;
createList: <T_9 extends RecordData>() => List<Record<T_9>>;
pushItem: <T_10 extends RecordData>(list: List<Record<T_10>>, item: Record<T_10>) => void;
deleteItem: <T_11 extends RecordData>(list: List<Record<T_11>>, index: number) => void;
deleteItemById: <T_12 extends RecordData>(list: List<Record<T_12>>, itemId: string) => void;
moveItem: <T_13 extends RecordData>(list: List<Record<T_13>>, index: number, targetIndex: number) => void;
getStorage: <TRoot>() => Promise<{
root: import("./doc").LiveRecord<TRoot>;
}>;
selectors: {
getConnectionState: () => "failed" | "closed" | "open" | "connecting" | "authenticating" | "unavailable";
getSelf: <TPresence extends Serializable = Serializable>() => User<TPresence> | null;
getPresence: <T_14 extends Serializable>() => T_14;
getOthers: <T_15 extends Serializable>() => Others<T_15>;
getStorage: () => LiveStorage;
getSelf: <TPresence extends Presence = Presence>() => User<TPresence> | null;
getPresence: <T_5 extends Presence>() => T_5;
getOthers: <T_6 extends Presence>() => Others<T_6>;
};
};
export declare function defaultState(me?: Presence): State;
export declare function defaultState(me?: Presence, defaultStorageRoot?: {
[key: string]: any;
}): State;
export declare type InternalRoom = {

@@ -117,4 +109,5 @@ room: Room;

export declare function createRoom(name: string, options: ClientOptions & {
initialPresence?: Presence;
defaultPresence?: Presence;
defaultStorageRoot?: RecordData;
}): InternalRoom;
export {};

@@ -30,10 +30,11 @@ "use strict";

};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createRoom = exports.defaultState = exports.makeStateMachine = void 0;
const doc_1 = require("./doc");
const types_1 = require("./types");
const doc_2 = require("./doc");
const utils_1 = require("./utils");
const authentication_1 = __importStar(require("./authentication"));
const live_1 = require("./live");
const storage_1 = __importDefault(require("./storage"));
const BACKOFF_RETRY_DELAYS = [250, 500, 1000, 2000, 4000, 8000, 10000];

@@ -44,4 +45,3 @@ const HEARTBEAT_INTERVAL = 30000;

function isValidRoomEventType(value) {
return (value === "storage" ||
value === "my-presence" ||
return (value === "my-presence" ||
value === "others" ||

@@ -274,10 +274,2 @@ value === "event" ||

switch (message.type) {
case live_1.ServerMessageType.InitialStorageState: {
onInitialStorageState(message);
break;
}
case live_1.ServerMessageType.UpdateStorage: {
onStorageUpdates(message);
break;
}
case live_1.ServerMessageType.UserJoined: {

@@ -304,2 +296,3 @@ onUserJoinedMessage(message);

}
storage.onMessage(message);
}

@@ -487,82 +480,29 @@ // function onWakeUp() {

}
/**
* STORAGE
*/
function onStorageUpdates(message) {
if (state.doc == null) {
// TODO: Cache updates in case they are coming while root is queried
return;
}
updateDoc(message.ops.reduce((doc, op) => doc.dispatch(op), state.doc));
function dispatch(ops) {
state.flushData.storageOperations.push(...ops);
tryFlushing();
}
function updateDoc(doc) {
state.doc = doc;
if (doc) {
for (const listener of state.listeners.storage) {
listener(getStorage());
const storage = new storage_1.default({
fetchStorage: () => {
state.flushData.messages.push({ type: live_1.ClientMessageType.FetchStorage });
tryFlushing();
},
dispatch,
getConnectionId: () => {
const me = getSelf();
if (me) {
return me.connectionId;
}
}
}
throw new Error("Unexpected");
},
defaultRoot: state.defaultStorageRoot,
});
function getStorage() {
if (state.storageState === types_1.LiveStorageState.Loaded) {
return __awaiter(this, void 0, void 0, function* () {
const doc = yield storage.getDocument();
return {
state: state.storageState,
root: state.doc.root,
root: doc.root,
};
}
return {
state: state.storageState,
};
});
}
function onInitialStorageState(message) {
state.storageState = types_1.LiveStorageState.Loaded;
if (message.root == null) {
const rootId = makeId();
state.doc = doc_1.Doc.empty(rootId, (op) => dispatch(op));
updateDoc(state.doc.updateRecord(rootId, state.initialStorageFactory({
createRecord: (data) => createRecord(data),
createList: () => createList(),
})));
}
else {
updateDoc(doc_1.Doc.load(message.root, (op) => dispatch(op)));
}
}
function makeId() {
if (state.idFactory == null) {
throw new Error("Can't generate id. Id factory is missing.");
}
return state.idFactory();
}
function dispatch(op) {
state.flushData.storageOperations.push(op);
tryFlushing();
}
function createRecord(data) {
return doc_2.createRecord(makeId(), data);
}
function createList() {
return doc_2.createList(makeId());
}
function fetchStorage(initialStorageFactory) {
state.initialStorageFactory = initialStorageFactory;
state.storageState = types_1.LiveStorageState.Loading;
state.flushData.messages.push({ type: live_1.ClientMessageType.FetchStorage });
tryFlushing();
}
function updateRecord(record, overrides) {
updateDoc(state.doc.updateRecord(record.id, overrides));
}
function pushItem(list, item) {
updateDoc(state.doc.pushItem(list.id, item));
}
function deleteItem(list, index) {
updateDoc(state.doc.deleteItem(list.id, index));
}
function deleteItemById(list, itemId) {
updateDoc(state.doc.deleteItemById(list.id, itemId));
}
function moveItem(list, index, targetIndex) {
updateDoc(state.doc.moveItem(list.id, index, targetIndex));
}
return {

@@ -586,11 +526,3 @@ // Internal

broadcastEvent,
// Storage
fetchStorage,
createRecord,
updateRecord,
createList,
pushItem,
deleteItem,
deleteItemById,
moveItem,
getStorage,
selectors: {

@@ -603,4 +535,2 @@ // Core

getOthers,
// Storage
getStorage,
},

@@ -610,3 +540,3 @@ };

exports.makeStateMachine = makeStateMachine;
function defaultState(me) {
function defaultState(me, defaultStorageRoot) {
return {

@@ -616,3 +546,2 @@ connection: { state: "closed" },

listeners: {
storage: [],
event: [],

@@ -642,5 +571,3 @@ others: [],

others: makeOthers({}),
storageState: types_1.LiveStorageState.NotInitialized,
initialStorageFactory: null,
doc: null,
defaultStorageRoot,
idFactory: null,

@@ -652,5 +579,5 @@ };

const throttleDelay = options.throttle || 100;
const liveblocksServer = options.liveblocksServer || "wss://liveblocks.net";
const liveblocksServer = options.liveblocksServer || "wss://liveblocks.net/v2";
const authEndpoint = options.authEndpoint;
const state = defaultState(options.initialPresence);
const state = defaultState(options.defaultPresence, options.defaultStorageRoot);
const machine = makeStateMachine(state, {

@@ -670,14 +597,2 @@ throttleDelay,

unsubscribe: machine.unsubscribe,
/////////////
// Storage //
/////////////
getStorage: machine.selectors.getStorage,
fetchStorage: machine.fetchStorage,
createRecord: machine.createRecord,
createList: machine.createList,
updateRecord: machine.updateRecord,
pushItem: machine.pushItem,
deleteItem: machine.deleteItem,
deleteItemById: machine.deleteItemById,
moveItem: machine.moveItem,
//////////////

@@ -690,2 +605,3 @@ // Presence //

broadcastEvent: machine.broadcastEvent,
getStorage: machine.getStorage,
};

@@ -692,0 +608,0 @@ return {

@@ -1,3 +0,2 @@

import { RecordData, Record, List } from "./doc";
export declare type StorageCallback<T extends RecordData = RecordData> = (storage: LiveStorage<T>) => void;
import { LiveRecord, RecordData } from "./doc";
export declare type MyPresenceCallback<T extends Presence = Presence> = (me: T) => void;

@@ -12,3 +11,2 @@ export declare type OthersEventCallback<T extends Presence = Presence> = (others: Others<T>, event: OthersEvent<T>) => void;

export declare type RoomEventCallbackMap = {
storage: StorageCallback;
"my-presence": MyPresenceCallback;

@@ -20,8 +18,2 @@ others: OthersEventCallback;

};
export declare type CreateRecord = Room["createRecord"];
export declare type CreateList = Room["createList"];
export declare type InitialStorageFactory<TRoot = RecordData> = (factories: {
createRecord: CreateRecord;
createList: CreateList;
}) => TRoot;
export declare type Client = {

@@ -39,3 +31,6 @@ /**

*/
enter(roomId: string, defaultPresence?: Presence): Room;
enter<TStorageRoot = RecordData>(roomId: string, options?: {
defaultPresence?: Presence;
defaultStorageRoot?: TStorageRoot;
}): Room;
/**

@@ -91,6 +86,4 @@ * Leaves a room.

};
export declare type Presence = Serializable;
export declare type SerializablePrimitive = boolean | string | number | null;
export declare type Serializable = {
[key: string]: SerializablePrimitive | Serializable | SerializablePrimitive[];
export declare type Presence = {
[key: string]: any;
};

@@ -112,13 +105,2 @@ declare type AuthEndpointCallback = (room: string) => Promise<{

};
export declare enum LiveStorageState {
NotInitialized = 0,
Loading = 1,
Loaded = 2
}
export declare type LiveStorage<T extends RecordData = RecordData> = {
state: LiveStorageState.Loading | LiveStorageState.NotInitialized;
} | {
state: LiveStorageState.Loaded;
root: Record<T>;
};
declare type ConnectionState = "closed" | "authenticating" | "unavailable" | "failed" | "open" | "connecting";

@@ -154,8 +136,6 @@ export declare type Connection = {

*
* ### Example
* ``` typescript
* @example
* room.subscribe("my-presence", (presence) => {
* // Do something
* });
* ```
*/

@@ -168,8 +148,6 @@ <T extends Presence>(type: "my-presence", listener: MyPresenceCallback<T>): void;

*
* ### Example
* ``` typescript
* @example
* room.subscribe("others", (others) => {
* // Do something
* });
* ```
*/

@@ -182,11 +160,8 @@ <T extends Presence>(type: "others", listener: OthersEventCallback<T>): void;

*
* ### Example
* ``` typescript
* @example
* room.subscribe("event", ({ event, connectionId }) => {
* // Do something
* });
* ```
*/
(type: "event", listener: EventCallback): void;
<T extends RecordData>(type: "storage", listener: StorageCallback<T>): void;
/**

@@ -207,8 +182,6 @@ * Subscribe to errors thrown in the room.

*
* ### Example
* ``` typescript
* @example
* const onPresenceChange = (presence) => { };
* room.subscribe("my-presence", onPresenceChange);
* room.unsubscribe("my-presence", onPresenceChange);
* ```
*/

@@ -221,8 +194,6 @@ <T extends Presence>(type: "my-presence", listener: MyPresenceCallback<T>): void;

*
* ### Example
* ``` typescript
* @example
* const onOthersChange = (presence) => { };
* room.subscribe("others", onOthersChange);
* room.unsubscribe("others", onOthersChange);
* ```
*/

@@ -235,11 +206,8 @@ <T extends Presence>(type: "others", listener: OthersEventCallback<T>): void;

*
* ### Example
* ``` typescript
* @example
* const onEvent = ({ event, connectionId }) => { };
* room.subscribe("event", onEvent);
* room.unsubscribe("event", onEvent);
* ```
*/
(type: "event", listener: EventCallback): void;
<T extends RecordData>(type: "storage", listener: StorageCallback<T>): void;
/**

@@ -258,6 +226,4 @@ * Unsubscribe to errors thrown in the room.

*
* ### Example
* ``` typescript
* @example
* const user = room.getSelf();
* ```
*/

@@ -268,6 +234,4 @@ getSelf<TPresence extends Presence = Presence>(): User<TPresence> | null;

*
* ### Example
* ``` typescript
* @example
* const presence = room.getPresence();
* ```
*/

@@ -278,6 +242,4 @@ getPresence: <T extends Presence>() => T;

*
* ### Example
* ``` typescript
* @example
* const others = room.getOthers();
* ```
*/

@@ -287,6 +249,5 @@ getOthers: <T extends Presence>() => Others<T>;

* Updates the presence of the current user. Only pass the properties you want to update. No need to send the full presence.
* @param {Partial<T>} overrides A partial object that contains the properties you want to update.
* @param {Partial<T>} overrides - A partial object that contains the properties you want to update.
*
* ### Example
* ``` typescript
* @example
* room.updatePresence({ x: 0 });

@@ -297,3 +258,2 @@ * room.updatePresence({ y: 0 });

* // presence is equivalent to { x: 0, y: 0 }
* ```
*/

@@ -303,7 +263,5 @@ updatePresence: <T extends Presence>(overrides: Partial<T>) => void;

* Broadcast an event to other users in the room. Event broadcasted to the room can be listened with {@link Room.subscribe}("event").
* @param {any} event the event to broadcast. Should be serializable to JSON
* @param {any} event - the event to broadcast. Should be serializable to JSON
*
* ### Example
* ``` typescript
*
* @example
* // On client A

@@ -318,15 +276,8 @@ * room.broadcastEvent({ type: "EMOJI", emoji: "🔥" });

* });
* ```
*/
broadcastEvent: (event: any) => void;
getStorage: () => LiveStorage;
fetchStorage(initialStorageFactory: InitialStorageFactory): void;
createRecord: <T extends RecordData>(data: T) => Record<T>;
createList: <T extends RecordData>() => List<Record<T>>;
updateRecord<T extends RecordData>(record: Record<T>, overrides: Partial<T>): void;
pushItem<T extends RecordData>(list: List<Record<T>>, item: Record<T>): void;
deleteItem<T extends RecordData>(list: List<Record<T>>, index: number): void;
deleteItemById<T extends RecordData>(list: List<Record<T>>, itemId: string): void;
moveItem<T extends RecordData>(list: List<Record<T>>, index: number, targetIndex: number): void;
getStorage: <TRoot>() => Promise<{
root: LiveRecord<TRoot>;
}>;
};
export {};
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.LiveStorageState = void 0;
var LiveStorageState;
(function (LiveStorageState) {
LiveStorageState[LiveStorageState["NotInitialized"] = 0] = "NotInitialized";
LiveStorageState[LiveStorageState["Loading"] = 1] = "Loading";
LiveStorageState[LiveStorageState["Loaded"] = 2] = "Loaded";
})(LiveStorageState = exports.LiveStorageState || (exports.LiveStorageState = {}));

@@ -5,4 +5,3 @@ import { ClientOptions, Client } from "./types";

*
* ### Example
* ```
* @example
* const client = createClient({

@@ -28,4 +27,3 @@ * authEndpoint: "/api/auth"

* });
* ```
*/
export declare function createClient(options: ClientOptions): Client;

@@ -5,4 +5,3 @@ import { createRoom } from "./room";

*
* ### Example
* ```
* @example
* const client = createClient({

@@ -28,7 +27,7 @@ * authEndpoint: "/api/auth"

* });
* ```
*/
export function createClient(options) {
if (typeof options.throttle === "number") {
if (options.throttle < 80 || options.throttle > 1000) {
const clientOptions = options;
if (typeof clientOptions.throttle === "number") {
if (clientOptions.throttle < 80 || clientOptions.throttle > 1000) {
throw new Error("Liveblocks client throttle should be between 80 and 1000 ms");

@@ -42,3 +41,3 @@ }

}
function enter(roomId, initialPresence) {
function enter(roomId, options = {}) {
let internalRoom = rooms.get(roomId);

@@ -48,3 +47,3 @@ if (internalRoom) {

}
internalRoom = createRoom(roomId, Object.assign(Object.assign({}, options), { initialPresence }));
internalRoom = createRoom(roomId, Object.assign(Object.assign({}, clientOptions), options));
rooms.set(roomId, internalRoom);

@@ -51,0 +50,0 @@ internalRoom.connect();

@@ -1,51 +0,97 @@

import { Op, SerializedRecord } from "./live";
import { Serializable, SerializablePrimitive } from "./types";
declare type Link = {
parentId: string;
parentKey: string;
};
declare type Links = Map<string, Link>;
declare type ListCache = Map<string, Map<string, Crdt>>;
declare type Cache = {
links: Links;
listCache: ListCache;
};
declare type Crdt = Record | List<any>;
declare const RECORD: unique symbol;
declare const LIST: unique symbol;
export declare function createRecord<T extends RecordData>(id: string, data: T): Record<T>;
export declare function createList<T>(id: string, items?: T[]): List<T>;
export declare type RecordData = {
[key: string]: RecordValue;
};
declare type RecordValue = SerializablePrimitive | Array<SerializablePrimitive> | Serializable | Record<any> | List<any>;
export declare type Record<T extends RecordData = RecordData> = {
readonly id: string;
readonly $$type: typeof RECORD;
} & T;
export declare type List<T> = {
readonly id: string;
readonly $$type: typeof LIST;
toArray(): Array<T>;
map<U>(callback: (value: T, index: number) => U): U[];
readonly length: number;
};
declare type Emit = (op: Op) => void;
export declare class Doc<T extends RecordData> {
root: Record<T>;
private _cache;
private _emit;
constructor(root: Record<T>, _cache: Cache, _emit: Emit);
static empty<T extends RecordData>(id?: string, emit?: Emit): Doc<T>;
static createFromRoot<T extends RecordData>(data: T, id?: string, emit?: Emit): Doc<T>;
static load<T extends RecordData>(root: SerializedRecord, emit?: Emit): Doc<T>;
get data(): Record<T>;
dispatch(op: Op, shouldEmit?: boolean): Doc<T>;
private getChild;
updateRecord<TRecord>(id: string, overrides: Partial<TRecord>): Doc<T>;
pushItem<TItem>(id: string, item: TItem): Doc<T>;
moveItem(id: string, index: number, targetIndex: number): Doc<T>;
deleteItem(id: string, index: number): Doc<T>;
deleteItemById(id: string, itemId: string): Doc<T>;
import { Op, SerializedCrdtWithId, SerializedList } from "./live";
declare const INTERNAL: unique symbol;
declare type Dispatch = (ops: Op[]) => void;
declare type Crdt = LiveRecord | LiveList;
export declare type RecordData = Record<string, any>;
export declare class Doc<T extends RecordData = RecordData> {
private _root;
private actor;
private _dispatch;
private _clock;
private _items;
private constructor();
static from<T>(root: T, actor?: number, dispatch?: Dispatch): Doc<T>;
static load<T>(items: SerializedCrdtWithId[], actor: number, dispatch?: Dispatch): Doc<T>;
dispatch(ops: Op[]): void;
addItem(id: string, item: Crdt): void;
deleteItem(id: string): void;
apply(op: Op): void;
private applyDeleteRecordKey;
private applyUpdateRecord;
private applyCreateRecord;
private applyDeleteRecord;
private applySetParentKey;
get root(): LiveRecord<T>;
count(): number;
generateId(): string;
}
export declare class LiveRecord<T extends RecordData = RecordData> {
private _map;
private _listeners;
private _ctx?;
constructor(object?: T);
static deserialize([id, item]: SerializedCrdtWithId, parentToChildren: Map<string, SerializedCrdtWithId[]>, doc: Doc): LiveRecord<{
[key: string]: any;
}>;
get [INTERNAL](): {
ctx: {
id: string;
doc: Doc<Record<string, any>>;
parentId?: string | undefined;
} | undefined;
attachChild: (key: keyof T, child: Crdt) => void;
detachChild: (child: Crdt) => void;
detach: () => void;
attach: (id: string, doc: Doc<Record<string, any>>, parentId?: string | undefined, parentKey?: string | undefined) => Op[];
apply: (op: Op) => void;
};
private attach;
private attachChild;
private detachChild;
private detach;
private apply;
private notify;
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 LiveList<T extends LiveRecord = LiveRecord> {
private _listeners;
private _ctx?;
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: LiveRecord<Record<string, any>>) => void;
detachChild: (child: Crdt) => void;
attach: (id: string, doc: Doc<Record<string, any>>, parentId: string, parentKey: string) => Op[];
detach: () => void;
apply: (op: Op) => void;
setChildKey: (key: string, child: Crdt) => void;
};
private attach;
private detach;
private attachChild;
private detachChild;
private setChildKey;
private apply;
private notify;
push(item: T): void;
insert(item: T, index: number): void;
move(index: number, targetIndex: number): void;
delete(index: number): void;
toArray(): T[];
get(index: number): T;
subscribe(listener: () => void): void;
unsubscribe(listener: () => void): void;
}
export {};

@@ -1,451 +0,531 @@

import { OpType, CrdtType, } from "./live";
import { remove } from "./utils";
import { CrdtType, OpType, } from "./live";
import { compare, makePosition } from "./position";
const RECORD = Symbol("liveblocks.record");
const LIST = Symbol("liveblocks.list");
export function createRecord(id, data) {
return Object.assign({ id, $$type: RECORD }, data);
}
export function createList(id, items = []) {
return {
id,
$$type: LIST,
length: items.length,
toArray: () => items,
map: (callback) => items.map(callback),
};
}
function noop() { }
const INTERNAL = Symbol("liveblocks.internal");
function noOp() { }
export class Doc {
constructor(root, _cache, _emit) {
this.root = root;
this._cache = _cache;
this._emit = _emit;
constructor(_root, actor = 0, _dispatch = noOp) {
this._root = _root;
this.actor = actor;
this._dispatch = _dispatch;
this._clock = 0;
this._items = new Map();
}
static empty(id = "root", emit = noop) {
const root = {
id,
$$type: RECORD,
};
return new Doc(root, { links: new Map(), listCache: new Map() }, emit);
static from(root, actor = 0, dispatch = noOp) {
const rootRecord = new LiveRecord(root);
const storage = new Doc(rootRecord, actor, dispatch);
const ops = rootRecord[INTERNAL].attach(storage.generateId(), storage);
storage.dispatch(ops);
return storage;
}
static createFromRoot(data, id = "root", emit = noop) {
let doc = Doc.empty(id, emit);
doc = doc.updateRecord(doc.root.id, data);
static load(items, actor, dispatch = noOp) {
if (items.length === 0) {
throw new Error("Internal error: cannot load storage without items");
}
const parentToChildren = new Map();
let root = null;
for (const tuple of items) {
const parentId = tuple[1].parentId;
if (parentId == null) {
root = tuple;
}
else {
const children = parentToChildren.get(parentId);
if (children != null) {
children.push(tuple);
}
else {
parentToChildren.set(parentId, [tuple]);
}
}
}
if (root == null) {
throw new Error("Root can't be null");
}
const doc = new Doc(null, actor, dispatch);
doc._root = LiveRecord.deserialize(root, parentToChildren, doc);
return doc;
}
static load(root, emit = noop) {
let doc = Doc.empty(root.id, emit);
return doc.dispatch({
type: OpType.RecordUpdate,
id: root.id,
data: root.data,
});
dispatch(ops) {
this._dispatch(ops);
}
get data() {
return this.root;
addItem(id, item) {
this._items.set(id, item);
}
dispatch(op, shouldEmit = false) {
if (shouldEmit) {
this._emit(op);
deleteItem(id) {
this._items.delete(id);
}
apply(op) {
switch (op.type) {
case OpType.UpdateRecord: {
this.applyUpdateRecord(op);
break;
}
case OpType.CreateRecord: {
this.applyCreateRecord(op);
break;
}
case OpType.DeleteRecord: {
this.applyDeleteRecord(op);
break;
}
case OpType.SetParentKey: {
this.applySetParentKey(op);
break;
}
case OpType.DeleteRecordKey: {
this.applyDeleteRecordKey(op);
break;
}
}
if (op.id === this.root.id) {
const node = dispatch(this.root, op, this._cache, []);
return new Doc(node, this._cache, this._emit);
}
applyDeleteRecordKey(op) {
const item = this._items.get(op.id);
if (item) {
item[INTERNAL].apply(op);
}
else {
const links = getAllLinks(op.id, this.root.id, this._cache.links);
const node = dispatch(this.root, op, this._cache, links);
return new Doc(node, this._cache, this._emit);
}
applyUpdateRecord(op) {
const item = this._items.get(op.id);
if (item) {
item[INTERNAL].apply(op);
}
}
getChild(id) {
if (id === this.root.id) {
return this.root;
applyCreateRecord(op) {
const newRecord = new LiveRecord(op.data);
newRecord[INTERNAL].attach(op.id, this, op.parentId, op.parentKey);
if (op.parentId && op.parentKey) {
const parent = this._items.get(op.parentId);
if (parent == null) {
throw new Error("Parent is missing");
}
parent[INTERNAL].attachChild(op.parentKey, newRecord);
}
const allLinks = getAllLinks(id, this.root.id, this._cache.links);
return getChildDeep(this.root, id, allLinks, this._cache);
}
updateRecord(id, overrides) {
const currentRecord = this.getChild(id);
if (currentRecord == null) {
throw new Error(`Record with id "${id}" does not exist`);
applyDeleteRecord(op) {
const item = this._items.get(op.id);
if (item == null) {
return;
}
let data = {};
for (const key in overrides) {
const value = overrides[key];
data[key] = serialize(value);
const parentId = item[INTERNAL].ctx.parentId;
if (parentId == null) {
return;
}
const op = {
id: currentRecord.id,
type: OpType.RecordUpdate,
data,
};
return this.dispatch(op, true);
const parent = this._items.get(parentId);
if (parent) {
parent[INTERNAL].detachChild(item);
}
}
pushItem(id, item) {
const list = this.getChild(id);
if (list == null) {
throw new Error(`List with id "${id}" does not exist`);
applySetParentKey(op) {
const item = this._items.get(op.id);
if (item == null) {
return;
}
if (list.$$type !== LIST) {
throw new Error(`Node with id "${id}" is not a list`);
const parentId = item[INTERNAL].ctx.parentId;
if (parentId == null) {
return;
}
if (!isRecord(item)) {
throw new Error("List can't only have Record as children");
const parent = this._items.get(parentId);
if (parent && parent instanceof LiveList) {
parent[INTERNAL].setChildKey(op.parentKey, item);
}
const data = serialize(item);
if (list.length === 0) {
return this.dispatch({
type: OpType.ListInsert,
id: list.id,
position: makePosition(),
data,
}, true);
}
const items = sortedListItems(getListItems(this._cache, id));
const [tailPosition] = items[items.length - 1];
const position = makePosition(tailPosition);
const operation = {
type: OpType.ListInsert,
id: list.id,
position,
data,
};
return this.dispatch(operation, true);
}
moveItem(id, index, targetIndex) {
const list = this.getChild(id);
if (list == null) {
throw new Error(`List with id "${id}" does not exist`);
get root() {
return this._root;
}
count() {
return this._items.size;
}
generateId() {
return `${this.actor}:${this._clock++}`;
}
}
export class LiveRecord {
constructor(object = {}) {
this._listeners = [];
this._map = new Map(Object.entries(object));
}
static deserialize([id, item], parentToChildren, doc) {
if (item.type !== CrdtType.Record) {
throw new Error(`Tried to deserialize a record but item type is "${item.type}"`);
}
if (list.$$type !== LIST) {
throw new Error(`Node with id "${id}" is not a list`);
const record = new LiveRecord(item.data);
record.attach(id, doc, item.parentId, item.parentKey);
const children = parentToChildren.get(id);
if (children == null) {
return record;
}
const items = sortedListItems(getListItems(this._cache, id));
if (targetIndex < 0) {
throw new Error("targetIndex cannot be less than 0");
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);
}
if (targetIndex >= items.length) {
throw new Error("targetIndex cannot be greater or equal than the list length");
return record;
}
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),
};
}
attach(id, doc, parentId, parentKey) {
if (this._ctx) {
throw new Error("LiveRecord is already part of the storage!");
}
if (index < 0) {
throw new Error("index cannot be less than 0");
doc.addItem(id, this);
this._ctx = {
id,
doc: doc,
parentId,
};
const ops = [];
const createOp = {
id: this._ctx.id,
type: OpType.CreateRecord,
parentId,
parentKey,
data: {},
};
ops.push(createOp);
for (const [key, value] of this._map) {
if (value instanceof LiveRecord) {
ops.push(...value.attach(doc.generateId(), doc, this._ctx.id, key));
}
else if (value instanceof LiveList) {
ops.push(...value[INTERNAL].attach(doc.generateId(), doc, this._ctx.id, key));
}
else {
createOp.data[key] = value;
}
}
if (index >= items.length) {
throw new Error("index cannot be greater or equal than the list length");
return ops;
}
attachChild(key, child) {
this._map.set(key, child);
this.notify();
}
detachChild(child) {
for (const [key, value] of this._map) {
if (value === child) {
this._map.delete(key);
}
}
if (index === targetIndex) {
return this;
if (child instanceof LiveRecord) {
child.detach();
}
let beforePosition = null;
let afterPosition = null;
if (index < targetIndex) {
afterPosition =
targetIndex === items.length - 1
? undefined
: items[targetIndex + 1][0];
beforePosition = items[targetIndex][0];
this.notify();
}
detach() {
if (this._ctx == null) {
return;
}
else {
afterPosition = items[targetIndex][0];
beforePosition =
targetIndex === 0 ? undefined : items[targetIndex - 1][0];
this._ctx.doc.deleteItem(this._ctx.id);
for (const [, value] of this._map) {
if (value instanceof LiveRecord) {
value.detach();
}
}
const position = makePosition(beforePosition, afterPosition);
const [, item] = items[index];
return this.dispatch({
type: OpType.ListMove,
id: list.id,
itemId: item.id,
position,
}, true);
}
deleteItem(id, index) {
const list = this.getChild(id);
if (list == null) {
throw new Error(`List with id "${id}" does not exist`);
apply(op) {
if (op.type === OpType.UpdateRecord) {
for (const key in op.data) {
const oldValue = this._map.get(key);
if (oldValue instanceof LiveRecord) {
oldValue.detach();
}
const value = op.data[key];
this._map.set(key, value);
}
this.notify();
}
if (list.$$type !== LIST) {
throw new Error(`Node with id "${id}" is not a list`);
else if (op.type === OpType.DeleteRecordKey) {
const key = op.key;
const oldValue = this._map.get(key);
if (oldValue instanceof LiveRecord) {
oldValue.detach();
}
this._map.delete(key);
this.notify();
}
const items = sortedListItems(getListItems(this._cache, id));
const [, item] = items[index];
return this.dispatch({
type: OpType.ListRemove,
id: list.id,
itemId: item.id,
}, true);
}
deleteItemById(id, itemId) {
const list = this.getChild(id);
if (list == null) {
throw new Error(`List with id "${id}" does not exist`);
notify() {
for (const listener of this._listeners) {
listener();
}
if (list.$$type !== LIST) {
throw new Error(`Node with id "${id}" is not a list`);
}
const itemsMap = getListItems(this._cache, id);
let item = null;
for (const [, crdt] of itemsMap) {
if (crdt.id === itemId) {
item = crdt;
break;
}
toObject() {
return Object.fromEntries(this._map);
}
set(key, value) {
// TODO: Find out why typescript complains
this.update({ [key]: value });
}
get(key) {
return this._map.get(key);
}
delete(key) {
if (this._ctx) {
const ops = [];
const item = this._map.get(key);
if (item instanceof LiveRecord) {
item.detach();
}
this._ctx.doc.dispatch([
{ type: OpType.DeleteRecordKey, id: this._ctx.id, key: key },
]);
}
if (item == null) {
throw new Error(`List with id "${id}" does not have an item with id "${itemId}"`);
}
return this.dispatch({
type: OpType.ListRemove,
id: list.id,
itemId: item.id,
}, true);
this._map.delete(key);
this.notify();
}
}
function getAllLinks(id, rootId, links) {
let currentId = id;
const result = [];
do {
const link = links.get(currentId);
if (link == null) {
throw new Error(`Can't find link for id "${currentId}"`);
update(overrides) {
if (this._ctx) {
const ops = [];
const updateOperation = {
id: this._ctx.id,
type: OpType.UpdateRecord,
data: {},
};
ops.push(updateOperation);
for (const key in overrides) {
const oldValue = this._map.get(key);
if (oldValue instanceof LiveRecord) {
oldValue.detach();
}
const value = overrides[key];
if (value instanceof LiveRecord) {
ops.push(...value.attach(this._ctx.doc.generateId(), this._ctx.doc, this._ctx.id, key));
}
else {
updateOperation.data[key] = value;
}
this._map.set(key, value);
}
this._ctx.doc.dispatch(ops);
this.notify();
}
currentId = link.parentId;
result.push(link);
} while (currentId !== rootId);
return result;
}
function deserializeList(serialized, cache) {
const listItems = new Map();
for (const position in serialized.data) {
const item = deserialize(serialized.data[position], cache);
if (!isRecord(item)) {
throw new Error("TODO");
else {
for (const key in overrides) {
const value = overrides[key];
this._map.set(key, value);
}
this.notify();
}
listItems.set(position, item);
cache.links.set(item.id, { parentId: serialized.id, parentKey: position });
}
cache.listCache.set(serialized.id, listItems);
return createList(serialized.id, listItemsToArray(listItems));
}
function getListItems(cache, listId) {
const items = cache.listCache.get(listId);
if (items == null) {
throw new Error(`Can't find list cache for id "${listId}"`);
subscribe(listener) {
this._listeners.push(listener);
}
return items;
unsubscribe(listener) {
remove(this._listeners, listener);
}
}
function deserializeRecord(serialized, cache) {
const result = {
id: serialized.id,
$$type: RECORD,
};
for (const key in serialized.data) {
const item = deserialize(serialized.data[key], cache);
if (isCrdt(item)) {
cache.links.set(item.id, {
parentId: serialized.id,
parentKey: key,
});
export class LiveList {
constructor(items = []) {
this._listeners = [];
// TODO: Find a better data structure
this._items = [];
let position = undefined;
for (let i = 0; i < items.length; i++) {
const newPosition = makePosition(position);
this._items.push([items[i], newPosition]);
position = newPosition;
}
result[key] = item;
}
return result;
}
function deserialize(serialized, cache) {
switch (serialized.type) {
case CrdtType.Register: {
return serialized.data;
static deserialize([id, item], parentToChildren, doc) {
const list = new LiveList([]);
list.attach(id, doc, item.parentId, item.parentKey);
const children = parentToChildren.get(id);
if (children == null) {
return list;
}
case CrdtType.Record: {
return deserializeRecord(serialized, cache);
for (const entry of children) {
const child = LiveRecord.deserialize(entry, parentToChildren, doc);
list.attachChild(entry[1].parentKey, child);
}
case CrdtType.List: {
return deserializeList(serialized, cache);
}
default: {
throw new Error("TODO");
}
return list;
}
}
function dispatchOnRecord(record, op, cache, links) {
if (links.length === 0) {
if (record.id !== op.id) {
throw new Error("TODO");
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),
};
}
attach(id, doc, parentId, parentKey) {
if (this._ctx) {
throw new Error("LiveList is already part of the storage!");
}
switch (op.type) {
case OpType.RecordUpdate: {
return updateRecord(record, op, cache);
}
default: {
console.warn("Unsupported operation");
return record;
}
doc.addItem(id, this);
this._ctx = {
doc: doc,
id: id,
parentId: parentId,
};
const ops = [];
const createOp = {
id: this._ctx.id,
type: OpType.CreateList,
parentId,
parentKey,
};
ops.push(createOp);
for (const [item, position] of this._items) {
ops.push(...item[INTERNAL].attach(doc.generateId(), doc, this._ctx.id, position));
}
return ops;
}
const currentLink = links.pop();
const child = record[currentLink.parentKey];
const newNode = dispatch(child, op, cache, links);
return Object.assign(Object.assign({}, record), { [currentLink.parentKey]: newNode });
}
function dispatchOnList(list, op, cache, links) {
if (links.length === 0) {
if (list.id !== op.id) {
throw new Error("TODO");
detach() {
if (this._ctx == null) {
return;
}
switch (op.type) {
case OpType.ListInsert: {
return listInsert(list, op, cache);
}
case OpType.ListMove: {
return listMove(list, op, cache);
}
case OpType.ListRemove: {
return listDelete(list, op, cache);
}
default: {
console.warn("Unsupported operation");
return list;
}
}
this._ctx.doc.deleteItem(this._ctx.id);
}
const currentLink = links.pop();
const position = currentLink.parentKey;
const items = getListItems(cache, list.id);
const item = items.get(position);
if (item == null) {
throw new Error("TODO");
attachChild(key, child) {
this._items.push([child, key]);
this._items.sort((itemA, itemB) => compare({ position: itemA[1] }, { position: itemB[1] }));
this.notify();
}
const newItem = dispatch(item, op, cache, links);
items.set(position, newItem);
return createList(list.id, listItemsToArray(items));
}
function dispatch(node, op, cache, links) {
switch (node.$$type) {
case RECORD:
return dispatchOnRecord(node, op, cache, links);
case LIST:
return dispatchOnList(node, op, cache, links);
default: {
throw new Error("Unknown CRDT");
detachChild(child) {
const indexToDelete = this._items.findIndex((item) => item[0] === child);
this._items.splice(indexToDelete);
if (child instanceof LiveRecord) {
child[INTERNAL].detach();
}
this.notify();
}
}
function updateRecord(node, op, cache) {
const result = Object.assign({}, node);
for (const key in op.data) {
const value = op.data[key];
const item = deserialize(value, cache);
if (isCrdt(item)) {
cache.links.set(item.id, { parentId: node.id, parentKey: key });
setChildKey(key, child) {
const item = this._items.find((item) => item[0] === child);
if (item) {
item[1] = key;
}
result[key] = item;
this._items.sort((itemA, itemB) => compare({ position: itemA[1] }, { position: itemB[1] }));
this.notify();
}
return result;
}
function listInsert(list, op, cache) {
const items = getListItems(cache, list.id);
const item = deserialize(op.data, cache);
if (isCrdt(item)) {
items.set(op.position, item);
cache.links.set(item.id, { parentId: list.id, parentKey: op.position });
apply(op) { }
notify() {
for (const listener of this._listeners) {
listener();
}
}
return createList(list.id, listItemsToArray(items));
}
function listMove(list, op, cache) {
const items = getListItems(cache, list.id);
const link = getLinkOrThrow(cache, op.itemId);
const item = items.get(link.parentKey);
if (item == null) {
throw new Error("TODO");
push(item) {
const position = this._items.length === 0
? makePosition()
: makePosition(this._items[this._items.length - 1][1]);
this._items.push([item, position]);
this.notify();
if (this._ctx) {
const ops = item[INTERNAL].attach(this._ctx.doc.generateId(), this._ctx.doc, this._ctx.id, position);
this._ctx.doc.dispatch(ops);
}
}
// Delete old position cache entry
items.delete(link.parentKey);
// Insert new position in cache
items.set(op.position, item);
// Update link
cache.links.set(op.itemId, { parentId: list.id, parentKey: op.position });
return createList(list.id, listItemsToArray(items));
}
function getLinkOrThrow(cache, id) {
const link = cache.links.get(id);
if (link == null) {
throw new Error(`Can't find link with id "${id}"`);
insert(item, index) {
if (index < 0 || index > this._items.length) {
throw new Error(`Cannot delete list item at index "${index}". index should be between 0 and ${this._items.length}`);
}
let before = this._items[index - 1] ? this._items[index - 1][1] : undefined;
let after = this._items[index] ? this._items[index][1] : undefined;
const position = makePosition(before, after);
this._items.push([item, position]);
this._items.sort((itemA, itemB) => compare({ position: itemA[1] }, { position: itemB[1] }));
this.notify();
if (this._ctx) {
const ops = item[INTERNAL].attach(this._ctx.doc.generateId(), this._ctx.doc, this._ctx.id, position);
this._ctx.doc.dispatch(ops);
}
}
return link;
}
function listDelete(list, op, cache) {
const items = getListItems(cache, list.id);
const link = getLinkOrThrow(cache, op.itemId);
items.delete(link.parentKey);
cache.links.delete(op.itemId);
return createList(list.id, listItemsToArray(items));
}
function listItemsToArray(items) {
return sortedListItems(items).map((entry) => entry[1]);
}
function sortedListItems(items) {
return Array.from(items.entries()).sort((entryA, entryB) => compare({ position: entryA[0] }, { position: entryB[0] }));
}
function getChildDeep(node, id, links, cache) {
let currentNode = node;
while (currentNode.id !== id) {
const link = links.pop();
if (link == null || link.parentId !== currentNode.id) {
throw new Error("TODO");
move(index, targetIndex) {
if (targetIndex < 0) {
throw new Error("targetIndex cannot be less than 0");
}
if (currentNode.$$type === RECORD) {
currentNode = currentNode[link.parentKey];
if (targetIndex >= this._items.length) {
throw new Error("targetIndex cannot be greater or equal than the list length");
}
if (index < 0) {
throw new Error("index cannot be less than 0");
}
if (index >= this._items.length) {
throw new Error("index cannot be greater or equal than the list length");
}
let beforePosition = null;
let afterPosition = null;
if (index < targetIndex) {
afterPosition =
targetIndex === this._items.length - 1
? undefined
: this._items[targetIndex + 1][1];
beforePosition = this._items[targetIndex][1];
}
else {
const listItems = getListItems(cache, currentNode.id);
const item = listItems.get(link.parentKey);
if (item == null) {
throw new Error("TODO");
}
currentNode = item;
afterPosition = this._items[targetIndex][1];
beforePosition =
targetIndex === 0 ? undefined : this._items[targetIndex - 1][1];
}
const position = makePosition(beforePosition, afterPosition);
const item = this._items[index];
item[1] = position;
this._items.sort((itemA, itemB) => compare({ position: itemA[1] }, { position: itemB[1] }));
this.notify();
if (this._ctx) {
this._ctx.doc.dispatch([
{
type: OpType.SetParentKey,
id: item[0][INTERNAL].ctx.id,
parentKey: position,
},
]);
}
}
return currentNode;
}
function isRecord(value) {
return value != null && typeof value === "object" && value.$$type === RECORD;
}
function isList(value) {
return value != null && typeof value === "object" && value.$$type === LIST;
}
function isCrdt(value) {
return isRecord(value) || isList(value);
}
function serializeRecord(record) {
const serializedData = {};
for (const key in record) {
if (key !== "id" && key !== "$$type") {
const value = record[key]; // TODO: Find out why typescript does not like that
serializedData[key] = serialize(value);
delete(index) {
if (index < 0 || index >= this._items.length) {
throw new Error(`Cannot delete list item at index "${index}". index should be between 0 and ${this._items.length - 1}`);
}
const item = this._items[index];
this._items.splice(index, 1);
if (this._ctx) {
const childRecord = item[0];
this._ctx.doc.dispatch([
{
id: childRecord[INTERNAL].ctx.id,
type: OpType.DeleteRecord,
},
]);
childRecord[INTERNAL].detach();
}
this.notify();
}
return {
id: record.id,
type: CrdtType.Record,
data: serializedData,
};
}
function serializeList(list) {
return {
id: list.id,
type: CrdtType.List,
data: {},
};
}
function serialize(value) {
if (isRecord(value)) {
return serializeRecord(value);
toArray() {
return this._items.map((entry) => entry[0]);
}
else if (isList(value)) {
return serializeList(value);
get(index) {
return this._items[index][0];
}
else {
return { type: CrdtType.Register, data: value };
subscribe(listener) {
this._listeners.push(listener);
}
unsubscribe(listener) {
remove(this._listeners, listener);
}
}
function deserialize(entry, parentToChildren, doc) {
switch (entry[1].type) {
case CrdtType.Record: {
return LiveRecord.deserialize(entry, parentToChildren, doc);
}
case CrdtType.List: {
return LiveList.deserialize(entry, parentToChildren, doc);
}
default: {
throw new Error("Unexpected CRDT type");
}
}
}

@@ -1,4 +0,3 @@

export type { Record, RecordData, List } from "./doc";
export { LiveRecord, LiveList, RecordData } from "./doc";
export type { Others, Presence, Room, Client, User } from "./types";
export { createClient } from "./client";
export { LiveStorageState } from "./types";
export type { Others, Presence, Room, InitialStorageFactory, Client, LiveStorage, User, } from "./types";

@@ -0,2 +1,2 @@

export { LiveRecord, LiveList } from "./doc";
export { createClient } from "./client";
export { LiveStorageState } from "./types";

@@ -41,5 +41,6 @@ import { Presence } from "./types";

};
export declare type SerializedCrdtWithId = [id: string, crdt: SerializedCrdt];
export declare type InitialDocumentStateMessage = {
type: ServerMessageType.InitialStorageState;
root: SerializedRecord | null;
items: SerializedCrdtWithId[];
};

@@ -75,57 +76,64 @@ export declare type UpdateStorageMessage = {

Record = 0,
List = 1,
Register = 2
List = 1
}
export declare type SerializedRecord = {
id: string;
type: CrdtType.Record;
parentId?: string;
parentKey?: string;
data: {
[key: string]: SerializedCrdt;
[key: string]: any;
};
};
export declare type SerializedList = {
id: string;
type: CrdtType.List;
data: {
[position: string]: SerializedCrdt;
};
parentId: string;
parentKey: string;
};
export declare type SerializedRegister = {
id?: string;
type: CrdtType.Register;
data: any;
};
export declare type SerializedCrdt = SerializedRecord | SerializedList | SerializedRegister;
export declare type SerializedCrdt = SerializedRecord | SerializedList;
export declare enum OpType {
Init = 100,
ListInsert = 200,
ListMove = 201,
ListRemove = 202,
RecordUpdate = 300
Init = 0,
SetParentKey = 1,
CreateList = 2,
UpdateRecord = 3,
CreateRecord = 4,
DeleteRecord = 5,
DeleteRecordKey = 6
}
export declare type Op = RecordUpdateOp | ListInsertOp | ListDeleteOp | ListMoveOp;
export declare type Op = CreateRecordOp | RecordUpdateOp | DeleteRecordOp | CreateListOp | SetParentKeyOp | DeleteRecordKeyOp;
export declare type RecordUpdateOp = {
id: string;
type: OpType.RecordUpdate;
type: OpType.UpdateRecord;
data: {
[key: string]: SerializedCrdt;
[key: string]: any;
};
};
export declare type ListInsertOp = {
export declare type CreateRecordOp = {
id: string;
type: OpType.ListInsert;
position: string;
data: SerializedCrdt;
type: OpType.CreateRecord;
parentId?: string;
parentKey?: string;
data: {
[key: string]: any;
};
};
export declare type ListMoveOp = {
export declare type CreateListOp = {
id: string;
type: OpType.ListMove;
itemId: string;
position: string;
type: OpType.CreateList;
parentId: string;
parentKey: string;
};
export declare type ListDeleteOp = {
export declare type DeleteRecordOp = {
id: string;
type: OpType.ListRemove;
itemId: string;
type: OpType.DeleteRecord;
};
export declare type SetParentKeyOp = {
id: string;
type: OpType.SetParentKey;
parentKey: string;
};
export declare type DeleteRecordKeyOp = {
id: string;
type: OpType.DeleteRecordKey;
key: string;
};
export declare enum WebsocketCloseCodes {

@@ -132,0 +140,0 @@ CLOSE_ABNORMAL = 1006,

@@ -22,11 +22,12 @@ export var ServerMessageType;

CrdtType[CrdtType["List"] = 1] = "List";
CrdtType[CrdtType["Register"] = 2] = "Register";
})(CrdtType || (CrdtType = {}));
export var OpType;
(function (OpType) {
OpType[OpType["Init"] = 100] = "Init";
OpType[OpType["ListInsert"] = 200] = "ListInsert";
OpType[OpType["ListMove"] = 201] = "ListMove";
OpType[OpType["ListRemove"] = 202] = "ListRemove";
OpType[OpType["RecordUpdate"] = 300] = "RecordUpdate";
OpType[OpType["Init"] = 0] = "Init";
OpType[OpType["SetParentKey"] = 1] = "SetParentKey";
OpType[OpType["CreateList"] = 2] = "CreateList";
OpType[OpType["UpdateRecord"] = 3] = "UpdateRecord";
OpType[OpType["CreateRecord"] = 4] = "CreateRecord";
OpType[OpType["DeleteRecord"] = 5] = "DeleteRecord";
OpType[OpType["DeleteRecordKey"] = 6] = "DeleteRecordKey";
})(OpType || (OpType = {}));

@@ -33,0 +34,0 @@ export var WebsocketCloseCodes;

@@ -1,4 +0,3 @@

import { RecordData, List } from ".";
import { Doc, Record } from "./doc";
import { Others, Presence, ClientOptions, Room, InitialStorageFactory, MyPresenceCallback, OthersEventCallback, StorageCallback, AuthEndpoint, LiveStorageState, LiveStorage, EventCallback, User, Connection, Serializable, ErrorCallback, AuthenticationToken, ConnectionCallback } from "./types";
import { RecordData } from "./doc";
import { Others, Presence, ClientOptions, Room, MyPresenceCallback, OthersEventCallback, AuthEndpoint, EventCallback, User, Connection, ErrorCallback, AuthenticationToken, ConnectionCallback } from "./types";
import { ClientMessage, Op } from "./live";

@@ -24,3 +23,2 @@ declare type IdFactory = () => string;

listeners: {
storage: StorageCallback[];
event: EventCallback[];

@@ -39,5 +37,5 @@ others: OthersEventCallback[];

numberOfRetry: number;
doc: Doc<any> | null;
storageState: LiveStorageState;
initialStorageFactory: InitialStorageFactory | null;
defaultStorageRoot?: {
[key: string]: any;
};
};

@@ -73,6 +71,5 @@ export declare type Effects = {

subscribe: {
<T extends Serializable>(type: "my-presence", listener: MyPresenceCallback<T>): void;
<T_1 extends Serializable>(type: "others", listener: OthersEventCallback<T_1>): void;
<T extends Presence>(type: "my-presence", listener: MyPresenceCallback<T>): void;
<T_1 extends Presence>(type: "others", listener: OthersEventCallback<T_1>): void;
(type: "event", listener: EventCallback): void;
<T_2 extends RecordData>(type: "storage", listener: StorageCallback<T_2>): void;
(type: "error", listener: ErrorCallback): void;

@@ -82,28 +79,23 @@ (type: "connection", listener: ConnectionCallback): void;

unsubscribe: {
<T_3 extends Serializable>(type: "my-presence", listener: MyPresenceCallback<T_3>): void;
<T_4 extends Serializable>(type: "others", listener: OthersEventCallback<T_4>): void;
<T_2 extends Presence>(type: "my-presence", listener: MyPresenceCallback<T_2>): void;
<T_3 extends Presence>(type: "others", listener: OthersEventCallback<T_3>): void;
(type: "event", listener: EventCallback): void;
<T_5 extends RecordData>(type: "storage", listener: StorageCallback<T_5>): void;
(type: "error", listener: ErrorCallback): void;
(type: "connection", listener: ConnectionCallback): void;
};
updatePresence: <T_6 extends Serializable>(overrides: Partial<T_6>) => void;
updatePresence: <T_4 extends Presence>(overrides: Partial<T_4>) => void;
broadcastEvent: (event: any) => void;
fetchStorage: (initialStorageFactory: InitialStorageFactory) => void;
createRecord: <T_7 extends RecordData>(data: any) => Record<T_7>;
updateRecord: <T_8 extends RecordData>(record: Record<T_8>, overrides: Partial<T_8>) => void;
createList: <T_9 extends RecordData>() => List<Record<T_9>>;
pushItem: <T_10 extends RecordData>(list: List<Record<T_10>>, item: Record<T_10>) => void;
deleteItem: <T_11 extends RecordData>(list: List<Record<T_11>>, index: number) => void;
deleteItemById: <T_12 extends RecordData>(list: List<Record<T_12>>, itemId: string) => void;
moveItem: <T_13 extends RecordData>(list: List<Record<T_13>>, index: number, targetIndex: number) => void;
getStorage: <TRoot>() => Promise<{
root: import("./doc").LiveRecord<TRoot>;
}>;
selectors: {
getConnectionState: () => "failed" | "closed" | "open" | "connecting" | "authenticating" | "unavailable";
getSelf: <TPresence extends Serializable = Serializable>() => User<TPresence> | null;
getPresence: <T_14 extends Serializable>() => T_14;
getOthers: <T_15 extends Serializable>() => Others<T_15>;
getStorage: () => LiveStorage;
getSelf: <TPresence extends Presence = Presence>() => User<TPresence> | null;
getPresence: <T_5 extends Presence>() => T_5;
getOthers: <T_6 extends Presence>() => Others<T_6>;
};
};
export declare function defaultState(me?: Presence): State;
export declare function defaultState(me?: Presence, defaultStorageRoot?: {
[key: string]: any;
}): State;
export declare type InternalRoom = {

@@ -117,4 +109,5 @@ room: Room;

export declare function createRoom(name: string, options: ClientOptions & {
initialPresence?: Presence;
defaultPresence?: Presence;
defaultStorageRoot?: RecordData;
}): InternalRoom;
export {};

@@ -10,8 +10,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {

};
import { Doc } from "./doc";
import { LiveStorageState, } from "./types";
import { createRecord as innerCreateRecord, createList as innerCreateList, } from "./doc";
import { remove } from "./utils";
import auth, { parseToken } from "./authentication";
import { ClientMessageType, ServerMessageType, } from "./live";
import Storage from "./storage";
const BACKOFF_RETRY_DELAYS = [250, 500, 1000, 2000, 4000, 8000, 10000];

@@ -22,4 +20,3 @@ const HEARTBEAT_INTERVAL = 30000;

function isValidRoomEventType(value) {
return (value === "storage" ||
value === "my-presence" ||
return (value === "my-presence" ||
value === "others" ||

@@ -252,10 +249,2 @@ value === "event" ||

switch (message.type) {
case ServerMessageType.InitialStorageState: {
onInitialStorageState(message);
break;
}
case ServerMessageType.UpdateStorage: {
onStorageUpdates(message);
break;
}
case ServerMessageType.UserJoined: {

@@ -282,2 +271,3 @@ onUserJoinedMessage(message);

}
storage.onMessage(message);
}

@@ -465,82 +455,29 @@ // function onWakeUp() {

}
/**
* STORAGE
*/
function onStorageUpdates(message) {
if (state.doc == null) {
// TODO: Cache updates in case they are coming while root is queried
return;
}
updateDoc(message.ops.reduce((doc, op) => doc.dispatch(op), state.doc));
function dispatch(ops) {
state.flushData.storageOperations.push(...ops);
tryFlushing();
}
function updateDoc(doc) {
state.doc = doc;
if (doc) {
for (const listener of state.listeners.storage) {
listener(getStorage());
const storage = new Storage({
fetchStorage: () => {
state.flushData.messages.push({ type: ClientMessageType.FetchStorage });
tryFlushing();
},
dispatch,
getConnectionId: () => {
const me = getSelf();
if (me) {
return me.connectionId;
}
}
}
throw new Error("Unexpected");
},
defaultRoot: state.defaultStorageRoot,
});
function getStorage() {
if (state.storageState === LiveStorageState.Loaded) {
return __awaiter(this, void 0, void 0, function* () {
const doc = yield storage.getDocument();
return {
state: state.storageState,
root: state.doc.root,
root: doc.root,
};
}
return {
state: state.storageState,
};
});
}
function onInitialStorageState(message) {
state.storageState = LiveStorageState.Loaded;
if (message.root == null) {
const rootId = makeId();
state.doc = Doc.empty(rootId, (op) => dispatch(op));
updateDoc(state.doc.updateRecord(rootId, state.initialStorageFactory({
createRecord: (data) => createRecord(data),
createList: () => createList(),
})));
}
else {
updateDoc(Doc.load(message.root, (op) => dispatch(op)));
}
}
function makeId() {
if (state.idFactory == null) {
throw new Error("Can't generate id. Id factory is missing.");
}
return state.idFactory();
}
function dispatch(op) {
state.flushData.storageOperations.push(op);
tryFlushing();
}
function createRecord(data) {
return innerCreateRecord(makeId(), data);
}
function createList() {
return innerCreateList(makeId());
}
function fetchStorage(initialStorageFactory) {
state.initialStorageFactory = initialStorageFactory;
state.storageState = LiveStorageState.Loading;
state.flushData.messages.push({ type: ClientMessageType.FetchStorage });
tryFlushing();
}
function updateRecord(record, overrides) {
updateDoc(state.doc.updateRecord(record.id, overrides));
}
function pushItem(list, item) {
updateDoc(state.doc.pushItem(list.id, item));
}
function deleteItem(list, index) {
updateDoc(state.doc.deleteItem(list.id, index));
}
function deleteItemById(list, itemId) {
updateDoc(state.doc.deleteItemById(list.id, itemId));
}
function moveItem(list, index, targetIndex) {
updateDoc(state.doc.moveItem(list.id, index, targetIndex));
}
return {

@@ -564,11 +501,3 @@ // Internal

broadcastEvent,
// Storage
fetchStorage,
createRecord,
updateRecord,
createList,
pushItem,
deleteItem,
deleteItemById,
moveItem,
getStorage,
selectors: {

@@ -581,8 +510,6 @@ // Core

getOthers,
// Storage
getStorage,
},
};
}
export function defaultState(me) {
export function defaultState(me, defaultStorageRoot) {
return {

@@ -592,3 +519,2 @@ connection: { state: "closed" },

listeners: {
storage: [],
event: [],

@@ -618,5 +544,3 @@ others: [],

others: makeOthers({}),
storageState: LiveStorageState.NotInitialized,
initialStorageFactory: null,
doc: null,
defaultStorageRoot,
idFactory: null,

@@ -627,5 +551,5 @@ };

const throttleDelay = options.throttle || 100;
const liveblocksServer = options.liveblocksServer || "wss://liveblocks.net";
const liveblocksServer = options.liveblocksServer || "wss://liveblocks.net/v2";
const authEndpoint = options.authEndpoint;
const state = defaultState(options.initialPresence);
const state = defaultState(options.defaultPresence, options.defaultStorageRoot);
const machine = makeStateMachine(state, {

@@ -645,14 +569,2 @@ throttleDelay,

unsubscribe: machine.unsubscribe,
/////////////
// Storage //
/////////////
getStorage: machine.selectors.getStorage,
fetchStorage: machine.fetchStorage,
createRecord: machine.createRecord,
createList: machine.createList,
updateRecord: machine.updateRecord,
pushItem: machine.pushItem,
deleteItem: machine.deleteItem,
deleteItemById: machine.deleteItemById,
moveItem: machine.moveItem,
//////////////

@@ -665,2 +577,3 @@ // Presence //

broadcastEvent: machine.broadcastEvent,
getStorage: machine.getStorage,
};

@@ -667,0 +580,0 @@ return {

@@ -1,3 +0,2 @@

import { RecordData, Record, List } from "./doc";
export declare type StorageCallback<T extends RecordData = RecordData> = (storage: LiveStorage<T>) => void;
import { LiveRecord, RecordData } from "./doc";
export declare type MyPresenceCallback<T extends Presence = Presence> = (me: T) => void;

@@ -12,3 +11,2 @@ export declare type OthersEventCallback<T extends Presence = Presence> = (others: Others<T>, event: OthersEvent<T>) => void;

export declare type RoomEventCallbackMap = {
storage: StorageCallback;
"my-presence": MyPresenceCallback;

@@ -20,8 +18,2 @@ others: OthersEventCallback;

};
export declare type CreateRecord = Room["createRecord"];
export declare type CreateList = Room["createList"];
export declare type InitialStorageFactory<TRoot = RecordData> = (factories: {
createRecord: CreateRecord;
createList: CreateList;
}) => TRoot;
export declare type Client = {

@@ -39,3 +31,6 @@ /**

*/
enter(roomId: string, defaultPresence?: Presence): Room;
enter<TStorageRoot = RecordData>(roomId: string, options?: {
defaultPresence?: Presence;
defaultStorageRoot?: TStorageRoot;
}): Room;
/**

@@ -91,6 +86,4 @@ * Leaves a room.

};
export declare type Presence = Serializable;
export declare type SerializablePrimitive = boolean | string | number | null;
export declare type Serializable = {
[key: string]: SerializablePrimitive | Serializable | SerializablePrimitive[];
export declare type Presence = {
[key: string]: any;
};

@@ -112,13 +105,2 @@ declare type AuthEndpointCallback = (room: string) => Promise<{

};
export declare enum LiveStorageState {
NotInitialized = 0,
Loading = 1,
Loaded = 2
}
export declare type LiveStorage<T extends RecordData = RecordData> = {
state: LiveStorageState.Loading | LiveStorageState.NotInitialized;
} | {
state: LiveStorageState.Loaded;
root: Record<T>;
};
declare type ConnectionState = "closed" | "authenticating" | "unavailable" | "failed" | "open" | "connecting";

@@ -154,8 +136,6 @@ export declare type Connection = {

*
* ### Example
* ``` typescript
* @example
* room.subscribe("my-presence", (presence) => {
* // Do something
* });
* ```
*/

@@ -168,8 +148,6 @@ <T extends Presence>(type: "my-presence", listener: MyPresenceCallback<T>): void;

*
* ### Example
* ``` typescript
* @example
* room.subscribe("others", (others) => {
* // Do something
* });
* ```
*/

@@ -182,11 +160,8 @@ <T extends Presence>(type: "others", listener: OthersEventCallback<T>): void;

*
* ### Example
* ``` typescript
* @example
* room.subscribe("event", ({ event, connectionId }) => {
* // Do something
* });
* ```
*/
(type: "event", listener: EventCallback): void;
<T extends RecordData>(type: "storage", listener: StorageCallback<T>): void;
/**

@@ -207,8 +182,6 @@ * Subscribe to errors thrown in the room.

*
* ### Example
* ``` typescript
* @example
* const onPresenceChange = (presence) => { };
* room.subscribe("my-presence", onPresenceChange);
* room.unsubscribe("my-presence", onPresenceChange);
* ```
*/

@@ -221,8 +194,6 @@ <T extends Presence>(type: "my-presence", listener: MyPresenceCallback<T>): void;

*
* ### Example
* ``` typescript
* @example
* const onOthersChange = (presence) => { };
* room.subscribe("others", onOthersChange);
* room.unsubscribe("others", onOthersChange);
* ```
*/

@@ -235,11 +206,8 @@ <T extends Presence>(type: "others", listener: OthersEventCallback<T>): void;

*
* ### Example
* ``` typescript
* @example
* const onEvent = ({ event, connectionId }) => { };
* room.subscribe("event", onEvent);
* room.unsubscribe("event", onEvent);
* ```
*/
(type: "event", listener: EventCallback): void;
<T extends RecordData>(type: "storage", listener: StorageCallback<T>): void;
/**

@@ -258,6 +226,4 @@ * Unsubscribe to errors thrown in the room.

*
* ### Example
* ``` typescript
* @example
* const user = room.getSelf();
* ```
*/

@@ -268,6 +234,4 @@ getSelf<TPresence extends Presence = Presence>(): User<TPresence> | null;

*
* ### Example
* ``` typescript
* @example
* const presence = room.getPresence();
* ```
*/

@@ -278,6 +242,4 @@ getPresence: <T extends Presence>() => T;

*
* ### Example
* ``` typescript
* @example
* const others = room.getOthers();
* ```
*/

@@ -287,6 +249,5 @@ getOthers: <T extends Presence>() => Others<T>;

* Updates the presence of the current user. Only pass the properties you want to update. No need to send the full presence.
* @param {Partial<T>} overrides A partial object that contains the properties you want to update.
* @param {Partial<T>} overrides - A partial object that contains the properties you want to update.
*
* ### Example
* ``` typescript
* @example
* room.updatePresence({ x: 0 });

@@ -297,3 +258,2 @@ * room.updatePresence({ y: 0 });

* // presence is equivalent to { x: 0, y: 0 }
* ```
*/

@@ -303,7 +263,5 @@ updatePresence: <T extends Presence>(overrides: Partial<T>) => void;

* Broadcast an event to other users in the room. Event broadcasted to the room can be listened with {@link Room.subscribe}("event").
* @param {any} event the event to broadcast. Should be serializable to JSON
* @param {any} event - the event to broadcast. Should be serializable to JSON
*
* ### Example
* ``` typescript
*
* @example
* // On client A

@@ -318,15 +276,8 @@ * room.broadcastEvent({ type: "EMOJI", emoji: "🔥" });

* });
* ```
*/
broadcastEvent: (event: any) => void;
getStorage: () => LiveStorage;
fetchStorage(initialStorageFactory: InitialStorageFactory): void;
createRecord: <T extends RecordData>(data: T) => Record<T>;
createList: <T extends RecordData>() => List<Record<T>>;
updateRecord<T extends RecordData>(record: Record<T>, overrides: Partial<T>): void;
pushItem<T extends RecordData>(list: List<Record<T>>, item: Record<T>): void;
deleteItem<T extends RecordData>(list: List<Record<T>>, index: number): void;
deleteItemById<T extends RecordData>(list: List<Record<T>>, itemId: string): void;
moveItem<T extends RecordData>(list: List<Record<T>>, index: number, targetIndex: number): void;
getStorage: <TRoot>() => Promise<{
root: LiveRecord<TRoot>;
}>;
};
export {};

@@ -1,6 +0,1 @@

export var LiveStorageState;
(function (LiveStorageState) {
LiveStorageState[LiveStorageState["NotInitialized"] = 0] = "NotInitialized";
LiveStorageState[LiveStorageState["Loading"] = 1] = "Loading";
LiveStorageState[LiveStorageState["Loaded"] = 2] = "Loaded";
})(LiveStorageState || (LiveStorageState = {}));
export {};
{
"name": "@liveblocks/client",
"version": "0.11.0",
"version": "0.12.0-beta.1",
"description": "",

@@ -5,0 +5,0 @@ "main": "./lib/cjs/index.js",

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc