Compare commits

...

10 Commits

Author SHA1 Message Date
Kevin Jahns
a304024a76 13.6.20 2024-10-14 01:41:22 +02:00
Kevin Jahns
487465d701 lint 2024-10-14 01:39:15 +02:00
Kevin Jahns
345fd31b10 add yjs-inspector 2024-10-07 09:45:27 +02:00
Kevin Jahns
4ff65b5dc3 add devtools 2024-10-07 09:43:13 +02:00
Kevin Jahns
8152cf81cb [#667] sanity checks for Yjs caveats. In dev_mode, objects inserted into Yjs can't be manipulated. 2024-10-04 21:23:59 +02:00
Kevin Jahns
3bf44b9850 #667 - add sanity messages when data is read before type is added to a document. 2024-10-04 21:07:19 +02:00
Kevin Jahns
8cd1a482bb Y.Array.length should be 0 before it is integrated - #666 2024-09-26 19:30:34 +02:00
Kevin Jahns
9e9f294009 Merge pull request #665 from batchor/main
add ScienHub as a user.
2024-09-19 23:02:50 +02:00
Batchor
4fb7789cdd add ScienHub as a user. 2024-09-05 15:23:58 -07:00
Batchor
c1ef9a12b9 add ScienHub as a user. 2024-09-05 15:22:40 -07:00
10 changed files with 86 additions and 17 deletions

View File

@@ -120,12 +120,14 @@ Showcase](https://yjs-diagram.synergy.codes/).
* [Kanbert](https://kanbert.com) - Project management software * [Kanbert](https://kanbert.com) - Project management software
* [Eclipse Theia](https://github.com/eclipse-theia/theia) - A cloud & desktop * [Eclipse Theia](https://github.com/eclipse-theia/theia) - A cloud & desktop
IDE that runs in the browser. IDE that runs in the browser.
* [ScienHub](https://scienhub.com) - Collaborative LaTeX editor in the browser.
## Table of Contents ## Table of Contents
* [Overview](#overview) * [Overview](#overview)
* [Bindings](#bindings) * [Bindings](#bindings)
* [Providers](#providers) * [Providers](#providers)
* [Tooling](#tooling)
* [Ports](#ports) * [Ports](#ports)
* [Getting Started](#getting-started) * [Getting Started](#getting-started)
* [API](#api) * [API](#api)
@@ -293,7 +295,13 @@ A database and connection provider for Yjs based on Firestore.
</dd> </dd>
</dl> </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. There are several Yjs-compatible ports to other programming languages.

13
package-lock.json generated
View File

@@ -1,15 +1,15 @@
{ {
"name": "yjs", "name": "yjs",
"version": "13.6.19", "version": "13.6.20",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "yjs", "name": "yjs",
"version": "13.6.19", "version": "13.6.20",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"lib0": "^0.2.86" "lib0": "^0.2.98"
}, },
"devDependencies": { "devDependencies": {
"@rollup/plugin-commonjs": "^24.0.1", "@rollup/plugin-commonjs": "^24.0.1",
@@ -2785,13 +2785,14 @@
} }
}, },
"node_modules/lib0": { "node_modules/lib0": {
"version": "0.2.88", "version": "0.2.98",
"resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.88.tgz", "resolved": "https://registry.npmjs.org/lib0/-/lib0-0.2.98.tgz",
"integrity": "sha512-KyroiEvCeZcZEMx5Ys+b4u4eEBbA1ch7XUaBhYpwa/nPMrzTjUhI4RfcytmQfYoTBPcdyx+FX6WFNIoNuJzJfQ==", "integrity": "sha512-XteTiNO0qEXqqweWx+b21p/fBnNHUA1NwAtJNJek1oPrewEZs2uiT4gWivHKr9GqCjDPAhchz0UQO8NwU3bBNA==",
"dependencies": { "dependencies": {
"isomorphic.js": "^0.2.4" "isomorphic.js": "^0.2.4"
}, },
"bin": { "bin": {
"0ecdsa-generate-keypair": "bin/0ecdsa-generate-keypair.js",
"0gentesthtml": "bin/gentesthtml.js", "0gentesthtml": "bin/gentesthtml.js",
"0serve": "bin/0serve.js" "0serve": "bin/0serve.js"
}, },

View File

@@ -1,6 +1,6 @@
{ {
"name": "yjs", "name": "yjs",
"version": "13.6.19", "version": "13.6.20",
"description": "Shared Editing Library", "description": "Shared Editing Library",
"main": "./dist/yjs.cjs", "main": "./dist/yjs.cjs",
"module": "./dist/yjs.mjs", "module": "./dist/yjs.mjs",
@@ -13,7 +13,7 @@
}, },
"scripts": { "scripts": {
"clean": "rm -rf dist docs", "clean": "rm -rf dist docs",
"test": "npm run dist && node ./dist/tests.cjs --repetition-time 50", "test": "npm run dist && NODE_ENV=development node ./dist/tests.cjs --repetition-time 50",
"test-extensive": "npm run lint && npm run dist && node ./dist/tests.cjs --production --repetition-time 10000", "test-extensive": "npm run lint && npm run dist && node ./dist/tests.cjs --production --repetition-time 10000",
"dist": "npm run clean && rollup -c && tsc", "dist": "npm run clean && rollup -c && tsc",
"watch": "rollup -wc", "watch": "rollup -wc",
@@ -76,7 +76,7 @@
}, },
"homepage": "https://docs.yjs.dev", "homepage": "https://docs.yjs.dev",
"dependencies": { "dependencies": {
"lib0": "^0.2.86" "lib0": "^0.2.98"
}, },
"devDependencies": { "devDependencies": {
"@rollup/plugin-commonjs": "^24.0.1", "@rollup/plugin-commonjs": "^24.0.1",

View File

@@ -2,6 +2,11 @@ import {
UpdateEncoderV1, UpdateEncoderV2, UpdateDecoderV1, UpdateDecoderV2, Transaction, Item, StructStore // eslint-disable-line UpdateEncoderV1, UpdateEncoderV2, UpdateDecoderV1, UpdateDecoderV2, Transaction, Item, StructStore // eslint-disable-line
} from '../internals.js' } 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 { export class ContentAny {
/** /**
* @param {Array<any>} arr * @param {Array<any>} arr
@@ -11,6 +16,7 @@ export class ContentAny {
* @type {Array<any>} * @type {Array<any>}
*/ */
this.arr = arr this.arr = arr
isDevMode && object.deepFreeze(arr)
} }
/** /**

View File

@@ -17,6 +17,12 @@ import * as map from 'lib0/map'
import * as iterator from 'lib0/iterator' import * as iterator from 'lib0/iterator'
import * as error from 'lib0/error' import * as error from 'lib0/error'
import * as math from 'lib0/math' 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 const maxSearchMarker = 80
@@ -215,6 +221,7 @@ export const updateMarkerChanges = (searchMarker, index, len) => {
* @return {Array<Item>} * @return {Array<Item>}
*/ */
export const getTypeChildren = t => { export const getTypeChildren = t => {
t.doc ?? warnPrematureAccess()
let s = t._start let s = t._start
const arr = [] const arr = []
while (s) { while (s) {
@@ -408,6 +415,7 @@ export class AbstractType {
* @function * @function
*/ */
export const typeListSlice = (type, start, end) => { export const typeListSlice = (type, start, end) => {
type.doc ?? warnPrematureAccess()
if (start < 0) { if (start < 0) {
start = type._length + start start = type._length + start
} }
@@ -443,6 +451,7 @@ export const typeListSlice = (type, start, end) => {
* @function * @function
*/ */
export const typeListToArray = type => { export const typeListToArray = type => {
type.doc ?? warnPrematureAccess()
const cs = [] const cs = []
let n = type._start let n = type._start
while (n !== null) { while (n !== null) {
@@ -492,6 +501,7 @@ export const typeListToArraySnapshot = (type, snapshot) => {
export const typeListForEach = (type, f) => { export const typeListForEach = (type, f) => {
let index = 0 let index = 0
let n = type._start let n = type._start
type.doc ?? warnPrematureAccess()
while (n !== null) { while (n !== null) {
if (n.countable && !n.deleted) { if (n.countable && !n.deleted) {
const c = n.content.getContent() const c = n.content.getContent()
@@ -606,6 +616,7 @@ export const typeListForEachSnapshot = (type, f, snapshot) => {
* @function * @function
*/ */
export const typeListGet = (type, index) => { export const typeListGet = (type, index) => {
type.doc ?? warnPrematureAccess()
const marker = findMarker(type, index) const marker = findMarker(type, index)
let n = type._start let n = type._start
if (marker !== null) { if (marker !== null) {
@@ -874,6 +885,7 @@ export const typeMapSet = (transaction, parent, key, value) => {
* @function * @function
*/ */
export const typeMapGet = (parent, key) => { export const typeMapGet = (parent, key) => {
parent.doc ?? warnPrematureAccess()
const val = parent._map.get(key) const val = parent._map.get(key)
return val !== undefined && !val.deleted ? val.content.getContent()[val.length - 1] : undefined return val !== undefined && !val.deleted ? val.content.getContent()[val.length - 1] : undefined
} }
@@ -890,6 +902,7 @@ export const typeMapGetAll = (parent) => {
* @type {Object<string,any>} * @type {Object<string,any>}
*/ */
const res = {} const res = {}
parent.doc ?? warnPrematureAccess()
parent._map.forEach((value, key) => { parent._map.forEach((value, key) => {
if (!value.deleted) { if (!value.deleted) {
res[key] = value.content.getContent()[value.length - 1] res[key] = value.content.getContent()[value.length - 1]
@@ -907,6 +920,7 @@ export const typeMapGetAll = (parent) => {
* @function * @function
*/ */
export const typeMapHas = (parent, key) => { export const typeMapHas = (parent, key) => {
parent.doc ?? warnPrematureAccess()
const val = parent._map.get(key) const val = parent._map.get(key)
return val !== undefined && !val.deleted 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>>} * @return {IterableIterator<Array<any>>}
* *
* @private * @private
* @function * @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)
}

View File

@@ -16,6 +16,7 @@ import {
YArrayRefID, YArrayRefID,
callTypeObservers, callTypeObservers,
transact, transact,
warnPrematureAccess,
ArraySearchMarker, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item // eslint-disable-line ArraySearchMarker, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item // eslint-disable-line
} from '../internals.js' } from '../internals.js'
import { typeListSlice } from './AbstractType.js' import { typeListSlice } from './AbstractType.js'
@@ -104,7 +105,8 @@ export class YArray extends AbstractType {
} }
get length () { get length () {
return this._prelimContent === null ? this._length : this._prelimContent.length this.doc ?? warnPrematureAccess()
return this._length
} }
/** /**

View File

@@ -13,6 +13,7 @@ import {
YMapRefID, YMapRefID,
callTypeObservers, callTypeObservers,
transact, transact,
warnPrematureAccess,
UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item // eslint-disable-line UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item // eslint-disable-line
} from '../internals.js' } from '../internals.js'
@@ -121,6 +122,7 @@ export class YMap extends AbstractType {
* @return {Object<string,any>} * @return {Object<string,any>}
*/ */
toJSON () { toJSON () {
this.doc ?? warnPrematureAccess()
/** /**
* @type {Object<string,MapType>} * @type {Object<string,MapType>}
*/ */
@@ -140,7 +142,7 @@ export class YMap extends AbstractType {
* @return {number} * @return {number}
*/ */
get size () { get size () {
return [...createMapIterator(this._map)].length return [...createMapIterator(this)].length
} }
/** /**
@@ -149,7 +151,7 @@ export class YMap extends AbstractType {
* @return {IterableIterator<string>} * @return {IterableIterator<string>}
*/ */
keys () { 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>} * @return {IterableIterator<MapType>}
*/ */
values () { 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]>} * @return {IterableIterator<[string, MapType]>}
*/ */
entries () { 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. * @param {function(MapType,string,YMap<MapType>):void} f A function to execute on every element of this YArray.
*/ */
forEach (f) { forEach (f) {
this.doc ?? warnPrematureAccess()
this._map.forEach((item, key) => { this._map.forEach((item, key) => {
if (!item.deleted) { if (!item.deleted) {
f(item.content.getContent()[item.length - 1], key, this) f(item.content.getContent()[item.length - 1], key, this)

View File

@@ -26,6 +26,7 @@ import {
typeMapGetAll, typeMapGetAll,
updateMarkerChanges, updateMarkerChanges,
ContentType, ContentType,
warnPrematureAccess,
ArraySearchMarker, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, ID, Doc, Item, Snapshot, Transaction // eslint-disable-line ArraySearchMarker, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, ID, Doc, Item, Snapshot, Transaction // eslint-disable-line
} from '../internals.js' } from '../internals.js'
@@ -875,6 +876,7 @@ export class YText extends AbstractType {
* @type {number} * @type {number}
*/ */
get length () { get length () {
this.doc ?? warnPrematureAccess()
return this._length return this._length
} }
@@ -931,6 +933,7 @@ export class YText extends AbstractType {
* @public * @public
*/ */
toString () { toString () {
this.doc ?? warnPrematureAccess()
let str = '' let str = ''
/** /**
* @type {Item|null} * @type {Item|null}
@@ -1004,6 +1007,7 @@ export class YText extends AbstractType {
* @public * @public
*/ */
toDelta (snapshot, prevSnapshot, computeYChange) { toDelta (snapshot, prevSnapshot, computeYChange) {
this.doc ?? warnPrematureAccess()
/** /**
* @type{Array<any>} * @type{Array<any>}
*/ */

View File

@@ -17,6 +17,7 @@ import {
transact, transact,
typeListGet, typeListGet,
typeListSlice, typeListSlice,
warnPrematureAccess,
UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, ContentType, Transaction, Item, YXmlText, YXmlHook // eslint-disable-line UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, ContentType, Transaction, Item, YXmlText, YXmlHook // eslint-disable-line
} from '../internals.js' } from '../internals.js'
@@ -66,6 +67,7 @@ export class YXmlTreeWalker {
*/ */
this._currentNode = /** @type {Item} */ (root._start) this._currentNode = /** @type {Item} */ (root._start)
this._firstCall = true this._firstCall = true
root.doc ?? warnPrematureAccess()
} }
[Symbol.iterator] () { [Symbol.iterator] () {
@@ -177,6 +179,7 @@ export class YXmlFragment extends AbstractType {
} }
get length () { get length () {
this.doc ?? warnPrematureAccess()
return this._prelimContent === null ? this._length : this._prelimContent.length return this._prelimContent === null ? this._length : this._prelimContent.length
} }

View File

@@ -4,6 +4,9 @@ import * as Y from '../src/index.js'
import * as t from 'lib0/testing' import * as t from 'lib0/testing'
import * as prng from 'lib0/prng' import * as prng from 'lib0/prng'
import * as math from 'lib0/math' import * as math from 'lib0/math'
import * as env from 'lib0/environment'
const isDevMode = env.getVariable('node_env') === 'development'
/** /**
* @param {t.TestCase} tc * @param {t.TestCase} tc
@@ -17,6 +20,28 @@ export const testBasicUpdate = tc => {
t.compare(doc2.getArray('array').toArray(), ['hi']) 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 * @param {t.TestCase} tc
*/ */