diff --git a/README.v13.md b/README.v13.md
index 3ae9ae1f..77a59ec8 100644
--- a/README.v13.md
+++ b/README.v13.md
@@ -1,13 +1,13 @@
# 
-> The shared editing library
+> A CRDT framework with a powerful abstraction of shared data
-Yjs is a library for automatic conflict resolution on shared state. It implements an [operation-based CRDT](#Yjs-CRDT-Algorithm) and exposes its internal model as shared types. Shared types are common data types like `Map` or `Array` with superpowers: changes are automatically distributed to other peers and merged without merge conflicts.
+Yjs is a [CRDT implementation](#Yjs-CRDT-Algorithm) that exposes its internal data structure as *shared types*. Shared types are common data types like `Map` or `Array` with superpowers: changes are automatically distributed to other peers and merged without merge conflicts.
-Yjs is **network agnostic** (p2p!), supports many existing **rich text editors**, **offline editing**, **version snapshots**, **shared cursors**, and encodes update messages using **binary protocol encoding**.
+Yjs is **network agnostic** (p2p!), supports many existing **rich text editors**, **offline editing**, **version snapshots**, **undo/redo** and **shared cursors**. It scales well with an unlimited number of users and is well suited for even large documents.
* Chat: [https://gitter.im/y-js/yjs](https://gitter.im/y-js/yjs)
* Demos: [https://github.com/y-js/yjs-demos](https://github.com/y-js/yjs-demos)
-* API Docs: [https://yjs.website/](https://yjs.website/)
+* Benchmarks: [https://github.com/dmonad/crdt-benchmarks](https://github.com/dmonad/crdt-benchmarks)
# Table of Contents
@@ -16,18 +16,15 @@ Yjs is **network agnostic** (p2p!), supports many existing **rich text editors**
* [Providers](#Providers)
* [Getting Started](#Getting-Started)
* [API](#API)
-* [Transaction](#Transaction)
-* [Offline Editing](#Offline-Editing)
-* [Awareness](#Awareness)
-* [Working with Yjs](#Working-with-Yjs)
+ * [Shared Types](#Shared-Types)
+ * [Y.Doc](#Y.Doc)
+ * [Document Updates](#Document-Updates)
+ * [Relative Positions](#Relative-Positions)
+* [Miscellaneous](#Miscellaneous)
* [Typescript Declarations](#Typescript-Declarations)
-* [Binary Protocols](#Binary-Protocols)
- * [Sync Protocol](#Sync-Protocols)
- * [Awareness Protocol](#Awareness-Protocols)
- * [Auth Protocol](#Auth-Protocol)
* [Yjs CRDT Algorithm](#Yjs-CRDT-Algorithm)
* [Evaluation](#Evaluation)
- * [Existing shared editing libraries](#Exisisting-Javascript-Products)
+ * [Existing shared editing libraries](#Exisisting-Javascript-Libraries)
* [CRDT Algorithms](#CRDT-Algorithms)
* [Comparison of CRDT with OT](#Comparing-CRDT-with-OT)
* [Comparison of CRDT Algorithms](#Comparing-CRDT-Algorithms)
@@ -52,7 +49,7 @@ This repository contains a collection of shared types that can be observed for c
### Providers
-Setting up the communication between clients, managing awareness information, and storing shared data for offline usage is quite a hassle. **Providers** manage all that for you and are a good off-the-shelf solution.
+Setting up the communication between clients, managing awareness information, and storing shared data for offline usage is quite a hassle. **Providers** manage all that for you and are the perfect starting point for your collaborative app.
[WIP] Creates a connected graph of webrtc connections with a high strength. It requires a signalling server that connects a client to the first peer. But after that the network manages itself. It is well suited for large and small networks.
[WIP] Writes updates effinciently in the dat network using multifeed. Each client has an append-only log of CRDT local updates (hypercore). Multifeed manages and sync hypercores and y-dat listens to changes and applies them to the Yjs document.
+
[WIP] Write document updates effinciently to the dat network using multifeed. Each client has an append-only log of CRDT local updates (hypercore). Multifeed manages and sync hypercores and y-dat listens to changes and applies them to the Yjs document.
## Getting Started
-Install Yjs and a provider with your favorite package manager. In this section we are going to bind a YText to a DOM textarea.
+Install Yjs and a provider with your favorite package manager.
```sh
-npm i yjs@13.0.0-80 y-websocket@1.0.0-1 y-textarea
+npm i yjs@13.0.0-81 y-websocket@1.0.0-2 y-textarea
```
**Start the y-websocket server**
@@ -77,7 +74,9 @@ npm i yjs@13.0.0-80 y-websocket@1.0.0-1 y-textarea
PORT=1234 node ./node_modules/y-websocket/bin/server.js
```
-**Textarea Binding Example**
+**Example: Textarea Binding**
+
+This is a complete example on how to create a connection to a [y-websocket](https://github.com/y-js/y-websocket) server instance, sync the shared document to all clients in a *room*, and bind a Y.Text type to a dom textarea. All changes to the textarea are automatically shared with everyone in the same room.
```js
import * as Y from 'yjs'
@@ -85,15 +84,39 @@ import { WebsocketProvider } from 'y-websocket'
import { TextareaBinding } from 'y-textarea'
const provider = new WebsocketProvider('http://localhost:1234')
-const sharedDocument = provider.get('my-favourites')
+const doc = provider.get('roomname')
// Define a shared type on the document.
-const ytext = sharedDocument.getText('my resume')
+const ytext = doc.getText('my resume')
-// bind to a textarea
+// use data bindings to bind types to editors
const binding = new TextareaBinding(ytext, document.querySelector('textarea'))
```
+**Example: Observe types**
+
+```js
+const yarray = doc.getArray('my-array')
+yarray.observe(event => {
+ console.log('yarray was modified')
+})
+// every time a local or remote client modifies yarray, the observer is called
+yarray.insert(0, ['val']) // => "yarray was modified"
+```
+
+**Example: Nest types**
+
+Remember, shared types are just plain old data types. The only limitation is that a shared type must exist only once in the shared document.
+
+```js
+const ymap = doc.getMap('map')
+const foodArray = new Y.Array()
+foodArray.insert(0, ['apple', 'banana'])
+ymap.set('food', foodArray)
+ymap.get('food') === foodArray // => true
+ymap.set('fruit', foodArray) // => Error! foodArray is already defined on the shared document
+```
+
Now you understand how types are defined on a shared document. Next you can jump to the [demo repository](https://github.com/y-js/yjs-demos) or continue reading the API docs.
## API
@@ -102,6 +125,8 @@ Now you understand how types are defined on a shared document. Next you can jump
import * as Y from 'yjs'
```
+### Shared Types
+
Y.Array
@@ -110,11 +135,11 @@ import * as Y from 'yjs'
Every change on the shared document happens in a transaction. Observer calls and the update event are called after each transaction. You should bundle changes into a single transaction to reduce the amount of event calls. I.e. doc.transact(() => { yarray.insert(..); ymap.set(..) }) triggers a single change event. You can specify an optional origin parameter that is stored on transaction.origin and on('update', (update, origin) => ..).
+ get(string, Y.[TypeClass]):[Type]
+
Define a shared type.
+ getArray(string):Y.Array
+
Define a shared Y.Array type. Is equivalent to y.get(string, Y.Array).
+ getMap(string):Y.Map
+
Define a shared Y.Map type. Is equivalent to y.get(string, Y.Map).
+ getXmlFragment(string):Y.XmlFragment
+
Define a shared Y.XmlFragment type. Is equivalent to y.get(string, Y.XmlFragment).
-Sync steps
+### Document Updates
-### Awareness Protocol
+Changes on the shared document are encoded into *document updates*. Document updates are *commutative* and *idempotent*. This means that they can be applied in any order and multiple times.
-### Auth Protocol
+**Example: Listen to update events and apply them on remote client**
+```js
+const doc1 = new Y.Doc()
+const doc2 = new Y.Doc()
-## Offline Editing
+doc1.on('update', update => {
+ Y.applyUpdate(doc2, update)
+})
-It is trivial with Yjs to persist the local state to indexeddb, so it is always available when working offline. But there are two non-trivial questions that need to answered when implementing a professional offline editing app:
+doc2.on('update', update => {
+ Y.applyUpdate(doc1, update)
+})
-1. How does a client sync down all rooms that were modified while offline?
-2. How does a client sync up all rooms that were modified while offline?
+// All changes are also applied to the other document
+doc1.getArray('myarray').insert(0, ['Hello doc2, you got this?'])
+doc2.getArray('myarray').get(0) // => 'Hello doc2, you got this?'
+```
-Assuming 5000 documents are stored on each client for offline usage. How do we sync up/down each of those documents after a client comes online? It would be inefficient to sync each of those rooms separately. The only provider that currently supports syncing many rooms efficiently is Ydb, because its database layer is optimized to sync many rooms with each client.
+Yjs internally maintains a [State Vector](#State-Vector) that denotes the next expected clock from each client. In a different interpretation it holds the number of structs created by each client. When two clients sync, you can either exchange the complete document structure or only the differences by sending the state vector to compute the differences.
-If you do not care about 1. and 2. you can use `/persistences/indexeddb.js` to mirror the local state to indexeddb.
+**Example: Sync two clients by exchanging the complete document structure**
-## Working with Yjs
+```js
+const state1 = Y.encodeStateAsUpdate(ydoc1)
+const state2 = Y.encodeStateAsUpdate(ydoc2)
+Y.applyUpdate(ydoc1, state2)
+Y.applyUpdate(ydoc2, state1)
+```
+
+**Example: Sync two clients by computing the differences**
+
+This example shows how to sync two clients with the minimal amount of exchanged data by computing only the differences using the state vector of the remote client. Syncing clients using the state vector requires another roundtrip, but can safe a lot of bandwidth.
+
+```js
+const stateVector1 = Y.encodeDocumentStateVector(ydoc1)
+const stateVector2 = Y.encodeDocumentStateVector(ydoc2)
+const diff1 = Y.encodeStateAsUpdate(ydoc1, stateVector2)
+const diff2 = Y.encodeStateAsUpdate(ydoc2, stateVector1)
+Y.applyUpdate(ydoc1, diff2)
+Y.applyUpdate(ydoc2, diff1)
+```
+
+
Apply a document update on the shared document. Optionally you can specify transactionOrigin that will be stored on transaction.origin and ydoc.on('update', (update, origin) => ..).
Encode the document state as a single update message that can be applied on the remote document. Optionally specify the target state vector to only write the differences to the update message.
+ Y.encodeDocumentStateVector(Y.Doc):Uint8Array
+
Computes the state vector and encodes it into an Uint8Array.
+
+
+### Relative Positions
+> This API is not stable yet
+
+This feature is intended for managing selections / cursors. When working with other users that manipulate the shared document, you can't trust that an index position (an integer) will stay at the intended location. A *relative position* is fixated to an element in the shared document and is not affected by remote changes. I.e. given the document `"a|c"`, the relative position is attached to `c`. When a remote user modifies the document by inserting a character before the cursor, the cursor will stay attached to the character `c`. `insert(1, 'x')("a|c") = "ax|c"`. When the *relative position* is set to the end of the document, it will stay attached to the end of the document.
+
+**Example: Transform to RelativePosition and back**
+```js
+const relPos = Y.createRelativePositionFromTypeIndex(ytext, 2)
+const pos = Y.createAbsolutePositionFromRelativePosition(relPos, doc)
+pos.type === ytext // => true
+pos.index === 2 // => true
+```
+
+**Example: Send relative position to remote client (json)**
+```js
+const relPos = Y.createRelativePositionFromTypeIndex(ytext, 2)
+const encodedRelPos = JSON.stringify(relPos)
+// send encodedRelPos to remote client..
+const parsedRelPos = JSON.parse(encodedRelPos)
+const pos = Y.createAbsolutePositionFromRelativePosition(parsedRelPos, remoteDoc)
+pos.type === remoteytext // => true
+pos.index === 2 // => true
+```
+
+**Example: Send relative position to remote client (Uint8Array)**
+```js
+const relPos = Y.createRelativePositionFromTypeIndex(ytext, 2)
+const encodedRelPos = Y.encodeRelativePosition(relPos)
+// send encodedRelPos to remote client..
+const parsedRelPos = Y.decodeRelativePosition(encodedRelPos)
+const pos = Y.createAbsolutePositionFromRelativePosition(parsedRelPos, remoteDoc)
+pos.type === remoteytext // => true
+pos.index === 2 // => true
+```
+
+
+
+## Miscellaneous
### Typescript Declarations
-Until [this](https://github.com/Microsoft/TypeScript/issues/7546) is fixed, the only way to get type declarations is by adding Yjs to the list of checked files:
+Yjs has type descriptions. But until [this ticket](https://github.com/Microsoft/TypeScript/issues/7546) is fixed, this is how you can make use of Yjs type declarations.
```json
{
@@ -395,10 +530,26 @@ Until [this](https://github.com/Microsoft/TypeScript/issues/7546) is fixed, the
## Yjs CRDT Algorithm
+*Conflict-free replicated data types* (CRDT) for collaborative editing are an alternative approach to *operational transformation* (OT). A very simple differenciation between the two approaches is that OT attempts to transform index positions to ensure convergence (all clients end up with the same content), while CRDTs use models that usually do not involve index transformations, like linked lists. OT is currently the de-facto standard for shared editing on text. OT approaches that support shared editing without a central source of truth (a central server) require too much bookkeeping to be viable in practice. CRDTs are better suited for distributed systems, provide additional guarantees that the document can be synced with remote clients, and do not require a central source of truth / central source of failure.
+
+Yjs implements a modified version of the algorithm described in [this paper](https://www.researchgate.net/publication/310212186_Near_Real-Time_Peer-to-Peer_Shared_Editing_on_Extensible_Data_Types). I will eventually publish a paper that describes the new approach. Note: Since operations make up the document structure, we prefer the term *struct* now.
+
+CRDTs suitable for shared text editing suffer from the fact that they only grow in size. There are CRDTs that do not grow in size, but they do not have the characteristics that are benificial for shared text editing (like intention preservation). Yjs implements many improvements to the original algorithm that diminish the trade-off that the document only grows in size. We can't garbage collect deleted structs (tombstones) while ensuring a unique order of the structs. But we can 1. merge preceeding structs into a single struct to reduce the amount of meta information, 2. we can delete content from the struct if it is deleted, and 3. we can garbage collect tombstones if we don't care about the order of the structs anymore (e.g. if the parent was deleted).
+
+**Examples:**
+1. If a user inserts elements in sequence, the struct will be merged into a single struct. E.g. `array.insert(0, ['a']), array.insert(0, ['b']);` is first represented as two structs (`[{id: {client, clock: 0}, content: 'a'}, {id: {client, clock: 1}, content: 'b'}`) and then merged into a single struct: `[{id: {client, clock: 0}, content: 'ab'}]`.
+2. When a struct that contains content (e.g. `ItemString`) is deleted, the struct will be replaced with an `ItemDeleted` that does not contain content anymore.
+3. When a type is deleted, all child elements are transformed to `GC` structs. A `GC` struct only denotes the existence of a struct and that it is deleted. `GC` structs can always be merged with other `GC` structs if the id's are adjacent.
+
+Especially when working on structured content (e.g. shared editing on ProseMirror), these improvements yield very good results when [benchmarking](#Benchmarks) random document edits. In practice they show even better results, because users usually edit text in sequence, resulting in structs that can easily be merged. The benchmarks showt that even in the worst case scenario that a user edits text from right to left, Yjs achieves good performance even for huge documents.
+
+#### State Vector
+Yjs has the ability to exchange only the differences when syncing two clients. We use lamport timestamps to identify structs and to track in which order a client created them. Each struct has an `struct.id = { client: number, clock: number}` that uniquely identifies a struct. We define the next expected `clock` by each client as the *state vector*. This data structure is similar to the [version vectors](https://en.wikipedia.org/wiki/Version_vector) data structure. But we use state vectors only to describe the state of the local document, so we can compute the missing struct of the remote client. We do not use it to track causality.
+
## License and Author
Yjs and all related projects are [**MIT licensed**](./LICENSE).
-Yjs is based on the research I did as a student at the [RWTH i5](http://dbis.rwth-aachen.de/). Now I am working on Yjs in my spare time.
+Yjs is based on my research as a student at the [RWTH i5](http://dbis.rwth-aachen.de/). Now I am working on Yjs in my spare time.
-Support me on [Patreon](https://www.patreon.com/dmonad) to fund this project or hire [me](https://github.com/dmonad) for professional support.
+Fund this project by donating on [Patreon](https://www.patreon.com/dmonad) or hiring [me](https://github.com/dmonad) for professional support.
diff --git a/examples/prosemirror-history.js b/examples/prosemirror-history.js
index d84f3fbf..48a45cf7 100644
--- a/examples/prosemirror-history.js
+++ b/examples/prosemirror-history.js
@@ -154,6 +154,6 @@ class NoteHistoryPlugin {
const encoder = encoding.createEncoder()
historyProtocol.writeHistorySnapshot(encoder, y, updatedUserMap)
encoding.writeUint32(encoder, Math.floor(Date.now() / 1000))
- history.push([encoding.toBuffer(encoder)])
+ history.push([encoding.toUint8Array(encoder)])
}
}
diff --git a/package-lock.json b/package-lock.json
index 06a53285..ee3bdcca 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -3036,9 +3036,9 @@
}
},
"lib0": {
- "version": "0.0.2",
- "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.0.2.tgz",
- "integrity": "sha512-7bJgV2emHGRO5kpj66Gdc9SQKVfhDBLx0UIS/aU5P8R0179nRFHKDTYGvLlNloWbeUUARlqk3ndFIO4JbUy7Sw=="
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.0.4.tgz",
+ "integrity": "sha512-osSGIxFM0mUuVAclVOQAio4lq0YYk1xFfj6J+1i3u5az8rXAQKDil2skA19aiiG0sfAdasOtr8Mk+9Mrw10cfQ=="
},
"live-server": {
"version": "1.2.1",
@@ -5579,10 +5579,13 @@
"dev": true
},
"y-protocols": {
- "version": "0.0.3",
- "resolved": "https://registry.npmjs.org/y-protocols/-/y-protocols-0.0.3.tgz",
- "integrity": "sha512-b6yUR4KuRN9ehf2b0/YyaxxO2bxk7ti5DDTg5Jm/FEtPf0Vk6s4ez5wIhkV1nA7bloz/NDmVpoCDPFX1zZORhg==",
- "dev": true
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/y-protocols/-/y-protocols-0.0.4.tgz",
+ "integrity": "sha512-a7F8t16y6rVPYGvwsbfeN5EauHFBo+SIsYadvaONdp6jCyhcKokDK8u30BQOJCOxwd1FkuAhMs14m6CtJbfRqg==",
+ "dev": true,
+ "requires": {
+ "lib0": "0.0.4"
+ }
},
"yallist": {
"version": "2.1.2",
diff --git a/package.json b/package.json
index 6b70b4d3..a574d425 100644
--- a/package.json
+++ b/package.json
@@ -14,7 +14,7 @@
"lint": "standard && tsc",
"docs": "rm -rf docs; jsdoc --configure ./.jsdoc.json --verbose --readme ./README.v13.md --package ./package.json || true",
"serve-docs": "npm run docs && serve ./docs/",
- "preversion": "PRODUCTION=1 npm run dist && node ./dist/tests.js --repitition-time 1000",
+ "preversion": "PRODUCTION=1 npm run dist && npm run docs && node ./dist/tests.js --repitition-time 1000",
"postversion": "git push && git push --tags",
"debug": "concurrently 'live-server --port=3443 --entry-file=test.html' 'npm run watch'",
"trace-deopt": "clear && rollup -c && node --trace-deopt dist/test.js",
@@ -54,10 +54,10 @@
},
"homepage": "http://y-js.org",
"dependencies": {
- "lib0": "0.0.2"
+ "lib0": "0.0.4"
},
"devDependencies": {
- "y-protocols": "0.0.3",
+ "y-protocols": "0.0.4",
"codemirror": "^5.42.0",
"concurrently": "^3.6.1",
"jsdoc": "^3.5.5",
diff --git a/src/index.js b/src/index.js
index 9f2d3e38..2a4fa55a 100644
--- a/src/index.js
+++ b/src/index.js
@@ -1,6 +1,6 @@
export {
- Y,
+ Doc,
Transaction,
YArray as Array,
YMap as Map,
@@ -24,25 +24,23 @@ export {
ItemString,
ItemType,
AbstractType,
- compareCursors,
- Cursor,
- createCursorFromTypeOffset,
- createCursorFromJSON,
- createAbsolutePositionFromCursor,
- writeCursor,
- readCursor,
+ RelativePosition,
+ createRelativePositionFromTypeIndex,
+ createRelativePositionFromJSON,
+ createAbsolutePositionFromRelativePosition,
+ compareRelativePositions,
+ writeRelativePosition,
+ readRelativePosition,
ID,
createID,
compareIDs,
getState,
- getStates,
- readStatesAsMap,
- writeStates,
- writeModel,
- readModel,
Snapshot,
findRootTypeKey,
- typeArrayToArraySnapshot,
+ typeListToArraySnapshot,
typeMapGetSnapshot,
- iterateDeletedStructs
+ iterateDeletedStructs,
+ applyUpdate,
+ encodeStateAsUpdate,
+ encodeDocumentStateVector
} from './internals.js'
diff --git a/src/internals.js b/src/internals.js
index 56eb25e1..ed0cced1 100644
--- a/src/internals.js
+++ b/src/internals.js
@@ -2,12 +2,12 @@ export * from './utils/DeleteSet.js'
export * from './utils/EventHandler.js'
export * from './utils/ID.js'
export * from './utils/isParentOf.js'
-export * from './utils/cursor.js'
+export * from './utils/RelativePosition.js'
export * from './utils/Snapshot.js'
export * from './utils/StructStore.js'
export * from './utils/Transaction.js'
// export * from './utils/UndoManager.js'
-export * from './utils/Y.js'
+export * from './utils/Doc.js'
export * from './utils/YEvent.js'
export * from './types/AbstractType.js'
diff --git a/src/structs/AbstractItem.js b/src/structs/AbstractItem.js
index a27b0de7..16a3d8ea 100644
--- a/src/structs/AbstractItem.js
+++ b/src/structs/AbstractItem.js
@@ -17,7 +17,7 @@ import {
getItemType,
getItemCleanEnd,
getItemCleanStart,
- YEvent, StructStore, ID, AbstractType, Y, Transaction // eslint-disable-line
+ YEvent, StructStore, ID, AbstractType, Transaction // eslint-disable-line
} from '../internals.js'
import * as error from 'lib0/error.js'
@@ -153,7 +153,7 @@ export class AbstractItem extends AbstractStruct {
* @private
*/
integrate (transaction) {
- const store = transaction.y.store
+ const store = transaction.doc.store
const id = this.id
const parent = this.parent
const parentSub = this.parentSub
@@ -415,23 +415,19 @@ export class AbstractItem extends AbstractStruct {
}
/**
- * @param {Transaction} transaction
* @param {StructStore} store
*
* @private
*/
- gcChildren (transaction, store) { }
+ gcChildren (store) { }
/**
- * @todo remove transaction param
- *
- * @param {Transaction} transaction
* @param {StructStore} store
* @param {boolean} parentGCd
*
* @private
*/
- gc (transaction, store, parentGCd) {
+ gc (store, parentGCd) {
if (!this.deleted) {
throw error.unexpectedCase()
}
@@ -619,12 +615,16 @@ export const computeItemParams = (transaction, store, leftid, rightid, parentid,
case GC:
break
default:
+ // Edge case: toStruct is called with an offset > 0. In this case left is defined.
+ // Depending in which order structs arrive, left may be GC'd and the parent not
+ // deleted. This is why we check if left is GC'd. Strictly we probably don't have
+ // to check if right is GC'd, but we will in case we run into future issues
if (!parentItem.deleted && (left === null || left.constructor !== GC) && (right === null || right.constructor !== GC)) {
parent = parentItem.type
}
}
} else if (parentYKey !== null) {
- parent = transaction.y.get(parentYKey)
+ parent = transaction.doc.get(parentYKey)
} else if (left !== null) {
if (left.constructor !== GC) {
parent = left.parent
diff --git a/src/structs/AbstractStruct.js b/src/structs/AbstractStruct.js
index 2b576d85..3572b44d 100644
--- a/src/structs/AbstractStruct.js
+++ b/src/structs/AbstractStruct.js
@@ -1,6 +1,6 @@
import {
- Y, StructStore, ID, Transaction // eslint-disable-line
+ StructStore, ID, Transaction // eslint-disable-line
} from '../internals.js'
import * as encoding from 'lib0/encoding.js' // eslint-disable-line
diff --git a/src/structs/GC.js b/src/structs/GC.js
index 4431dc53..a35a8334 100644
--- a/src/structs/GC.js
+++ b/src/structs/GC.js
@@ -4,7 +4,7 @@ import {
AbstractStruct,
createID,
addStruct,
- Y, StructStore, Transaction, ID // eslint-disable-line
+ StructStore, Transaction, ID // eslint-disable-line
} from '../internals.js'
import * as decoding from 'lib0/decoding.js'
@@ -48,7 +48,7 @@ export class GC extends AbstractStruct {
* @param {Transaction} transaction
*/
integrate (transaction) {
- addStruct(transaction.y.store, this)
+ addStruct(transaction.doc.store, this)
}
/**
diff --git a/src/structs/ItemBinary.js b/src/structs/ItemBinary.js
index 917db5c4..3640d9b7 100644
--- a/src/structs/ItemBinary.js
+++ b/src/structs/ItemBinary.js
@@ -9,6 +9,7 @@ import {
import * as encoding from 'lib0/encoding.js'
import * as decoding from 'lib0/decoding.js'
+import * as buffer from 'lib0/buffer.js'
/**
* @private
@@ -27,7 +28,7 @@ export class ItemBinary extends AbstractItem {
* @param {ID | null} rightOrigin
* @param {AbstractType} parent
* @param {string | null} parentSub
- * @param {ArrayBuffer} content
+ * @param {Uint8Array} content
*/
constructor (id, left, origin, right, rightOrigin, parent, parentSub, content) {
super(id, left, origin, right, rightOrigin, parent, parentSub)
@@ -54,7 +55,7 @@ export class ItemBinary extends AbstractItem {
*/
write (encoder, offset) {
super.write(encoder, offset, structBinaryRefNumber)
- encoding.writePayload(encoder, this.content)
+ encoding.writeVarUint8Array(encoder, this.content)
}
}
@@ -70,9 +71,9 @@ export class ItemBinaryRef extends AbstractItemRef {
constructor (decoder, id, info) {
super(decoder, id, info)
/**
- * @type {ArrayBuffer}
+ * @type {Uint8Array}
*/
- this.content = decoding.readPayload(decoder)
+ this.content = buffer.copyUint8Array(decoding.readVarUint8Array(decoder))
}
/**
* @param {Transaction} transaction
diff --git a/src/structs/ItemDeleted.js b/src/structs/ItemDeleted.js
index c2f803e8..71497137 100644
--- a/src/structs/ItemDeleted.js
+++ b/src/structs/ItemDeleted.js
@@ -8,7 +8,7 @@ import {
splitItem,
addToDeleteSet,
mergeItemWith,
- Y, StructStore, Transaction, ID, AbstractType // eslint-disable-line
+ StructStore, Transaction, ID, AbstractType // eslint-disable-line
} from '../internals.js'
import * as encoding from 'lib0/encoding.js'
@@ -87,15 +87,14 @@ export class ItemDeleted extends AbstractItem {
}
/**
- * @param {Transaction} transaction
* @param {StructStore} store
* @param {boolean} parentGCd
*
* @private
*/
- gc (transaction, store, parentGCd) {
+ gc (store, parentGCd) {
if (parentGCd) {
- super.gc(transaction, store, parentGCd)
+ super.gc(store, parentGCd)
}
}
/**
diff --git a/src/structs/ItemEmbed.js b/src/structs/ItemEmbed.js
index a317dc4f..e30fb426 100644
--- a/src/structs/ItemEmbed.js
+++ b/src/structs/ItemEmbed.js
@@ -67,7 +67,7 @@ export class ItemEmbedRef extends AbstractItemRef {
constructor (decoder, id, info) {
super(decoder, id, info)
/**
- * @type {ArrayBuffer}
+ * @type {Object}
*/
this.embed = JSON.parse(decoding.readVarString(decoder))
}
diff --git a/src/structs/ItemJSON.js b/src/structs/ItemJSON.js
index 1b69a90e..837ff401 100644
--- a/src/structs/ItemJSON.js
+++ b/src/structs/ItemJSON.js
@@ -7,7 +7,7 @@ import {
changeItemRefOffset,
GC,
mergeItemWith,
- Transaction, StructStore, Y, ID, AbstractType // eslint-disable-line
+ Transaction, StructStore, ID, AbstractType // eslint-disable-line
} from '../internals.js'
import * as encoding from 'lib0/encoding.js'
diff --git a/src/structs/ItemString.js b/src/structs/ItemString.js
index 8409c9f7..bbaee867 100644
--- a/src/structs/ItemString.js
+++ b/src/structs/ItemString.js
@@ -7,7 +7,7 @@ import {
changeItemRefOffset,
GC,
mergeItemWith,
- Transaction, StructStore, Y, ID, AbstractType // eslint-disable-line
+ Transaction, StructStore, ID, AbstractType // eslint-disable-line
} from '../internals.js'
import * as encoding from 'lib0/encoding.js'
diff --git a/src/structs/ItemType.js b/src/structs/ItemType.js
index bdf648b0..61ecf5df 100644
--- a/src/structs/ItemType.js
+++ b/src/structs/ItemType.js
@@ -10,7 +10,7 @@ import {
readYXmlFragment,
readYXmlHook,
readYXmlText,
- StructStore, Y, GC, Transaction, ID, AbstractType // eslint-disable-line
+ StructStore, GC, Transaction, ID, AbstractType // eslint-disable-line
} from '../internals.js'
import * as encoding from 'lib0/encoding.js' // eslint-disable-line
@@ -83,7 +83,7 @@ export class ItemType extends AbstractItem {
*/
integrate (transaction) {
super.integrate(transaction)
- this.type._integrate(transaction.y, this)
+ this.type._integrate(transaction.doc, this)
}
/**
* @param {encoding.Encoder} encoder
@@ -129,19 +129,18 @@ export class ItemType extends AbstractItem {
}
/**
- * @param {Transaction} transaction
* @param {StructStore} store
*/
- gcChildren (transaction, store) {
+ gcChildren (store) {
let item = this.type._start
while (item !== null) {
- item.gc(transaction, store, true)
+ item.gc(store, true)
item = item.right
}
this.type._start = null
this.type._map.forEach(item => {
while (item !== null) {
- item.gc(transaction, store, true)
+ item.gc(store, true)
// @ts-ignore
item = item.left
}
@@ -150,13 +149,12 @@ export class ItemType extends AbstractItem {
}
/**
- * @param {Transaction} transaction
* @param {StructStore} store
* @param {boolean} parentGCd
*/
- gc (transaction, store, parentGCd) {
- this.gcChildren(transaction, store)
- super.gc(transaction, store, parentGCd)
+ gc (store, parentGCd) {
+ this.gcChildren(store)
+ super.gc(store, parentGCd)
}
}
diff --git a/src/types/AbstractType.js b/src/types/AbstractType.js
index ab9bd7c6..ae3f1e33 100644
--- a/src/types/AbstractType.js
+++ b/src/types/AbstractType.js
@@ -11,7 +11,7 @@ import {
ItemBinary,
createID,
getItemCleanStart,
- Y, Snapshot, Transaction, EventHandler, YEvent, AbstractItem, // eslint-disable-line
+ Doc, Snapshot, Transaction, EventHandler, YEvent, AbstractItem, // eslint-disable-line
} from '../internals.js'
import * as map from 'lib0/map.js'
@@ -64,9 +64,9 @@ export class AbstractType {
this._start = null
/**
* @private
- * @type {Y|null}
+ * @type {Doc|null}
*/
- this._y = null
+ this.doc = null
this._length = 0
/**
* Event handlers
@@ -87,12 +87,12 @@ export class AbstractType {
* * This type is sent to other client
* * Observer functions are fired
*
- * @param {Y} y The Yjs instance
+ * @param {Doc} y The Yjs instance
* @param {ItemType|null} item
* @private
*/
_integrate (y, item) {
- this._y = y
+ this.doc = y
this._item = item
}
@@ -182,7 +182,7 @@ export class AbstractType {
* @private
* @function
*/
-export const typeArrayToArray = type => {
+export const typeListToArray = type => {
const cs = []
let n = type._start
while (n !== null) {
@@ -205,7 +205,7 @@ export const typeArrayToArray = type => {
* @private
* @function
*/
-export const typeArrayToArraySnapshot = (type, snapshot) => {
+export const typeListToArraySnapshot = (type, snapshot) => {
const cs = []
let n = type._start
while (n !== null) {
@@ -229,7 +229,7 @@ export const typeArrayToArraySnapshot = (type, snapshot) => {
* @private
* @function
*/
-export const typeArrayForEach = (type, f) => {
+export const typeListForEach = (type, f) => {
let index = 0
let n = type._start
while (n !== null) {
@@ -252,12 +252,12 @@ export const typeArrayForEach = (type, f) => {
* @private
* @function
*/
-export const typeArrayMap = (type, f) => {
+export const typeListMap = (type, f) => {
/**
* @type {Array}
*/
const result = []
- typeArrayForEach(type, (c, i) => {
+ typeListForEach(type, (c, i) => {
result.push(f(c, i, type))
})
return result
@@ -270,7 +270,7 @@ export const typeArrayMap = (type, f) => {
* @private
* @function
*/
-export const typeArrayCreateIterator = type => {
+export const typeListCreateIterator = type => {
let n = type._start
/**
* @type {Array|null}
@@ -326,7 +326,7 @@ export const typeArrayCreateIterator = type => {
* @private
* @function
*/
-export const typeArrayForEachSnapshot = (type, f, snapshot) => {
+export const typeListForEachSnapshot = (type, f, snapshot) => {
let index = 0
let n = type._start
while (n !== null) {
@@ -348,7 +348,7 @@ export const typeArrayForEachSnapshot = (type, f, snapshot) => {
* @private
* @function
*/
-export const typeArrayGet = (type, index) => {
+export const typeListGet = (type, index) => {
for (let n = type._start; n !== null; n = n.right) {
if (!n.deleted && n.countable) {
if (index < n.length) {
@@ -363,12 +363,12 @@ export const typeArrayGet = (type, index) => {
* @param {Transaction} transaction
* @param {AbstractType} parent
* @param {AbstractItem?} referenceItem
- * @param {Array