Compare commits
67 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
05e9ba4145 | ||
|
|
b2b1863624 | ||
|
|
ad0d915794 | ||
|
|
2ef9ccd170 | ||
|
|
3ecfb4e898 | ||
|
|
35c030d834 | ||
|
|
e3739bce8e | ||
|
|
afa4c35866 | ||
|
|
09fbb62ba9 | ||
|
|
78e0527b46 | ||
|
|
69d4a5c821 | ||
|
|
cc9a857441 | ||
|
|
4b865764b8 | ||
|
|
40725e373b | ||
|
|
c05b815b4c | ||
|
|
e53c44e3a6 | ||
|
|
1bec008862 | ||
|
|
bb5410b6dd | ||
|
|
2d2e662d4d | ||
|
|
80e83a84c6 | ||
|
|
3c9c0f17d1 | ||
|
|
e67b1296a7 | ||
|
|
1a0d4aa797 | ||
|
|
f18eab2dfe | ||
|
|
89dddc2a95 | ||
|
|
f583d2a211 | ||
|
|
1b0f2e5463 | ||
|
|
4404d090e4 | ||
|
|
d4d4ae5f53 | ||
|
|
4ffd3709f8 | ||
|
|
0419b74315 | ||
|
|
c951f2b7ea | ||
|
|
4e2d3c8ac6 | ||
|
|
8dc1296a0b | ||
|
|
4329997350 | ||
|
|
2b7ea8a2af | ||
|
|
4f47355893 | ||
|
|
6074f80257 | ||
|
|
42bbb44bfc | ||
|
|
cc2d7320aa | ||
|
|
e804dd7573 | ||
|
|
a304024a76 | ||
|
|
487465d701 | ||
|
|
345fd31b10 | ||
|
|
4ff65b5dc3 | ||
|
|
8152cf81cb | ||
|
|
3bf44b9850 | ||
|
|
8cd1a482bb | ||
|
|
9e9f294009 | ||
|
|
9a993f81d4 | ||
|
|
f604250fc3 | ||
|
|
4fb7789cdd | ||
|
|
c1ef9a12b9 | ||
|
|
7422b18e87 | ||
|
|
95e2bc4429 | ||
|
|
f2ff8b9536 | ||
|
|
3f9bfe42f7 | ||
|
|
5b4d2a6bcf | ||
|
|
44e51080af | ||
|
|
dd17228a8f | ||
|
|
eeb4c9969d | ||
|
|
56d5e3287b | ||
|
|
294c6a15c5 | ||
|
|
c944a4553c | ||
|
|
f29cd2baf4 | ||
|
|
384ec4db78 | ||
|
|
5e19c35405 |
@@ -60,7 +60,7 @@ characters have either been deleted or all characters are not deleted. The item
|
||||
will be split if the run is interrupted for any reason (eg a character in the
|
||||
middle of the run is deleted).
|
||||
|
||||
When an item is created, it stores a reference to the IDs of the preceeding and
|
||||
When an item is created, it stores a reference to the IDs of the preceding and
|
||||
succeeding item. These are stored in the item's `origin` and `originRight`
|
||||
fields, respectively. These are used when peers concurrently insert at the same
|
||||
location in a document. Though quite rare in practice, Yjs needs to make sure
|
||||
|
||||
102
README.md
102
README.md
@@ -3,7 +3,7 @@
|
||||
|
||||
> A CRDT framework with a powerful abstraction of shared data
|
||||
|
||||
Yjs is a [CRDT implementation](#Yjs-CRDT-Algorithm) that exposes its internal
|
||||
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.
|
||||
@@ -99,7 +99,6 @@ Showcase](https://yjs-diagram.synergy.codes/).
|
||||
* [AWS SageMaker](https://aws.amazon.com/sagemaker/) Tools for building Machine
|
||||
Learning Models
|
||||
* [linear](https://linear.app) Streamline issues, projects, and product roadmaps.
|
||||
* [btw](https://www.btw.so) - Personal website builder
|
||||
* [AWS SageMaker](https://aws.amazon.com/sagemaker/) - Machine Learning Service
|
||||
* [Arkiter](https://www.arkiter.com/) - Live interview software
|
||||
* [Appflowy](https://www.appflowy.io/) - They use Yrs
|
||||
@@ -110,26 +109,37 @@ Showcase](https://yjs-diagram.synergy.codes/).
|
||||
* [Synthesia](https://www.synthesia.io) - Collaborative Video Editor
|
||||
* [thinkdeli](https://thinkdeli.com) - A fast and simple notes app powered by AI
|
||||
* [ourboard](https://github.com/raimohanska/ourboard) - A collaborative whiteboard
|
||||
applicaiton
|
||||
application
|
||||
* [Ellie.ai](https://ellie.ai) - Data Product Design and Collaboration
|
||||
* [GoPeer](https://gopeer.org/) - Collaborative tutoring
|
||||
* [screen.garden](https://screen.garden) Collaborative backend for PKM apps.
|
||||
* [screen.garden](https://screen.garden) - Collaborative backend for PKM apps.
|
||||
* [NextCloud](https://nextcloud.com/) - Content Collaboration Platform
|
||||
* [keystatic](https://github.com/Thinkmill/keystatic) - git-based CMS
|
||||
* [QDAcity](https://qdacity.com) - Collaborative qualitative data analysis platform
|
||||
* [Kanbert](https://kanbert.com) - Project management software
|
||||
* [Eclipse Theia](https://github.com/eclipse-theia/theia) - A cloud & desktop
|
||||
IDE that runs in the browser.
|
||||
* [ScienHub](https://scienhub.com) - Collaborative LaTeX editor in the browser.
|
||||
* [Open Collaboration Tools](https://www.open-collab.tools/) - Collaborative
|
||||
editing for your IDE or custom editor
|
||||
* [Typst](https://typst.app/) - Compose, edit, and automate technical documents
|
||||
|
||||
## Table of Contents
|
||||
|
||||
* [Overview](#Overview)
|
||||
* [Bindings](#Bindings)
|
||||
* [Providers](#Providers)
|
||||
* [Ports](#Ports)
|
||||
* [Getting Started](#Getting-Started)
|
||||
* [API](#API)
|
||||
* [Shared Types](#Shared-Types)
|
||||
* [Y.Doc](#YDoc)
|
||||
* [Document Updates](#Document-Updates)
|
||||
* [Relative Positions](#Relative-Positions)
|
||||
* [Y.UndoManager](#YUndoManager)
|
||||
* [Yjs CRDT Algorithm](#Yjs-CRDT-Algorithm)
|
||||
* [License and Author](#License-and-Author)
|
||||
* [Overview](#overview)
|
||||
* [Bindings](#bindings)
|
||||
* [Providers](#providers)
|
||||
* [Tooling](#tooling)
|
||||
* [Ports](#ports)
|
||||
* [Getting Started](#getting-started)
|
||||
* [API](#api)
|
||||
* [Shared Types](#shared-types)
|
||||
* [Y.Doc](#ydoc)
|
||||
* [Document Updates](#document-updates)
|
||||
* [Relative Positions](#relative-positions)
|
||||
* [Y.UndoManager](#yundomanager)
|
||||
* [Yjs CRDT Algorithm](#yjs-crdt-algorithm)
|
||||
* [License and Author](#license-and-author)
|
||||
|
||||
## Overview
|
||||
|
||||
@@ -150,8 +160,11 @@ are implemented in separate modules.
|
||||
| [Lexical](https://lexical.dev/) | ✔ | (native) | [demo](https://lexical.dev/docs/collaboration/react#see-it-in-action) |
|
||||
| [valtio](https://github.com/pmndrs/valtio) | | [valtio-yjs](https://github.com/dai-shi/valtio-yjs) | [demo](https://codesandbox.io/s/valtio-yjs-demo-ox3iy) |
|
||||
| [immer](https://github.com/immerjs/immer) | | [immer-yjs](https://github.com/sep2/immer-yjs) | [demo](https://codesandbox.io/s/immer-yjs-demo-6e0znb) |
|
||||
| React | | [react-yjs](https://github.com/nikgraf/react-yjs) | [demo](https://react-yjs-example.vercel.app/) |
|
||||
| React / Vue / Svelte / MobX | | [SyncedStore](https://syncedstore.org) | [demo](https://syncedstore.org/docs/react) |
|
||||
| [mobx-keystone](https://mobx-keystone.js.org/) | | [mobx-keystone-yjs](https://github.com/xaviergonz/mobx-keystone/tree/master/packages/mobx-keystone-yjs) | [demo](https://mobx-keystone.js.org/examples/yjs-binding) |
|
||||
| [PSPDFKit](https://www.nutrient.io/) | | [yjs-pspdfkit](https://github.com/hoangqwe159/yjs-pspdfkit) | [demo](https://github.com/hoangqwe159/yjs-pspdfkit) |
|
||||
| [Rows n'Columns](https://www.rowsncolumns.app/) | ✔ | [@rowsncolumns/y-spreadsheet](https://docs.rowsncolumns.app/collaboration/yjs-collaboration) | |
|
||||
|
||||
### Providers
|
||||
|
||||
@@ -177,12 +190,12 @@ backends to y-websocket.
|
||||
<dt><a href="https://github.com/yjs/y-webrtc">y-webrtc</a></dt>
|
||||
<dd>
|
||||
Propagates document updates peer-to-peer using WebRTC. The peers exchange
|
||||
signaling data over signaling servers. Publically available signaling servers
|
||||
signaling data over signaling servers. Publicly available signaling servers
|
||||
are available. Communication over the signaling servers can be encrypted by
|
||||
providing a shared secret, keeping the connection information and the shared
|
||||
document private.
|
||||
</dd>
|
||||
<dt><a href="https://github.com/liveblocks/liveblocks">@liveblocks/yjs</a></dt>
|
||||
<dt><a href="https://github.com/liveblocks/liveblocks">@liveblocks/yjs </a> 🌟</dt>
|
||||
<dd>
|
||||
<a href="https://liveblocks.io/document/yjs">Liveblocks Yjs</a> provides a fully
|
||||
hosted WebSocket infrastructure and persisted data store for Yjs
|
||||
@@ -190,15 +203,23 @@ documents. No configuration or maintenance is required. It also features
|
||||
Yjs webhook events, REST API to read and update Yjs documents, and a
|
||||
browser DevTools extension.
|
||||
</dd>
|
||||
<dt><a href="https://github.com/drifting-in-space/y-sweet">y-sweet</a></dt>
|
||||
<dt><a href="https://github.com/drifting-in-space/y-sweet">y-sweet</a> ⭐</dt>
|
||||
<dd>
|
||||
A standalone yjs server with persistence to S3 or filesystem. They offer a
|
||||
<a href="https://y-sweet.cloud">cloud service</a> as well.
|
||||
</dd>
|
||||
<dt><a href="https://github.com/ueberdosis/hocuspocus">Hocuspocus</a></dt>
|
||||
<dt><a href="https://github.com/ueberdosis/hocuspocus">Hocuspocus</a> ⭐</dt>
|
||||
<dd>
|
||||
A standalone extensible yjs server with sqlite persistence, webhooks, auth and more.
|
||||
</dd>
|
||||
<dt><a href="https://docs.superviz.com/collaboration/integrations/YJS/overview">@superviz/yjs</a></dt>
|
||||
<dd>
|
||||
SuperViz Yjs Provider comes with a secure, scalable real-time infrastructure
|
||||
for Yjs documents, fully compatible with a set of real-time
|
||||
collaboration components offered by SuperViz. This solution ensures
|
||||
synchronization, offline editing, and real-time updates, enabling
|
||||
multiple users to collaborate effectively within shared workspaces.
|
||||
</dd>
|
||||
<dt><a href="https://docs.partykit.io/reference/y-partykit-api/">PartyKit</a></dt>
|
||||
<dd>
|
||||
Cloud service for building multiplayer apps.
|
||||
@@ -243,6 +264,11 @@ The reactive data store for local-first apps. They support multiple CRDTs and
|
||||
<dd>
|
||||
Provider for sharing data in <a href="https://webxdc.org">webxdc chat apps</a>.
|
||||
</dd>
|
||||
<dt><a href="https://www.secsync.com/">secsync</a></dt>
|
||||
<dd>
|
||||
An architecture to relay end-to-end encrypted CRDTs over a central service.
|
||||
</dd>
|
||||
|
||||
</dl>
|
||||
|
||||
#### Persistence Providers
|
||||
@@ -258,11 +284,6 @@ network provider.
|
||||
<dd>
|
||||
Adds persistent storage to a server with MongoDB. Can be used with the
|
||||
y-websocket provider.
|
||||
</dd>
|
||||
<dt><a href="https://github.com/toeverything/AFFiNE/tree/master/packages/y-indexeddb">
|
||||
@toeverything/y-indexeddb</a></dt>
|
||||
<dd>
|
||||
Like y-indexeddb, but with sub-documents support and fully TypeScript.
|
||||
</dd>
|
||||
<dt><a href="https://github.com/podraven/y-fire">y-fire</a></dt>
|
||||
<dd>
|
||||
@@ -281,7 +302,13 @@ A database and connection provider for Yjs based on Firestore.
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
# Ports
|
||||
### Tooling
|
||||
|
||||
* [y-sweet debugger](https://docs.jamsocket.com/y-sweet/advanced/debugger)
|
||||
* [liveblocks devtools](https://liveblocks.io/devtools)
|
||||
* [Yjs inspector](https://inspector.yjs.dev)
|
||||
|
||||
### Ports
|
||||
|
||||
There are several Yjs-compatible ports to other programming languages.
|
||||
|
||||
@@ -295,6 +322,7 @@ language bindings to other languages
|
||||
* [yswift](https://github.com/y-crdt/yswift) - Swift binding
|
||||
* [yffi](https://github.com/y-crdt/y-crdt/tree/main/yffi) - C-FFI
|
||||
* [ywasm](https://github.com/y-crdt/y-crdt/tree/main/ywasm) - WASM binding
|
||||
* [y_ex](https://github.com/satoren/y_ex) - Elixir bindings
|
||||
* [ycs](https://github.com/yjs/ycs) - .Net compatible C# implementation.
|
||||
|
||||
## Getting Started
|
||||
@@ -409,6 +437,11 @@ necessary.
|
||||
</p>
|
||||
<pre>const yarray = new Y.Array()</pre>
|
||||
<dl>
|
||||
<b><code>
|
||||
Y.Array.from(Array<object|boolean|Array|string|number|null|Uint8Array|Y.Type>):
|
||||
Y.Array
|
||||
</code></b>
|
||||
<dd>An alternative factory function to create a Y.Array based on existing content.</dd>
|
||||
<b><code>parent:Y.AbstractType|null</code></b>
|
||||
<dd></dd>
|
||||
<b><code>insert(index:number, content:Array<object|boolean|Array|string|number|null|Uint8Array|Y.Type>)</code></b>
|
||||
@@ -438,6 +471,11 @@ forEach(function(value:object|boolean|Array|string|number|null|Uint8Array|Y.Type
|
||||
<dd></dd>
|
||||
<b><code>map(function(T, number, YArray):M):Array<M></code></b>
|
||||
<dd></dd>
|
||||
<b><code>clone(): Y.Array</code></b>
|
||||
<dd>
|
||||
Clone all values into a fresh Y.Array instance. The returned type can be
|
||||
included into the Yjs document.
|
||||
</dd>
|
||||
<b><code>toArray():Array<object|boolean|Array|string|number|null|Uint8Array|Y.Type></code></b>
|
||||
<dd>Copies the content of this YArray to a new Array.</dd>
|
||||
<b><code>toJSON():Array<Object|boolean|Array|string|number|null></code></b>
|
||||
@@ -494,8 +532,6 @@ or any of its children.
|
||||
<dd></dd>
|
||||
<b><code>has(key:string):boolean</code></b>
|
||||
<dd></dd>
|
||||
<b><code>get(index:number)</code></b>
|
||||
<dd></dd>
|
||||
<b><code>clear()</code></b>
|
||||
<dd>Removes all elements from this YMap.</dd>
|
||||
<b><code>clone():Y.Map</code></b>
|
||||
@@ -849,7 +885,7 @@ doc1.getArray('myarray').insert(0, ['Hello doc2, you got this?'])
|
||||
doc2.getArray('myarray').get(0) // => 'Hello doc2, you got this?'
|
||||
```
|
||||
|
||||
Yjs internally maintains a [state vector](#State-Vector) that denotes the next
|
||||
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
|
||||
@@ -1062,7 +1098,7 @@ encoding format for document updates. If you prefer JSON encoding, you can
|
||||
simply JSON.stringify / JSON.parse the relative position instead.
|
||||
</dd>
|
||||
<b><code>Y.decodeRelativePosition(Uint8Array):RelativePosition</code></b>
|
||||
<dd>Decode a binary-encoded relative position to a RelativePositon object.</dd>
|
||||
<dd>Decode a binary-encoded relative position to a RelativePosition object.</dd>
|
||||
</dl>
|
||||
|
||||
### Y.UndoManager
|
||||
@@ -1242,11 +1278,11 @@ More information about the specific implementation is available in
|
||||
|
||||
CRDTs that are 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
|
||||
have the characteristics that are beneficial 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
|
||||
order of the structs. But we can 1. merge preceding 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
|
||||
|
||||
142
funding.json
Normal file
142
funding.json
Normal file
@@ -0,0 +1,142 @@
|
||||
{
|
||||
"version": "v1.0.0",
|
||||
"entity": {
|
||||
"type": "group",
|
||||
"role": "steward",
|
||||
"name": "Kevin Jahns",
|
||||
"email": "kevin.jahns@protonmail.com",
|
||||
"phone": "",
|
||||
"description": "OSS Developer",
|
||||
"webpageUrl": {
|
||||
"url": "https://github.com/yjs"
|
||||
}
|
||||
},
|
||||
"projects": [
|
||||
{
|
||||
"guid": "yjs",
|
||||
"name": "Yjs",
|
||||
"description": "A library for building collaborative applications. #p2p #local-first #CRDT Funding this project will also enable me to maintain the other Yjs-related technologies.",
|
||||
"webpageUrl": {
|
||||
"url": "https://github.com/yjs/yjs"
|
||||
},
|
||||
"repositoryUrl": {
|
||||
"url": "https://github.com/yjs/yjs"
|
||||
},
|
||||
"licenses": [
|
||||
"spdx:MIT"
|
||||
],
|
||||
"tags": [
|
||||
"collaboration",
|
||||
"p2p",
|
||||
"CRDT",
|
||||
"rich-text",
|
||||
"real-time"
|
||||
]
|
||||
},
|
||||
{
|
||||
"guid": "Titanic",
|
||||
"name": "Y/Titanic",
|
||||
"description": "A provider for syncing millions of docs efficiently with other peers. This will become the foundation for building real local-first apps with Yjs.",
|
||||
"webpageUrl": {
|
||||
"url": "https://github.com/yjs/titanic",
|
||||
"wellKnown": "https://github.com/yjs/titanic/blob/main/.well-known/funding-manifest-urls"
|
||||
},
|
||||
"repositoryUrl": {
|
||||
"url": "https://github.com/yjs/titanic",
|
||||
"wellKnown": "https://github.com/yjs/titanic/blob/main/.well-known/funding-manifest-urls"
|
||||
},
|
||||
"licenses": [
|
||||
"spdx:MIT"
|
||||
],
|
||||
"tags": [
|
||||
"privacy",
|
||||
"collaboration",
|
||||
"p2p",
|
||||
"CRDT",
|
||||
"rich-text",
|
||||
"real-time",
|
||||
"web-development"
|
||||
]
|
||||
}
|
||||
],
|
||||
"funding": {
|
||||
"channels": [
|
||||
{
|
||||
"guid": "github-sponsors",
|
||||
"type": "payment-provider",
|
||||
"address": "",
|
||||
"description": "For funding of the Yjs project"
|
||||
},
|
||||
{
|
||||
"guid": "y-collective",
|
||||
"type": "payment-provider",
|
||||
"address": "https://opencollective.com/y-collective",
|
||||
"description": "For funding the Y-CRDT - the Rust implementation of Yjs and other listed projects."
|
||||
}
|
||||
],
|
||||
"plans": [
|
||||
{
|
||||
"guid": "supporter",
|
||||
"status": "active",
|
||||
"name": "Supporter",
|
||||
"description": "",
|
||||
"amount": 0,
|
||||
"currency": "USD",
|
||||
"frequency": "monthly",
|
||||
"channels": [
|
||||
"github-sponsors",
|
||||
"y-collective"
|
||||
]
|
||||
},
|
||||
{
|
||||
"guid": "titanic-funding",
|
||||
"status": "active",
|
||||
"name": "Titanic Funding",
|
||||
"description": "Fund the next generation of local-first providers.",
|
||||
"amount": 30000,
|
||||
"currency": "USD",
|
||||
"frequency": "one-time",
|
||||
"channels": [
|
||||
"github-sponsors"
|
||||
]
|
||||
},
|
||||
{
|
||||
"guid": "bronze-sponsor",
|
||||
"status": "active",
|
||||
"name": "Bronze Sponsor",
|
||||
"description": "This is the recommended plan for companies that use Yjs.",
|
||||
"amount": 500,
|
||||
"currency": "USD",
|
||||
"frequency": "monthly",
|
||||
"channels": [
|
||||
"github-sponsors"
|
||||
]
|
||||
},
|
||||
{
|
||||
"guid": "silver-sponsor",
|
||||
"status": "active",
|
||||
"name": "Silver Sponsor",
|
||||
"description": "This is the recommended plan for large/successfull companies that use Yjs.",
|
||||
"amount": 1000,
|
||||
"currency": "USD",
|
||||
"frequency": "monthly",
|
||||
"channels": [
|
||||
"github-sponsors"
|
||||
]
|
||||
},
|
||||
{
|
||||
"guid": "gold-sponsor",
|
||||
"status": "active",
|
||||
"name": "Gold Sponsor",
|
||||
"description": "This is the recommended plan for successful companies that build their entire product around Yjs-related technologies.",
|
||||
"amount": 3000,
|
||||
"currency": "USD",
|
||||
"frequency": "monthly",
|
||||
"channels": [
|
||||
"github-sponsors"
|
||||
]
|
||||
}
|
||||
],
|
||||
"history": null
|
||||
}
|
||||
}
|
||||
1377
package-lock.json
generated
1377
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
48
package.json
48
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "yjs",
|
||||
"version": "13.6.18",
|
||||
"version": "13.6.24",
|
||||
"description": "Shared Editing Library",
|
||||
"main": "./dist/yjs.cjs",
|
||||
"module": "./dist/yjs.mjs",
|
||||
@@ -13,28 +13,38 @@
|
||||
},
|
||||
"scripts": {
|
||||
"clean": "rm -rf dist docs",
|
||||
"test": "npm run dist && node ./dist/tests.cjs --repetition-time 50",
|
||||
"test-extensive": "npm run lint && npm run dist && node ./dist/tests.cjs --production --repetition-time 10000",
|
||||
"test": "NODE_ENV=development node ./tests/index.js --repetition-time 50",
|
||||
"test-extensive": "node ./tests/index.js --production --repetition-time 10000",
|
||||
"dist": "npm run clean && rollup -c && tsc",
|
||||
"watch": "rollup -wc",
|
||||
"lint": "markdownlint README.md && standard && tsc",
|
||||
"docs": "rm -rf docs; jsdoc --configure ./.jsdoc.json --verbose --readme ./README.md --package ./package.json || true",
|
||||
"serve-docs": "npm run docs && http-server ./docs/",
|
||||
"preversion": "npm run lint && PRODUCTION=1 npm run dist && npm run docs && node ./dist/tests.cjs --repetition-time 1000 && test -e dist/src/index.d.ts && test -e dist/yjs.cjs && test -e dist/yjs.cjs",
|
||||
"debug": "concurrently 'http-server -o test.html' 'npm run watch'",
|
||||
"trace-deopt": "clear && rollup -c && node --trace-deopt dist/test.cjs",
|
||||
"trace-opt": "clear && rollup -c && node --trace-opt dist/test.cjs"
|
||||
"serve-docs": "npm run docs && 0serve ./docs/",
|
||||
"preversion": "npm run lint && PRODUCTION=1 npm run dist && npm run docs && node ./tests/index.js --repetition-time 1000 && test -e dist/src/index.d.ts && test -e dist/yjs.cjs && test -e dist/yjs.cjs",
|
||||
"debug": "npm run gentesthtml && 0serve -o test.html",
|
||||
"trace-deopt": "clear && node --trace-deopt ./tests/index.js",
|
||||
"trace-opt": "clear && node --trace-opt ./tests/index.js",
|
||||
"gentesthtml": "0gentesthtml --script ./tests/index.js > test.html"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/src/index.d.ts",
|
||||
"module": "./dist/yjs.mjs",
|
||||
"import": "./dist/yjs.mjs",
|
||||
"require": "./dist/yjs.cjs"
|
||||
"require": "./dist/yjs.cjs",
|
||||
"import": "./src/index.js"
|
||||
},
|
||||
"./internals": {
|
||||
"types": "./dist/src/internals.d.ts",
|
||||
"module": "./dist/internals.mjs",
|
||||
"require": "./dist/internals.cjs",
|
||||
"import": "./src/internals.js"
|
||||
},
|
||||
"./testHelper": {
|
||||
"types": "./dist/testHelper.d.ts",
|
||||
"module": "./dist/testHelper.mjs",
|
||||
"require": "./dist/testHelper.cjs",
|
||||
"import": "./tests/testHelper.js"
|
||||
},
|
||||
"./src/index.js": "./src/index.js",
|
||||
"./tests/testHelper.js": "./tests/testHelper.js",
|
||||
"./testHelper": "./dist/testHelper.mjs",
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"files": [
|
||||
@@ -76,21 +86,19 @@
|
||||
},
|
||||
"homepage": "https://docs.yjs.dev",
|
||||
"dependencies": {
|
||||
"lib0": "^0.2.86"
|
||||
"lib0": "^0.2.101",
|
||||
"y-protocols": "^1.0.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@rollup/plugin-commonjs": "^24.0.1",
|
||||
"@rollup/plugin-node-resolve": "^15.0.1",
|
||||
"@types/node": "^18.15.5",
|
||||
"concurrently": "^3.6.1",
|
||||
"http-server": "^0.12.3",
|
||||
"jsdoc": "^3.6.7",
|
||||
"markdownlint-cli": "^0.23.2",
|
||||
"rollup": "^3.20.0",
|
||||
"markdownlint-cli": "^0.41.0",
|
||||
"rollup": "^4.37.0",
|
||||
"standard": "^16.0.4",
|
||||
"tui-jsdoc-template": "^1.2.2",
|
||||
"typescript": "^4.9.5",
|
||||
"y-protocols": "^1.0.5"
|
||||
"yjs": "."
|
||||
},
|
||||
"engines": {
|
||||
"npm": ">=8.0.0",
|
||||
|
||||
120
rollup.config.js
120
rollup.config.js
@@ -1,106 +1,44 @@
|
||||
import nodeResolve from '@rollup/plugin-node-resolve'
|
||||
import commonjs from '@rollup/plugin-commonjs'
|
||||
|
||||
const localImports = process.env.LOCALIMPORTS
|
||||
|
||||
const customModules = new Set([
|
||||
'y-websocket',
|
||||
'y-codemirror',
|
||||
'y-ace',
|
||||
'y-textarea',
|
||||
'y-quill',
|
||||
'y-dom',
|
||||
'y-prosemirror'
|
||||
])
|
||||
/**
|
||||
* @type {Set<any>}
|
||||
*/
|
||||
const customLibModules = new Set([
|
||||
'lib0',
|
||||
'y-protocols'
|
||||
])
|
||||
const debugResolve = {
|
||||
const resolver = {
|
||||
resolveId (importee) {
|
||||
return
|
||||
if (importee === 'yjs') {
|
||||
return `${process.cwd()}/src/index.js`
|
||||
}
|
||||
if (localImports) {
|
||||
if (customModules.has(importee.split('/')[0])) {
|
||||
return `${process.cwd()}/../${importee}/src/${importee}.js`
|
||||
}
|
||||
if (customLibModules.has(importee.split('/')[0])) {
|
||||
return `${process.cwd()}/../${importee}`
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export default [{
|
||||
input: './src/index.js',
|
||||
// cjs output
|
||||
input: {
|
||||
yjs: './src/index.js',
|
||||
testHelper: './tests/testHelper.js',
|
||||
internals: './src/internals.js'
|
||||
},
|
||||
output: {
|
||||
name: 'Y',
|
||||
file: 'dist/yjs.cjs',
|
||||
dir: 'dist',
|
||||
format: 'cjs',
|
||||
sourcemap: true
|
||||
},
|
||||
external: id => /^lib0\//.test(id)
|
||||
}, {
|
||||
input: './src/index.js',
|
||||
output: {
|
||||
name: 'Y',
|
||||
file: 'dist/yjs.mjs',
|
||||
format: 'esm',
|
||||
sourcemap: true
|
||||
},
|
||||
external: id => /^lib0\//.test(id)
|
||||
}, {
|
||||
input: './tests/testHelper.js',
|
||||
output: {
|
||||
name: 'Y',
|
||||
file: 'dist/testHelper.mjs',
|
||||
format: 'esm',
|
||||
sourcemap: true
|
||||
},
|
||||
external: id => /^lib0\//.test(id) || id === 'yjs',
|
||||
plugins: [{
|
||||
resolveId (importee) {
|
||||
if (importee === '../src/index.js') {
|
||||
return 'yjs'
|
||||
}
|
||||
return null
|
||||
}
|
||||
}]
|
||||
}, {
|
||||
input: './tests/index.js',
|
||||
output: {
|
||||
name: 'test',
|
||||
file: 'dist/tests.js',
|
||||
format: 'iife',
|
||||
entryFileNames : '[name].cjs',
|
||||
sourcemap: true
|
||||
},
|
||||
plugins: [
|
||||
debugResolve,
|
||||
nodeResolve({
|
||||
mainFields: ['browser', 'module', 'main']
|
||||
}),
|
||||
commonjs()
|
||||
]
|
||||
}, {
|
||||
input: './tests/index.js',
|
||||
output: {
|
||||
name: 'test',
|
||||
file: 'dist/tests.cjs',
|
||||
format: 'cjs',
|
||||
sourcemap: true
|
||||
},
|
||||
plugins: [
|
||||
debugResolve,
|
||||
nodeResolve({
|
||||
mainFields: ['node', 'module', 'main'],
|
||||
exportConditions: ['node', 'module', 'import', 'default']
|
||||
}),
|
||||
commonjs()
|
||||
resolver
|
||||
],
|
||||
external: id => /^lib0\//.test(id)
|
||||
external: id => /^(lib0|y-protocols)\//.test(id)
|
||||
}, {
|
||||
// esm output
|
||||
input: {
|
||||
yjs: './src/index.js',
|
||||
testHelper: './tests/testHelper.js',
|
||||
internals: './src/internals.js'
|
||||
},
|
||||
output: {
|
||||
dir: 'dist',
|
||||
format: 'esm',
|
||||
entryFileNames : '[name].mjs',
|
||||
sourcemap: true
|
||||
},
|
||||
plugins: [
|
||||
resolver
|
||||
],
|
||||
external: id => /^(lib0|y-protocols)\//.test(id)
|
||||
}]
|
||||
|
||||
@@ -50,6 +50,8 @@ export {
|
||||
findRootTypeKey,
|
||||
findIndexSS,
|
||||
getItem,
|
||||
getItemCleanStart,
|
||||
getItemCleanEnd,
|
||||
typeListToArraySnapshot,
|
||||
typeMapGetSnapshot,
|
||||
typeMapGetAllSnapshot,
|
||||
|
||||
@@ -26,7 +26,7 @@ export class AbstractStruct {
|
||||
* This method is already assuming that `this.id.clock + this.length === this.id.clock`.
|
||||
* Also this method does *not* remove right from StructStore!
|
||||
* @param {AbstractStruct} right
|
||||
* @return {boolean} wether this merged with right
|
||||
* @return {boolean} whether this merged with right
|
||||
*/
|
||||
mergeWith (right) {
|
||||
return false
|
||||
|
||||
@@ -2,6 +2,11 @@ import {
|
||||
UpdateEncoderV1, UpdateEncoderV2, UpdateDecoderV1, UpdateDecoderV2, Transaction, Item, StructStore // eslint-disable-line
|
||||
} from '../internals.js'
|
||||
|
||||
import * as env from 'lib0/environment'
|
||||
import * as object from 'lib0/object'
|
||||
|
||||
const isDevMode = env.getVariable('node_env') === 'development'
|
||||
|
||||
export class ContentAny {
|
||||
/**
|
||||
* @param {Array<any>} arr
|
||||
@@ -11,6 +16,7 @@ export class ContentAny {
|
||||
* @type {Array<any>}
|
||||
*/
|
||||
this.arr = arr
|
||||
isDevMode && object.deepFreeze(arr)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -393,8 +393,7 @@ export class Item extends AbstractStruct {
|
||||
if (this.left && this.left.constructor === Item) {
|
||||
this.parent = this.left.parent
|
||||
this.parentSub = this.left.parentSub
|
||||
}
|
||||
if (this.right && this.right.constructor === Item) {
|
||||
} else if (this.right && this.right.constructor === Item) {
|
||||
this.parent = this.right.parent
|
||||
this.parentSub = this.right.parentSub
|
||||
}
|
||||
|
||||
@@ -17,6 +17,12 @@ import * as map from 'lib0/map'
|
||||
import * as iterator from 'lib0/iterator'
|
||||
import * as error from 'lib0/error'
|
||||
import * as math from 'lib0/math'
|
||||
import * as log from 'lib0/logging'
|
||||
|
||||
/**
|
||||
* https://docs.yjs.dev/getting-started/working-with-shared-types#caveats
|
||||
*/
|
||||
export const warnPrematureAccess = () => { log.warn('Invalid access: Add Yjs type to a document before reading data.') }
|
||||
|
||||
const maxSearchMarker = 80
|
||||
|
||||
@@ -149,11 +155,11 @@ export const findMarker = (yarray, index) => {
|
||||
// }
|
||||
// }
|
||||
// if (marker) {
|
||||
// if (window.lengthes == null) {
|
||||
// window.lengthes = []
|
||||
// window.getLengthes = () => window.lengthes.sort((a, b) => a - b)
|
||||
// if (window.lengths == null) {
|
||||
// window.lengths = []
|
||||
// window.getLengths = () => window.lengths.sort((a, b) => a - b)
|
||||
// }
|
||||
// window.lengthes.push(marker.index - pindex)
|
||||
// window.lengths.push(marker.index - pindex)
|
||||
// console.log('distance', marker.index - pindex, 'len', p && p.parent.length)
|
||||
// }
|
||||
if (marker !== null && math.abs(marker.index - pindex) < /** @type {YText|YArray<any>} */ (p.parent).length / maxSearchMarker) {
|
||||
@@ -215,6 +221,7 @@ export const updateMarkerChanges = (searchMarker, index, len) => {
|
||||
* @return {Array<Item>}
|
||||
*/
|
||||
export const getTypeChildren = t => {
|
||||
t.doc ?? warnPrematureAccess()
|
||||
let s = t._start
|
||||
const arr = []
|
||||
while (s) {
|
||||
@@ -408,6 +415,7 @@ export class AbstractType {
|
||||
* @function
|
||||
*/
|
||||
export const typeListSlice = (type, start, end) => {
|
||||
type.doc ?? warnPrematureAccess()
|
||||
if (start < 0) {
|
||||
start = type._length + start
|
||||
}
|
||||
@@ -443,6 +451,7 @@ export const typeListSlice = (type, start, end) => {
|
||||
* @function
|
||||
*/
|
||||
export const typeListToArray = type => {
|
||||
type.doc ?? warnPrematureAccess()
|
||||
const cs = []
|
||||
let n = type._start
|
||||
while (n !== null) {
|
||||
@@ -492,6 +501,7 @@ export const typeListToArraySnapshot = (type, snapshot) => {
|
||||
export const typeListForEach = (type, f) => {
|
||||
let index = 0
|
||||
let n = type._start
|
||||
type.doc ?? warnPrematureAccess()
|
||||
while (n !== null) {
|
||||
if (n.countable && !n.deleted) {
|
||||
const c = n.content.getContent()
|
||||
@@ -606,6 +616,7 @@ export const typeListForEachSnapshot = (type, f, snapshot) => {
|
||||
* @function
|
||||
*/
|
||||
export const typeListGet = (type, index) => {
|
||||
type.doc ?? warnPrematureAccess()
|
||||
const marker = findMarker(type, index)
|
||||
let n = type._start
|
||||
if (marker !== null) {
|
||||
@@ -740,7 +751,7 @@ export const typeListInsertGenerics = (transaction, parent, index, content) => {
|
||||
|
||||
/**
|
||||
* Pushing content is special as we generally want to push after the last item. So we don't have to update
|
||||
* the serach marker.
|
||||
* the search marker.
|
||||
*
|
||||
* @param {Transaction} transaction
|
||||
* @param {AbstractType<any>} parent
|
||||
@@ -874,6 +885,7 @@ export const typeMapSet = (transaction, parent, key, value) => {
|
||||
* @function
|
||||
*/
|
||||
export const typeMapGet = (parent, key) => {
|
||||
parent.doc ?? warnPrematureAccess()
|
||||
const val = parent._map.get(key)
|
||||
return val !== undefined && !val.deleted ? val.content.getContent()[val.length - 1] : undefined
|
||||
}
|
||||
@@ -890,6 +902,7 @@ export const typeMapGetAll = (parent) => {
|
||||
* @type {Object<string,any>}
|
||||
*/
|
||||
const res = {}
|
||||
parent.doc ?? warnPrematureAccess()
|
||||
parent._map.forEach((value, key) => {
|
||||
if (!value.deleted) {
|
||||
res[key] = value.content.getContent()[value.length - 1]
|
||||
@@ -907,6 +920,7 @@ export const typeMapGetAll = (parent) => {
|
||||
* @function
|
||||
*/
|
||||
export const typeMapHas = (parent, key) => {
|
||||
parent.doc ?? warnPrematureAccess()
|
||||
const val = parent._map.get(key)
|
||||
return val !== undefined && !val.deleted
|
||||
}
|
||||
@@ -957,10 +971,13 @@ export const typeMapGetAllSnapshot = (parent, snapshot) => {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Map<string,Item>} map
|
||||
* @param {AbstractType<any> & { _map: Map<string, Item> }} type
|
||||
* @return {IterableIterator<Array<any>>}
|
||||
*
|
||||
* @private
|
||||
* @function
|
||||
*/
|
||||
export const createMapIterator = map => iterator.iteratorFilter(map.entries(), /** @param {any} entry */ entry => !entry[1].deleted)
|
||||
export const createMapIterator = type => {
|
||||
type.doc ?? warnPrematureAccess()
|
||||
return iterator.iteratorFilter(type._map.entries(), /** @param {any} entry */ entry => !entry[1].deleted)
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
YArrayRefID,
|
||||
callTypeObservers,
|
||||
transact,
|
||||
warnPrematureAccess,
|
||||
ArraySearchMarker, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item // eslint-disable-line
|
||||
} from '../internals.js'
|
||||
import { typeListSlice } from './AbstractType.js'
|
||||
@@ -104,7 +105,8 @@ export class YArray extends AbstractType {
|
||||
}
|
||||
|
||||
get length () {
|
||||
return this._prelimContent === null ? this._length : this._prelimContent.length
|
||||
this.doc ?? warnPrematureAccess()
|
||||
return this._length
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
YMapRefID,
|
||||
callTypeObservers,
|
||||
transact,
|
||||
warnPrematureAccess,
|
||||
UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item // eslint-disable-line
|
||||
} from '../internals.js'
|
||||
|
||||
@@ -121,6 +122,7 @@ export class YMap extends AbstractType {
|
||||
* @return {Object<string,any>}
|
||||
*/
|
||||
toJSON () {
|
||||
this.doc ?? warnPrematureAccess()
|
||||
/**
|
||||
* @type {Object<string,MapType>}
|
||||
*/
|
||||
@@ -140,7 +142,7 @@ export class YMap extends AbstractType {
|
||||
* @return {number}
|
||||
*/
|
||||
get size () {
|
||||
return [...createMapIterator(this._map)].length
|
||||
return [...createMapIterator(this)].length
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -149,7 +151,7 @@ export class YMap extends AbstractType {
|
||||
* @return {IterableIterator<string>}
|
||||
*/
|
||||
keys () {
|
||||
return iterator.iteratorMap(createMapIterator(this._map), /** @param {any} v */ v => v[0])
|
||||
return iterator.iteratorMap(createMapIterator(this), /** @param {any} v */ v => v[0])
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -158,7 +160,7 @@ export class YMap extends AbstractType {
|
||||
* @return {IterableIterator<MapType>}
|
||||
*/
|
||||
values () {
|
||||
return iterator.iteratorMap(createMapIterator(this._map), /** @param {any} v */ v => v[1].content.getContent()[v[1].length - 1])
|
||||
return iterator.iteratorMap(createMapIterator(this), /** @param {any} v */ v => v[1].content.getContent()[v[1].length - 1])
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -167,7 +169,7 @@ export class YMap extends AbstractType {
|
||||
* @return {IterableIterator<[string, MapType]>}
|
||||
*/
|
||||
entries () {
|
||||
return iterator.iteratorMap(createMapIterator(this._map), /** @param {any} v */ v => /** @type {any} */ ([v[0], v[1].content.getContent()[v[1].length - 1]]))
|
||||
return iterator.iteratorMap(createMapIterator(this), /** @param {any} v */ v => /** @type {any} */ ([v[0], v[1].content.getContent()[v[1].length - 1]]))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -176,6 +178,7 @@ export class YMap extends AbstractType {
|
||||
* @param {function(MapType,string,YMap<MapType>):void} f A function to execute on every element of this YArray.
|
||||
*/
|
||||
forEach (f) {
|
||||
this.doc ?? warnPrematureAccess()
|
||||
this._map.forEach((item, key) => {
|
||||
if (!item.deleted) {
|
||||
f(item.content.getContent()[item.length - 1], key, this)
|
||||
|
||||
@@ -26,6 +26,7 @@ import {
|
||||
typeMapGetAll,
|
||||
updateMarkerChanges,
|
||||
ContentType,
|
||||
warnPrematureAccess,
|
||||
ArraySearchMarker, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, ID, Doc, Item, Snapshot, Transaction // eslint-disable-line
|
||||
} from '../internals.js'
|
||||
|
||||
@@ -477,7 +478,7 @@ export const cleanupYTextFormatting = type => {
|
||||
}
|
||||
|
||||
/**
|
||||
* This will be called by the transction once the event handlers are called to potentially cleanup
|
||||
* This will be called by the transaction once the event handlers are called to potentially cleanup
|
||||
* formatting attributes.
|
||||
*
|
||||
* @param {Transaction} transaction
|
||||
@@ -567,7 +568,7 @@ const deleteText = (transaction, currPos, length) => {
|
||||
|
||||
/**
|
||||
* The Quill Delta format represents changes on a text document with
|
||||
* formatting information. For mor information visit {@link https://quilljs.com/docs/delta/|Quill Delta}
|
||||
* formatting information. For more information visit {@link https://quilljs.com/docs/delta/|Quill Delta}
|
||||
*
|
||||
* @example
|
||||
* {
|
||||
@@ -875,6 +876,7 @@ export class YText extends AbstractType {
|
||||
* @type {number}
|
||||
*/
|
||||
get length () {
|
||||
this.doc ?? warnPrematureAccess()
|
||||
return this._length
|
||||
}
|
||||
|
||||
@@ -931,6 +933,7 @@ export class YText extends AbstractType {
|
||||
* @public
|
||||
*/
|
||||
toString () {
|
||||
this.doc ?? warnPrematureAccess()
|
||||
let str = ''
|
||||
/**
|
||||
* @type {Item|null}
|
||||
@@ -958,7 +961,7 @@ export class YText extends AbstractType {
|
||||
/**
|
||||
* Apply a {@link Delta} on this shared YText type.
|
||||
*
|
||||
* @param {any} delta The changes to apply on this element.
|
||||
* @param {Array<any>} delta The changes to apply on this element.
|
||||
* @param {object} opts
|
||||
* @param {boolean} [opts.sanitize] Sanitize input delta. Removes ending newlines if set to true.
|
||||
*
|
||||
@@ -1004,6 +1007,7 @@ export class YText extends AbstractType {
|
||||
* @public
|
||||
*/
|
||||
toDelta (snapshot, prevSnapshot, computeYChange) {
|
||||
this.doc ?? warnPrematureAccess()
|
||||
/**
|
||||
* @type{Array<any>}
|
||||
*/
|
||||
|
||||
@@ -12,7 +12,7 @@ export class YXmlEvent extends YEvent {
|
||||
* @param {YXmlElement|YXmlText|YXmlFragment} target The target on which the event is created.
|
||||
* @param {Set<string|null>} subs The set of changed attributes. `null` is included if the
|
||||
* child list changed.
|
||||
* @param {Transaction} transaction The transaction instance with wich the
|
||||
* @param {Transaction} transaction The transaction instance with which the
|
||||
* change was created.
|
||||
*/
|
||||
constructor (target, subs, transaction) {
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
transact,
|
||||
typeListGet,
|
||||
typeListSlice,
|
||||
warnPrematureAccess,
|
||||
UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, ContentType, Transaction, Item, YXmlText, YXmlHook // eslint-disable-line
|
||||
} from '../internals.js'
|
||||
|
||||
@@ -66,6 +67,7 @@ export class YXmlTreeWalker {
|
||||
*/
|
||||
this._currentNode = /** @type {Item} */ (root._start)
|
||||
this._firstCall = true
|
||||
root.doc ?? warnPrematureAccess()
|
||||
}
|
||||
|
||||
[Symbol.iterator] () {
|
||||
@@ -94,8 +96,12 @@ export class YXmlTreeWalker {
|
||||
} else {
|
||||
// walk right or up in the tree
|
||||
while (n !== null) {
|
||||
if (n.right !== null) {
|
||||
n = n.right
|
||||
/**
|
||||
* @type {Item | null}
|
||||
*/
|
||||
const nxt = n.next
|
||||
if (nxt !== null) {
|
||||
n = nxt
|
||||
break
|
||||
} else if (n.parent === this._root) {
|
||||
n = null
|
||||
@@ -177,6 +183,7 @@ export class YXmlFragment extends AbstractType {
|
||||
}
|
||||
|
||||
get length () {
|
||||
this.doc ?? warnPrematureAccess()
|
||||
return this._prelimContent === null ? this._length : this._prelimContent.length
|
||||
}
|
||||
|
||||
|
||||
177
src/utils/Delta.js
Normal file
177
src/utils/Delta.js
Normal file
@@ -0,0 +1,177 @@
|
||||
import * as object from 'lib0/object'
|
||||
import * as array from 'lib0/array'
|
||||
|
||||
/**
|
||||
* @typedef {InsertOp|RetainOp|DeleteOp} DeltaOp
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {{ [key: string]: any }} FormattingAttributes
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {Object} Attribution
|
||||
* @property {boolean} [Attribution.isDeleted]
|
||||
* @property {boolean} [Attribution.isAdded]
|
||||
* @property {string} [Attribution.creator]
|
||||
* @property {number} [Attribution.timestamp]
|
||||
*/
|
||||
|
||||
export class InsertOp {
|
||||
/**
|
||||
* @param {string} insert
|
||||
* @param {FormattingAttributes|null} attributes
|
||||
* @param {Attribution|null} attribution
|
||||
*/
|
||||
constructor (insert, attributes, attribution) {
|
||||
this.insert = insert
|
||||
this.attributes = attributes
|
||||
this.attribution = attribution
|
||||
}
|
||||
toJSON () {
|
||||
return object.assign({ insert: this.insert }, this.attributes ? { attributes: this.attributes } : {}, this.attribution ? { attribution: this.attribution } : {})
|
||||
}
|
||||
}
|
||||
|
||||
class DeleteOp {
|
||||
/**
|
||||
* @param {number} len
|
||||
*/
|
||||
constructor (len) {
|
||||
this.delete = len
|
||||
}
|
||||
toJSON () {
|
||||
return { delete: this.delete }
|
||||
}
|
||||
}
|
||||
|
||||
class RetainOp {
|
||||
/**
|
||||
* @param {number} retain
|
||||
* @param {FormattingAttributes|null} attributes
|
||||
* @param {Attribution|null} attribution
|
||||
*/
|
||||
constructor (retain, attributes, attribution) {
|
||||
this.retain = retain
|
||||
this.attributes = attributes
|
||||
this.attribution = attribution
|
||||
}
|
||||
toJSON () {
|
||||
return object.assign({ retain: this.retain }, this.attributes ? { attributes: this.attributes } : {}, this.attribution ? { attribution: this.attribution } : {})
|
||||
}
|
||||
}
|
||||
|
||||
export class Delta {
|
||||
constructor () {
|
||||
/**
|
||||
* @type {Array<DeltaOp>}
|
||||
*/
|
||||
this.ops = []
|
||||
}
|
||||
toJSON () {
|
||||
return { ops: this.ops.map(o => o.toJSON()) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to merge attribution and attributes. The latter input "wins".
|
||||
*
|
||||
* @template {{ [key: string]: any }} T
|
||||
* @param {T | null} a
|
||||
* @param {T | null} b
|
||||
*/
|
||||
const mergeAttrs = (a, b) => a == null ? b : (b == null ? a : object.assign({}, a, b))
|
||||
|
||||
export class DeltaBuilder extends Delta {
|
||||
constructor () {
|
||||
super()
|
||||
/**
|
||||
* @private
|
||||
* @type {FormattingAttributes?}
|
||||
*/
|
||||
this._useAttributes = null
|
||||
/**
|
||||
* @private
|
||||
* @type {Attribution?}
|
||||
*/
|
||||
this._useAttribution = null
|
||||
/**
|
||||
* @private
|
||||
* @type {DeltaOp?}
|
||||
*/
|
||||
this._lastOp = null
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {FormattingAttributes} attributes
|
||||
* @return {this}
|
||||
*/
|
||||
useAttributes (attributes) {
|
||||
if (this._useAttributes === attributes) return this
|
||||
this._useAttributes = object.assign({}, attributes)
|
||||
if (this._lastOp?.constructor !== DeleteOp) this._lastOp = null
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Attribution} attribution
|
||||
*/
|
||||
useAttribution (attribution) {
|
||||
if (this._useAttribution === attribution) return this
|
||||
this._useAttribution = object.assign({}, attribution)
|
||||
if (this._lastOp?.constructor !== DeleteOp) this._lastOp = null
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} insert
|
||||
* @param {FormattingAttributes?} attributes
|
||||
* @param {Attribution?} attribution
|
||||
* @return {this}
|
||||
*/
|
||||
insert (insert, attributes = null, attribution = null) {
|
||||
if (attributes === null && attribution === null && this._lastOp instanceof InsertOp) {
|
||||
this._lastOp.insert += insert
|
||||
} else {
|
||||
this.ops.push(this._lastOp = new InsertOp(insert, mergeAttrs(this.useAttributes, attributes), mergeAttrs(this._useAttribution, attribution)))
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} retain
|
||||
* @param {FormattingAttributes?} attributes
|
||||
* @param {Attribution?} attribution
|
||||
* @return {this}
|
||||
*/
|
||||
retain (retain, attributes = null, attribution = null) {
|
||||
if (attributes === null && attribution === null && this._lastOp instanceof RetainOp) {
|
||||
this._lastOp.retain += retain
|
||||
} else {
|
||||
this.ops.push(this._lastOp = new RetainOp(retain, mergeAttrs(this.useAttributes, attributes), mergeAttrs(this._useAttribution, attribution)))
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number} len
|
||||
* @return {this}
|
||||
*/
|
||||
delete (len) {
|
||||
if (this._lastOp instanceof DeleteOp) {
|
||||
this._lastOp.delete += len
|
||||
} else {
|
||||
this.ops.push(this._lastOp = new DeleteOp(len))
|
||||
}
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {Delta}
|
||||
*/
|
||||
done () {
|
||||
return this
|
||||
}
|
||||
}
|
||||
|
||||
export const create = () => new DeltaBuilder()
|
||||
@@ -104,8 +104,9 @@ export class Doc extends ObservableV2 {
|
||||
* lost (with false as a parameter).
|
||||
*/
|
||||
this.isSynced = false
|
||||
this.isDestroyed = false
|
||||
/**
|
||||
* Promise that resolves once the document has been loaded from a presistence provider.
|
||||
* Promise that resolves once the document has been loaded from a persistence provider.
|
||||
*/
|
||||
this.whenLoaded = promise.create(resolve => {
|
||||
this.on('load', () => {
|
||||
@@ -322,6 +323,7 @@ export class Doc extends ObservableV2 {
|
||||
* Emit `destroy` event and unregister all event handlers.
|
||||
*/
|
||||
destroy () {
|
||||
this.isDestroyed = true
|
||||
array.from(this.subdocs).forEach(subdoc => subdoc.destroy())
|
||||
const item = this._item
|
||||
if (item !== null) {
|
||||
|
||||
@@ -62,7 +62,7 @@ export class PermanentUserData {
|
||||
initUser(storeType.get(userDescription), userDescription)
|
||||
)
|
||||
})
|
||||
// add intial data
|
||||
// add initial data
|
||||
storeType.forEach(initUser)
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
ContentType,
|
||||
followRedone,
|
||||
getItem,
|
||||
ID, Doc, AbstractType // eslint-disable-line
|
||||
StructStore, ID, Doc, AbstractType, // eslint-disable-line
|
||||
} from '../internals.js'
|
||||
|
||||
import * as encoding from 'lib0/encoding'
|
||||
@@ -66,7 +66,7 @@ export class RelativePosition {
|
||||
* after the meant position.
|
||||
* I.e. position 1 in 'ab' is associated to character 'b'.
|
||||
*
|
||||
* If assoc < 0, then the relative position is associated to the caharacter
|
||||
* If assoc < 0, then the relative position is associated to the character
|
||||
* before the meant position.
|
||||
*
|
||||
* @type {number}
|
||||
@@ -256,6 +256,18 @@ export const readRelativePosition = decoder => {
|
||||
*/
|
||||
export const decodeRelativePosition = uint8Array => readRelativePosition(decoding.createDecoder(uint8Array))
|
||||
|
||||
/**
|
||||
* @param {StructStore} store
|
||||
* @param {ID} id
|
||||
*/
|
||||
const getItemWithOffset = (store, id) => {
|
||||
const item = getItem(store, id)
|
||||
const diff = id.clock - item.id.clock
|
||||
return {
|
||||
item, diff
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform a relative position to an absolute position.
|
||||
*
|
||||
@@ -286,7 +298,7 @@ export const createAbsolutePositionFromRelativePosition = (rpos, doc, followUndo
|
||||
if (getState(store, rightID.client) <= rightID.clock) {
|
||||
return null
|
||||
}
|
||||
const res = followUndoneDeletions ? followRedone(store, rightID) : { item: getItem(store, rightID), diff: 0 }
|
||||
const res = followUndoneDeletions ? followRedone(store, rightID) : getItemWithOffset(store, rightID)
|
||||
const right = res.item
|
||||
if (!(right instanceof Item)) {
|
||||
return null
|
||||
|
||||
@@ -66,13 +66,13 @@ export const getState = (store, client) => {
|
||||
* @private
|
||||
* @function
|
||||
*/
|
||||
export const integretyCheck = store => {
|
||||
export const integrityCheck = store => {
|
||||
store.clients.forEach(structs => {
|
||||
for (let i = 1; i < structs.length; i++) {
|
||||
const l = structs[i - 1]
|
||||
const r = structs[i]
|
||||
if (l.id.clock + l.length !== r.id.clock) {
|
||||
throw new Error('StructStore failed integrety check')
|
||||
throw new Error('StructStore failed integrity check')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -39,7 +39,7 @@ export class StackItem {
|
||||
*/
|
||||
const clearUndoManagerStackItem = (tr, um, stackItem) => {
|
||||
iterateDeletedStructs(tr, stackItem.deletions, item => {
|
||||
if (item instanceof Item && um.scope.some(type => isParentOf(type, item))) {
|
||||
if (item instanceof Item && um.scope.some(type => type === tr.doc || isParentOf(/** @type {AbstractType<any>} */ (type), item))) {
|
||||
keepItem(item, false)
|
||||
}
|
||||
})
|
||||
@@ -81,7 +81,7 @@ const popStackItem = (undoManager, stack, eventType) => {
|
||||
}
|
||||
struct = item
|
||||
}
|
||||
if (!struct.deleted && scope.some(type => isParentOf(type, /** @type {Item} */ (struct)))) {
|
||||
if (!struct.deleted && scope.some(type => type === transaction.doc || isParentOf(/** @type {AbstractType<any>} */ (type), /** @type {Item} */ (struct)))) {
|
||||
itemsToDelete.push(struct)
|
||||
}
|
||||
}
|
||||
@@ -89,7 +89,7 @@ const popStackItem = (undoManager, stack, eventType) => {
|
||||
iterateDeletedStructs(transaction, stackItem.deletions, struct => {
|
||||
if (
|
||||
struct instanceof Item &&
|
||||
scope.some(type => isParentOf(type, struct)) &&
|
||||
scope.some(type => type === transaction.doc || isParentOf(/** @type {AbstractType<any>} */ (type), struct)) &&
|
||||
// Never redo structs in stackItem.insertions because they were created and deleted in the same capture interval.
|
||||
!isDeleted(stackItem.insertions, struct.id)
|
||||
) {
|
||||
@@ -159,7 +159,7 @@ const popStackItem = (undoManager, stack, eventType) => {
|
||||
*/
|
||||
export class UndoManager extends ObservableV2 {
|
||||
/**
|
||||
* @param {AbstractType<any>|Array<AbstractType<any>>} typeScope Accepts either a single type, or an array of types
|
||||
* @param {Doc|AbstractType<any>|Array<AbstractType<any>>} typeScope Limits the scope of the UndoManager. If this is set to a ydoc instance, all changes on that ydoc will be undone. If set to a specific type, only changes on that type or its children will be undone. Also accepts an array of types.
|
||||
* @param {UndoManagerOptions} options
|
||||
*/
|
||||
constructor (typeScope, {
|
||||
@@ -168,11 +168,11 @@ export class UndoManager extends ObservableV2 {
|
||||
deleteFilter = () => true,
|
||||
trackedOrigins = new Set([null]),
|
||||
ignoreRemoteMapChanges = false,
|
||||
doc = /** @type {Doc} */ (array.isArray(typeScope) ? typeScope[0].doc : typeScope.doc)
|
||||
doc = /** @type {Doc} */ (array.isArray(typeScope) ? typeScope[0].doc : typeScope instanceof Doc ? typeScope : typeScope.doc)
|
||||
} = {}) {
|
||||
super()
|
||||
/**
|
||||
* @type {Array<AbstractType<any>>}
|
||||
* @type {Array<AbstractType<any> | Doc>}
|
||||
*/
|
||||
this.scope = []
|
||||
this.doc = doc
|
||||
@@ -212,7 +212,7 @@ export class UndoManager extends ObservableV2 {
|
||||
// Only track certain transactions
|
||||
if (
|
||||
!this.captureTransaction(transaction) ||
|
||||
!this.scope.some(type => transaction.changedParentTypes.has(type)) ||
|
||||
!this.scope.some(type => transaction.changedParentTypes.has(/** @type {AbstractType<any>} */ (type)) || type === this.doc) ||
|
||||
(!this.trackedOrigins.has(transaction.origin) && (!transaction.origin || !this.trackedOrigins.has(transaction.origin.constructor)))
|
||||
) {
|
||||
return
|
||||
@@ -251,7 +251,7 @@ export class UndoManager extends ObservableV2 {
|
||||
}
|
||||
// make sure that deleted structs are not gc'd
|
||||
iterateDeletedStructs(transaction, transaction.deleteSet, /** @param {Item|GC} item */ item => {
|
||||
if (item instanceof Item && this.scope.some(type => isParentOf(type, item))) {
|
||||
if (item instanceof Item && this.scope.some(type => type === transaction.doc || isParentOf(/** @type {AbstractType<any>} */ (type), item))) {
|
||||
keepItem(item, true)
|
||||
}
|
||||
})
|
||||
@@ -272,13 +272,17 @@ export class UndoManager extends ObservableV2 {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Array<AbstractType<any>> | AbstractType<any>} ytypes
|
||||
* Extend the scope.
|
||||
*
|
||||
* @param {Array<AbstractType<any> | Doc> | AbstractType<any> | Doc} ytypes
|
||||
*/
|
||||
addToScope (ytypes) {
|
||||
const tmpSet = new Set(this.scope)
|
||||
ytypes = array.isArray(ytypes) ? ytypes : [ytypes]
|
||||
ytypes.forEach(ytype => {
|
||||
if (this.scope.every(yt => yt !== ytype)) {
|
||||
if (ytype.doc !== this.doc) logging.warn('[yjs#509] Not same Y.Doc') // use MultiDocUndoManager instead. also see https://github.com/yjs/yjs/issues/509
|
||||
if (!tmpSet.has(ytype)) {
|
||||
tmpSet.add(ytype)
|
||||
if (ytype instanceof AbstractType ? ytype.doc !== this.doc : ytype !== this.doc) logging.warn('[yjs#509] Not same Y.Doc') // use MultiDocUndoManager instead. also see https://github.com/yjs/yjs/issues/509
|
||||
this.scope.push(ytype)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -167,7 +167,7 @@ export class UpdateEncoderV2 extends DSEncoderV2 {
|
||||
*/
|
||||
this.keyMap = new Map()
|
||||
/**
|
||||
* Refers to the next uniqe key-identifier to me used.
|
||||
* Refers to the next unique key-identifier to me used.
|
||||
* See writeKey method for more information.
|
||||
*
|
||||
* @type {number}
|
||||
|
||||
@@ -211,7 +211,7 @@ export const readClientsStructRefs = (decoder, doc) => {
|
||||
* then we start emptying the stack.
|
||||
*
|
||||
* It is not possible to have circles: i.e. struct1 (from client1) depends on struct2 (from client2)
|
||||
* depends on struct3 (from client1). Therefore the max stack size is eqaul to `structReaders.length`.
|
||||
* depends on struct3 (from client1). Therefore the max stack size is equal to `structReaders.length`.
|
||||
*
|
||||
* This method is implemented in a way so that we can resume computation if this update
|
||||
* causally depends on another update.
|
||||
@@ -279,14 +279,14 @@ const integrateStructs = (transaction, store, clientsStructRefs) => {
|
||||
const addStackToRestSS = () => {
|
||||
for (const item of stack) {
|
||||
const client = item.id.client
|
||||
const unapplicableItems = clientsStructRefs.get(client)
|
||||
if (unapplicableItems) {
|
||||
const inapplicableItems = clientsStructRefs.get(client)
|
||||
if (inapplicableItems) {
|
||||
// decrement because we weren't able to apply previous operation
|
||||
unapplicableItems.i--
|
||||
restStructs.clients.set(client, unapplicableItems.refs.slice(unapplicableItems.i))
|
||||
inapplicableItems.i--
|
||||
restStructs.clients.set(client, inapplicableItems.refs.slice(inapplicableItems.i))
|
||||
clientsStructRefs.delete(client)
|
||||
unapplicableItems.i = 0
|
||||
unapplicableItems.refs = []
|
||||
inapplicableItems.i = 0
|
||||
inapplicableItems.refs = []
|
||||
} else {
|
||||
// item was the last item on clientsStructRefs and the field was already cleared. Add item to restStructs and continue
|
||||
restStructs.clients.set(client, [item])
|
||||
|
||||
322
test.html
322
test.html
@@ -1,9 +1,327 @@
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Testing Yjs</title>
|
||||
<title>Testing yjs</title>
|
||||
<script type="importmap">
|
||||
{
|
||||
"imports": {
|
||||
"yjs": "./src/index.js",
|
||||
"yjs/internals": "./src/internals.js",
|
||||
"yjs/testHelper": "./tests/testHelper.js",
|
||||
"yjs/package.json": "./package.json",
|
||||
"lib0/package.json": "./node_modules/lib0/package.json",
|
||||
"lib0": "./node_modules/lib0/index.js",
|
||||
"lib0/array.js": "./node_modules/lib0/array.js",
|
||||
"lib0/dist/array.cjs": "./node_modules/lib0/dist/array.cjs",
|
||||
"lib0/array": "./node_modules/lib0/array.js",
|
||||
"lib0/binary.js": "./node_modules/lib0/binary.js",
|
||||
"lib0/dist/binary.cjs": "./node_modules/lib0/dist/binary.cjs",
|
||||
"lib0/binary": "./node_modules/lib0/binary.js",
|
||||
"lib0/broadcastchannel.js": "./node_modules/lib0/broadcastchannel.js",
|
||||
"lib0/dist/broadcastchannel.cjs": "./node_modules/lib0/dist/broadcastchannel.cjs",
|
||||
"lib0/broadcastchannel": "./node_modules/lib0/broadcastchannel.js",
|
||||
"lib0/buffer.js": "./node_modules/lib0/buffer.js",
|
||||
"lib0/dist/buffer.cjs": "./node_modules/lib0/dist/buffer.cjs",
|
||||
"lib0/buffer": "./node_modules/lib0/buffer.js",
|
||||
"lib0/cache.js": "./node_modules/lib0/cache.js",
|
||||
"lib0/dist/cache.cjs": "./node_modules/lib0/dist/cache.cjs",
|
||||
"lib0/cache": "./node_modules/lib0/cache.js",
|
||||
"lib0/component.js": "./node_modules/lib0/component.js",
|
||||
"lib0/dist/component.cjs": "./node_modules/lib0/dist/component.cjs",
|
||||
"lib0/component": "./node_modules/lib0/component.js",
|
||||
"lib0/conditions.js": "./node_modules/lib0/conditions.js",
|
||||
"lib0/dist/conditions.cjs": "./node_modules/lib0/dist/conditions.cjs",
|
||||
"lib0/conditions": "./node_modules/lib0/conditions.js",
|
||||
"lib0/crypto/jwt": "./node_modules/lib0/crypto/jwt.js",
|
||||
"lib0/crypto/aes-gcm": "./node_modules/lib0/crypto/aes-gcm.js",
|
||||
"lib0/crypto/ecdsa": "./node_modules/lib0/crypto/ecdsa.js",
|
||||
"lib0/crypto/rsa-oaep": "./node_modules/lib0/crypto/rsa-oaep.js",
|
||||
"lib0/hash/rabin": "./node_modules/lib0/hash/rabin.js",
|
||||
"lib0/hash/sha256": "./node_modules/lib0/hash/sha256.js",
|
||||
"lib0/decoding.js": "./node_modules/lib0/decoding.js",
|
||||
"lib0/dist/decoding.cjs": "./node_modules/lib0/dist/decoding.cjs",
|
||||
"lib0/decoding": "./node_modules/lib0/decoding.js",
|
||||
"lib0/diff.js": "./node_modules/lib0/diff.js",
|
||||
"lib0/dist/diff.cjs": "./node_modules/lib0/dist/diff.cjs",
|
||||
"lib0/diff": "./node_modules/lib0/diff.js",
|
||||
"lib0/dom.js": "./node_modules/lib0/dom.js",
|
||||
"lib0/dist/dom.cjs": "./node_modules/lib0/dist/dom.cjs",
|
||||
"lib0/dom": "./node_modules/lib0/dom.js",
|
||||
"lib0/encoding.js": "./node_modules/lib0/encoding.js",
|
||||
"lib0/dist/encoding.cjs": "./node_modules/lib0/dist/encoding.cjs",
|
||||
"lib0/encoding": "./node_modules/lib0/encoding.js",
|
||||
"lib0/environment.js": "./node_modules/lib0/environment.js",
|
||||
"lib0/dist/environment.cjs": "./node_modules/lib0/dist/environment.cjs",
|
||||
"lib0/environment": "./node_modules/lib0/environment.js",
|
||||
"lib0/error.js": "./node_modules/lib0/error.js",
|
||||
"lib0/dist/error.cjs": "./node_modules/lib0/dist/error.cjs",
|
||||
"lib0/error": "./node_modules/lib0/error.js",
|
||||
"lib0/eventloop.js": "./node_modules/lib0/eventloop.js",
|
||||
"lib0/dist/eventloop.cjs": "./node_modules/lib0/dist/eventloop.cjs",
|
||||
"lib0/eventloop": "./node_modules/lib0/eventloop.js",
|
||||
"lib0/function.js": "./node_modules/lib0/function.js",
|
||||
"lib0/dist/function.cjs": "./node_modules/lib0/dist/function.cjs",
|
||||
"lib0/function": "./node_modules/lib0/function.js",
|
||||
"lib0/indexeddb.js": "./node_modules/lib0/indexeddb.js",
|
||||
"lib0/dist/indexeddb.cjs": "./node_modules/lib0/dist/indexeddb.cjs",
|
||||
"lib0/indexeddb": "./node_modules/lib0/indexeddb.js",
|
||||
"lib0/isomorphic.js": "./node_modules/lib0/isomorphic.js",
|
||||
"lib0/dist/isomorphic.cjs": "./node_modules/lib0/dist/isomorphic.cjs",
|
||||
"lib0/isomorphic": "./node_modules/lib0/isomorphic.js",
|
||||
"lib0/iterator.js": "./node_modules/lib0/iterator.js",
|
||||
"lib0/dist/iterator.cjs": "./node_modules/lib0/dist/iterator.cjs",
|
||||
"lib0/iterator": "./node_modules/lib0/iterator.js",
|
||||
"lib0/json.js": "./node_modules/lib0/json.js",
|
||||
"lib0/dist/json.cjs": "./node_modules/lib0/dist/json.cjs",
|
||||
"lib0/json": "./node_modules/lib0/json.js",
|
||||
"lib0/list.js": "./node_modules/lib0/list.js",
|
||||
"lib0/dist/list.cjs": "./node_modules/lib0/dist/list.cjs",
|
||||
"lib0/list": "./node_modules/lib0/list.js",
|
||||
"lib0/logging.js": "./node_modules/lib0/logging.js",
|
||||
"lib0/dist/logging.cjs": "./node_modules/lib0/dist/logging.node.cjs",
|
||||
"lib0/logging": "./node_modules/lib0/logging.js",
|
||||
"lib0/map.js": "./node_modules/lib0/map.js",
|
||||
"lib0/dist/map.cjs": "./node_modules/lib0/dist/map.cjs",
|
||||
"lib0/map": "./node_modules/lib0/map.js",
|
||||
"lib0/math.js": "./node_modules/lib0/math.js",
|
||||
"lib0/dist/math.cjs": "./node_modules/lib0/dist/math.cjs",
|
||||
"lib0/math": "./node_modules/lib0/math.js",
|
||||
"lib0/metric.js": "./node_modules/lib0/metric.js",
|
||||
"lib0/dist/metric.cjs": "./node_modules/lib0/dist/metric.cjs",
|
||||
"lib0/metric": "./node_modules/lib0/metric.js",
|
||||
"lib0/mutex.js": "./node_modules/lib0/mutex.js",
|
||||
"lib0/dist/mutex.cjs": "./node_modules/lib0/dist/mutex.cjs",
|
||||
"lib0/mutex": "./node_modules/lib0/mutex.js",
|
||||
"lib0/number.js": "./node_modules/lib0/number.js",
|
||||
"lib0/dist/number.cjs": "./node_modules/lib0/dist/number.cjs",
|
||||
"lib0/number": "./node_modules/lib0/number.js",
|
||||
"lib0/object.js": "./node_modules/lib0/object.js",
|
||||
"lib0/dist/object.cjs": "./node_modules/lib0/dist/object.cjs",
|
||||
"lib0/object": "./node_modules/lib0/object.js",
|
||||
"lib0/observable.js": "./node_modules/lib0/observable.js",
|
||||
"lib0/dist/observable.cjs": "./node_modules/lib0/dist/observable.cjs",
|
||||
"lib0/observable": "./node_modules/lib0/observable.js",
|
||||
"lib0/pair.js": "./node_modules/lib0/pair.js",
|
||||
"lib0/dist/pair.cjs": "./node_modules/lib0/dist/pair.cjs",
|
||||
"lib0/pair": "./node_modules/lib0/pair.js",
|
||||
"lib0/prng.js": "./node_modules/lib0/prng.js",
|
||||
"lib0/dist/prng.cjs": "./node_modules/lib0/dist/prng.cjs",
|
||||
"lib0/prng": "./node_modules/lib0/prng.js",
|
||||
"lib0/promise.js": "./node_modules/lib0/promise.js",
|
||||
"lib0/dist/promise.cjs": "./node_modules/lib0/dist/promise.cjs",
|
||||
"lib0/promise": "./node_modules/lib0/promise.js",
|
||||
"lib0/queue.js": "./node_modules/lib0/queue.js",
|
||||
"lib0/dist/queue.cjs": "./node_modules/lib0/dist/queue.cjs",
|
||||
"lib0/queue": "./node_modules/lib0/queue.js",
|
||||
"lib0/random.js": "./node_modules/lib0/random.js",
|
||||
"lib0/dist/random.cjs": "./node_modules/lib0/dist/random.cjs",
|
||||
"lib0/random": "./node_modules/lib0/random.js",
|
||||
"lib0/set.js": "./node_modules/lib0/set.js",
|
||||
"lib0/dist/set.cjs": "./node_modules/lib0/dist/set.cjs",
|
||||
"lib0/set": "./node_modules/lib0/set.js",
|
||||
"lib0/sort.js": "./node_modules/lib0/sort.js",
|
||||
"lib0/dist/sort.cjs": "./node_modules/lib0/dist/sort.cjs",
|
||||
"lib0/sort": "./node_modules/lib0/sort.js",
|
||||
"lib0/statistics.js": "./node_modules/lib0/statistics.js",
|
||||
"lib0/dist/statistics.cjs": "./node_modules/lib0/dist/statistics.cjs",
|
||||
"lib0/statistics": "./node_modules/lib0/statistics.js",
|
||||
"lib0/storage.js": "./node_modules/lib0/storage.js",
|
||||
"lib0/dist/storage.cjs": "./node_modules/lib0/dist/storage.cjs",
|
||||
"lib0/storage": "./node_modules/lib0/storage.js",
|
||||
"lib0/string.js": "./node_modules/lib0/string.js",
|
||||
"lib0/dist/string.cjs": "./node_modules/lib0/dist/string.cjs",
|
||||
"lib0/string": "./node_modules/lib0/string.js",
|
||||
"lib0/symbol.js": "./node_modules/lib0/symbol.js",
|
||||
"lib0/dist/symbol.cjs": "./node_modules/lib0/dist/symbol.cjs",
|
||||
"lib0/symbol": "./node_modules/lib0/symbol.js",
|
||||
"lib0/testing.js": "./node_modules/lib0/testing.js",
|
||||
"lib0/dist/testing.cjs": "./node_modules/lib0/dist/testing.cjs",
|
||||
"lib0/testing": "./node_modules/lib0/testing.js",
|
||||
"lib0/time.js": "./node_modules/lib0/time.js",
|
||||
"lib0/dist/time.cjs": "./node_modules/lib0/dist/time.cjs",
|
||||
"lib0/time": "./node_modules/lib0/time.js",
|
||||
"lib0/tree.js": "./node_modules/lib0/tree.js",
|
||||
"lib0/dist/tree.cjs": "./node_modules/lib0/dist/tree.cjs",
|
||||
"lib0/tree": "./node_modules/lib0/tree.js",
|
||||
"lib0/url.js": "./node_modules/lib0/url.js",
|
||||
"lib0/dist/url.cjs": "./node_modules/lib0/dist/url.cjs",
|
||||
"lib0/url": "./node_modules/lib0/url.js",
|
||||
"lib0/websocket.js": "./node_modules/lib0/websocket.js",
|
||||
"lib0/dist/websocket.cjs": "./node_modules/lib0/dist/websocket.cjs",
|
||||
"lib0/websocket": "./node_modules/lib0/websocket.js",
|
||||
"lib0/webcrypto": "./node_modules/lib0/webcrypto.js",
|
||||
"lib0/performance.js": "./node_modules/lib0/performance.js",
|
||||
"lib0/dist/performance.cjs": "./node_modules/lib0/dist/performance.node.cjs",
|
||||
"lib0/performance": "./node_modules/lib0/performance.js",
|
||||
"y-protocols/package.json": "./node_modules/y-protocols/package.json",
|
||||
"y-protocols/sync.js": "./node_modules/y-protocols/sync.js",
|
||||
"y-protocols/dist/sync.cjs": "./node_modules/y-protocols/dist/sync.cjs",
|
||||
"y-protocols/sync": "./node_modules/y-protocols/sync.js",
|
||||
"y-protocols/awareness.js": "./node_modules/y-protocols/awareness.js",
|
||||
"y-protocols/dist/awareness.cjs": "./node_modules/y-protocols/dist/awareness.cjs",
|
||||
"y-protocols/awareness": "./node_modules/y-protocols/awareness.js",
|
||||
"y-protocols/auth.js": "./node_modules/y-protocols/auth.js",
|
||||
"y-protocols/dist/auth.cjs": "./node_modules/y-protocols/dist/auth.cjs",
|
||||
"y-protocols/auth": "./node_modules/y-protocols/auth.js"
|
||||
},
|
||||
"scopes": {
|
||||
"./node_modules/lib0/": {
|
||||
"isomorphic.js": "./node_modules/isomorphic.js/browser.mjs",
|
||||
"isomorphic.js/package.json": "./node_modules/isomorphic.js/package.json"
|
||||
},
|
||||
"./node_modules/y-protocols/": {
|
||||
"lib0/package.json": "./node_modules/lib0/package.json",
|
||||
"lib0": "./node_modules/lib0/index.js",
|
||||
"lib0/array.js": "./node_modules/lib0/array.js",
|
||||
"lib0/dist/array.cjs": "./node_modules/lib0/dist/array.cjs",
|
||||
"lib0/array": "./node_modules/lib0/array.js",
|
||||
"lib0/binary.js": "./node_modules/lib0/binary.js",
|
||||
"lib0/dist/binary.cjs": "./node_modules/lib0/dist/binary.cjs",
|
||||
"lib0/binary": "./node_modules/lib0/binary.js",
|
||||
"lib0/broadcastchannel.js": "./node_modules/lib0/broadcastchannel.js",
|
||||
"lib0/dist/broadcastchannel.cjs": "./node_modules/lib0/dist/broadcastchannel.cjs",
|
||||
"lib0/broadcastchannel": "./node_modules/lib0/broadcastchannel.js",
|
||||
"lib0/buffer.js": "./node_modules/lib0/buffer.js",
|
||||
"lib0/dist/buffer.cjs": "./node_modules/lib0/dist/buffer.cjs",
|
||||
"lib0/buffer": "./node_modules/lib0/buffer.js",
|
||||
"lib0/cache.js": "./node_modules/lib0/cache.js",
|
||||
"lib0/dist/cache.cjs": "./node_modules/lib0/dist/cache.cjs",
|
||||
"lib0/cache": "./node_modules/lib0/cache.js",
|
||||
"lib0/component.js": "./node_modules/lib0/component.js",
|
||||
"lib0/dist/component.cjs": "./node_modules/lib0/dist/component.cjs",
|
||||
"lib0/component": "./node_modules/lib0/component.js",
|
||||
"lib0/conditions.js": "./node_modules/lib0/conditions.js",
|
||||
"lib0/dist/conditions.cjs": "./node_modules/lib0/dist/conditions.cjs",
|
||||
"lib0/conditions": "./node_modules/lib0/conditions.js",
|
||||
"lib0/crypto/jwt": "./node_modules/lib0/crypto/jwt.js",
|
||||
"lib0/crypto/aes-gcm": "./node_modules/lib0/crypto/aes-gcm.js",
|
||||
"lib0/crypto/ecdsa": "./node_modules/lib0/crypto/ecdsa.js",
|
||||
"lib0/crypto/rsa-oaep": "./node_modules/lib0/crypto/rsa-oaep.js",
|
||||
"lib0/hash/rabin": "./node_modules/lib0/hash/rabin.js",
|
||||
"lib0/hash/sha256": "./node_modules/lib0/hash/sha256.js",
|
||||
"lib0/decoding.js": "./node_modules/lib0/decoding.js",
|
||||
"lib0/dist/decoding.cjs": "./node_modules/lib0/dist/decoding.cjs",
|
||||
"lib0/decoding": "./node_modules/lib0/decoding.js",
|
||||
"lib0/diff.js": "./node_modules/lib0/diff.js",
|
||||
"lib0/dist/diff.cjs": "./node_modules/lib0/dist/diff.cjs",
|
||||
"lib0/diff": "./node_modules/lib0/diff.js",
|
||||
"lib0/dom.js": "./node_modules/lib0/dom.js",
|
||||
"lib0/dist/dom.cjs": "./node_modules/lib0/dist/dom.cjs",
|
||||
"lib0/dom": "./node_modules/lib0/dom.js",
|
||||
"lib0/encoding.js": "./node_modules/lib0/encoding.js",
|
||||
"lib0/dist/encoding.cjs": "./node_modules/lib0/dist/encoding.cjs",
|
||||
"lib0/encoding": "./node_modules/lib0/encoding.js",
|
||||
"lib0/environment.js": "./node_modules/lib0/environment.js",
|
||||
"lib0/dist/environment.cjs": "./node_modules/lib0/dist/environment.cjs",
|
||||
"lib0/environment": "./node_modules/lib0/environment.js",
|
||||
"lib0/error.js": "./node_modules/lib0/error.js",
|
||||
"lib0/dist/error.cjs": "./node_modules/lib0/dist/error.cjs",
|
||||
"lib0/error": "./node_modules/lib0/error.js",
|
||||
"lib0/eventloop.js": "./node_modules/lib0/eventloop.js",
|
||||
"lib0/dist/eventloop.cjs": "./node_modules/lib0/dist/eventloop.cjs",
|
||||
"lib0/eventloop": "./node_modules/lib0/eventloop.js",
|
||||
"lib0/function.js": "./node_modules/lib0/function.js",
|
||||
"lib0/dist/function.cjs": "./node_modules/lib0/dist/function.cjs",
|
||||
"lib0/function": "./node_modules/lib0/function.js",
|
||||
"lib0/indexeddb.js": "./node_modules/lib0/indexeddb.js",
|
||||
"lib0/dist/indexeddb.cjs": "./node_modules/lib0/dist/indexeddb.cjs",
|
||||
"lib0/indexeddb": "./node_modules/lib0/indexeddb.js",
|
||||
"lib0/isomorphic.js": "./node_modules/lib0/isomorphic.js",
|
||||
"lib0/dist/isomorphic.cjs": "./node_modules/lib0/dist/isomorphic.cjs",
|
||||
"lib0/isomorphic": "./node_modules/lib0/isomorphic.js",
|
||||
"lib0/iterator.js": "./node_modules/lib0/iterator.js",
|
||||
"lib0/dist/iterator.cjs": "./node_modules/lib0/dist/iterator.cjs",
|
||||
"lib0/iterator": "./node_modules/lib0/iterator.js",
|
||||
"lib0/json.js": "./node_modules/lib0/json.js",
|
||||
"lib0/dist/json.cjs": "./node_modules/lib0/dist/json.cjs",
|
||||
"lib0/json": "./node_modules/lib0/json.js",
|
||||
"lib0/list.js": "./node_modules/lib0/list.js",
|
||||
"lib0/dist/list.cjs": "./node_modules/lib0/dist/list.cjs",
|
||||
"lib0/list": "./node_modules/lib0/list.js",
|
||||
"lib0/logging.js": "./node_modules/lib0/logging.js",
|
||||
"lib0/dist/logging.cjs": "./node_modules/lib0/dist/logging.node.cjs",
|
||||
"lib0/logging": "./node_modules/lib0/logging.js",
|
||||
"lib0/map.js": "./node_modules/lib0/map.js",
|
||||
"lib0/dist/map.cjs": "./node_modules/lib0/dist/map.cjs",
|
||||
"lib0/map": "./node_modules/lib0/map.js",
|
||||
"lib0/math.js": "./node_modules/lib0/math.js",
|
||||
"lib0/dist/math.cjs": "./node_modules/lib0/dist/math.cjs",
|
||||
"lib0/math": "./node_modules/lib0/math.js",
|
||||
"lib0/metric.js": "./node_modules/lib0/metric.js",
|
||||
"lib0/dist/metric.cjs": "./node_modules/lib0/dist/metric.cjs",
|
||||
"lib0/metric": "./node_modules/lib0/metric.js",
|
||||
"lib0/mutex.js": "./node_modules/lib0/mutex.js",
|
||||
"lib0/dist/mutex.cjs": "./node_modules/lib0/dist/mutex.cjs",
|
||||
"lib0/mutex": "./node_modules/lib0/mutex.js",
|
||||
"lib0/number.js": "./node_modules/lib0/number.js",
|
||||
"lib0/dist/number.cjs": "./node_modules/lib0/dist/number.cjs",
|
||||
"lib0/number": "./node_modules/lib0/number.js",
|
||||
"lib0/object.js": "./node_modules/lib0/object.js",
|
||||
"lib0/dist/object.cjs": "./node_modules/lib0/dist/object.cjs",
|
||||
"lib0/object": "./node_modules/lib0/object.js",
|
||||
"lib0/observable.js": "./node_modules/lib0/observable.js",
|
||||
"lib0/dist/observable.cjs": "./node_modules/lib0/dist/observable.cjs",
|
||||
"lib0/observable": "./node_modules/lib0/observable.js",
|
||||
"lib0/pair.js": "./node_modules/lib0/pair.js",
|
||||
"lib0/dist/pair.cjs": "./node_modules/lib0/dist/pair.cjs",
|
||||
"lib0/pair": "./node_modules/lib0/pair.js",
|
||||
"lib0/prng.js": "./node_modules/lib0/prng.js",
|
||||
"lib0/dist/prng.cjs": "./node_modules/lib0/dist/prng.cjs",
|
||||
"lib0/prng": "./node_modules/lib0/prng.js",
|
||||
"lib0/promise.js": "./node_modules/lib0/promise.js",
|
||||
"lib0/dist/promise.cjs": "./node_modules/lib0/dist/promise.cjs",
|
||||
"lib0/promise": "./node_modules/lib0/promise.js",
|
||||
"lib0/queue.js": "./node_modules/lib0/queue.js",
|
||||
"lib0/dist/queue.cjs": "./node_modules/lib0/dist/queue.cjs",
|
||||
"lib0/queue": "./node_modules/lib0/queue.js",
|
||||
"lib0/random.js": "./node_modules/lib0/random.js",
|
||||
"lib0/dist/random.cjs": "./node_modules/lib0/dist/random.cjs",
|
||||
"lib0/random": "./node_modules/lib0/random.js",
|
||||
"lib0/set.js": "./node_modules/lib0/set.js",
|
||||
"lib0/dist/set.cjs": "./node_modules/lib0/dist/set.cjs",
|
||||
"lib0/set": "./node_modules/lib0/set.js",
|
||||
"lib0/sort.js": "./node_modules/lib0/sort.js",
|
||||
"lib0/dist/sort.cjs": "./node_modules/lib0/dist/sort.cjs",
|
||||
"lib0/sort": "./node_modules/lib0/sort.js",
|
||||
"lib0/statistics.js": "./node_modules/lib0/statistics.js",
|
||||
"lib0/dist/statistics.cjs": "./node_modules/lib0/dist/statistics.cjs",
|
||||
"lib0/statistics": "./node_modules/lib0/statistics.js",
|
||||
"lib0/storage.js": "./node_modules/lib0/storage.js",
|
||||
"lib0/dist/storage.cjs": "./node_modules/lib0/dist/storage.cjs",
|
||||
"lib0/storage": "./node_modules/lib0/storage.js",
|
||||
"lib0/string.js": "./node_modules/lib0/string.js",
|
||||
"lib0/dist/string.cjs": "./node_modules/lib0/dist/string.cjs",
|
||||
"lib0/string": "./node_modules/lib0/string.js",
|
||||
"lib0/symbol.js": "./node_modules/lib0/symbol.js",
|
||||
"lib0/dist/symbol.cjs": "./node_modules/lib0/dist/symbol.cjs",
|
||||
"lib0/symbol": "./node_modules/lib0/symbol.js",
|
||||
"lib0/testing.js": "./node_modules/lib0/testing.js",
|
||||
"lib0/dist/testing.cjs": "./node_modules/lib0/dist/testing.cjs",
|
||||
"lib0/testing": "./node_modules/lib0/testing.js",
|
||||
"lib0/time.js": "./node_modules/lib0/time.js",
|
||||
"lib0/dist/time.cjs": "./node_modules/lib0/dist/time.cjs",
|
||||
"lib0/time": "./node_modules/lib0/time.js",
|
||||
"lib0/tree.js": "./node_modules/lib0/tree.js",
|
||||
"lib0/dist/tree.cjs": "./node_modules/lib0/dist/tree.cjs",
|
||||
"lib0/tree": "./node_modules/lib0/tree.js",
|
||||
"lib0/url.js": "./node_modules/lib0/url.js",
|
||||
"lib0/dist/url.cjs": "./node_modules/lib0/dist/url.cjs",
|
||||
"lib0/url": "./node_modules/lib0/url.js",
|
||||
"lib0/websocket.js": "./node_modules/lib0/websocket.js",
|
||||
"lib0/dist/websocket.cjs": "./node_modules/lib0/dist/websocket.cjs",
|
||||
"lib0/websocket": "./node_modules/lib0/websocket.js",
|
||||
"lib0/webcrypto": "./node_modules/lib0/webcrypto.js",
|
||||
"lib0/performance.js": "./node_modules/lib0/performance.js",
|
||||
"lib0/dist/performance.cjs": "./node_modules/lib0/dist/performance.node.cjs",
|
||||
"lib0/performance": "./node_modules/lib0/performance.js"
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<script type="module" src="./dist/tests.js"></script>
|
||||
<script type="module" src="./tests/index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Testing if encoding/decoding compatibility and integration compatiblity is given.
|
||||
* Testing if encoding/decoding compatibility and integration compatibility is given.
|
||||
* We expect that the document always looks the same, even if we upgrade the integration algorithm, or add additional encoding approaches.
|
||||
*
|
||||
* The v1 documents were generated with Yjs v13.2.0 based on the randomisized tests.
|
||||
|
||||
11
tests/delta.tests.js
Normal file
11
tests/delta.tests.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import * as t from 'lib0/testing'
|
||||
import * as delta from '../src/utils/Delta.js'
|
||||
|
||||
/**
|
||||
* @param {t.TestCase} _tc
|
||||
*/
|
||||
export const testDelta = _tc => {
|
||||
const d = delta.create().insert('hello').insert(' ').useAttributes({ bold: true }).insert('world').useAttribution({ creator: 'tester' }).insert('!').done()
|
||||
t.compare(d.toJSON().ops, [{ insert: 'hello' }, { insert: ' world', attributes: { bold: true } }, { insert: '!', attributes: { bold: true }, attribution: { creator: 'tester' } }])
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import * as doc from './doc.tests.js'
|
||||
import * as snapshot from './snapshot.tests.js'
|
||||
import * as updates from './updates.tests.js'
|
||||
import * as relativePositions from './relativePositions.tests.js'
|
||||
// import * as delta from './delta.tests.js'
|
||||
|
||||
import { runTests } from 'lib0/testing'
|
||||
import { isBrowser, isNode } from 'lib0/environment'
|
||||
@@ -19,11 +20,19 @@ import * as log from 'lib0/logging'
|
||||
if (isBrowser) {
|
||||
log.createVConsole(document.body)
|
||||
}
|
||||
runTests({
|
||||
doc, map, array, text, xml, encoding, undoredo, compatibility, snapshot, updates, relativePositions
|
||||
}).then(success => {
|
||||
|
||||
/**
|
||||
* @type {any}
|
||||
*/
|
||||
const tests = {
|
||||
doc, map, array, text, xml, encoding, undoredo, compatibility, snapshot, updates, relativePositions, delta
|
||||
}
|
||||
|
||||
const run = async () => {
|
||||
const success = await runTests(tests)
|
||||
/* istanbul ignore next */
|
||||
if (isNode) {
|
||||
process.exit(success ? 0 : 1)
|
||||
}
|
||||
})
|
||||
}
|
||||
run()
|
||||
|
||||
@@ -85,6 +85,26 @@ export const testRelativePositionCase6 = tc => {
|
||||
checkRelativePositions(ytext)
|
||||
}
|
||||
|
||||
/**
|
||||
* Testing https://github.com/yjs/yjs/issues/657
|
||||
*
|
||||
* @param {t.TestCase} tc
|
||||
*/
|
||||
export const testRelativePositionCase7 = tc => {
|
||||
const docA = new Y.Doc()
|
||||
const textA = docA.getText('text')
|
||||
textA.insert(0, 'abcde')
|
||||
// Create a relative position at index 2 in 'textA'
|
||||
const relativePosition = Y.createRelativePositionFromTypeIndex(textA, 2)
|
||||
// Verify that the absolutes positions on 'docA' are the same
|
||||
const absolutePositionWithFollow =
|
||||
Y.createAbsolutePositionFromRelativePosition(relativePosition, docA, true)
|
||||
const absolutePositionWithoutFollow =
|
||||
Y.createAbsolutePositionFromRelativePosition(relativePosition, docA, false)
|
||||
t.assert(absolutePositionWithFollow?.index === 2)
|
||||
t.assert(absolutePositionWithoutFollow?.index === 2)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {t.TestCase} tc
|
||||
*/
|
||||
|
||||
@@ -58,7 +58,7 @@ export const testEmptyRestoreSnapshot = _tc => {
|
||||
t.compare(docRestored.getArray().toArray(), [])
|
||||
t.compare(doc.getArray().toArray(), ['world'])
|
||||
|
||||
// now this snapshot reflects the latest state. It shoult still work.
|
||||
// now this snapshot reflects the latest state. It should still work.
|
||||
const snap2 = Y.snapshot(doc)
|
||||
const docRestored2 = Y.createDocFromSnapshot(doc, snap2)
|
||||
t.compare(docRestored2.getArray().toArray(), ['world'])
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { init } from './testHelper.js' // eslint-disable-line
|
||||
|
||||
import * as Y from '../src/index.js'
|
||||
import { init } from './testHelper.js' // eslint-disable-line
|
||||
import * as t from 'lib0/testing'
|
||||
|
||||
export const testInconsistentFormat = () => {
|
||||
@@ -116,6 +115,72 @@ export const testEmptyTypeScope = _tc => {
|
||||
t.assert(yarray.length === 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {t.TestCase} _tc
|
||||
*/
|
||||
export const testRejectUpdateExample = _tc => {
|
||||
const tmpydoc1 = new Y.Doc()
|
||||
tmpydoc1.getArray('restricted').insert(0, [1])
|
||||
tmpydoc1.getArray('public').insert(0, [1])
|
||||
const update1 = Y.encodeStateAsUpdate(tmpydoc1)
|
||||
const tmpydoc2 = new Y.Doc()
|
||||
tmpydoc2.getArray('public').insert(0, [2])
|
||||
const update2 = Y.encodeStateAsUpdate(tmpydoc2)
|
||||
|
||||
const ydoc = new Y.Doc()
|
||||
const restrictedType = ydoc.getArray('restricted')
|
||||
|
||||
/**
|
||||
* Assume this function handles incoming updates via a communication channel like websockets.
|
||||
* Changes to the `ydoc.getMap('restricted')` type should be rejected.
|
||||
*
|
||||
* - set up undo manager on the restricted types
|
||||
* - cache pending* updates from the Ydoc to avoid certain attacks
|
||||
* - apply received update and check whether the restricted type (or any of its children) has been changed.
|
||||
* - catch errors that might try to circumvent the restrictions
|
||||
* - undo changes on restricted types
|
||||
* - reapply pending* updates
|
||||
*
|
||||
* @param {Uint8Array} update
|
||||
*/
|
||||
const updateHandler = (update) => {
|
||||
// don't handle changes of the local undo manager, which is used to undo invalid changes
|
||||
const um = new Y.UndoManager(restrictedType, { trackedOrigins: new Set(['remote change']) })
|
||||
const beforePendingDs = ydoc.store.pendingDs
|
||||
const beforePendingStructs = ydoc.store.pendingStructs?.update
|
||||
try {
|
||||
Y.applyUpdate(ydoc, update, 'remote change')
|
||||
} finally {
|
||||
while (um.undoStack.length) {
|
||||
um.undo()
|
||||
}
|
||||
um.destroy()
|
||||
ydoc.store.pendingDs = beforePendingDs
|
||||
ydoc.store.pendingStructs = null
|
||||
if (beforePendingStructs) {
|
||||
Y.applyUpdateV2(ydoc, beforePendingStructs)
|
||||
}
|
||||
}
|
||||
}
|
||||
updateHandler(update1)
|
||||
updateHandler(update2)
|
||||
t.assert(restrictedType.length === 0)
|
||||
t.assert(ydoc.getArray('public').length === 2)
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case to fix #241
|
||||
* @param {t.TestCase} _tc
|
||||
*/
|
||||
export const testGlobalScope = _tc => {
|
||||
const ydoc = new Y.Doc()
|
||||
const um = new Y.UndoManager(ydoc)
|
||||
const yarray = ydoc.getArray()
|
||||
yarray.insert(0, [1])
|
||||
um.undo()
|
||||
t.assert(yarray.length === 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Test case to fix #241
|
||||
* @param {t.TestCase} _tc
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as t from 'lib0/testing'
|
||||
import { init, compare } from './testHelper.js' // eslint-disable-line
|
||||
import * as Y from '../src/index.js'
|
||||
import { init, compare } from './testHelper.js' // eslint-disable-line
|
||||
import { readClientsStructRefs, readDeleteSet, UpdateDecoderV2, UpdateEncoderV2, writeDeleteSet } from '../src/internals.js'
|
||||
import * as encoding from 'lib0/encoding'
|
||||
import * as decoding from 'lib0/decoding'
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { init, compare, applyRandomTests, Doc } from './testHelper.js' // eslint-disable-line
|
||||
|
||||
import * as Y from '../src/index.js'
|
||||
import * as t from 'lib0/testing'
|
||||
import * as prng from 'lib0/prng'
|
||||
import * as math from 'lib0/math'
|
||||
import * as env from 'lib0/environment'
|
||||
|
||||
const isDevMode = env.getVariable('node_env') === 'development'
|
||||
|
||||
/**
|
||||
* @param {t.TestCase} tc
|
||||
@@ -17,6 +19,28 @@ export const testBasicUpdate = tc => {
|
||||
t.compare(doc2.getArray('array').toArray(), ['hi'])
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {t.TestCase} tc
|
||||
*/
|
||||
export const testFailsObjectManipulationInDevMode = tc => {
|
||||
if (isDevMode) {
|
||||
t.info('running in dev mode')
|
||||
const doc = new Y.Doc()
|
||||
const a = [1, 2, 3]
|
||||
const b = { o: 1 }
|
||||
doc.getArray('test').insert(0, [a])
|
||||
doc.getMap('map').set('k', b)
|
||||
t.fails(() => {
|
||||
a[0] = 42
|
||||
})
|
||||
t.fails(() => {
|
||||
b.o = 42
|
||||
})
|
||||
} else {
|
||||
t.info('not in dev mode')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {t.TestCase} tc
|
||||
*/
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import * as Y from '../src/index.js'
|
||||
import { init, compare, applyRandomTests, Doc } from './testHelper.js' // eslint-disable-line
|
||||
|
||||
import {
|
||||
compareIDs
|
||||
} from '../src/internals.js'
|
||||
|
||||
import * as Y from '../src/index.js'
|
||||
import * as t from 'lib0/testing'
|
||||
import * as prng from 'lib0/prng'
|
||||
|
||||
@@ -369,11 +367,11 @@ export const testObserversUsingObservedeep = tc => {
|
||||
/**
|
||||
* @type {Array<Array<string|number>>}
|
||||
*/
|
||||
const pathes = []
|
||||
const paths = []
|
||||
let calls = 0
|
||||
map0.observeDeep(events => {
|
||||
events.forEach(event => {
|
||||
pathes.push(event.path)
|
||||
paths.push(event.path)
|
||||
})
|
||||
calls++
|
||||
})
|
||||
@@ -381,7 +379,7 @@ export const testObserversUsingObservedeep = tc => {
|
||||
map0.get('map').set('array', new Y.Array())
|
||||
map0.get('map').get('array').insert(0, ['content'])
|
||||
t.assert(calls === 3)
|
||||
t.compare(pathes, [[], ['map'], ['map', 'array']])
|
||||
t.compare(paths, [[], ['map'], ['map', 'array']])
|
||||
compare(users)
|
||||
}
|
||||
|
||||
@@ -393,14 +391,14 @@ export const testPathsOfSiblingEvents = tc => {
|
||||
/**
|
||||
* @type {Array<Array<string|number>>}
|
||||
*/
|
||||
const pathes = []
|
||||
const paths = []
|
||||
let calls = 0
|
||||
const doc = users[0]
|
||||
map0.set('map', new Y.Map())
|
||||
map0.get('map').set('text1', new Y.Text('initial'))
|
||||
map0.observeDeep(events => {
|
||||
events.forEach(event => {
|
||||
pathes.push(event.path)
|
||||
paths.push(event.path)
|
||||
})
|
||||
calls++
|
||||
})
|
||||
@@ -409,7 +407,7 @@ export const testPathsOfSiblingEvents = tc => {
|
||||
map0.get('map').set('text2', new Y.Text('new'))
|
||||
})
|
||||
t.assert(calls === 1)
|
||||
t.compare(pathes, [['map'], ['map', 'text1']])
|
||||
t.compare(paths, [['map'], ['map', 'text1']])
|
||||
compare(users)
|
||||
}
|
||||
|
||||
|
||||
@@ -376,7 +376,7 @@ export const testDeltaBug = _tc => {
|
||||
},
|
||||
{
|
||||
insert: '\n',
|
||||
// This attibutes has only list and no table-cell-line
|
||||
// This attributes has only list and no table-cell-line
|
||||
attributes: {
|
||||
list: {
|
||||
rowspan: '1',
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { init, compare } from './testHelper.js'
|
||||
import * as Y from '../src/index.js'
|
||||
|
||||
import { init, compare } from './testHelper.js'
|
||||
import * as t from 'lib0/testing'
|
||||
|
||||
export const testCustomTypings = () => {
|
||||
|
||||
@@ -14,7 +14,9 @@
|
||||
"noImplicitAny": true,
|
||||
"moduleResolution": "nodenext",
|
||||
"paths": {
|
||||
"yjs": ["./src/index.js"]
|
||||
"yjs": ["./src/index.js"],
|
||||
"yjs/internals": ["./src/internals.js"],
|
||||
"yjs/testHelper": ["./tests/testHelper.js"]
|
||||
}
|
||||
},
|
||||
"include": ["./src/**/*.js", "./tests/**/*.js"]
|
||||
|
||||
Reference in New Issue
Block a user