From 75f4a0a5f0c664855f8fd4c45f6e522a24f0f354 Mon Sep 17 00:00:00 2001 From: Kevin Jahns Date: Fri, 1 Mar 2019 23:26:40 +0100 Subject: [PATCH] restructuring the project --- .esdoc.json | 11 - .gitignore | 8 +- .jsdoc.json | 2 +- README.v13.md | 4 +- bindings/codemirror.js | 180 - bindings/dom.js | 1 - bindings/dom/DomBinding.js | 248 - bindings/dom/domObserver.js | 150 - bindings/dom/domToType.js | 74 - bindings/dom/filter.js | 71 - bindings/dom/selection.js | 49 - bindings/dom/typeObserver.js | 110 - bindings/dom/util.js | 135 - bindings/prosemirror.js | 756 -- bindings/quill.js | 71 - bindings/textarea.js | 72 - examples/codemirror.html | 2 +- examples/codemirror.js | 6 +- examples/dom.html | 2 +- examples/dom.js | 6 +- examples/prosemirror-history.js | 12 +- examples/prosemirror-schema.js | 197 + examples/prosemirror.css | 330 + examples/prosemirror.html | 2 +- examples/prosemirror.js | 12 +- examples/quill.html | 2 +- examples/quill.js | 6 +- examples/textarea.html | 2 +- examples/textarea.js | 6 +- examples_all/ace/index.html | 33 - examples_all/ace/index.js | 17 - examples_all/chat/index.html | 19 - examples_all/chat/index.js | 65 - examples_all/codemirror/index.html | 24 - examples_all/codemirror/index.js | 16 - examples_all/drawing/index.html | 20 - examples_all/drawing/index.js | 74 - .../html-editor-drawing-hook/index.html | 36 - .../html-editor-drawing-hook/index.js | 134 - examples_all/html-editor/index.html | 11 - examples_all/html-editor/index.js | 77 - examples_all/indexeddb/index.html | 23 - examples_all/indexeddb/index.js | 19 - examples_all/jigsaw/index.html | 24 - examples_all/jigsaw/index.js | 67 - examples_all/monaco/index.html | 21 - examples_all/monaco/index.js | 22 - examples_all/notes/index.html | 17 - examples_all/notes/index.js | 132 - examples_all/notes/style.css | 100 - examples_all/quill-cursors/index.html | 21 - examples_all/quill-cursors/index.js | 78 - examples_all/serviceworker/index.html | 31 - examples_all/serviceworker/index.js | 49 - examples_all/serviceworker/yjs-sw-template.js | 22 - index.js | 57 - lib/NamedEventHandler.js | 113 - lib/Tree.js | 468 -- lib/binary.js | 40 - lib/broadcastchannel.js | 72 - lib/decoding.js | 205 - lib/diff.js | 50 - lib/encoding.js | 243 - lib/encoding.test.js | 49 - lib/globals.js | 65 - lib/idb.js | 166 - lib/idb.test.js | 34 - lib/logging.js | 26 - lib/math.js | 28 - lib/mutex.js | 31 - lib/number.js | 6 - lib/object.js | 14 - lib/prng/PRNG/Mt19937.js | 67 - lib/prng/PRNG/PRNG.tests.js | 51 - lib/prng/PRNG/README.md | 5 - lib/prng/PRNG/Xoroshiro128plus.js | 101 - lib/prng/PRNG/Xorshift32.js | 29 - lib/prng/prng.js | 134 - lib/prng/prng.test.js | 114 - lib/random.js | 3 - lib/string.js | 6 - lib/testing.js | 37 - lib/time.js | 3 - package-lock.json | 6442 ++++++++++++++--- package.json | 62 +- persistence/FilePersistence.js | 74 - persistence/IndexedDBPersistence.js | 553 -- persistence/decodePersisted.js | 53 - persistence/indexeddb.js | 0 persistence/leveldb.js | 125 - protocols/auth.js | 33 - protocols/awareness.js | 121 - protocols/history.js | 46 - protocols/sync.js | 264 - provider/websocket.js | 5 - provider/websocket/WebSocketProvider.js | 192 - provider/websocket/index.js | 2 - provider/websocket/server.js | 155 - provider/ydb/NamedEventHandler.js | 23 - provider/ydb/README.md | 1 - provider/ydb/TODO.md | 56 - provider/ydb/YdbClient.js | 218 - provider/ydb/YdbClient.test.js | 89 - provider/ydb/broadcastchannel.js | 311 - provider/ydb/idbactions.js | 394 - provider/ydb/idbactions.test.js | 23 - provider/ydb/index.js | 11 - provider/ydb/message.js | 124 - rollup.config.js | 160 +- src/index.js | 28 + {structs => src/structs}/Delete.js | 8 +- {structs => src/structs}/GC.js | 6 +- {structs => src/structs}/Item.js | 29 +- {structs => src/structs}/ItemBinary.js | 8 +- {structs => src/structs}/ItemEmbed.js | 6 +- {structs => src/structs}/ItemFormat.js | 6 +- {structs => src/structs}/ItemJSON.js | 6 +- {structs => src/structs}/ItemString.js | 6 +- {structs => src/structs}/Type.js | 0 {types => src/types}/YArray.js | 2 +- {types => src/types}/YMap.js | 2 +- {types => src/types}/YText.js | 4 +- {types => src/types}/YXmlElement.js | 110 +- {types => src/types}/YXmlEvent.js | 0 {types => src/types}/YXmlHook.js | 13 +- {types => src/types}/YXmlText.js | 6 +- {utils => src/utils}/BindMapping.js | 0 src/utils/DeleteStore.js | 89 + {utils => src/utils}/EventHandler.js | 0 {utils => src/utils}/ID.js | 6 +- {utils => src/utils}/OperationStore.js | 6 +- src/utils/StateStore.js | 53 + {utils => src/utils}/Transaction.js | 7 +- {utils => src/utils}/UndoManager.js | 0 {utils => src/utils}/Y.js | 51 +- {utils => src/utils}/YEvent.js | 2 - {utils => src/utils}/defragmentItemContent.js | 0 .../utils}/integrateRemoteStructs.js | 4 +- {utils => src/utils}/isParentOf.js | 0 {utils => src/utils}/relativePosition.js | 0 {utils => src/utils}/snapshot.js | 0 {utils => src/utils}/structEncoding.js | 0 {utils => src/utils}/structManipulation.js | 0 test.html | 9 + tests/DeleteStore.tests.js | 87 +- tests/diff.tests.js | 29 - tests/encode-decode.tests.js | 65 - tests/helper.js | 26 +- tests/index.html | 2 +- tests/index.js | 19 +- tests/prosemirror.test.js | 37 - tests/red-black-tree.js | 192 - tests/y-array.tests.js | 8 +- tests/y-map.tests.js | 4 +- tests/y-text.tests.js | 6 +- tests/y-xml.tests.js | 270 +- tsconfig.json | 1 + types/YXmlTreeWalker.js | 80 - utils/DeleteStore.js | 256 - utils/StateStore.js | 111 - utils/generateRandomUint32.js | 20 - utils/structReferences.js | 34 - utils/structStringify.js | 46 - 163 files changed, 6423 insertions(+), 10770 deletions(-) delete mode 100644 .esdoc.json delete mode 100644 bindings/codemirror.js delete mode 100644 bindings/dom.js delete mode 100644 bindings/dom/DomBinding.js delete mode 100644 bindings/dom/domObserver.js delete mode 100644 bindings/dom/domToType.js delete mode 100644 bindings/dom/filter.js delete mode 100644 bindings/dom/selection.js delete mode 100644 bindings/dom/typeObserver.js delete mode 100644 bindings/dom/util.js delete mode 100644 bindings/prosemirror.js delete mode 100644 bindings/quill.js delete mode 100644 bindings/textarea.js create mode 100644 examples/prosemirror-schema.js create mode 100644 examples/prosemirror.css delete mode 100644 examples_all/ace/index.html delete mode 100644 examples_all/ace/index.js delete mode 100644 examples_all/chat/index.html delete mode 100644 examples_all/chat/index.js delete mode 100644 examples_all/codemirror/index.html delete mode 100644 examples_all/codemirror/index.js delete mode 100644 examples_all/drawing/index.html delete mode 100644 examples_all/drawing/index.js delete mode 100644 examples_all/html-editor-drawing-hook/index.html delete mode 100644 examples_all/html-editor-drawing-hook/index.js delete mode 100644 examples_all/html-editor/index.html delete mode 100644 examples_all/html-editor/index.js delete mode 100644 examples_all/indexeddb/index.html delete mode 100644 examples_all/indexeddb/index.js delete mode 100644 examples_all/jigsaw/index.html delete mode 100644 examples_all/jigsaw/index.js delete mode 100644 examples_all/monaco/index.html delete mode 100644 examples_all/monaco/index.js delete mode 100644 examples_all/notes/index.html delete mode 100644 examples_all/notes/index.js delete mode 100644 examples_all/notes/style.css delete mode 100644 examples_all/quill-cursors/index.html delete mode 100644 examples_all/quill-cursors/index.js delete mode 100644 examples_all/serviceworker/index.html delete mode 100644 examples_all/serviceworker/index.js delete mode 100644 examples_all/serviceworker/yjs-sw-template.js delete mode 100644 index.js delete mode 100644 lib/NamedEventHandler.js delete mode 100644 lib/Tree.js delete mode 100644 lib/binary.js delete mode 100644 lib/broadcastchannel.js delete mode 100644 lib/decoding.js delete mode 100644 lib/diff.js delete mode 100644 lib/encoding.js delete mode 100644 lib/encoding.test.js delete mode 100644 lib/globals.js delete mode 100644 lib/idb.js delete mode 100644 lib/idb.test.js delete mode 100644 lib/logging.js delete mode 100644 lib/math.js delete mode 100644 lib/mutex.js delete mode 100644 lib/number.js delete mode 100644 lib/object.js delete mode 100644 lib/prng/PRNG/Mt19937.js delete mode 100644 lib/prng/PRNG/PRNG.tests.js delete mode 100644 lib/prng/PRNG/README.md delete mode 100644 lib/prng/PRNG/Xoroshiro128plus.js delete mode 100644 lib/prng/PRNG/Xorshift32.js delete mode 100644 lib/prng/prng.js delete mode 100644 lib/prng/prng.test.js delete mode 100644 lib/random.js delete mode 100644 lib/string.js delete mode 100644 lib/testing.js delete mode 100644 lib/time.js delete mode 100644 persistence/FilePersistence.js delete mode 100644 persistence/IndexedDBPersistence.js delete mode 100644 persistence/decodePersisted.js delete mode 100644 persistence/indexeddb.js delete mode 100644 persistence/leveldb.js delete mode 100644 protocols/auth.js delete mode 100644 protocols/awareness.js delete mode 100644 protocols/history.js delete mode 100644 protocols/sync.js delete mode 100644 provider/websocket.js delete mode 100644 provider/websocket/WebSocketProvider.js delete mode 100644 provider/websocket/index.js delete mode 100644 provider/websocket/server.js delete mode 100644 provider/ydb/NamedEventHandler.js delete mode 100644 provider/ydb/README.md delete mode 100644 provider/ydb/TODO.md delete mode 100644 provider/ydb/YdbClient.js delete mode 100644 provider/ydb/YdbClient.test.js delete mode 100644 provider/ydb/broadcastchannel.js delete mode 100644 provider/ydb/idbactions.js delete mode 100644 provider/ydb/idbactions.test.js delete mode 100644 provider/ydb/index.js delete mode 100644 provider/ydb/message.js create mode 100644 src/index.js rename {structs => src/structs}/Delete.js (93%) rename {structs => src/structs}/GC.js (94%) rename {structs => src/structs}/Item.js (97%) rename {structs => src/structs}/ItemBinary.js (84%) rename {structs => src/structs}/ItemEmbed.js (86%) rename {structs => src/structs}/ItemFormat.js (88%) rename {structs => src/structs}/ItemJSON.js (92%) rename {structs => src/structs}/ItemString.js (89%) rename {structs => src/structs}/Type.js (100%) rename {types => src/types}/YArray.js (99%) rename {types => src/types}/YMap.js (98%) rename {types => src/types}/YText.js (99%) rename {types => src/types}/YXmlElement.js (81%) rename {types => src/types}/YXmlEvent.js (100%) rename {types => src/types}/YXmlHook.js (89%) rename {types => src/types}/YXmlText.js (90%) rename {utils => src/utils}/BindMapping.js (100%) create mode 100644 src/utils/DeleteStore.js rename {utils => src/utils}/EventHandler.js (100%) rename {utils => src/utils}/ID.js (93%) rename {utils => src/utils}/OperationStore.js (94%) create mode 100644 src/utils/StateStore.js rename {utils => src/utils}/Transaction.js (87%) rename {utils => src/utils}/UndoManager.js (100%) rename {utils => src/utils}/Y.js (77%) rename {utils => src/utils}/YEvent.js (92%) rename {utils => src/utils}/defragmentItemContent.js (100%) rename {utils => src/utils}/integrateRemoteStructs.js (97%) rename {utils => src/utils}/isParentOf.js (100%) rename {utils => src/utils}/relativePosition.js (100%) rename {utils => src/utils}/snapshot.js (100%) rename {utils => src/utils}/structEncoding.js (100%) rename {utils => src/utils}/structManipulation.js (100%) create mode 100644 test.html delete mode 100644 tests/diff.tests.js delete mode 100644 tests/encode-decode.tests.js delete mode 100644 tests/prosemirror.test.js delete mode 100644 tests/red-black-tree.js delete mode 100644 types/YXmlTreeWalker.js delete mode 100644 utils/DeleteStore.js delete mode 100644 utils/StateStore.js delete mode 100644 utils/generateRandomUint32.js delete mode 100644 utils/structReferences.js delete mode 100644 utils/structStringify.js diff --git a/.esdoc.json b/.esdoc.json deleted file mode 100644 index eed42cb0..00000000 --- a/.esdoc.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "source": ".", - "destination": "./docs", - "excludes": ["build", "node_modules", "tests-lib", "test"], - "plugins": [{ - "name": "esdoc-standard-plugin", - "option": { - "accessor": {"access": ["public"], "autoPrivate": true} - } - }] -} diff --git a/.gitignore b/.gitignore index 786f7885..875a48b9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,4 @@ node_modules -bower_components -docs -/y.* -/examples_all/*/index.dist.* +dist .vscode -.yjsPersisted -build +./docs diff --git a/.jsdoc.json b/.jsdoc.json index 9324d17d..1cca0d93 100644 --- a/.jsdoc.json +++ b/.jsdoc.json @@ -5,7 +5,7 @@ "dictionaries": ["jsdoc"] }, "source": { - "include": ["./structs/Type.js", "./types", "./utils/UndoManager.js", "./utils/YEvent.js", "./utils/Y.js", "./provider", "./bindings"], + "include": ["./src"], "includePattern": ".js$" }, "plugins": [ diff --git a/README.v13.md b/README.v13.md index ea085128..8db4425e 100644 --- a/README.v13.md +++ b/README.v13.md @@ -350,9 +350,7 @@ Until [this](https://github.com/Microsoft/TypeScript/issues/7546) is fixed, the "checkJs": true, .. }, - "include": [ - "./node_modules/yjs/" - ] + "maxNodeModuleJsDepth": 5 } ``` diff --git a/bindings/codemirror.js b/bindings/codemirror.js deleted file mode 100644 index a27305f8..00000000 --- a/bindings/codemirror.js +++ /dev/null @@ -1,180 +0,0 @@ -/** - * @module bindings/textarea - */ - -import { createMutex } from '../lib/mutex.js' -import * as math from '../lib/math.js' -import * as ypos from '../utils/relativePosition.js' - -const typeObserver = (binding, event) => { - binding._mux(() => { - const cm = binding.target - cm.operation(() => { - const delta = event.delta - let index = 0 - for (let i = 0; i < event.delta.length; i++) { - const d = delta[i] - if (d.retain) { - index += d.retain - } else if (d.insert) { - const pos = cm.posFromIndex(index) - cm.replaceRange(d.insert, pos, pos, 'prosemirror-binding') - index += d.insert.length - } else if (d.delete) { - const start = cm.posFromIndex(index) - const end = cm.posFromIndex(index + d.delete) - cm.replaceRange('', start, end, 'prosemirror-binding') - } - } - }) - }) -} - -const targetObserver = (binding, change) => { - binding._mux(() => { - const start = binding.target.indexFromPos(change.from) - const delLen = change.removed.map(s => s.length).reduce(math.add) + change.removed.length - 1 - if (delLen > 0) { - binding.type.delete(start, delLen) - } - if (change.text.length > 0) { - binding.type.insert(start, change.text.join('\n')) - } - }) -} - -const createRemoteCaret = (username, color) => { - const caret = document.createElement('span') - caret.classList.add('remote-caret') - caret.setAttribute('style', `border-color: ${color}`) - const userDiv = document.createElement('div') - userDiv.setAttribute('style', `background-color: ${color}`) - userDiv.insertBefore(document.createTextNode(username), null) - caret.insertBefore(userDiv, null) - return caret -} - -const updateRemoteSelection = (y, cm, type, cursors, clientId) => { - // destroy current text mark - const m = cursors.get(clientId) - if (m !== undefined) { - m.caret.clear() - if (m.sel !== null) { - m.sel.clear() - } - cursors.delete(clientId) - } - // redraw caret and selection for clientId - const aw = y.awareness.get(clientId) - if (aw === undefined) { - return - } - const user = aw.user || {} - if (user.color == null) { - user.color = '#ffa500' - } - if (user.name == null) { - user.name = `User: ${clientId}` - } - const cursor = aw.cursor - if (cursor == null || cursor.anchor == null || cursor.head == null) { - return - } - const anchor = ypos.fromRelativePosition(y, cursor.anchor || null) - const head = ypos.fromRelativePosition(y, cursor.head || null) - if (anchor !== null && head !== null && anchor.type === type && head.type === type) { - const headpos = cm.posFromIndex(head.offset) - const anchorpos = cm.posFromIndex(anchor.offset) - let from, to - if (head.offset < anchor.offset) { - from = headpos - to = anchorpos - } else { - from = anchorpos - to = headpos - } - const caretEl = createRemoteCaret(user.name, user.color) - const caret = cm.setBookmark(headpos, { widget: caretEl, insertLeft: true }) - let sel = null - if (head.offset !== anchor.offset) { - sel = cm.markText(from, to, { css: `background-color: ${user.color}70`, inclusiveRight: true, inclusiveLeft: false }) - } - cursors.set(clientId, { caret, sel }) - } -} - -const prosemirrorCursorActivity = (y, cm, type) => { - if (!cm.hasFocus()) { - return - } - const aw = y.getLocalAwarenessInfo() - const anchor = ypos.getRelativePosition(type, cm.indexFromPos(cm.getCursor('anchor'))) - const head = ypos.getRelativePosition(type, cm.indexFromPos(cm.getCursor('head'))) - if (aw.cursor == null || !ypos.equal(aw.cursor.anchor, anchor) || !ypos.equal(aw.cursor.head, head)) { - y.setAwarenessField('cursor', { - anchor, head - }) - } -} - -/** - * A binding that binds a YText to a CodeMirror editor. - * - * @example - * const ytext = ydocument.define('codemirror', Y.Text) - * const editor = new CodeMirror(document.querySelector('#container'), { - * mode: 'javascript', - * lineNumbers: true - * }) - * const binding = new CodeMirrorBinding(editor) - * - */ -export class CodeMirrorBinding { - /** - * @param {YText} textType - * @param {CodeMirror} codeMirror - * @param {Object} [options={cursors: true}] - */ - constructor (textType, codeMirror, { cursors = true } = {}) { - const y = textType._y - this.type = textType - this.target = codeMirror - /** - * @private - */ - this._mux = createMutex() - // set initial value - codeMirror.setValue(textType.toString()) - // observe type and target - this._typeObserver = event => typeObserver(this, event) - this._targetObserver = (_, change) => targetObserver(this, change) - this._cursors = new Map() - this._awarenessListener = event => { - const f = clientId => updateRemoteSelection(y, codeMirror, textType, this._cursors, clientId) - event.added.forEach(f) - event.removed.forEach(f) - event.updated.forEach(f) - } - this._cursorListener = () => prosemirrorCursorActivity(y, codeMirror, textType) - this._blurListeer = () => - y.setAwarenessField('cursor', null) - textType.observe(this._typeObserver) - codeMirror.on('change', this._targetObserver) - if (cursors) { - y.on('awareness', this._awarenessListener) - codeMirror.on('cursorActivity', this._cursorListener) - codeMirror.on('blur', this._blurListeer) - codeMirror.on('focus', this._cursorListener) - } - } - destroy () { - this.type.unobserve(this._typeObserver) - this.target.off('change', this._targetObserver) - this.type.off('awareness', this._awarenessListener) - this.target.off('cursorActivity', this._cursorListener) - this.target.off('focus', this._cursorListener) - this.target.off('blur', this._blurListeer) - this.type = null - this.target = null - } -} diff --git a/bindings/dom.js b/bindings/dom.js deleted file mode 100644 index 4eb4d91f..00000000 --- a/bindings/dom.js +++ /dev/null @@ -1 +0,0 @@ -export * from './dom/DomBinding.js' diff --git a/bindings/dom/DomBinding.js b/bindings/dom/DomBinding.js deleted file mode 100644 index 4c12cead..00000000 --- a/bindings/dom/DomBinding.js +++ /dev/null @@ -1,248 +0,0 @@ -/** - * @module bindings/dom - */ - -/* global MutationObserver, getSelection */ - -import { fromRelativePosition } from '../../utils/relativePosition.js' -import { createMutex } from '../../lib/mutex.js' -import { createAssociation, removeAssociation } from './util.js' -import { beforeTransactionSelectionFixer, afterTransactionSelectionFixer, getCurrentRelativeSelection } from './selection.js' -import { defaultFilter, applyFilterOnType } from './filter.js' -import { typeObserver } from './typeObserver.js' -import { domObserver } from './domObserver.js' -import { YXmlFragment } from '../../types/YXmlElement.js' // eslint-disable-line - -/** - * @callback DomFilter - * @param {string} nodeName - * @param {Map} attrs - * @return {Map | null} - */ - -/** - * A binding that binds the children of a YXmlFragment to a DOM element. - * - * This binding is automatically destroyed when its parent is deleted. - * - * @example - * const div = document.createElement('div') - * const type = y.define('xml', Y.XmlFragment) - * const binding = new Y.QuillBinding(type, div) - * - * @class - */ -export class DomBinding { - /** - * @param {YXmlFragment} type The bind source. This is the ultimate source of - * truth. - * @param {Element} target The bind target. Mirrors the target. - * @param {Object} [opts] Optional configurations - - * @param {DomFilter} [opts.filter=defaultFilter] The filter function to use. - * @param {Document} [opts.document=document] The filter function to use. - * @param {Object} [opts.hooks] The filter function to use. - * @param {Element} [opts.scrollingElement=null] The filter function to use. - */ - constructor (type, target, opts = {}) { - // Binding handles textType as this.type and domTextarea as this.target - /** - * The Yjs type that is bound to `target` - * @type {YXmlFragment} - */ - this.type = type - /** - * The target that `type` is bound to. - * @type {Element} - */ - this.target = target - /** - * @private - */ - this._mutualExclude = createMutex() - this.opts = opts - opts.document = opts.document || document - opts.hooks = opts.hooks || {} - this.scrollingElement = opts.scrollingElement || null - /** - * Maps each DOM element to the type that it is associated with. - * @type {Map} - */ - this.domToType = new Map() - /** - * Maps each YXml type to the DOM element that it is associated with. - * @type {Map} - */ - this.typeToDom = new Map() - /** - * Defines which DOM attributes and elements to filter out. - * Also filters remote changes. - * @type {DomFilter} - */ - this.filter = opts.filter || defaultFilter - // set initial value - target.innerHTML = '' - type.forEach(child => { - target.insertBefore(child.toDom(opts.document, opts.hooks, this), null) - }) - this._typeObserver = typeObserver.bind(this) - this._domObserver = mutations => { - domObserver.call(this, mutations, opts.document) - } - type.observeDeep(this._typeObserver) - this._mutationObserver = new MutationObserver(this._domObserver) - this._mutationObserver.observe(target, { - childList: true, - attributes: true, - characterData: true, - subtree: true - }) - this._currentSel = null - this._selectionchange = () => { - this._currentSel = getCurrentRelativeSelection(this) - } - document.addEventListener('selectionchange', this._selectionchange) - const y = type._y - this.y = y - // Force flush dom changes before Type changes are applied (they might - // modify the dom) - this._beforeTransactionHandler = y => { - this._domObserver(this._mutationObserver.takeRecords()) - this._mutualExclude(() => { - beforeTransactionSelectionFixer(this) - }) - } - y.on('beforeTransaction', this._beforeTransactionHandler) - this._afterTransactionHandler = (y, transaction) => { - this._mutualExclude(() => { - afterTransactionSelectionFixer(this) - }) - // remove associations - // TODO: this could be done more efficiently - // e.g. Always delete using the following approach, or removeAssociation - // in dom/type-observer.. - transaction.deletedStructs.forEach(type => { - const dom = this.typeToDom.get(type) - if (dom !== undefined) { - removeAssociation(this, dom, type) - } - }) - } - y.on('afterTransaction', this._afterTransactionHandler) - // Before calling observers, apply dom filter to all changed and new types. - this._beforeObserverCallsHandler = (y, transaction) => { - // Apply dom filter to new and changed types - transaction.changedTypes.forEach((subs, type) => { - // Only check attributes. New types are filtered below. - if ((subs.size > 1 || (subs.size === 1 && subs.has(null) === false))) { - applyFilterOnType(y, this, type) - } - }) - transaction.newTypes.forEach(type => { - applyFilterOnType(y, this, type) - }) - } - y.on('beforeObserverCalls', this._beforeObserverCallsHandler) - createAssociation(this, target, type) - } - - flushDomChanges () { - this._domObserver(this._mutationObserver.takeRecords()) - } - - /** - * NOTE: - * * does not apply filter to existing elements! - * * only guarantees that changes are filtered locally. Remote sites may see different content. - * - * @param {DomFilter} filter The filter function to use from now on. - */ - setFilter (filter) { - this.filter = filter - // TODO: apply filter to all elements - } - - _getUndoStackInfo () { - return this.getSelection() - } - - _restoreUndoStackInfo (info) { - this.restoreSelection(info) - } - - getSelection () { - return this._currentSel - } - - restoreSelection (selection) { - if (selection !== null) { - const { to, from } = selection - /** - * There is little information on the difference between anchor/focus and base/extent. - * MDN doesn't even mention base/extent anymore.. though you still have to call - * setBaseAndExtent to change the selection.. - * I can observe that base/extend refer to notes higher up in the xml hierachy. - * Espesially for undo/redo this is preferred. If this becomes a problem in the future, - * we should probably go back to anchor/focus. - */ - const browserSelection = getSelection() - let { baseNode, baseOffset, extentNode, extentOffset } = browserSelection - if (from !== null) { - let sel = fromRelativePosition(this.y, from) - if (sel !== null) { - let node = this.typeToDom.get(sel.type) - let offset = sel.offset - if (node !== baseNode || offset !== baseOffset) { - baseNode = node - baseOffset = offset - } - } - } - if (to !== null) { - let sel = fromRelativePosition(this.y, to) - if (sel !== null) { - let node = this.typeToDom.get(sel.type) - let offset = sel.offset - if (node !== extentNode || offset !== extentOffset) { - extentNode = node - extentOffset = offset - } - } - } - browserSelection.setBaseAndExtent( - baseNode, - baseOffset, - extentNode, - extentOffset - ) - } - } - - /** - * Remove all properties that are handled by this class. - */ - destroy () { - this.domToType = null - this.typeToDom = null - this.type.unobserveDeep(this._typeObserver) - this._mutationObserver.disconnect() - const y = this.type._y - y.off('beforeTransaction', this._beforeTransactionHandler) - y.off('beforeObserverCalls', this._beforeObserverCallsHandler) - y.off('afterTransaction', this._afterTransactionHandler) - document.removeEventListener('selectionchange', this._selectionchange) - this.type = null - this.target = null - } -} - -/** - * A filter defines which elements and attributes to share. - * Return null if the node should be filtered. Otherwise return the Map of - * accepted attributes. - * - * @callback FilterFunction - * @param {string} nodeName - * @param {Map} attrs - * @return {Map|null} - */ diff --git a/bindings/dom/domObserver.js b/bindings/dom/domObserver.js deleted file mode 100644 index f0531256..00000000 --- a/bindings/dom/domObserver.js +++ /dev/null @@ -1,150 +0,0 @@ -/** - * @module bindings/dom - */ - -import { YXmlHook } from '../../types/YXmlHook.js' -import { - iterateUntilUndeleted, - removeAssociation, - insertNodeHelper } from './util.js' -import { simpleDiff } from '../../lib/diff.js' -import { YXmlFragment } from '../../types/YXmlElement.js' - -/** - * 1. Check if any of the nodes was deleted - * 2. Iterate over the children. - * 2.1 If a node exists that is not yet bound to a type, insert a new node - * 2.2 If _contents.length < dom.childNodes.length, fill the - * rest of _content with childNodes - * 2.3 If a node was moved, delete it and - * recreate a new yxml element that is bound to that node. - * You can detect that a node was moved because expectedId - * !== actualId in the list - * - * @function - * @private - */ -const applyChangesFromDom = (binding, dom, yxml, _document) => { - if (yxml == null || yxml === false || yxml.constructor === YXmlHook) { - return - } - const y = yxml._y - const knownChildren = new Set() - for (let i = dom.childNodes.length - 1; i >= 0; i--) { - const type = binding.domToType.get(dom.childNodes[i]) - if (type !== undefined && type !== false) { - knownChildren.add(type) - } - } - // 1. Check if any of the nodes was deleted - yxml.forEach(childType => { - if (knownChildren.has(childType) === false) { - childType._delete(y) - removeAssociation(binding, binding.typeToDom.get(childType), childType) - } - }) - // 2. iterate - const childNodes = dom.childNodes - const len = childNodes.length - let prevExpectedType = null - let expectedType = iterateUntilUndeleted(yxml._start) - for (let domCnt = 0; domCnt < len; domCnt++) { - const childNode = childNodes[domCnt] - const childType = binding.domToType.get(childNode) - if (childType !== undefined) { - if (childType === false) { - // should be ignored or is going to be deleted - continue - } - if (expectedType !== null) { - if (expectedType !== childType) { - // 2.3 Not expected node - if (childType._parent !== yxml) { - // child was moved from another parent - // childType is going to be deleted by its previous parent - removeAssociation(binding, childNode, childType) - } else { - // child was moved to a different position. - removeAssociation(binding, childNode, childType) - childType._delete(y) - } - prevExpectedType = insertNodeHelper(yxml, prevExpectedType, childNode, _document, binding) - } else { - // Found expected node. Continue. - prevExpectedType = expectedType - expectedType = iterateUntilUndeleted(expectedType._right) - } - } else { - // 2.2 Fill _content with child nodes - prevExpectedType = insertNodeHelper(yxml, prevExpectedType, childNode, _document, binding) - } - } else { - // 2.1 A new node was found - prevExpectedType = insertNodeHelper(yxml, prevExpectedType, childNode, _document, binding) - } - } -} - -/** - * @private - * @function - */ -export function domObserver (mutations, _document) { - this._mutualExclude(() => { - this.type._y.transact(() => { - let diffChildren = new Set() - mutations.forEach(mutation => { - const dom = mutation.target - const yxml = this.domToType.get(dom) - if (yxml === undefined) { // In case yxml is undefined, we double check if we forgot to bind the dom - let parent = dom - let yParent - do { - parent = parent.parentElement - yParent = this.domToType.get(parent) - } while (yParent === undefined && parent !== null) - if (yParent !== false && yParent !== undefined && yParent.constructor !== YXmlHook) { - diffChildren.add(parent) - } - return - } else if (yxml === false || yxml.constructor === YXmlHook) { - // dom element is filtered / a dom hook - return - } - switch (mutation.type) { - case 'characterData': - var change = simpleDiff(yxml.toString(), dom.nodeValue) - yxml.delete(change.pos, change.remove) - yxml.insert(change.pos, change.insert) - break - case 'attributes': - if (yxml.constructor === YXmlFragment) { - break - } - let name = mutation.attributeName - let val = dom.getAttribute(name) - // check if filter accepts attribute - let attributes = new Map() - attributes.set(name, val) - if (yxml.constructor !== YXmlFragment && this.filter(dom.nodeName, attributes).size > 0) { - if (yxml.getAttribute(name) !== val) { - if (val == null) { - yxml.removeAttribute(name) - } else { - yxml.setAttribute(name, val) - } - } - } - break - case 'childList': - diffChildren.add(mutation.target) - break - } - }) - for (let dom of diffChildren) { - const yxml = this.domToType.get(dom) - applyChangesFromDom(this, dom, yxml, _document) - } - }) - }) -} diff --git a/bindings/dom/domToType.js b/bindings/dom/domToType.js deleted file mode 100644 index dbd03941..00000000 --- a/bindings/dom/domToType.js +++ /dev/null @@ -1,74 +0,0 @@ -/** - * @module bindings/dom - */ - -/* eslint-env browser */ -import { YXmlText } from '../../types/YXmlText.js' -import { YXmlHook } from '../../types/YXmlHook.js' -import { YXmlElement } from '../../types/YXmlElement.js' -import { createAssociation, domsToTypes } from './util.js' -import { filterDomAttributes, defaultFilter } from './filter.js' -import { DomBinding } from './DomBinding.js' // eslint-disable-line - -/** - * @callback DomFilter - * @param {string} nodeName - * @param {Map} attrs - * @return {Map | null} - */ - -/** - * Creates a Yjs type (YXml) based on the contents of a DOM Element. - * - * @function - * @param {Element|Text} element The DOM Element - * @param {?Document} _document Optional. Provide the global document object - * @param {Object} [hooks = {}] Optional. Set of Yjs Hooks - * @param {DomFilter} [filter=defaultFilter] Optional. Dom element filter - * @param {?DomBinding} binding Warning: This property is for internal use only! - * @return {YXmlElement | YXmlText | false} - */ -export const domToType = (element, _document = document, hooks = {}, filter = defaultFilter, binding) => { - /** - * @type {any} - */ - let type = null - if (element instanceof Element) { - let hookName = null - let hook - // configure `hookName !== undefined` if element is a hook. - if (element.hasAttribute('data-yjs-hook')) { - hookName = element.getAttribute('data-yjs-hook') - hook = hooks[hookName] - if (hook === undefined) { - console.error(`Unknown hook "${hookName}". Deleting yjsHook dataset property.`) - element.removeAttribute('data-yjs-hook') - hookName = null - } - } - if (hookName === null) { - // Not a hook - const attrs = filterDomAttributes(element, filter) - if (attrs === null) { - type = false - } else { - type = new YXmlElement(element.nodeName) - attrs.forEach((val, key) => { - type.setAttribute(key, val) - }) - type.insert(0, domsToTypes(element.childNodes, document, hooks, filter, binding)) - } - } else { - // Is a hook - type = new YXmlHook(hookName) - hook.fillType(element, type) - } - } else if (element instanceof Text) { - type = new YXmlText() - type.insert(0, element.nodeValue) - } else { - throw new Error('Can\'t transform this node type to a YXml type!') - } - createAssociation(binding, element, type) - return type -} diff --git a/bindings/dom/filter.js b/bindings/dom/filter.js deleted file mode 100644 index bd4c41ef..00000000 --- a/bindings/dom/filter.js +++ /dev/null @@ -1,71 +0,0 @@ -/** - * @module bindings/dom - */ - -import { Y } from '../../utils/Y.js' // eslint-disable-line -import { YXmlElement, YXmlFragment } from '../../types/YXmlElement.js' // eslint-disable-line -import { isParentOf } from '../../utils/isParentOf.js' -import { DomBinding } from './DomBinding.js' // eslint-disable-line - -/** - * Default filter method (does nothing). - * - * @function - * @param {String} nodeName The nodeName of the element - * @param {Map} attrs Map of key-value pairs that are attributes of the node. - * @return {Map | null} The allowed attributes or null, if the element should be - * filtered. - */ -export const defaultFilter = (nodeName, attrs) => { - // TODO: implement basic filter that filters out dangerous properties! - return attrs -} - -/** - * @private - * @function - * @param {Element} dom - * @param {Function} filter - */ -export const filterDomAttributes = (dom, filter) => { - const attrs = new Map() - for (let i = dom.attributes.length - 1; i >= 0; i--) { - const attr = dom.attributes[i] - attrs.set(attr.name, attr.value) - } - return filter(dom.nodeName, attrs) -} - -/** - * Applies a filter on a type. - * - * @private - * @function - * @param {Y} y The Yjs instance. - * @param {DomBinding} binding The DOM binding instance that has the dom filter. - * @param {YXmlElement | YXmlFragment } type The type to apply the filter to. - */ -export const applyFilterOnType = (y, binding, type) => { - if (isParentOf(binding.type, type) && type instanceof YXmlElement) { - const nodeName = type.nodeName - let attributes = new Map() - if (type.getAttributes !== undefined) { - let attrs = type.getAttributes() - for (let key in attrs) { - attributes.set(key, attrs[key]) - } - } - const filteredAttributes = binding.filter(nodeName, new Map(attributes)) - if (filteredAttributes === null) { - type._delete(y, true) - } else { - // iterate original attributes - attributes.forEach((value, key) => { - // delete all attributes that are not in filteredAttributes - if (filteredAttributes.has(key) === false) { - type.removeAttribute(key) - } - }) - } - } -} diff --git a/bindings/dom/selection.js b/bindings/dom/selection.js deleted file mode 100644 index d541b205..00000000 --- a/bindings/dom/selection.js +++ /dev/null @@ -1,49 +0,0 @@ -/** - * @module bindings/dom - */ - -/* globals getSelection */ - -import { getRelativePosition } from '../../utils/relativePosition.js' - -let relativeSelection = null - -/** - * @private - */ -const _getCurrentRelativeSelection = domBinding => { - const { baseNode, baseOffset, extentNode, extentOffset } = getSelection() - const baseNodeType = domBinding.domToType.get(baseNode) - const extentNodeType = domBinding.domToType.get(extentNode) - if (baseNodeType !== undefined && extentNodeType !== undefined) { - return { - from: getRelativePosition(baseNodeType, baseOffset), - to: getRelativePosition(extentNodeType, extentOffset) - } - } - return null -} - -/** - * @private - */ -export const getCurrentRelativeSelection = typeof getSelection !== 'undefined' ? _getCurrentRelativeSelection : domBinding => null - -/** - * @private - */ -export const beforeTransactionSelectionFixer = domBinding => { - relativeSelection = getCurrentRelativeSelection(domBinding) -} - -/** - * Reset the browser range after every transaction. - * This prevents any collapsing issues with the local selection. - * - * @private - */ -export const afterTransactionSelectionFixer = domBinding => { - if (relativeSelection !== null) { - domBinding.restoreSelection(relativeSelection) - } -} diff --git a/bindings/dom/typeObserver.js b/bindings/dom/typeObserver.js deleted file mode 100644 index 5767a932..00000000 --- a/bindings/dom/typeObserver.js +++ /dev/null @@ -1,110 +0,0 @@ -/** - * @module bindings/dom - */ - -/* eslint-env browser */ -/* global getSelection */ - -import { YXmlText } from '../../types/YXmlText.js' -import { YXmlHook } from '../../types/YXmlHook.js' -import { removeDomChildrenUntilElementFound } from './util.js' - -const findScrollReference = scrollingElement => { - if (scrollingElement !== null) { - let anchor = getSelection().anchorNode - if (anchor == null) { - let children = scrollingElement.children // only iterate through non-text nodes - for (let i = 0; i < children.length; i++) { - const elem = children[i] - const rect = elem.getBoundingClientRect() - if (rect.top >= 0) { - return { elem, top: rect.top } - } - } - } else { - /** - * @type {Element} - */ - let elem = anchor.parentElement - if (anchor instanceof Element) { - elem = anchor - } - return { - elem, - top: elem.getBoundingClientRect().top - } - } - } - return null -} - -const fixScroll = (scrollingElement, ref) => { - if (ref !== null) { - const { elem, top } = ref - const currentTop = elem.getBoundingClientRect().top - const newScroll = scrollingElement.scrollTop + currentTop - top - if (newScroll >= 0) { - scrollingElement.scrollTop = newScroll - } - } -} - -/** - * @private - */ -export const typeObserver = function (events) { - this._mutualExclude(() => { - const scrollRef = findScrollReference(this.scrollingElement) - events.forEach(event => { - const yxml = event.target - const dom = this.typeToDom.get(yxml) - if (dom !== undefined && dom !== false) { - if (yxml.constructor === YXmlText) { - dom.nodeValue = yxml.toString() - } else if (event.attributesChanged !== undefined) { - // update attributes - event.attributesChanged.forEach(attributeName => { - const value = yxml.getAttribute(attributeName) - if (value === undefined) { - dom.removeAttribute(attributeName) - } else { - dom.setAttribute(attributeName, value) - } - }) - /* - * TODO: instead of hard-checking the types, it would be best to - * specify the type's features. E.g. - * - _yxmlHasAttributes - * - _yxmlHasChildren - * Furthermore, the features shouldn't be encoded in the types, - * only in the attributes (above) - */ - if (event.childListChanged && yxml.constructor !== YXmlHook) { - let currentChild = dom.firstChild - yxml.forEach(childType => { - const childNode = this.typeToDom.get(childType) - switch (childNode) { - case undefined: - // Does not exist. Create it. - const node = childType.toDom(this.opts.document, this.opts.hooks, this) - dom.insertBefore(node, currentChild) - break - case false: - // nop - break - default: - // Is already attached to the dom. - // Find it and remove all dom nodes in-between. - removeDomChildrenUntilElementFound(dom, currentChild, childNode) - currentChild = childNode.nextSibling - break - } - }) - removeDomChildrenUntilElementFound(dom, currentChild, null) - } - } - } - }) - fixScroll(this.scrollingElement, scrollRef) - }) -} diff --git a/bindings/dom/util.js b/bindings/dom/util.js deleted file mode 100644 index 04459a97..00000000 --- a/bindings/dom/util.js +++ /dev/null @@ -1,135 +0,0 @@ -/** - * @module bindings/dom - */ - -import { domToType } from './domToType.js' -import { DomBinding } from './DomBinding.js' // eslint-disable-line - -/** - * Iterates items until an undeleted item is found. - * - * @private - */ -export const iterateUntilUndeleted = item => { - while (item !== null && item._deleted) { - item = item._right - } - return item -} - -/** - * Removes an association (the information that a DOM element belongs to a - * type). - * - * @private - * @function - * @param {DomBinding} domBinding The binding object - * @param {Element} dom The dom that is to be associated with type - * @param {YXmlElement|YXmlHook} type The type that is to be associated with dom - * - */ -export const removeAssociation = (domBinding, dom, type) => { - domBinding.domToType.delete(dom) - domBinding.typeToDom.delete(type) -} - -/** - * Creates an association (the information that a DOM element belongs to a - * type). - * - * @private - * @function - * @param {DomBinding} domBinding The binding object - * @param {DocumentFragment|Element|Text} dom The dom that is to be associated with type - * @param {YXmlFragment|YXmlElement|YXmlHook|YXmlText} type The type that is to be associated with dom - * - */ -export const createAssociation = (domBinding, dom, type) => { - if (domBinding !== undefined) { - domBinding.domToType.set(dom, type) - domBinding.typeToDom.set(type, dom) - } -} - -/** - * If oldDom is associated with a type, associate newDom with the type and - * forget about oldDom. If oldDom is not associated with any type, nothing happens. - * - * @private - * @function - * @param {DomBinding} domBinding The binding object - * @param {Element} oldDom The existing dom - * @param {Element} newDom The new dom object - */ -export const switchAssociation = (domBinding, oldDom, newDom) => { - if (domBinding !== undefined) { - const type = domBinding.domToType.get(oldDom) - if (type !== undefined) { - removeAssociation(domBinding, oldDom, type) - createAssociation(domBinding, newDom, type) - } - } -} - -/** - * Insert Dom Elements after one of the children of this YXmlFragment. - * The Dom elements will be bound to a new YXmlElement and inserted at the - * specified position. - * - * @private - * @function - * @param {YXmlElement} type The type in which to insert DOM elements. - * @param {YXmlElement|null} prev The reference node. New YxmlElements are - * inserted after this node. Set null to insert at - * the beginning. - * @param {Array} doms The Dom elements to insert. - * @param {?Document} _document Optional. Provide the global document object. - * @param {DomBinding} binding The dom binding - * @return {Array} The YxmlElements that are inserted. - */ -export const insertDomElementsAfter = (type, prev, doms, _document, binding) => { - const types = domsToTypes(doms, _document, binding.opts.hooks, binding.filter, binding) - return type.insertAfter(prev, types) -} - -export const domsToTypes = (doms, _document, hooks, filter, binding) => { - const types = [] - for (let dom of doms) { - const t = domToType(dom, _document, hooks, filter, binding) - if (t !== false) { - types.push(t) - } - } - return types -} - -/** - * @private - * @function - */ -export const insertNodeHelper = (yxml, prevExpectedNode, child, _document, binding) => { - let insertedNodes = insertDomElementsAfter(yxml, prevExpectedNode, [child], _document, binding) - if (insertedNodes.length > 0) { - return insertedNodes[0] - } else { - return prevExpectedNode - } -} - -/** - * Remove children until `elem` is found. - * - * @private - * @function - * @param {Element} parent The parent of `elem` and `currentChild`. - * @param {Node} currentChild Start removing elements with `currentChild`. If - * `currentChild` is `elem` it won't be removed. - * @param {Element|null} elem The elemnt to look for. - */ -export const removeDomChildrenUntilElementFound = (parent, currentChild, elem) => { - while (currentChild !== elem) { - const del = currentChild - currentChild = currentChild.nextSibling - parent.removeChild(del) - } -} diff --git a/bindings/prosemirror.js b/bindings/prosemirror.js deleted file mode 100644 index df11d635..00000000 --- a/bindings/prosemirror.js +++ /dev/null @@ -1,756 +0,0 @@ -/** - * @module bindings/prosemirror - */ - -import { YText } from '../types/YText.js' // eslint-disable-line -import { YXmlElement, YXmlFragment } from '../types/YXmlElement.js' // eslint-disable-line -import { createMutex } from '../lib/mutex.js' -import * as PModel from 'prosemirror-model' -import { EditorView, Decoration, DecorationSet } from 'prosemirror-view' // eslint-disable-line -import { Plugin, PluginKey, EditorState, TextSelection } from 'prosemirror-state' // eslint-disable-line -import * as math from '../lib/math.js' -import * as object from '../lib/object.js' -import * as YPos from '../utils/relativePosition.js' -import { isVisible } from '../utils/snapshot.js' -import { simpleDiff } from '../lib/diff.js' - -/** - * @typedef {Map} ProsemirrorMapping - */ - -/** - * The unique prosemirror plugin key for prosemirrorPlugin. - * - * @public - */ -export const prosemirrorPluginKey = new PluginKey('yjs') - -/** - * This plugin listens to changes in prosemirror view and keeps yXmlState and view in sync. - * - * This plugin also keeps references to the type and the shared document so other plugins can access it. - * @param {YXmlFragment} yXmlFragment - * @return {Plugin} Returns a prosemirror plugin that binds to this type - */ -export const prosemirrorPlugin = yXmlFragment => { - let changedInitialContent = false - const plugin = new Plugin({ - props: { - editable: (state) => prosemirrorPluginKey.getState(state).snapshot == null - }, - key: prosemirrorPluginKey, - state: { - init: (initargs, state) => { - return { - type: yXmlFragment, - y: yXmlFragment._y, - binding: null, - snapshot: null, - isChangeOrigin: false - } - }, - apply: (tr, pluginState) => { - const change = tr.getMeta(prosemirrorPluginKey) - if (change !== undefined) { - pluginState = Object.assign({}, pluginState) - for (let key in change) { - pluginState[key] = change[key] - } - } - // always set isChangeOrigin. If undefined, this is not change origin. - pluginState.isChangeOrigin = change !== undefined && !!change.isChangeOrigin - if (pluginState.binding !== null) { - if (change !== undefined && change.snapshot !== undefined) { - // snapshot changed, rerender next - setTimeout(() => { - if (change.restore == null) { - pluginState.binding._renderSnapshot(change.snapshot, change.prevSnapshot) - } else { - pluginState.binding._renderSnapshot(change.snapshot, change.snapshot) - // reset to current prosemirror state - delete pluginState.restore - delete pluginState.snapshot - delete pluginState.prevSnapshot - pluginState.binding._prosemirrorChanged(pluginState.binding.prosemirrorView.state.doc) - } - }, 0) - } else if (pluginState.snapshot == null) { - // only apply if no snapshot active - // update Yjs state when apply is called. We need to do this here to compute the correct cursor decorations with the cursor plugin - if (changedInitialContent || tr.doc.content.size > 4) { - changedInitialContent = true - pluginState.binding._prosemirrorChanged(tr.doc) - } - } - } - return pluginState - } - }, - view: view => { - const binding = new ProsemirrorBinding(yXmlFragment, view) - view.dispatch(view.state.tr.setMeta(prosemirrorPluginKey, { binding })) - return { - update: () => { - const pluginState = plugin.getState(view.state) - if (pluginState.snapshot == null) { - if (changedInitialContent || view.state.doc.content.size > 4) { - changedInitialContent = true - binding._prosemirrorChanged(view.state.doc) - } - } - }, - destroy: () => { - binding.destroy() - } - } - } - }) - return plugin -} - -/** - * The unique prosemirror plugin key for cursorPlugin.type - * - * @public - */ -export const cursorPluginKey = new PluginKey('yjs-cursor') - -/** - * A prosemirror plugin that listens to awareness information on Yjs. - * This requires that a `prosemirrorPlugin` is also bound to the prosemirror. - * - * @public - */ -export const cursorPlugin = new Plugin({ - key: cursorPluginKey, - props: { - decorations: state => { - const ystate = prosemirrorPluginKey.getState(state) - const y = ystate.y - const awareness = y.getAwarenessInfo() - const decorations = [] - if (ystate.snapshot != null) { - // do not render cursors while snapshot is active - return - } - awareness.forEach((aw, userID) => { - if (userID === y.userID) { - return - } - if (aw.cursor != null) { - let user = aw.user || {} - if (user.color == null) { - user.color = '#ffa500' - } - if (user.name == null) { - user.name = `User: ${userID}` - } - let anchor = relativePositionToAbsolutePosition(ystate.type, aw.cursor.anchor || null, ystate.binding.mapping) - let head = relativePositionToAbsolutePosition(ystate.type, aw.cursor.head || null, ystate.binding.mapping) - if (anchor !== null && head !== null) { - let maxsize = math.max(state.doc.content.size - 1, 0) - anchor = math.min(anchor, maxsize) - head = math.min(head, maxsize) - decorations.push(Decoration.widget(head, () => { - const cursor = document.createElement('span') - cursor.classList.add('ProseMirror-yjs-cursor') - cursor.setAttribute('style', `border-color: ${user.color}`) - const userDiv = document.createElement('div') - userDiv.setAttribute('style', `background-color: ${user.color}`) - userDiv.insertBefore(document.createTextNode(user.name), null) - cursor.insertBefore(userDiv, null) - return cursor - }, { key: userID + '' })) - const from = math.min(anchor, head) - const to = math.max(anchor, head) - decorations.push(Decoration.inline(from, to, { style: `background-color: ${user.color}70` })) - } - } - }) - return DecorationSet.create(state.doc, decorations) - } - }, - view: view => { - const ystate = prosemirrorPluginKey.getState(view.state) - const y = ystate.y - const awarenessListener = () => { - view.updateState(view.state) - } - const updateCursorInfo = () => { - const current = y.getLocalAwarenessInfo() - if (view.hasFocus() && ystate.binding !== null) { - const anchor = absolutePositionToRelativePosition(view.state.selection.anchor, ystate.type, ystate.binding.mapping) - const head = absolutePositionToRelativePosition(view.state.selection.head, ystate.type, ystate.binding.mapping) - if (current.cursor == null || !YPos.equal(current.cursor.anchor, anchor) || !YPos.equal(current.cursor.head, head)) { - y.setAwarenessField('cursor', { - anchor, head - }) - } - } else if (current.cursor !== null) { - y.setAwarenessField('cursor', null) - } - } - y.on('awareness', awarenessListener) - view.dom.addEventListener('focusin', updateCursorInfo) - view.dom.addEventListener('focusout', updateCursorInfo) - return { - update: updateCursorInfo, - destroy: () => { - const y = prosemirrorPluginKey.getState(view.state).y - y.setAwarenessField('cursor', null) - y.off('awareness', awarenessListener) - } - } - } -}) - -/** - * Transforms a Prosemirror based absolute position to a Yjs based relative position. - * - * @param {number} pos - * @param {YXmlFragment} type - * @param {ProsemirrorMapping} mapping - * @return {any} relative position - */ -export const absolutePositionToRelativePosition = (pos, type, mapping) => { - if (pos === 0) { - return YPos.getRelativePosition(type, 0) - } - let n = type._first - if (n !== null) { - while (type !== n) { - const pNodeSize = (mapping.get(n) || { nodeSize: 0 }).nodeSize - if (n.constructor === YText) { - if (n.length >= pos) { - return YPos.getRelativePosition(n, pos) - } else { - pos -= n.length - } - if (n._next !== null) { - n = n._next - } else { - do { - n = n._parent - pos-- - } while (n._next === null && n !== type) - if (n !== type) { - n = n._next - } - } - } else if (n._first !== null && pos < pNodeSize) { - n = n._first - pos-- - } else { - if (pos === 1 && n.length === 0 && pNodeSize > 1) { - // edge case, should end in this paragraph - return ['endof', n._id.user, n._id.clock, null, null] - } - pos -= pNodeSize - if (n._next !== null) { - n = n._next - } else { - if (pos === 0) { - n = n._parent - return ['endof', n._id.user, n._id.clock || null, n._id.name || null, n._id.type || null] - } - do { - n = n._parent - pos-- - } while (n._next === null && n !== type) - if (n !== type) { - n = n._next - } - } - } - if (pos === 0 && n.constructor !== YText && n !== type) { // TODO: set to <= 0 - return [n._id.user, n._id.clock] - } - } - } - return YPos.getRelativePosition(type, type.length) -} - -/** - * @param {YXmlFragment} yDoc Top level type that is bound to pView - * @param {any} relPos Encoded Yjs based relative position - * @param {ProsemirrorMapping} mapping - */ -export const relativePositionToAbsolutePosition = (yDoc, relPos, mapping) => { - const decodedPos = YPos.fromRelativePosition(yDoc._y, relPos) - if (decodedPos === null) { - return null - } - let type = decodedPos.type - let pos = 0 - if (type.constructor === YText) { - pos = decodedPos.offset - } else if (!type._deleted) { - let n = type._first - let i = 0 - while (i < type.length && i < decodedPos.offset && n !== null) { - i++ - pos += mapping.get(n).nodeSize - n = n._next - } - pos += 1 // increase because we go out of n - } - while (type !== yDoc) { - const parent = type._parent - if (!parent._deleted) { - pos += 1 // the start tag - let n = parent._first - // now iterate until we found type - while (n !== null) { - if (n === type) { - break - } - pos += mapping.get(n).nodeSize - n = n._next - } - } - type = parent - } - return pos - 1 // we don't count the most outer tag, because it is a fragment -} - -/** - * Binding for prosemirror. - * - * @protected - */ -export class ProsemirrorBinding { - /** - * @param {YXmlFragment} yXmlFragment The bind source - * @param {EditorView} prosemirrorView The target binding - */ - constructor (yXmlFragment, prosemirrorView) { - this.type = yXmlFragment - this.prosemirrorView = prosemirrorView - this.mux = createMutex() - /** - * @type {ProsemirrorMapping} - */ - this.mapping = new Map() - this._observeFunction = this._typeChanged.bind(this) - this.y = yXmlFragment._y - /** - * current selection as relative positions in the Yjs model - */ - this._relSelection = null - this.y.on('beforeTransaction', e => { - this._relSelection = { - anchor: absolutePositionToRelativePosition(this.prosemirrorView.state.selection.anchor, yXmlFragment, this.mapping), - head: absolutePositionToRelativePosition(this.prosemirrorView.state.selection.head, yXmlFragment, this.mapping) - } - }) - yXmlFragment.observeDeep(this._observeFunction) - } - _forceRerender () { - this.mapping = new Map() - this.mux(() => { - const fragmentContent = this.type.toArray().map(t => createNodeFromYElement(t, this.prosemirrorView.state.schema, this.mapping)).filter(n => n !== null) - const tr = this.prosemirrorView.state.tr.replace(0, this.prosemirrorView.state.doc.content.size, new PModel.Slice(new PModel.Fragment(fragmentContent), 0, 0)) - this.prosemirrorView.dispatch(tr) - }) - } - /** - * - * @param {*} snapshot - * @param {*} prevSnapshot - */ - _renderSnapshot (snapshot, prevSnapshot) { - // clear mapping because we are going to rerender - this.mapping = new Map() - this.mux(() => { - const fragmentContent = this.type.toArray({ sm: snapshot.sm, ds: prevSnapshot.ds}).map(t => createNodeFromYElement(t, this.prosemirrorView.state.schema, new Map(), snapshot, prevSnapshot)).filter(n => n !== null) - const tr = this.prosemirrorView.state.tr.replace(0, this.prosemirrorView.state.doc.content.size, new PModel.Slice(new PModel.Fragment(fragmentContent), 0, 0)) - this.prosemirrorView.dispatch(tr) - }) - } - _typeChanged (events, transaction) { - if (events.length === 0 || prosemirrorPluginKey.getState(this.prosemirrorView.state).snapshot != null) { - // drop out if snapshot is active - return - } - console.info('new types:', transaction.newTypes.size, 'deleted types:', transaction.deletedStructs.size, transaction.newTypes, transaction.deletedStructs) - this.mux(() => { - const delStruct = (_, struct) => this.mapping.delete(struct) - transaction.deletedStructs.forEach(struct => this.mapping.delete(struct)) - transaction.changedTypes.forEach(delStruct) - transaction.changedParentTypes.forEach(delStruct) - const fragmentContent = this.type.toArray().map(t => createNodeIfNotExists(t, this.prosemirrorView.state.schema, this.mapping)).filter(n => n !== null) - let tr = this.prosemirrorView.state.tr.replace(0, this.prosemirrorView.state.doc.content.size, new PModel.Slice(new PModel.Fragment(fragmentContent), 0, 0)) - const relSel = this._relSelection - if (relSel !== null && relSel.anchor !== null && relSel.head !== null) { - const anchor = relativePositionToAbsolutePosition(this.type, relSel.anchor, this.mapping) - const head = relativePositionToAbsolutePosition(this.type, relSel.head, this.mapping) - if (anchor !== null && head !== null) { - tr = tr.setSelection(TextSelection.create(tr.doc, anchor, head)) - } - } - tr = tr.setMeta(prosemirrorPluginKey, { isChangeOrigin: true }) - this.prosemirrorView.dispatch(tr) - }) - } - _prosemirrorChanged (doc) { - this.mux(() => { - updateYFragment(this.type, doc.content, this.mapping) - }) - } - destroy () { - this.type.unobserveDeep(this._observeFunction) - } -} - -/** - * @private - * @param {YXmlElement} el - * @param {PModel.Schema} schema - * @param {ProsemirrorMapping} mapping - * @param {HistorySnapshot} [snapshot] - * @param {HistorySnapshot} [prevSnapshot] - * @return {PModel.Node} - */ -export const createNodeIfNotExists = (el, schema, mapping, snapshot, prevSnapshot) => { - const node = mapping.get(el) - if (node === undefined) { - return createNodeFromYElement(el, schema, mapping, snapshot, prevSnapshot) - } - return node -} - -/** - * @private - * @param {YXmlElement} el - * @param {PModel.Schema} schema - * @param {ProsemirrorMapping} mapping - * @param {import('../protocols/history.js').HistorySnapshot} [snapshot] - * @param {import('../protocols/history.js').HistorySnapshot} [prevSnapshot] - * @return {PModel.Node | null} Returns node if node could be created. Otherwise it deletes the yjs type and returns null - */ -export const createNodeFromYElement = (el, schema, mapping, snapshot, prevSnapshot) => { - let _snapshot = snapshot - let _prevSnapshot = prevSnapshot - if (snapshot !== undefined && prevSnapshot !== undefined) { - if (!isVisible(el, snapshot)) { - // if this element is already rendered as deleted (ychange), then do not render children as deleted - _snapshot = {sm: snapshot.sm, ds: prevSnapshot.ds} - _prevSnapshot = _snapshot - } else if (!isVisible(el, prevSnapshot)) { - _prevSnapshot = _snapshot - } - } - const children = [] - const createChildren = type => { - if (type.constructor === YXmlElement) { - const n = createNodeIfNotExists(type, schema, mapping, _snapshot, _prevSnapshot) - if (n !== null) { - children.push(n) - } - } else { - const ns = createTextNodesFromYText(type, schema, mapping, _snapshot, _prevSnapshot) - if (ns !== null) { - ns.forEach(textchild => { - if (textchild !== null) { - children.push(textchild) - } - }) - } - } - } - if (snapshot === undefined || prevSnapshot === undefined) { - el.toArray().forEach(createChildren) - } else { - el.toArray({sm: snapshot.sm, ds: prevSnapshot.ds}).forEach(createChildren) - } - let node - try { - const attrs = el.getAttributes(_snapshot) - if (snapshot !== undefined) { - if (!isVisible(el, snapshot)) { - attrs.ychange = { user: el._id.user, state: 'removed' } - } else if (!isVisible(el, prevSnapshot)) { - attrs.ychange = { user: el._id.user, state: 'added' } - } - } - node = schema.node(el.nodeName.toLowerCase(), attrs, children) - } catch (e) { - // an error occured while creating the node. This is probably a result because of a concurrent action. - // ignore the node while rendering - /* do not delete anymore - el._y.transact(() => { - el._delete(el._y, true) - }) - */ - return null - } - mapping.set(el, node) - return node -} - -/** - * @private - * @param {YText} text - * @param {PModel.Schema} schema - * @param {ProsemirrorMapping} mapping - * @param {HistorySnapshot} [snapshot] - * @param {HistorySnapshot} [prevSnapshot] - * @return {Array} - */ -export const createTextNodesFromYText = (text, schema, mapping, snapshot, prevSnapshot) => { - const nodes = [] - const deltas = text.toDelta(snapshot, prevSnapshot) - try { - for (let i = 0; i < deltas.length; i++) { - const delta = deltas[i] - const marks = [] - for (let markName in delta.attributes) { - marks.push(schema.mark(markName, delta.attributes[markName])) - } - nodes.push(schema.text(delta.insert, marks)) - } - if (nodes.length > 0) { - mapping.set(text, nodes[0]) // only map to first child, all following children are also considered bound to this type - } - } catch (e) { - /* - text._y.transact(() => { - text._delete(text._y, true) - }) - */ - return null - } - return nodes -} - -/** - * @private - * @param {PModel.Node} node - * @param {ProsemirrorMapping} mapping - * @return {YXmlElement | YText} - */ -export const createTypeFromNode = (node, mapping) => { - let type - if (node.isText) { - type = new YText() - const attrs = {} - node.marks.forEach(mark => { - if (mark.type.name !== 'ychange') { - attrs[mark.type.name] = mark.attrs - } - }) - type.insert(0, node.text, attrs) - } else { - type = new YXmlElement(node.type.name) - for (let key in node.attrs) { - const val = node.attrs[key] - if (val !== null && key !== 'ychange') { - type.setAttribute(key, val) - } - } - const ins = [] - for (let i = 0; i < node.childCount; i++) { - ins.push(createTypeFromNode(node.child(i), mapping)) - } - type.insert(0, ins) - } - mapping.set(type, node) - return type -} - -const equalAttrs = (pattrs, yattrs) => { - const keys = Object.keys(pattrs).filter(key => pattrs[key] === null) - let eq = keys.length === Object.keys(yattrs).filter(key => yattrs[key] === null).length - for (let i = 0; i < keys.length && eq; i++) { - const key = keys[i] - const l = pattrs[key] - const r = yattrs[key] - eq = key === 'ychange' || l === r || (typeof l === 'object' && typeof r === 'object' && equalAttrs(l, r)) - } - return eq -} - -const equalYTextPText = (ytext, ptext) => { - const d = ytext.toDelta()[0] - return d.insert === ptext.text && object.keys(d.attributes || {}).length === ptext.marks.length && ptext.marks.every(mark => equalAttrs(d.attributes[mark.type.name], mark.attrs)) -} - -const equalYTypePNode = (ytype, pnode) => - ytype.constructor === YText - ? equalYTextPText(ytype, pnode) - : (matchNodeName(ytype, pnode) && ytype.length === pnode.childCount && equalAttrs(ytype.getAttributes(), pnode.attrs) && ytype.toArray().every((ychild, i) => equalYTypePNode(ychild, pnode.child(i)))) - -const computeChildEqualityFactor = (ytype, pnode, mapping) => { - const yChildren = ytype.toArray() - const pChildCnt = pnode.childCount - const yChildCnt = yChildren.length - const minCnt = math.min(yChildCnt, pChildCnt) - let left = 0 - let right = 0 - let foundMappedChild = false - for (; left < minCnt; left++) { - const leftY = yChildren[left] - const leftP = pnode.child(left) - if (mapping.get(leftY) === leftP) { - foundMappedChild = true// definite (good) match! - } else if (!equalYTypePNode(leftY, leftP)) { - break - } - } - for (; left + right < minCnt; right++) { - const rightY = yChildren[yChildCnt - right - 1] - const rightP = pnode.child(pChildCnt - right - 1) - if (mapping.get(rightY) !== rightP) { - foundMappedChild = true - } else if (!equalYTypePNode(rightP, rightP)) { - break - } - } - return { - equalityFactor: left + right, - foundMappedChild - } -} - -/** - * @private - * @param {YXmlFragment} yDomFragment - * @param {PModel.Node} pContent - * @param {ProsemirrorMapping} mapping - */ -const updateYFragment = (yDomFragment, pContent, mapping) => { - if (yDomFragment instanceof YXmlElement && yDomFragment.nodeName.toLowerCase() !== pContent.type.name) { - throw new Error('node name mismatch!') - } - mapping.set(yDomFragment, pContent) - // update attributes - if (yDomFragment instanceof YXmlElement) { - const yDomAttrs = yDomFragment.getAttributes() - const pAttrs = pContent.attrs - for (let key in pAttrs) { - if (pAttrs[key] !== null) { - if (yDomAttrs[key] !== pAttrs[key] && key !== 'ychange') { - yDomFragment.setAttribute(key, pAttrs[key]) - } - } else { - yDomFragment.removeAttribute(key) - } - } - // remove all keys that are no longer in pAttrs - for (let key in yDomAttrs) { - if (pAttrs[key] === undefined) { - yDomFragment.removeAttribute(key) - } - } - } - // update children - const pChildCnt = pContent.childCount - const yChildren = yDomFragment.toArray() - const yChildCnt = yChildren.length - const minCnt = math.min(pChildCnt, yChildCnt) - let left = 0 - let right = 0 - // find number of matching elements from left - for (;left < minCnt; left++) { - const leftY = yChildren[left] - const leftP = pContent.child(left) - if (mapping.get(leftY) !== leftP) { - if (equalYTypePNode(leftY, leftP)) { - // update mapping - mapping.set(leftY, leftP) - } else { - break - } - } - } - // find number of matching elements from right - for (;right + left < minCnt; right++) { - const rightY = yChildren[yChildCnt - right - 1] - const rightP = pContent.child(pChildCnt - right - 1) - if (mapping.get(rightY) !== rightP) { - if (equalYTypePNode(rightY, rightP)) { - // update mapping - mapping.set(rightY, rightP) - } else { - break - } - } - } - yDomFragment._y.transact(() => { - // try to compare and update - while (yChildCnt - left - right > 0 && pChildCnt - left - right > 0) { - const leftY = yChildren[left] - const leftP = pContent.child(left) - const rightY = yChildren[yChildCnt - right - 1] - const rightP = pContent.child(pChildCnt - right - 1) - if (leftY.constructor === YText && leftP.isText) { - if (!equalYTextPText(leftY, leftP)) { - // try to apply diff. Only if attrs don't match, delete insert - // TODO: use a single ytext to hold all following Prosemirror Text nodes - const pattrs = {} - leftP.marks.forEach(mark => { - if (mark.type.name !== 'ychange') { - pattrs[mark.type.name] = mark.attrs - } - }) - const delta = leftY.toDelta() - if (delta.length === 1 && delta[0].insert && equalAttrs(pattrs, delta[0].attributes || {})) { - const diff = simpleDiff(delta[0].insert, leftP.text) - leftY.delete(diff.pos, diff.remove) - leftY.insert(diff.pos, diff.insert) - } else { - yDomFragment.delete(left, 1) - yDomFragment.insert(left, [createTypeFromNode(leftP, mapping)]) - } - } - left += 1 - } else { - let updateLeft = matchNodeName(leftY, leftP) - let updateRight = matchNodeName(rightY, rightP) - if (updateLeft && updateRight) { - // decide which which element to update - const equalityLeft = computeChildEqualityFactor(leftY, leftP, mapping) - const equalityRight = computeChildEqualityFactor(rightY, rightP, mapping) - if (equalityLeft.foundMappedChild && !equalityRight.foundMappedChild) { - updateRight = false - } else if (!equalityLeft.foundMappedChild && equalityRight.foundMappedChild) { - updateLeft = false - } else if (equalityLeft.equalityFactor < equalityRight.equalityFactor) { - updateLeft = false - } else { - updateRight = false - } - } - if (updateLeft) { - updateYFragment(leftY, leftP, mapping) - left += 1 - } else if (updateRight) { - updateYFragment(rightY, rightP, mapping) - right += 1 - } else { - yDomFragment.delete(left, 1) - yDomFragment.insert(left, [createTypeFromNode(leftP, mapping)]) - left += 1 - } - } - } - const yDelLen = yChildCnt - left - right - if (yDelLen > 0) { - yDomFragment.delete(left, yDelLen) - } - if (left + right < pChildCnt) { - const ins = [] - for (let i = left; i < pChildCnt - right; i++) { - ins.push(createTypeFromNode(pContent.child(i), mapping)) - } - yDomFragment.insert(left, ins) - } - }) -} - -/** - * @function - * @param {YXmlElement} yElement - * @param {any} pNode Prosemirror Node - */ -const matchNodeName = (yElement, pNode) => yElement.nodeName === pNode.type.name.toUpperCase() diff --git a/bindings/quill.js b/bindings/quill.js deleted file mode 100644 index 369adcf5..00000000 --- a/bindings/quill.js +++ /dev/null @@ -1,71 +0,0 @@ -/** - * @module bindings/quill - */ - -import { createMutex } from '../lib/mutex.js' - -const typeObserver = function (event) { - const quill = this.target - // Force flush Quill changes. - quill.update('yjs') - this._mutualExclude(() => { - // Apply computed delta. - quill.updateContents(event.delta, 'yjs') - // Force flush Quill changes. Ignore applied changes. - quill.update('yjs') - }) -} - -const quillObserver = function (delta) { - this._mutualExclude(() => { - this.type.applyDelta(delta.ops) - }) -} - -/** - * A Binding that binds a YText type to a Quill editor. - * - * @example - * const quill = new Quill(document.createElement('div')) - * const type = y.define('quill', Y.Text) - * const binding = new Y.QuillBinding(quill, type) - * // Now modifications on the DOM will be reflected in the Type, and the other - * // way around! - */ -export class QuillBinding { - /** - * @param {YText} textType - * @param {Quill} quill - */ - constructor (textType, quill) { - // Binding handles textType as this.type and quill as this.target. - /** - * The Yjs type that is bound to `target` - * @type {YText} - */ - this.type = textType - /** - * The target that `type` is bound to. - * @type {Quill} - */ - this.target = quill - /** - * @private - */ - this._mutualExclude = createMutex() - // Set initial value. - quill.setContents(textType.toDelta(), 'yjs') - // Observers are handled by this class. - this._typeObserver = typeObserver.bind(this) - this._quillObserver = quillObserver.bind(this) - textType.observe(this._typeObserver) - quill.on('text-change', this._quillObserver) - } - destroy () { - // Remove everything that is handled by this class. - this.type.unobserve(this._typeObserver) - this.target.off('text-change', this._quillObserver) - this.type = null - this.target = null - } -} diff --git a/bindings/textarea.js b/bindings/textarea.js deleted file mode 100644 index 73c4c087..00000000 --- a/bindings/textarea.js +++ /dev/null @@ -1,72 +0,0 @@ -/** - * @module bindings/textarea - */ - -import { simpleDiff } from '../lib/diff.js' -import { getRelativePosition, fromRelativePosition } from '../utils/relativePosition.js' -import { createMutex } from '../lib/mutex.js' - -function typeObserver () { - this._mutualExclude(() => { - const textarea = this.target - const textType = this.type - const relativeStart = getRelativePosition(textType, textarea.selectionStart) - const relativeEnd = getRelativePosition(textType, textarea.selectionEnd) - textarea.value = textType.toString() - const start = fromRelativePosition(textType._y, relativeStart) - const end = fromRelativePosition(textType._y, relativeEnd) - textarea.setSelectionRange(start, end) - }) -} - -function domObserver () { - this._mutualExclude(() => { - let diff = simpleDiff(this.type.toString(), this.target.value) - this.type.delete(diff.pos, diff.remove) - this.type.insert(diff.pos, diff.insert) - }) -} - -/** - * A binding that binds a YText to a dom textarea. - * - * This binding is automatically destroyed when its parent is deleted. - * - * @example - * const textare = document.createElement('textarea') - * const type = y.define('textarea', Y.Text) - * const binding = new Y.QuillBinding(type, textarea) - * - */ -export class TextareaBinding { - constructor (textType, domTextarea) { - /** - * The Yjs type that is bound to `target` - * @type {Type} - */ - this.type = textType - /** - * The target that `type` is bound to. - * @type {*} - */ - this.target = domTextarea - /** - * @private - */ - this._mutualExclude = createMutex() - // set initial value - domTextarea.value = textType.toString() - // Observers are handled by this class - this._typeObserver = typeObserver.bind(this) - this._domObserver = domObserver.bind(this) - textType.observe(this._typeObserver) - domTextarea.addEventListener('input', this._domObserver) - } - destroy () { - // Remove everything that is handled by this class - this.type.unobserve(this._typeObserver) - this.target.unobserve(this._domObserver) - this.type = null - this.target = null - } -} diff --git a/examples/codemirror.html b/examples/codemirror.html index 0ac5d75f..cf29c4d1 100644 --- a/examples/codemirror.html +++ b/examples/codemirror.html @@ -41,7 +41,7 @@
- - - - - - - diff --git a/examples_all/ace/index.js b/examples_all/ace/index.js deleted file mode 100644 index 85487bc2..00000000 --- a/examples_all/ace/index.js +++ /dev/null @@ -1,17 +0,0 @@ -/* global Y, ace */ - -let y = new Y('ace-example', { - connector: { - name: 'websockets-client', - url: 'http://127.0.0.1:1234' - } -}) - -window.yAce = y - -// bind the textarea to a shared text element -var editor = ace.edit('aceContainer') -editor.setTheme('ace/theme/chrome') -editor.getSession().setMode('ace/mode/javascript') - -y.define('ace', Y.Text).bindAce(editor) diff --git a/examples_all/chat/index.html b/examples_all/chat/index.html deleted file mode 100644 index 228c7e75..00000000 --- a/examples_all/chat/index.html +++ /dev/null @@ -1,19 +0,0 @@ - - - - -
-
- - - -
- - - - - diff --git a/examples_all/chat/index.js b/examples_all/chat/index.js deleted file mode 100644 index eb0f1d03..00000000 --- a/examples_all/chat/index.js +++ /dev/null @@ -1,65 +0,0 @@ -/* global Y */ - -let y = new Y('chat-example', { - connector: { - name: 'websockets-client', - url: 'http://127.0.0.1:1234' - } -}) - -window.yChat = y - -let chatprotocol = y.define('chatprotocol', Y.Array) - -let chatcontainer = document.querySelector('#chat') - -// This functions inserts a message at the specified position in the DOM -const appendMessage = (message, position) => { - var p = document.createElement('p') - var uname = document.createElement('span') - uname.appendChild(document.createTextNode(message.username + ': ')) - p.appendChild(uname) - p.appendChild(document.createTextNode(message.message)) - chatcontainer.insertBefore(p, chatcontainer.children[position] || null) -} - -// This function makes sure that only 7 messages exist in the chat history. -// The rest is deleted -const cleanupChat = () => { - if (chatprotocol.length > 7) { - chatprotocol.delete(0, chatprotocol.length - 7) - } -} -cleanupChat() - -// Insert the initial content -chatprotocol.toArray().forEach(appendMessage) - -// whenever content changes, make sure to reflect the changes in the DOM -chatprotocol.observe(event => { - // concurrent insertions may result in a history > 7, so cleanup here - cleanupChat() - chatcontainer.innerHTML = '' - chatprotocol.toArray().forEach(appendMessage) -}) -document.querySelector('#chatform').onsubmit = function (event) { - // the form is submitted - var message = { - username: this.querySelector('[name=username]').value, - message: this.querySelector('[name=message]').value - } - if (message.username.length > 0 && message.message.length > 0) { - if (chatprotocol.length > 6) { - // If we are goint to insert the 8th element, make sure to delete first. - chatprotocol.delete(0) - } - // Here we insert a message in the shared chat type. - // This will call the observe function (see line 40) - // and reflect the change in the DOM - chatprotocol.push([message]) - this.querySelector('[name=message]').value = '' - } - // Do not send this form! - event.preventDefault() - return false -} diff --git a/examples_all/codemirror/index.html b/examples_all/codemirror/index.html deleted file mode 100644 index 2233240e..00000000 --- a/examples_all/codemirror/index.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - -
- - - - - - - - - - diff --git a/examples_all/codemirror/index.js b/examples_all/codemirror/index.js deleted file mode 100644 index ff216191..00000000 --- a/examples_all/codemirror/index.js +++ /dev/null @@ -1,16 +0,0 @@ -/* global Y, CodeMirror */ - -let y = new Y('codemirror-example', { - connector: { - name: 'websockets-client', - url: 'http://127.0.0.1:1234' - } -}) - -window.yCodeMirror = y - -var editor = CodeMirror(document.querySelector('#codeMirrorContainer'), { - mode: 'javascript', - lineNumbers: true -}) -y.define('codemirror', Y.Text).bindCodeMirror(editor) diff --git a/examples_all/drawing/index.html b/examples_all/drawing/index.html deleted file mode 100644 index a9dda2c8..00000000 --- a/examples_all/drawing/index.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - diff --git a/examples_all/drawing/index.js b/examples_all/drawing/index.js deleted file mode 100644 index 26d9c3f4..00000000 --- a/examples_all/drawing/index.js +++ /dev/null @@ -1,74 +0,0 @@ -/* globals Y, d3 */ - -let y = new Y('drawing-example', { - connector: { - name: 'websockets-client', - url: 'http://127.0.0.1:1234' - } -}) - -window.yDrawing = y -var drawing = y.define('drawing', Y.Array) -var renderPath = d3.svg.line() - .x(function (d) { return d[0] }) - .y(function (d) { return d[1] }) - .interpolate('basic') - -var svg = d3.select('#drawingCanvas') - .call(d3.behavior.drag() - .on('dragstart', dragstart) - .on('drag', drag) - .on('dragend', dragend)) - -// create line from a shared array object and update the line when the array changes -function drawLine (yarray) { - var line = svg.append('path').datum(yarray.toArray()) - line.attr('d', renderPath) - yarray.observe(function (event) { - line.remove() - line = svg.append('path').datum(yarray.toArray()) - line.attr('d', renderPath) - }) -} -// call drawLine every time an array is appended -drawing.observe(function (event) { - event.removedElements.forEach(function () { - // if one is deleted, all will be deleted!! - svg.selectAll('path').remove() - }) - event.addedElements.forEach(function (path) { - drawLine(path) - }) -}) -// draw all existing content -for (var i = 0; i < drawing.length; i++) { - drawLine(drawing.get(i)) -} - -// clear canvas on request -document.querySelector('#clearDrawingCanvas').onclick = function () { - drawing.delete(0, drawing.length) -} - -var sharedLine = null -function dragstart () { - drawing.insert(drawing.length, [Y.Array]) - sharedLine = drawing.get(drawing.length - 1) -} - -// After one dragged event is recognized, we ignore them for 33ms. -var ignoreDrag = null -function drag () { - if (sharedLine != null && ignoreDrag == null) { - ignoreDrag = window.setTimeout(function () { - ignoreDrag = null - }, 10) - sharedLine.push([d3.mouse(this)]) - } -} - -function dragend () { - sharedLine = null - window.clearTimeout(ignoreDrag) - ignoreDrag = null -} diff --git a/examples_all/html-editor-drawing-hook/index.html b/examples_all/html-editor-drawing-hook/index.html deleted file mode 100644 index 50166af5..00000000 --- a/examples_all/html-editor-drawing-hook/index.html +++ /dev/null @@ -1,36 +0,0 @@ - - - - - - - - - - - - diff --git a/examples_all/html-editor-drawing-hook/index.js b/examples_all/html-editor-drawing-hook/index.js deleted file mode 100644 index 7da324bc..00000000 --- a/examples_all/html-editor-drawing-hook/index.js +++ /dev/null @@ -1,134 +0,0 @@ -/* global Y, d3 */ - -const hooks = { - 'magic-drawing': { - fillType: function (dom, type) { - initDrawingBindings(type, dom) - }, - createDom: function (type) { - const dom = document.createElement('magic-drawing') - initDrawingBindings(type, dom) - return dom - } - } -} - -window.onload = function () { - window.domBinding = new Y.DomBinding(window.yXmlType, document.body, { hooks }) -} - -window.addMagicDrawing = function addMagicDrawing () { - let mt = document.createElement('magic-drawing') - mt.setAttribute('data-yjs-hook', 'magic-drawing') - document.body.append(mt) -} - -var renderPath = d3.svg.line() - .x(function (d) { return d[0] }) - .y(function (d) { return d[1] }) - .interpolate('basic') - -function initDrawingBindings (type, dom) { - dom.contentEditable = 'false' - dom.setAttribute('data-yjs-hook', 'magic-drawing') - var drawing = type.get('drawing') - if (drawing === undefined) { - drawing = type.set('drawing', new Y.Array()) - } - var canvas = dom.querySelector('.drawingCanvas') - if (canvas == null) { - canvas = document.createElementNS('http://www.w3.org/2000/svg', 'svg') - canvas.setAttribute('class', 'drawingCanvas') - canvas.setAttribute('viewbox', '0 0 100 100') - dom.insertBefore(canvas, null) - } - var clearDrawingButton = dom.querySelector('.clearDrawingButton') - if (clearDrawingButton == null) { - clearDrawingButton = document.createElement('button') - clearDrawingButton.setAttribute('type', 'button') - clearDrawingButton.setAttribute('class', 'clearDrawingButton') - clearDrawingButton.innerText = 'Clear Drawing' - dom.insertBefore(clearDrawingButton, null) - } - var svg = d3.select(canvas) - .call(d3.behavior.drag() - .on('dragstart', dragstart) - .on('drag', drag) - .on('dragend', dragend)) - // create line from a shared array object and update the line when the array changes - function drawLine (yarray, svg) { - var line = svg.append('path').datum(yarray.toArray()) - line.attr('d', renderPath) - yarray.observe(function (event) { - line.remove() - line = svg.append('path').datum(yarray.toArray()) - line.attr('d', renderPath) - }) - } - // call drawLine every time an array is appended - drawing.observe(function (event) { - event.removedElements.forEach(function () { - // if one is deleted, all will be deleted!! - svg.selectAll('path').remove() - }) - event.addedElements.forEach(function (path) { - drawLine(path, svg) - }) - }) - // draw all existing content - for (var i = 0; i < drawing.length; i++) { - drawLine(drawing.get(i), svg) - } - - // clear canvas on request - clearDrawingButton.onclick = function () { - drawing.delete(0, drawing.length) - } - - var sharedLine = null - function dragstart () { - drawing.insert(drawing.length, [Y.Array]) - sharedLine = drawing.get(drawing.length - 1) - } - - // After one dragged event is recognized, we ignore them for 33ms. - var ignoreDrag = null - function drag () { - if (sharedLine != null && ignoreDrag == null) { - ignoreDrag = window.setTimeout(function () { - ignoreDrag = null - }, 10) - sharedLine.push([d3.mouse(this)]) - } - } - - function dragend () { - sharedLine = null - window.clearTimeout(ignoreDrag) - ignoreDrag = null - } -} - -let y = new Y('html-editor-drawing-hook-example', { - connector: { - name: 'websockets-client', - url: 'http://127.0.0.1:1234' - } -}) - -window.yXml = y -window.yXmlType = y.define('xml', Y.XmlFragment) -window.undoManager = new Y.utils.UndoManager(window.yXmlType, { - captureTimeout: 500 -}) - -document.onkeydown = function interceptUndoRedo (e) { - if (e.keyCode === 90 && e.metaKey) { - if (!e.shiftKey) { - window.undoManager.undo() - } else { - window.undoManager.redo() - } - e.preventDefault() - } -} diff --git a/examples_all/html-editor/index.html b/examples_all/html-editor/index.html deleted file mode 100644 index 179c64c9..00000000 --- a/examples_all/html-editor/index.html +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - -
- - diff --git a/examples_all/html-editor/index.js b/examples_all/html-editor/index.js deleted file mode 100644 index 6e49544d..00000000 --- a/examples_all/html-editor/index.js +++ /dev/null @@ -1,77 +0,0 @@ - -import YWebsocketsConnector from '../../src/Connectors/WebsocketsConnector/WebsocketsConnector.js' -import Y from '../../src/Y.js' -import DomBinding from '../../bindings/DomBinding/DomBinding.js' -import UndoManager from '../../src/Util/UndoManager.js' -import YXmlFragment from '../../src/Types/YXml/YXmlFragment.js' -import YXmlText from '../../src/Types/YXml/YXmlText.js' -import YXmlElement from '../../src/Types/YXml/YXmlElement.js' -import YIndexdDBPersistence from '../../src/Persistences/IndexedDBPersistence.js' - -const connector = new YWebsocketsConnector() -const persistence = new YIndexdDBPersistence() - -const roomInput = document.querySelector('#room') - -let currentRoomName = null -let y = null -let domBinding = null - -function setRoomName (roomName) { - if (currentRoomName !== roomName) { - console.log(`change room: "${roomName}"`) - roomInput.value = roomName - currentRoomName = roomName - location.hash = '#' + roomName - if (y !== null) { - domBinding.destroy() - } - - const room = connector._rooms.get(roomName) - if (room !== undefined) { - y = room.y - } else { - y = new Y(roomName, null, null, { gc: true }) - persistence.connectY(roomName, y).then(() => { - // connect after persisted content was applied to y - // If we don't wait for persistence, the other peer will send all data, waisting - // network bandwidth.. - connector.connectY(roomName, y) - }) - window.y = y - } - - window.y = y - window.yXmlType = y.define('xml', YXmlFragment) - - domBinding = new DomBinding(window.yXmlType, document.querySelector('#content'), { scrollingElement: document.scrollingElement }) - } -} -window.setRoomName = setRoomName - -window.createRooms = function (i = 0) { - setInterval(function () { - setRoomName(i + '') - i++ - const nodes = [] - for (let j = 0; j < 100; j++) { - const node = new YXmlElement('p') - node.insert(0, [new YXmlText(`This is the ${i}th paragraph of room ${i}`)]) - nodes.push(node) - } - y.share.xml.insert(0, nodes) - }, 100) -} - -connector.syncPersistence(persistence) - -window.connector = connector -window.persistence = persistence - -window.onload = function () { - setRoomName((location.hash || '#default').slice(1)) - roomInput.addEventListener('input', e => { - const roomName = e.target.value - setRoomName(roomName) - }) -} diff --git a/examples_all/indexeddb/index.html b/examples_all/indexeddb/index.html deleted file mode 100644 index 8b3a4746..00000000 --- a/examples_all/indexeddb/index.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - - -
- - - - - - - - - diff --git a/examples_all/indexeddb/index.js b/examples_all/indexeddb/index.js deleted file mode 100644 index da3947f7..00000000 --- a/examples_all/indexeddb/index.js +++ /dev/null @@ -1,19 +0,0 @@ -/* global Y, CodeMirror */ - -const persistence = new Y.IndexedDB() -const connector = { - connector: { - name: 'websockets-client', - room: 'codemirror-example' - } -} - -const y = new Y('codemirror-example', connector, persistence) -window.yCodeMirror = y - -var editor = CodeMirror(document.querySelector('#codeMirrorContainer'), { - mode: 'javascript', - lineNumbers: true -}) - -y.define('codemirror', Y.Text).bindCodeMirror(editor) diff --git a/examples_all/jigsaw/index.html b/examples_all/jigsaw/index.html deleted file mode 100644 index 6030ca53..00000000 --- a/examples_all/jigsaw/index.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/examples_all/jigsaw/index.js b/examples_all/jigsaw/index.js deleted file mode 100644 index 2142757d..00000000 --- a/examples_all/jigsaw/index.js +++ /dev/null @@ -1,67 +0,0 @@ -/* global Y, d3 */ - -let y = new Y('jigsaw-example', { - connector: { - name: 'websockets-client', - url: 'http://127.0.0.1:1234' - } -}) - -let jigsaw = y.define('jigsaw', Y.Map) -window.yJigsaw = y - -var origin // mouse start position - translation of piece -var drag = d3.behavior.drag() - .on('dragstart', function (params) { - // get the translation of the element - var translation = d3 - .select(this) - .attr('transform') - .slice(10, -1) - .split(',') - .map(Number) - // mouse coordinates - var mouse = d3.mouse(this.parentNode) - origin = { - x: mouse[0] - translation[0], - y: mouse[1] - translation[1] - } - }) - .on('drag', function () { - var mouse = d3.mouse(this.parentNode) - var x = mouse[0] - origin.x // =^= mouse - mouse at dragstart + translation at dragstart - var y = mouse[1] - origin.y - d3.select(this).attr('transform', 'translate(' + x + ',' + y + ')') - }) - .on('dragend', function (piece, i) { - // save the current translation of the puzzle piece - var mouse = d3.mouse(this.parentNode) - var x = mouse[0] - origin.x - var y = mouse[1] - origin.y - jigsaw.set(piece, {x: x, y: y}) - }) - -var data = ['piece1', 'piece2', 'piece3', 'piece4'] -var pieces = d3.select(document.querySelector('#puzzle-example')).selectAll('path').data(data) - -pieces - .classed('draggable', true) - .attr('transform', function (piece) { - var translation = piece.get('translation') || {x: 0, y: 0} - return 'translate(' + translation.x + ',' + translation.y + ')' - }).call(drag) - -data.forEach(function (piece) { - jigsaw.observe(function () { - // whenever a property of a piece changes, update the translation of the pieces - pieces - .transition() - .attr('transform', function (piece) { - var translation = piece.get(piece) - if (translation == null || typeof translation.x !== 'number' || typeof translation.y !== 'number') { - translation = { x: 0, y: 0 } - } - return 'translate(' + translation.x + ',' + translation.y + ')' - }) - }) -}) diff --git a/examples_all/monaco/index.html b/examples_all/monaco/index.html deleted file mode 100644 index c4ef9e71..00000000 --- a/examples_all/monaco/index.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - -
- - - - - - - diff --git a/examples_all/monaco/index.js b/examples_all/monaco/index.js deleted file mode 100644 index 3c208f39..00000000 --- a/examples_all/monaco/index.js +++ /dev/null @@ -1,22 +0,0 @@ -/* global Y, monaco */ - -require.config({ paths: { 'vs': '../node_modules/monaco-editor/min/vs' } }) - -let y = new Y('monaco-example', { - connector: { - name: 'websockets-client', - url: 'http://127.0.0.1:1234' - } -}) - -require(['vs/editor/editor.main'], function () { - window.yMonaco = y - - // Create Monaco editor - var editor = monaco.editor.create(document.getElementById('monacoContainer'), { - language: 'javascript' - }) - - // Bind to y.share.monaco - y.define('monaco', Y.Text).bindMonaco(editor) -}) diff --git a/examples_all/notes/index.html b/examples_all/notes/index.html deleted file mode 100644 index d7f3379a..00000000 --- a/examples_all/notes/index.html +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - -
-

-
-
- - \ No newline at end of file diff --git a/examples_all/notes/index.js b/examples_all/notes/index.js deleted file mode 100644 index 74f31c6e..00000000 --- a/examples_all/notes/index.js +++ /dev/null @@ -1,132 +0,0 @@ -/* eslint-env browser */ - -import { createYdbClient } from '../../YdbClient/index.js' -import Y from '../../src/Y.dist.js' -import * as ydb from '../../YdbClient/YdbClient.js' -import DomBinding from '../../bindings/DomBinding/DomBinding.js' - -const uuidv4 = () => 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, c => { - const r = Math.random() * 16 | 0 - return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16) -}) - -createYdbClient('ws://localhost:8899/ws').then(ydbclient => { - const y = ydbclient.getY('notelist') - let ynotelist = y.define('notelist', Y.Array) - window.ynotelist = ynotelist - const domNoteList = document.querySelector('.notelist') - - // utils - const addEventListener = (element, eventname, f) => element.addEventListener(eventname, f) - - // create note button - const createNoteButton = event => { - ynotelist.insert(0, [{ - guid: uuidv4(), - title: 'Note #' + ynotelist.length - }]) - } - addEventListener(document.querySelector('#createNoteButton'), 'click', createNoteButton) - window.createNote = createNoteButton - window.createNotes = n => { - y.transact(() => { - for (let i = 0; i < n; i++) { - createNoteButton() - } - }) - } - - // clear note list function - window.clearNotes = () => ynotelist.delete(0, ynotelist.length) - - // update editor and editor title - let domBinding = null - const updateEditor = () => { - domNoteList.querySelectorAll('a').forEach(a => a.classList.remove('selected')) - const domNote = document.querySelector('.notelist').querySelector(`[href="${location.hash}"]`) - if (domNote !== null) { - domNote.classList.add('selected') - const note = ynotelist.toArray().find(note => note.guid === location.hash.slice(1)) - if (note !== undefined) { - const ydoc = ydbclient.getY(note.guid) - const ycontent = ydoc.define('content', Y.XmlFragment) - if (domBinding !== null) { - domBinding.destroy() - } - domBinding = new DomBinding(ycontent, document.querySelector('#editor')) - document.querySelector('#headline').innerText = note.title - document.querySelector('#editor').focus() - } - } - } - - // listen to url-hash changes - addEventListener(window, 'hashchange', updateEditor) - updateEditor() - - const styleSyncedState = (div, noteSyncedState) => { - let classes = [] - if (noteSyncedState.persisted) { - classes.push('persisted') - } else { - if (noteSyncedState.upsynced) { - classes.push('upsynced') - } else { - classes.push('noupsynced') - } - if (noteSyncedState.downsynced) { - classes.push('downsynced') - } else { - classes.push('nodownsynced') - } - } - div.setAttribute('class', classes.join(' ')) - } - - ydbclient.on('syncstate', event => event.updated.forEach((state, room) => { - const a = document.querySelector(`[href="#${room}"]`) - if (a !== null) { - styleSyncedState(a.firstChild, state) - } - })) - - // render note list - const renderNoteList = (elementList, insertRef = domNoteList.firstChild) => { - const fragment = document.createDocumentFragment() - const addNow = elementList.splice(0, 100) - addNow.forEach(note => { - const a = document.createElement('a') - const div = document.createElement('div') - a.insertBefore(div, null) - a.setAttribute('href', '#' + note.guid) - div.innerText = note.title - styleSyncedState(div, ydbclient.getRoomState(note.guid)) - fragment.insertBefore(a, null) - }) - if (domBinding == null) { - updateEditor() - } - domNoteList.insertBefore(fragment, insertRef) - if (elementList.length > 0) { - setTimeout(() => renderNoteList(elementList, insertRef), 100) - } - } - { - const notelist = ynotelist.toArray() - if (notelist.length > 0) { - renderNoteList(notelist) - ydb.subscribeRooms(ydbclient, notelist.map(note => note.guid)) - } - } - ynotelist.observe(event => { - const addedNotes = [] - event.addedElements.forEach(itemJson => itemJson._content.forEach(json => addedNotes.push(json))) - renderNoteList(addedNotes.slice().reverse()) // renderNoteList modifies addedNotes, so first make a copy of it - setTimeout(() => { - ydb.subscribeRooms(ydbclient, addedNotes.map(note => note.guid)) - }, 200) - if (domBinding === null) { - updateEditor() - } - }) -}) diff --git a/examples_all/notes/style.css b/examples_all/notes/style.css deleted file mode 100644 index c729f2ec..00000000 --- a/examples_all/notes/style.css +++ /dev/null @@ -1,100 +0,0 @@ -.sidebar { - height: 100%; /* Full-height: remove this if you want "auto" height */ - width: 180px; /* Set the width of the sidebar */ - position: fixed; /* Fixed Sidebar (stay in place on scroll) */ - z-index: 1; /* Stay on top */ - top: 0; /* Stay at the top */ - left: 0; - background-color: #111; /* Black */ - overflow-x: hidden; /* Disable horizontal scroll */ - padding-top: 20px; - color: #50abff; -} - -#createNoteButton { - padding-left: .5em; - padding-top: .5em; - padding-bottom: .7em; - margin: 0; - cursor: pointer; -} - -.notelist > a { - padding: 6px 8px 6px 16px; - text-decoration: none; - font-size: 13px; - color: #818181; - display: block; -} - -.notelist > a.selected { - border-style: outset; -} - -.notelist > a > div { - position: relative; - display: inline; -} - -/* When you mouse over the navigation links, change their color */ -.sidebar a:hover { - color: #f1f1f1; -} - -/* Style page content */ -.main { - margin-left: 180px; /* Same as the width of the sidebar */ - padding: 0px 10px; -} - -/* On smaller screens, where height is less than 450px, change the style of the sidebar (less padding and a smaller font size) */ -@media screen and (max-height: 450px) { - .sidebar {padding-top: 15px;} - .sidebar a {font-size: 18px;} -} - -#editor { - min-height: 400px; -} - -[contenteditable]:focus { - outline: 0px solid transparent; -} - -.persisted::before { - content: "✔"; - color: green; - position: absolute; - right: -14px; - top: 0px; -} - -.upsynced::before { - content: "↑"; - color: green; - position: absolute; - right: -14px; - top: 0px; -} - -.noupsynced::before { - content: "↑"; - color: red; - position: absolute; - right: -14px; - top: 0px; -} -.downsynced::after { - content: "↓"; - color: green; - position: absolute; - right: -22px; - top: 0px; -} -.nodownsynced::after { - content: "↓"; - color: red; - position: absolute; - right: -22px; - top: 0px; -} \ No newline at end of file diff --git a/examples_all/quill-cursors/index.html b/examples_all/quill-cursors/index.html deleted file mode 100644 index 1e63961c..00000000 --- a/examples_all/quill-cursors/index.html +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - -
-
-
-
- - - diff --git a/examples_all/quill-cursors/index.js b/examples_all/quill-cursors/index.js deleted file mode 100644 index 2a767bf1..00000000 --- a/examples_all/quill-cursors/index.js +++ /dev/null @@ -1,78 +0,0 @@ -/* global Y, Quill, QuillCursors */ - -Quill.register('modules/cursors', QuillCursors) - -let y = new Y('quill-0', { - connector: { - name: 'websockets-client', - url: 'http://127.0.0.1:1234' - } -}) -let users = y.define('users', Y.Array) -let myUserInfo = new Y.Map() -myUserInfo.set('name', 'dada') -myUserInfo.set('color', 'red') -users.push([myUserInfo]) - -let quill = new Quill('#quill-container', { - modules: { - toolbar: [ - [{ header: [1, 2, false] }], - ['bold', 'italic', 'underline'], - ['image', 'code-block'], - [{ color: [] }, { background: [] }], // Snow theme fills in values - [{ script: 'sub' }, { script: 'super' }], - ['link', 'image'], - ['link', 'code-block'], - [{ list: 'ordered' }, { list: 'bullet' }] - ], - cursors: { - hideDelay: 500 - } - }, - placeholder: 'Compose an epic...', - theme: 'snow' // or 'bubble' -}) - -let cursors = quill.getModule('cursors') - -const drawCursors = () => { - cursors.clearCursors() - users.map((user, userId) => { - if (user !== myUserInfo) { - let relativeRange = user.get('range') - let lastUpdated = new Date(user.get('last updated')).getTime() - if (lastUpdated != null && new Date().getTime() - lastUpdated < 20000 && relativeRange != null) { - let start = Y.utils.fromRelativePosition(y, relativeRange.start).offset - let end = Y.utils.fromRelativePosition(y, relativeRange.end).offset - let range = { index: start, length: end - start } - cursors.setCursor(userId + '', range, user.get('name'), user.get('color')) - } - } - }) -} - -users.observeDeep(drawCursors) -drawCursors() - -quill.on('selection-change', function (range) { - if (range != null) { - myUserInfo.set('range', { - start: Y.utils.getRelativePosition(yText, range.index), - end: Y.utils.getRelativePosition(yText, range.index + range.length) - }) - } else { - myUserInfo.delete('range') - } - myUserInfo.set('last updated', new Date().toString()) -}) - -let yText = y.define('quill', Y.Text) -let quillBinding = new Y.QuillBinding(yText, quill) - -window.quillBinding = quillBinding -window.yText = yText -window.y = y -window.quill = quill -window.users = users -window.cursors = cursors diff --git a/examples_all/serviceworker/index.html b/examples_all/serviceworker/index.html deleted file mode 100644 index d0aea5a7..00000000 --- a/examples_all/serviceworker/index.html +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - -
-
-
-
- - - - - - - - - diff --git a/examples_all/serviceworker/index.js b/examples_all/serviceworker/index.js deleted file mode 100644 index c2d50b31..00000000 --- a/examples_all/serviceworker/index.js +++ /dev/null @@ -1,49 +0,0 @@ -/* global Y, Quill */ - -// register yjs service worker -if ('serviceWorker' in navigator) { - // Register service worker - // it is important to copy yjs-sw-template to the root directory! - navigator.serviceWorker.register('./yjs-sw-template.js').then(function (reg) { - console.log('Yjs service worker registration succeeded. Scope is ' + reg.scope) - }).catch(function (err) { - console.error('Yjs service worker registration failed with error ' + err) - }) -} - -// initialize a shared object. This function call returns a promise! -Y({ - db: { - name: 'memory' - }, - connector: { - name: 'serviceworker', - room: 'ServiceWorkerExample2' - }, - sourceDir: '/bower_components', - share: { - richtext: 'Richtext' // y.share.richtext is of type Y.Richtext - } -}).then(function (y) { - window.yServiceWorker = y - - // create quill element - window.quill = new Quill('#quill', { - modules: { - formula: true, - syntax: true, - toolbar: [ - [{ size: ['small', false, 'large', 'huge'] }], - ['bold', 'italic', 'underline'], - [{ color: [] }, { background: [] }], // Snow theme fills in values - [{ script: 'sub' }, { script: 'super' }], - ['link', 'image'], - ['link', 'code-block'], - [{ list: 'ordered' }] - ] - }, - theme: 'snow' - }) - // bind quill to richtext type - y.share.richtext.bind(window.quill) -}) diff --git a/examples_all/serviceworker/yjs-sw-template.js b/examples_all/serviceworker/yjs-sw-template.js deleted file mode 100644 index 0fc8dd13..00000000 --- a/examples_all/serviceworker/yjs-sw-template.js +++ /dev/null @@ -1,22 +0,0 @@ -/* eslint-env worker */ - -// copy and modify this file - -self.DBConfig = { - name: 'indexeddb' -} -self.ConnectorConfig = { - name: 'websockets-client', - // url: '..', - options: { - jsonp: false - } -} - -importScripts( - '/bower_components/yjs/y.js', - '/bower_components/y-memory/y-memory.js', - '/bower_components/y-indexeddb/y-indexeddb.js', - '/bower_components/y-websockets-client/y-websockets-client.js', - '/bower_components/y-serviceworker/yjs-sw-include.js' -) diff --git a/index.js b/index.js deleted file mode 100644 index 7ed6866e..00000000 --- a/index.js +++ /dev/null @@ -1,57 +0,0 @@ - -import './structs/Item.js' -import { Delete } from './structs/Delete.js' -import { ItemJSON } from './structs/ItemJSON.js' -import { ItemString } from './structs/ItemString.js' -import { ItemFormat } from './structs/ItemFormat.js' -import { ItemEmbed } from './structs/ItemEmbed.js' -import { ItemBinary } from './structs/ItemBinary.js' -import { GC } from './structs/GC.js' - -import { YArray } from './types/YArray.js' -import { YMap } from './types/YMap.js' -import { YText } from './types/YText.js' -import { YXmlText } from './types/YXmlText.js' -import { YXmlHook } from './types/YXmlHook.js' -import { YXmlElement, YXmlFragment } from './types/YXmlElement.js' - -import { registerStruct } from './utils/structReferences.js' - -import * as decoding from './lib/decoding.js' -import * as encoding from './lib/encoding.js' -import * as awarenessProtocol from './protocols/awareness.js' -import * as syncProtocol from './protocols/sync.js' -import * as authProtocol from './protocols/auth.js' - -export { decoding, encoding, awarenessProtocol, syncProtocol, authProtocol } - -export { Y } from './utils/Y.js' -export { UndoManager } from './utils/UndoManager.js' -export { Transaction } from './utils/Transaction.js' - -export { YArray as Array } from './types/YArray.js' -export { YMap as Map } from './types/YMap.js' -export { YText as Text } from './types/YText.js' -export { YXmlText as XmlText } from './types/YXmlText.js' -export { YXmlHook as XmlHook } from './types/YXmlHook.js' -export { YXmlElement as XmlElement, YXmlFragment as XmlFragment } from './types/YXmlElement.js' - -export { getRelativePosition, fromRelativePosition } from './utils/relativePosition.js' -export { registerStruct } from './utils/structReferences.js' -export * from './lib/mutex.js' - -registerStruct(0, GC) -registerStruct(1, ItemJSON) -registerStruct(2, ItemString) -registerStruct(3, ItemFormat) -registerStruct(4, Delete) - -registerStruct(5, YArray) -registerStruct(6, YMap) -registerStruct(7, YText) -registerStruct(8, YXmlFragment) -registerStruct(9, YXmlElement) -registerStruct(10, YXmlText) -registerStruct(11, YXmlHook) -registerStruct(12, ItemEmbed) -registerStruct(13, ItemBinary) diff --git a/lib/NamedEventHandler.js b/lib/NamedEventHandler.js deleted file mode 100644 index e1d10bbd..00000000 --- a/lib/NamedEventHandler.js +++ /dev/null @@ -1,113 +0,0 @@ - -/** - * Handles named events. - */ -export class NamedEventHandler { - constructor () { - this._eventListener = new Map() - this._stateListener = new Map() - } - - /** - * @private - * Returns all listeners that listen to a specified name. - * - * @param {String} name The query event name. - */ - _getListener (name) { - let listeners = this._eventListener.get(name) - if (listeners === undefined) { - listeners = { - once: new Set(), - on: new Set() - } - this._eventListener.set(name, listeners) - } - return listeners - } - - /** - * Adds a named event listener. The listener is removed after it has been - * called once. - * - * @param {String} name The event name to listen to. - * @param {Function} f The function that is executed when the event is fired. - */ - once (name, f) { - let listeners = this._getListener(name) - listeners.once.add(f) - } - - /** - * Adds a named event listener. - * - * @param {String} name The event name to listen to. - * @param {Function} f The function that is executed when the event is fired. - */ - on (name, f) { - let listeners = this._getListener(name) - listeners.on.add(f) - } - - /** - * @private - * Init the saved state for an event name. - */ - _initStateListener (name) { - let state = this._stateListener.get(name) - if (state === undefined) { - state = {} - state.promise = new Promise(resolve => { - state.resolve = resolve - }) - this._stateListener.set(name, state) - } - return state - } - - /** - * Returns a Promise that is resolved when the event name is called. - * The Promise is immediately resolved when the event name was called in the - * past. - */ - when (name) { - return this._initStateListener(name).promise - } - - /** - * Remove an event listener that was registered with either - * {@link EventHandler#on} or {@link EventHandler#once}. - */ - off (name, f) { - if (name == null || f == null) { - throw new Error('You must specify event name and function!') - } - const listener = this._eventListener.get(name) - if (listener !== undefined) { - listener.on.delete(f) - listener.once.delete(f) - } - } - - /** - * Emit a named event. All registered event listeners that listen to the - * specified name will receive the event. - * - * @param {String} name The event name. - * @param {Array} args The arguments that are applied to the event listener. - */ - emit (name, ...args) { - this._initStateListener(name).resolve() - const listener = this._eventListener.get(name) - if (listener !== undefined) { - listener.on.forEach(f => f.apply(null, args)) - listener.once.forEach(f => f.apply(null, args)) - listener.once = new Set() - } else if (name === 'error') { - console.error(args[0]) - } - } - destroy () { - this._eventListener = null - } -} diff --git a/lib/Tree.js b/lib/Tree.js deleted file mode 100644 index f189fe74..00000000 --- a/lib/Tree.js +++ /dev/null @@ -1,468 +0,0 @@ -/** - * @module tree - */ - -const rotate = (tree, parent, newParent, n) => { - if (parent === null) { - tree.root = newParent - newParent._parent = null - } else if (parent.left === n) { - parent.left = newParent - } else if (parent.right === n) { - parent.right = newParent - } else { - throw new Error('The elements are wrongly connected!') - } -} - -class N { - // A created node is always red! - constructor (val) { - this.val = val - this.color = true - this._left = null - this._right = null - this._parent = null - } - isRed () { return this.color } - isBlack () { return !this.color } - redden () { this.color = true; return this } - blacken () { this.color = false; return this } - get grandparent () { - return this.parent.parent - } - get parent () { - return this._parent - } - get sibling () { - return (this === this.parent.left) - ? this.parent.right : this.parent.left - } - get left () { - return this._left - } - get right () { - return this._right - } - set left (n) { - if (n !== null) { - n._parent = this - } - this._left = n - } - set right (n) { - if (n !== null) { - n._parent = this - } - this._right = n - } - rotateLeft (tree) { - const parent = this.parent - const newParent = this.right - const newRight = this.right.left - newParent.left = this - this.right = newRight - rotate(tree, parent, newParent, this) - } - next () { - if (this.right !== null) { - // search the most left node in the right tree - var o = this.right - while (o.left !== null) { - o = o.left - } - return o - } else { - var p = this - while (p.parent !== null && p !== p.parent.left) { - p = p.parent - } - return p.parent - } - } - prev () { - if (this.left !== null) { - // search the most right node in the left tree - var o = this.left - while (o.right !== null) { - o = o.right - } - return o - } else { - var p = this - while (p.parent !== null && p !== p.parent.right) { - p = p.parent - } - return p.parent - } - } - rotateRight (tree) { - const parent = this.parent - const newParent = this.left - const newLeft = this.left.right - newParent.right = this - this.left = newLeft - rotate(tree, parent, newParent, this) - } - getUncle () { - // we can assume that grandparent exists when this is called! - if (this.parent === this.parent.parent.left) { - return this.parent.parent.right - } else { - return this.parent.parent.left - } - } -} - -const isBlack = node => - node !== null ? node.isBlack() : true - -const isRed = (node) => - node !== null ? node.isRed() : false - -/* - * This is a Red Black Tree implementation - */ -export class Tree { - constructor () { - this.root = null - this.length = 0 - } - findNext (id) { - var nextID = id.clone() - nextID.clock += 1 - return this.findWithLowerBound(nextID) - } - findPrev (id) { - let prevID = id.clone() - prevID.clock -= 1 - return this.findWithUpperBound(prevID) - } - findNodeWithLowerBound (from) { - var o = this.root - if (o === null) { - return null - } else { - while (true) { - if (from === null || (from.lessThan(o.val._id) && o.left !== null)) { - // o is included in the bound - // try to find an element that is closer to the bound - o = o.left - } else if (from !== null && o.val._id.lessThan(from)) { - // o is not within the bound, maybe one of the right elements is.. - if (o.right !== null) { - o = o.right - } else { - // there is no right element. Search for the next bigger element, - // this should be within the bounds - return o.next() - } - } else { - return o - } - } - } - } - findNodeWithUpperBound (to) { - if (to === void 0) { - throw new Error('You must define from!') - } - var o = this.root - if (o === null) { - return null - } else { - while (true) { - if ((to === null || o.val._id.lessThan(to)) && o.right !== null) { - // o is included in the bound - // try to find an element that is closer to the bound - o = o.right - } else if (to !== null && to.lessThan(o.val._id)) { - // o is not within the bound, maybe one of the left elements is.. - if (o.left !== null) { - o = o.left - } else { - // there is no left element. Search for the prev smaller element, - // this should be within the bounds - return o.prev() - } - } else { - return o - } - } - } - } - findSmallestNode () { - var o = this.root - while (o != null && o.left != null) { - o = o.left - } - return o - } - findWithLowerBound (from) { - var n = this.findNodeWithLowerBound(from) - return n == null ? null : n.val - } - findWithUpperBound (to) { - var n = this.findNodeWithUpperBound(to) - return n == null ? null : n.val - } - iterate (from, to, f) { - var o - if (from === null) { - o = this.findSmallestNode() - } else { - o = this.findNodeWithLowerBound(from) - } - while ( - o !== null && - ( - to === null || // eslint-disable-line no-unmodified-loop-condition - o.val._id.lessThan(to) || - o.val._id.equals(to) - ) - ) { - f(o.val) - o = o.next() - } - } - find (id) { - let n = this.findNode(id) - if (n !== null) { - return n.val - } else { - return null - } - } - findNode (id) { - var o = this.root - if (o === null) { - return null - } else { - while (true) { - if (o === null) { - return null - } - if (id.lessThan(o.val._id)) { - o = o.left - } else if (o.val._id.lessThan(id)) { - o = o.right - } else { - return o - } - } - } - } - delete (id) { - var d = this.findNode(id) - if (d == null) { - // throw new Error('Element does not exist!') - return - } - this.length-- - if (d.left !== null && d.right !== null) { - // switch d with the greates element in the left subtree. - // o should have at most one child. - var o = d.left - // find - while (o.right !== null) { - o = o.right - } - // switch - d.val = o.val - d = o - } - // d has at most one child - // let n be the node that replaces d - var isFakeChild - var child = d.left || d.right - if (child === null) { - isFakeChild = true - child = new N(null) - child.blacken() - d.right = child - } else { - isFakeChild = false - } - - if (d.parent === null) { - if (!isFakeChild) { - this.root = child - child.blacken() - child._parent = null - } else { - this.root = null - } - return - } else if (d.parent.left === d) { - d.parent.left = child - } else if (d.parent.right === d) { - d.parent.right = child - } else { - throw new Error('Impossible!') - } - if (d.isBlack()) { - if (child.isRed()) { - child.blacken() - } else { - this._fixDelete(child) - } - } - this.root.blacken() - if (isFakeChild) { - if (child.parent.left === child) { - child.parent.left = null - } else if (child.parent.right === child) { - child.parent.right = null - } else { - throw new Error('Impossible #3') - } - } - } - _fixDelete (n) { - if (n.parent === null) { - // this can only be called after the first iteration of fixDelete. - return - } - // d was already replaced by the child - // d is not the root - // d and child are black - var sibling = n.sibling - if (isRed(sibling)) { - // make sibling the grandfather - n.parent.redden() - sibling.blacken() - if (n === n.parent.left) { - n.parent.rotateLeft(this) - } else if (n === n.parent.right) { - n.parent.rotateRight(this) - } else { - throw new Error('Impossible #2') - } - sibling = n.sibling - } - // parent, sibling, and children of n are black - if (n.parent.isBlack() && - sibling.isBlack() && - isBlack(sibling.left) && - isBlack(sibling.right) - ) { - sibling.redden() - this._fixDelete(n.parent) - } else if (n.parent.isRed() && - sibling.isBlack() && - isBlack(sibling.left) && - isBlack(sibling.right) - ) { - sibling.redden() - n.parent.blacken() - } else { - if (n === n.parent.left && - sibling.isBlack() && - isRed(sibling.left) && - isBlack(sibling.right) - ) { - sibling.redden() - sibling.left.blacken() - sibling.rotateRight(this) - sibling = n.sibling - } else if (n === n.parent.right && - sibling.isBlack() && - isRed(sibling.right) && - isBlack(sibling.left) - ) { - sibling.redden() - sibling.right.blacken() - sibling.rotateLeft(this) - sibling = n.sibling - } - sibling.color = n.parent.color - n.parent.blacken() - if (n === n.parent.left) { - sibling.right.blacken() - n.parent.rotateLeft(this) - } else { - sibling.left.blacken() - n.parent.rotateRight(this) - } - } - } - put (v) { - var node = new N(v) - if (this.root !== null) { - var p = this.root // p abbrev. parent - while (true) { - if (node.val._id.lessThan(p.val._id)) { - if (p.left === null) { - p.left = node - break - } else { - p = p.left - } - } else if (p.val._id.lessThan(node.val._id)) { - if (p.right === null) { - p.right = node - break - } else { - p = p.right - } - } else { - p.val = node.val - return p - } - } - this._fixInsert(node) - } else { - this.root = node - } - this.length++ - this.root.blacken() - return node - } - _fixInsert (n) { - if (n.parent === null) { - n.blacken() - return - } else if (n.parent.isBlack()) { - return - } - var uncle = n.getUncle() - if (uncle !== null && uncle.isRed()) { - // Note: parent: red, uncle: red - n.parent.blacken() - uncle.blacken() - n.grandparent.redden() - this._fixInsert(n.grandparent) - } else { - // Note: parent: red, uncle: black or null - // Now we transform the tree in such a way that - // either of these holds: - // 1) grandparent.left.isRed - // and grandparent.left.left.isRed - // 2) grandparent.right.isRed - // and grandparent.right.right.isRed - if (n === n.parent.right && n.parent === n.grandparent.left) { - n.parent.rotateLeft(this) - // Since we rotated and want to use the previous - // cases, we need to set n in such a way that - // n.parent.isRed again - n = n.left - } else if (n === n.parent.left && n.parent === n.grandparent.right) { - n.parent.rotateRight(this) - // see above - n = n.right - } - // Case 1) or 2) hold from here on. - // Now traverse grandparent, make parent a black node - // on the highest level which holds two red nodes. - n.parent.blacken() - n.grandparent.redden() - if (n === n.parent.left) { - // Case 1 - n.grandparent.rotateRight(this) - } else { - // Case 2 - n.grandparent.rotateLeft(this) - } - } - } -} diff --git a/lib/binary.js b/lib/binary.js deleted file mode 100644 index 645f5f87..00000000 --- a/lib/binary.js +++ /dev/null @@ -1,40 +0,0 @@ -/* eslint-env browser */ - -/** - * @module binary - */ - -import * as string from './string.js' -import * as globals from './globals.js' - -export const BITS32 = 0xFFFFFFFF -export const BITS21 = (1 << 21) - 1 -export const BITS16 = (1 << 16) - 1 - -export const BIT26 = 1 << 26 -export const BIT32 = 1 << 32 - -/** - * @param {Uint8Array} bytes - * @return {string} - */ -export const toBase64 = bytes => { - let s = '' - for (let i = 0; i < bytes.byteLength; i++) { - s += string.fromCharCode(bytes[i]) - } - return btoa(s) -} - -/** - * @param {string} s - * @return {Uint8Array} - */ -export const fromBase64 = s => { - const a = atob(s) - const bytes = globals.createUint8ArrayFromLen(a.length) - for (let i = 0; i < a.length; i++) { - bytes[i] = a.charCodeAt(i) - } - return bytes -} diff --git a/lib/broadcastchannel.js b/lib/broadcastchannel.js deleted file mode 100644 index 2cee4448..00000000 --- a/lib/broadcastchannel.js +++ /dev/null @@ -1,72 +0,0 @@ -/* eslint-env browser */ - -import * as binary from './binary.js' -import * as globals from './globals.js' - -/** - * @typedef {Object} Channel - * @property {Set} Channel.subs - * @property {BC} Channel.bc - */ - -/** - * @type {Map} - */ -const channels = new Map() - -class LocalStoragePolyfill { - constructor (room) { - this.room = room - this.onmessage = null - addEventListener('storage', e => e.key === room && this.onmessage !== null && this.onmessage({ data: binary.fromBase64(e.newValue) })) - } - /** - * @param {ArrayBuffer} data - */ - postMessage (buf) { - if (typeof localStorage !== 'undefined') { - localStorage.setItem(this.room, binary.toBase64(globals.createUint8ArrayFromArrayBuffer(buf))) - } - } -} - -// Use BroadcastChannel or Polyfill -const BC = typeof BroadcastChannel === 'undefined' ? LocalStoragePolyfill : BroadcastChannel - -/** - * @param {string} room - * @return {Channel} - */ -const getChannel = room => { - let c = channels.get(room) - if (c === undefined) { - const subs = new Set() - const bc = new BC(room) - bc.onmessage = e => subs.forEach(sub => sub(e.data)) - c = { - bc, subs - } - channels.set(room, c) - } - return c -} - -/** - * @function - * @param {string} room - * @param {Function} f - */ -export const subscribe = (room, f) => getChannel(room).subs.add(f) - -/** - * Publish data to all subscribers (including subscribers on this tab) - * - * @function - * @param {string} room - * @param {ArrayBuffer} data - */ -export const publish = (room, data) => { - const c = getChannel(room) - c.bc.postMessage(data) - c.subs.forEach(sub => sub(data)) -} diff --git a/lib/decoding.js b/lib/decoding.js deleted file mode 100644 index 306b9ec0..00000000 --- a/lib/decoding.js +++ /dev/null @@ -1,205 +0,0 @@ -/** - * @module decoding - */ - -/* global Buffer */ - -import * as globals from './globals.js' - -/** - * A Decoder handles the decoding of an ArrayBuffer. - */ -export class Decoder { - /** - * @param {ArrayBuffer} buffer Binary data to decode - */ - constructor (buffer) { - this.arr = new Uint8Array(buffer) - this.pos = 0 - } -} - -/** - * @function - * @param {ArrayBuffer} buffer - * @return {Decoder} - */ -export const createDecoder = buffer => new Decoder(buffer) - -/** - * @function - * @param {Decoder} decoder - * @return {boolean} - */ -export const hasContent = decoder => decoder.pos !== decoder.arr.length - -/** - * Clone a decoder instance. - * Optionally set a new position parameter. - * - * @function - * @param {Decoder} decoder The decoder instance - * @param {number} [newPos] Defaults to current position - * @return {Decoder} A clone of `decoder` - */ -export const clone = (decoder, newPos = decoder.pos) => { - let _decoder = createDecoder(decoder.arr.buffer) - _decoder.pos = newPos - return _decoder -} - -/** - * Read `len` bytes as an ArrayBuffer. - * @function - * @param {Decoder} decoder The decoder instance - * @param {number} len The length of bytes to read - * @return {ArrayBuffer} - */ -export const readArrayBuffer = (decoder, len) => { - const arrayBuffer = globals.createUint8ArrayFromLen(len) - const view = globals.createUint8ArrayFromBuffer(decoder.arr.buffer, decoder.pos, len) - arrayBuffer.set(view) - decoder.pos += len - return arrayBuffer.buffer -} - -/** - * Read variable length payload as ArrayBuffer - * @function - * @param {Decoder} decoder - * @return {ArrayBuffer} - */ -export const readPayload = decoder => readArrayBuffer(decoder, readVarUint(decoder)) - -/** - * Read the rest of the content as an ArrayBuffer - * @function - * @param {Decoder} decoder - * @return {ArrayBuffer} - */ -export const readTail = decoder => readArrayBuffer(decoder, decoder.arr.length - decoder.pos) - -/** - * Skip one byte, jump to the next position. - * @function - * @param {Decoder} decoder The decoder instance - * @return {number} The next position - */ -export const skip8 = decoder => decoder.pos++ - -/** - * Read one byte as unsigned integer. - * @function - * @param {Decoder} decoder The decoder instance - * @return {number} Unsigned 8-bit integer - */ -export const readUint8 = decoder => decoder.arr[decoder.pos++] - -/** - * Read 4 bytes as unsigned integer. - * - * @function - * @param {Decoder} decoder - * @return {number} An unsigned integer. - */ -export const readUint32 = decoder => { - let uint = - decoder.arr[decoder.pos] + - (decoder.arr[decoder.pos + 1] << 8) + - (decoder.arr[decoder.pos + 2] << 16) + - (decoder.arr[decoder.pos + 3] << 24) - decoder.pos += 4 - return uint -} - -/** - * Look ahead without incrementing position. - * to the next byte and read it as unsigned integer. - * - * @function - * @param {Decoder} decoder - * @return {number} An unsigned integer. - */ -export const peekUint8 = decoder => decoder.arr[decoder.pos] - -/** - * Read unsigned integer (32bit) with variable length. - * 1/8th of the storage is used as encoding overhead. - * * numbers < 2^7 is stored in one bytlength - * * numbers < 2^14 is stored in two bylength - * - * @function - * @param {Decoder} decoder - * @return {number} An unsigned integer.length - */ -export const readVarUint = decoder => { - let num = 0 - let len = 0 - while (true) { - let r = decoder.arr[decoder.pos++] - num = num | ((r & 0b1111111) << len) - len += 7 - if (r < 1 << 7) { - return num >>> 0 // return unsigned number! - } - if (len > 35) { - throw new Error('Integer out of range!') - } - } -} - -/** - * Look ahead and read varUint without incrementing position - * - * @function - * @param {Decoder} decoder - * @return {number} - */ -export const peekVarUint = decoder => { - let pos = decoder.pos - let s = readVarUint(decoder) - decoder.pos = pos - return s -} - -/** - * Read string of variable length - * * varUint is used to store the length of the string - * - * Transforming utf8 to a string is pretty expensive. The code performs 10x better - * when String.fromCodePoint is fed with all characters as arguments. - * But most environments have a maximum number of arguments per functions. - * For effiency reasons we apply a maximum of 10000 characters at once. - * - * @function - * @param {Decoder} decoder - * @return {String} The read String. - */ -export const readVarString = decoder => { - let remainingLen = readVarUint(decoder) - let encodedString = '' - while (remainingLen > 0) { - const nextLen = remainingLen < 10000 ? remainingLen : 10000 - const bytes = new Array(nextLen) - for (let i = 0; i < nextLen; i++) { - bytes[i] = decoder.arr[decoder.pos++] - } - encodedString += String.fromCodePoint.apply(null, bytes) - remainingLen -= nextLen - } - return decodeURIComponent(escape(encodedString)) -} - -/** - * Look ahead and read varString without incrementing position - * - * @function - * @param {Decoder} decoder - * @return {string} - */ -export const peekVarString = decoder => { - let pos = decoder.pos - let s = readVarString(decoder) - decoder.pos = pos - return s -} diff --git a/lib/diff.js b/lib/diff.js deleted file mode 100644 index e364715b..00000000 --- a/lib/diff.js +++ /dev/null @@ -1,50 +0,0 @@ -/** - * @module diff - */ - -/** - * A SimpleDiff describes a change on a String. - * - * @example - * console.log(a) // the old value - * console.log(b) // the updated value - * // Apply changes of diff (pseudocode) - * a.remove(diff.pos, diff.remove) // Remove `diff.remove` characters - * a.insert(diff.pos, diff.insert) // Insert `diff.insert` - * a === b // values match - * - * @typedef {Object} SimpleDiff - * @property {Number} pos The index where changes were applied - * @property {Number} remove The number of characters to delete starting - * at `index`. - * @property {String} insert The new text to insert at `index` after applying - * `delete` - */ - -/** - * Create a diff between two strings. This diff implementation is highly - * efficient, but not very sophisticated. - * - * @public - * @param {String} a The old version of the string - * @param {String} b The updated version of the string - * @return {SimpleDiff} The diff description. - */ -export const simpleDiff = (a, b) => { - let left = 0 // number of same characters counting from left - let right = 0 // number of same characters counting from right - while (left < a.length && left < b.length && a[left] === b[left]) { - left++ - } - if (left !== a.length || left !== b.length) { - // Only check right if a !== b - while (right + left < a.length && right + left < b.length && a[a.length - right - 1] === b[b.length - right - 1]) { - right++ - } - } - return { - pos: left, // TODO: rename to index (also in type above) - remove: a.length - left - right, - insert: b.slice(left, b.length - right) - } -} diff --git a/lib/encoding.js b/lib/encoding.js deleted file mode 100644 index 5de8d260..00000000 --- a/lib/encoding.js +++ /dev/null @@ -1,243 +0,0 @@ -/** - * @module encoding - */ -import * as globals from './globals.js' - -const bits7 = 0b1111111 -const bits8 = 0b11111111 - -/** - * A BinaryEncoder handles the encoding to an ArrayBuffer. - */ -export class Encoder { - constructor () { - this.cpos = 0 - this.cbuf = globals.createUint8ArrayFromLen(1000) - this.bufs = [] - } -} - -/** - * @function - * @return {Encoder} - */ -export const createEncoder = () => new Encoder() - -/** - * The current length of the encoded data. - * - * @function - * @param {Encoder} encoder - * @return {number} - */ -export const length = encoder => { - let len = encoder.cpos - for (let i = 0; i < encoder.bufs.length; i++) { - len += encoder.bufs[i].length - } - return len -} - -/** - * Transform to ArrayBuffer. TODO: rename to .toArrayBuffer - * - * @function - * @param {Encoder} encoder - * @return {ArrayBuffer} The created ArrayBuffer. - */ -export const toBuffer = encoder => { - const uint8arr = globals.createUint8ArrayFromLen(length(encoder)) - let curPos = 0 - for (let i = 0; i < encoder.bufs.length; i++) { - let d = encoder.bufs[i] - uint8arr.set(d, curPos) - curPos += d.length - } - uint8arr.set(globals.createUint8ArrayFromBuffer(encoder.cbuf.buffer, 0, encoder.cpos), curPos) - return uint8arr.buffer -} - -/** - * Write one byte to the encoder. - * - * @function - * @param {Encoder} encoder - * @param {number} num The byte that is to be encoded. - */ -export const write = (encoder, num) => { - if (encoder.cpos === encoder.cbuf.length) { - encoder.bufs.push(encoder.cbuf) - encoder.cbuf = globals.createUint8ArrayFromLen(encoder.cbuf.length * 2) - encoder.cpos = 0 - } - encoder.cbuf[encoder.cpos++] = num -} - -/** - * Write one byte at a specific position. - * Position must already be written (i.e. encoder.length > pos) - * - * @function - * @param {Encoder} encoder - * @param {number} pos Position to which to write data - * @param {number} num Unsigned 8-bit integer - */ -export const set = (encoder, pos, num) => { - let buffer = null - // iterate all buffers and adjust position - for (let i = 0; i < encoder.bufs.length && buffer === null; i++) { - const b = encoder.bufs[i] - if (pos < b.length) { - buffer = b // found buffer - } else { - pos -= b.length - } - } - if (buffer === null) { - // use current buffer - buffer = encoder.cbuf - } - buffer[pos] = num -} - -/** - * Write one byte as an unsigned integer. - * - * @function - * @param {Encoder} encoder - * @param {number} num The number that is to be encoded. - */ -export const writeUint8 = (encoder, num) => write(encoder, num & bits8) - -/** - * Write one byte as an unsigned Integer at a specific location. - * - * @function - * @param {Encoder} encoder - * @param {number} pos The location where the data will be written. - * @param {number} num The number that is to be encoded. - */ -export const setUint8 = (encoder, pos, num) => set(encoder, pos, num & bits8) - -/** - * Write two bytes as an unsigned integer. - * - * @function - * @param {Encoder} encoder - * @param {number} num The number that is to be encoded. - */ -export const writeUint16 = (encoder, num) => { - write(encoder, num & bits8) - write(encoder, (num >>> 8) & bits8) -} -/** - * Write two bytes as an unsigned integer at a specific location. - * - * @function - * @param {Encoder} encoder - * @param {number} pos The location where the data will be written. - * @param {number} num The number that is to be encoded. - */ -export const setUint16 = (encoder, pos, num) => { - set(encoder, pos, num & bits8) - set(encoder, pos + 1, (num >>> 8) & bits8) -} - -/** - * Write two bytes as an unsigned integer - * - * @function - * @param {Encoder} encoder - * @param {number} num The number that is to be encoded. - */ -export const writeUint32 = (encoder, num) => { - for (let i = 0; i < 4; i++) { - write(encoder, num & bits8) - num >>>= 8 - } -} - -/** - * Write two bytes as an unsigned integer at a specific location. - * - * @function - * @param {Encoder} encoder - * @param {number} pos The location where the data will be written. - * @param {number} num The number that is to be encoded. - */ -export const setUint32 = (encoder, pos, num) => { - for (let i = 0; i < 4; i++) { - set(encoder, pos + i, num & bits8) - num >>>= 8 - } -} - -/** - * Write a variable length unsigned integer. - * - * Encodes integers in the range from [0, 4294967295] / [0, 0xffffffff]. (max 32 bit unsigned integer) - * - * @function - * @param {Encoder} encoder - * @param {number} num The number that is to be encoded. - */ -export const writeVarUint = (encoder, num) => { - while (num >= 0b10000000) { - write(encoder, 0b10000000 | (bits7 & num)) - num >>>= 7 - } - write(encoder, bits7 & num) -} - -/** - * Write a variable length string. - * - * @function - * @param {Encoder} encoder - * @param {String} str The string that is to be encoded. - */ -export const writeVarString = (encoder, str) => { - const encodedString = unescape(encodeURIComponent(str)) - const len = encodedString.length - writeVarUint(encoder, len) - for (let i = 0; i < len; i++) { - write(encoder, encodedString.codePointAt(i)) - } -} - -/** - * Write the content of another Encoder. - * - * TODO: can be improved! - * - * @function - * @param {Encoder} encoder The enUint8Arr - * @param {Encoder} append The BinaryEncoder to be written. - */ -export const writeBinaryEncoder = (encoder, append) => writeArrayBuffer(encoder, toBuffer(append)) - -/** - * Append an arrayBuffer to the encoder. - * - * @function - * @param {Encoder} encoder - * @param {ArrayBuffer} arrayBuffer - */ -export const writeArrayBuffer = (encoder, arrayBuffer) => { - const prevBufferLen = encoder.cbuf.length - // TODO: Append to cbuf if possible - encoder.bufs.push(globals.createUint8ArrayFromBuffer(encoder.cbuf.buffer, 0, encoder.cpos)) - encoder.bufs.push(globals.createUint8ArrayFromArrayBuffer(arrayBuffer)) - encoder.cbuf = globals.createUint8ArrayFromLen(prevBufferLen) - encoder.cpos = 0 -} - -/** - * @function - * @param {Encoder} encoder - * @param {ArrayBuffer} arrayBuffer - */ -export const writePayload = (encoder, arrayBuffer) => { - writeVarUint(encoder, arrayBuffer.byteLength) - writeArrayBuffer(encoder, arrayBuffer) -} diff --git a/lib/encoding.test.js b/lib/encoding.test.js deleted file mode 100644 index b957707b..00000000 --- a/lib/encoding.test.js +++ /dev/null @@ -1,49 +0,0 @@ -import * as encoding from './encoding.js' - -/** - * Check if binary encoding is compatible with golang binary encoding - binary.PutVarUint. - * - * Result: is compatible up to 32 bit: [0, 4294967295] / [0, 0xffffffff]. (max 32 bit unsigned integer) - */ -let err = null -try { - const tests = [ - { in: 0, out: [0] }, - { in: 1, out: [1] }, - { in: 128, out: [128, 1] }, - { in: 200, out: [200, 1] }, - { in: 32, out: [32] }, - { in: 500, out: [244, 3] }, - { in: 256, out: [128, 2] }, - { in: 700, out: [188, 5] }, - { in: 1024, out: [128, 8] }, - { in: 1025, out: [129, 8] }, - { in: 4048, out: [208, 31] }, - { in: 5050, out: [186, 39] }, - { in: 1000000, out: [192, 132, 61] }, - { in: 34951959, out: [151, 166, 213, 16] }, - { in: 2147483646, out: [254, 255, 255, 255, 7] }, - { in: 2147483647, out: [255, 255, 255, 255, 7] }, - { in: 2147483648, out: [128, 128, 128, 128, 8] }, - { in: 2147483700, out: [180, 128, 128, 128, 8] }, - { in: 4294967294, out: [254, 255, 255, 255, 15] }, - { in: 4294967295, out: [255, 255, 255, 255, 15] } - ] - tests.forEach(test => { - const encoder = encoding.createEncoder() - encoding.writeVarUint(encoder, test.in) - const buffer = new Uint8Array(encoding.toBuffer(encoder)) - if (buffer.byteLength !== test.out.length) { - throw new Error('Length don\'t match!') - } - for (let j = 0; j < buffer.length; j++) { - if (buffer[j] !== test[1][j]) { - throw new Error('values don\'t match!') - } - } - }) -} catch (error) { - err = error -} finally { - console.log('YDB Client: Encoding varUint compatiblity test: ', err || 'success!') -} diff --git a/lib/globals.js b/lib/globals.js deleted file mode 100644 index 2777d3c3..00000000 --- a/lib/globals.js +++ /dev/null @@ -1,65 +0,0 @@ -/** - * @module globals - */ - -/* eslint-env browser */ - -export const Uint8Array_ = Uint8Array - -/** - * @param {Array} arr - * @return {ArrayBuffer} - */ -export const createArrayBufferFromArray = arr => new Uint8Array_(arr).buffer - -export const createUint8ArrayFromLen = len => new Uint8Array_(len) - -/** - * Create Uint8Array with initial content from buffer - */ -export const createUint8ArrayFromBuffer = (buffer, byteOffset, length) => new Uint8Array_(buffer, byteOffset, length) - -/** - * Create Uint8Array with initial content from buffer - */ -export const createUint8ArrayFromArrayBuffer = arraybuffer => new Uint8Array_(arraybuffer) -export const createArrayFromArrayBuffer = arraybuffer => Array.from(createUint8ArrayFromArrayBuffer(arraybuffer)) - -export const createPromise = f => new Promise(f) - -export const createMap = () => new Map() -export const createSet = () => new Set() - -/** - * `Promise.all` wait for all promises in the array to resolve and return the result - * @param {Array>} arrp - * @return {any} - */ -export const pall = arrp => Promise.all(arrp) -export const preject = reason => Promise.reject(reason) -export const presolve = res => Promise.resolve(res) - -export const until = (timeout, check) => createPromise((resolve, reject) => { - const hasTimeout = timeout > 0 - const untilInterval = () => { - if (check()) { - clearInterval(intervalHandle) - resolve() - } else if (hasTimeout) { - timeout -= 10 - if (timeout < 0) { - clearInterval(intervalHandle) - reject(error('Timeout')) - } - } - } - const intervalHandle = setInterval(untilInterval, 10) -}) - -export const error = description => new Error(description) - -/** - * @param {number} t Time to wait - * @return {Promise} Promise that is resolved after t ms - */ -export const wait = t => createPromise(r => setTimeout(r, t)) diff --git a/lib/idb.js b/lib/idb.js deleted file mode 100644 index d1a1cc4f..00000000 --- a/lib/idb.js +++ /dev/null @@ -1,166 +0,0 @@ -/** - * @module lib/idb - */ - -/* eslint-env browser */ - -import * as globals from './globals.js' - -/* - * IDB Request to Promise transformer - */ -export const rtop = request => globals.createPromise((resolve, reject) => { - request.onerror = event => reject(new Error(event.target.error)) - request.onblocked = () => location.reload() - request.onsuccess = event => resolve(event.target.result) -}) - -/** - * @param {string} name - * @param {Function} initDB Called when the database is first created - * @return {Promise} - */ -export const openDB = (name, initDB) => globals.createPromise((resolve, reject) => { - let request = indexedDB.open(name) - /** - * @param {any} event - */ - request.onupgradeneeded = event => initDB(event.target.result) - /** - * @param {any} event - */ - request.onerror = event => reject(new Error(event.target.error)) - request.onblocked = () => location.reload() - /** - * @param {any} event - */ - request.onsuccess = event => { - const db = event.target.result - db.onversionchange = () => { db.close() } - addEventListener('unload', () => db.close()) - resolve(db) - } -}) - -export const deleteDB = name => rtop(indexedDB.deleteDatabase(name)) - -export const createStores = (db, definitions) => definitions.forEach(d => - db.createObjectStore.apply(db, d) -) - -/** - * @param {IDBObjectStore} store - * @param {String | number | ArrayBuffer | Date | Array } key - * @return {Promise} - */ -export const get = (store, key) => - rtop(store.get(key)) - -/** - * @param {IDBObjectStore} store - * @param {String | number | ArrayBuffer | Date | IDBKeyRange | Array } key - */ -export const del = (store, key) => - rtop(store.delete(key)) - -/** - * @param {IDBObjectStore} store - * @param {String | number | ArrayBuffer | Date | boolean} item - * @param {String | number | ArrayBuffer | Date | Array} [key] - */ -export const put = (store, item, key) => - rtop(store.put(item, key)) - -/** - * @param {IDBObjectStore} store - * @param {String | number | ArrayBuffer | Date | boolean} item - * @param {String | number | ArrayBuffer | Date | Array} [key] - * @return {Promise} - */ -export const add = (store, item, key) => - rtop(store.add(item, key)) - -/** - * @param {IDBObjectStore} store - * @param {String | number | ArrayBuffer | Date} item - * @return {Promise} - */ -export const addAutoKey = (store, item) => - rtop(store.add(item)) - -/** - * @param {IDBObjectStore} store - * @param {IDBKeyRange} [range] - */ -export const getAll = (store, range) => - rtop(store.getAll(range)) - -/** - * @param {IDBObjectStore} store - * @param {IDBKeyRange} [range] - */ -export const getAllKeys = (store, range) => - rtop(store.getAllKeys(range)) - -/** - * @typedef KeyValuePair - * @type {Object} - * @property {any} k key - * @property {any} v Value - */ - -/** - * @param {IDBObjectStore} store - * @param {IDBKeyRange} [range] - * @return {Promise>} - */ -export const getAllKeysValues = (store, range) => - globals.pall([getAllKeys(store, range), getAll(store, range)]).then(([ks, vs]) => ks.map((k, i) => ({ k, v: vs[i] }))) - -/** - * Iterate on keys and values - * @param {IDBObjectStore} store - * @param {IDBKeyRange?} keyrange - * @param {Function} f Return true in order to continue the cursor - */ -export const iterate = (store, keyrange, f) => globals.createPromise((resolve, reject) => { - const request = store.openCursor(keyrange) - request.onerror = reject - /** - * @param {any} event - */ - request.onsuccess = event => { - const cursor = event.target.result - if (cursor === null) { - return resolve() - } - f(cursor.value, cursor.key) - cursor.continue() - } -}) - -/** - * Iterate on the keys (no values) - * - * @param {IDBObjectStore} store - * @param {IDBKeyRange} keyrange - * @param {function} f Call `idbcursor.continue()` to iterate further - */ -export const iterateKeys = (store, keyrange, f) => { - /** - * @param {any} event - */ - store.openKeyCursor(keyrange).onsuccess = event => f(event.target.result) -} - -/** - * Open store from transaction - * @param {IDBTransaction} t - * @param {String} store - * @returns {IDBObjectStore} - */ -export const getStore = (t, store) => t.objectStore(store) - -export const createIDBKeyRangeBound = (lower, upper, lowerOpen, upperOpen) => IDBKeyRange.bound(lower, upper, lowerOpen, upperOpen) -export const createIDBKeyRangeUpperBound = (upper, upperOpen) => IDBKeyRange.upperBound(upper, upperOpen) -export const createIDBKeyRangeLowerBound = (lower, lowerOpen) => IDBKeyRange.lowerBound(lower, lowerOpen) diff --git a/lib/idb.test.js b/lib/idb.test.js deleted file mode 100644 index e96a1352..00000000 --- a/lib/idb.test.js +++ /dev/null @@ -1,34 +0,0 @@ -import * as test from './testing.js' -import * as idb from './idb.js' -import * as logging from './logging.js' - -const initTestDB = db => idb.createStores(db, [['test']]) -const testDBName = 'idb-test' - -const createTransaction = db => db.transaction(['test'], 'readwrite') -/** - * @param {IDBTransaction} t - * @return {IDBObjectStore} - */ -const getStore = t => idb.getStore(t, 'test') - -idb.deleteDB(testDBName).then(() => idb.openDB(testDBName, initTestDB)).then(db => { - test.run('idb iteration', async testname => { - const t = createTransaction(db) - await idb.put(getStore(t), 0, ['t', 0]) - await idb.put(getStore(t), 1, ['t', 1]) - const valsGetAll = await idb.getAll(getStore(t)) - if (valsGetAll.length !== 2) { - logging.fail('getAll does not return two values') - } - const valsIterate = [] - const keyrange = idb.createIDBKeyRangeBound(['t', 0], ['t', 1], false, false) - await idb.put(getStore(t), 2, ['t', 2]) - await idb.iterate(getStore(t), keyrange, (val, key) => { - valsIterate.push(val) - }) - if (valsIterate.length !== 2) { - logging.fail('iterate does not return two values') - } - }) -}) diff --git a/lib/logging.js b/lib/logging.js deleted file mode 100644 index 6af7e49b..00000000 --- a/lib/logging.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * @module logging - */ - -import * as globals from './globals.js' - -let date = new Date().getTime() - -const writeDate = () => { - const oldDate = date - date = new Date().getTime() - return date - oldDate -} - -export const print = (...args) => console.log(...args) -export const log = m => print(`%cydb-client: %c${m} %c+${writeDate()}ms`, 'color: blue;', '', 'color: blue') - -export const fail = m => { - throw new Error(m) -} - -/** - * @param {ArrayBuffer} buffer - * @return {string} - */ -export const arrayBufferToString = buffer => JSON.stringify(Array.from(globals.createUint8ArrayFromBuffer(buffer))) diff --git a/lib/math.js b/lib/math.js deleted file mode 100644 index 47efeb74..00000000 --- a/lib/math.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * @module math - */ -export const floor = Math.floor - -/** - * @function - * @param {number} a - * @param {number} b - * @return {number} The sum of a and b - */ -export const add = (a, b) => a + b - -/** - * @function - * @param {number} a - * @param {number} b - * @return {number} The smaller element of a and b - */ -export const min = (a, b) => a < b ? a : b - -/** - * @function - * @param {number} a - * @param {number} b - * @return {number} The bigger element of a and b - */ -export const max = (a, b) => a > b ? a : b diff --git a/lib/mutex.js b/lib/mutex.js deleted file mode 100644 index 307c4cd0..00000000 --- a/lib/mutex.js +++ /dev/null @@ -1,31 +0,0 @@ - -/** - * Creates a mutual exclude function with the following property: - * - * @example - * const mutex = createMutex() - * mutex(() => { - * // This function is immediately executed - * mutex(() => { - * // This function is not executed, as the mutex is already active. - * }) - * }) - * - * @return {Function} A mutual exclude function - * @public - */ -export const createMutex = () => { - let token = true - return (f, g) => { - if (token) { - token = false - try { - f() - } finally { - token = true - } - } else if (g !== undefined) { - g() - } - } -} diff --git a/lib/number.js b/lib/number.js deleted file mode 100644 index 541930ee..00000000 --- a/lib/number.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * @module number - */ - -export const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER -export const MIN_SAFE_INTEGER = Number.MIN_SAFE_INTEGER diff --git a/lib/object.js b/lib/object.js deleted file mode 100644 index f9a3f2ef..00000000 --- a/lib/object.js +++ /dev/null @@ -1,14 +0,0 @@ - -export const create = Object.create(null) - -export const keys = Object.keys - -export const equalFlat = (a, b) => { - const keys = Object.keys(a) - let eq = keys.length === Object.keys(b).length - for (let i = 0; i < keys.length && eq; i++) { - const key = keys[i] - eq = a[key] === b[key] - } - return eq -} diff --git a/lib/prng/PRNG/Mt19937.js b/lib/prng/PRNG/Mt19937.js deleted file mode 100644 index c1aa9e77..00000000 --- a/lib/prng/PRNG/Mt19937.js +++ /dev/null @@ -1,67 +0,0 @@ -/** - * @module prng - */ -const N = 624 -const M = 397 - -const twist = (u, v) => ((((u & 0x80000000) | (v & 0x7fffffff)) >>> 1) ^ ((v & 1) ? 0x9908b0df : 0)) - -const nextState = (state) => { - let p = 0 - let j - for (j = N - M + 1; --j; p++) { - state[p] = state[p + M] ^ twist(state[p], state[p + 1]) - } - for (j = M; --j; p++) { - state[p] = state[p + M - N] ^ twist(state[p], state[p + 1]) - } - state[p] = state[p + M - N] ^ twist(state[p], state[0]) -} - -/** - * This is a port of Shawn Cokus's implementation of the original Mersenne Twister algorithm (http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/MT2002/CODES/MTARCOK/mt19937ar-cok.c). - * MT has a very high period of 2^19937. Though the authors of xorshift describe that a high period is not - * very relevant (http://vigna.di.unimi.it/xorshift/). It is four times slower than xoroshiro128plus and - * needs to recompute its state after generating 624 numbers. - * - * @example - * const gen = new Mt19937(new Date().getTime()) - * console.log(gen.next()) - * - * @public - */ -export class Mt19937 { - /** - * @param {Number} seed The starting point for the random number generation. If you use the same seed, the generator will return the same sequence of random numbers. - */ - constructor (seed) { - this.seed = seed - const state = new Uint32Array(N) - state[0] = seed - for (let i = 1; i < N; i++) { - state[i] = (Math.imul(1812433253, (state[i - 1] ^ (state[i - 1] >>> 30))) + i) & 0xFFFFFFFF - } - this._state = state - this._i = 0 - nextState(this._state) - } - - /** - * Generate a random signed integer. - * - * @return {Number} A 32 bit signed integer. - */ - next () { - if (this._i === N) { - // need to compute a new state - nextState(this._state) - this._i = 0 - } - let y = this._state[this._i++] - y ^= (y >>> 11) - y ^= (y << 7) & 0x9d2c5680 - y ^= (y << 15) & 0xefc60000 - y ^= (y >>> 18) - return y - } -} diff --git a/lib/prng/PRNG/PRNG.tests.js b/lib/prng/PRNG/PRNG.tests.js deleted file mode 100644 index f524edd3..00000000 --- a/lib/prng/PRNG/PRNG.tests.js +++ /dev/null @@ -1,51 +0,0 @@ -/** - * @module prng - */ - -import { Mt19937 } from './Mt19937.js' -import { Xoroshiro128plus } from './Xoroshiro128plus.js' -import { Xorshift32 } from './Xorshift32.js' -import * as time from '../../time.js' - -const DIAMETER = 300 -const NUMBERS = 10000 - -const runPRNG = (name, Gen) => { - console.log('== ' + name + ' ==') - const gen = new Gen(1234) - let head = 0 - let tails = 0 - const date = time.getUnixTime() - const canvas = document.createElement('canvas') - canvas.height = DIAMETER - canvas.width = DIAMETER - const ctx = canvas.getContext('2d') - const vals = new Set() - ctx.fillStyle = 'blue' - for (let i = 0; i < NUMBERS; i++) { - const n = gen.next() & 0xFFFFFF - const x = (gen.next() >>> 0) % DIAMETER - const y = (gen.next() >>> 0) % DIAMETER - ctx.fillRect(x, y, 1, 2) - if ((n & 1) === 1) { - head++ - } else { - tails++ - } - if (vals.has(n)) { - console.warn(`The generator generated a duplicate`) - } - vals.add(n) - } - console.log('time: ', time.getUnixTime() - date) - console.log('head:', head, 'tails:', tails) - console.log('%c ', `font-size: 200px; background: url(${canvas.toDataURL()}) no-repeat;`) - const h1 = document.createElement('h1') - h1.insertBefore(document.createTextNode(name), null) - document.body.insertBefore(h1, null) - document.body.appendChild(canvas) -} - -runPRNG('mt19937', Mt19937) -runPRNG('xoroshiro128plus', Xoroshiro128plus) -runPRNG('xorshift32', Xorshift32) diff --git a/lib/prng/PRNG/README.md b/lib/prng/PRNG/README.md deleted file mode 100644 index a7de088b..00000000 --- a/lib/prng/PRNG/README.md +++ /dev/null @@ -1,5 +0,0 @@ -# Pseudo Random Number Generators (PRNG) - -Given a seed a PRNG generates a sequence of numbers that cannot be reasonably predicted. Two PRNGs must generate the same random sequence of numbers if given the same seed. - -TODO: explain what POINT is \ No newline at end of file diff --git a/lib/prng/PRNG/Xoroshiro128plus.js b/lib/prng/PRNG/Xoroshiro128plus.js deleted file mode 100644 index 13b97156..00000000 --- a/lib/prng/PRNG/Xoroshiro128plus.js +++ /dev/null @@ -1,101 +0,0 @@ -/** - * @module prng - */ - -import { Xorshift32 } from './Xorshift32.js' - -/** - * This is a variant of xoroshiro128plus - the fastest full-period generator passing BigCrush without systematic failures. - * - * This implementation follows the idea of the original xoroshiro128plus implementation, - * but is optimized for the JavaScript runtime. I.e. - * * The operations are performed on 32bit integers (the original implementation works with 64bit values). - * * The initial 128bit state is computed based on a 32bit seed and Xorshift32. - * * This implementation returns two 32bit values based on the 64bit value that is computed by xoroshiro128plus. - * Caution: The last addition step works slightly different than in the original implementation - the add carry of the - * first 32bit addition is not carried over to the last 32bit. - * - * [Reference implementation](http://vigna.di.unimi.it/xorshift/xoroshiro128plus.c) - */ -export class Xoroshiro128plus { - constructor (seed) { - this.seed = seed - // This is a variant of Xoroshiro128plus to fill the initial state - const xorshift32 = new Xorshift32(seed) - this.state = new Uint32Array(4) - for (let i = 0; i < 4; i++) { - this.state[i] = xorshift32.next() - } - this._fresh = true - } - next () { - const state = this.state - if (this._fresh) { - this._fresh = false - return (state[0] + state[2]) & 0xFFFFFFFF - } else { - this._fresh = true - const s0 = state[0] - const s1 = state[1] - const s2 = state[2] ^ s0 - const s3 = state[3] ^ s1 - // function js_rotl (x, k) { - // k = k - 32 - // const x1 = x[0] - // const x2 = x[1] - // x[0] = x2 << k | x1 >>> (32 - k) - // x[1] = x1 << k | x2 >>> (32 - k) - // } - // rotl(s0, 55) // k = 23 = 55 - 32; j = 9 = 32 - 23 - state[0] = (s1 << 23 | s0 >>> 9) ^ s2 ^ (s2 << 14 | s3 >>> 18) - state[1] = (s0 << 23 | s1 >>> 9) ^ s3 ^ (s3 << 14) - // rol(s1, 36) // k = 4 = 36 - 32; j = 23 = 32 - 9 - state[2] = s3 << 4 | s2 >>> 28 - state[3] = s2 << 4 | s3 >>> 28 - return (state[1] + state[3]) & 0xFFFFFFFF - } - } -} - -/* - -// reference implementation -#include -#include - -uint64_t s[2]; - -static inline uint64_t rotl(const uint64_t x, int k) { - return (x << k) | (x >> (64 - k)); -} - -uint64_t next(void) { - const uint64_t s0 = s[0]; - uint64_t s1 = s[1]; - s1 ^= s0; - s[0] = rotl(s0, 55) ^ s1 ^ (s1 << 14); // a, b - s[1] = rotl(s1, 36); // c - return (s[0] + s[1]) & 0xFFFFFFFF; -} - -int main(void) -{ - int i; - s[0] = 1111 | (1337ul << 32); - s[1] = 1234 | (9999ul << 32); - - printf("1000 outputs of genrand_int31()\n"); - for (i=0; i<100; i++) { - printf("%10lu ", i); - printf("%10lu ", next()); - printf("- %10lu ", s[0] >> 32); - printf("%10lu ", (s[0] << 32) >> 32); - printf("%10lu ", s[1] >> 32); - printf("%10lu ", (s[1] << 32) >> 32); - printf("\n"); - // if (i%5==4) printf("\n"); - } - return 0; -} - -*/ diff --git a/lib/prng/PRNG/Xorshift32.js b/lib/prng/PRNG/Xorshift32.js deleted file mode 100644 index ad76a34e..00000000 --- a/lib/prng/PRNG/Xorshift32.js +++ /dev/null @@ -1,29 +0,0 @@ -/** - * @module prng - */ - -/** - * Xorshift32 is a very simple but elegang PRNG with a period of `2^32-1`. - */ -export class Xorshift32 { - /** - * @param {number} seed The starting point for the random number generation. If you use the same seed, the generator will return the same sequence of random numbers. - */ - constructor (seed) { - this.seed = seed - this._state = seed - } - /** - * Generate a random signed integer. - * - * @return {Number} A 32 bit signed integer. - */ - next () { - let x = this._state - x ^= x << 13 - x ^= x >> 17 - x ^= x << 5 - this._state = x - return x - } -} diff --git a/lib/prng/prng.js b/lib/prng/prng.js deleted file mode 100644 index ae9e898e..00000000 --- a/lib/prng/prng.js +++ /dev/null @@ -1,134 +0,0 @@ -/** - * @module prng - */ - -import * as binary from '../binary.js' -import { fromCharCode, fromCodePoint } from '../string.js' -import { MAX_SAFE_INTEGER, MIN_SAFE_INTEGER } from '../number.js' -import * as math from '../math.js' - -import { Xoroshiro128plus as DefaultPRNG } from './PRNG/Xoroshiro128plus.js' - -/** - * Description of the function - * @callback generatorNext - * @return {number} A 32bit integer - */ - -/** - * A random type generator. - * - * @typedef {Object} PRNG - * @property {generatorNext} next Generate new number - */ - -/** - * Create a Xoroshiro128plus Pseudo-Random-Number-Generator. - * This is the fastest full-period generator passing BigCrush without systematic failures. - * But there are more PRNGs available in ./PRNG/. - * - * @param {number} seed A positive 32bit integer. Do not use negative numbers. - * @return {PRNG} - */ -export const createPRNG = seed => new DefaultPRNG(Math.floor(seed < 1 ? seed * binary.BITS32 : seed)) - -/** - * Generates a single random bool. - * - * @param {PRNG} gen A random number generator. - * @return {Boolean} A random boolean - */ -export const bool = gen => (gen.next() & 2) === 2 // brackets are non-optional! - -/** - * Generates a random integer with 53 bit resolution. - * - * @param {PRNG} gen A random number generator. - * @param {Number} [min = MIN_SAFE_INTEGER] The lower bound of the allowed return values (inclusive). - * @param {Number} [max = MAX_SAFE_INTEGER] The upper bound of the allowed return values (inclusive). - * @return {Number} A random integer on [min, max] - */ -export const int53 = (gen, min = MIN_SAFE_INTEGER, max = MAX_SAFE_INTEGER) => math.floor(real53(gen) * (max + 1 - min) + min) - -/** - * Generates a random integer with 32 bit resolution. - * - * @param {PRNG} gen A random number generator. - * @param {Number} [min = MIN_SAFE_INTEGER] The lower bound of the allowed return values (inclusive). - * @param {Number} [max = MAX_SAFE_INTEGER] The upper bound of the allowed return values (inclusive). - * @return {Number} A random integer on [min, max] - */ -export const int32 = (gen, min = MIN_SAFE_INTEGER, max = MAX_SAFE_INTEGER) => min + ((gen.next() >>> 0) % (max + 1 - min)) - -/** - * Generates a random real on [0, 1) with 32 bit resolution. - * - * @param {PRNG} gen A random number generator. - * @return {Number} A random real number on [0, 1). - */ -export const real32 = gen => (gen.next() >>> 0) / binary.BITS32 - -/** - * Generates a random real on [0, 1) with 53 bit resolution. - * - * @param {PRNG} gen A random number generator. - * @return {Number} A random real number on [0, 1). - */ -export const real53 = gen => (((gen.next() >>> 5) * binary.BIT26) + (gen.next() >>> 6)) / MAX_SAFE_INTEGER - -/** - * Generates a random character from char code 32 - 126. I.e. Characters, Numbers, special characters, and Space: - * - * (Space)!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[/]^_`abcdefghijklmnopqrstuvwxyz{|}~ - */ -export const char = gen => fromCharCode(int32(gen, 32, 126)) - -/** - * @param {PRNG} gen - * @return {string} A single letter (a-z) - */ -export const letter = gen => fromCharCode(int32(gen, 97, 122)) - -/** - * @param {PRNG} gen - * @return {string} A random word without spaces consisting of letters (a-z) - */ -export const word = gen => { - const len = int32(gen, 0, 20) - let str = '' - for (let i = 0; i < len; i++) { - str += letter(gen) - } - return str -} - -/** - * TODO: this function produces invalid runes. Does not cover all of utf16!! - */ -export const utf16Rune = gen => { - const codepoint = int32(gen, 0, 256) - return fromCodePoint(codepoint) -} - -/** - * @param {PRNG} gen - * @param {number} [maxlen = 20] - */ -export const utf16String = (gen, maxlen = 20) => { - const len = int32(gen, 0, maxlen) - let str = '' - for (let i = 0; i < len; i++) { - str += utf16Rune(gen) - } - return str -} - -/** - * Returns one element of a given array. - * - * @param {PRNG} gen A random number generator. - * @param {Array} array Non empty Array of possible values. - * @return {T} One of the values of the supplied Array. - * @template T - */ -export const oneOf = (gen, array) => array[int32(gen, 0, array.length - 1)] diff --git a/lib/prng/prng.test.js b/lib/prng/prng.test.js deleted file mode 100644 index f6917f06..00000000 --- a/lib/prng/prng.test.js +++ /dev/null @@ -1,114 +0,0 @@ -/** - * @module prng - */ - -/** - *TODO: enable tests -import * as rt from '../rich-text/formatters.js'' -import { test } from '../test/test.js'' -import Xoroshiro128plus from './PRNG/Xoroshiro128plus.js'' -import Xorshift32 from './PRNG/Xorshift32.js'' -import MT19937 from './PRNG/Mt19937.js'' -import { generateBool, generateInt, generateInt32, generateReal, generateChar } from './random.js'' -import { MAX_SAFE_INTEGER } from '../number/constants.js'' -import { BIT32 } from '../binary/constants.js'' - -function init (Gen) { - return { - gen: new Gen(1234) - } -} - -const PRNGs = [ - { name: 'Xoroshiro128plus', Gen: Xoroshiro128plus }, - { name: 'Xorshift32', Gen: Xorshift32 }, - { name: 'MT19937', Gen: MT19937 } -] - -const ITERATONS = 1000000 - -for (const PRNG of PRNGs) { - const prefix = rt.orange`${PRNG.name}:` - - test(rt.plain`${prefix} generateBool`, function generateBoolTest (t) { - const { gen } = init(PRNG.Gen) - let head = 0 - let tail = 0 - let b - let i - - for (i = 0; i < ITERATONS; i++) { - b = generateBool(gen) - if (b) { - head++ - } else { - tail++ - } - } - t.log(`Generated ${head} heads and ${tail} tails.`) - t.assert(tail >= Math.floor(ITERATONS * 0.49), 'Generated enough tails.') - t.assert(head >= Math.floor(ITERATONS * 0.49), 'Generated enough heads.') - }) - - test(rt.plain`${prefix} generateInt integers average correctly`, function averageIntTest (t) { - const { gen } = init(PRNG.Gen) - let count = 0 - let i - - for (i = 0; i < ITERATONS; i++) { - count += generateInt(gen, 0, 100) - } - const average = count / ITERATONS - const expectedAverage = 100 / 2 - t.log(`Average is: ${average}. Expected average is ${expectedAverage}.`) - t.assert(Math.abs(average - expectedAverage) <= 1, 'Expected average is at most 1 off.') - }) - - test(rt.plain`${prefix} generateInt32 generates integer with 32 bits`, function generateLargeIntegers (t) { - const { gen } = init(PRNG.Gen) - let num = 0 - let i - let newNum - for (i = 0; i < ITERATONS; i++) { - newNum = generateInt32(gen, 0, MAX_SAFE_INTEGER) - if (newNum > num) { - num = newNum - } - } - t.log(`Largest number generated is ${num} (0b${num.toString(2)})`) - t.assert(num > (BIT32 >>> 0), 'Largest number is 32 bits long.') - }) - - test(rt.plain`${prefix} generateReal has 53 bit resolution`, function real53bitResolution (t) { - const { gen } = init(PRNG.Gen) - let num = 0 - let i - let newNum - for (i = 0; i < ITERATONS; i++) { - newNum = generateReal(gen) * MAX_SAFE_INTEGER - if (newNum > num) { - num = newNum - } - } - t.log(`Largest number generated is ${num}.`) - t.assert((MAX_SAFE_INTEGER - num) / MAX_SAFE_INTEGER < 0.01, 'Largest number is close to MAX_SAFE_INTEGER (at most 1% off).') - }) - - test(rt.plain`${prefix} generateChar generates all described characters`, function real53bitResolution (t) { - const { gen } = init(PRNG.Gen) - const charSet = new Set() - const chars = ' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[/]^_`abcdefghijklmnopqrstuvwxyz{|}~"' - let i - let char - for (i = chars.length - 1; i >= 0; i--) { - charSet.add(chars[i]) - } - for (i = 0; i < ITERATONS; i++) { - char = generateChar(gen) - charSet.delete(char) - } - t.log(`Charactes missing: ${charSet.size} - generating all of "${chars}"`) - t.assert(charSet.size === 0, 'Generated all documented characters.') - }) -} -*/ diff --git a/lib/random.js b/lib/random.js deleted file mode 100644 index 79447ad6..00000000 --- a/lib/random.js +++ /dev/null @@ -1,3 +0,0 @@ -/** - * @module random - */ diff --git a/lib/string.js b/lib/string.js deleted file mode 100644 index 68016c07..00000000 --- a/lib/string.js +++ /dev/null @@ -1,6 +0,0 @@ -/** - * @module string - */ - -export const fromCharCode = String.fromCharCode -export const fromCodePoint = String.fromCodePoint diff --git a/lib/testing.js b/lib/testing.js deleted file mode 100644 index 43c85a2a..00000000 --- a/lib/testing.js +++ /dev/null @@ -1,37 +0,0 @@ -/** - * @module testing - */ - -import * as logging from './logging.js' -import { simpleDiff } from './diff.js' - -export const run = async (name, f) => { - console.log(`%cStart:%c ${name}`, 'color:blue;', '') - const start = new Date() - try { - await f(name) - } catch (e) { - logging.print(`%cFailure:%c ${name} in %c${new Date().getTime() - start.getTime()}ms`, 'color:red;font-weight:bold', '', 'color:grey') - throw e - } - logging.print(`%cSuccess:%c ${name} in %c${new Date().getTime() - start.getTime()}ms`, 'color:green;font-weight:bold', '', 'color:grey') -} - -export const compareArrays = (as, bs) => { - if (as.length !== bs.length) { - return false - } - for (let i = 0; i < as.length; i++) { - if (as[i] !== bs[i]) { - return false - } - } - return true -} - -export const compareStrings = (a, b) => { - if (a !== b) { - const diff = simpleDiff(a, b) - logging.print(`%c${a.slice(0, diff.pos)}%c${a.slice(diff.pos, diff.remove)}%c${diff.insert}%c${a.slice(diff.pos + diff.remove)}`, 'color:grey', 'color:red', 'color:green', 'color:grey') - } -} diff --git a/lib/time.js b/lib/time.js deleted file mode 100644 index adcbb3b7..00000000 --- a/lib/time.js +++ /dev/null @@ -1,3 +0,0 @@ - -export const getDate = () => new Date() -export const getUnixTime = () => getDate().getTime() diff --git a/package-lock.json b/package-lock.json index 7186c062..a426692b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,9 +34,9 @@ } }, "chalk": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", - "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "requires": { "ansi-styles": "^3.2.1", @@ -62,15 +62,9 @@ } }, "@types/estree": { - "version": "0.0.38", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.38.tgz", - "integrity": "sha512-F/v7t1LwS4vnXuPooJQGBRKRGIoxWUTmA4VHfqjOccFsNDThD5bfUNpITive6s352O7o384wcpEaDV8rHCehDA==", - "dev": true - }, - "@types/events": { - "version": "1.2.0", - "resolved": "http://registry.npmjs.org/@types/events/-/events-1.2.0.tgz", - "integrity": "sha512-KEIlhXnIutzKwRbQkGWb/I4HFqBuUykAdHgDED6xqwXJfONCjF5VoE0cXEiurh3XauygxzeDzgtXUqvLkxFzzA==", + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", "dev": true }, "@types/node": { @@ -79,16 +73,6 @@ "integrity": "sha512-LiaH3mF+OAqR+9Wo1OTJDbZDtCewAVjTbMhF1ZgUJ3fc8xqOJq6VqbpBh9dJVCVzByGmYIg2fREbuXNX0TKiJA==", "dev": true }, - "@types/ws": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-6.0.1.tgz", - "integrity": "sha512-EzH8k1gyZ4xih/MaZTXwT2xOkPiIMSrhQ9b8wrlX88L0T02eYsddatQlwVFlEPyEqV0ChpdpNnE51QPH6NVT4Q==", - "dev": true, - "requires": { - "@types/events": "*", - "@types/node": "*" - } - }, "abab": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/abab/-/abab-1.0.4.tgz", @@ -96,14 +80,6 @@ "dev": true, "optional": true }, - "abstract-leveldown": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-5.0.0.tgz", - "integrity": "sha512-5mU5P1gXtsMIXg65/rsYGsi93+MlogXZ9FA8JnwKurHQg64bfXwGYVdVdijNTVNOlAsuIiOwHdvFFD5JqCJQ7A==", - "requires": { - "xtend": "~4.0.0" - } - }, "accepts": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", @@ -131,12 +107,6 @@ } } }, - "acorn": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", - "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", - "dev": true - }, "acorn-globals": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-1.0.9.tgz", @@ -200,7 +170,8 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true }, "ansi-styles": { "version": "2.2.1", @@ -233,22 +204,6 @@ "integrity": "sha1-7klza2ObTxCLbp5ibG2pkwa0FpI=", "dev": true }, - "aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", - "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", - "optional": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -273,12 +228,6 @@ "integrity": "sha1-onTthawIhJtr14R8RYB0XcUa37E=", "dev": true }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true - }, "array-find-index": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", @@ -344,24 +293,12 @@ "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", "dev": true }, - "async-array-reduce": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/async-array-reduce/-/async-array-reduce-0.2.1.tgz", - "integrity": "sha1-yL4BCitc0A3qlsgRFgNGk9/dgtE=", - "dev": true - }, "async-each": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", "dev": true }, - "async-limiter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", - "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", - "optional": true - }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -606,147 +543,6 @@ "js-tokens": "^3.0.0" } }, - "babel-core": { - "version": "6.26.3", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", - "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", - "dev": true, - "requires": { - "babel-code-frame": "^6.26.0", - "babel-generator": "^6.26.0", - "babel-helpers": "^6.24.1", - "babel-messages": "^6.23.0", - "babel-register": "^6.26.0", - "babel-runtime": "^6.26.0", - "babel-template": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "convert-source-map": "^1.5.1", - "debug": "^2.6.9", - "json5": "^0.5.1", - "lodash": "^4.17.4", - "minimatch": "^3.0.4", - "path-is-absolute": "^1.0.1", - "private": "^0.1.8", - "slash": "^1.0.0", - "source-map": "^0.5.7" - }, - "dependencies": { - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" - } - }, - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - } - }, - "babel-types": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", - "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "esutils": "^2.0.2", - "lodash": "^4.17.4", - "to-fast-properties": "^1.0.3" - } - }, - "convert-source-map": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", - "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.1" - } - }, - "private": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", - "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", - "dev": true - }, - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, - "babel-generator": { - "version": "6.26.1", - "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", - "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", - "dev": true, - "requires": { - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "detect-indent": "^4.0.0", - "jsesc": "^1.3.0", - "lodash": "^4.17.4", - "source-map": "^0.5.7", - "trim-right": "^1.0.1" - }, - "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - } - }, - "babel-types": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", - "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "esutils": "^2.0.2", - "lodash": "^4.17.4", - "to-fast-properties": "^1.0.3" - } - }, - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } - } - }, "babel-helper-builder-binary-assignment-operator-visitor": { "version": "6.24.1", "resolved": "https://registry.npmjs.org/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz", @@ -1432,45 +1228,6 @@ "babel-preset-es2017": "^6.24.1" } }, - "babel-register": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", - "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", - "dev": true, - "requires": { - "babel-core": "^6.26.0", - "babel-runtime": "^6.26.0", - "core-js": "^2.5.0", - "home-or-tmp": "^2.0.0", - "lodash": "^4.17.4", - "mkdirp": "^0.5.1", - "source-map-support": "^0.4.15" - }, - "dependencies": { - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - } - }, - "core-js": { - "version": "2.5.7", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz", - "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==", - "dev": true - }, - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true - } - } - }, "babel-runtime": { "version": "6.23.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.23.0.tgz", @@ -1606,12 +1363,6 @@ "to-fast-properties": "^1.0.1" } }, - "babylon": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", - "dev": true - }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", @@ -1663,54 +1414,6 @@ "integrity": "sha1-SOyNFt9Dd+rl+liEaCSAr02Vx3Q=", "dev": true }, - "bindings": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.3.1.tgz", - "integrity": "sha512-i47mqjF9UbjxJhxGf+pZ6kSxrnI3wBLlnGI2ArWJ4r0VrvDS7ZYXkprq/pLaBWYq4GM0r4zdHY+NNRqEMU7uew==", - "optional": true - }, - "bl": { - "version": "1.2.2", - "resolved": "http://registry.npmjs.org/bl/-/bl-1.2.2.tgz", - "integrity": "sha512-e8tQYnZodmebYDWGH7KMRvtzKXaJHx3BbilrgZCfvyLUYdKpK1t5PSPmpkny/SgiTSCnjfLW7v5rlONXVFkQEA==", - "optional": true, - "requires": { - "readable-stream": "^2.3.5", - "safe-buffer": "^5.1.1" - }, - "dependencies": { - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", - "optional": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, "bluebird": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.3.tgz", @@ -1744,28 +1447,6 @@ "repeat-element": "^1.1.2" } }, - "buffer-alloc": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", - "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", - "optional": true, - "requires": { - "buffer-alloc-unsafe": "^1.1.0", - "buffer-fill": "^1.0.0" - } - }, - "buffer-alloc-unsafe": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", - "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", - "optional": true - }, - "buffer-fill": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", - "integrity": "sha1-+PeLdniYiO858gXNY39o5wISKyw=", - "optional": true - }, "buffer-from": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.0.0.tgz", @@ -1887,12 +1568,6 @@ "readdirp": "^2.0.0" } }, - "chownr": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz", - "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", - "optional": true - }, "circular-json": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", @@ -1926,12 +1601,6 @@ "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", "dev": true }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "optional": true - }, "codemirror": { "version": "5.42.0", "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.42.0.tgz", @@ -2142,11 +1811,6 @@ } } }, - "console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" - }, "contains-path": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", @@ -2168,7 +1832,8 @@ "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true }, "crel": { "version": "3.1.0", @@ -2278,43 +1943,18 @@ "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", "dev": true }, - "decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", - "optional": true, - "requires": { - "mimic-response": "^1.0.0" - } - }, "deep-equal": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", "dev": true }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "optional": true - }, "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, - "deferred-leveldown": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-4.0.2.tgz", - "integrity": "sha512-5fMC8ek8alH16QiV0lTCis610D1Zt1+LA4MS4d63JgS32lrCjTFDUFz2ao09/j2I4Bqb5jL4FZYwu7Jz0XO1ww==", - "optional": true, - "requires": { - "abstract-leveldown": "~5.0.0", - "inherits": "^2.0.3" - } - }, "define-properties": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz", @@ -2360,12 +2000,6 @@ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", "dev": true }, - "delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", - "optional": true - }, "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -2387,12 +2021,6 @@ "repeating": "^2.0.0" } }, - "detect-libc": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", - "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", - "optional": true - }, "doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -2483,41 +2111,12 @@ "iconv-lite": "~0.4.13" } }, - "encoding-down": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/encoding-down/-/encoding-down-5.0.4.tgz", - "integrity": "sha512-8CIZLDcSKxgzT+zX8ZVfgNbu8Md2wq/iqa1Y7zyVR18QBEAc0Nmzuvj/N5ykSKpfGzjM8qxbaFntLPwnVoUhZw==", - "optional": true, - "requires": { - "abstract-leveldown": "^5.0.0", - "inherits": "^2.0.3", - "level-codec": "^9.0.0", - "level-errors": "^2.0.0", - "xtend": "^4.0.1" - } - }, - "end-of-stream": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz", - "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", - "requires": { - "once": "^1.4.0" - } - }, "entities": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.1.tgz", "integrity": "sha1-blwtClYhtdra7O+AuQ7ftc13cvA=", "dev": true }, - "errno": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", - "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", - "requires": { - "prr": "~1.0.1" - } - }, "error-ex": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", @@ -3208,6 +2807,12 @@ "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", "dev": true }, + "esm": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.6.tgz", + "integrity": "sha512-3wWjSurKSczMzYyHiBih3VVEQYCoZa6nfsqqcM2Tx6KBAQAeor0SZUfAol+zeVUtESLygayOi2ZcMfYZy7MCsg==", + "dev": true + }, "espree": { "version": "3.5.4", "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", @@ -3258,9 +2863,9 @@ "dev": true }, "estree-walker": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.2.1.tgz", - "integrity": "sha1-va/oCVOD2EFNXcLs9MkXO225QS4=", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.5.2.tgz", + "integrity": "sha512-XpCnW/AE10ws/kDAs37cngSkvgIR8aN3G0MS85m7dUpuK2EREo9VJ00uvw6Dg/hXEpfsE1I1TvJOJr+Z+TL+ig==", "dev": true }, "esutils": { @@ -3293,36 +2898,12 @@ "fill-range": "^2.1.0" } }, - "expand-template": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-1.1.1.tgz", - "integrity": "sha512-cebqLtV8KOZfw0UI8TEFWxtczxxC1jvyUvx6H4fyp1K1FN7A4Q+uggVUlOsI1K8AGU0rwOGqP8nCapdrw8CYQg==", - "optional": true - }, - "expand-tilde": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-1.2.2.tgz", - "integrity": "sha1-C4HrqJflo9MdHD0QL48BRB5VlEk=", - "dev": true, - "requires": { - "os-homedir": "^1.0.1" - } - }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "dev": true }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, "external-editor": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", @@ -3361,12 +2942,6 @@ "integrity": "sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig==", "dev": true }, - "fast-future": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/fast-future/-/fast-future-1.0.2.tgz", - "integrity": "sha1-hDWpqqAteSSNF9cE52JZMB2ZKAo=", - "optional": true - }, "fast-json-stable-stringify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", @@ -3553,18 +3128,6 @@ "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", "dev": true }, - "fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "optional": true - }, - "fs-exists-sync": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz", - "integrity": "sha1-mC1ok6+RjnLQjeyehnP/K1qNat0=", - "dev": true - }, "fs-extra": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-1.0.0.tgz", @@ -4129,40 +3692,31 @@ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, - "gauge": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", - "optional": true, + "funlib": { + "version": "file:../funlib", "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" + "rollup": "^1.1.2" }, "dependencies": { - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "optional": true, - "requires": { - "number-is-nan": "^1.0.0" - } + "@types/estree": { + "version": "0.0.39", + "bundled": true }, - "string-width": { - "version": "1.0.2", - "resolved": "http://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "optional": true, + "@types/node": { + "version": "10.12.21", + "bundled": true + }, + "acorn": { + "version": "6.0.7", + "bundled": true + }, + "rollup": { + "version": "1.1.2", + "bundled": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "@types/estree": "0.0.39", + "@types/node": "*", + "acorn": "^6.0.5" } } } @@ -4183,12 +3737,6 @@ "assert-plus": "^1.0.0" } }, - "github-from-package": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4=", - "optional": true - }, "glob": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", @@ -4222,28 +3770,6 @@ "is-glob": "^2.0.0" } }, - "global-modules": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-0.2.3.tgz", - "integrity": "sha1-6lo77ULG1s6ZWk+KEmm12uIjgo0=", - "dev": true, - "requires": { - "global-prefix": "^0.1.4", - "is-windows": "^0.2.0" - } - }, - "global-prefix": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-0.1.5.tgz", - "integrity": "sha1-jTvGuNo8qBEqFg2NSW/wRiv+948=", - "dev": true, - "requires": { - "homedir-polyfill": "^1.0.0", - "ini": "^1.3.4", - "is-windows": "^0.2.0", - "which": "^1.2.12" - } - }, "globals": { "version": "9.18.0", "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", @@ -4318,21 +3844,6 @@ "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, - "has-glob": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/has-glob/-/has-glob-0.1.1.tgz", - "integrity": "sha1-omHEwqbGZ+DHe3AKfyl8Oe86pYk=", - "dev": true, - "requires": { - "is-glob": "^2.0.1" - } - }, - "has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", - "optional": true - }, "home-or-tmp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", @@ -4343,15 +3854,6 @@ "os-tmpdir": "^1.0.1" } }, - "homedir-polyfill": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz", - "integrity": "sha1-TCu8inWJmP7r9e1oWA921GdotLw=", - "dev": true, - "requires": { - "parse-passwd": "^1.0.0" - } - }, "hosted-git-info": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz", @@ -4545,12 +4047,8 @@ "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", + "dev": true }, "inquirer": { "version": "3.3.0", @@ -4716,7 +4214,8 @@ "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true }, "is-glob": { "version": "2.0.1", @@ -4824,18 +4323,6 @@ "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", "dev": true }, - "is-valid-glob": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/is-valid-glob/-/is-valid-glob-0.3.0.tgz", - "integrity": "sha1-1LVcafUYhvm2XHDWwmItN+KfSP4=", - "dev": true - }, - "is-windows": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-0.2.0.tgz", - "integrity": "sha1-3hqm1j6indJIc3tp8f+LgALSEIw=", - "dev": true - }, "is-wsl": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", @@ -4845,7 +4332,8 @@ "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true }, "isexe": { "version": "2.0.0", @@ -4880,12 +4368,24 @@ "optional": true }, "jest-worker": { - "version": "23.2.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-23.2.0.tgz", - "integrity": "sha1-+vcGqNo2+uYOsmlXJX+ntdjqArk=", + "version": "24.0.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-24.0.0.tgz", + "integrity": "sha512-s64/OThpfQvoCeHG963MiEZOAAxu8kHsaL/rCMF7lpdzo7vgF0CtPml9hfguOMgykgH/eOm4jFP4ibfHLruytg==", "dev": true, "requires": { - "merge-stream": "^1.0.1" + "merge-stream": "^1.0.1", + "supports-color": "^6.1.0" + }, + "dependencies": { + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "js-tokens": { @@ -5096,126 +4596,6 @@ "graceful-fs": "^4.1.9" } }, - "lazy-cache": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-2.0.2.tgz", - "integrity": "sha1-uRkKT5EzVGlIQIWfio9whNiCImQ=", - "dev": true, - "requires": { - "set-getter": "^0.1.0" - } - }, - "level": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/level/-/level-4.0.0.tgz", - "integrity": "sha512-4epzCOlEcJ529NOdlAYiuiakS/kZTDdiKSBNJmE1B8bsmA+zEVwcpxyH86qJSQTpOu7SODrlaD9WgPRHLkGutA==", - "optional": true, - "requires": { - "level-packager": "^3.0.0", - "leveldown": "^4.0.0", - "opencollective-postinstall": "^2.0.0" - } - }, - "level-codec": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/level-codec/-/level-codec-9.0.0.tgz", - "integrity": "sha512-OIpVvjCcZNP5SdhcNupnsI1zo5Y9Vpm+k/F1gfG5kXrtctlrwanisakweJtE0uA0OpLukRfOQae+Fg0M5Debhg==", - "optional": true - }, - "level-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/level-errors/-/level-errors-2.0.0.tgz", - "integrity": "sha512-AmY4HCp9h3OiU19uG+3YWkdELgy05OTP/r23aNHaQKWv8DO787yZgsEuGVkoph40uwN+YdUKnANlrxSsoOaaxg==", - "requires": { - "errno": "~0.1.1" - } - }, - "level-iterator-stream": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/level-iterator-stream/-/level-iterator-stream-3.0.1.tgz", - "integrity": "sha512-nEIQvxEED9yRThxvOrq8Aqziy4EGzrxSZK+QzEFAVuJvQ8glfyZ96GB6BoI4sBbLfjMXm2w4vu3Tkcm9obcY0g==", - "optional": true, - "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.3.6", - "xtend": "^4.0.0" - }, - "dependencies": { - "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", - "optional": true - }, - "readable-stream": { - "version": "2.3.6", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "http://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "level-packager": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/level-packager/-/level-packager-3.1.0.tgz", - "integrity": "sha512-UxVEfK5WH0u0InR3WxTCSAroiorAGKzXWZT6i+nBjambmvINuXFUsFx2Ai3UIjUUtnyWhluv42jMlzUZCsAk9A==", - "optional": true, - "requires": { - "encoding-down": "~5.0.0", - "levelup": "^3.0.0" - } - }, - "leveldown": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/leveldown/-/leveldown-4.0.1.tgz", - "integrity": "sha512-ZlBKVSsglPIPJnz4ggB8o2R0bxDxbsMzuQohbfgoFMVApyTE118DK5LNRG0cRju6rt3OkGxe0V6UYACGlq/byg==", - "optional": true, - "requires": { - "abstract-leveldown": "~5.0.0", - "bindings": "~1.3.0", - "fast-future": "~1.0.2", - "nan": "~2.10.0", - "prebuild-install": "^4.0.0" - }, - "dependencies": { - "nan": { - "version": "2.10.0", - "resolved": "http://registry.npmjs.org/nan/-/nan-2.10.0.tgz", - "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==", - "optional": true - } - } - }, - "levelup": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/levelup/-/levelup-3.1.1.tgz", - "integrity": "sha512-9N10xRkUU4dShSRRFTBdNaBxofz+PGaIZO962ckboJZiNmLuhVT6FZ6ZKAsICKfUBO76ySaYU6fJWX/jnj3Lcg==", - "optional": true, - "requires": { - "deferred-leveldown": "~4.0.0", - "level-errors": "~2.0.0", - "level-iterator-stream": "~3.0.0", - "xtend": "~4.0.0" - } - }, "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", @@ -5505,12 +4885,12 @@ } }, "magic-string": { - "version": "0.22.5", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.22.5.tgz", - "integrity": "sha512-oreip9rJZkzvA8Qzk9HFs8fZGF/u7H/gtrE8EN6RjKJ9kh2HlC+yQ2QezifqTZfGyiuAV0dRv5a+y/8gBb1m9w==", + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.2.tgz", + "integrity": "sha512-iLs9mPjh9IuTtRsqqhNGYcZXGei0Nh/A4xirrsqW7c+QhKVFL2vm7U09ru6cHRD22azaP/wMDgI+HCqbETMTtg==", "dev": true, "requires": { - "vlq": "^0.2.2" + "sourcemap-codec": "^1.4.4" } }, "map-obj": { @@ -5531,23 +4911,6 @@ "integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==", "dev": true }, - "matched": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/matched/-/matched-0.4.4.tgz", - "integrity": "sha1-Vte36xgDPwz5vFLrIJD6x9weifo=", - "dev": true, - "requires": { - "arr-union": "^3.1.0", - "async-array-reduce": "^0.2.0", - "extend-shallow": "^2.0.1", - "fs-exists-sync": "^0.1.0", - "glob": "^7.0.5", - "has-glob": "^0.1.1", - "is-valid-glob": "^0.3.0", - "lazy-cache": "^2.0.1", - "resolve-dir": "^0.1.0" - } - }, "math-random": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.1.tgz", @@ -5633,12 +4996,6 @@ "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", "dev": true }, - "mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "optional": true - }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -5651,12 +5008,14 @@ "minimist": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true }, "mkdirp": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, "requires": { "minimist": "0.0.8" } @@ -5705,23 +5064,6 @@ "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=", "dev": true }, - "node-abi": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.5.0.tgz", - "integrity": "sha512-9g2twBGSP6wIR5PW7tXvAWnEWKJDH/VskdXp168xsw9VVxpEGov8K4jsP4/VeoC7b2ZAyzckvMCuQuQlw44lXg==", - "optional": true, - "requires": { - "semver": "^5.4.1" - }, - "dependencies": { - "semver": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", - "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", - "optional": true - } - } - }, "node-fetch": { "version": "1.7.3", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz", @@ -5732,12 +5074,6 @@ "is-stream": "^1.0.1" } }, - "noop-logger": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz", - "integrity": "sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI=", - "optional": true - }, "normalize-package-data": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", @@ -5759,18 +5095,6 @@ "remove-trailing-separator": "^1.0.1" } }, - "npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", - "optional": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, "nth-check": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.1.tgz", @@ -5783,7 +5107,8 @@ "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true }, "nwmatcher": { "version": "1.4.3", @@ -5802,7 +5127,8 @@ "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true }, "object-keys": { "version": "1.0.11", @@ -5839,6 +5165,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, "requires": { "wrappy": "1" } @@ -5852,12 +5179,6 @@ "mimic-fn": "^1.0.0" } }, - "opencollective-postinstall": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.1.tgz", - "integrity": "sha512-saQQ9hjLwu/oS0492eyYotoh+bra1819cfAT5rjY/e4REWwuc8IgZ844Oo44SiftWcJuBiqp0SA0BFVbmLX0IQ==", - "optional": true - }, "optionator": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", @@ -5889,7 +5210,8 @@ "os-homedir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true }, "os-tmpdir": { "version": "1.0.2", @@ -5959,12 +5281,6 @@ "error-ex": "^1.2.0" } }, - "parse-passwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", - "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", - "dev": true - }, "parse5": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/parse5/-/parse5-1.5.1.tgz", @@ -6128,37 +5444,6 @@ "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", "dev": true }, - "prebuild-install": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-4.0.0.tgz", - "integrity": "sha512-7tayxeYboJX0RbVzdnKyGl2vhQRWr6qfClEXDhOkXjuaOKCw2q8aiuFhONRYVsG/czia7KhpykIlI2S2VaPunA==", - "optional": true, - "requires": { - "detect-libc": "^1.0.3", - "expand-template": "^1.0.2", - "github-from-package": "0.0.0", - "minimist": "^1.2.0", - "mkdirp": "^0.5.1", - "node-abi": "^2.2.0", - "noop-logger": "^0.1.1", - "npmlog": "^4.0.1", - "os-homedir": "^1.0.1", - "pump": "^2.0.1", - "rc": "^1.1.6", - "simple-get": "^2.7.0", - "tar-fs": "^1.13.0", - "tunnel-agent": "^0.6.0", - "which-pm-runs": "^1.0.0" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "optional": true - } - } - }, "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", @@ -6180,7 +5465,8 @@ "process-nextick-args": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", + "dev": true }, "progress": { "version": "2.0.0", @@ -6360,11 +5646,6 @@ "prosemirror-transform": "^1.1.0" } }, - "prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=" - }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", @@ -6378,16 +5659,6 @@ "dev": true, "optional": true }, - "pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", - "optional": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", @@ -6473,26 +5744,6 @@ "integrity": "sha1-vOeMkhsjWCuuIR9ZdGSlkf0alPk=", "dev": true }, - "rc": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", - "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", - "optional": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "optional": true - } - } - }, "read-pkg": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", @@ -6542,6 +5793,7 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "dev": true, "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -6790,16 +6042,6 @@ "path-parse": "^1.0.5" } }, - "resolve-dir": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-0.1.1.tgz", - "integrity": "sha1-shklmlYC+sXFxJatiUpujMQwJh4=", - "dev": true, - "requires": { - "expand-tilde": "^1.2.2", - "global-modules": "^0.2.3" - } - }, "resolve-from": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", @@ -6826,13 +6068,22 @@ } }, "rollup": { - "version": "0.58.2", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-0.58.2.tgz", - "integrity": "sha512-RZVvCWm9BHOYloaE6LLiE/ibpjv1CmI8F8k0B0Cp+q1eezo3cswszJH1DN0djgzSlo0hjuuCmyeI+1XOYLl4wg==", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-1.1.2.tgz", + "integrity": "sha512-OkdMxqMl8pWoQc5D8y1cIinYQPPLV8ZkfLgCzL6SytXeNA2P7UHynEQXI9tYxuAjAMsSyvRaWnyJDLHMxq0XAg==", "dev": true, "requires": { - "@types/estree": "0.0.38", - "@types/node": "*" + "@types/estree": "0.0.39", + "@types/node": "*", + "acorn": "^6.0.5" + }, + "dependencies": { + "acorn": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.1.0.tgz", + "integrity": "sha512-MW/FjM+IvU9CgBzjO3UIPCE2pyEwUsoFl+VGdczOPEdxfGFjuKny/gN54mOuX7Qxmb9Rg9MCn2oKiSUeW+pjrw==", + "dev": true + } } }, "rollup-cli": { @@ -6841,134 +6092,79 @@ "integrity": "sha1-N/ShwgYxHikuMpfql3eduKIduZQ=", "dev": true }, - "rollup-plugin-babel": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rollup-plugin-babel/-/rollup-plugin-babel-2.7.1.tgz", - "integrity": "sha1-FlKBl7D5OKFTb0RoPHqT1XMYL1c=", - "dev": true, - "requires": { - "babel-core": "6", - "babel-plugin-transform-es2015-classes": "^6.9.0", - "object-assign": "^4.1.0", - "rollup-pluginutils": "^1.5.0" - } - }, "rollup-plugin-commonjs": { - "version": "8.4.1", - "resolved": "https://registry.npmjs.org/rollup-plugin-commonjs/-/rollup-plugin-commonjs-8.4.1.tgz", - "integrity": "sha512-mg+WuD+jlwoo8bJtW3Mvx7Tz6TsIdMsdhuvCnDMoyjh0oxsVgsjB/N0X984RJCWwc5IIiqNVJhXeeITcc73++A==", + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-commonjs/-/rollup-plugin-commonjs-9.2.0.tgz", + "integrity": "sha512-0RM5U4Vd6iHjL6rLvr3lKBwnPsaVml+qxOGaaNUWN1lSq6S33KhITOfHmvxV3z2vy9Mk4t0g4rNlVaJJsNQPWA==", "dev": true, "requires": { - "acorn": "^5.2.1", - "estree-walker": "^0.5.0", - "magic-string": "^0.22.4", - "resolve": "^1.4.0", - "rollup-pluginutils": "^2.0.1" + "estree-walker": "^0.5.2", + "magic-string": "^0.25.1", + "resolve": "^1.8.1", + "rollup-pluginutils": "^2.3.3" }, "dependencies": { - "estree-walker": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.5.2.tgz", - "integrity": "sha512-XpCnW/AE10ws/kDAs37cngSkvgIR8aN3G0MS85m7dUpuK2EREo9VJ00uvw6Dg/hXEpfsE1I1TvJOJr+Z+TL+ig==", + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", "dev": true }, "resolve": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", - "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz", + "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==", "dev": true, "requires": { - "path-parse": "^1.0.5" - } - }, - "rollup-pluginutils": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.3.3.tgz", - "integrity": "sha512-2XZwja7b6P5q4RZ5FhyX1+f46xi1Z3qBKigLRZ6VTZjwbN0K1IFGMlwm06Uu0Emcre2Z63l77nq/pzn+KxIEoA==", - "dev": true, - "requires": { - "estree-walker": "^0.5.2", - "micromatch": "^2.3.11" + "path-parse": "^1.0.6" } } } }, - "rollup-plugin-inject": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-inject/-/rollup-plugin-inject-2.2.0.tgz", - "integrity": "sha512-Wow9g+qkKbkK96wjLif2HqWOiuR6ZqkZbHSNt5r1bVUDQG96yzmuxlSl1grPzlTG5BbATUE7nA5HhQVfBXEigQ==", - "dev": true, - "requires": { - "estree-walker": "^0.5.0", - "magic-string": "^0.25.0", - "rollup-pluginutils": "^2.0.1" - }, - "dependencies": { - "estree-walker": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.5.2.tgz", - "integrity": "sha512-XpCnW/AE10ws/kDAs37cngSkvgIR8aN3G0MS85m7dUpuK2EREo9VJ00uvw6Dg/hXEpfsE1I1TvJOJr+Z+TL+ig==", - "dev": true - }, - "magic-string": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.1.tgz", - "integrity": "sha512-sCuTz6pYom8Rlt4ISPFn6wuFodbKMIHUMv4Qko9P17dpxb7s52KJTmRuZZqHdGmLCK9AOcDare039nRIcfdkEg==", - "dev": true, - "requires": { - "sourcemap-codec": "^1.4.1" - } - }, - "rollup-pluginutils": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.3.3.tgz", - "integrity": "sha512-2XZwja7b6P5q4RZ5FhyX1+f46xi1Z3qBKigLRZ6VTZjwbN0K1IFGMlwm06Uu0Emcre2Z63l77nq/pzn+KxIEoA==", - "dev": true, - "requires": { - "estree-walker": "^0.5.2", - "micromatch": "^2.3.11" - } - } - } - }, - "rollup-plugin-multi-entry": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/rollup-plugin-multi-entry/-/rollup-plugin-multi-entry-2.0.2.tgz", - "integrity": "sha512-TY72fCVJvcEAQBpBzkXykoYQx2fz0B20EVtcbh0WZaYr5eBu3U1dRPzgMt6aO8MePWWOdcmgoBtG6PhmYJr4Ew==", - "dev": true, - "requires": { - "matched": "^0.4.4" - } - }, "rollup-plugin-node-resolve": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-3.4.0.tgz", - "integrity": "sha512-PJcd85dxfSBWih84ozRtBkB731OjXk0KnzN0oGp7WOWcarAFkVa71cV5hTJg2qpVsV2U8EUwrzHP3tvy9vS3qg==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-4.0.0.tgz", + "integrity": "sha512-7Ni+/M5RPSUBfUaP9alwYQiIKnKeXCOHiqBpKUl9kwp3jX5ZJtgXAait1cne6pGEVUUztPD6skIKH9Kq9sNtfw==", "dev": true, "requires": { - "builtin-modules": "^2.0.0", + "builtin-modules": "^3.0.0", "is-module": "^1.0.0", - "resolve": "^1.1.6" + "resolve": "^1.8.1" }, "dependencies": { "builtin-modules": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-2.0.0.tgz", - "integrity": "sha512-3U5kUA5VPsRUA3nofm/BXX7GVHKfxz0hOBAPxXrIvHzlDRkQVqEn6yi8QJegxl4LzOHLdvb7XF5dVawa/VVYBg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.0.0.tgz", + "integrity": "sha512-hMIeU4K2ilbXV6Uv93ZZ0Avg/M91RaKXucQ+4me2Do1txxBDyDZWCBa5bJSLqoNTRpXTLwEzIk1KmloenDDjhg==", "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "resolve": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz", + "integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } } } }, - "rollup-plugin-uglify": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-uglify/-/rollup-plugin-uglify-6.0.0.tgz", - "integrity": "sha512-XtzZd159QuOaXNvcxyBcbUCSoBsv5YYWK+7ZwUyujSmISst8avRfjWlp7cGu8T2O52OJnpEBvl+D4WLV1k1iQQ==", + "rollup-plugin-terser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/rollup-plugin-terser/-/rollup-plugin-terser-4.0.4.tgz", + "integrity": "sha512-wPANT5XKVJJ8RDUN0+wIr7UPd0lIXBo4UdJ59VmlPCtlFsE20AM+14pe+tk7YunCsWEiuzkDBY3QIkSCjtrPXg==", "dev": true, "requires": { "@babel/code-frame": "^7.0.0", - "jest-worker": "^23.2.0", - "serialize-javascript": "^1.5.0", - "uglify-js": "^3.4.9" + "jest-worker": "^24.0.0", + "serialize-javascript": "^1.6.1", + "terser": "^3.14.1" } }, "rollup-plugin-uglify-es": { @@ -6978,56 +6174,27 @@ "dev": true, "requires": { "uglify-es": "3.0.3" - }, - "dependencies": { - "commander": { - "version": "2.9.0", - "resolved": "http://registry.npmjs.org/commander/-/commander-2.9.0.tgz", - "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", - "dev": true, - "requires": { - "graceful-readlink": ">= 1.0.0" - } - }, - "uglify-es": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.0.3.tgz", - "integrity": "sha1-Y8yEqpRos0lzpIh3h8ZMAaiodXY=", - "dev": true, - "requires": { - "commander": "~2.9.0", - "source-map": "~0.5.1", - "uglify-to-browserify": "~1.0.0" - } - } } }, "rollup-pluginutils": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-1.5.2.tgz", - "integrity": "sha1-HhVud4+UtyVb+hs9AXi+j1xVJAg=", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.3.3.tgz", + "integrity": "sha512-2XZwja7b6P5q4RZ5FhyX1+f46xi1Z3qBKigLRZ6VTZjwbN0K1IFGMlwm06Uu0Emcre2Z63l77nq/pzn+KxIEoA==", "dev": true, "requires": { - "estree-walker": "^0.2.1", - "minimatch": "^3.0.2" - } - }, - "rollup-regenerator-runtime": { - "version": "6.23.1", - "resolved": "https://registry.npmjs.org/rollup-regenerator-runtime/-/rollup-regenerator-runtime-6.23.1.tgz", - "integrity": "sha1-/Su7Z1/gkzUSQ+hyhzooHa2mvLY=", - "dev": true, - "requires": { - "regenerator-runtime": "^0.10.0" + "estree-walker": "^0.5.2", + "micromatch": "^2.3.11" } }, "rollup-watch": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/rollup-watch/-/rollup-watch-3.2.2.tgz", - "integrity": "sha1-XldCMunvNtqRd/RpRtgIDLJnNUs=", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/rollup-watch/-/rollup-watch-4.3.1.tgz", + "integrity": "sha512-6yjnIwfjpSrqA8IafyIu7fsEyeImNR4aDjA1bQ7KWeVuiA+Clfsx8+PGQkyABWIQzmauQ//tIJ5wAxLXsXs8qQ==", "dev": true, "requires": { - "require-relative": "0.8.7" + "chokidar": "^1.7.0", + "require-relative": "0.8.7", + "rollup-pluginutils": "^2.0.1" } }, "rope-sequence": { @@ -7075,7 +6242,8 @@ "safe-buffer": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -7097,9 +6265,9 @@ "dev": true }, "serialize-javascript": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.5.0.tgz", - "integrity": "sha512-Ga8c8NjAAp46Br4+0oZ2WxJCwIzwP60Gq1YPgU+39PiTVxyed/iKE/zyZI6+UlVYH5Q4PaQdHhcegIFPZTUfoQ==", + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-1.6.1.tgz", + "integrity": "sha512-A5MOagrPFga4YaKQSWHryl7AXvbQkEqpw4NNYMTNYUNV51bA8ABHgYFpqKx+YFFrw59xMV1qGH1R4AgoNIVgCw==", "dev": true }, "serve-index": { @@ -7140,21 +6308,6 @@ } } }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "optional": true - }, - "set-getter": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/set-getter/-/set-getter-0.1.0.tgz", - "integrity": "sha1-12nBgsnVpR9AkUXy+6guXoboA3Y=", - "dev": true, - "requires": { - "to-object-path": "^0.3.0" - } - }, "set-immediate-shim": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", @@ -7191,24 +6344,8 @@ "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" - }, - "simple-concat": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.0.tgz", - "integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY=", - "optional": true - }, - "simple-get": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-2.8.1.tgz", - "integrity": "sha512-lSSHRSw3mQNUGPAYRqo7xy9dhKmxFXIjLjp4KHpf99GEH2VH7C3AM+Qfx6du6jhfUi6Vm7XnbEVEf7Wb6N8jRw==", - "optional": true, - "requires": { - "decompress-response": "^3.3.0", - "once": "^1.3.1", - "simple-concat": "^1.0.0" - } + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true }, "slash": { "version": "1.0.0", @@ -7241,9 +6378,9 @@ } }, "sourcemap-codec": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.3.tgz", - "integrity": "sha512-vFrY/x/NdsD7Yc8mpTJXuao9S8lq08Z/kOITHz6b7YbfI9xL8Spe5EvSQUHOI7SbpY8bRPr0U3kKSsPuqEGSfA==", + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.4.tgz", + "integrity": "sha512-CYAPYdBu34781kLHkaW3m6b/uUSyMOC2R61gcYMWooeuaGtjof86ZA/8T+qVPPt7np1085CR9hmMGrySwEc8Xg==", "dev": true }, "spawn-command": { @@ -7398,6 +6535,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, "requires": { "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^4.0.0" @@ -7406,12 +6544,14 @@ "ansi-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true }, "strip-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, "requires": { "ansi-regex": "^3.0.0" } @@ -7422,6 +6562,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "dev": true, "requires": { "safe-buffer": "~5.1.0" } @@ -7430,6 +6571,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, "requires": { "ansi-regex": "^2.0.0" } @@ -7460,7 +6602,8 @@ "strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true }, "supports-color": { "version": "2.0.0", @@ -7532,45 +6675,41 @@ "integrity": "sha1-e/gQalwaSCUbPjvAoOFzJIn9Dcg=", "dev": true }, - "tar-fs": { - "version": "1.16.3", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-1.16.3.tgz", - "integrity": "sha512-NvCeXpYx7OsmOh8zIOP/ebG55zZmxLE0etfWRbWok+q2Qo8x/vOR/IJT1taADXPe+jsiu9axDb3X4B+iIgNlKw==", - "optional": true, + "terser": { + "version": "3.16.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-3.16.1.tgz", + "integrity": "sha512-JDJjgleBROeek2iBcSNzOHLKsB/MdDf+E/BOAJ0Tk9r7p9/fVobfv7LMJ/g/k3v9SXdmjZnIlFd5nfn/Rt0Xow==", + "dev": true, "requires": { - "chownr": "^1.0.1", - "mkdirp": "^0.5.1", - "pump": "^1.0.0", - "tar-stream": "^1.1.2" + "commander": "~2.17.1", + "source-map": "~0.6.1", + "source-map-support": "~0.5.9" }, "dependencies": { - "pump": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pump/-/pump-1.0.3.tgz", - "integrity": "sha512-8k0JupWme55+9tCVE+FS5ULT3K6AbgqrGa58lTT49RpyfwwcGedHqaC5LlQNdEAumn/wFsu6aPwkuPMioy8kqw==", - "optional": true, + "commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.10", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.10.tgz", + "integrity": "sha512-YfQ3tQFTK/yzlGJuX8pTwa4tifQj4QS2Mj7UegOu8jAz59MqIiMGPXxQhVQiIMNzayuUSF/jEuVnfFF5JqybmQ==", + "dev": true, "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" } } } }, - "tar-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-1.6.2.tgz", - "integrity": "sha512-rzS0heiNf8Xn7/mpdSVVSMAWAoy9bfb1WOTYC78Z0UQKeKa/CWS8FOq0lKGNa8DWKAn9gxjCvMLYc5PGXYlK2A==", - "optional": true, - "requires": { - "bl": "^1.0.0", - "buffer-alloc": "^1.2.0", - "end-of-stream": "^1.0.0", - "fs-constants": "^1.0.0", - "readable-stream": "^2.3.0", - "to-buffer": "^1.1.1", - "xtend": "^4.0.0" - } - }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -7598,27 +6737,12 @@ "os-tmpdir": "~1.0.2" } }, - "to-buffer": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", - "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==", - "optional": true - }, "to-fast-properties": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", "dev": true }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, "tough-cookie": { "version": "2.3.4", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", @@ -7667,6 +6791,7 @@ "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, "optional": true, "requires": { "safe-buffer": "^5.0.1" @@ -7699,27 +6824,25 @@ "integrity": "sha512-LtzwHlVHwFGTptfNSgezHp7WUlwiqb0gA9AALRbKaERfxwJoiX0A73QbTToxteIAuIaFshhgIZfqK8s7clqgnA==", "dev": true }, - "uglify-js": { - "version": "3.4.9", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz", - "integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==", + "uglify-es": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/uglify-es/-/uglify-es-3.0.3.tgz", + "integrity": "sha1-Y8yEqpRos0lzpIh3h8ZMAaiodXY=", "dev": true, "requires": { - "commander": "~2.17.1", - "source-map": "~0.6.1" + "commander": "~2.9.0", + "source-map": "~0.5.1", + "uglify-to-browserify": "~1.0.0" }, "dependencies": { "commander": { - "version": "2.17.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", - "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", + "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", + "dev": true, + "requires": { + "graceful-readlink": ">= 1.0.0" + } } } }, @@ -7786,7 +6909,8 @@ "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true }, "utils-merge": { "version": "1.0.0", @@ -7837,12 +6961,6 @@ "extsprintf": "^1.2.0" } }, - "vlq": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/vlq/-/vlq-0.2.3.tgz", - "integrity": "sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow==", - "dev": true - }, "w3c-keyname": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-1.1.8.tgz", @@ -7896,25 +7014,11 @@ "isexe": "^2.0.0" } }, - "which-pm-runs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz", - "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=", - "optional": true - }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "optional": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true }, "write": { "version": "0.2.1", @@ -7925,15 +7029,6 @@ "mkdirp": "^0.5.1" } }, - "ws": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.1.0.tgz", - "integrity": "sha512-H3dGVdGvW2H8bnYpIDc3u3LH8Wue3Qh+Zto6aXXFzvESkTVT6rAfKR6tR/+coaUvxs8yHtmNV0uioBF62ZGSTg==", - "optional": true, - "requires": { - "async-limiter": "~1.0.0" - } - }, "xml-name-validator": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-2.0.1.tgz", @@ -7950,7 +7045,5118 @@ "xtend": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" + "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", + "dev": true + }, + "y-protocols": { + "version": "file:../y-protocols", + "dependencies": { + "@types/estree": { + "version": "0.0.39", + "bundled": true + }, + "@types/node": { + "version": "10.12.24", + "bundled": true + }, + "acorn": { + "version": "6.1.0", + "bundled": true + }, + "funlib": { + "version": "1.0.0", + "bundled": true, + "requires": { + "rollup": "^1.1.2" + }, + "dependencies": { + "@types/estree": { + "version": "0.0.39", + "bundled": true + }, + "@types/node": { + "version": "10.12.21", + "bundled": true + }, + "acorn": { + "version": "6.0.7", + "bundled": true + }, + "rollup": { + "version": "1.1.2", + "bundled": true, + "requires": { + "@types/estree": "0.0.39", + "@types/node": "*", + "acorn": "^6.0.5" + } + } + } + }, + "rollup": { + "version": "1.1.2", + "bundled": true, + "requires": { + "@types/estree": "0.0.39", + "@types/node": "*", + "acorn": "^6.0.5" + } + }, + "rollup-cli": { + "version": "1.0.9", + "bundled": true + }, + "yjs": { + "version": "13.0.0-78", + "bundled": true, + "requires": { + "funlib": "file:../funlib" + }, + "dependencies": { + "@babel/code-frame": { + "version": "7.0.0", + "bundled": true, + "requires": { + "@babel/highlight": "^7.0.0" + } + }, + "@babel/highlight": { + "version": "7.0.0", + "bundled": true, + "requires": { + "chalk": "^2.0.0", + "esutils": "^2.0.2", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "bundled": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "bundled": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "js-tokens": { + "version": "4.0.0", + "bundled": true + }, + "supports-color": { + "version": "5.5.0", + "bundled": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@types/estree": { + "version": "0.0.39", + "bundled": true + }, + "@types/node": { + "version": "6.0.110", + "bundled": true + }, + "abab": { + "version": "1.0.4", + "bundled": true, + "optional": true + }, + "accepts": { + "version": "1.3.5", + "bundled": true, + "requires": { + "mime-types": "~2.1.18", + "negotiator": "0.6.1" + }, + "dependencies": { + "mime-db": { + "version": "1.37.0", + "bundled": true + }, + "mime-types": { + "version": "2.1.21", + "bundled": true, + "requires": { + "mime-db": "~1.37.0" + } + } + } + }, + "acorn-globals": { + "version": "1.0.9", + "bundled": true, + "optional": true, + "requires": { + "acorn": "^2.1.0" + }, + "dependencies": { + "acorn": { + "version": "2.7.0", + "bundled": true, + "optional": true + } + } + }, + "acorn-jsx": { + "version": "3.0.1", + "bundled": true, + "requires": { + "acorn": "^3.0.4" + }, + "dependencies": { + "acorn": { + "version": "3.3.0", + "bundled": true + } + } + }, + "ajv": { + "version": "5.5.2", + "bundled": true, + "requires": { + "co": "^4.6.0", + "fast-deep-equal": "^1.0.0", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.3.0" + } + }, + "ajv-keywords": { + "version": "2.1.1", + "bundled": true + }, + "ansi-escapes": { + "version": "3.1.0", + "bundled": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true + }, + "ansi-styles": { + "version": "2.2.1", + "bundled": true + }, + "anymatch": { + "version": "1.3.0", + "bundled": true, + "requires": { + "arrify": "^1.0.0", + "micromatch": "^2.1.5" + } + }, + "apache-crypt": { + "version": "1.2.1", + "bundled": true, + "requires": { + "unix-crypt-td-js": "^1.0.0" + } + }, + "apache-md5": { + "version": "1.1.2", + "bundled": true + }, + "argparse": { + "version": "1.0.10", + "bundled": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "arr-diff": { + "version": "2.0.0", + "bundled": true, + "requires": { + "arr-flatten": "^1.0.1" + } + }, + "arr-flatten": { + "version": "1.0.3", + "bundled": true + }, + "array-find-index": { + "version": "1.0.2", + "bundled": true + }, + "array-includes": { + "version": "3.0.3", + "bundled": true, + "requires": { + "define-properties": "^1.1.2", + "es-abstract": "^1.7.0" + } + }, + "array-union": { + "version": "1.0.2", + "bundled": true, + "requires": { + "array-uniq": "^1.0.1" + } + }, + "array-uniq": { + "version": "1.0.3", + "bundled": true + }, + "array-unique": { + "version": "0.2.1", + "bundled": true + }, + "arrify": { + "version": "1.0.1", + "bundled": true + }, + "asap": { + "version": "2.0.6", + "bundled": true + }, + "asn1": { + "version": "0.2.4", + "bundled": true, + "optional": true, + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "bundled": true + }, + "async-each": { + "version": "1.0.1", + "bundled": true + }, + "asynckit": { + "version": "0.4.0", + "bundled": true, + "optional": true + }, + "aws-sign2": { + "version": "0.7.0", + "bundled": true, + "optional": true + }, + "aws4": { + "version": "1.8.0", + "bundled": true, + "optional": true + }, + "babel-cli": { + "version": "6.26.0", + "bundled": true, + "requires": { + "babel-core": "^6.26.0", + "babel-polyfill": "^6.26.0", + "babel-register": "^6.26.0", + "babel-runtime": "^6.26.0", + "chokidar": "^1.6.1", + "commander": "^2.11.0", + "convert-source-map": "^1.5.0", + "fs-readdir-recursive": "^1.0.0", + "glob": "^7.1.2", + "lodash": "^4.17.4", + "output-file-sync": "^1.1.2", + "path-is-absolute": "^1.0.1", + "slash": "^1.0.0", + "source-map": "^0.5.6", + "v8flags": "^2.1.1" + }, + "dependencies": { + "babel-code-frame": { + "version": "6.26.0", + "bundled": true, + "requires": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + } + }, + "babel-core": { + "version": "6.26.3", + "bundled": true, + "requires": { + "babel-code-frame": "^6.26.0", + "babel-generator": "^6.26.0", + "babel-helpers": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-register": "^6.26.0", + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "convert-source-map": "^1.5.1", + "debug": "^2.6.9", + "json5": "^0.5.1", + "lodash": "^4.17.4", + "minimatch": "^3.0.4", + "path-is-absolute": "^1.0.1", + "private": "^0.1.8", + "slash": "^1.0.0", + "source-map": "^0.5.7" + }, + "dependencies": { + "convert-source-map": { + "version": "1.6.0", + "bundled": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "source-map": { + "version": "0.5.7", + "bundled": true + } + } + }, + "babel-generator": { + "version": "6.26.1", + "bundled": true, + "requires": { + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "detect-indent": "^4.0.0", + "jsesc": "^1.3.0", + "lodash": "^4.17.4", + "source-map": "^0.5.7", + "trim-right": "^1.0.1" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "bundled": true + } + } + }, + "babel-register": { + "version": "6.26.0", + "bundled": true, + "requires": { + "babel-core": "^6.26.0", + "babel-runtime": "^6.26.0", + "core-js": "^2.5.0", + "home-or-tmp": "^2.0.0", + "lodash": "^4.17.4", + "mkdirp": "^0.5.1", + "source-map-support": "^0.4.15" + }, + "dependencies": { + "core-js": { + "version": "2.5.7", + "bundled": true + } + } + }, + "babel-runtime": { + "version": "6.26.0", + "bundled": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "babel-template": { + "version": "6.26.0", + "bundled": true, + "requires": { + "babel-runtime": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "lodash": "^4.17.4" + } + }, + "babel-traverse": { + "version": "6.26.0", + "bundled": true, + "requires": { + "babel-code-frame": "^6.26.0", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "debug": "^2.6.8", + "globals": "^9.18.0", + "invariant": "^2.2.2", + "lodash": "^4.17.4" + } + }, + "babel-types": { + "version": "6.26.0", + "bundled": true, + "requires": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + } + }, + "babylon": { + "version": "6.18.0", + "bundled": true + }, + "debug": { + "version": "2.6.9", + "bundled": true, + "requires": { + "ms": "2.0.0" + } + }, + "lodash": { + "version": "4.17.11", + "bundled": true + }, + "private": { + "version": "0.1.8", + "bundled": true + }, + "regenerator-runtime": { + "version": "0.11.1", + "bundled": true + } + } + }, + "babel-code-frame": { + "version": "6.22.0", + "bundled": true, + "requires": { + "chalk": "^1.1.0", + "esutils": "^2.0.2", + "js-tokens": "^3.0.0" + } + }, + "babel-helper-builder-binary-assignment-operator-visitor": { + "version": "6.24.1", + "bundled": true, + "requires": { + "babel-helper-explode-assignable-expression": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-helper-call-delegate": { + "version": "6.24.1", + "bundled": true, + "requires": { + "babel-helper-hoist-variables": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-helper-define-map": { + "version": "6.26.0", + "bundled": true, + "requires": { + "babel-helper-function-name": "^6.24.1", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "lodash": "^4.17.4" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "bundled": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "babel-types": { + "version": "6.26.0", + "bundled": true, + "requires": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + } + }, + "regenerator-runtime": { + "version": "0.11.1", + "bundled": true + } + } + }, + "babel-helper-explode-assignable-expression": { + "version": "6.24.1", + "bundled": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-helper-function-name": { + "version": "6.24.1", + "bundled": true, + "requires": { + "babel-helper-get-function-arity": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-helper-get-function-arity": { + "version": "6.24.1", + "bundled": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-helper-hoist-variables": { + "version": "6.24.1", + "bundled": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-helper-optimise-call-expression": { + "version": "6.24.1", + "bundled": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-helper-regex": { + "version": "6.26.0", + "bundled": true, + "requires": { + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "lodash": "^4.17.4" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "bundled": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "babel-types": { + "version": "6.26.0", + "bundled": true, + "requires": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + } + }, + "regenerator-runtime": { + "version": "0.11.1", + "bundled": true + } + } + }, + "babel-helper-remap-async-to-generator": { + "version": "6.24.1", + "bundled": true, + "requires": { + "babel-helper-function-name": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-helper-replace-supers": { + "version": "6.24.1", + "bundled": true, + "requires": { + "babel-helper-optimise-call-expression": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-helpers": { + "version": "6.24.1", + "bundled": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "babel-messages": { + "version": "6.23.0", + "bundled": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-check-es2015-constants": { + "version": "6.22.0", + "bundled": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-external-helpers": { + "version": "6.22.0", + "bundled": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-syntax-async-functions": { + "version": "6.13.0", + "bundled": true + }, + "babel-plugin-syntax-exponentiation-operator": { + "version": "6.13.0", + "bundled": true + }, + "babel-plugin-syntax-trailing-function-commas": { + "version": "6.22.0", + "bundled": true + }, + "babel-plugin-transform-async-to-generator": { + "version": "6.24.1", + "bundled": true, + "requires": { + "babel-helper-remap-async-to-generator": "^6.24.1", + "babel-plugin-syntax-async-functions": "^6.8.0", + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-arrow-functions": { + "version": "6.22.0", + "bundled": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-block-scoped-functions": { + "version": "6.22.0", + "bundled": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-block-scoping": { + "version": "6.26.0", + "bundled": true, + "requires": { + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "lodash": "^4.17.4" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "bundled": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "babel-types": { + "version": "6.26.0", + "bundled": true, + "requires": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + } + }, + "regenerator-runtime": { + "version": "0.11.1", + "bundled": true + } + } + }, + "babel-plugin-transform-es2015-classes": { + "version": "6.24.1", + "bundled": true, + "requires": { + "babel-helper-define-map": "^6.24.1", + "babel-helper-function-name": "^6.24.1", + "babel-helper-optimise-call-expression": "^6.24.1", + "babel-helper-replace-supers": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-computed-properties": { + "version": "6.24.1", + "bundled": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-destructuring": { + "version": "6.23.0", + "bundled": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-duplicate-keys": { + "version": "6.24.1", + "bundled": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-for-of": { + "version": "6.23.0", + "bundled": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-function-name": { + "version": "6.24.1", + "bundled": true, + "requires": { + "babel-helper-function-name": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-literals": { + "version": "6.22.0", + "bundled": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-modules-amd": { + "version": "6.24.1", + "bundled": true, + "requires": { + "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-modules-commonjs": { + "version": "6.26.2", + "bundled": true, + "requires": { + "babel-plugin-transform-strict-mode": "^6.24.1", + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-types": "^6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "bundled": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "babel-types": { + "version": "6.26.0", + "bundled": true, + "requires": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + } + }, + "regenerator-runtime": { + "version": "0.11.1", + "bundled": true + } + } + }, + "babel-plugin-transform-es2015-modules-systemjs": { + "version": "6.24.1", + "bundled": true, + "requires": { + "babel-helper-hoist-variables": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-modules-umd": { + "version": "6.24.1", + "bundled": true, + "requires": { + "babel-plugin-transform-es2015-modules-amd": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-object-super": { + "version": "6.24.1", + "bundled": true, + "requires": { + "babel-helper-replace-supers": "^6.24.1", + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-parameters": { + "version": "6.24.1", + "bundled": true, + "requires": { + "babel-helper-call-delegate": "^6.24.1", + "babel-helper-get-function-arity": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-template": "^6.24.1", + "babel-traverse": "^6.24.1", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-shorthand-properties": { + "version": "6.24.1", + "bundled": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-spread": { + "version": "6.22.0", + "bundled": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-sticky-regex": { + "version": "6.24.1", + "bundled": true, + "requires": { + "babel-helper-regex": "^6.24.1", + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-plugin-transform-es2015-template-literals": { + "version": "6.22.0", + "bundled": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-typeof-symbol": { + "version": "6.23.0", + "bundled": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-es2015-unicode-regex": { + "version": "6.24.1", + "bundled": true, + "requires": { + "babel-helper-regex": "^6.24.1", + "babel-runtime": "^6.22.0", + "regexpu-core": "^2.0.0" + } + }, + "babel-plugin-transform-exponentiation-operator": { + "version": "6.24.1", + "bundled": true, + "requires": { + "babel-helper-builder-binary-assignment-operator-visitor": "^6.24.1", + "babel-plugin-syntax-exponentiation-operator": "^6.8.0", + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-regenerator": { + "version": "6.26.0", + "bundled": true, + "requires": { + "regenerator-transform": "^0.10.0" + } + }, + "babel-plugin-transform-runtime": { + "version": "6.23.0", + "bundled": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-plugin-transform-strict-mode": { + "version": "6.24.1", + "bundled": true, + "requires": { + "babel-runtime": "^6.22.0", + "babel-types": "^6.24.1" + } + }, + "babel-polyfill": { + "version": "6.26.0", + "bundled": true, + "requires": { + "babel-runtime": "^6.26.0", + "core-js": "^2.5.0", + "regenerator-runtime": "^0.10.5" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "bundled": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.11.1", + "bundled": true + } + } + }, + "core-js": { + "version": "2.5.7", + "bundled": true + } + } + }, + "babel-preset-es2015": { + "version": "6.24.1", + "bundled": true, + "requires": { + "babel-plugin-check-es2015-constants": "^6.22.0", + "babel-plugin-transform-es2015-arrow-functions": "^6.22.0", + "babel-plugin-transform-es2015-block-scoped-functions": "^6.22.0", + "babel-plugin-transform-es2015-block-scoping": "^6.24.1", + "babel-plugin-transform-es2015-classes": "^6.24.1", + "babel-plugin-transform-es2015-computed-properties": "^6.24.1", + "babel-plugin-transform-es2015-destructuring": "^6.22.0", + "babel-plugin-transform-es2015-duplicate-keys": "^6.24.1", + "babel-plugin-transform-es2015-for-of": "^6.22.0", + "babel-plugin-transform-es2015-function-name": "^6.24.1", + "babel-plugin-transform-es2015-literals": "^6.22.0", + "babel-plugin-transform-es2015-modules-amd": "^6.24.1", + "babel-plugin-transform-es2015-modules-commonjs": "^6.24.1", + "babel-plugin-transform-es2015-modules-systemjs": "^6.24.1", + "babel-plugin-transform-es2015-modules-umd": "^6.24.1", + "babel-plugin-transform-es2015-object-super": "^6.24.1", + "babel-plugin-transform-es2015-parameters": "^6.24.1", + "babel-plugin-transform-es2015-shorthand-properties": "^6.24.1", + "babel-plugin-transform-es2015-spread": "^6.22.0", + "babel-plugin-transform-es2015-sticky-regex": "^6.24.1", + "babel-plugin-transform-es2015-template-literals": "^6.22.0", + "babel-plugin-transform-es2015-typeof-symbol": "^6.22.0", + "babel-plugin-transform-es2015-unicode-regex": "^6.24.1", + "babel-plugin-transform-regenerator": "^6.24.1" + } + }, + "babel-preset-es2016": { + "version": "6.24.1", + "bundled": true, + "requires": { + "babel-plugin-transform-exponentiation-operator": "^6.24.1" + } + }, + "babel-preset-es2017": { + "version": "6.24.1", + "bundled": true, + "requires": { + "babel-plugin-syntax-trailing-function-commas": "^6.22.0", + "babel-plugin-transform-async-to-generator": "^6.24.1" + } + }, + "babel-preset-latest": { + "version": "6.24.1", + "bundled": true, + "requires": { + "babel-preset-es2015": "^6.24.1", + "babel-preset-es2016": "^6.24.1", + "babel-preset-es2017": "^6.24.1" + } + }, + "babel-runtime": { + "version": "6.23.0", + "bundled": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.10.0" + } + }, + "babel-template": { + "version": "6.26.0", + "bundled": true, + "requires": { + "babel-runtime": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "lodash": "^4.17.4" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "bundled": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "babel-types": { + "version": "6.26.0", + "bundled": true, + "requires": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + } + }, + "babylon": { + "version": "6.18.0", + "bundled": true + }, + "regenerator-runtime": { + "version": "0.11.1", + "bundled": true + } + } + }, + "babel-traverse": { + "version": "6.26.0", + "bundled": true, + "requires": { + "babel-code-frame": "^6.26.0", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "debug": "^2.6.8", + "globals": "^9.18.0", + "invariant": "^2.2.2", + "lodash": "^4.17.4" + }, + "dependencies": { + "babel-code-frame": { + "version": "6.26.0", + "bundled": true, + "requires": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + } + }, + "babel-runtime": { + "version": "6.26.0", + "bundled": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "babel-types": { + "version": "6.26.0", + "bundled": true, + "requires": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + } + }, + "babylon": { + "version": "6.18.0", + "bundled": true + }, + "regenerator-runtime": { + "version": "0.11.1", + "bundled": true + } + } + }, + "babel-types": { + "version": "6.25.0", + "bundled": true, + "requires": { + "babel-runtime": "^6.22.0", + "esutils": "^2.0.2", + "lodash": "^4.2.0", + "to-fast-properties": "^1.0.1" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true + }, + "basic-auth": { + "version": "2.0.1", + "bundled": true, + "requires": { + "safe-buffer": "5.1.2" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "bundled": true + } + } + }, + "batch": { + "version": "0.6.1", + "bundled": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "bundled": true, + "optional": true, + "requires": { + "tweetnacl": "^0.14.3" + } + }, + "bcryptjs": { + "version": "2.4.3", + "bundled": true + }, + "binary-extensions": { + "version": "1.8.0", + "bundled": true + }, + "bluebird": { + "version": "3.5.3", + "bundled": true + }, + "boolbase": { + "version": "1.0.0", + "bundled": true + }, + "brace-expansion": { + "version": "1.1.8", + "bundled": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "1.8.5", + "bundled": true, + "requires": { + "expand-range": "^1.8.1", + "preserve": "^0.2.0", + "repeat-element": "^1.1.2" + } + }, + "buffer-from": { + "version": "1.0.0", + "bundled": true + }, + "builtin-modules": { + "version": "1.1.1", + "bundled": true + }, + "caller-path": { + "version": "0.1.0", + "bundled": true, + "requires": { + "callsites": "^0.2.0" + } + }, + "callsites": { + "version": "0.2.0", + "bundled": true + }, + "camelcase-keys": { + "version": "2.1.0", + "bundled": true, + "requires": { + "camelcase": "^2.0.0", + "map-obj": "^1.0.0" + }, + "dependencies": { + "camelcase": { + "version": "2.1.1", + "bundled": true + } + } + }, + "caseless": { + "version": "0.12.0", + "bundled": true, + "optional": true + }, + "catharsis": { + "version": "0.8.9", + "bundled": true, + "requires": { + "underscore-contrib": "~0.3.0" + } + }, + "chalk": { + "version": "1.1.3", + "bundled": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "chardet": { + "version": "0.4.2", + "bundled": true + }, + "cheerio": { + "version": "0.22.0", + "bundled": true, + "requires": { + "css-select": "~1.2.0", + "dom-serializer": "~0.1.0", + "entities": "~1.1.1", + "htmlparser2": "^3.9.1", + "lodash.assignin": "^4.0.9", + "lodash.bind": "^4.1.4", + "lodash.defaults": "^4.0.1", + "lodash.filter": "^4.4.0", + "lodash.flatten": "^4.2.0", + "lodash.foreach": "^4.3.0", + "lodash.map": "^4.4.0", + "lodash.merge": "^4.4.0", + "lodash.pick": "^4.2.1", + "lodash.reduce": "^4.4.0", + "lodash.reject": "^4.4.0", + "lodash.some": "^4.4.0" + } + }, + "chokidar": { + "version": "1.7.0", + "bundled": true, + "requires": { + "anymatch": "^1.3.0", + "async-each": "^1.0.0", + "glob-parent": "^2.0.0", + "inherits": "^2.0.1", + "is-binary-path": "^1.0.0", + "is-glob": "^2.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.0.0" + } + }, + "circular-json": { + "version": "0.3.3", + "bundled": true + }, + "cli-cursor": { + "version": "2.1.0", + "bundled": true, + "requires": { + "restore-cursor": "^2.0.0" + } + }, + "cli-width": { + "version": "2.2.0", + "bundled": true + }, + "clone": { + "version": "2.1.2", + "bundled": true + }, + "co": { + "version": "4.6.0", + "bundled": true + }, + "codemirror": { + "version": "5.42.0", + "bundled": true + }, + "color-convert": { + "version": "1.9.1", + "bundled": true, + "requires": { + "color-name": "^1.1.1" + } + }, + "color-logger": { + "version": "0.0.3", + "bundled": true + }, + "color-name": { + "version": "1.1.3", + "bundled": true + }, + "combined-stream": { + "version": "1.0.7", + "bundled": true, + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.19.0", + "bundled": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + }, + "concat-stream": { + "version": "1.6.2", + "bundled": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "concurrently": { + "version": "3.6.1", + "bundled": true, + "requires": { + "chalk": "^2.4.1", + "commander": "2.6.0", + "date-fns": "^1.23.0", + "lodash": "^4.5.1", + "read-pkg": "^3.0.0", + "rx": "2.3.24", + "spawn-command": "^0.0.2-1", + "supports-color": "^3.2.3", + "tree-kill": "^1.1.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "bundled": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.1", + "bundled": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "bundled": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "commander": { + "version": "2.6.0", + "bundled": true + }, + "load-json-file": { + "version": "4.0.0", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "bundled": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "path-type": { + "version": "3.0.0", + "bundled": true, + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "bundled": true + }, + "read-pkg": { + "version": "3.0.0", + "bundled": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "supports-color": { + "version": "3.2.3", + "bundled": true, + "requires": { + "has-flag": "^1.0.0" + }, + "dependencies": { + "has-flag": { + "version": "1.0.0", + "bundled": true + } + } + } + } + }, + "connect": { + "version": "3.5.1", + "bundled": true, + "requires": { + "debug": "~2.2.0", + "finalhandler": "0.5.1", + "parseurl": "~1.3.1", + "utils-merge": "1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.2.0", + "bundled": true, + "requires": { + "ms": "0.7.1" + } + }, + "ms": { + "version": "0.7.1", + "bundled": true + } + } + }, + "contains-path": { + "version": "0.1.0", + "bundled": true + }, + "convert-source-map": { + "version": "1.5.0", + "bundled": true + }, + "core-js": { + "version": "2.4.1", + "bundled": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true + }, + "crel": { + "version": "3.1.0", + "bundled": true + }, + "cross-spawn": { + "version": "5.1.0", + "bundled": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + }, + "css-select": { + "version": "1.2.0", + "bundled": true, + "requires": { + "boolbase": "~1.0.0", + "css-what": "2.1", + "domutils": "1.5.1", + "nth-check": "~1.0.1" + } + }, + "css-what": { + "version": "2.1.0", + "bundled": true + }, + "cssom": { + "version": "0.3.2", + "bundled": true + }, + "cssstyle": { + "version": "0.2.37", + "bundled": true, + "optional": true, + "requires": { + "cssom": "0.3.x" + } + }, + "currently-unhandled": { + "version": "0.4.1", + "bundled": true, + "requires": { + "array-find-index": "^1.0.1" + } + }, + "cutest": { + "version": "0.1.9", + "bundled": true, + "requires": { + "live-server": "^1.2.0", + "meow": "^3.7.0", + "stacktrace-js": "^2.0.0" + } + }, + "dashdash": { + "version": "1.14.1", + "bundled": true, + "optional": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "date-fns": { + "version": "1.29.0", + "bundled": true + }, + "debug": { + "version": "2.6.9", + "bundled": true, + "requires": { + "ms": "2.0.0" + } + }, + "debug-log": { + "version": "1.0.1", + "bundled": true + }, + "decamelize": { + "version": "1.2.0", + "bundled": true + }, + "deep-equal": { + "version": "1.0.1", + "bundled": true + }, + "deep-is": { + "version": "0.1.3", + "bundled": true + }, + "define-properties": { + "version": "1.1.2", + "bundled": true, + "requires": { + "foreach": "^2.0.5", + "object-keys": "^1.0.8" + } + }, + "deglob": { + "version": "2.1.0", + "bundled": true, + "requires": { + "find-root": "^1.0.0", + "glob": "^7.0.5", + "ignore": "^3.0.9", + "pkg-config": "^1.1.0", + "run-parallel": "^1.1.2", + "uniq": "^1.0.1" + } + }, + "del": { + "version": "2.2.2", + "bundled": true, + "requires": { + "globby": "^5.0.0", + "is-path-cwd": "^1.0.0", + "is-path-in-cwd": "^1.0.0", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "rimraf": "^2.2.8" + } + }, + "delayed-stream": { + "version": "1.0.0", + "bundled": true + }, + "depd": { + "version": "1.1.2", + "bundled": true + }, + "destroy": { + "version": "1.0.4", + "bundled": true + }, + "detect-indent": { + "version": "4.0.0", + "bundled": true, + "requires": { + "repeating": "^2.0.0" + } + }, + "doctrine": { + "version": "2.1.0", + "bundled": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "dom-serializer": { + "version": "0.1.0", + "bundled": true, + "requires": { + "domelementtype": "~1.1.1", + "entities": "~1.1.1" + }, + "dependencies": { + "domelementtype": { + "version": "1.1.3", + "bundled": true + } + } + }, + "domelementtype": { + "version": "1.3.0", + "bundled": true + }, + "domhandler": { + "version": "2.4.1", + "bundled": true, + "requires": { + "domelementtype": "1" + } + }, + "domutils": { + "version": "1.5.1", + "bundled": true, + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "duplexer": { + "version": "0.1.1", + "bundled": true + }, + "ecc-jsbn": { + "version": "0.1.2", + "bundled": true, + "optional": true, + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "ee-first": { + "version": "1.1.1", + "bundled": true + }, + "encodeurl": { + "version": "1.0.1", + "bundled": true + }, + "encoding": { + "version": "0.1.12", + "bundled": true, + "requires": { + "iconv-lite": "~0.4.13" + } + }, + "entities": { + "version": "1.1.1", + "bundled": true + }, + "error-ex": { + "version": "1.3.1", + "bundled": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "error-stack-parser": { + "version": "2.0.1", + "bundled": true, + "requires": { + "stackframe": "^1.0.3" + } + }, + "es-abstract": { + "version": "1.11.0", + "bundled": true, + "requires": { + "es-to-primitive": "^1.1.1", + "function-bind": "^1.1.1", + "has": "^1.0.1", + "is-callable": "^1.1.3", + "is-regex": "^1.0.4" + } + }, + "es-to-primitive": { + "version": "1.1.1", + "bundled": true, + "requires": { + "is-callable": "^1.1.1", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.1" + } + }, + "escape-html": { + "version": "1.0.3", + "bundled": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "bundled": true + }, + "escodegen": { + "version": "1.9.1", + "bundled": true, + "optional": true, + "requires": { + "esprima": "^3.1.3", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1", + "source-map": "~0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "bundled": true, + "optional": true + } + } + }, + "esdoc": { + "version": "1.1.0", + "bundled": true, + "requires": { + "babel-generator": "6.26.1", + "babel-traverse": "6.26.0", + "babylon": "6.18.0", + "cheerio": "1.0.0-rc.2", + "color-logger": "0.0.6", + "escape-html": "1.0.3", + "fs-extra": "5.0.0", + "ice-cap": "0.0.4", + "marked": "0.3.19", + "minimist": "1.2.0", + "taffydb": "2.7.3" + }, + "dependencies": { + "babel-code-frame": { + "version": "6.26.0", + "bundled": true, + "requires": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + } + }, + "babel-generator": { + "version": "6.26.1", + "bundled": true, + "requires": { + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "detect-indent": "^4.0.0", + "jsesc": "^1.3.0", + "lodash": "^4.17.4", + "source-map": "^0.5.7", + "trim-right": "^1.0.1" + } + }, + "babel-runtime": { + "version": "6.26.0", + "bundled": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "babel-traverse": { + "version": "6.26.0", + "bundled": true, + "requires": { + "babel-code-frame": "^6.26.0", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "debug": "^2.6.8", + "globals": "^9.18.0", + "invariant": "^2.2.2", + "lodash": "^4.17.4" + } + }, + "babel-types": { + "version": "6.26.0", + "bundled": true, + "requires": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + } + }, + "babylon": { + "version": "6.18.0", + "bundled": true + }, + "cheerio": { + "version": "1.0.0-rc.2", + "bundled": true, + "requires": { + "css-select": "~1.2.0", + "dom-serializer": "~0.1.0", + "entities": "~1.1.1", + "htmlparser2": "^3.9.1", + "lodash": "^4.15.0", + "parse5": "^3.0.1" + } + }, + "color-logger": { + "version": "0.0.6", + "bundled": true + }, + "fs-extra": { + "version": "5.0.0", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "jsonfile": { + "version": "4.0.0", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "marked": { + "version": "0.3.19", + "bundled": true + }, + "minimist": { + "version": "1.2.0", + "bundled": true + }, + "parse5": { + "version": "3.0.3", + "bundled": true, + "requires": { + "@types/node": "*" + } + }, + "regenerator-runtime": { + "version": "0.11.1", + "bundled": true + }, + "source-map": { + "version": "0.5.7", + "bundled": true + }, + "taffydb": { + "version": "2.7.3", + "bundled": true + } + } + }, + "esdoc-accessor-plugin": { + "version": "1.0.0", + "bundled": true + }, + "esdoc-brand-plugin": { + "version": "1.0.0", + "bundled": true, + "requires": { + "cheerio": "0.22.0" + } + }, + "esdoc-coverage-plugin": { + "version": "1.1.0", + "bundled": true + }, + "esdoc-external-ecmascript-plugin": { + "version": "1.0.0", + "bundled": true, + "requires": { + "fs-extra": "1.0.0" + } + }, + "esdoc-integrate-manual-plugin": { + "version": "1.0.0", + "bundled": true + }, + "esdoc-integrate-test-plugin": { + "version": "1.0.0", + "bundled": true + }, + "esdoc-lint-plugin": { + "version": "1.0.1", + "bundled": true + }, + "esdoc-publish-html-plugin": { + "version": "1.1.2", + "bundled": true, + "requires": { + "babel-generator": "6.11.4", + "cheerio": "0.22.0", + "escape-html": "1.0.3", + "fs-extra": "1.0.0", + "ice-cap": "0.0.4", + "marked": "0.3.19", + "taffydb": "2.7.2" + }, + "dependencies": { + "babel-generator": { + "version": "6.11.4", + "bundled": true, + "requires": { + "babel-messages": "^6.8.0", + "babel-runtime": "^6.9.0", + "babel-types": "^6.10.2", + "detect-indent": "^3.0.1", + "lodash": "^4.2.0", + "source-map": "^0.5.0" + } + }, + "detect-indent": { + "version": "3.0.1", + "bundled": true, + "requires": { + "get-stdin": "^4.0.1", + "minimist": "^1.1.0", + "repeating": "^1.1.0" + } + }, + "get-stdin": { + "version": "4.0.1", + "bundled": true + }, + "minimist": { + "version": "1.2.0", + "bundled": true + }, + "repeating": { + "version": "1.1.3", + "bundled": true, + "requires": { + "is-finite": "^1.0.0" + } + } + } + }, + "esdoc-standard-plugin": { + "version": "1.0.0", + "bundled": true, + "requires": { + "esdoc-accessor-plugin": "^1.0.0", + "esdoc-brand-plugin": "^1.0.0", + "esdoc-coverage-plugin": "^1.0.0", + "esdoc-external-ecmascript-plugin": "^1.0.0", + "esdoc-integrate-manual-plugin": "^1.0.0", + "esdoc-integrate-test-plugin": "^1.0.0", + "esdoc-lint-plugin": "^1.0.0", + "esdoc-publish-html-plugin": "^1.0.0", + "esdoc-type-inference-plugin": "^1.0.0", + "esdoc-undocumented-identifier-plugin": "^1.0.0", + "esdoc-unexported-identifier-plugin": "^1.0.0" + } + }, + "esdoc-type-inference-plugin": { + "version": "1.0.1", + "bundled": true + }, + "esdoc-undocumented-identifier-plugin": { + "version": "1.0.0", + "bundled": true + }, + "esdoc-unexported-identifier-plugin": { + "version": "1.0.0", + "bundled": true + }, + "eslint": { + "version": "4.18.2", + "bundled": true, + "requires": { + "ajv": "^5.3.0", + "babel-code-frame": "^6.22.0", + "chalk": "^2.1.0", + "concat-stream": "^1.6.0", + "cross-spawn": "^5.1.0", + "debug": "^3.1.0", + "doctrine": "^2.1.0", + "eslint-scope": "^3.7.1", + "eslint-visitor-keys": "^1.0.0", + "espree": "^3.5.2", + "esquery": "^1.0.0", + "esutils": "^2.0.2", + "file-entry-cache": "^2.0.0", + "functional-red-black-tree": "^1.0.1", + "glob": "^7.1.2", + "globals": "^11.0.1", + "ignore": "^3.3.3", + "imurmurhash": "^0.1.4", + "inquirer": "^3.0.6", + "is-resolvable": "^1.0.0", + "js-yaml": "^3.9.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.3.0", + "lodash": "^4.17.4", + "minimatch": "^3.0.2", + "mkdirp": "^0.5.1", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.2", + "pluralize": "^7.0.0", + "progress": "^2.0.0", + "require-uncached": "^1.0.3", + "semver": "^5.3.0", + "strip-ansi": "^4.0.0", + "strip-json-comments": "~2.0.1", + "table": "4.0.2", + "text-table": "~0.2.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "bundled": true + }, + "ansi-styles": { + "version": "3.2.1", + "bundled": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.1", + "bundled": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "debug": { + "version": "3.1.0", + "bundled": true, + "requires": { + "ms": "2.0.0" + } + }, + "globals": { + "version": "11.5.0", + "bundled": true + }, + "has-flag": { + "version": "3.0.0", + "bundled": true + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "supports-color": { + "version": "5.4.0", + "bundled": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "eslint-config-standard": { + "version": "11.0.0", + "bundled": true + }, + "eslint-config-standard-jsx": { + "version": "5.0.0", + "bundled": true + }, + "eslint-import-resolver-node": { + "version": "0.3.2", + "bundled": true, + "requires": { + "debug": "^2.6.9", + "resolve": "^1.5.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "bundled": true, + "requires": { + "ms": "2.0.0" + } + }, + "resolve": { + "version": "1.7.1", + "bundled": true, + "requires": { + "path-parse": "^1.0.5" + } + } + } + }, + "eslint-module-utils": { + "version": "2.2.0", + "bundled": true, + "requires": { + "debug": "^2.6.8", + "pkg-dir": "^1.0.0" + } + }, + "eslint-plugin-import": { + "version": "2.9.0", + "bundled": true, + "requires": { + "builtin-modules": "^1.1.1", + "contains-path": "^0.1.0", + "debug": "^2.6.8", + "doctrine": "1.5.0", + "eslint-import-resolver-node": "^0.3.1", + "eslint-module-utils": "^2.1.1", + "has": "^1.0.1", + "lodash": "^4.17.4", + "minimatch": "^3.0.3", + "read-pkg-up": "^2.0.0" + }, + "dependencies": { + "doctrine": { + "version": "1.5.0", + "bundled": true, + "requires": { + "esutils": "^2.0.2", + "isarray": "^1.0.0" + } + }, + "find-up": { + "version": "2.1.0", + "bundled": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "path-type": { + "version": "2.0.0", + "bundled": true, + "requires": { + "pify": "^2.0.0" + } + }, + "read-pkg": { + "version": "2.0.0", + "bundled": true, + "requires": { + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "bundled": true, + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" + } + } + } + }, + "eslint-plugin-node": { + "version": "6.0.1", + "bundled": true, + "requires": { + "ignore": "^3.3.6", + "minimatch": "^3.0.4", + "resolve": "^1.3.3", + "semver": "^5.4.1" + }, + "dependencies": { + "semver": { + "version": "5.5.0", + "bundled": true + } + } + }, + "eslint-plugin-promise": { + "version": "3.7.0", + "bundled": true + }, + "eslint-plugin-react": { + "version": "7.7.0", + "bundled": true, + "requires": { + "doctrine": "^2.0.2", + "has": "^1.0.1", + "jsx-ast-utils": "^2.0.1", + "prop-types": "^15.6.0" + } + }, + "eslint-plugin-standard": { + "version": "3.0.1", + "bundled": true + }, + "eslint-scope": { + "version": "3.7.1", + "bundled": true, + "requires": { + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "eslint-visitor-keys": { + "version": "1.0.0", + "bundled": true + }, + "esm": { + "version": "3.2.6", + "bundled": true + }, + "espree": { + "version": "3.5.4", + "bundled": true, + "requires": { + "acorn": "^5.5.0", + "acorn-jsx": "^3.0.0" + }, + "dependencies": { + "acorn": { + "version": "5.5.3", + "bundled": true + } + } + }, + "esprima": { + "version": "3.1.3", + "bundled": true, + "optional": true + }, + "esquery": { + "version": "1.0.1", + "bundled": true, + "requires": { + "estraverse": "^4.0.0" + } + }, + "esrecurse": { + "version": "4.2.1", + "bundled": true, + "requires": { + "estraverse": "^4.1.0" + } + }, + "estraverse": { + "version": "4.2.0", + "bundled": true + }, + "estree-walker": { + "version": "0.5.2", + "bundled": true + }, + "esutils": { + "version": "2.0.2", + "bundled": true + }, + "eventemitter3": { + "version": "2.0.3", + "bundled": true + }, + "expand-brackets": { + "version": "0.1.5", + "bundled": true, + "requires": { + "is-posix-bracket": "^0.1.0" + } + }, + "expand-range": { + "version": "1.8.2", + "bundled": true, + "requires": { + "fill-range": "^2.1.0" + } + }, + "extend": { + "version": "3.0.2", + "bundled": true + }, + "external-editor": { + "version": "2.2.0", + "bundled": true, + "requires": { + "chardet": "^0.4.0", + "iconv-lite": "^0.4.17", + "tmp": "^0.0.33" + } + }, + "extglob": { + "version": "0.3.2", + "bundled": true, + "requires": { + "is-extglob": "^1.0.0" + } + }, + "extsprintf": { + "version": "1.3.0", + "bundled": true + }, + "fast-deep-equal": { + "version": "1.1.0", + "bundled": true + }, + "fast-diff": { + "version": "1.1.2", + "bundled": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "bundled": true + }, + "fast-levenshtein": { + "version": "2.0.6", + "bundled": true + }, + "faye-websocket": { + "version": "0.11.1", + "bundled": true, + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "fbjs": { + "version": "0.8.16", + "bundled": true, + "requires": { + "core-js": "^1.0.0", + "isomorphic-fetch": "^2.1.1", + "loose-envify": "^1.0.0", + "object-assign": "^4.1.0", + "promise": "^7.1.1", + "setimmediate": "^1.0.5", + "ua-parser-js": "^0.7.9" + }, + "dependencies": { + "core-js": { + "version": "1.2.7", + "bundled": true + } + } + }, + "figures": { + "version": "2.0.0", + "bundled": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "file-entry-cache": { + "version": "2.0.0", + "bundled": true, + "requires": { + "flat-cache": "^1.2.1", + "object-assign": "^4.0.1" + } + }, + "filename-regex": { + "version": "2.0.1", + "bundled": true + }, + "fill-range": { + "version": "2.2.4", + "bundled": true, + "requires": { + "is-number": "^2.1.0", + "isobject": "^2.0.0", + "randomatic": "^3.0.0", + "repeat-element": "^1.1.2", + "repeat-string": "^1.5.2" + } + }, + "finalhandler": { + "version": "0.5.1", + "bundled": true, + "requires": { + "debug": "~2.2.0", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "statuses": "~1.3.1", + "unpipe": "~1.0.0" + }, + "dependencies": { + "debug": { + "version": "2.2.0", + "bundled": true, + "requires": { + "ms": "0.7.1" + } + }, + "ms": { + "version": "0.7.1", + "bundled": true + } + } + }, + "find-root": { + "version": "1.1.0", + "bundled": true + }, + "find-up": { + "version": "1.1.2", + "bundled": true, + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "flat-cache": { + "version": "1.3.0", + "bundled": true, + "requires": { + "circular-json": "^0.3.1", + "del": "^2.0.2", + "graceful-fs": "^4.1.2", + "write": "^0.2.1" + } + }, + "for-in": { + "version": "1.0.2", + "bundled": true + }, + "for-own": { + "version": "0.1.5", + "bundled": true, + "requires": { + "for-in": "^1.0.1" + } + }, + "foreach": { + "version": "2.0.5", + "bundled": true + }, + "forever-agent": { + "version": "0.6.1", + "bundled": true, + "optional": true + }, + "form-data": { + "version": "2.3.3", + "bundled": true, + "optional": true, + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "from": { + "version": "0.1.7", + "bundled": true + }, + "fs-extra": { + "version": "1.0.0", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^2.1.0", + "klaw": "^1.0.0" + } + }, + "fs-readdir-recursive": { + "version": "1.1.0", + "bundled": true + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true + }, + "function-bind": { + "version": "1.1.1", + "bundled": true + }, + "functional-red-black-tree": { + "version": "1.0.1", + "bundled": true + }, + "funlib": { + "version": "file:../funlib", + "bundled": true, + "requires": { + "rollup": "^1.1.2" + }, + "dependencies": { + "@types/estree": { + "version": "0.0.39", + "bundled": true + }, + "@types/node": { + "version": "10.12.21", + "bundled": true + }, + "acorn": { + "version": "6.0.7", + "bundled": true + }, + "rollup": { + "version": "1.1.2", + "bundled": true, + "requires": { + "@types/estree": "0.0.39", + "@types/node": "*", + "acorn": "^6.0.5" + } + } + } + }, + "get-stdin": { + "version": "6.0.0", + "bundled": true + }, + "getpass": { + "version": "0.1.7", + "bundled": true, + "optional": true, + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-base": { + "version": "0.3.0", + "bundled": true, + "requires": { + "glob-parent": "^2.0.0", + "is-glob": "^2.0.0" + } + }, + "glob-parent": { + "version": "2.0.0", + "bundled": true, + "requires": { + "is-glob": "^2.0.0" + } + }, + "globals": { + "version": "9.18.0", + "bundled": true + }, + "globby": { + "version": "5.0.0", + "bundled": true, + "requires": { + "array-union": "^1.0.1", + "arrify": "^1.0.0", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "graceful-fs": { + "version": "4.1.11", + "bundled": true + }, + "graceful-readlink": { + "version": "1.0.1", + "bundled": true + }, + "har-schema": { + "version": "2.0.0", + "bundled": true, + "optional": true + }, + "har-validator": { + "version": "5.1.0", + "bundled": true, + "optional": true, + "requires": { + "ajv": "^5.3.0", + "har-schema": "^2.0.0" + } + }, + "has": { + "version": "1.0.1", + "bundled": true, + "requires": { + "function-bind": "^1.0.2" + } + }, + "has-ansi": { + "version": "2.0.0", + "bundled": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "bundled": true + }, + "home-or-tmp": { + "version": "2.0.0", + "bundled": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.1" + } + }, + "hosted-git-info": { + "version": "2.5.0", + "bundled": true + }, + "htmlparser2": { + "version": "3.9.2", + "bundled": true, + "requires": { + "domelementtype": "^1.3.0", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^2.0.2" + } + }, + "http-auth": { + "version": "3.1.3", + "bundled": true, + "requires": { + "apache-crypt": "^1.1.2", + "apache-md5": "^1.0.6", + "bcryptjs": "^2.3.0", + "uuid": "^3.0.0" + } + }, + "http-errors": { + "version": "1.6.3", + "bundled": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "dependencies": { + "setprototypeof": { + "version": "1.1.0", + "bundled": true + }, + "statuses": { + "version": "1.5.0", + "bundled": true + } + } + }, + "http-signature": { + "version": "1.2.0", + "bundled": true, + "optional": true, + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "ice-cap": { + "version": "0.0.4", + "bundled": true, + "requires": { + "cheerio": "0.20.0", + "color-logger": "0.0.3" + }, + "dependencies": { + "cheerio": { + "version": "0.20.0", + "bundled": true, + "requires": { + "css-select": "~1.2.0", + "dom-serializer": "~0.1.0", + "entities": "~1.1.1", + "htmlparser2": "~3.8.1", + "jsdom": "^7.0.2", + "lodash": "^4.1.0" + } + }, + "domhandler": { + "version": "2.3.0", + "bundled": true, + "requires": { + "domelementtype": "1" + } + }, + "htmlparser2": { + "version": "3.8.3", + "bundled": true, + "requires": { + "domelementtype": "1", + "domhandler": "2.3", + "domutils": "1.5", + "entities": "1.0", + "readable-stream": "1.1" + }, + "dependencies": { + "entities": { + "version": "1.0.0", + "bundled": true + } + } + }, + "isarray": { + "version": "0.0.1", + "bundled": true + }, + "readable-stream": { + "version": "1.1.14", + "bundled": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "string_decoder": { + "version": "0.10.31", + "bundled": true + } + } + }, + "iconv-lite": { + "version": "0.4.23", + "bundled": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore": { + "version": "3.3.8", + "bundled": true + }, + "imurmurhash": { + "version": "0.1.4", + "bundled": true + }, + "indent-string": { + "version": "2.1.0", + "bundled": true, + "requires": { + "repeating": "^2.0.0" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true + }, + "inquirer": { + "version": "3.3.0", + "bundled": true, + "requires": { + "ansi-escapes": "^3.0.0", + "chalk": "^2.0.0", + "cli-cursor": "^2.1.0", + "cli-width": "^2.0.0", + "external-editor": "^2.0.4", + "figures": "^2.0.0", + "lodash": "^4.3.0", + "mute-stream": "0.0.7", + "run-async": "^2.2.0", + "rx-lite": "^4.0.8", + "rx-lite-aggregates": "^4.0.8", + "string-width": "^2.1.0", + "strip-ansi": "^4.0.0", + "through": "^2.3.6" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "bundled": true + }, + "ansi-styles": { + "version": "3.2.1", + "bundled": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.1", + "bundled": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "has-flag": { + "version": "3.0.0", + "bundled": true + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "supports-color": { + "version": "5.4.0", + "bundled": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "invariant": { + "version": "2.2.2", + "bundled": true, + "requires": { + "loose-envify": "^1.0.0" + } + }, + "is-arrayish": { + "version": "0.2.1", + "bundled": true + }, + "is-binary-path": { + "version": "1.0.1", + "bundled": true, + "requires": { + "binary-extensions": "^1.0.0" + } + }, + "is-buffer": { + "version": "1.1.5", + "bundled": true + }, + "is-builtin-module": { + "version": "1.0.0", + "bundled": true, + "requires": { + "builtin-modules": "^1.0.0" + } + }, + "is-callable": { + "version": "1.1.3", + "bundled": true + }, + "is-date-object": { + "version": "1.0.1", + "bundled": true + }, + "is-dotfile": { + "version": "1.0.3", + "bundled": true + }, + "is-equal-shallow": { + "version": "0.1.3", + "bundled": true, + "requires": { + "is-primitive": "^2.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "bundled": true + }, + "is-extglob": { + "version": "1.0.0", + "bundled": true + }, + "is-finite": { + "version": "1.0.2", + "bundled": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "bundled": true + }, + "is-glob": { + "version": "2.0.1", + "bundled": true, + "requires": { + "is-extglob": "^1.0.0" + } + }, + "is-module": { + "version": "1.0.0", + "bundled": true + }, + "is-number": { + "version": "2.1.0", + "bundled": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-path-cwd": { + "version": "1.0.0", + "bundled": true + }, + "is-path-in-cwd": { + "version": "1.0.1", + "bundled": true, + "requires": { + "is-path-inside": "^1.0.0" + } + }, + "is-path-inside": { + "version": "1.0.1", + "bundled": true, + "requires": { + "path-is-inside": "^1.0.1" + } + }, + "is-posix-bracket": { + "version": "0.1.1", + "bundled": true + }, + "is-primitive": { + "version": "2.0.0", + "bundled": true + }, + "is-promise": { + "version": "2.1.0", + "bundled": true + }, + "is-regex": { + "version": "1.0.4", + "bundled": true, + "requires": { + "has": "^1.0.1" + } + }, + "is-resolvable": { + "version": "1.1.0", + "bundled": true + }, + "is-stream": { + "version": "1.1.0", + "bundled": true + }, + "is-symbol": { + "version": "1.0.1", + "bundled": true + }, + "is-typedarray": { + "version": "1.0.0", + "bundled": true, + "optional": true + }, + "is-utf8": { + "version": "0.2.1", + "bundled": true + }, + "is-wsl": { + "version": "1.1.0", + "bundled": true + }, + "isarray": { + "version": "1.0.0", + "bundled": true + }, + "isexe": { + "version": "2.0.0", + "bundled": true + }, + "isobject": { + "version": "2.1.0", + "bundled": true, + "requires": { + "isarray": "1.0.0" + } + }, + "isomorphic-fetch": { + "version": "2.2.1", + "bundled": true, + "requires": { + "node-fetch": "^1.0.1", + "whatwg-fetch": ">=0.10.0" + } + }, + "isstream": { + "version": "0.1.2", + "bundled": true, + "optional": true + }, + "jest-worker": { + "version": "24.0.0", + "bundled": true, + "requires": { + "merge-stream": "^1.0.1", + "supports-color": "^6.1.0" + }, + "dependencies": { + "supports-color": { + "version": "6.1.0", + "bundled": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "js-tokens": { + "version": "3.0.2", + "bundled": true + }, + "js-yaml": { + "version": "3.11.0", + "bundled": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "dependencies": { + "esprima": { + "version": "4.0.0", + "bundled": true + } + } + }, + "js2xmlparser": { + "version": "3.0.0", + "bundled": true, + "requires": { + "xmlcreate": "^1.0.1" + } + }, + "jsbn": { + "version": "0.1.1", + "bundled": true + }, + "jsdoc": { + "version": "3.5.5", + "bundled": true, + "requires": { + "babylon": "7.0.0-beta.19", + "bluebird": "~3.5.0", + "catharsis": "~0.8.9", + "escape-string-regexp": "~1.0.5", + "js2xmlparser": "~3.0.0", + "klaw": "~2.0.0", + "marked": "~0.3.6", + "mkdirp": "~0.5.1", + "requizzle": "~0.2.1", + "strip-json-comments": "~2.0.1", + "taffydb": "2.6.2", + "underscore": "~1.8.3" + }, + "dependencies": { + "babylon": { + "version": "7.0.0-beta.19", + "bundled": true + }, + "klaw": { + "version": "2.0.0", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.9" + } + }, + "taffydb": { + "version": "2.6.2", + "bundled": true + } + } + }, + "jsdom": { + "version": "7.2.2", + "bundled": true, + "optional": true, + "requires": { + "abab": "^1.0.0", + "acorn": "^2.4.0", + "acorn-globals": "^1.0.4", + "cssom": ">= 0.3.0 < 0.4.0", + "cssstyle": ">= 0.2.29 < 0.3.0", + "escodegen": "^1.6.1", + "nwmatcher": ">= 1.3.7 < 2.0.0", + "parse5": "^1.5.1", + "request": "^2.55.0", + "sax": "^1.1.4", + "symbol-tree": ">= 3.1.0 < 4.0.0", + "tough-cookie": "^2.2.0", + "webidl-conversions": "^2.0.0", + "whatwg-url-compat": "~0.6.5", + "xml-name-validator": ">= 2.0.1 < 3.0.0" + }, + "dependencies": { + "acorn": { + "version": "2.7.0", + "bundled": true, + "optional": true + } + } + }, + "jsesc": { + "version": "1.3.0", + "bundled": true + }, + "json-parse-better-errors": { + "version": "1.0.2", + "bundled": true + }, + "json-schema": { + "version": "0.2.3", + "bundled": true, + "optional": true + }, + "json-schema-traverse": { + "version": "0.3.1", + "bundled": true + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "bundled": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "bundled": true, + "optional": true + }, + "json5": { + "version": "0.5.1", + "bundled": true + }, + "jsonfile": { + "version": "2.4.0", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "jsprim": { + "version": "1.4.1", + "bundled": true, + "optional": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "jsx-ast-utils": { + "version": "2.0.1", + "bundled": true, + "requires": { + "array-includes": "^3.0.3" + } + }, + "kind-of": { + "version": "3.2.2", + "bundled": true, + "requires": { + "is-buffer": "^1.1.5" + } + }, + "klaw": { + "version": "1.3.1", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.9" + } + }, + "levn": { + "version": "0.3.0", + "bundled": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "live-server": { + "version": "1.2.0", + "bundled": true, + "requires": { + "chokidar": "^1.6.0", + "connect": "3.5.x", + "faye-websocket": "0.11.x", + "http-auth": "3.1.x", + "morgan": "^1.6.1", + "serve-index": "^1.7.2" + }, + "dependencies": { + "colors": { + "version": "1.1.2", + "bundled": true + }, + "cors": { + "version": "2.8.4", + "bundled": true, + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, + "debug": { + "version": "2.6.9", + "bundled": true, + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "1.1.1", + "bundled": true + }, + "etag": { + "version": "1.8.1", + "bundled": true + }, + "event-stream": { + "version": "3.3.4", + "bundled": true, + "requires": { + "duplexer": "~0.1.1", + "from": "~0", + "map-stream": "~0.1.0", + "pause-stream": "0.0.11", + "split": "0.3", + "stream-combiner": "~0.0.4", + "through": "~2.3.1" + } + }, + "fresh": { + "version": "0.5.2", + "bundled": true + }, + "http-errors": { + "version": "1.6.2", + "bundled": true, + "requires": { + "depd": "1.1.1", + "inherits": "2.0.3", + "setprototypeof": "1.0.3", + "statuses": ">= 1.3.1 < 2" + } + }, + "mime": { + "version": "1.4.1", + "bundled": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true + }, + "opn": { + "version": "5.1.0", + "bundled": true, + "requires": { + "is-wsl": "^1.1.0" + } + }, + "proxy-middleware": { + "version": "0.15.0", + "bundled": true + }, + "send": { + "version": "0.16.1", + "bundled": true, + "requires": { + "debug": "2.6.9", + "depd": "~1.1.1", + "destroy": "~1.0.4", + "encodeurl": "~1.0.1", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.6.2", + "mime": "1.4.1", + "ms": "2.0.0", + "on-finished": "~2.3.0", + "range-parser": "~1.2.0", + "statuses": "~1.3.1" + } + } + } + }, + "load-json-file": { + "version": "2.0.0", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "bundled": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + }, + "dependencies": { + "path-exists": { + "version": "3.0.0", + "bundled": true + } + } + }, + "lodash": { + "version": "4.17.11", + "bundled": true + }, + "lodash.assignin": { + "version": "4.2.0", + "bundled": true + }, + "lodash.bind": { + "version": "4.2.1", + "bundled": true + }, + "lodash.defaults": { + "version": "4.2.0", + "bundled": true + }, + "lodash.filter": { + "version": "4.6.0", + "bundled": true + }, + "lodash.flatten": { + "version": "4.4.0", + "bundled": true + }, + "lodash.foreach": { + "version": "4.5.0", + "bundled": true + }, + "lodash.map": { + "version": "4.6.0", + "bundled": true + }, + "lodash.merge": { + "version": "4.6.1", + "bundled": true + }, + "lodash.pick": { + "version": "4.4.0", + "bundled": true + }, + "lodash.reduce": { + "version": "4.6.0", + "bundled": true + }, + "lodash.reject": { + "version": "4.6.0", + "bundled": true + }, + "lodash.some": { + "version": "4.6.0", + "bundled": true + }, + "loose-envify": { + "version": "1.3.1", + "bundled": true, + "requires": { + "js-tokens": "^3.0.0" + } + }, + "loud-rejection": { + "version": "1.6.0", + "bundled": true, + "requires": { + "currently-unhandled": "^0.4.1", + "signal-exit": "^3.0.0" + } + }, + "lru-cache": { + "version": "4.1.3", + "bundled": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "magic-string": { + "version": "0.25.2", + "bundled": true, + "requires": { + "sourcemap-codec": "^1.4.4" + } + }, + "map-obj": { + "version": "1.0.1", + "bundled": true + }, + "map-stream": { + "version": "0.1.0", + "bundled": true + }, + "marked": { + "version": "0.3.19", + "bundled": true + }, + "math-random": { + "version": "1.0.1", + "bundled": true + }, + "meow": { + "version": "3.7.0", + "bundled": true, + "requires": { + "camelcase-keys": "^2.0.0", + "decamelize": "^1.1.2", + "loud-rejection": "^1.0.0", + "map-obj": "^1.0.1", + "minimist": "^1.1.3", + "normalize-package-data": "^2.3.4", + "object-assign": "^4.0.1", + "read-pkg-up": "^1.0.1", + "redent": "^1.0.0", + "trim-newlines": "^1.0.0" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true + } + } + }, + "merge-stream": { + "version": "1.0.1", + "bundled": true, + "requires": { + "readable-stream": "^2.0.1" + } + }, + "micromatch": { + "version": "2.3.11", + "bundled": true, + "requires": { + "arr-diff": "^2.0.0", + "array-unique": "^0.2.1", + "braces": "^1.8.2", + "expand-brackets": "^0.1.4", + "extglob": "^0.3.1", + "filename-regex": "^2.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.1", + "kind-of": "^3.0.2", + "normalize-path": "^2.0.1", + "object.omit": "^2.0.0", + "parse-glob": "^3.0.4", + "regex-cache": "^0.4.2" + } + }, + "mime-db": { + "version": "1.27.0", + "bundled": true, + "optional": true + }, + "mime-types": { + "version": "2.1.15", + "bundled": true, + "optional": true, + "requires": { + "mime-db": "~1.27.0" + } + }, + "mimic-fn": { + "version": "1.2.0", + "bundled": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "requires": { + "minimist": "0.0.8" + } + }, + "morgan": { + "version": "1.9.1", + "bundled": true, + "requires": { + "basic-auth": "~2.0.0", + "debug": "2.6.9", + "depd": "~1.1.2", + "on-finished": "~2.3.0", + "on-headers": "~1.0.1" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true + }, + "mute-stream": { + "version": "0.0.7", + "bundled": true + }, + "natural-compare": { + "version": "1.4.0", + "bundled": true + }, + "negotiator": { + "version": "0.6.1", + "bundled": true + }, + "node-fetch": { + "version": "1.7.3", + "bundled": true, + "requires": { + "encoding": "^0.1.11", + "is-stream": "^1.0.1" + } + }, + "normalize-package-data": { + "version": "2.4.0", + "bundled": true, + "requires": { + "hosted-git-info": "^2.1.4", + "is-builtin-module": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "normalize-path": { + "version": "2.1.1", + "bundled": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + }, + "nth-check": { + "version": "1.0.1", + "bundled": true, + "requires": { + "boolbase": "~1.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true + }, + "nwmatcher": { + "version": "1.4.3", + "bundled": true, + "optional": true + }, + "oauth-sign": { + "version": "0.9.0", + "bundled": true, + "optional": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true + }, + "object-keys": { + "version": "1.0.11", + "bundled": true + }, + "object.omit": { + "version": "2.0.1", + "bundled": true, + "requires": { + "for-own": "^0.1.4", + "is-extendable": "^0.1.1" + } + }, + "on-finished": { + "version": "2.3.0", + "bundled": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "on-headers": { + "version": "1.0.1", + "bundled": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "2.0.1", + "bundled": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "optionator": { + "version": "0.8.2", + "bundled": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.4", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "wordwrap": "~1.0.0" + }, + "dependencies": { + "wordwrap": { + "version": "1.0.0", + "bundled": true + } + } + }, + "orderedmap": { + "version": "1.0.0", + "bundled": true + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true + }, + "output-file-sync": { + "version": "1.1.2", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.4", + "mkdirp": "^0.5.1", + "object-assign": "^4.1.0" + } + }, + "p-limit": { + "version": "1.2.0", + "bundled": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "bundled": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "bundled": true + }, + "parchment": { + "version": "1.1.4", + "bundled": true + }, + "parse-glob": { + "version": "3.0.4", + "bundled": true, + "requires": { + "glob-base": "^0.3.0", + "is-dotfile": "^1.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.0" + } + }, + "parse-json": { + "version": "2.2.0", + "bundled": true, + "requires": { + "error-ex": "^1.2.0" + } + }, + "parse5": { + "version": "1.5.1", + "bundled": true, + "optional": true + }, + "parseurl": { + "version": "1.3.1", + "bundled": true + }, + "path-exists": { + "version": "2.1.0", + "bundled": true, + "requires": { + "pinkie-promise": "^2.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true + }, + "path-is-inside": { + "version": "1.0.2", + "bundled": true + }, + "path-parse": { + "version": "1.0.5", + "bundled": true + }, + "path-type": { + "version": "1.1.0", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "pause-stream": { + "version": "0.0.11", + "bundled": true, + "requires": { + "through": "~2.3" + } + }, + "performance-now": { + "version": "2.1.0", + "bundled": true, + "optional": true + }, + "pify": { + "version": "2.3.0", + "bundled": true + }, + "pinkie": { + "version": "2.0.4", + "bundled": true + }, + "pinkie-promise": { + "version": "2.0.1", + "bundled": true, + "requires": { + "pinkie": "^2.0.0" + } + }, + "pkg-conf": { + "version": "2.1.0", + "bundled": true, + "requires": { + "find-up": "^2.0.0", + "load-json-file": "^4.0.0" + }, + "dependencies": { + "find-up": { + "version": "2.1.0", + "bundled": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "load-json-file": { + "version": "4.0.0", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "bundled": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "pify": { + "version": "3.0.0", + "bundled": true + } + } + }, + "pkg-config": { + "version": "1.1.1", + "bundled": true, + "requires": { + "debug-log": "^1.0.0", + "find-root": "^1.0.0", + "xtend": "^4.0.1" + } + }, + "pkg-dir": { + "version": "1.0.0", + "bundled": true, + "requires": { + "find-up": "^1.0.0" + } + }, + "pluralize": { + "version": "7.0.0", + "bundled": true + }, + "prelude-ls": { + "version": "1.1.2", + "bundled": true + }, + "preserve": { + "version": "0.2.0", + "bundled": true + }, + "private": { + "version": "0.1.7", + "bundled": true + }, + "process-nextick-args": { + "version": "1.0.7", + "bundled": true + }, + "progress": { + "version": "2.0.0", + "bundled": true + }, + "promise": { + "version": "7.3.1", + "bundled": true, + "requires": { + "asap": "~2.0.3" + } + }, + "prop-types": { + "version": "15.6.1", + "bundled": true, + "requires": { + "fbjs": "^0.8.16", + "loose-envify": "^1.3.1", + "object-assign": "^4.1.1" + } + }, + "prosemirror-commands": { + "version": "1.0.7", + "bundled": true, + "requires": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.0.0" + } + }, + "prosemirror-dropcursor": { + "version": "1.1.1", + "bundled": true, + "requires": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0", + "prosemirror-view": "^1.1.0" + } + }, + "prosemirror-example-setup": { + "version": "1.0.1", + "bundled": true, + "requires": { + "prosemirror-commands": "^1.0.0", + "prosemirror-dropcursor": "^1.0.0", + "prosemirror-gapcursor": "^1.0.0", + "prosemirror-history": "^1.0.0", + "prosemirror-inputrules": "^1.0.0", + "prosemirror-keymap": "^1.0.0", + "prosemirror-menu": "^1.0.0", + "prosemirror-schema-list": "^1.0.0", + "prosemirror-state": "^1.0.0" + } + }, + "prosemirror-gapcursor": { + "version": "1.0.3", + "bundled": true, + "requires": { + "prosemirror-keymap": "^1.0.0", + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-view": "^1.0.0" + } + }, + "prosemirror-history": { + "version": "1.0.3", + "bundled": true, + "requires": { + "prosemirror-state": "^1.2.2", + "prosemirror-transform": "^1.0.0", + "rope-sequence": "^1.2.0" + } + }, + "prosemirror-inputrules": { + "version": "1.0.1", + "bundled": true, + "requires": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.0.0" + } + }, + "prosemirror-keymap": { + "version": "1.0.1", + "bundled": true, + "requires": { + "prosemirror-state": "^1.0.0", + "w3c-keyname": "^1.1.8" + } + }, + "prosemirror-menu": { + "version": "1.0.5", + "bundled": true, + "requires": { + "crel": "^3.0.0", + "prosemirror-commands": "^1.0.0", + "prosemirror-history": "^1.0.0", + "prosemirror-state": "^1.0.0" + } + }, + "prosemirror-model": { + "version": "1.6.3", + "bundled": true, + "requires": { + "orderedmap": "^1.0.0" + } + }, + "prosemirror-schema-basic": { + "version": "1.0.0", + "bundled": true, + "requires": { + "prosemirror-model": "^1.0.0" + } + }, + "prosemirror-schema-list": { + "version": "1.0.1", + "bundled": true, + "requires": { + "prosemirror-model": "^1.0.0", + "prosemirror-transform": "^1.0.0" + } + }, + "prosemirror-state": { + "version": "1.2.2", + "bundled": true, + "requires": { + "prosemirror-model": "^1.0.0", + "prosemirror-transform": "^1.0.0" + } + }, + "prosemirror-transform": { + "version": "1.1.3", + "bundled": true, + "requires": { + "prosemirror-model": "^1.0.0" + } + }, + "prosemirror-view": { + "version": "1.6.5", + "bundled": true, + "requires": { + "prosemirror-model": "^1.1.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0" + } + }, + "pseudomap": { + "version": "1.0.2", + "bundled": true + }, + "psl": { + "version": "1.1.29", + "bundled": true, + "optional": true + }, + "punycode": { + "version": "1.4.1", + "bundled": true + }, + "qs": { + "version": "6.5.2", + "bundled": true, + "optional": true + }, + "quill": { + "version": "1.3.6", + "bundled": true, + "requires": { + "clone": "^2.1.1", + "deep-equal": "^1.0.1", + "eventemitter3": "^2.0.3", + "extend": "^3.0.1", + "parchment": "^1.1.4", + "quill-delta": "^3.6.2" + } + }, + "quill-cursors": { + "version": "1.0.3", + "bundled": true, + "requires": { + "rangefix": "^0.2.5", + "tinycolor2": "^1.4.1" + } + }, + "quill-delta": { + "version": "3.6.3", + "bundled": true, + "requires": { + "deep-equal": "^1.0.1", + "extend": "^3.0.2", + "fast-diff": "1.1.2" + } + }, + "randomatic": { + "version": "3.1.0", + "bundled": true, + "requires": { + "is-number": "^4.0.0", + "kind-of": "^6.0.0", + "math-random": "^1.0.1" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "bundled": true + }, + "kind-of": { + "version": "6.0.2", + "bundled": true + } + } + }, + "range-parser": { + "version": "1.2.0", + "bundled": true + }, + "rangefix": { + "version": "0.2.5", + "bundled": true + }, + "read-pkg": { + "version": "1.1.0", + "bundled": true, + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + }, + "dependencies": { + "load-json-file": { + "version": "1.1.0", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + } + }, + "strip-bom": { + "version": "2.0.0", + "bundled": true, + "requires": { + "is-utf8": "^0.2.0" + } + } + } + }, + "read-pkg-up": { + "version": "1.0.1", + "bundled": true, + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + } + }, + "readable-stream": { + "version": "2.3.3", + "bundled": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~1.0.6", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.0.3", + "util-deprecate": "~1.0.1" + } + }, + "readdirp": { + "version": "2.1.0", + "bundled": true, + "requires": { + "graceful-fs": "^4.1.2", + "minimatch": "^3.0.2", + "readable-stream": "^2.0.2", + "set-immediate-shim": "^1.0.1" + } + }, + "redent": { + "version": "1.0.0", + "bundled": true, + "requires": { + "indent-string": "^2.1.0", + "strip-indent": "^1.0.1" + } + }, + "regenerate": { + "version": "1.3.2", + "bundled": true + }, + "regenerator-runtime": { + "version": "0.10.5", + "bundled": true + }, + "regenerator-transform": { + "version": "0.10.1", + "bundled": true, + "requires": { + "babel-runtime": "^6.18.0", + "babel-types": "^6.19.0", + "private": "^0.1.6" + } + }, + "regex-cache": { + "version": "0.4.3", + "bundled": true, + "requires": { + "is-equal-shallow": "^0.1.3", + "is-primitive": "^2.0.0" + } + }, + "regexpu-core": { + "version": "2.0.0", + "bundled": true, + "requires": { + "regenerate": "^1.2.1", + "regjsgen": "^0.2.0", + "regjsparser": "^0.1.4" + } + }, + "regjsgen": { + "version": "0.2.0", + "bundled": true + }, + "regjsparser": { + "version": "0.1.5", + "bundled": true, + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "bundled": true + } + } + }, + "remove-trailing-separator": { + "version": "1.0.2", + "bundled": true + }, + "repeat-element": { + "version": "1.1.2", + "bundled": true + }, + "repeat-string": { + "version": "1.6.1", + "bundled": true + }, + "repeating": { + "version": "2.0.1", + "bundled": true, + "requires": { + "is-finite": "^1.0.0" + } + }, + "request": { + "version": "2.88.0", + "bundled": true, + "optional": true, + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "extend": { + "version": "3.0.2", + "bundled": true, + "optional": true + }, + "mime-db": { + "version": "1.37.0", + "bundled": true, + "optional": true + }, + "mime-types": { + "version": "2.1.21", + "bundled": true, + "optional": true, + "requires": { + "mime-db": "~1.37.0" + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true, + "optional": true + }, + "tough-cookie": { + "version": "2.4.3", + "bundled": true, + "optional": true, + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + } + }, + "uuid": { + "version": "3.3.2", + "bundled": true, + "optional": true + } + } + }, + "require-relative": { + "version": "0.8.7", + "bundled": true + }, + "require-uncached": { + "version": "1.0.3", + "bundled": true, + "requires": { + "caller-path": "^0.1.0", + "resolve-from": "^1.0.0" + } + }, + "requizzle": { + "version": "0.2.1", + "bundled": true, + "requires": { + "underscore": "~1.6.0" + }, + "dependencies": { + "underscore": { + "version": "1.6.0", + "bundled": true + } + } + }, + "resolve": { + "version": "1.3.3", + "bundled": true, + "requires": { + "path-parse": "^1.0.5" + } + }, + "resolve-from": { + "version": "1.0.1", + "bundled": true + }, + "restore-cursor": { + "version": "2.0.0", + "bundled": true, + "requires": { + "onetime": "^2.0.0", + "signal-exit": "^3.0.2" + } + }, + "rimraf": { + "version": "2.6.2", + "bundled": true, + "requires": { + "glob": "^7.0.5" + } + }, + "rollup": { + "version": "1.1.2", + "bundled": true, + "requires": { + "@types/estree": "0.0.39", + "@types/node": "*", + "acorn": "^6.0.5" + }, + "dependencies": { + "acorn": { + "version": "6.1.0", + "bundled": true + } + } + }, + "rollup-cli": { + "version": "1.0.9", + "bundled": true + }, + "rollup-plugin-commonjs": { + "version": "9.2.0", + "bundled": true, + "requires": { + "estree-walker": "^0.5.2", + "magic-string": "^0.25.1", + "resolve": "^1.8.1", + "rollup-pluginutils": "^2.3.3" + }, + "dependencies": { + "path-parse": { + "version": "1.0.6", + "bundled": true + }, + "resolve": { + "version": "1.10.0", + "bundled": true, + "requires": { + "path-parse": "^1.0.6" + } + } + } + }, + "rollup-plugin-node-resolve": { + "version": "4.0.0", + "bundled": true, + "requires": { + "builtin-modules": "^3.0.0", + "is-module": "^1.0.0", + "resolve": "^1.8.1" + }, + "dependencies": { + "builtin-modules": { + "version": "3.0.0", + "bundled": true + }, + "path-parse": { + "version": "1.0.6", + "bundled": true + }, + "resolve": { + "version": "1.10.0", + "bundled": true, + "requires": { + "path-parse": "^1.0.6" + } + } + } + }, + "rollup-plugin-terser": { + "version": "4.0.4", + "bundled": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "jest-worker": "^24.0.0", + "serialize-javascript": "^1.6.1", + "terser": "^3.14.1" + } + }, + "rollup-plugin-uglify-es": { + "version": "0.0.1", + "bundled": true, + "requires": { + "uglify-es": "3.0.3" + } + }, + "rollup-pluginutils": { + "version": "2.3.3", + "bundled": true, + "requires": { + "estree-walker": "^0.5.2", + "micromatch": "^2.3.11" + } + }, + "rollup-watch": { + "version": "4.3.1", + "bundled": true, + "requires": { + "chokidar": "^1.7.0", + "require-relative": "0.8.7", + "rollup-pluginutils": "^2.0.1" + } + }, + "rope-sequence": { + "version": "1.2.2", + "bundled": true + }, + "run-async": { + "version": "2.3.0", + "bundled": true, + "requires": { + "is-promise": "^2.1.0" + } + }, + "run-parallel": { + "version": "1.1.9", + "bundled": true + }, + "rx": { + "version": "2.3.24", + "bundled": true + }, + "rx-lite": { + "version": "4.0.8", + "bundled": true + }, + "rx-lite-aggregates": { + "version": "4.0.8", + "bundled": true, + "requires": { + "rx-lite": "*" + } + }, + "safe-buffer": { + "version": "5.1.1", + "bundled": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "optional": true + }, + "semver": { + "version": "5.3.0", + "bundled": true + }, + "serialize-javascript": { + "version": "1.6.1", + "bundled": true + }, + "serve-index": { + "version": "1.9.1", + "bundled": true, + "requires": { + "accepts": "~1.3.4", + "batch": "0.6.1", + "debug": "2.6.9", + "escape-html": "~1.0.3", + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "dependencies": { + "mime-db": { + "version": "1.37.0", + "bundled": true + }, + "mime-types": { + "version": "2.1.21", + "bundled": true, + "requires": { + "mime-db": "~1.37.0" + } + }, + "parseurl": { + "version": "1.3.2", + "bundled": true + } + } + }, + "set-immediate-shim": { + "version": "1.0.1", + "bundled": true + }, + "setimmediate": { + "version": "1.0.5", + "bundled": true + }, + "setprototypeof": { + "version": "1.0.3", + "bundled": true + }, + "shebang-command": { + "version": "1.2.0", + "bundled": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "bundled": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true + }, + "slash": { + "version": "1.0.0", + "bundled": true + }, + "slice-ansi": { + "version": "1.0.0", + "bundled": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0" + } + }, + "source-map": { + "version": "0.5.6", + "bundled": true + }, + "source-map-support": { + "version": "0.4.15", + "bundled": true, + "requires": { + "source-map": "^0.5.6" + } + }, + "sourcemap-codec": { + "version": "1.4.4", + "bundled": true + }, + "spawn-command": { + "version": "0.0.2-1", + "bundled": true + }, + "spdx-correct": { + "version": "1.0.2", + "bundled": true, + "requires": { + "spdx-license-ids": "^1.0.2" + } + }, + "spdx-expression-parse": { + "version": "1.0.4", + "bundled": true + }, + "spdx-license-ids": { + "version": "1.2.2", + "bundled": true + }, + "split": { + "version": "0.3.3", + "bundled": true, + "requires": { + "through": "2" + } + }, + "sprintf-js": { + "version": "1.0.3", + "bundled": true + }, + "sshpk": { + "version": "1.15.1", + "bundled": true, + "optional": true, + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + } + }, + "stack-generator": { + "version": "2.0.1", + "bundled": true, + "requires": { + "stackframe": "^1.0.3" + } + }, + "stackframe": { + "version": "1.0.3", + "bundled": true + }, + "stacktrace-gps": { + "version": "3.0.1", + "bundled": true, + "requires": { + "source-map": "0.5.6", + "stackframe": "^1.0.3" + } + }, + "stacktrace-js": { + "version": "2.0.0", + "bundled": true, + "requires": { + "error-stack-parser": "^2.0.1", + "stack-generator": "^2.0.1", + "stacktrace-gps": "^3.0.1" + } + }, + "standard": { + "version": "11.0.1", + "bundled": true, + "requires": { + "eslint": "~4.18.0", + "eslint-config-standard": "11.0.0", + "eslint-config-standard-jsx": "5.0.0", + "eslint-plugin-import": "~2.9.0", + "eslint-plugin-node": "~6.0.0", + "eslint-plugin-promise": "~3.7.0", + "eslint-plugin-react": "~7.7.0", + "eslint-plugin-standard": "~3.0.1", + "standard-engine": "~8.0.0" + } + }, + "standard-engine": { + "version": "8.0.1", + "bundled": true, + "requires": { + "deglob": "^2.1.0", + "get-stdin": "^6.0.0", + "minimist": "^1.1.0", + "pkg-conf": "^2.0.0" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true + } + } + }, + "statuses": { + "version": "1.3.1", + "bundled": true + }, + "stream-combiner": { + "version": "0.0.4", + "bundled": true, + "requires": { + "duplexer": "~0.1.1" + } + }, + "string-width": { + "version": "2.1.1", + "bundled": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "bundled": true + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "string_decoder": { + "version": "1.0.3", + "bundled": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "bundled": true + }, + "strip-indent": { + "version": "1.0.1", + "bundled": true, + "requires": { + "get-stdin": "^4.0.1" + }, + "dependencies": { + "get-stdin": { + "version": "4.0.1", + "bundled": true + } + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true + }, + "supports-color": { + "version": "2.0.0", + "bundled": true + }, + "symbol-tree": { + "version": "3.2.2", + "bundled": true, + "optional": true + }, + "table": { + "version": "4.0.2", + "bundled": true, + "requires": { + "ajv": "^5.2.3", + "ajv-keywords": "^2.1.0", + "chalk": "^2.1.0", + "lodash": "^4.17.4", + "slice-ansi": "1.0.0", + "string-width": "^2.1.1" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "bundled": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.1", + "bundled": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "has-flag": { + "version": "3.0.0", + "bundled": true + }, + "supports-color": { + "version": "5.4.0", + "bundled": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "taffydb": { + "version": "2.7.2", + "bundled": true + }, + "terser": { + "version": "3.16.1", + "bundled": true, + "requires": { + "commander": "~2.17.1", + "source-map": "~0.6.1", + "source-map-support": "~0.5.9" + }, + "dependencies": { + "commander": { + "version": "2.17.1", + "bundled": true + }, + "source-map": { + "version": "0.6.1", + "bundled": true + }, + "source-map-support": { + "version": "0.5.10", + "bundled": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + } + } + }, + "text-table": { + "version": "0.2.0", + "bundled": true + }, + "through": { + "version": "2.3.8", + "bundled": true + }, + "tinycolor2": { + "version": "1.4.1", + "bundled": true + }, + "tmp": { + "version": "0.0.33", + "bundled": true, + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "to-fast-properties": { + "version": "1.0.3", + "bundled": true + }, + "tough-cookie": { + "version": "2.3.4", + "bundled": true, + "optional": true, + "requires": { + "punycode": "^1.4.1" + } + }, + "tr46": { + "version": "0.0.3", + "bundled": true, + "optional": true + }, + "tree-kill": { + "version": "1.2.0", + "bundled": true + }, + "trim-newlines": { + "version": "1.0.0", + "bundled": true + }, + "trim-right": { + "version": "1.0.1", + "bundled": true + }, + "tui-jsdoc-template": { + "version": "1.2.2", + "bundled": true, + "requires": { + "cheerio": "^0.22.0" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "bundled": true, + "optional": true, + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "bundled": true + }, + "type-check": { + "version": "0.3.2", + "bundled": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "typedarray": { + "version": "0.0.6", + "bundled": true + }, + "ua-parser-js": { + "version": "0.7.18", + "bundled": true + }, + "uglify-es": { + "version": "3.0.3", + "bundled": true, + "requires": { + "commander": "~2.9.0", + "source-map": "~0.5.1", + "uglify-to-browserify": "~1.0.0" + }, + "dependencies": { + "commander": { + "version": "2.9.0", + "bundled": true, + "requires": { + "graceful-readlink": ">= 1.0.0" + } + } + } + }, + "uglify-to-browserify": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "underscore": { + "version": "1.8.3", + "bundled": true + }, + "underscore-contrib": { + "version": "0.3.0", + "bundled": true, + "requires": { + "underscore": "1.6.0" + }, + "dependencies": { + "underscore": { + "version": "1.6.0", + "bundled": true + } + } + }, + "uniq": { + "version": "1.0.1", + "bundled": true + }, + "universalify": { + "version": "0.1.2", + "bundled": true + }, + "unix-crypt-td-js": { + "version": "1.0.0", + "bundled": true + }, + "unpipe": { + "version": "1.0.0", + "bundled": true + }, + "user-home": { + "version": "1.1.1", + "bundled": true + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true + }, + "utils-merge": { + "version": "1.0.0", + "bundled": true + }, + "uuid": { + "version": "3.1.0", + "bundled": true + }, + "v8flags": { + "version": "2.1.1", + "bundled": true, + "requires": { + "user-home": "^1.1.1" + } + }, + "validate-npm-package-license": { + "version": "3.0.1", + "bundled": true, + "requires": { + "spdx-correct": "~1.0.0", + "spdx-expression-parse": "~1.0.0" + } + }, + "vary": { + "version": "1.1.1", + "bundled": true + }, + "verror": { + "version": "1.10.0", + "bundled": true, + "optional": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "w3c-keyname": { + "version": "1.1.8", + "bundled": true + }, + "webidl-conversions": { + "version": "2.0.1", + "bundled": true, + "optional": true + }, + "websocket-driver": { + "version": "0.6.5", + "bundled": true, + "requires": { + "websocket-extensions": ">=0.1.1" + } + }, + "websocket-extensions": { + "version": "0.1.1", + "bundled": true + }, + "whatwg-fetch": { + "version": "2.0.4", + "bundled": true + }, + "whatwg-url-compat": { + "version": "0.6.5", + "bundled": true, + "optional": true, + "requires": { + "tr46": "~0.0.1" + } + }, + "which": { + "version": "1.2.14", + "bundled": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true + }, + "write": { + "version": "0.2.1", + "bundled": true, + "requires": { + "mkdirp": "^0.5.1" + } + }, + "xml-name-validator": { + "version": "2.0.1", + "bundled": true, + "optional": true + }, + "xmlcreate": { + "version": "1.0.2", + "bundled": true + }, + "xtend": { + "version": "4.0.1", + "bundled": true + }, + "yallist": { + "version": "2.1.2", + "bundled": true + } + } + } + } }, "yallist": { "version": "2.1.2", diff --git a/package.json b/package.json index 2078d503..70dc95f7 100644 --- a/package.json +++ b/package.json @@ -2,15 +2,15 @@ "name": "yjs", "version": "13.0.0-78", "description": "A ", - "main": "./build/yjs.js", - "module": "./index.js'", + "main": "./dist/yjs.js", + "module": "./dist/yjs.mjs'", "sideEffects": false, "scripts": { "test": "npm run lint", - "build": "rm -rf build examples/build && PRODUCTION=1 rollup -c", + "dist": "rm -rf build examples/build && PRODUCTION=1 rollup -c", "watch": "rollup -wc", "debug": "concurrently 'npm run watch' 'cutest-serve build/y.test.js -o'", - "lint": "standard **/*.js", + "lint": "standard", "docs": "rm -rf docs; jsdoc --configure ./.jsdoc.json --verbose --readme ./README.v13.md --package ./package.json || true", "serve-docs": "npm run docs && serve ./docs/", "postversion": "npm run build", @@ -18,38 +18,23 @@ "now-start": "npm run websocket-server" }, "files": [ - "build/*", - "bindings/*", - "docs/*", + "dist/*", "examples/*", - "lib/*", - "persistences/*", - "protocols/*", - "provider/*", - "bindings/*", - "structs/*", - "tests/*", - "types/*", - "utils/*", - "index.js", + "docs/*", "README.md", "LICENSE" ], "dictionaries": { "doc": "docs", "example": "examples", - "test": "tests", - "lib": "./" - }, - "bin": { - "y-websocket-server": "provider/websocket/server.js" + "test": "tests" }, "standard": { "ignore": [ - "/build", + "/dist", "/node_modules", - "/rollup.test.js", - "/rollup.test.js" + "/docs", + "/examples/build" ] }, "repository": { @@ -67,16 +52,18 @@ }, "homepage": "http://y-js.org", "devDependencies": { - "@types/ws": "^6.0.1", "babel-cli": "^6.26.0", "babel-plugin-external-helpers": "^6.22.0", "babel-plugin-transform-regenerator": "^6.26.0", "babel-plugin-transform-runtime": "^6.23.0", "babel-preset-latest": "^6.24.1", + "codemirror": "^5.42.0", "concurrently": "^3.6.1", + "crel": "^3.1.0", "cutest": "^0.1.9", "esdoc": "^1.1.0", "esdoc-standard-plugin": "^1.0.0", + "esm": "^3.2.6", "jsdoc": "^3.5.5", "prosemirror-example-setup": "^1.0.1", "prosemirror-schema-basic": "^1.0.0", @@ -84,24 +71,19 @@ "prosemirror-view": "^1.6.5", "quill": "^1.3.6", "quill-cursors": "^1.0.3", - "rollup": "^0.58.2", + "rollup": "^1.1.2", "rollup-cli": "^1.0.9", - "rollup-plugin-babel": "^2.7.1", - "rollup-plugin-commonjs": "^8.4.1", - "rollup-plugin-inject": "^2.2.0", - "rollup-plugin-multi-entry": "^2.0.2", - "rollup-plugin-node-resolve": "^3.4.0", - "rollup-plugin-uglify": "^6.0.0", + "rollup-plugin-commonjs": "^9.2.0", + "rollup-plugin-node-resolve": "^4.0.0", + "rollup-plugin-terser": "^4.0.4", "rollup-plugin-uglify-es": "0.0.1", - "rollup-regenerator-runtime": "^6.23.1", - "rollup-watch": "^3.2.2", + "rollup-watch": "^4.3.1", "standard": "^11.0.1", "tui-jsdoc-template": "^1.2.2", - "codemirror": "^5.42.0", - "crel": "^3.1.0" + "y-codemirror": "*" }, - "optionalDependencies": { - "level": "^4.0.0", - "ws": "^6.1.0" + "dependencies": { + "funlib": "file:../funlib", + "y-protocols": "file:../y-protocols" } } diff --git a/persistence/FilePersistence.js b/persistence/FilePersistence.js deleted file mode 100644 index 0c22ed43..00000000 --- a/persistence/FilePersistence.js +++ /dev/null @@ -1,74 +0,0 @@ -/* -import fs from 'fs' -import path from 'path' -import * as encoding from '../lib/encoding.js' -import * as decoding from '../lib/decoding.js' -import { createMutex } from '../lib/mutex.js' -import { encodeUpdate, encodeStructsDS, decodePersisted } from './decodePersisted.js' - -function createFilePath (persistence, roomName) { - // TODO: filename checking! - return path.join(persistence.dir, roomName) -} - -export class FilePersistence { - constructor (dir) { - this.dir = dir - this._mutex = createMutex() - } - setRemoteUpdateCounter (roomName, remoteUpdateCounter) { - // TODO: implement - // nop - } - saveUpdate (room, y, encodedStructs) { - return new Promise((resolve, reject) => { - this._mutex(() => { - const filePath = createFilePath(this, room) - const updateMessage = encoding.createEncoder() - encodeUpdate(y, encodedStructs, updateMessage) - fs.appendFile(filePath, Buffer.from(encoding.toBuffer(updateMessage)), (err) => { - if (err !== null) { - reject(err) - } else { - resolve() - } - }) - }, resolve) - }) - } - saveState (roomName, y) { - return new Promise((resolve, reject) => { - const encoder = encoding.createEncoder() - encodeStructsDS(y, encoder) - const filePath = createFilePath(this, roomName) - fs.writeFile(filePath, Buffer.from(encoding.toBuffer(encoder)), (err) => { - if (err !== null) { - reject(err) - } else { - resolve() - } - }) - }) - } - readState (roomName, y) { - // Check if the file exists in the current directory. - return new Promise((resolve, reject) => { - const filePath = path.join(this.dir, roomName) - fs.readFile(filePath, (err, data) => { - if (err !== null) { - resolve() - // reject(err) - } else { - this._mutex(() => { - console.info(`unpacking data (${data.length})`) - console.time('unpacking') - decodePersisted(y, decoding.createDecoder(data.buffer)) - console.timeEnd('unpacking') - }) - resolve() - } - }) - }) - } -} -*/ diff --git a/persistence/IndexedDBPersistence.js b/persistence/IndexedDBPersistence.js deleted file mode 100644 index 6c4cc950..00000000 --- a/persistence/IndexedDBPersistence.js +++ /dev/null @@ -1,553 +0,0 @@ -/* -import { Y } from '../utils/Y.js' -import { createMutex } from '../lib/mutex.js' -import { decodePersisted, encodeStructsDS, encodeUpdate, PERSIST_STRUCTS_DS, PERSIST_UPDATE } from './decodePersisted.js' - -function rtop (request) { - return new Promise(function (resolve, reject) { - request.onerror = function (event) { - reject(new Error(event.target.error)) - } - request.onblocked = function () { - location.reload() - } - request.onsuccess = function (event) { - resolve(event.target.result) - } - }) -} - -function openDB (room) { - return new Promise(function (resolve, reject) { - let request = indexedDB.open(room) - request.onupgradeneeded = function (event) { - const db = event.target.result - if (db.objectStoreNames.contains('updates')) { - db.deleteObjectStore('updates') - } - db.createObjectStore('updates', {autoIncrement: true}) - } - request.onerror = function (event) { - reject(new Error(event.target.error)) - } - request.onblocked = function () { - location.reload() - } - request.onsuccess = function (event) { - const db = event.target.result - db.onversionchange = function () { db.close() } - resolve(db) - } - }) -} - -function persist (room) { - let t = room.db.transaction(['updates'], 'readwrite') - let updatesStore = t.objectStore('updates') - return rtop(updatesStore.getAll()) - .then(updates => { - // apply all previous updates before deleting them - room.mutex(() => { - updates.forEach(update => { - decodePersisted(y, new BinaryDecoder(update)) - }) - }) - const encoder = new BinaryEncoder() - encodeStructsDS(y, encoder) - // delete all pending updates - rtop(updatesStore.clear()).then(() => { - // write current model - updatesStore.put(encoder.createBuffer()) - }) - }) -} - -function saveUpdate (room, updateBuffer) { - const db = room.db - if (db !== null) { - const t = db.transaction(['updates'], 'readwrite') - const updatesStore = t.objectStore('updates') - const updatePut = rtop(updatesStore.put(updateBuffer)) - rtop(updatesStore.count()).then(cnt => { - if (cnt >= PREFERRED_TRIM_SIZE) { - persist(room) - } - }) - return updatePut - } -} - -function registerRoomInPersistence (documentsDB, roomName) { - return documentsDB.then( - db => Promise.all([ - db, - rtop(db.transaction(['documents'], 'readonly').objectStore('documents').get(roomName)) - ]) - ).then( - ([db, doc]) => { - if (doc === undefined) { - return rtop(db.transaction(['documents'], 'readwrite').objectStore('documents').add({ roomName, serverUpdateCounter: 0 })) - } - } - ) -} - -const PREFERRED_TRIM_SIZE = 400 - -export class IndexedDBPersistence { - constructor () { - this._rooms = new Map() - this._documentsDB = new Promise(function (resolve, reject) { - let request = indexedDB.open('_yjs_documents') - request.onupgradeneeded = function (event) { - const db = event.target.result - if (db.objectStoreNames.contains('documents')) { - db.deleteObjectStore('documents') - } - db.createObjectStore('documents', { keyPath: "roomName" }) - } - request.onerror = function (event) { - reject(new Error(event.target.error)) - } - request.onblocked = function () { - location.reload() - } - request.onsuccess = function (event) { - const db = event.target.result - db.onversionchange = function () { db.close() } - resolve(db) - } - }) - addEventListener('unload', () => { - // close everything when page unloads - this._rooms.forEach(room => { - if (room.db !== null) { - room.db.close() - } else { - room.dbPromise.then(db => db.close()) - } - }) - this._documentsDB.then(db => db.close()) - }) - } - getAllDocuments () { - return this._documentsDB.then( - db => rtop(db.transaction(['documents'], 'readonly').objectStore('documents').getAll()) - ) - } - setRemoteUpdateCounter (roomName, remoteUpdateCounter) { - this._documentsDB.then( - db => rtop(db.transaction(['documents'], 'readwrite').objectStore('documents').put({ roomName, remoteUpdateCounter })) - ) - } - - _createYInstance (roomName) { - const room = this._rooms.get(roomName) - if (room !== undefined) { - return room.y - } - const y = new Y() - return openDB(roomName).then( - db => rtop(db.transaction(['updates'], 'readonly').objectStore('updates').getAll()) - ).then( - updates => - y.transact(() => { - updates.forEach(update => { - decodePersisted(y, new BinaryDecoder(update)) - }) - }, true) - ).then(() => Promise.resolve(y)) - } - - _persistStructsDS (roomName, structsDS) { - const encoder = new BinaryEncoder() - encoder.writeVarUint(PERSIST_STRUCTS_DS) - encoder.writeArrayBuffer(structsDS) - return openDB(roomName).then(db => { - const t = db.transaction(['updates'], 'readwrite') - const updatesStore = t.objectStore('updates') - return rtop(updatesStore.put(encoder.createBuffer())) - }) - } - - _persistStructs (roomName, structs) { - const encoder = new BinaryEncoder() - encoder.writeVarUint(PERSIST_UPDATE) - encoder.writeArrayBuffer(structs) - return openDB(roomName).then(db => { - const t = db.transaction(['updates'], 'readwrite') - const updatesStore = t.objectStore('updates') - return rtop(updatesStore.put(encoder.createBuffer())) - }) - } - - connectY (roomName, y) { - if (this._rooms.has(roomName)) { - throw new Error('A Y instance is already bound to this room!') - } - let room = { - db: null, - dbPromise: null, - channel: null, - mutex: createMutex(), - y - } - if (typeof BroadcastChannel !== 'undefined') { - room.channel = new BroadcastChannel('__yjs__' + roomName) - room.channel.addEventListener('message', e => { - room.mutex(function () { - decodePersisted(y, new BinaryDecoder(e.data)) - }) - }) - } - y.on('destroyed', () => { - this.disconnectY(roomName, y) - }) - y.on('afterTransaction', (y, transaction) => { - room.mutex(() => { - if (transaction.encodedStructsLen > 0) { - const encoder = new BinaryEncoder() - const update = new BinaryEncoder() - encodeUpdate(y, transaction.encodedStructs, update) - const updateBuffer = update.createBuffer() - if (room.channel !== null) { - room.channel.postMessage(updateBuffer) - } - if (transaction.encodedStructsLen > 0 - import { Y } from '../utils/Y.js' - import { createMutex } from '../lib/mutex.js' - import { decodePersisted, encodeStructsDS, encodeUpdate, PERSIST_STRUCTS_DS, PERSIST_UPDATE } from './decodePersisted.js' - - function rtop (request) { - return new Promise(function (resolve, reject) { - request.onerror = function (event) { - reject(new Error(event.target.error)) - } - request.onblocked = function () { - location.reload() - } - request.onsuccess = function (event) { - resolve(event.target.result) - } - }) - } - - function openDB (room) { - return new Promise(function (resolve, reject) { - let request = indexedDB.open(room) - request.onupgradeneeded = function (event) { - const db = event.target.result - if (db.objectStoreNames.contains('updates')) { - db.deleteObjectStore('updates') - } - db.createObjectStore('updates', {autoIncrement: true}) - } - request.onerror = function (event) { - reject(new Error(event.target.error)) - } - request.onblocked = function () { - location.reload() - } - request.onsuccess = function (event) { - const db = event.target.result - db.onversionchange = function () { db.close() } - resolve(db) - } - }) - } - - function persist (room) { - let t = room.db.transaction(['updates'], 'readwrite') - let updatesStore = t.objectStore('updates') - return rtop(updatesStore.getAll()) - .then(updates => { - // apply all previous updates before deleting them - room.mutex(() => { - updates.forEach(update => { - decodePersisted(y, new BinaryDecoder(update)) - }) - }) - const encoder = new BinaryEncoder() - encodeStructsDS(y, encoder) - // delete all pending updates - rtop(updatesStore.clear()).then(() => { - // write current model - updatesStore.put(encoder.createBuffer()) - }) - }) - } - - function saveUpdate (room, updateBuffer) { - const db = room.db - if (db !== null) { - const t = db.transaction(['updates'], 'readwrite') - const updatesStore = t.objectStore('updates') - const updatePut = rtop(updatesStore.put(updateBuffer)) - rtop(updatesStore.count()).then(cnt => { - if (cnt >= PREFERRED_TRIM_SIZE) { - persist(room) - } - }) - return updatePut - } - } - - function registerRoomInPersistence (documentsDB, roomName) { - return documentsDB.then( - db => Promise.all([ - db, - rtop(db.transaction(['documents'], 'readonly').objectStore('documents').get(roomName)) - ]) - ).then( - ([db, doc]) => { - if (doc === undefined) { - return rtop(db.transaction(['documents'], 'readwrite').objectStore('documents').add({ roomName, serverUpdateCounter: 0 })) - } - } - ) - } - - const PREFERRED_TRIM_SIZE = 400 - - export class IndexedDBPersistence { - constructor () { - this._rooms = new Map() - this._documentsDB = new Promise(function (resolve, reject) { - let request = indexedDB.open('_yjs_documents') - request.onupgradeneeded = function (event) { - const db = event.target.result - if (db.objectStoreNames.contains('documents')) { - db.deleteObjectStore('documents') - } - db.createObjectStore('documents', { keyPath: "roomName" }) - } - request.onerror = function (event) { - reject(new Error(event.target.error)) - } - request.onblocked = function () { - location.reload() - } - request.onsuccess = function (event) { - const db = event.target.result - db.onversionchange = function () { db.close() } - resolve(db) - } - }) - addEventListener('unload', () => { - // close everything when page unloads - this._rooms.forEach(room => { - if (room.db !== null) { - room.db.close() - } else { - room.dbPromise.then(db => db.close()) - } - }) - this._documentsDB.then(db => db.close()) - }) - } - getAllDocuments () { - return this._documentsDB.then( - db => rtop(db.transaction(['documents'], 'readonly').objectStore('documents').getAll()) - ) - } - setRemoteUpdateCounter (roomName, remoteUpdateCounter) { - this._documentsDB.then( - db => rtop(db.transaction(['documents'], 'readwrite').objectStore('documents').put({ roomName, remoteUpdateCounter })) - ) - } - - _createYInstance (roomName) { - const room = this._rooms.get(roomName) - if (room !== undefined) { - return room.y - } - const y = new Y() - return openDB(roomName).then( - db => rtop(db.transaction(['updates'], 'readonly').objectStore('updates').getAll()) - ).then( - updates => - y.transact(() => { - updates.forEach(update => { - decodePersisted(y, new BinaryDecoder(update)) - }) - }, true) - ).then(() => Promise.resolve(y)) - } - - _persistStructsDS (roomName, structsDS) { - const encoder = new BinaryEncoder() - encoder.writeVarUint(PERSIST_STRUCTS_DS) - encoder.writeArrayBuffer(structsDS) - return openDB(roomName).then(db => { - const t = db.transaction(['updates'], 'readwrite') - const updatesStore = t.objectStore('updates') - return rtop(updatesStore.put(encoder.createBuffer())) - }) - } - - _persistStructs (roomName, structs) { - const encoder = new BinaryEncoder() - encoder.writeVarUint(PERSIST_UPDATE) - encoder.writeArrayBuffer(structs) - return openDB(roomName).then(db => { - const t = db.transaction(['updates'], 'readwrite') - const updatesStore = t.objectStore('updates') - return rtop(updatesStore.put(encoder.createBuffer())) - }) - } - - connectY (roomName, y) { - if (this._rooms.has(roomName)) { - throw new Error('A Y instance is already bound to this room!') - } - let room = { - db: null, - dbPromise: null, - channel: null, - mutex: createMutex(), - y - } - if (typeof BroadcastChannel !== 'undefined') { - room.channel = new BroadcastChannel('__yjs__' + roomName) - room.channel.addEventListener('message', e => { - room.mutex(function () { - decodePersisted(y, new BinaryDecoder(e.data)) - }) - }) - } - y.on('destroyed', () => { - this.disconnectY(roomName, y) - }) - y.on('afterTransaction', (y, transaction) => { - room.mutex(() => { - if (transaction.encodedStructsLen > 0) { - const encoder = new BinaryEncoder() - const update = new BinaryEncoder() - encodeUpdate(y, transaction.encodedStructs, update) - const updateBuffer = update.createBuffer() - if (room.channel !== null) { - room.channel.postMessage(updateBuffer) - } - if (transaction.encodedStructsLen > 0) { - if (room.db !== null) { - saveUpdate(room, updateBuffer) - } - } - } - }) - }) - // register document in documentsDB - this._documentsDB.then( - db => - rtop(db.transaction(['documents'], 'readonly').objectStore('documents').get(roomName)) - .then( - doc => doc === undefined && rtop(db.transaction(['documents'], 'readwrite').objectStore('documents').add({ roomName, serverUpdateCounter: -1 })) - ) - ) - // open room db and read existing data - return room.dbPromise = openDB(roomName) - .then(db => { - room.db = db - const t = room.db.transaction(['updates'], 'readwrite') - const updatesStore = t.objectStore('updates') - // write current state as update - const encoder = new BinaryEncoder() - encodeStructsDS(y, encoder) - return rtop(updatesStore.put(encoder.createBuffer())).then(() => { - // read persisted state - return rtop(updatesStore.getAll()).then(updates => { - room.mutex(() => { - y.transact(() => { - updates.forEach(update => { - decodePersisted(y, new BinaryDecoder(update)) - }) - }, true) - }) - }) - }) - }) - } - disconnectY (roomName) { - const { - db, channel - } = this._rooms.get(roomName) - db.close() - if (channel !== null) { - channel.close() - } - this._rooms.delete(roomName) - } - - /** - * Remove all persisted data that belongs to a room. - * Automatically destroys all Yjs all Yjs instances that persist to - * the room. If `destroyYjsInstances = false` the persistence functionality - * will be removed from the Yjs instances. - * - removePersistedData (roomName, destroyYjsInstances = true) { - this.disconnectY(roomName) - return rtop(indexedDB.deleteDatabase(roomName)) - } - } - { - if (room.db !== null) { - saveUpdate(room, updateBuffer) - } - } - } - }) - }) - // register document in documentsDB - this._documentsDB.then( - db => - rtop(db.transaction(['documents'], 'readonly').objectStore('documents').get(roomName)) - .then( - doc => doc === undefined && rtop(db.transaction(['documents'], 'readwrite').objectStore('documents').add({ roomName, serverUpdateCounter: -1 })) - ) - ) - // open room db and read existing data - return room.dbPromise = openDB(roomName) - .then(db => { - room.db = db - const t = room.db.transaction(['updates'], 'readwrite') - const updatesStore = t.objectStore('updates') - // write current state as update - const encoder = new BinaryEncoder() - encodeStructsDS(y, encoder) - return rtop(updatesStore.put(encoder.createBuffer())).then(() => { - // read persisted state - return rtop(updatesStore.getAll()).then(updates => { - room.mutex(() => { - y.transact(() => { - updates.forEach(update => { - decodePersisted(y, new BinaryDecoder(update)) - }) - }, true) - }) - }) - }) - }) - } - disconnectY (roomName) { - const { - db, channel - } = this._rooms.get(roomName) - db.close() - if (channel !== null) { - channel.close() - } - this._rooms.delete(roomName) - } - - /** - * Remove all persisted data that belongs to a room. - * Automatically destroys all Yjs all Yjs instances that persist to - * the room. If `destroyYjsInstances = false` the persistence functionality - * will be removed from the Yjs instances. - * - removePersistedData (roomName, destroyYjsInstances = true) { - this.disconnectY(roomName) - return rtop(indexedDB.deleteDatabase(roomName)) - } -} -*/ diff --git a/persistence/decodePersisted.js b/persistence/decodePersisted.js deleted file mode 100644 index 20d92320..00000000 --- a/persistence/decodePersisted.js +++ /dev/null @@ -1,53 +0,0 @@ -/* -import { integrateRemoteStructs } from '../MessageHandler/integrateRemoteStructs.js' -import { writeStructs } from '../MessageHandler/syncStep1.js' -import { writeDeleteSet, readDeleteSet } from '../MessageHandler/deleteSet.js' - -export const PERSIST_UPDATE = 0 -/** - * Write an update to an encoder. - * - * @param {Y} y A Yjs instance - * @param {Encoder} updateEncoder I.e. transaction.encodedStructs - * -export const encodeUpdate = (y, updateEncoder, encoder) => { - encoder.writeVarUint(PERSIST_UPDATE) - encoder.writeBinaryEncoder(updateEncoder) -} - -export const PERSIST_STRUCTS_DS = 1 - -/** - * Write the current Yjs data model to an encoder. - * - * @param {Y} y A Yjs instance - * @param {Encoder} encoder An encoder to write to - * -export const encodeStructsDS = (y, encoder) => { - encoder.writeVarUint(PERSIST_STRUCTS_DS) - writeStructs(y, encoder, new Map()) - writeDeleteSet(y, encoder) -} - -/** - * Feed the Yjs instance with the persisted state - * @param {Y} y A Yjs instance. - * @param {Decoder} decoder A Decoder instance that holds the file content. - * -export const decodePersisted = (y, decoder) => { - y.transact(() => { - while (decoder.hasContent()) { - const contentType = decoder.readVarUint() - switch (contentType) { - case PERSIST_UPDATE: - integrateRemoteStructs(decoder, y) - break - case PERSIST_STRUCTS_DS: - integrateRemoteStructs(decoder, y) - readDeleteSet(y, decoder) - break - } - } - }, true) -} -*/ diff --git a/persistence/indexeddb.js b/persistence/indexeddb.js deleted file mode 100644 index e69de29b..00000000 diff --git a/persistence/leveldb.js b/persistence/leveldb.js deleted file mode 100644 index 5095922b..00000000 --- a/persistence/leveldb.js +++ /dev/null @@ -1,125 +0,0 @@ -/** - * @module persistence/leveldb - * This module re-uses the encoding of syncProtocol to store and read updates from leveldb. - */ - -const level = require('level') -const Y = require('../build/yjs.js') -const mux = Y.createMutex() - -/* - * Improves the uniqueness of timestamps. - * We gamble with the fact that users won't create more than 10000 changes on a single document - * within one millisecond (also assuming clock works correctly). - */ -let timestampIterator = 0 -/** - * @return {string} A random, time-based string starting with "${roomName}:" - */ -const getNextTimestamp = () => { - timestampIterator = (timestampIterator + 1) % 10000 - return `${Date.now()}${timestampIterator.toString().padStart(4, '0')}` -} - -/** - * @param {string} docName - * @return {string} - */ -const generateEntryKey = docName => `${docName}#${getNextTimestamp()}` - -/** - * - * @param {any} db - * @param {string} docName - * @param {Uint8Array | ArrayBuffer} buf - */ -const writeEntry = (db, docName, buf) => db.put(generateEntryKey(docName), buf) - -/** - * @param {Uint8Array} arr - * @param {Y.Y} ydocument - */ -const readEntry = (arr, ydocument) => mux(() => - Y.syncProtocol.readSyncMessage(Y.decoding.createDecoder(arr), Y.encoding.createEncoder(), ydocument) -) - -/** - * @param {any} db - * @param {string} docName - * @param {Y.Y} ydocument - */ -const loadFromPersistence = (db, docName, ydocument) => new Promise((resolve, reject) => - db.createReadStream({ - gte: `${docName}#`, - lte: `${docName}#Z`, - keys: false, - values: true - }) - .on('data', data => readEntry(data, ydocument)) - .on('error', reject) - .on('end', resolve) - .on('close', resolve) -) - -const persistState = (db, docName, ydocument) => { - const encoder = Y.encoding.createEncoder() - Y.syncProtocol.writeSyncStep2(encoder, ydocument, new Map()) - const entryKey = generateEntryKey(docName) - const entryPromise = db.put(entryKey, Y.encoding.toBuffer(encoder)) - const delOps = [] - return new Promise((resolve, reject) => db.createKeyStream({ - gte: `${docName}#`, - lt: entryKey - }) - .on('data', key => delOps.push({ type: 'del', key })) - .on('error', reject) - .on('end', resolve) - .on('close', resolve) - ).then(() => entryPromise).then(() => db.batch(delOps)) -} - -/** - * Persistence layer for Leveldb. - */ -exports.LevelDbPersistence = class LevelDbPersistence { - /** - * @param {string} fpath Path to leveldb database - */ - constructor (fpath) { - this.db = level(fpath, { valueEncoding: 'binary' }) - } - /** - * Retrieve all data from LevelDB and automatically persist all document updates to leveldb. - * - * @param {string} docName - * @param {Y.Y} ydocument - */ - bindState (docName, ydocument) { - // write all updates received from other clients - // - unless it is created by this persistence layer (e.g. loadFromPersistence, we we mux). - ydocument.on('afterTransaction', (y, transaction) => { - if (transaction.encodedStructsLen > 0) { - mux(() => { - const encoder = Y.encoding.createEncoder() - Y.syncProtocol.writeUpdate(encoder, transaction.encodedStructsLen, transaction.encodedStructs) - writeEntry(this.db, docName, Y.encoding.toBuffer(encoder)) - }) - } - }) - // read all data from persistence - return loadFromPersistence(this.db, docName, ydocument).then(() => - // write current state (just in case anything was added before state was bound) - this.writeState(docName, ydocument) - ) - } - /** - * Write current state to persistence layer. Deletes all entries that were made before. - * Call this method at any time - the recommended time to call this method is before the ydocument is destroyed. - * - * @param {string} docName - * @param {Y.Y} ydocument - */ - writeState (docName, ydocument) { - return persistState(this.db, docName, ydocument) - } -} diff --git a/protocols/auth.js b/protocols/auth.js deleted file mode 100644 index 73b68e3c..00000000 --- a/protocols/auth.js +++ /dev/null @@ -1,33 +0,0 @@ - -import * as encoding from '../lib/encoding.js' -import * as decoding from '../lib/decoding.js' -import { Y } from '../utils/Y.js' // eslint-disable-line - -export const messagePermissionDenied = 0 - -/** - * @param {encoding.Encoder} encoder - * @param {string} reason - */ -export const writePermissionDenied = (encoder, reason) => { - encoding.writeVarUint(encoder, messagePermissionDenied) - encoding.writeVarString(encoder, reason) -} - -/** - * @callback PermissionDeniedHandler - * @param {any} y - * @param {string} reason - */ - -/** - * - * @param {decoding.Decoder} decoder - * @param {Y} y - * @param {PermissionDeniedHandler} permissionDeniedHandler - */ -export const readAuthMessage = (decoder, y, permissionDeniedHandler) => { - switch (decoding.readVarUint(decoder)) { - case messagePermissionDenied: permissionDeniedHandler(y, decoding.readVarString(decoder)) - } -} diff --git a/protocols/awareness.js b/protocols/awareness.js deleted file mode 100644 index 9e935f56..00000000 --- a/protocols/awareness.js +++ /dev/null @@ -1,121 +0,0 @@ -/** - * @module awareness-protocol - */ - -import * as encoding from '../lib/encoding.js' -import * as decoding from '../lib/decoding.js' -import { Y } from '../utils/Y.js' // eslint-disable-line - -const messageUsersStateChanged = 0 - -/** - * @typedef {Object} UserStateUpdate - * @property {number} UserStateUpdate.userID - * @property {number} UserStateUpdate.clock - * @property {Object} UserStateUpdate.state - */ - -/** - * @param {encoding.Encoder} encoder - * @param {Array} stateUpdates - */ -export const writeUsersStateChange = (encoder, stateUpdates) => { - const len = stateUpdates.length - encoding.writeVarUint(encoder, messageUsersStateChanged) - encoding.writeVarUint(encoder, len) - for (let i = 0; i < len; i++) { - const {userID, state, clock} = stateUpdates[i] - encoding.writeVarUint(encoder, userID) - encoding.writeVarUint(encoder, clock) - encoding.writeVarString(encoder, JSON.stringify(state)) - } -} - -export const readUsersStateChange = (decoder, y) => { - const added = [] - const updated = [] - const removed = [] - const len = decoding.readVarUint(decoder) - for (let i = 0; i < len; i++) { - const userID = decoding.readVarUint(decoder) - const clock = decoding.readVarUint(decoder) - const state = JSON.parse(decoding.readVarString(decoder)) - const uClock = y.awarenessClock.get(userID) || 0 - y.awarenessClock.set(userID, clock) - if (state === null) { - // only write if clock increases. cannot overwrite - if (y.awareness.has(userID) && uClock < clock) { - y.awareness.delete(userID) - removed.push(userID) - } - } else if (uClock <= clock) { // allow to overwrite (e.g. when client was on, then offline) - if (y.awareness.has(userID)) { - updated.push(userID) - } else { - added.push(userID) - } - y.awareness.set(userID, state) - y.awarenessClock.set(userID, clock) - } - } - if (added.length > 0 || updated.length > 0 || removed.length > 0) { - y.emit('awareness', { - added, updated, removed - }) - } -} - -/** - * @param {decoding.Decoder} decoder - * @param {encoding.Encoder} encoder - * @return {Array} - */ -export const forwardUsersStateChange = (decoder, encoder) => { - const len = decoding.readVarUint(decoder) - const updates = [] - encoding.writeVarUint(encoder, messageUsersStateChanged) - encoding.writeVarUint(encoder, len) - for (let i = 0; i < len; i++) { - const userID = decoding.readVarUint(decoder) - const clock = decoding.readVarUint(decoder) - const state = decoding.readVarString(decoder) - encoding.writeVarUint(encoder, userID) - encoding.writeVarUint(encoder, clock) - encoding.writeVarString(encoder, state) - updates.push({userID, state: JSON.parse(state), clock}) - } - return updates -} - -/** - * @param {decoding.Decoder} decoder - * @param {Y} y - */ -export const readAwarenessMessage = (decoder, y) => { - switch (decoding.readVarUint(decoder)) { - case messageUsersStateChanged: - readUsersStateChange(decoder, y) - break - } -} - -/** - * @typedef {Object} UserState - * @property {number} UserState.userID - * @property {any} UserState.state - * @property {number} UserState.clock - */ - -/** - * @param {decoding.Decoder} decoder - * @param {encoding.Encoder} encoder - * @return {Array} Array of state updates - */ -export const forwardAwarenessMessage = (decoder, encoder) => { - let s = [] - switch (decoding.readVarUint(decoder)) { - case messageUsersStateChanged: - s = forwardUsersStateChange(decoder, encoder) - } - return s -} diff --git a/protocols/history.js b/protocols/history.js deleted file mode 100644 index 8ee5c603..00000000 --- a/protocols/history.js +++ /dev/null @@ -1,46 +0,0 @@ - -import * as encoding from '../lib/encoding.js' -import * as decoding from '../lib/decoding.js' -import { Y } from '../utils/Y.js' // eslint-disable-line -import { writeDeleteStore, readFreshDeleteStore, DeleteStore } from '../utils/DeleteStore.js' // eslint-disable-line -import { writeStateMap, readStateMap } from '../utils/StateStore.js' - -/** - * @typedef {Object} HistorySnapshot - * @property {DeleteStore} HistorySnapshot.ds - * @property {Map} HistorySnapshot.sm - * @property {Map} HistorySnapshot.userMap - */ - -/** - * @param {encoding.Encoder} encoder - * @param {Y} y - * @param {Map} userMap - */ -export const writeHistorySnapshot = (encoder, y, userMap) => { - writeDeleteStore(encoder, y.ds) - writeStateMap(encoder, y.ss.state) - encoding.writeVarUint(encoder, userMap.size) - userMap.forEach((accountname, userid) => { - encoding.writeVarUint(encoder, userid) - encoding.writeVarString(encoder, accountname) - }) -} - -/** - * - * @param {decoding.Decoder} decoder - * @return {HistorySnapshot} - */ -export const readHistorySnapshot = decoder => { - const ds = readFreshDeleteStore(decoder) - const sm = readStateMap(decoder) - const size = decoding.readVarUint(decoder) - const userMap = new Map() - for (let i = 0; i < size; i++) { - const userid = decoding.readVarUint(decoder) - const accountname = decoding.readVarString(decoder) - userMap.set(userid, accountname) - } - return { ds, sm, userMap } -} diff --git a/protocols/sync.js b/protocols/sync.js deleted file mode 100644 index 5600555e..00000000 --- a/protocols/sync.js +++ /dev/null @@ -1,264 +0,0 @@ -/** - * @module sync-protocol - */ - -import * as encoding from '../lib/encoding.js' -import * as decoding from '../lib/decoding.js' -import * as ID from '../utils/ID.js' -import { getStruct } from '../utils/structReferences.js' -import { deleteItemRange } from '../utils/structManipulation.js' -import { integrateRemoteStruct } from '../utils/integrateRemoteStructs.js' -import { Y } from '../utils/Y.js' // eslint-disable-line -import * as stringify from '../utils/structStringify.js' -import { readStateMap, writeStateMap } from '../utils/StateStore.js' -import { writeDeleteStore, readDeleteStore, stringifyDeleteStore } from '../utils/DeleteStore.js' - -/** - * @typedef {Map} StateMap - */ - -/** - * Core Yjs only defines three message types: - * • YjsSyncStep1: Includes the State Set of the sending client. When received, the client should reply with YjsSyncStep2. - * • YjsSyncStep2: Includes all missing structs and the complete delete set. When received, the the client is assured that - * it received all information from the remote client. - * - * In a peer-to-peer network, you may want to introduce a SyncDone message type. Both parties should initiate the connection - * with SyncStep1. When a client received SyncStep2, it should reply with SyncDone. When the local client received both - * SyncStep2 and SyncDone, it is assured that it is synced to the remote client. - * - * In a client-server model, you want to handle this differently: The client should initiate the connection with SyncStep1. - * When the server receives SyncStep1, it should reply with SyncStep2 immediately followed by SyncStep1. The client replies - * with SyncStep2 when it receives SyncStep1. Optionally the server may send a SyncDone after it received SyncStep2, so the - * client knows that the sync is finished. There are two reasons for this more elaborated sync model: 1. This protocol can - * easily be implemented on top of http and websockets. 2. The server shoul only reply to requests, and not initiate them. - * Therefore it is necesarry that the client initiates the sync. - * - * Construction of a message: - * [messageType : varUint, message definition..] - * - * Note: A message does not include information about the room name. This must to be handled by the upper layer protocol! - * - * stringify[messageType] stringifies a message definition (messageType is already read from the bufffer) - */ - -export const messageYjsSyncStep1 = 0 -export const messageYjsSyncStep2 = 1 -export const messageYjsUpdate = 2 - -/** - * @param {decoding.Decoder} decoder - * @param {Y} y - * @return {string} - */ -export const stringifyStructs = (decoder, y) => { - let str = '' - const len = decoding.readUint32(decoder) - for (let i = 0; i < len; i++) { - let reference = decoding.readVarUint(decoder) - let Constr = getStruct(reference) - let struct = new Constr() - let missing = struct._fromBinary(y, decoder) - let logMessage = ' ' + struct._logString() - if (missing.length > 0) { - logMessage += ' .. missing: ' + missing.map(stringify.stringifyItemID).join(', ') - } - str += logMessage + '\n' - } - return str -} - -/** - * Write all Items that are not not included in ss to - * the encoder object. - * - * @param {encoding.Encoder} encoder - * @param {Y} y - * @param {StateMap} ss State Set received from a remote client. Maps from client id to number of created operations by client id. - */ -export const writeStructs = (encoder, y, ss) => { - const lenPos = encoding.length(encoder) - encoding.writeUint32(encoder, 0) - let len = 0 - for (let user of y.ss.state.keys()) { - let clock = ss.get(user) || 0 - if (user !== ID.RootFakeUserID) { - const minBound = ID.createID(user, clock) - const overlappingLeft = y.os.findPrev(minBound) - const rightID = overlappingLeft === null ? null : overlappingLeft._id - if (rightID !== null && rightID.user === user && rightID.clock + overlappingLeft._length > clock) { - // TODO: only write partial content (only missing content) - // const struct = overlappingLeft._clonePartial(clock - rightID.clock) - const struct = overlappingLeft - struct._toBinary(encoder) - len++ - } - y.os.iterate(minBound, ID.createID(user, Number.MAX_VALUE), struct => { - struct._toBinary(encoder) - len++ - }) - } - } - encoding.setUint32(encoder, lenPos, len) -} - -/** - * Read structs and delete operations from decoder and apply them on a shared document. - * - * @param {decoding.Decoder} decoder - * @param {Y} y - */ -export const readStructs = (decoder, y) => { - const len = decoding.readUint32(decoder) - for (let i = 0; i < len; i++) { - integrateRemoteStruct(decoder, y) - } -} - -/** - * Read SyncStep1 and return it as a readable string. - * - * @param {decoding.Decoder} decoder - * @return {string} - */ -export const stringifySyncStep1 = (decoder) => { - let s = 'SyncStep1: ' - const len = decoding.readUint32(decoder) - for (let i = 0; i < len; i++) { - const user = decoding.readVarUint(decoder) - const clock = decoding.readVarUint(decoder) - s += `(${user}:${clock})` - } - return s -} - -/** - * Create a sync step 1 message based on the state of the current shared document. - * - * @param {encoding.Encoder} encoder - * @param {Y} y - */ -export const writeSyncStep1 = (encoder, y) => { - encoding.writeVarUint(encoder, messageYjsSyncStep1) - writeStateMap(encoder, y.ss.state) -} - -/** - * @param {encoding.Encoder} encoder - * @param {Y} y - * @param {Map} ss - */ -export const writeSyncStep2 = (encoder, y, ss) => { - encoding.writeVarUint(encoder, messageYjsSyncStep2) - writeStructs(encoder, y, ss) - writeDeleteStore(encoder, y.ds) -} - -/** - * Read SyncStep1 message and reply with SyncStep2. - * - * @param {decoding.Decoder} decoder The reply to the received message - * @param {encoding.Encoder} encoder The received message - * @param {Y} y - */ -export const readSyncStep1 = (decoder, encoder, y) => - writeSyncStep2(encoder, y, readStateMap(decoder)) - -/** - * @param {decoding.Decoder} decoder - * @param {Y} y - * @return {string} - */ -export const stringifySyncStep2 = (decoder, y) => { - let str = ' == Sync step 2:\n' - str += ' + Structs:\n' - str += stringifyStructs(decoder, y) - // write DS to string - str += ' + Delete Set:\n' - str += stringifyDeleteStore(decoder) - return str -} - -/** - * Read and apply Structs and then DeleteStore to a y instance. - * - * @param {decoding.Decoder} decoder - * @param {Y} y - */ -export const readSyncStep2 = (decoder, y) => { - readStructs(decoder, y) - readDeleteStore(decoder, y) -} - -/** - * @param {decoding.Decoder} decoder - * @param {Y} y - * @return {string} - */ -export const stringifyUpdate = (decoder, y) => - ' == Update:\n' + stringifyStructs(decoder, y) - -/** - * @param {encoding.Encoder} encoder - * @param {number} numOfStructs - * @param {encoding.Encoder} updates - */ -export const writeUpdate = (encoder, numOfStructs, updates) => { - encoding.writeVarUint(encoder, messageYjsUpdate) - encoding.writeUint32(encoder, numOfStructs) - encoding.writeBinaryEncoder(encoder, updates) -} - -export const readUpdate = readStructs - -/** - * @param {decoding.Decoder} decoder - * @param {Y} y - * @return {string} The message converted to string - */ -export const stringifySyncMessage = (decoder, y) => { - const messageType = decoding.readVarUint(decoder) - let stringifiedMessage - let stringifiedMessageType - switch (messageType) { - case messageYjsSyncStep1: - stringifiedMessageType = 'YjsSyncStep1' - stringifiedMessage = stringifySyncStep1(decoder) - break - case messageYjsSyncStep2: - stringifiedMessageType = 'YjsSyncStep2' - stringifiedMessage = stringifySyncStep2(decoder, y) - break - case messageYjsUpdate: - stringifiedMessageType = 'YjsUpdate' - stringifiedMessage = stringifyStructs(decoder, y) - break - default: - stringifiedMessageType = 'Unknown' - stringifiedMessage = 'Unknown' - } - return `Message ${stringifiedMessageType}:\n${stringifiedMessage}` -} - -/** - * @param {decoding.Decoder} decoder A message received from another client - * @param {encoding.Encoder} encoder The reply message. Will not be sent if empty. - * @param {Y} y - */ -export const readSyncMessage = (decoder, encoder, y) => { - const messageType = decoding.readVarUint(decoder) - switch (messageType) { - case messageYjsSyncStep1: - readSyncStep1(decoder, encoder, y) - break - case messageYjsSyncStep2: - y.transact(() => readSyncStep2(decoder, y), true) - break - case messageYjsUpdate: - y.transact(() => readUpdate(decoder, y), true) - break - default: - throw new Error('Unknown message type') - } - return messageType -} diff --git a/provider/websocket.js b/provider/websocket.js deleted file mode 100644 index 03a23052..00000000 --- a/provider/websocket.js +++ /dev/null @@ -1,5 +0,0 @@ -/** - * @module provider/websocket - */ - -export * from './websocket/WebSocketProvider.js' diff --git a/provider/websocket/WebSocketProvider.js b/provider/websocket/WebSocketProvider.js deleted file mode 100644 index 30ed5d3b..00000000 --- a/provider/websocket/WebSocketProvider.js +++ /dev/null @@ -1,192 +0,0 @@ -/* -Unlike stated in the LICENSE file, it is not necessary to include the copyright notice and permission notice when you copy code from this file. -*/ - -/** - * @module provider/websocket - */ - -/* eslint-env browser */ - -import * as Y from '../../index.js' -import * as bc from '../../lib/broadcastchannel.js' - -const messageSync = 0 -const messageAwareness = 1 -const messageAuth = 2 - -const reconnectTimeout = 3000 - -/** - * @param {WebsocketsSharedDocument} doc - * @param {string} reason - */ -const permissionDeniedHandler = (doc, reason) => console.warn(`Permission denied to access ${doc.url}.\n${reason}`) - -/** - * @param {WebsocketsSharedDocument} doc - * @param {ArrayBuffer} buf - * @return {Y.encoding.Encoder} - */ -const readMessage = (doc, buf) => { - const decoder = Y.decoding.createDecoder(buf) - const encoder = Y.encoding.createEncoder() - const messageType = Y.decoding.readVarUint(decoder) - switch (messageType) { - case messageSync: - Y.encoding.writeVarUint(encoder, messageSync) - doc.mux(() => - Y.syncProtocol.readSyncMessage(decoder, encoder, doc) - ) - break - case messageAwareness: - Y.awarenessProtocol.readAwarenessMessage(decoder, doc) - break - case messageAuth: - Y.authProtocol.readAuthMessage(decoder, doc, permissionDeniedHandler) - } - return encoder -} - -const setupWS = (doc, url) => { - const websocket = new WebSocket(url) - websocket.binaryType = 'arraybuffer' - doc.ws = websocket - websocket.onmessage = event => { - const encoder = readMessage(doc, event.data) - if (Y.encoding.length(encoder) > 1) { - websocket.send(Y.encoding.toBuffer(encoder)) - } - } - websocket.onclose = () => { - doc.ws = null - doc.wsconnected = false - // update awareness (all users left) - const removed = [] - doc.getAwarenessInfo().forEach((_, userid) => { - removed.push(userid) - }) - doc.awareness = new Map() - doc.emit('awareness', { - added: [], updated: [], removed - }) - doc.emit('status', { - status: 'disconnected' - }) - setTimeout(setupWS, reconnectTimeout, doc, url) - } - websocket.onopen = () => { - doc.wsconnected = true - doc.emit('status', { - status: 'connected' - }) - // always send sync step 1 when connected - const encoder = Y.encoding.createEncoder() - Y.encoding.writeVarUint(encoder, messageSync) - Y.syncProtocol.writeSyncStep1(encoder, doc) - websocket.send(Y.encoding.toBuffer(encoder)) - // force send stored awareness info - doc.setAwarenessField(null, null) - } -} - -const broadcastUpdate = (y, transaction) => { - if (transaction.encodedStructsLen > 0) { - y.mux(() => { - const encoder = Y.encoding.createEncoder() - Y.encoding.writeVarUint(encoder, messageSync) - Y.syncProtocol.writeUpdate(encoder, transaction.encodedStructsLen, transaction.encodedStructs) - const buf = Y.encoding.toBuffer(encoder) - if (y.wsconnected) { - y.ws.send(buf) - } - bc.publish(y.url, buf) - }) - } -} - -class WebsocketsSharedDocument extends Y.Y { - constructor (url, opts) { - super(opts) - this.url = url - this.wsconnected = false - this.mux = Y.createMutex() - this.ws = null - this._localAwarenessState = {} - this.awareness = new Map() - this.awarenessClock = new Map() - setupWS(this, url) - this.on('afterTransaction', broadcastUpdate) - this._bcSubscriber = data => { - const encoder = readMessage(this, data) // already muxed - this.mux(() => { - if (Y.encoding.length(encoder) > 1) { - bc.publish(url, Y.encoding.toBuffer(encoder)) - } - }) - } - bc.subscribe(url, this._bcSubscriber) - // send sync step1 to bc - this.mux(() => { - const encoder = Y.encoding.createEncoder() - Y.encoding.writeVarUint(encoder, messageSync) - Y.syncProtocol.writeSyncStep1(encoder, this) - bc.publish(url, Y.encoding.toBuffer(encoder)) - }) - } - getLocalAwarenessInfo () { - return this._localAwarenessState - } - getAwarenessInfo () { - return this.awareness - } - setAwarenessField (field, value) { - if (field !== null) { - this._localAwarenessState[field] = value - } - if (this.wsconnected) { - const clock = (this.awarenessClock.get(this.userID) || 0) + 1 - this.awarenessClock.set(this.userID, clock) - const encoder = Y.encoding.createEncoder() - Y.encoding.writeVarUint(encoder, messageAwareness) - Y.awarenessProtocol.writeUsersStateChange(encoder, [{ userID: this.userID, state: this._localAwarenessState, clock }]) - const buf = Y.encoding.toBuffer(encoder) - this.ws.send(buf) - } - } -} - -/** - * Websocket Provider for Yjs. Creates a single websocket connection to each document. - * The document name is attached to the provided url. I.e. the following example - * creates a websocket connection to http://localhost:1234/my-document-name - * - * @example - * import { WebsocketProvider } from 'yjs/provider/websocket/client.js' - * const provider = new WebsocketProvider('http://localhost:1234') - * const ydocument = provider.get('my-document-name') - */ -export class WebsocketProvider { - constructor (url) { - // ensure that url is always ends with / - while (url[url.length - 1] === '/') { - url = url.slice(0, url.length - 1) - } - this.url = url + '/' - /** - * @type {Map} - */ - this.docs = new Map() - } - /** - * @param {string} name - * @return {WebsocketsSharedDocument} - */ - get (name, opts) { - let doc = this.docs.get(name) - if (doc === undefined) { - doc = new WebsocketsSharedDocument(this.url + name, opts) - } - return doc - } -} diff --git a/provider/websocket/index.js b/provider/websocket/index.js deleted file mode 100644 index b6c6497c..00000000 --- a/provider/websocket/index.js +++ /dev/null @@ -1,2 +0,0 @@ - -export * from './WebSocketProvider.js' diff --git a/provider/websocket/server.js b/provider/websocket/server.js deleted file mode 100644 index 9c422a5f..00000000 --- a/provider/websocket/server.js +++ /dev/null @@ -1,155 +0,0 @@ -/* -Unlike stated in the LICENSE file, it is not necessary to include the copyright notice and permission notice when you copy code from this file. -*/ - -/** - * @module provider/websocket/server - */ - -const Y = require('../../build/yjs.js') -const WebSocket = require('ws') -const http = require('http') - -const port = process.env.PORT || 1234 - -// disable gc when using snapshots! -const gcEnabled = process.env.GC !== 'false' && process.env.GC !== '0' -const persistenceDir = process.env.YPERSISTENCE -let persistence = null -if (typeof persistenceDir === 'string') { - const LevelDbPersistence = require('../../persistence/leveldb.js').LevelDbPersistence - persistence = new LevelDbPersistence(persistenceDir) -} - -const server = http.createServer((req, res) => { - res.writeHead(200, { 'Content-Type': 'text/plain' }) - res.end('okay') -}) - -const wss = new WebSocket.Server({ noServer: true }) - -const docs = new Map() - -const messageSync = 0 -const messageAwareness = 1 -const messageAuth = 2 - -const afterTransaction = (doc, transaction) => { - if (transaction.encodedStructsLen > 0) { - const encoder = Y.encoding.createEncoder() - Y.encoding.writeVarUint(encoder, messageSync) - Y.syncProtocol.writeUpdate(encoder, transaction.encodedStructsLen, transaction.encodedStructs) - const message = Y.encoding.toBuffer(encoder) - doc.conns.forEach((_, conn) => conn.send(message)) - } -} - -class WSSharedDoc extends Y.Y { - constructor () { - super({ gc: gcEnabled }) - this.mux = Y.createMutex() - /** - * Maps from conn to set of controlled user ids. Delete all user ids from awareness when this conn is closed - * @type {Map>} - */ - this.conns = new Map() - this.awareness = new Map() - this.awarenessClock = new Map() - this.on('afterTransaction', afterTransaction) - } -} - -const messageListener = (conn, doc, message) => { - const encoder = Y.encoding.createEncoder() - const decoder = Y.decoding.createDecoder(message) - const messageType = Y.decoding.readVarUint(decoder) - switch (messageType) { - case messageSync: - Y.encoding.writeVarUint(encoder, messageSync) - Y.syncProtocol.readSyncMessage(decoder, encoder, doc) - if (Y.encoding.length(encoder) > 1) { - conn.send(Y.encoding.toBuffer(encoder)) - } - break - case messageAwareness: { - Y.encoding.writeVarUint(encoder, messageAwareness) - const updates = Y.awarenessProtocol.forwardAwarenessMessage(decoder, encoder) - updates.forEach(update => { - doc.awareness.set(update.userID, update.state) - doc.awarenessClock.set(update.userID, update.clock) - doc.conns.get(conn).add(update.userID) - }) - const buff = Y.encoding.toBuffer(encoder) - doc.conns.forEach((_, c) => { - c.send(buff) - }) - break - } - } -} - -const setupConnection = (conn, req) => { - conn.binaryType = 'arraybuffer' - // get doc, create if it does not exist yet - const docName = req.url.slice(1) - let doc = docs.get(docName) - if (doc === undefined) { - doc = new WSSharedDoc() - if (persistence !== null) { - persistence.bindState(docName, doc) - } - docs.set(docName, doc) - } - doc.conns.set(conn, new Set()) - // listen and reply to events - conn.on('message', message => messageListener(conn, doc, message)) - conn.on('close', () => { - const controlledIds = doc.conns.get(conn) - doc.conns.delete(conn) - const encoder = Y.encoding.createEncoder() - Y.encoding.writeVarUint(encoder, messageAwareness) - Y.awarenessProtocol.writeUsersStateChange(encoder, Array.from(controlledIds).map(userID => { - const clock = (doc.awarenessClock.get(userID) || 0) + 1 - doc.awareness.delete(userID) - doc.awarenessClock.delete(userID) - return { userID, state: null, clock } - })) - const buf = Y.encoding.toBuffer(encoder) - doc.conns.forEach((_, conn) => conn.send(buf)) - if (doc.conns.size === 0 && persistence !== null) { - // if persisted, we store state and destroy ydocument - persistence.writeState(docName, doc).then(() => { - doc.destroy() - }) - docs.delete(docName) - } - }) - // send sync step 1 - const encoder = Y.encoding.createEncoder() - Y.encoding.writeVarUint(encoder, messageSync) - Y.syncProtocol.writeSyncStep1(encoder, doc) - conn.send(Y.encoding.toBuffer(encoder)) - if (doc.awareness.size > 0) { - const encoder = Y.encoding.createEncoder() - const userStates = [] - doc.awareness.forEach((state, userID) => { - userStates.push({ state, userID, clock: (doc.awarenessClock.get(userID) || 0) }) - }) - Y.encoding.writeVarUint(encoder, messageAwareness) - Y.awarenessProtocol.writeUsersStateChange(encoder, userStates) - conn.send(Y.encoding.toBuffer(encoder)) - } -} - -wss.on('connection', setupConnection) - -server.on('upgrade', (request, socket, head) => { - // You may check auth of request here.. - wss.handleUpgrade(request, socket, head, ws => { - wss.emit('connection', ws, request) - }) -}) - -server.listen(port) - -console.log('running on port', port) diff --git a/provider/ydb/NamedEventHandler.js b/provider/ydb/NamedEventHandler.js deleted file mode 100644 index 7474dfad..00000000 --- a/provider/ydb/NamedEventHandler.js +++ /dev/null @@ -1,23 +0,0 @@ -/** - * @module provider/ydb - */ - -import * as globals from './globals.js' - -export const Class = class NamedEventHandler { - constructor () { - this.l = globals.createMap() - } - on (eventname, f) { - const l = this.l - let h = l.get(eventname) - if (h === undefined) { - h = globals.createSet() - l.set(eventname, h) - } - h.add(f) - } -} - -export const fire = (handler, eventname, event) => - handler.l.get(eventname).forEach(f => f(event)) diff --git a/provider/ydb/README.md b/provider/ydb/README.md deleted file mode 100644 index 3e740cee..00000000 --- a/provider/ydb/README.md +++ /dev/null @@ -1 +0,0 @@ -* Host should discard message when confNumber is older than expected \ No newline at end of file diff --git a/provider/ydb/TODO.md b/provider/ydb/TODO.md deleted file mode 100644 index 4f71aaba..00000000 --- a/provider/ydb/TODO.md +++ /dev/null @@ -1,56 +0,0 @@ -Implement default dom filter.. - -But requires more explicit filtering of src attributes - -e.g. src="java\nscript:alert(0)" - -function domFilter (nodeName, attributes) { - // Filter all attributes that start with on*. E.g. onclick does execute code - // If key is 'href' or 'src', filter everything but 'http*', 'blob*', or 'data:image*' urls - attributes.forEach(function (value, key) { - key = key.toLowerCase(); - value = value.toLowerCase(); - if (key != null && ( - // filter all attributes starting with 'on' - key.substr(0, 2) === 'on' || - // if key is 'href' or 'src', filter everything but http, blob, or data:image - ( - (key === 'href' || key === 'src') && - value.substr(0, 4) !== 'http' && - value.substr(0, 4) !== 'blob' && - value.substr(0, 10) !== 'data:image' - ) - )) { - attributes.delete(key); - } - }); - switch (nodeName) { - case 'SCRIPT': - return null; - case 'EN-ADORNMENTS': - // TODO: Remove EN-ADORNMENTS check when merged into master branch! - return null; - case 'EN-TABLE': - attributes.delete('class'); - return attributes; - case 'EN-COMMENT': - attributes.delete('style'); - attributes.delete('class'); - return attributes; - case 'SPAN': - return (attributes.get('id') || '').substr(0, 5) === 'goog_' ? null : attributes; - case 'TD': - attributes.delete('class'); - return attributes; - case 'EMBED': - attributes.delete('src'); - attributes.delete('style'); - attributes.delete('data-reference'); - return attributes; - case 'FORM': - attributes.delete('action'); - return attributes; - default: - return (nodeName || '').substr(0, 3) === 'UI-' ? null : attributes; - } - } diff --git a/provider/ydb/YdbClient.js b/provider/ydb/YdbClient.js deleted file mode 100644 index 39da5cf6..00000000 --- a/provider/ydb/YdbClient.js +++ /dev/null @@ -1,218 +0,0 @@ -/** - * @module provider/ydb - */ - -/* eslint-env browser */ -import * as idbactions from './idbactions.js' -import * as globals from '../../lib/globals.js' -import * as message from './message.js' -import * as bc from './broadcastchannel.js' -import * as encoding from '../../lib/encoding.js' -import * as logging from '../../lib/logging.js' -import * as idb from '../../lib/idb.js' -import * as decoding from '../../lib/decoding.js' -import { Y } from '../../utils/Y.js' -import { integrateRemoteStruct } from '../MessageHandler/integrateRemoteStructs.js' -import { createMutualExclude } from '../../lib/mutualExclude.js' - -import * as NamedEventHandler from './NamedEventHandler.js' - -/** - * @typedef RoomState - * @type {Object} - * @property {number} rsid room session id, -1 if unknown (created locally) - * @property {number} offset By server, -1 if unknown - * @property {number} cOffset current offset by client - */ - -/** - * @typedef SyncState - * @type {Object} - * @property {boolean} upsynced True if all local updates have been sent to the server and the server confirmed that it received the update - * @property {boolean} downsynced True if the current session subscribed to the room, the server confirmed the subscription, and the initial data was received - * @property {boolean} persisted True if the server confirmed that it persisted all published data - */ - -/** - * - */ -export class YdbClient extends NamedEventHandler.Class { - constructor (url, db) { - super() - this.url = url - this.ws = new WebSocket(url) - this.rooms = globals.createMap() - this.db = db - this.connected = false - /** - * Set of room states. We try to keep it up in sync with idb, but this may fail due to concurrency with other windows. - * TODO: implement tests for this - * @type Map - */ - this.roomStates = globals.createMap() - /** - * Meta information about unconfirmed updates created by this client. - * Maps from confid to roomname - * @type Map - */ - this.clientUnconfirmedStates = globals.createMap() - bc.subscribeYdbEvents(this) - initWS(this, this.ws) - } - /** - * Open a Yjs instance that connects to `roomname`. - * @param {string} roomname - * @return {Y} - */ - getY (roomname) { - const y = new Y(roomname) - const mutex = createMutualExclude() - y.on('afterTransaction', (y, transaction) => mutex(() => { - if (transaction.encodedStructsLen > 0) { - update(this, roomname, transaction.encodedStructs.createBuffer()) - } - })) - subscribe(this, roomname, update => mutex(() => { - y.transact(() => { - const decoder = decoding.createDecoder(update) - while (decoding.hasContent(decoder)) { - integrateRemoteStruct(y, decoder) - } - }, true) - })) - return y - } - getRoomState (roomname) { - return bc.computeRoomState(this, bc.getUnconfirmedRooms(this), roomname) - } - getRoomStates () { - const unconfirmedRooms = bc.getUnconfirmedRooms(this) - const states = globals.createMap() - this.roomStates.forEach((rstate, roomname) => states.set(roomname, bc.computeRoomState(this, unconfirmedRooms, roomname))) - return states - } -} - -/** - * Initialize WebSocket connection. Try to reconnect on error/disconnect. - * @param {YdbClient} ydb - * @param {WebSocket} ws - */ -const initWS = (ydb, ws) => { - ws.binaryType = 'arraybuffer' - ws.onclose = () => { - ydb.connected = false - logging.log('Disconnected from ydb. Reconnecting..') - ydb.ws = new WebSocket(ydb.url) - initWS(ydb, ws) - } - ws.onopen = () => { - const t = idbactions.createTransaction(ydb.db) - globals.pall([idbactions.getRoomMetas(t), idbactions.getUnconfirmedSubscriptions(t), idbactions.getUnconfirmedUpdates(t)]).then(([metas, us, unconfirmedUpdates]) => { - let subs = [] - metas.forEach(meta => { - subs.push({ - room: meta.room, - offset: meta.offset, - rsid: meta.rsid - }) - }) - us.forEach(room => { - subs.push({ - room, offset: 0, rsid: 0 - }) - }) - subs = subs.filter(subdev => !ydb.roomStates.has(subdev.room)) // filter already subbed rooms - ydb.connected = true - const encoder = encoding.createEncoder() - if (subs.length > 0) { - encoding.writeArrayBuffer(encoder, message.createSub(subs)) - bc._broadcastYdbSyncingRoomsToServer(subs.map(subdev => subdev.room)) - } - encoding.writeArrayBuffer(encoder, unconfirmedUpdates) - send(ydb, encoding.toBuffer(encoder)) - }) - } - ws.onmessage = event => message.readMessage(ydb, event.data) -} - -// maps from dbNamespace to db -const dbPromises = new Map() - -/** - * Factory function. Get a ydb instance that connects to url, and uses dbNamespace as indexeddb namespace. - * Create if it does not exist yet. - * - * @param {string} url - * @param {string} dbNamespace - * @return {Promise} - */ -export const get = (url, dbNamespace = 'ydb') => { - if (!dbPromises.has(dbNamespace)) { - dbPromises.set(dbNamespace, idbactions.openDB(dbNamespace)) - } - return dbPromises.get(dbNamespace).then(db => globals.presolve(new YdbClient(url, db))) -} - -/** - * Remove a db namespace. Call this to remove any persisted data. Make sure to close active sessions. - * TODO: destroy active ydbClient sessions / throw if a session is still active - * @param {string} dbNamespace - * @return {Promise} - */ -export const clear = (dbNamespace = 'ydb') => idb.deleteDB(dbNamespace) - -/** - * @param {YdbClient} ydb - * @param {ArrayBuffer} m - */ -export const send = (ydb, m) => ydb.connected && m.byteLength !== 0 && ydb.ws.send(m) - -/** - * @param {YdbClient} ydb - * @param {string} room - * @param {ArrayBuffer} update - */ -export const update = (ydb, room, update) => { - bc.publishRoomData(room, update) - const t = idbactions.createTransaction(ydb.db) - logging.log(`Write Unconfirmed Update. room "${room}", ${JSON.stringify(update)}`) - return idbactions.writeClientUnconfirmed(t, room, update).then(clientConf => { - logging.log(`Send Unconfirmed Update. connected ${ydb.connected} room "${room}", clientConf ${clientConf}`) - bc._broadcastYdbCUConfCreated(clientConf, room) - send(ydb, message.createUpdate(room, update, clientConf)) - }) -} - -export const subscribe = (ydb, room, f) => { - bc.subscribeRoomData(room, f) - const t = idbactions.createTransaction(ydb.db) - if (!ydb.roomStates.has(room)) { - subscribeRooms(ydb, [room]) - } - idbactions.getRoomData(t, room).then(data => { - if (data.byteLength > 0) { - f(data) - } - }) -} - -export const subscribeRooms = (ydb, rooms) => { - const t = idbactions.createTransaction(ydb.db) - let subs = [] - // TODO: try not to do too many single calls. Implement getRoomMetas(t, rooms) or retrieve all metas once and store them on ydb - // TODO: find out performance of getRoomMetas with all metas - return globals.pall(rooms.map(room => idbactions.getRoomMeta(t, room).then(meta => { - if (meta === undefined) { - subs.push(room) - return idbactions.writeUnconfirmedSubscription(t, room) - } - }))).then(() => { - subs = subs.filter(room => !ydb.roomStates.has(room)) - // write all sub messages when all unconfirmed subs are writted to idb - if (subs.length > 0) { - send(ydb, message.createSub(subs.map(room => ({room, offset: 0, rsid: 0})))) - bc._broadcastYdbSyncingRoomsToServer(subs) - } - }) -} diff --git a/provider/ydb/YdbClient.test.js b/provider/ydb/YdbClient.test.js deleted file mode 100644 index 9468e811..00000000 --- a/provider/ydb/YdbClient.test.js +++ /dev/null @@ -1,89 +0,0 @@ -/** - * @module provider/ydb - */ - -/* eslint-env browser */ - -import * as test from './test.js' -import * as ydbClient from './YdbClient.js' -import * as globals from './globals.js' -import * as idbactions from './idbactions.js' -import * as logging from './logging.js' - -const wsUrl = 'ws://127.0.0.1:8899/ws' -const testRoom = 'testroom' - -class YdbTestClient { - constructor (ydb) { - this.ydb = ydb - this.createdUpdates = new Set() - this.data = [] - this.checked = new Set() - } -} - -const clearAllYdbContent = () => fetch('http://127.0.0.1:8899/clearAll', { mode: 'no-cors' }) - -/** - * @param {string} name - * @return {Promise} - */ -const getTestClient = async name => { - await ydbClient.clear('ydb-' + name) - const ydb = await ydbClient.get(wsUrl, 'ydb-' + name) - const testClient = new YdbTestClient(ydb) - ydbClient.subscribe(ydb, testRoom, data => { - testClient.data.push(data) - globals.createArrayFromArrayBuffer(data).forEach(d => { - if (d < nextUpdateNumber) { - testClient.checked.add(d) - } - }) - console.log(name, 'received data', data, testClient.data) - }) - return testClient -} - -// TODO: does only work for 8bit numbers.. -let nextUpdateNumber = 0 - -/** - * Create an update. We use an increasing message counter so each update is unique. - * @param {YdbTestClient} client - */ -const update = (client) => { - ydbClient.update(client.ydb, testRoom, globals.createArrayBufferFromArray([nextUpdateNumber++])) -} - -/** - * Check if tsclient has all data in dataset - * @param {...YdbTestClient} testClients - */ -const checkTestClients = (...testClients) => globals.until(100000, () => testClients.every(testClient => - testClient.checked.size === nextUpdateNumber -)).then(() => - globals.wait(150) // wait 150 for all conf messages to come in.. - // TODO: do the below check in the until handler -).then(() => globals.pall(testClients.map(testClient => idbactions.getRoomData(idbactions.createTransaction(testClient.ydb.db), testRoom)))).then(testClientsData => { - testClientsData.forEach((testClientData, i) => { - const checked = new Set() - globals.createArrayFromArrayBuffer(testClientData).forEach(d => { - if (checked.has(d)) { - logging.fail('duplicate content') - } - checked.add(d) - }) - if (checked.size !== nextUpdateNumber) { - logging.fail(`Not all data is available in idb in client ${i}`) - } - }) -}) - -clearAllYdbContent().then(() => { - test.run('ydb-client', async testname => { - const c1 = await getTestClient('1') - const c2 = await getTestClient('2') - update(c1) - await checkTestClients(c1, c2) - }) -}) diff --git a/provider/ydb/broadcastchannel.js b/provider/ydb/broadcastchannel.js deleted file mode 100644 index 38ec88b9..00000000 --- a/provider/ydb/broadcastchannel.js +++ /dev/null @@ -1,311 +0,0 @@ -/** - * @module provider/ydb - */ - -/* eslint-env browser */ - -import * as decoding from '../../lib/decoding.js' -import * as encoding from '../../lib/encoding.js' -import * as globals from '../../lib/globals.js' -import * as NamedEventHandler from './NamedEventHandler.js' - -const bc = new BroadcastChannel('ydb-client') - -/** - * @type {Map>} - */ -const datasubs = globals.createMap() -/** - * Set of Ydb instances - * @type {Set} - */ -const ydbinstances = globals.createSet() - -const bcRoomDataMessage = 0 -const bcYdbCUConfCreated = 1 -const bcYdbCUConfConfirmed = 2 -const bcYdbRemoteOffsetReceived = 3 -const bcYdbRemoteOffsetConfirmed = 4 -const bcYdbSyncingRoomsToServer = 5 -const bcYdbSyncFromServer = 6 - -export const getUnconfirmedRooms = ydb => { - const unconfirmedRooms = globals.createSet() - ydb.clientUnconfirmedStates.forEach(room => unconfirmedRooms.add(room)) - return unconfirmedRooms -} - -export const computeRoomState = (ydb, unconfirmedRooms, room) => { - // state is a RoomState, defined in YdbClient.js - const state = ydb.roomStates.get(room) - if (state === undefined) { - return { - upsynced: false, - downsynced: false, - persisted: false - } - } - return { - upsynced: !unconfirmedRooms.has(room), - downsynced: state.offset >= 0 && state.coffset >= state.offset, - persisted: state.coffset === state.offset && state.offset >= 0 && !unconfirmedRooms.has(room) - } -} - -let roomStatesUpdating = [] -const fireRoomStateUpdate = (ydb, room) => { - roomStatesUpdating.push(room) - if (roomStatesUpdating.length === 1) { - // first time this is called, trigger actual publisher - // setTimeout(() => { - const updated = new Map() - const unconfirmedRooms = getUnconfirmedRooms(ydb) - roomStatesUpdating.forEach(room => { - if (!updated.has(room)) { - updated.set(room, computeRoomState(ydb, unconfirmedRooms, room)) - } - }) - NamedEventHandler.fire(ydb, 'syncstate', { - updated - }) - roomStatesUpdating = [] - // }, 0) - } -} - -const receiveBCData = data => { - const decoder = decoding.createDecoder(data) - while (decoding.hasContent(decoder)) { - const messageType = decoding.readVarUint(decoder) - switch (messageType) { - case bcRoomDataMessage: { - const room = decoding.readVarString(decoder) - const update = decoding.readTail(decoder) - const rsubs = datasubs.get(room) - if (rsubs !== undefined) { - rsubs.forEach(f => f(update)) - } - break - } - case bcYdbCUConfCreated: { - const confid = decoding.readVarUint(decoder) - const room = decoding.readVarString(decoder) - ydbinstances.forEach(ydb => { - ydb.clientUnconfirmedStates.set(confid, room) - fireRoomStateUpdate(ydb, room) - }) - break - } - case bcYdbCUConfConfirmed: { - const confid = decoding.readVarUint(decoder) - const offset = decoding.readVarUint(decoder) - ydbinstances.forEach(ydb => { - const room = ydb.clientUnconfirmedStates.get(confid) - if (room !== undefined) { - ydb.clientUnconfirmedStates.delete(confid) - const state = ydb.roomStates.get(room) - if (state.coffset < offset) { - state.coffset = offset - } - fireRoomStateUpdate(ydb, room) - } - }) - break - } - case bcYdbRemoteOffsetReceived: { - const len = decoding.readVarUint(decoder) - for (let i = 0; i < len; i++) { - const room = decoding.readVarString(decoder) - const offset = decoding.readVarUint(decoder) - ydbinstances.forEach(ydb => { - // this is only called when an update is received - // so roomState.get(room) should exist - const state = ydb.roomStates.get(room) - if (state.coffset < offset) { - state.coffset = offset - } - fireRoomStateUpdate(ydb, room) - }) - } - break - } - case bcYdbRemoteOffsetConfirmed: { - const len = decoding.readVarUint(decoder) - for (let i = 0; i < len; i++) { - const room = decoding.readVarString(decoder) - const offset = decoding.readVarUint(decoder) - ydbinstances.forEach(ydb => { - const state = ydb.roomStates.get(room) - state.offset = offset - fireRoomStateUpdate(ydb, room) - }) - } - break - } - case bcYdbSyncingRoomsToServer: { - const len = decoding.readVarUint(decoder) - for (let i = 0; i < len; i++) { - const room = decoding.readVarString(decoder) - ydbinstances.forEach(ydb => { - const state = ydb.roomStates.get(room) - if (state === undefined) { - ydb.roomStates.set(room, { - rsid: -1, - offset: -1, - coffset: 0 - }) - fireRoomStateUpdate(ydb, room) - } - }) - } - break - } - case bcYdbSyncFromServer: { - const len = decoding.readVarUint(decoder) - for (let i = 0; i < len; i++) { - const room = decoding.readVarString(decoder) - const offset = decoding.readVarUint(decoder) - const rsid = decoding.readVarUint(decoder) - ydbinstances.forEach(ydb => { - const state = ydb.roomStates.get(room) - state.offset = offset - state.rsid = rsid - fireRoomStateUpdate(ydb, room) - }) - } - break - } - default: - globals.error('Unexpected bc message type') - } - } -} - -bc.onmessage = event => receiveBCData(event.data) - -/** - * Publish to all, including self - * @param {encoding.Encoder} encoder - */ -export const publishAll = encoder => { - const buffer = encoding.toBuffer(encoder) - bc.postMessage(buffer) - receiveBCData(buffer) -} - -/** - * Call this when update was created by this user and confid was created - * @param {number} cconf - * @param {string} roomname - */ -export const _broadcastYdbCUConfCreated = (cconf, roomname) => { - const encoder = encoding.createEncoder() - encoding.writeVarUint(encoder, bcYdbCUConfCreated) - encoding.writeVarUint(encoder, cconf) - encoding.writeVarString(encoder, roomname) - publishAll(encoder) -} - -/** - * Call this when user confid was confirmed by host - * @param {number} cconf - * @param {number} offset The conf-offset of the client-created offset - */ -export const _broadcastYdbCUConfConfirmed = (cconf, offset) => { - const encoder = encoding.createEncoder() - encoding.writeVarUint(encoder, bcYdbCUConfConfirmed) - encoding.writeVarUint(encoder, cconf) - encoding.writeVarUint(encoder, offset) - publishAll(encoder) -} - -/** - * Call this when remote update is received (thus host has increased, but not confirmed, the offset) - * @param {Array} subs sub is { room, offset } - */ -export const _broadcastYdbRemoteOffsetReceived = subs => { - const encoder = encoding.createEncoder() - encoding.writeVarUint(encoder, bcYdbRemoteOffsetReceived) - encoding.writeVarUint(encoder, subs.length) - subs.forEach(sub => { - encoding.writeVarString(encoder, sub.room) - encoding.writeVarUint(encoder, sub.offset) - }) - publishAll(encoder) -} - -/** - * @param {Array} subs sub is { room, offset } - */ -export const _broadcastYdbRemoteOffsetConfirmed = subs => { - const encoder = encoding.createEncoder() - encoding.writeVarUint(encoder, bcYdbRemoteOffsetConfirmed) - encoding.writeVarUint(encoder, subs.length) - subs.forEach(sub => { - encoding.writeVarString(encoder, sub.room) - encoding.writeVarUint(encoder, sub.offset) - }) - publishAll(encoder) -} - -/** - * Call this when a subscription is created - * @param {Array} rooms - */ -export const _broadcastYdbSyncingRoomsToServer = rooms => { - const encoder = encoding.createEncoder() - encoding.writeVarUint(encoder, bcYdbSyncingRoomsToServer) - encoding.writeVarUint(encoder, rooms.length) - rooms.forEach(room => { - encoding.writeVarString(encoder, room) - }) - publishAll(encoder) -} - -/** - * Call this when sync confirmed by host - * @param {Array} subs sub is {room, offset, rsid} - */ -export const _broadcastYdbSyncFromServer = subs => { - const encoder = encoding.createEncoder() - encoding.writeVarUint(encoder, bcYdbSyncFromServer) - encoding.writeVarUint(encoder, subs.length) - subs.forEach(sub => { - encoding.writeVarString(encoder, sub.room) - encoding.writeVarUint(encoder, sub.offset) - encoding.writeVarUint(encoder, sub.rsid) - }) - publishAll(encoder) -} - -/** - * @param {string} room - * @param {Function} f - */ -export const subscribeRoomData = (room, f) => { - let rsubs = datasubs.get(room) - if (rsubs === undefined) { - rsubs = new Set() - datasubs.set(room, rsubs) - } - rsubs.add(f) -} - -/** - * @param {string} room - * @param {ArrayBuffer} update - */ -export const publishRoomData = (room, update) => { - const encoder = encoding.createEncoder() - encoding.writeVarString(encoder, room) - encoding.writeArrayBuffer(encoder, update) - bc.postMessage(encoding.toBuffer(encoder)) - // call subs directly here instead of calling receivedBCData - const rsubs = datasubs.get(room) - if (rsubs !== undefined) { - rsubs.forEach(f => f(update)) - } -} - -export const subscribeYdbEvents = ydb => - ydbinstances.add(ydb) diff --git a/provider/ydb/idbactions.js b/provider/ydb/idbactions.js deleted file mode 100644 index b88b025c..00000000 --- a/provider/ydb/idbactions.js +++ /dev/null @@ -1,394 +0,0 @@ -/** - * @module provider/ydb - */ - -/* eslint-env browser */ - -/** - * Naming conventions: - * * ydb: Think of ydb as a federated set of servers. This is not yet true, but we will eventually get there. With this assumption come some challenges with the client - * * ydb instance: A single ydb instance that this ydb-client connects to - * * (room) host: Exactly one ydb instance controls a room at any time. The ownership may change over time. The host of a room is the ydb instance that owns it. This is not necessarily the instance we connect to. - * * room session id: An random id that is assigned to a room. When the server dies unexpectedly, we can conclude which data is missing and send it to the server (or delete it and prevent duplicate content) - * * update: An ArrayBuffer of binary data. Neither Ydb nor Ydb-client care about the content of update. Updates may be appended to each other. - * - * The database has four tables: - * - * CU "client-unconfirmed" confid -> room, update - * - The client writes to this table when it creates an update. - * - Then it sends an update to the host with the generated confid - * - In case the host doesn't confirm that it received this update, it is sent again on next sync - * HU "host-unconfirmed" room, offset -> update - * - Updates from the host are written to this table - * - When host confirms that an unconfirmed update was persisted, the update is written to the Co table - * - When client sync to host and the room session ids don't match, all host-unconfirmed messages are sent to host - * Co "confirmed": - * data:{room} -> update - * - this field holds confirmed room updates - * meta:{room} -> room session id, confirmed offset - * - this field holds metadata about the room - * US "unconfirmed-subscriptions" room -> _ - * - Subscriptions sent to the server, but didn't receive confirmation yet - * - Either a room is in US or in Co - * - A client may update a room when the room is in either US or Co - */ - -import * as encoding from '../../lib/encoding.js' -import * as decoding from '../../lib/decoding.js' -import * as idb from '../../lib/idb.js' -import * as globals from '../../lib/globals.js' -import * as message from './message.js' - -/** - * Get 'client-unconfirmed' store from transaction - * @param {IDBTransaction} t - * @return {IDBObjectStore} - */ -const getStoreCU = t => idb.getStore(t, STORE_CU) -/** - * Get 'host-unconfirmed' store from transaction - * @param {IDBTransaction} t - * @return {IDBObjectStore} - */ -const getStoreHU = t => idb.getStore(t, STORE_HU) -/** - * Get 'confirmed' store from transaction - * @param {IDBTransaction} t - * @return {IDBObjectStore} - */ -const getStoreCo = t => idb.getStore(t, STORE_CO) - -/** - * Get `unconfirmed-subscriptions` store from transaction - * @param {IDBTransaction} t - * @return {IDBObjectStore} - */ -const getStoreUS = t => idb.getStore(t, STORE_US) - -/** - * @param {string} room - * @param {number} offset - * @return {HUTableKey} - */ -const encodeHUKey = (room, offset) => [room, offset] - -/** - * Array of length 2: [string, number] - * @typedef HUTableKey - * @type {any} - */ - -/** - * @typedef RoomAndOffset - * @type {Object} - * @property {string} room - * @property {number} offset Received offsets (including offsets that are not yet confirmed) - */ - -/** - * @param {HUTableKey} key - * @return {RoomAndOffset} - */ -const decodeHUKey = key => { - return { - room: key[0], - offset: key[1] - } -} - -const getCoMetaKey = room => 'meta:' + room -const getCoDataKey = room => 'data:' + room - -const STORE_CU = 'client-unconfirmed' -const STORE_US = 'unconfirmed-subscriptions' -const STORE_CO = 'confirmed' -const STORE_HU = 'host-unconfirmed' - -/** - * @param {string} dbNamespace - * @return {Promise} - */ -export const openDB = dbNamespace => idb.openDB(dbNamespace, db => idb.createStores(db, [ - [STORE_CU, { autoIncrement: true }], - [STORE_HU], - [STORE_CO], - [STORE_US] -])) - -export const deleteDB = name => idb.deleteDB(name) - -/** - * Create a new IDBTransaction accessing all object stores. Normally we should care that we can access object stores in parallel. - * But this is not possible in ydb-client since at least two object stores are requested in every IDB change. - * @param {IDBDatabase} db - * @return {IDBTransaction} - */ -export const createTransaction = db => db.transaction([STORE_CU, STORE_HU, STORE_CO, STORE_US], 'readwrite') - -/** - * Write an update to the db after the client created it. This update is not yet received by the host. - * This function returns a client confirmation number. The confirmation number must be send to the host so it can identify the update, - * and we can move the update to HU when it is confirmed (@see writeHostUnconfirmedByClient) - * @param {IDBTransaction} t - * @param {String} room - * @param {ArrayBuffer} update - * @return {Promise} client confirmation number - */ -export const writeClientUnconfirmed = (t, room, update) => { - const encoder = encoding.createEncoder() - encoding.writeVarString(encoder, room) - encoding.writeArrayBuffer(encoder, update) - return idb.addAutoKey(getStoreCU(t), encoding.toBuffer(encoder)) -} - -/** - * Get all updates that are not yet confirmed by host. - * @param {IDBTransaction} t - * @return {Promise} All update messages as a single ArrayBuffer - */ -export const getUnconfirmedUpdates = t => { - const encoder = encoding.createEncoder() - return idb.iterate(getStoreCU(t), null, (value, clientConf) => { - const decoder = decoding.createDecoder(value) - const room = decoding.readVarString(decoder) - const update = decoding.readTail(decoder) - encoding.writeArrayBuffer(encoder, message.createUpdate(room, update, clientConf)) - }).then(() => encoding.toBuffer(encoder)) -} - -/** - * The host confirms that it received and persisted an update. The update can be safely removed from CU. - * It is necessary to call this function in case that the client disconnected before the host could send `writeHostUnconfirmedByClient`. - * @param {IDBTransaction} t - * @param {number} clientConf - * @return {Promise} - */ -export const confirmClient = (t, clientConf) => idb.del(getStoreCU(t), idb.createIDBKeyRangeUpperBound(clientConf, false)) - -/** - * The host confirms that it received and broadcasted an update sent from this client. - * Calling this method does not confirm that the update has been persisted by the server. - * - * Other clients will receive an update with `writeHostUnconfirmed`. Since this client created the update, it only receives a confirmation. So - * we can simply move the update from CU to HU. - * - * @param {IDBTransaction} t - * @param {number} clientConf The client confirmation number that identifies the update - * @param {number} offset The offset with wich the server will store the information - */ -export const writeHostUnconfirmedByClient = (t, clientConf, offset) => idb.get(getStoreCU(t), clientConf).then(roomAndUpdate => { - const decoder = decoding.createDecoder(roomAndUpdate) - const room = decoding.readVarString(decoder) - const update = decoding.readTail(decoder) - return writeHostUnconfirmed(t, room, offset, update).then(() => - idb.del(getStoreCU(t), clientConf) - ) -}) - -/** - * The host broadcasts an update created by another client. It assures that the update will eventually be persisted with - * `offset`. Calling this function does not imply that the update was persisted by the host. In case of mismatching room session ids - * the updates in HU will be sent to the server. - * - * @param {IDBTransaction} t - * @param {String} room - * @param {number} offset - * @param {ArrayBuffer} update - * @return {Promise} - */ -export const writeHostUnconfirmed = (t, room, offset, update) => idb.put(getStoreHU(t), update, encodeHUKey(room, offset)) - -/** - * The host confirms that it persisted updates up until (including) offset. updates may be moved from HU to Co. - * - * @param {IDBTransaction} t - * @param {String} room - * @param {number} offset Inclusive range [0, offset - 1] has been stored to host - */ -export const writeConfirmedByHost = (t, room, offset) => { - const co = getStoreCo(t) - return globals.pall([idb.get(co, getCoDataKey(room)), idb.get(co, getCoMetaKey(room))]).then(async arr => { - const data = arr[0] - const meta = decodeMetaValue(arr[1]) - const dataEncoder = encoding.createEncoder() - if (meta.offset >= offset) { - return // nothing to do - } - encoding.writeArrayBuffer(dataEncoder, data) - const hu = getStoreHU(t) - const huKeyRange = idb.createIDBKeyRangeBound(encodeHUKey(room, 0), encodeHUKey(room, offset), false, false) - return idb.iterate(hu, huKeyRange, (value, _key) => { - const key = decodeHUKey(_key) // @kevin _key is an array. remove decodeHUKey functions - if (key.room === room && key.offset <= offset) { - encoding.writeArrayBuffer(dataEncoder, value) - } - }).then(() => { - globals.pall([idb.put(co, encodeMetaValue(meta.rsid, offset), getCoMetaKey(room)), idb.put(co, encoding.toBuffer(dataEncoder), getCoDataKey(room)), idb.del(hu, huKeyRange)]) - }) - }) -} - -/** - * @typedef RoomMeta - * @type {Object} - * @property {string} room - * @property {number} rsid Room session id - * @property {number} offset Received offsets (including offsets that are not yet confirmed) - */ - -/** - * Get all meta information for all rooms. - * - * @param {IDBTransaction} t - * @return {Promise>} - */ -export const getRoomMetas = t => { - // const result = [] - const storeCo = getStoreCo(t) - const coQuery = idb.createIDBKeyRangeLowerBound('meta:', false) - return globals.pall([idb.getAll(storeCo, coQuery), idb.getAllKeys(storeCo, coQuery)]).then(([metaValues, metaKeys]) => globals.pall(metaValues.map((metavalue, i) => { - const room = metaKeys[i].slice(5) - const { rsid, offset } = decodeMetaValue(metavalue) - return { - room, - rsid, - offset: offset - } - }))) - /* - return idb.iterate(getStoreCo(t), idb.createIDBKeyRangeLowerBound('meta:', false), (metavalue, metakey) => - idb.getAllKeys(hu, idb.createIDBKeyRangeBound(encodeHUKey(metakey.slice(5), 0), encodeHUKey(metakey.slice(5), 2 ** 32), false, false)).then(keys => { - const { rsid, offset } = decodeMetaValue(metavalue) - result.push({ - room: metakey.slice(5), - rsid, - offset: keys.reduce((cur, key) => math.max(decodeHUKey(key).offset, cur), offset) - }) - }) - ).then(() => globals.presolve(result)) - */ -} - -export const getRoomMeta = (t, room) => - idb.get(getStoreCo(t), getCoMetaKey(room)) - -/** - * Get all data from idb, excluding unconfirmed updates. - * TODO: include updates in CU - * @param {IDBTransaction} t - * @param {string} room - * @return {Promise} - */ -export const getRoomDataWithoutCU = (t, room) => globals.pall([idb.get(getStoreCo(t), 'data:' + room), idb.getAll(getStoreHU(t), idb.createIDBKeyRangeBound(encodeHUKey(room, 0), encodeHUKey(room, 2 ** 32), false, false))]).then(([data, updates]) => { - const encoder = encoding.createEncoder() - encoding.writeArrayBuffer(encoder, data || new Uint8Array(0)) - updates.forEach(update => encoding.writeArrayBuffer(encoder, update)) - return encoding.toBuffer(encoder) -}) - -/** - * Get all data from idb, including unconfirmed updates. - * TODO: include updates in CU - * @param {IDBTransaction} t - * @param {string} room - * @return {Promise} - */ -export const getRoomData = (t, room) => globals.pall([idb.get(getStoreCo(t), 'data:' + room), idb.getAll(getStoreHU(t), idb.createIDBKeyRangeBound(encodeHUKey(room, 0), encodeHUKey(room, 2 ** 32), false, false)), idb.getAll(getStoreCU(t))]).then(([data, updates, cuUpdates]) => { - const encoder = encoding.createEncoder() - encoding.writeArrayBuffer(encoder, data || new Uint8Array(0)) - updates.forEach(update => encoding.writeArrayBuffer(encoder, update)) - cuUpdates.forEach(roomAndUpdate => { - const decoder = decoding.createDecoder(roomAndUpdate) - if (decoding.readVarString(decoder) === room) { - encoding.writeArrayBuffer(encoder, decoding.readTail(decoder)) - } - }) - return encoding.toBuffer(encoder) -}) - -const decodeMetaValue = buffer => { - const decoder = decoding.createDecoder(buffer) - const rsid = decoding.readVarUint(decoder) - const offset = decoding.readVarUint(decoder) - return { - rsid, offset - } -} -/** - * @param {number} rsid room session id - * @param {number} offset - * @return {ArrayBuffer} - */ -const encodeMetaValue = (rsid, offset) => { - const encoder = encoding.createEncoder() - encoding.writeVarUint(encoder, rsid) - encoding.writeVarUint(encoder, offset) - return encoding.toBuffer(encoder) -} - -const writeInitialCoEntry = (t, room, roomsessionid, offset) => globals.pall([ - idb.put(getStoreCo(t), encodeMetaValue(roomsessionid, offset), getCoMetaKey(room)), - idb.put(getStoreCo(t), globals.createArrayBufferFromArray([]), getCoDataKey(room)) -]) - -const _confirmSub = (t, metaval, sub) => { - if (metaval === undefined) { - return writeInitialCoEntry(t, sub.room, sub.rsid, sub.offset).then(() => idb.del(getStoreUS(t), sub.room)).then(() => null) - } - const meta = decodeMetaValue(metaval) - if (meta.rsid !== sub.rsid) { - // TODO: Yjs sync with server here - // get all room data (without CU) and save it as a client update. Then remove all data - return getRoomDataWithoutCU(t, sub.room) - .then(roomdata => - writeClientUnconfirmed(t, sub.room, roomdata) - .then(clientConf => message.createUpdate(sub.room, roomdata, clientConf)) - .then(update => - writeInitialCoEntry(t, sub.room, sub.rsid, sub.offset).then(() => update) - ) - ) - } else if (meta.offset < sub.offset) { - return writeConfirmedByHost(t, sub.room, sub.offset).then(() => null) - } else { - // nothing needs to happen - return null - } -} - -/** - * @typedef Sub - * @type {Object} - * @property {string} room room name - * @property {number} rsid room session id - * @property {number} offset - */ - -/** - * Set the initial room data. Overwrites initial data if there is any! - * @param {IDBTransaction} t - * @param {Sub} sub - * @return {Promise} Message to send to server - */ -export const confirmSubscription = (t, sub) => idb.get(getStoreCo(t), getCoMetaKey(sub.room)).then(metaval => _confirmSub(t, metaval, sub)) - -export const confirmSubscriptions = (t, subs) => idb.getAllKeysValues(getStoreCo(t), idb.createIDBKeyRangeLowerBound('meta:', false)).then(kvs => { - const ps = [] - const subMap = new Map() - subs.forEach(sub => subMap.set(sub.room, sub)) - for (let i = 0, len = kvs.length; i < len; i++) { - const kv = kvs[i] - const kvroom = kv.k.slice(5) - const exSub = subMap.get(kvroom) - if (exSub !== undefined) { - subMap.delete(kvroom) - ps.push(_confirmSub(t, kv.v, exSub)) - } - } - // all remaining elements in subMap do not exist yet in Co. - subMap.forEach(nonexSub => ps.push(_confirmSub(t, undefined, nonexSub))) - return ps -}) - -export const writeUnconfirmedSubscription = (t, room) => idb.put(getStoreUS(t), true, room) - -export const getUnconfirmedSubscriptions = t => idb.getAllKeys(getStoreUS(t)) diff --git a/provider/ydb/idbactions.test.js b/provider/ydb/idbactions.test.js deleted file mode 100644 index 41cf61c3..00000000 --- a/provider/ydb/idbactions.test.js +++ /dev/null @@ -1,23 +0,0 @@ -import * as globals from '../../lib/globals.js' -import * as idbactions from './idbactions.js' -import * as test from '../../lib/testing.js' - -idbactions.deleteDB().then(() => idbactions.openDB()).then(db => { - test.run('update lifetime 1', async (testname) => { - const update = new Uint8Array([1, 2, 3]).buffer - const t = idbactions.createTransaction(db) - idbactions.writeInitialRoomData(t, testname, 42, 1, new Uint8Array([0]).buffer) - const clientConf = await idbactions.writeClientUnconfirmed(t, testname, update) - await idbactions.writeHostUnconfirmedByClient(t, clientConf, 0) - await idbactions.writeConfirmedByHost(t, testname, 4) - const metas = await idbactions.getRoomMetas(t) - const roommeta = metas.find(meta => meta.room === testname) - if (roommeta == null || roommeta.offset !== 4 || roommeta.rsid !== 42) { - throw globals.error() - } - const data = await idbactions.getRoomData(t, testname) - if (!test.compareArrays(new Uint8Array(data), new Uint8Array([0, 1, 2, 3]))) { - throw globals.error() - } - }) -}) diff --git a/provider/ydb/index.js b/provider/ydb/index.js deleted file mode 100644 index 12cb6b17..00000000 --- a/provider/ydb/index.js +++ /dev/null @@ -1,11 +0,0 @@ -/** - * @module provider/ydb - */ - -import * as ydbclient from './YdbClient.js' - -/** - * @param {string} url - * @return {Promise} - */ -export const createYdbClient = url => ydbclient.get(url) diff --git a/provider/ydb/message.js b/provider/ydb/message.js deleted file mode 100644 index 86af478b..00000000 --- a/provider/ydb/message.js +++ /dev/null @@ -1,124 +0,0 @@ -/** - * @module provider/ydb - */ - -import * as encoding from './encoding.js' -import * as decoding from './decoding.js' -import * as idbactions from './idbactions.js' -import * as logging from './logging.js' -import * as bc from './broadcastchannel.js' - -/* make sure to update message.go in ydb when updating these values.. */ -export const MESSAGE_UPDATE = 0 // TODO: rename host_unconfirmed? -export const MESSAGE_SUB = 1 -export const MESSAGE_CONFIRMATION = 2 -export const MESSAGE_SUB_CONF = 3 -export const MESSAGE_HOST_UNCONFIRMED_BY_CLIENT = 4 -export const MESSAGE_CONFIRMED_BY_HOST = 5 - -/** - * @param {any} ydb YdbClient instance - * @param {ArrayBuffer} message - */ -export const readMessage = (ydb, message) => { - const t = idbactions.createTransaction(ydb.db) - const decoder = decoding.createDecoder(message) - while (decoding.hasContent(decoder)) { - switch (decoding.readVarUint(decoder)) { - case MESSAGE_UPDATE: { - const offset = decoding.readVarUint(decoder) - const room = decoding.readVarString(decoder) - const update = decoding.readPayload(decoder) - logging.log(`Received Update. room "${room}", offset ${offset}`) - idbactions.writeHostUnconfirmed(t, room, offset, update) - bc.publishRoomData(room, update) - bc._broadcastYdbRemoteOffsetReceived([{ room, offset }]) - break - } - case MESSAGE_SUB_CONF: { - const nSubs = decoding.readVarUint(decoder) - const subs = [] - for (let i = 0; i < nSubs; i++) { - const room = decoding.readVarString(decoder) - const offset = decoding.readVarUint(decoder) - const rsid = decoding.readVarUint(decoder) - subs.push({ - room, offset, rsid - }) - } - bc._broadcastYdbSyncFromServer(subs) - if (nSubs < 500) { - subs.map(sub => idbactions.confirmSubscription(t, sub)) - } else { - idbactions.confirmSubscriptions(t, subs) - } - break - } - case MESSAGE_CONFIRMATION: { // TODO: duplicate with MESSAGE_CONFIRMED_BY_HOST! - const room = decoding.readVarString(decoder) - const offset = decoding.readVarUint(decoder) - logging.log(`Received Confirmation. room "${room}", offset ${offset}`) - idbactions.writeConfirmedByHost(t, room, offset) - bc._broadcastYdbRemoteOffsetConfirmed([{ room, offset }]) - break - } - case MESSAGE_HOST_UNCONFIRMED_BY_CLIENT: { - const clientConf = decoding.readVarUint(decoder) - const offset = decoding.readVarUint(decoder) - logging.log(`Received HostUnconfirmedByClient. clientConf "${clientConf}", offset ${offset}`) - idbactions.writeHostUnconfirmedByClient(t, clientConf, offset) - bc._broadcastYdbCUConfConfirmed(clientConf, offset) - break - } - case MESSAGE_CONFIRMED_BY_HOST: { - const room = decoding.readVarString(decoder) - const offset = decoding.readVarUint(decoder) - logging.log(`Received Confirmation By Host. room "${room}", offset ${offset}`) - idbactions.writeConfirmedByHost(t, room, offset) - bc._broadcastYdbRemoteOffsetConfirmed([{ room, offset }]) - break - } - default: - logging.fail(`Unexpected message type`) - } - } -} - -/** - * @param {string} room - * @param {ArrayBuffer} update - * @param {number} clientConf - * @return {ArrayBuffer} - */ -export const createUpdate = (room, update, clientConf) => { - const encoder = encoding.createEncoder() - encoding.writeVarUint(encoder, MESSAGE_UPDATE) - encoding.writeVarUint(encoder, clientConf) - encoding.writeVarString(encoder, room) - encoding.writePayload(encoder, update) - return encoding.toBuffer(encoder) -} - -/** - * @typedef SubDef - * @type {Object} - * @property {string} room - * @property {number} offset - * @property {number} rsid - */ - -/** - * @param {Array} rooms - * @return {ArrayBuffer} - */ -export const createSub = rooms => { - const encoder = encoding.createEncoder() - encoding.writeVarUint(encoder, MESSAGE_SUB) - encoding.writeVarUint(encoder, rooms.length) - for (let i = 0; i < rooms.length; i++) { - encoding.writeVarString(encoder, rooms[i].room) - encoding.writeVarUint(encoder, rooms[i].offset) - encoding.writeVarUint(encoder, rooms[i].rsid) - } - return encoding.toBuffer(encoder) -} diff --git a/rollup.config.js b/rollup.config.js index 5cde7ae4..3277b64b 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,23 +1,99 @@ import nodeResolve from 'rollup-plugin-node-resolve' import commonjs from 'rollup-plugin-commonjs' -import babel from 'rollup-plugin-babel' -import uglify from 'rollup-plugin-uglify-es' +import { terser } from 'rollup-plugin-terser' + +const customModules = new Set([ + 'y-websocket', + 'y-codemirror', + 'y-ace', + 'y-textarea', + 'y-quill', + 'y-dom', + 'y-prosemirror' +]) +const customLibModules = new Set([ + // 'funlib', + 'y-protocols' +]) +const debugResolve = { + resolveId (importee) { + if (importee === 'yjs') { + return `${process.cwd()}/src/index.js` + } + 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 + } +} // set this to [] to disable obfuscation -const minificationPlugins = process.env.PRODUCTION ? [babel(), uglify()] : [] +const minificationPlugins = process.env.PRODUCTION ? [terser({ + module: true, + compress: { + hoist_vars: true, + module: true, + passes: 5, + pure_getters: true, + unsafe_comps: true, + unsafe_undefined: true + }, + mangle: { + toplevel: true + // properties: true + } +})] : [] export default [{ - input: './index.js', - output: [{ + input: './src/index.js', + output: { name: 'Y', - file: 'build/yjs.js', + file: 'dist/yjs.js', format: 'cjs', - sourcemap: true - }] + sourcemap: true, + paths: path => { + if (/^funlib\//.test(path)) { + return `funlib/dist${path.slice(6)}` + } + return path + } + }, + plugins: minificationPlugins }, { + input: './src/index.js', + output: { + name: 'Y', + file: 'dist/yjs.mjs', + format: 'esm', + sourcemap: true + }, + plugins: minificationPlugins +}, { + input: './tests/index.js', + output: { + name: 'test', + file: 'dist/tests.js', + format: 'iife', + sourcemap: true + }, + plugins: [ + debugResolve, + nodeResolve({ + sourcemap: true, + module: true, + browser: true + }), + commonjs() + ] +}, + +/* { input: 'tests/index.js', output: { - file: 'build/y.test.js', + file: 'dist/y.test.js', format: 'iife', name: 'ytests', sourcemap: true @@ -29,67 +105,21 @@ export default [{ }), commonjs() ] -}, { - input: './examples/codemirror.js', +}, */{ + input: ['./examples/codemirror.js', './examples/textarea.js', './examples/quill.js', './examples/dom.js', './examples/prosemirror.js'], // fs.readdirSync('./examples').filter(file => /(? './examples/' + file), output: { - name: 'codemirror', - file: 'examples/build/codemirror.js', - format: 'iife', + dir: 'examples/build', + format: 'esm', sourcemap: true }, plugins: [ + debugResolve, nodeResolve({ sourcemap: true, - module: true + module: true, + browser: true }), - commonjs() - ].concat(minificationPlugins) -}, { - input: './examples/prosemirror.js', - output: { - name: 'prosemirror', - file: 'examples/build/prosemirror.js', - format: 'iife', - sourcemap: true - }, - plugins: [ - nodeResolve({ - sourcemap: true, - module: true - }), - commonjs() - ].concat(minificationPlugins) -}, { - input: './examples/dom.js', - output: { - name: 'dom', - file: 'examples/build/dom.js', - format: 'iife', - sourcemap: true - }, - plugins: minificationPlugins -}, { - input: './examples/textarea.js', - output: { - name: 'textarea', - file: 'examples/build/textarea.js', - format: 'iife', - sourcemap: true - }, - plugins: minificationPlugins -}, { - input: './examples/quill.js', - output: { - name: 'textarea', - file: 'examples/build/quill.js', - format: 'iife', - sourcemap: true - }, - plugins: [ - nodeResolve({ - sourcemap: true, - module: true - }), - commonjs() - ].concat(minificationPlugins) + commonjs(), + ...minificationPlugins + ] }] diff --git a/src/index.js b/src/index.js new file mode 100644 index 00000000..612a4423 --- /dev/null +++ b/src/index.js @@ -0,0 +1,28 @@ + +export { Y } from './utils/Y.js' +export { UndoManager } from './utils/UndoManager.js' +export { Transaction } from './utils/Transaction.js' + +export { Item } from './structs/Item.js' +export { Delete } from './structs/Delete.js' +export { ItemJSON } from './structs/ItemJSON.js' +export { ItemString } from './structs/ItemString.js' +export { ItemFormat } from './structs/ItemFormat.js' +export { ItemEmbed } from './structs/ItemEmbed.js' +export { ItemBinary } from './structs/ItemBinary.js' +export { GC } from './structs/GC.js' + +export { YArray as Array } from './types/YArray.js' +export { YMap as Map } from './types/YMap.js' +export { YText as Text } from './types/YText.js' +export { YXmlText as XmlText } from './types/YXmlText.js' +export { YXmlHook as XmlHook } from './types/YXmlHook.js' +export { YXmlElement as XmlElement, YXmlFragment as XmlFragment } from './types/YXmlElement.js' + +export { getRelativePosition, fromRelativePosition, equal as equalRelativePosition } from './utils/relativePosition.js' + +export { ID, createID, RootFakeUserID } from './utils/ID.js' +export { DeleteStore, DSNode } from './utils/DeleteStore.js' +export { deleteItemRange } from './utils/structManipulation.js' +export { integrateRemoteStruct, integrateRemoteStructs } from './utils/integrateRemoteStructs.js' +export { isParentOf } from './utils/isParentOf.js' diff --git a/structs/Delete.js b/src/structs/Delete.js similarity index 93% rename from structs/Delete.js rename to src/structs/Delete.js index 16724b81..34698b2a 100644 --- a/structs/Delete.js +++ b/src/structs/Delete.js @@ -2,15 +2,15 @@ * @module structs */ -import { getStructReference } from '../utils/structReferences.js' +import { getStructReference } from 'y-protocols/sync.js' import * as ID from '../utils/ID.js' import { writeStructToTransaction } from '../utils/structEncoding.js' -import * as decoding from '../lib/decoding.js' -import * as encoding from '../lib/encoding.js' +import * as decoding from 'funlib/decoding.js' +import * as encoding from 'funlib/encoding.js' // import { Item } from './Item.js' // eslint-disable-line // import { Y } from '../utils/Y.js' // eslint-disable-line import { deleteItemRange } from '../utils/structManipulation.js' -import * as stringify from '../utils/structStringify.js' +import * as stringify from 'y-protocols/utils/structStringify.js' /** * @private diff --git a/structs/GC.js b/src/structs/GC.js similarity index 94% rename from structs/GC.js rename to src/structs/GC.js index 1abf7411..a7034fe4 100644 --- a/structs/GC.js +++ b/src/structs/GC.js @@ -2,11 +2,11 @@ * @module structs */ -import { getStructReference } from '../utils/structReferences.js' +import { getStructReference } from 'y-protocols/sync.js' import * as ID from '../utils/ID.js' import { writeStructToTransaction } from '../utils/structEncoding.js' -import * as decoding from '../lib/decoding.js' -import * as encoding from '../lib/encoding.js' +import * as decoding from 'funlib/decoding.js' +import * as encoding from 'funlib/encoding.js' // import { Y } from '../utils/Y.js' // eslint-disable-line // TODO should have the same base class as Item diff --git a/structs/Item.js b/src/structs/Item.js similarity index 97% rename from structs/Item.js rename to src/structs/Item.js index 3fa23157..885fe397 100644 --- a/structs/Item.js +++ b/src/structs/Item.js @@ -2,14 +2,13 @@ * @module structs */ -import { getStructReference } from '../utils/structReferences.js' +import { getStructReference } from 'y-protocols/sync.js' import * as ID from '../utils/ID.js' import { Delete } from './Delete.js' import { writeStructToTransaction } from '../utils/structEncoding.js' import { GC } from './GC.js' -import * as encoding from '../lib/encoding.js' -import * as decoding from '../lib/decoding.js' -// import { Type } from './Type.js' // eslint-disable-line +import * as encoding from 'funlib/encoding.js' +import * as decoding from 'funlib/decoding.js' /** * @private @@ -79,32 +78,32 @@ export class Item { constructor () { /** * The uniqe identifier of this type. - * @type {ID.ID | ID.RootID} + * @type {ID.ID | ID.RootID | null} */ this._id = null /** * The item that was originally to the left of this item. - * @type {Item} + * @type {Item | null} */ this._origin = null /** * The item that is currently to the left of this item. - * @type {Item} + * @type {Item | null} */ this._left = null /** * The item that is currently to the right of this item. - * @type {Item} + * @type {Item | null} */ this._right = null /** * The item that was originally to the right of this item. - * @type {Item} + * @type {Item | null} */ this._right_origin = null /** * The parent type. - * @type {Y|Type} + * @type {Y | Type | null} */ this._parent = null /** @@ -112,7 +111,7 @@ export class Item { * key is specified here. The key is then used to refer to the list in which * to insert this item. If `parentSub = null` type._start is the list in * which to insert to. Otherwise it is `parent._map`. - * @type {String} + * @type {String | null} */ this._parentSub = null /** @@ -123,7 +122,7 @@ export class Item { /** * If this type's effect is reundone this type refers to the type that undid * this operation. - * @type {Type} + * @type {Type | null} */ this._redone = null } @@ -159,6 +158,7 @@ export class Item { */ _copy () { const C = this.constructor + // @ts-ignore return new C() } @@ -284,12 +284,15 @@ export class Item { * @param {Y} y The Yjs instance * @param {boolean} createDelete Whether to propagate a message that this * Type was deleted. - * @param {boolean} gcChildren + * @param {boolean} [gcChildren] * * @private */ _delete (y, createDelete = true, gcChildren) { if (!this._deleted) { + /** + * @type { Type } + */ const parent = this._parent const len = this._length // adjust the length of parent diff --git a/structs/ItemBinary.js b/src/structs/ItemBinary.js similarity index 84% rename from structs/ItemBinary.js rename to src/structs/ItemBinary.js index b3347a59..b3714c6e 100644 --- a/structs/ItemBinary.js +++ b/src/structs/ItemBinary.js @@ -4,10 +4,10 @@ // TODO: ItemBinary should be able to merge with right (similar to other items). Or the other items (ItemJSON) should not be able to merge - extra byte + consistency -import { Item, splitHelper } from './Item.js' -import * as stringify from '../utils/structStringify.js' -import * as encoding from '../lib/encoding.js' -import * as decoding from '../lib/decoding.js' +import { Item } from './Item.js' +import * as stringify from 'y-protocols/utils/structStringify.js' +import * as encoding from 'funlib/encoding.js' +import * as decoding from 'funlib/decoding.js' import { Y } from '../utils/Y.js' // eslint-disable-line export class ItemBinary extends Item { diff --git a/structs/ItemEmbed.js b/src/structs/ItemEmbed.js similarity index 86% rename from structs/ItemEmbed.js rename to src/structs/ItemEmbed.js index e7f008a4..551de984 100644 --- a/structs/ItemEmbed.js +++ b/src/structs/ItemEmbed.js @@ -3,9 +3,9 @@ */ import { Item } from './Item.js' -import * as stringify from '../utils/structStringify.js' -import * as encoding from '../lib/encoding.js' -import * as decoding from '../lib/decoding.js' +import * as stringify from 'y-protocols/utils/structStringify.js' +import * as encoding from 'funlib/encoding.js' +import * as decoding from 'funlib/decoding.js' import { Y } from '../utils/Y.js' // eslint-disable-line export class ItemEmbed extends Item { diff --git a/structs/ItemFormat.js b/src/structs/ItemFormat.js similarity index 88% rename from structs/ItemFormat.js rename to src/structs/ItemFormat.js index 39df91eb..d9472068 100644 --- a/structs/ItemFormat.js +++ b/src/structs/ItemFormat.js @@ -3,9 +3,9 @@ */ import { Item } from './Item.js' -import * as stringify from '../utils/structStringify.js' -import * as encoding from '../lib/encoding.js' -import * as decoding from '../lib/decoding.js' +import * as stringify from 'y-protocols/utils/structStringify.js' +import * as encoding from 'funlib/encoding.js' +import * as decoding from 'funlib/decoding.js' import { Y } from '../utils/Y.js' // eslint-disable-line export class ItemFormat extends Item { diff --git a/structs/ItemJSON.js b/src/structs/ItemJSON.js similarity index 92% rename from structs/ItemJSON.js rename to src/structs/ItemJSON.js index e85cf892..5ccdbaf2 100644 --- a/structs/ItemJSON.js +++ b/src/structs/ItemJSON.js @@ -3,9 +3,9 @@ */ import { Item, splitHelper } from './Item.js' -import * as stringify from '../utils/structStringify.js' -import * as encoding from '../lib/encoding.js' -import * as decoding from '../lib/decoding.js' +import * as stringify from 'y-protocols/utils/structStringify.js' +import * as encoding from 'funlib/encoding.js' +import * as decoding from 'funlib/decoding.js' import { Y } from '../utils/Y.js' // eslint-disable-line export class ItemJSON extends Item { diff --git a/structs/ItemString.js b/src/structs/ItemString.js similarity index 89% rename from structs/ItemString.js rename to src/structs/ItemString.js index b10f0566..753be26f 100644 --- a/structs/ItemString.js +++ b/src/structs/ItemString.js @@ -3,9 +3,9 @@ */ import { Item, splitHelper } from './Item.js' -import * as stringify from '../utils/structStringify.js' -import * as encoding from '../lib/encoding.js' -import * as decoding from '../lib/decoding.js' +import * as stringify from 'y-protocols/utils/structStringify.js' +import * as encoding from 'funlib/encoding.js' +import * as decoding from 'funlib/decoding.js' import { Y } from '../utils/Y.js' // eslint-disable-line export class ItemString extends Item { diff --git a/structs/Type.js b/src/structs/Type.js similarity index 100% rename from structs/Type.js rename to src/structs/Type.js diff --git a/types/YArray.js b/src/types/YArray.js similarity index 99% rename from types/YArray.js rename to src/types/YArray.js index e31b081d..91fe3c93 100644 --- a/types/YArray.js +++ b/src/types/YArray.js @@ -5,7 +5,7 @@ import { Type } from '../structs/Type.js' import { ItemJSON } from '../structs/ItemJSON.js' import { ItemString } from '../structs/ItemString.js' -import * as stringify from '../utils/structStringify.js' +import * as stringify from 'y-protocols/utils/structStringify.js' import { YEvent } from '../utils/YEvent.js' import { Transaction } from '../utils/Transaction.js' // eslint-disable-line import { Item } from '../structs/Item.js' // eslint-disable-line diff --git a/types/YMap.js b/src/types/YMap.js similarity index 98% rename from types/YMap.js rename to src/types/YMap.js index 2bf9e905..966297a9 100644 --- a/types/YMap.js +++ b/src/types/YMap.js @@ -5,7 +5,7 @@ import { Item } from '../structs/Item.js' import { Type } from '../structs/Type.js' import { ItemJSON } from '../structs/ItemJSON.js' -import * as stringify from '../utils/structStringify.js' +import * as stringify from 'y-protocols/utils/structStringify.js' import { YEvent } from '../utils/YEvent.js' import { ItemBinary } from '../structs/ItemBinary.js' import { isVisible } from '../utils/snapshot.js' diff --git a/types/YText.js b/src/types/YText.js similarity index 99% rename from types/YText.js rename to src/types/YText.js index b49f7037..7d5520df 100644 --- a/types/YText.js +++ b/src/types/YText.js @@ -5,7 +5,7 @@ import { ItemEmbed } from '../structs/ItemEmbed.js' import { ItemString } from '../structs/ItemString.js' import { ItemFormat } from '../structs/ItemFormat.js' -import * as stringify from '../utils/structStringify.js' +import * as stringify from 'y-protocols/utils/structStringify.js' import { YArrayEvent, YArray } from './YArray.js' import { isVisible } from '../utils/snapshot.js' @@ -516,7 +516,7 @@ export class YText extends YArray { for (let nodeName in delta.attributes) { const attrs = [] for (let key in delta.attributes[nodeName]) { - attrs.push({key, value: delta.attributes[nodeName][key]}) + attrs.push({ key, value: delta.attributes[nodeName][key] }) } // sort attributes to get a unique order attrs.sort((a, b) => a.key < b.key ? -1 : 1) diff --git a/types/YXmlElement.js b/src/types/YXmlElement.js similarity index 81% rename from types/YXmlElement.js rename to src/types/YXmlElement.js index 8ede1092..ee1225ed 100644 --- a/types/YXmlElement.js +++ b/src/types/YXmlElement.js @@ -3,15 +3,87 @@ */ import { YMap } from './YMap.js' -import { createAssociation } from '../bindings/dom/util.js' -import * as encoding from '../lib/encoding.js' -import * as decoding from '../lib/decoding.js' +import * as encoding from 'funlib/encoding.js' +import * as decoding from 'funlib/decoding.js' import { Y } from '../utils/Y.js' // eslint-disable-line -import { DomBinding } from '../bindings/dom/DomBinding.js' // eslint-disable-line -import { YXmlTreeWalker } from './YXmlTreeWalker.js' import { YArray } from './YArray.js' import { YXmlEvent } from './YXmlEvent.js' -import * as stringify from '../utils/structStringify.js' +import * as stringify from 'y-protocols/utils/structStringify.js' + +/** + * Define the elements to which a set of CSS queries apply. + * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors|CSS_Selectors} + * + * @example + * query = '.classSelector' + * query = 'nodeSelector' + * query = '#idSelector' + * + * @typedef {string} CSS_Selector + */ + +/** + * Represents a subset of the nodes of a YXmlElement / YXmlFragment and a + * position within them. + * + * Can be created with {@link YXmlFragment#createTreeWalker} + * + * @public + */ +export class YXmlTreeWalker { + constructor (root, f) { + this._filter = f || (() => true) + this._root = root + this._currentNode = root + this._firstCall = true + } + [Symbol.iterator] () { + return this + } + /** + * Get the next node. + * + * @return {YXmlElement} The next node. + * + * @public + */ + next () { + let n = this._currentNode + if (this._firstCall) { + this._firstCall = false + if (!n._deleted && this._filter(n)) { + return { value: n, done: false } + } + } + do { + if (!n._deleted && (n.constructor === YXmlElement || n.constructor === YXmlFragment) && n._start !== null) { + // walk down in the tree + n = n._start + } else { + // walk right or up in the tree + while (n !== this._root) { + if (n._right !== null) { + n = n._right + break + } + n = n._parent + } + if (n === this._root) { + n = null + } + } + if (n === this._root) { + break + } + } while (n !== null && (n._deleted || !this._filter(n))) + this._currentNode = n + if (n === null) { + return { done: true } + } else { + return { value: n, done: false } + } + } +} /** * Dom filter function. @@ -136,22 +208,20 @@ export class YXmlFragment extends YArray { * @param {Document} [_document=document] The document object (you must define * this when calling this method in * nodejs) - * @param {Object.} [hooks={}] Optional property to customize how hooks - * are presented in the // TODO: include all tests - - * @param {DomBinding} [binding] You should not set this property. T// TODO: include all tests - - * used if DomBinding wants to create // TODO: include all tests - - * association to the created DOM type// TODO: include all tests - + * @param {Object} [hooks={}] Optional property to customize how hooks + * are presented in the DOM + * @param {DomBinding} [binding] You should not set this property. This is + * used if DomBinding wants to create a + * association to the created DOM type. * @return {DocumentFragment} The {@link https://developer.mozilla.org/en-US/docs/Web/API/Element|Dom Element} * * @public */ toDom (_document = document, hooks = {}, binding) { const fragment = _document.createDocumentFragment() - createAssociation(binding, fragment, this) + if (binding !== undefined) { + binding._createAssociation(fragment, this) + } this.forEach(xmlType => { fragment.insertBefore(xmlType.toDom(_document, hooks, binding), null) }) @@ -300,7 +370,7 @@ export class YXmlElement extends YXmlFragment { * * @param {String} attributeName The attribute name that identifies the * queried value. - * @param {import('../protocols/history.js').HistorySnapshot} [snapshot] + * @param {HistorySnapshot} [snapshot] * @return {String} The queried attribute value. * * @public @@ -312,7 +382,7 @@ export class YXmlElement extends YXmlFragment { /** * Returns all attribute name/value pairs in a JSON Object. * - * @param {import('../protocols/history.js').HistorySnapshot} [snapshot] + * @param {HistorySnapshot} [snapshot] * @return {Object} A JSON Object that describes the attributes. * * @public @@ -357,7 +427,9 @@ export class YXmlElement extends YXmlFragment { this.forEach(yxml => { dom.appendChild(yxml.toDom(_document, hooks, binding)) }) - createAssociation(binding, dom, this) + if (binding !== undefined) { + binding._createAssociation(dom, this) + } return dom } } diff --git a/types/YXmlEvent.js b/src/types/YXmlEvent.js similarity index 100% rename from types/YXmlEvent.js rename to src/types/YXmlEvent.js diff --git a/types/YXmlHook.js b/src/types/YXmlHook.js similarity index 89% rename from types/YXmlHook.js rename to src/types/YXmlHook.js index ad45f1eb..cb287ffd 100644 --- a/types/YXmlHook.js +++ b/src/types/YXmlHook.js @@ -3,11 +3,8 @@ */ import { YMap } from './YMap.js' -import { createAssociation } from '../bindings/dom/util.js' -import * as encoding from '../lib/encoding.js' -import * as decoding from '../lib/decoding.js' - -import { DomBinding } from '../bindings/dom/DomBinding.js' // eslint-disable-line +import * as encoding from 'funlib/encoding.js' +import * as decoding from 'funlib/decoding.js' import { Y } from '../utils/Y.js' // eslint-disable-line /** @@ -17,7 +14,7 @@ import { Y } from '../utils/Y.js' // eslint-disable-line */ export class YXmlHook extends YMap { /** - * @param {String} hookName nodeName of the Dom Node. + * @param {String} [hookName] nodeName of the Dom Node. */ constructor (hookName) { super() @@ -62,7 +59,9 @@ export class YXmlHook extends YMap { dom = document.createElement(this.hookName) } dom.setAttribute('data-yjs-hook', this.hookName) - createAssociation(binding, dom, this) + if (binding !== undefined) { + binding._createAssociation(dom, this) + } return dom } diff --git a/types/YXmlText.js b/src/types/YXmlText.js similarity index 90% rename from types/YXmlText.js rename to src/types/YXmlText.js index 9a7f8f6f..9b31151c 100644 --- a/types/YXmlText.js +++ b/src/types/YXmlText.js @@ -3,9 +3,7 @@ */ import { YText } from './YText.js' -import { createAssociation } from '../bindings/dom/util.js' import { Y } from '../utils/Y.js' // eslint-disable-line -import { DomBinding } from '../bindings/dom/DomBinding.js' // eslint-disable-line /** * Represents text in a Dom Element. In the future this type will also handle @@ -29,7 +27,9 @@ export class YXmlText extends YText { */ toDom (_document = document, hooks, binding) { const dom = _document.createTextNode(this.toString()) - createAssociation(binding, dom, this) + if (binding !== undefined) { + binding._createAssociation(dom, this) + } return dom } diff --git a/utils/BindMapping.js b/src/utils/BindMapping.js similarity index 100% rename from utils/BindMapping.js rename to src/utils/BindMapping.js diff --git a/src/utils/DeleteStore.js b/src/utils/DeleteStore.js new file mode 100644 index 00000000..355fbc73 --- /dev/null +++ b/src/utils/DeleteStore.js @@ -0,0 +1,89 @@ +/** + * @module utils + */ + +import { Tree } from 'funlib/tree.js' +import * as ID from './ID.js' + +export class DSNode { + constructor (id, len, gc) { + this._id = id + this.len = len + this.gc = gc + } + clone () { + return new DSNode(this._id, this.len, this.gc) + } +} + +export class DeleteStore extends Tree { + logTable () { + const deletes = [] + this.iterate(null, null, n => { + deletes.push({ + user: n._id.user, + clock: n._id.clock, + len: n.len, + gc: n.gc + }) + }) + console.table(deletes) + } + isDeleted (id) { + var n = this.findWithUpperBound(id) + return n !== null && n._id.user === id.user && id.clock < n._id.clock + n.len + } + mark (id, length, gc) { + if (length === 0) return + // Step 1. Unmark range + const leftD = this.findWithUpperBound(ID.createID(id.user, id.clock - 1)) + // Resize left DSNode if necessary + if (leftD !== null && leftD._id.user === id.user) { + if (leftD._id.clock < id.clock && id.clock < leftD._id.clock + leftD.len) { + // node is overlapping. need to resize + if (id.clock + length < leftD._id.clock + leftD.len) { + // overlaps new mark range and some more + // create another DSNode to the right of new mark + this.put(new DSNode(ID.createID(id.user, id.clock + length), leftD._id.clock + leftD.len - id.clock - length, leftD.gc)) + } + // resize left DSNode + leftD.len = id.clock - leftD._id.clock + } // Otherwise there is no overlapping + } + // Resize right DSNode if necessary + const upper = ID.createID(id.user, id.clock + length - 1) + const rightD = this.findWithUpperBound(upper) + if (rightD !== null && rightD._id.user === id.user) { + if (rightD._id.clock < id.clock + length && id.clock <= rightD._id.clock && id.clock + length < rightD._id.clock + rightD.len) { // we only consider the case where we resize the node + const d = id.clock + length - rightD._id.clock + rightD._id = ID.createID(rightD._id.user, rightD._id.clock + d) + rightD.len -= d + } + } + // Now we only have to delete all inner marks + const deleteNodeIds = [] + this.iterate(id, upper, m => { + deleteNodeIds.push(m._id) + }) + for (let i = deleteNodeIds.length - 1; i >= 0; i--) { + this.delete(deleteNodeIds[i]) + } + let newMark = new DSNode(id, length, gc) + // Step 2. Check if we can extend left or right + if (leftD !== null && leftD._id.user === id.user && leftD._id.clock + leftD.len === id.clock && leftD.gc === gc) { + // We can extend left + leftD.len += length + newMark = leftD + } + const rightNext = this.find(ID.createID(id.user, id.clock + length)) + if (rightNext !== null && rightNext._id.user === id.user && id.clock + length === rightNext._id.clock && gc === rightNext.gc) { + // We can merge newMark and rightNext + newMark.len += rightNext.len + this.delete(rightNext._id) + } + if (leftD !== newMark) { + // only put if we didn't extend left + this.put(newMark) + } + } +} diff --git a/utils/EventHandler.js b/src/utils/EventHandler.js similarity index 100% rename from utils/EventHandler.js rename to src/utils/EventHandler.js diff --git a/utils/ID.js b/src/utils/ID.js similarity index 93% rename from utils/ID.js rename to src/utils/ID.js index cd421309..bb8d82fa 100644 --- a/utils/ID.js +++ b/src/utils/ID.js @@ -2,9 +2,9 @@ * @module utils */ -import { getStructReference } from './structReferences.js' -import * as decoding from '../lib/decoding.js' -import * as encoding from '../lib/encoding.js' +import { getStructReference } from 'y-protocols/sync.js' +import * as decoding from 'funlib/decoding.js' +import * as encoding from 'funlib/encoding.js' export class ID { constructor (user, clock) { diff --git a/utils/OperationStore.js b/src/utils/OperationStore.js similarity index 94% rename from utils/OperationStore.js rename to src/utils/OperationStore.js index b6a77165..a64300b1 100644 --- a/utils/OperationStore.js +++ b/src/utils/OperationStore.js @@ -2,11 +2,11 @@ * @module utils */ -import { Tree } from '../lib/Tree.js' +import { Tree } from 'funlib/tree.js' import * as ID from '../utils/ID.js' -import { getStruct } from '../utils/structReferences.js' +import { getStruct } from 'y-protocols/sync.js' import { GC } from '../structs/GC.js' -import * as stringify from '../utils/structStringify.js' +import * as stringify from 'y-protocols/utils/structStringify.js' export class OperationStore extends Tree { constructor (y) { diff --git a/src/utils/StateStore.js b/src/utils/StateStore.js new file mode 100644 index 00000000..e44d1443 --- /dev/null +++ b/src/utils/StateStore.js @@ -0,0 +1,53 @@ +/** + * @module utils + */ + +import * as ID from '../utils/ID.js' + +/** + */ +export class StateStore { + constructor (y) { + this.y = y + this.state = new Map() + } + logTable () { + const entries = [] + for (let [user, state] of this.state) { + entries.push({ + user, state + }) + } + console.table(entries) + } + getNextID (len) { + const user = this.y.userID + const state = this.getState(user) + this.setState(user, state + len) + return ID.createID(user, state) + } + updateRemoteState (struct) { + let user = struct._id.user + let userState = this.state.get(user) + while (struct !== null && struct._id.clock === userState) { + userState += struct._length + struct = this.y.os.get(ID.createID(user, userState)) + } + this.state.set(user, userState) + } + getState (user) { + let state = this.state.get(user) + if (state == null) { + return 0 + } + return state + } + setState (user, state) { + // TODO: modify missingi structs here + const beforeState = this.y._transaction.beforeState + if (!beforeState.has(user)) { + beforeState.set(user, this.getState(user)) + } + this.state.set(user, state) + } +} diff --git a/utils/Transaction.js b/src/utils/Transaction.js similarity index 87% rename from utils/Transaction.js rename to src/utils/Transaction.js index 83a0b7e1..269d7d07 100644 --- a/utils/Transaction.js +++ b/src/utils/Transaction.js @@ -2,11 +2,8 @@ * @module utils */ -import * as encoding from '../lib/encoding.js' -import { Y } from '../utils/Y.js' // eslint-disable-line -import { Item } from '../structs/Item.js' // eslint-disable-line -import { Type } from '../structs/Type.js' // eslint-disable-line -import { YEvent } from './YEvent.js' // eslint-disable-line +import * as encoding from 'funlib/encoding.js' + /** * A transaction is created for every change on the Yjs model. It is possible * to bundle changes on the Yjs model in a single transaction to diff --git a/utils/UndoManager.js b/src/utils/UndoManager.js similarity index 100% rename from utils/UndoManager.js rename to src/utils/UndoManager.js diff --git a/utils/Y.js b/src/utils/Y.js similarity index 77% rename from utils/Y.js rename to src/utils/Y.js index 937ef47c..8079f44d 100644 --- a/utils/Y.js +++ b/src/utils/Y.js @@ -1,15 +1,10 @@ -import { DeleteStore, readDeleteStore, writeDeleteStore } from './DeleteStore.js' +import { DeleteStore } from './DeleteStore.js' import { OperationStore } from './OperationStore.js' import { StateStore } from './StateStore.js' -import { generateRandomUint32 } from './generateRandomUint32.js' +import * as random from 'funlib/random.js' import { createRootID } from './ID.js' -import { NamedEventHandler } from '../lib/NamedEventHandler.js' +import { Observable } from 'funlib/observable.js' import { Transaction } from './Transaction.js' -import * as encoding from '../lib/encoding.js' -import * as message from '../protocols/sync.js' -import { integrateRemoteStructs } from './integrateRemoteStructs.js' -import { Type } from '../structs/Type.js' // eslint-disable-line -import { Decoder } from '../lib/decoding.js' // eslint-disable-line /** * Anything that can be encoded with `JSON.stringify` and can be decoded with @@ -26,7 +21,7 @@ import { Decoder } from '../lib/decoding.js' // eslint-disable-line /** * A Yjs instance handles the state of shared data. */ -export class Y extends NamedEventHandler { +export class Y extends Observable { /** * @param {Object} [conf] configuration */ @@ -34,7 +29,7 @@ export class Y extends NamedEventHandler { super() this.gcEnabled = conf.gc || false this._contentReady = false - this.userID = generateRandomUint32() + this.userID = random.uint32() // TODO: This should be a Map so we can use encodables as keys this._map = new Map() this.ds = new DeleteStore() @@ -50,30 +45,6 @@ export class Y extends NamedEventHandler { this._deleted = false // for compatiblity of having this as a parent for types this._id = null } - - /** - * Read the Decoder and fill the Yjs instance with data in the decoder. - * - * @param {Decoder} decoder The BinaryDecoder to read from. - */ - importModel (decoder) { - this.transact(() => { - integrateRemoteStructs(decoder, this) - readDeleteStore(decoder, this) - }) - } - - /** - * Encode the Yjs model to ArrayBuffer - * - * @return {ArrayBuffer} The Yjs model as ArrayBuffer - */ - exportModel () { - const encoder = encoding.createEncoder() - message.writeStructs(encoder, this, new Map()) - writeDeleteStore(encoder, this.ds) - return encoding.toBuffer(encoder) - } _beforeChange () {} _callObserver (transaction, subs, remote) {} /** @@ -91,7 +62,7 @@ export class Y extends NamedEventHandler { let initialCall = this._transaction === null if (initialCall) { this._transaction = new Transaction(this) - this.emit('beforeTransaction', this, this._transaction, remote) + this.emit('beforeTransaction', [this, this._transaction, remote]) } try { f(this) @@ -99,7 +70,7 @@ export class Y extends NamedEventHandler { console.error(e) } if (initialCall) { - this.emit('beforeObserverCalls', this, this._transaction, remote) + this.emit('beforeObserverCalls', [this, this._transaction, remote]) const transaction = this._transaction this._transaction = null // emit change events on changed types @@ -124,7 +95,7 @@ export class Y extends NamedEventHandler { } }) // when all changes & events are processed, emit afterTransaction event - this.emit('afterTransaction', this, transaction, remote) + this.emit('afterTransaction', [this, transaction, remote]) } } @@ -204,11 +175,7 @@ export class Y extends NamedEventHandler { * Disconnect from the room, and destroy all traces of this Yjs instance. */ destroy () { - this.emit('destroyed', true) + this.emit('destroyed', [true]) super.destroy() - this._map = null - this.os = null - this.ds = null - this.ss = null } } diff --git a/utils/YEvent.js b/src/utils/YEvent.js similarity index 92% rename from utils/YEvent.js rename to src/utils/YEvent.js index 78bccb26..b644b87e 100644 --- a/utils/YEvent.js +++ b/src/utils/YEvent.js @@ -2,8 +2,6 @@ * @module utils */ -import { Type } from '../structs/Type.js' // eslint-disable-line - /** * YEvent describes the changes on a YType. */ diff --git a/utils/defragmentItemContent.js b/src/utils/defragmentItemContent.js similarity index 100% rename from utils/defragmentItemContent.js rename to src/utils/defragmentItemContent.js diff --git a/utils/integrateRemoteStructs.js b/src/utils/integrateRemoteStructs.js similarity index 97% rename from utils/integrateRemoteStructs.js rename to src/utils/integrateRemoteStructs.js index 265cad2f..ae04d190 100644 --- a/utils/integrateRemoteStructs.js +++ b/src/utils/integrateRemoteStructs.js @@ -2,8 +2,8 @@ * @module utils */ -import { getStruct } from '../utils/structReferences.js' -import * as decoding from '../lib/decoding.js' +import { getStruct } from 'y-protocols/sync.js' +import * as decoding from 'funlib/decoding.js' import { GC } from '../structs/GC.js' import { Y } from '../utils/Y.js' // eslint-disable-line import { Item } from '../structs/Item.js' // eslint-disable-line diff --git a/utils/isParentOf.js b/src/utils/isParentOf.js similarity index 100% rename from utils/isParentOf.js rename to src/utils/isParentOf.js diff --git a/utils/relativePosition.js b/src/utils/relativePosition.js similarity index 100% rename from utils/relativePosition.js rename to src/utils/relativePosition.js diff --git a/utils/snapshot.js b/src/utils/snapshot.js similarity index 100% rename from utils/snapshot.js rename to src/utils/snapshot.js diff --git a/utils/structEncoding.js b/src/utils/structEncoding.js similarity index 100% rename from utils/structEncoding.js rename to src/utils/structEncoding.js diff --git a/utils/structManipulation.js b/src/utils/structManipulation.js similarity index 100% rename from utils/structManipulation.js rename to src/utils/structManipulation.js diff --git a/test.html b/test.html new file mode 100644 index 00000000..c3b3ab2d --- /dev/null +++ b/test.html @@ -0,0 +1,9 @@ + + + + Testing Yjs + + + + + diff --git a/tests/DeleteStore.tests.js b/tests/DeleteStore.tests.js index 2317587e..f5da48ef 100644 --- a/tests/DeleteStore.tests.js +++ b/tests/DeleteStore.tests.js @@ -1,7 +1,7 @@ -import { test } from 'cutest' -import * as random from '../lib/prng/prng.js' -import { DeleteStore } from '../utils/DeleteStore.js' -import * as ID from '../utils/ID.js' +import * as prng from 'funlib/prng.js' +import * as t from 'funlib/testing.js' +import { DeleteStore } from '../src/utils/DeleteStore.js' +import * as ID from '../src/utils/ID.js' /** * Converts a DS to an array of length 10. @@ -13,9 +13,9 @@ import * as ID from '../utils/ID.js' * ds.mark(ID.createID(0, 3), 1, false) * dsToArray(ds) // => [0, 1, undefined, 0] * - * @return {Array<(undefined|number)>} Array of numbers indicating if array[i] is deleted (0), garbage collected (1), or undeleted (undefined). + * @return {Array<(null|number)>} Array of numbers indicating if array[i] is deleted (0), garbage collected (1), or undeleted (undefined). */ -function dsToArray (ds) { +const dsToArray = ds => { const array = [] let i = 0 ds.iterate(null, null, n => { @@ -30,53 +30,58 @@ function dsToArray (ds) { return array } -test('DeleteStore', async function ds1 (t) { +/** + * @param {t.TestCase} tc + */ +export const testDeleteStore = tc => { + t.describe('Integrity tests') const ds = new DeleteStore() ds.mark(ID.createID(0, 1), 1, false) ds.mark(ID.createID(0, 2), 1, false) ds.mark(ID.createID(0, 3), 1, false) - t.compare(dsToArray(ds), [null, 0, 0, 0]) + t.compareArrays(dsToArray(ds), [null, 0, 0, 0]) ds.mark(ID.createID(0, 2), 1, true) - t.compare(dsToArray(ds), [null, 0, 1, 0]) + t.compareArrays(dsToArray(ds), [null, 0, 1, 0]) ds.mark(ID.createID(0, 1), 1, true) - t.compare(dsToArray(ds), [null, 1, 1, 0]) + t.compareArrays(dsToArray(ds), [null, 1, 1, 0]) ds.mark(ID.createID(0, 3), 1, true) - t.compare(dsToArray(ds), [null, 1, 1, 1]) + t.compareArrays(dsToArray(ds), [null, 1, 1, 1]) ds.mark(ID.createID(0, 5), 1, true) ds.mark(ID.createID(0, 4), 1, true) - t.compare(dsToArray(ds), [null, 1, 1, 1, 1, 1]) + t.compareArrays(dsToArray(ds), [null, 1, 1, 1, 1, 1]) ds.mark(ID.createID(0, 0), 3, false) - t.compare(dsToArray(ds), [0, 0, 0, 1, 1, 1]) -}) + t.compareArrays(dsToArray(ds), [0, 0, 0, 1, 1, 1]) -test('random DeleteStore tests', async function randomDS (t) { - const prng = random.createPRNG(t.getSeed()) - const ds = new DeleteStore() - const dsArray = [] - for (let i = 0; i < 200; i++) { - const pos = random.int32(prng, 0, 10) - const len = random.int32(prng, 0, 4) - const gc = random.bool(prng) - ds.mark(ID.createID(0, pos), len, gc) - for (let j = 0; j < len; j++) { - dsArray[pos + j] = gc ? 1 : 0 + t.describe('Random tests') + const gen = tc.prng + for (let i = 0; i < tc.repititions; i++) { + const ds = new DeleteStore() + const dsArray = [] + for (let i = 0; i < 200; i++) { + const pos = prng.int32(gen, 0, 10) + const len = prng.int32(gen, 0, 4) + const gc = prng.bool(gen) + ds.mark(ID.createID(0, pos), len, gc) + for (let j = 0; j < len; j++) { + dsArray[pos + j] = gc ? 1 : 0 + } } - } - // fill empty fields - for (let i = 0; i < dsArray.length; i++) { - if (dsArray[i] !== 0 && dsArray[i] !== 1) { - dsArray[i] = null + // fill empty fields + for (let i = 0; i < dsArray.length; i++) { + if (dsArray[i] !== 0 && dsArray[i] !== 1) { + dsArray[i] = null + } } - } - t.compare(dsToArray(ds), dsArray, 'expected DS result') - let size = 0 - let lastEl = null - for (let i = 0; i < dsArray.length; i++) { - let el = dsArray[i] - if (lastEl !== el && el !== null) { - size++ + t.compareArrays(dsToArray(ds), dsArray, 'Expected DS result') + let size = 0 + let lastEl = null + for (let i = 0; i < dsArray.length; i++) { + let el = dsArray[i] + if (lastEl !== el && el !== null) { + size++ + } + lastEl = el } - lastEl = el + t.assert(size === ds.length, 'DS sizes match') } - t.compare(size, ds.length, 'expected ds size') -}) +} diff --git a/tests/diff.tests.js b/tests/diff.tests.js deleted file mode 100644 index f8e91f6b..00000000 --- a/tests/diff.tests.js +++ /dev/null @@ -1,29 +0,0 @@ -import { test } from 'cutest' -import { simpleDiff } from '../lib/diff.js' -import * as random from '../lib/prng/prng.js' - -function runDiffTest (t, a, b, expected) { - let result = simpleDiff(a, b) - t.compare(result, expected, `Compare "${a}" with "${b}"`) -} - -test('diff tests', async function diff1 (t) { - runDiffTest(t, 'abc', 'axc', { pos: 1, remove: 1, insert: 'x' }) - runDiffTest(t, 'bc', 'xc', { pos: 0, remove: 1, insert: 'x' }) - runDiffTest(t, 'ab', 'ax', { pos: 1, remove: 1, insert: 'x' }) - runDiffTest(t, 'b', 'x', { pos: 0, remove: 1, insert: 'x' }) - runDiffTest(t, '', 'abc', { pos: 0, remove: 0, insert: 'abc' }) - runDiffTest(t, 'abc', 'xyz', { pos: 0, remove: 3, insert: 'xyz' }) - runDiffTest(t, 'axz', 'au', { pos: 1, remove: 2, insert: 'u' }) - runDiffTest(t, 'ax', 'axy', { pos: 2, remove: 0, insert: 'y' }) -}) - -test('random diff tests', async function randomDiff (t) { - const gen = random.createPRNG(t.getSeed() * 1000000000) - let a = random.word(gen) - let b = random.word(gen) - let change = simpleDiff(a, b) - let arr = Array.from(a) - arr.splice(change.pos, change.remove, ...Array.from(change.insert)) - t.assert(arr.join('') === b, 'Applying change information is correct') -}) diff --git a/tests/encode-decode.tests.js b/tests/encode-decode.tests.js deleted file mode 100644 index e6fc7b17..00000000 --- a/tests/encode-decode.tests.js +++ /dev/null @@ -1,65 +0,0 @@ -import { test } from 'cutest' -import { generateRandomUint32 } from '../utils/generateRandomUint32.js' -import * as encoding from '../lib/encoding.js' -import * as decoding from '../lib/decoding.js' -import * as random from '../lib/prng/prng.js' - -function testEncoding (t, write, read, val) { - let encoder = encoding.createEncoder() - write(encoder, val) - let reader = decoding.createDecoder(encoding.toBuffer(encoder)) - let result = read(reader) - t.log(`string encode: ${JSON.stringify(val).length} bytes / binary encode: ${encoding.length(encoder)} bytes`) - t.compare(val, result, 'Compare results') -} - -const writeVarUint = (encoder, val) => encoding.writeVarUint(encoder, val) -const readVarUint = decoder => decoding.readVarUint(decoder) - -test('varUint 1 byte', async function varUint1 (t) { - testEncoding(t, writeVarUint, readVarUint, 42) -}) - -test('varUint 2 bytes', async function varUint2 (t) { - testEncoding(t, writeVarUint, readVarUint, 1 << 9 | 3) - testEncoding(t, writeVarUint, readVarUint, 1 << 9 | 3) -}) -test('varUint 3 bytes', async function varUint3 (t) { - testEncoding(t, writeVarUint, readVarUint, 1 << 17 | 1 << 9 | 3) -}) - -test('varUint 4 bytes', async function varUint4 (t) { - testEncoding(t, writeVarUint, readVarUint, 1 << 25 | 1 << 17 | 1 << 9 | 3) -}) - -test('varUint of 2839012934', async function varUint2839012934 (t) { - testEncoding(t, writeVarUint, readVarUint, 2839012934) -}) - -test('varUint random', async function varUintRandom (t) { - const prng = random.createPRNG(t.getSeed() * Math.pow(Number.MAX_SAFE_INTEGER, 2)) - testEncoding(t, writeVarUint, readVarUint, random.int32(prng, 0, (1 << 28) - 1)) -}) - -test('varUint random user id', async function varUintRandomUserId (t) { - t.getSeed() // enforces that this test is repeated - testEncoding(t, writeVarUint, readVarUint, generateRandomUint32()) -}) - -const writeVarString = (encoder, val) => encoding.writeVarString(encoder, val) -const readVarString = decoder => decoding.readVarString(decoder) - -test('varString', async function varString (t) { - testEncoding(t, writeVarString, readVarString, 'hello') - testEncoding(t, writeVarString, readVarString, 'test!') - testEncoding(t, writeVarString, readVarString, '☺☺☺') - testEncoding(t, writeVarString, readVarString, '1234') - testEncoding(t, writeVarString, readVarString, '쾟') - testEncoding(t, writeVarString, readVarString, '龟') // surrogate length 3 - testEncoding(t, writeVarString, readVarString, '😝') // surrogate length 4 -}) - -test('varString random', async function varStringRandom (t) { - const prng = random.createPRNG(t.getSeed() * 10000000) - testEncoding(t, writeVarString, readVarString, random.utf16String(prng)) -}) diff --git a/tests/helper.js b/tests/helper.js index 2db8397c..1d979da4 100644 --- a/tests/helper.js +++ b/tests/helper.js @@ -1,19 +1,19 @@ -import * as Y from '../index.js' -import { ItemJSON } from '../structs/ItemJSON.js' -import { ItemString } from '../structs/ItemString.js' -import { defragmentItemContent } from '../utils/defragmentItemContent.js' +import * as Y from '../src/index.js' +import { ItemJSON } from '../src/structs/ItemJSON.js' +import { ItemString } from '../src/structs/ItemString.js' +import { defragmentItemContent } from '../src/utils/defragmentItemContent.js' import Quill from 'quill' -import { GC } from '../structs/GC.js' -import * as random from '../lib/prng/prng.js' -import * as syncProtocol from '../protocols/sync.js' -import * as encoding from '../lib/encoding.js' -import * as decoding from '../lib/decoding.js' -import { createMutex } from '../lib/mutex.js' -import { QuillBinding } from '../bindings/quill.js' -import { DomBinding } from '../bindings/dom/DomBinding.js' +import { GC } from '../src/structs/GC.js' +import * as random from 'funlib/prng.js' +import * as syncProtocol from 'y-protocols/sync.js' +import * as encoding from 'funlib/encoding.js' +import * as decoding from 'funlib/decoding.js' +import { createMutex } from 'funlib/mutex.js' +import { QuillBinding } from 'y-quill' +import { DomBinding } from 'y-dom' -export * from '../index.js' +export * from '../src/index.js' /** * @param {TestYInstance} y diff --git a/tests/index.html b/tests/index.html index 5f423d4b..cf894761 100644 --- a/tests/index.html +++ b/tests/index.html @@ -3,6 +3,6 @@ - + diff --git a/tests/index.js b/tests/index.js index 8f30d9ae..84ce95e0 100644 --- a/tests/index.js +++ b/tests/index.js @@ -1,9 +1,10 @@ -// TODO: include all tests -import './red-black-tree.js' -import './y-array.tests.js' -import './y-text.tests.js' -import './y-map.tests.js' -import './y-xml.tests.js' -import './encode-decode.tests.js' -import './diff.tests.js' -import './prosemirror.test.js' + +import { runTests } from 'funlib/testing.js' +import * as deleteStore from './DeleteStore.tests.js' +import { isBrowser } from 'funlib/environment.js' +import * as log from 'funlib/logging.js' + +if (isBrowser) { + log.createVConsole(document.body) +} +runTests({ deleteStore }) diff --git a/tests/prosemirror.test.js b/tests/prosemirror.test.js deleted file mode 100644 index 7a005207..00000000 --- a/tests/prosemirror.test.js +++ /dev/null @@ -1,37 +0,0 @@ -import { test } from 'cutest' -import * as random from '../lib/prng/prng.js' -import * as Y from '../index.js' - -import { prosemirrorPlugin } from '../bindings/prosemirror.js' -import {EditorState} from 'prosemirror-state' -import {EditorView} from 'prosemirror-view' -import {schema} from 'prosemirror-schema-basic' -import {exampleSetup} from 'prosemirror-example-setup' - -const createNewProsemirrorView = y => { - const view = new EditorView(document.createElement('div'), { - state: EditorState.create({ - schema, - plugins: exampleSetup({schema}).concat([prosemirrorPlugin(y.define('prosemirror', Y.XmlFragment))]) - }) - }) - return view -} - -test('random prosemirror insertions', async t => { - const gen = random.createPRNG(t.getSeed()) - const y = new Y.Y() - const p1 = createNewProsemirrorView(y) - const p2 = createNewProsemirrorView(y) - for (let i = 0; i < 10; i++) { - const p = random.oneOf(gen, [p1, p2]) - const insertPos = random.int32(gen, 0, p.state.doc.content.size) - const overwrite = random.int32(gen, 0, p.state.doc.content.size - insertPos) - p.dispatch(p.state.tr.insertText('' + i, insertPos, insertPos + overwrite)) - } - t.compare( - p1.state.doc.toJSON(), - p2.state.doc.toJSON(), - 'compare prosemirror models' - ) -}) diff --git a/tests/red-black-tree.js b/tests/red-black-tree.js deleted file mode 100644 index 857550f0..00000000 --- a/tests/red-black-tree.js +++ /dev/null @@ -1,192 +0,0 @@ -import { Tree as RedBlackTree } from '../lib/Tree.js' -import * as ID from '../utils/ID.js' -import { test, proxyConsole } from 'cutest' -import * as random from '../lib/prng/prng.js' - -proxyConsole() - -var numberOfRBTreeTests = 10000 - -const checkRedNodesDoNotHaveBlackChildren = (t, tree) => { - let correct = true - const traverse = n => { - if (n == null) { - return - } - if (n.isRed()) { - if (n.left != null) { - correct = correct && !n.left.isRed() - } - if (n.right != null) { - correct = correct && !n.right.isRed() - } - } - traverse(n.left) - traverse(n.right) - } - traverse(tree.root) - t.assert(correct, 'Red nodes do not have black children') -} - -const checkBlackHeightOfSubTreesAreEqual = (t, tree) => { - let correct = true - const traverse = n => { - if (n == null) { - return 0 - } - var sub1 = traverse(n.left) - var sub2 = traverse(n.right) - if (sub1 !== sub2) { - correct = false - } - if (n.isRed()) { - return sub1 - } else { - return sub1 + 1 - } - } - traverse(tree.root) - t.assert(correct, 'Black-height of sub-trees are equal') -} - -const checkRootNodeIsBlack = (t, tree) => { - t.assert(tree.root == null || tree.root.isBlack(), 'root node is black') -} - -test('RedBlack Tree', async function redBlackTree (t) { - let tree = new RedBlackTree() - tree.put({_id: ID.createID(8433, 0)}) - tree.put({_id: ID.createID(12844, 0)}) - tree.put({_id: ID.createID(1795, 0)}) - tree.put({_id: ID.createID(30302, 0)}) - tree.put({_id: ID.createID(64287)}) - tree.delete(ID.createID(8433, 0)) - tree.put({_id: ID.createID(28996)}) - tree.delete(ID.createID(64287)) - tree.put({_id: ID.createID(22721)}) - checkRootNodeIsBlack(t, tree) - checkBlackHeightOfSubTreesAreEqual(t, tree) - checkRedNodesDoNotHaveBlackChildren(t, tree) -}) - -test(`random tests (${numberOfRBTreeTests})`, async function randomRBTree (t) { - let prng = random.createPRNG(t.getSeed() * 1000000000) - let tree = new RedBlackTree() - let elements = [] - for (var i = 0; i < numberOfRBTreeTests; i++) { - if (random.int32(prng, 0, 100) < 80) { - // 80% chance to insert an element - let obj = ID.createID(random.int32(prng, 0, numberOfRBTreeTests), random.int32(prng, 0, 1)) - let nodeExists = tree.find(obj) - if (nodeExists === null) { - if (elements.some(e => e.equals(obj))) { - t.assert(false, 'tree and elements contain different results') - } - elements.push(obj) - tree.put({_id: obj}) - } - } else if (elements.length > 0) { - // ~20% chance to delete an element - var elem = random.oneOf(prng, elements) - elements = elements.filter(e => { - return !e.equals(elem) - }) - tree.delete(elem) - } - } - checkRootNodeIsBlack(t, tree) - checkBlackHeightOfSubTreesAreEqual(t, tree) - checkRedNodesDoNotHaveBlackChildren(t, tree) - // TEST if all nodes exist - let allNodesExist = true - for (let id of elements) { - let node = tree.find(id) - if (!node._id.equals(id)) { - allNodesExist = false - } - } - t.assert(allNodesExist, 'All inserted nodes exist') - // TEST lower bound search - let findAllNodesWithLowerBoundSerach = true - for (let id of elements) { - let node = tree.findWithLowerBound(id) - if (!node._id.equals(id)) { - findAllNodesWithLowerBoundSerach = false - } - } - t.assert( - findAllNodesWithLowerBoundSerach, - 'Find every object with lower bound search' - ) - // TEST iteration (with lower bound search) - let lowerBound = random.oneOf(prng, elements) - let expectedResults = elements.filter((e, pos) => - (lowerBound.lessThan(e) || e.equals(lowerBound)) && - elements.indexOf(e) === pos - ).length - let actualResults = 0 - tree.iterate(lowerBound, null, val => { - if (val == null) { - t.assert(false, 'val is undefined!') - } - actualResults++ - }) - t.assert( - expectedResults === actualResults, - 'Iterating over a tree with lower bound yields the right amount of results' - ) - - expectedResults = elements.filter((e, pos) => - elements.indexOf(e) === pos - ).length - actualResults = 0 - tree.iterate(null, null, val => { - if (val == null) { - t.assert(false, 'val is undefined!') - } - actualResults++ - }) - t.assert( - expectedResults === actualResults, - 'iterating over a tree without bounds yields the right amount of results' - ) - - let upperBound = random.oneOf(prng, elements) - expectedResults = elements.filter((e, pos) => - (e.lessThan(upperBound) || e.equals(upperBound)) && - elements.indexOf(e) === pos - ).length - actualResults = 0 - tree.iterate(null, upperBound, val => { - if (val == null) { - t.assert(false, 'val is undefined!') - } - actualResults++ - }) - t.assert( - expectedResults === actualResults, - 'iterating over a tree with upper bound yields the right amount of results' - ) - - upperBound = random.oneOf(prng, elements) - lowerBound = random.oneOf(prng, elements) - if (upperBound.lessThan(lowerBound)) { - [lowerBound, upperBound] = [upperBound, lowerBound] - } - expectedResults = elements.filter((e, pos) => - (lowerBound.lessThan(e) || e.equals(lowerBound)) && - (e.lessThan(upperBound) || e.equals(upperBound)) && - elements.indexOf(e) === pos - ).length - actualResults = 0 - tree.iterate(lowerBound, upperBound, val => { - if (val == null) { - t.assert(false, 'val is undefined!') - } - actualResults++ - }) - t.assert( - expectedResults === actualResults, - 'iterating over a tree with upper bound yields the right amount of results' - ) -}) diff --git a/tests/y-array.tests.js b/tests/y-array.tests.js index b0a30a48..0dedbcaa 100644 --- a/tests/y-array.tests.js +++ b/tests/y-array.tests.js @@ -1,7 +1,7 @@ import { initArrays, compareUsers, applyRandomTests } from './helper.js' -import * as Y from '../index.js' +import * as Y from '../src/index.js' import { test, proxyConsole } from 'cutest' -import * as random from '../lib/prng/prng.js' +import * as random from 'funlib/prng/prng.js' proxyConsole() test('basic spec', async function array0 (t) { @@ -213,13 +213,13 @@ test('should correctly iterate an array containing types', async function iterat const y = new Y.Y() const arr = y.define('arr', Y.Array) const numItems = 10 - for(let i = 0; i < numItems; i++) { + for (let i = 0; i < numItems; i++) { const map = new Y.Map() map.set('value', i) arr.push([map]) } let cnt = 0 - for(let item of arr) { + for (let item of arr) { t.assert(item.get('value') === cnt++, 'value is correct') } y.destroy() diff --git a/tests/y-map.tests.js b/tests/y-map.tests.js index 3e9fefb5..805acb0d 100644 --- a/tests/y-map.tests.js +++ b/tests/y-map.tests.js @@ -1,7 +1,7 @@ import { initArrays, compareUsers, applyRandomTests } from './helper.js' -import * as Y from '../index.js' +import * as Y from '../src/index.js' import { test, proxyConsole } from 'cutest' -import * as random from '../lib/prng/prng.js' +import * as random from 'funlib/prng/prng.js' proxyConsole() diff --git a/tests/y-text.tests.js b/tests/y-text.tests.js index 118c8087..8a079318 100644 --- a/tests/y-text.tests.js +++ b/tests/y-text.tests.js @@ -1,7 +1,5 @@ import { initArrays, compareUsers } from './helper.js' -import { test, proxyConsole } from 'cutest' - -proxyConsole() +import { test } from 'cutest' test('basic insert delete', async function text0 (t) { let { users, text0 } = await initArrays(t, { users: 2 }) @@ -47,7 +45,7 @@ test('basic format', async function text1 (t) { t.assert(text0.toString() === 'b', 'Basic delete works (position 1)') t.compare(text0.toDelta(), [{ insert: 'b', attributes: { bold: true } }]) t.compare(delta, [{ retain: 1 }, { delete: 1 }]) - text0.insert(0, 'z', {bold: true}) + text0.insert(0, 'z', { bold: true }) t.assert(text0.toString() === 'zb') t.compare(text0.toDelta(), [{ insert: 'zb', attributes: { bold: true } }]) t.compare(delta, [{ insert: 'z', attributes: { bold: true } }]) diff --git a/tests/y-xml.tests.js b/tests/y-xml.tests.js index f963cd6a..f7e0a06b 100644 --- a/tests/y-xml.tests.js +++ b/tests/y-xml.tests.js @@ -1,7 +1,6 @@ -import { initArrays, compareUsers, applyRandomTests } from './helper.js' +import { initArrays, compareUsers } from './helper.js' import { test } from 'cutest' -import * as Y from '../index.js' -import * as random from '../lib/prng/prng.js' +import * as Y from '../src/index.js' test('set property', async function xml0 (t) { var { testConnector, users, xml0, xml1 } = await initArrays(t, { users: 2 }) @@ -60,30 +59,6 @@ test('attribute modifications (y -> dom)', async function xml2 (t) { await compareUsers(t, users) }) -test('attribute modifications (dom -> y)', async function xml3 (t) { - var { users, xml0, dom0, domBinding0 } = await initArrays(t, { users: 3 }) - dom0.setAttribute('height', '100px') - domBinding0.flushDomChanges() - t.assert(xml0.getAttribute('height') === '100px', 'setAttribute') - dom0.removeAttribute('height') - domBinding0.flushDomChanges() - t.assert(xml0.getAttribute('height') == null, 'removeAttribute') - dom0.setAttribute('class', 'stuffy stuff') - domBinding0.flushDomChanges() - t.assert(xml0.getAttribute('class') === 'stuffy stuff', 'set class attribute') - await compareUsers(t, users) -}) - -test('element insert (dom -> y)', async function xml4 (t) { - var { users, xml0, dom0, domBinding0 } = await initArrays(t, { users: 3 }) - dom0.insertBefore(document.createTextNode('some text'), null) - dom0.insertBefore(document.createElement('p'), null) - domBinding0.flushDomChanges() - t.assert(xml0.get(0).toString() === 'some text', 'Retrieve Text Node') - t.assert(xml0.get(1).nodeName === 'P', 'Retrieve Element node') - await compareUsers(t, users) -}) - test('element insert (y -> dom)', async function xml5 (t) { var { users, xml0, dom0 } = await initArrays(t, { users: 3 }) xml0.insert(0, [new Y.XmlText('some text')]) @@ -93,17 +68,6 @@ test('element insert (y -> dom)', async function xml5 (t) { await compareUsers(t, users) }) -test('y on insert, then delete (dom -> y)', async function xml6 (t) { - var { users, xml0, dom0, domBinding0 } = await initArrays(t, { users: 3 }) - dom0.insertBefore(document.createElement('p'), null) - domBinding0.flushDomChanges() - t.assert(xml0.length === 1, 'one node present') - dom0.childNodes[0].remove() - domBinding0.flushDomChanges() - t.assert(xml0.length === 0, 'no node present after delete') - await compareUsers(t, users) -}) - test('y on insert, then delete (y -> dom)', async function xml7 (t) { var { users, xml0, dom0 } = await initArrays(t, { users: 3 }) xml0.insert(0, [new Y.XmlElement('p')]) @@ -169,79 +133,6 @@ test('Receive a bunch of elements (with disconnect)', async function xml12 (t) { await compareUsers(t, users) }) -test('move element to a different position', async function xml13 (t) { - var { testConnector, users, dom0, dom1, domBinding0, domBinding1 } = await initArrays(t, { users: 3 }) - dom0.append(document.createElement('div')) - dom0.append(document.createElement('h1')) - domBinding0.flushDomChanges() - testConnector.flushAllMessages() - dom1.insertBefore(dom1.childNodes[0], null) - domBinding1.flushDomChanges() - t.assert(dom1.childNodes[0].nodeName === 'H1', 'div was deleted (user 0)') - t.assert(dom1.childNodes[1].nodeName === 'DIV', 'div was moved to the correct position (user 0)') - t.assert(dom1.childNodes[0].nodeName === 'H1', 'div was deleted (user 1)') - t.assert(dom1.childNodes[1].nodeName === 'DIV', 'div was moved to the correct position (user 1)') - await compareUsers(t, users) -}) - -test('filter node', async function xml14 (t) { - var { testConnector, users, dom0, dom1, domBinding0, domBinding1 } = await initArrays(t, { users: 3 }) - let domFilter = (nodeName, attrs) => { - if (nodeName === 'H1') { - return null - } else { - return attrs - } - } - domBinding0.setFilter(domFilter) - domBinding1.setFilter(domFilter) - dom0.append(document.createElement('div')) - dom0.append(document.createElement('h1')) - domBinding0.flushDomChanges() - testConnector.flushAllMessages() - t.assert(dom1.childNodes.length === 1, 'Only one node was not transmitted') - t.assert(dom1.childNodes[0].nodeName === 'DIV', 'div node was transmitted') - await compareUsers(t, users) -}) - -test('filter attribute', async function xml15 (t) { - var { testConnector, users, dom0, dom1, domBinding0, domBinding1 } = await initArrays(t, { users: 3 }) - let domFilter = (nodeName, attrs) => { - attrs.delete('hidden') - return attrs - } - domBinding0.setFilter(domFilter) - domBinding1.setFilter(domFilter) - dom0.setAttribute('hidden', 'true') - dom0.setAttribute('style', 'height: 30px') - dom0.setAttribute('data-me', '77') - domBinding0.flushDomChanges() - testConnector.flushAllMessages() - t.assert(dom0.getAttribute('hidden') === 'true', 'User 0 still has the attribute') - t.assert(dom1.getAttribute('hidden') == null, 'User 1 did not receive update') - t.assert(dom1.getAttribute('style') === 'height: 30px', 'User 1 received style update') - t.assert(dom1.getAttribute('data-me') === '77', 'User 1 received data update') - await compareUsers(t, users) -}) - -test('deep element insert', async function xml16 (t) { - var { testConnector, users, dom0, dom1, domBinding0 } = await initArrays(t, { users: 3 }) - let deepElement = document.createElement('p') - let boldElement = document.createElement('b') - let attrElement = document.createElement('img') - attrElement.setAttribute('src', 'http:localhost:8080/nowhere') - boldElement.append(document.createTextNode('hi')) - deepElement.append(boldElement) - deepElement.append(attrElement) - dom0.append(deepElement) - let str0 = dom0.outerHTML - domBinding0.flushDomChanges() - testConnector.flushAllMessages() - let str1 = dom1.outerHTML - t.compare(str0, str1, 'Dom string representation matches') - await compareUsers(t, users) -}) - test('treeWalker', async function xml17 (t) { var { users, xml0 } = await initArrays(t, { users: 3 }) let paragraph1 = new Y.XmlElement('p') @@ -257,160 +148,3 @@ test('treeWalker', async function xml17 (t) { t.assert(xml0.querySelector('p') === paragraph1, 'querySelector found paragraph1') await compareUsers(t, users) }) - -/** - * The expected behavior is that changes on your own dom (e.g. malicious attributes) persist. - * Yjs should just ignore them, never propagate those attributes. - * Incoming changes that contain malicious attributes should be deleted. - */ -test('Filtering remote changes', async function xmlFilteringRemote (t) { - var { testConnector, users, xml0, xml1, domBinding0, domBinding1 } = await initArrays(t, { users: 3 }) - const filter = (nodeName, attributes) => { - attributes.delete('malicious') - if (nodeName === 'HIDEME') { - return null - } else if (attributes.has('isHidden')) { - return null - } else { - return attributes - } - } - domBinding0.setFilter(filter) - domBinding1.setFilter(filter) - let paragraph = new Y.XmlElement('p') - let hideMe = new Y.XmlElement('hideMe') - let span = new Y.XmlElement('span') - span.setAttribute('malicious', 'alert("give me money")') - let tag = new Y.XmlElement('tag') - tag.setAttribute('isHidden', 'true') - paragraph.insert(0, [hideMe, span, tag]) - xml0.insert(0, [paragraph]) - let tag2 = new Y.XmlElement('tag') - tag2.setAttribute('isHidden', 'true') - paragraph.insert(0, [tag2]) - domBinding0.flushDomChanges() - testConnector.flushAllMessages() - // check dom - domBinding0.typeToDom.get(paragraph).setAttribute('malicious', 'true') - domBinding0.typeToDom.get(span).setAttribute('malicious', 'true') - domBinding0.flushDomChanges() - // check incoming attributes - xml1.get(0).get(0).setAttribute('malicious', 'true') - xml1.insert(0, [new Y.XmlElement('hideMe')]) - domBinding0.flushDomChanges() - testConnector.flushAllMessages() - - await compareUsers(t, users) -}) - -// TODO: move elements -var xmlTransactions = [ - function attributeChange (t, user, prng) { - // random.word generates non-empty words. prepend something - user.dom.setAttribute('_' + random.word(prng), random.word(prng)) - user.domBinding.flushDomChanges() - }, - function attributeChangeHidden (t, user, prng) { - user.dom.setAttribute('hidden', random.word(prng)) - user.domBinding.flushDomChanges() - }, - function insertText (t, user, prng) { - let dom = user.dom - var succ = dom.children.length > 0 ? random.oneOf(prng, dom.children) : null - dom.insertBefore(document.createTextNode(random.word(prng)), succ) - user.domBinding.flushDomChanges() - }, - function insertHiddenDom (t, user, prng) { - let dom = user.dom - var succ = dom.children.length > 0 ? random.oneOf(prng, dom.children) : null - dom.insertBefore(document.createElement('hidden'), succ) - user.domBinding.flushDomChanges() - }, - function insertDom (t, user, prng) { - let dom = user.dom - var succ = dom.children.length > 0 ? random.oneOf(prng, dom.children) : null - dom.insertBefore(document.createElement('my-' + random.word(prng)), succ) - user.domBinding.flushDomChanges() - }, - function deleteChild (t, user, prng) { - let dom = user.dom - if (dom.childNodes.length > 0) { - var d = random.oneOf(prng, dom.childNodes) - d.remove() - user.domBinding.flushDomChanges() - } - }, - function insertTextSecondLayer (t, user, prng) { - let dom = user.dom - if (dom.children.length > 0) { - let dom2 = random.oneOf(prng, dom.children) - let succ = dom2.childNodes.length > 0 ? random.oneOf(prng, dom2.childNodes) : null - dom2.insertBefore(document.createTextNode(random.word(prng)), succ) - user.domBinding.flushDomChanges() - } - }, - function insertDomSecondLayer (t, user, prng) { - let dom = user.dom - if (dom.children.length > 0) { - let dom2 = random.oneOf(prng, dom.children) - let succ = dom2.childNodes.length > 0 ? random.oneOf(prng, dom2.childNodes) : null - dom2.insertBefore(document.createElement('my-' + random.word(prng)), succ) - user.domBinding.flushDomChanges() - } - }, - function deleteChildSecondLayer (t, user, prng) { - let dom = user.dom - if (dom.children.length > 0) { - let dom2 = random.oneOf(prng, dom.children) - if (dom2.childNodes.length > 0) { - let d = random.oneOf(prng, dom2.childNodes) - d.remove() - } - user.domBinding.flushDomChanges() - } - } -] - -test('y-xml: Random tests (10)', async function xmlRandom10 (t) { - await applyRandomTests(t, xmlTransactions, 10) -}) - -test('y-xml: Random tests (42)', async function xmlRandom42 (t) { - await applyRandomTests(t, xmlTransactions, 42) -}) - -test('y-xml: Random tests (43)', async function xmlRandom43 (t) { - await applyRandomTests(t, xmlTransactions, 43) -}) - -test('y-xml: Random tests (44)', async function xmlRandom44 (t) { - await applyRandomTests(t, xmlTransactions, 44) -}) - -test('y-xml: Random tests (45)', async function xmlRandom45 (t) { - await applyRandomTests(t, xmlTransactions, 45) -}) - -test('y-xml: Random tests (46)', async function xmlRandom46 (t) { - await applyRandomTests(t, xmlTransactions, 46) -}) - -test('y-xml: Random tests (47)', async function xmlRandom47 (t) { - await applyRandomTests(t, xmlTransactions, 47) -}) - -test('y-xml: Random tests (100)', async function xmlRandom100 (t) { - await applyRandomTests(t, xmlTransactions, 100) -}) - -test('y-xml: Random tests (200)', async function xmlRandom200 (t) { - await applyRandomTests(t, xmlTransactions, 200) -}) - -test('y-xml: Random tests (500)', async function xmlRandom500 (t) { - await applyRandomTests(t, xmlTransactions, 500) -}) - -test('y-xml: Random tests (1000)', async function xmlRandom1000 (t) { - await applyRandomTests(t, xmlTransactions, 1000) -}) diff --git a/tsconfig.json b/tsconfig.json index f16a4aed..98c6f8af 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -54,5 +54,6 @@ /* Experimental Options */ // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + "maxNodeModuleJsDepth": 5 } } diff --git a/types/YXmlTreeWalker.js b/types/YXmlTreeWalker.js deleted file mode 100644 index 605266c7..00000000 --- a/types/YXmlTreeWalker.js +++ /dev/null @@ -1,80 +0,0 @@ -/** - * @module types - */ - -import { YXmlElement, YXmlFragment } from './YXmlElement.js' // eslint-disable-line - -/** - * Define the elements to which a set of CSS queries apply. - * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors|CSS_Selectors} - * - * @example - * query = '.classSelector' - * query = 'nodeSelector' - * query = '#idSelector' - * - * @typedef {string} CSS_Selector - */ - -/** - * Represents a subset of the nodes of a YXmlElement / YXmlFragment and a - * position within them. - * - * Can be created with {@link YXmlFragment#createTreeWalker} - * - * @public - */ -export class YXmlTreeWalker { - constructor (root, f) { - this._filter = f || (() => true) - this._root = root - this._currentNode = root - this._firstCall = true - } - [Symbol.iterator] () { - return this - } - /** - * Get the next node. - * - * @return {YXmlElement} The next node. - * - * @public - */ - next () { - let n = this._currentNode - if (this._firstCall) { - this._firstCall = false - if (!n._deleted && this._filter(n)) { - return { value: n, done: false } - } - } - do { - if (!n._deleted && (n.constructor === YXmlElement || n.constructor === YXmlFragment) && n._start !== null) { - // walk down in the tree - n = n._start - } else { - // walk right or up in the tree - while (n !== this._root) { - if (n._right !== null) { - n = n._right - break - } - n = n._parent - } - if (n === this._root) { - n = null - } - } - if (n === this._root) { - break - } - } while (n !== null && (n._deleted || !this._filter(n))) - this._currentNode = n - if (n === null) { - return { done: true } - } else { - return { value: n, done: false } - } - } -} diff --git a/utils/DeleteStore.js b/utils/DeleteStore.js deleted file mode 100644 index 83fa657b..00000000 --- a/utils/DeleteStore.js +++ /dev/null @@ -1,256 +0,0 @@ -/** - * @module utils - */ - -import { Tree } from '../lib/Tree.js' -import * as ID from './ID.js' - -import * as encoding from '../lib/encoding.js' -import * as decoding from '../lib/decoding.js' -import { deleteItemRange } from '../utils/structManipulation.js' - -class DSNode { - constructor (id, len, gc) { - this._id = id - this.len = len - this.gc = gc - } - clone () { - return new DSNode(this._id, this.len, this.gc) - } -} - -export class DeleteStore extends Tree { - logTable () { - const deletes = [] - this.iterate(null, null, n => { - deletes.push({ - user: n._id.user, - clock: n._id.clock, - len: n.len, - gc: n.gc - }) - }) - console.table(deletes) - } - isDeleted (id) { - var n = this.findWithUpperBound(id) - return n !== null && n._id.user === id.user && id.clock < n._id.clock + n.len - } - mark (id, length, gc) { - if (length === 0) return - // Step 1. Unmark range - const leftD = this.findWithUpperBound(ID.createID(id.user, id.clock - 1)) - // Resize left DSNode if necessary - if (leftD !== null && leftD._id.user === id.user) { - if (leftD._id.clock < id.clock && id.clock < leftD._id.clock + leftD.len) { - // node is overlapping. need to resize - if (id.clock + length < leftD._id.clock + leftD.len) { - // overlaps new mark range and some more - // create another DSNode to the right of new mark - this.put(new DSNode(ID.createID(id.user, id.clock + length), leftD._id.clock + leftD.len - id.clock - length, leftD.gc)) - } - // resize left DSNode - leftD.len = id.clock - leftD._id.clock - } // Otherwise there is no overlapping - } - // Resize right DSNode if necessary - const upper = ID.createID(id.user, id.clock + length - 1) - const rightD = this.findWithUpperBound(upper) - if (rightD !== null && rightD._id.user === id.user) { - if (rightD._id.clock < id.clock + length && id.clock <= rightD._id.clock && id.clock + length < rightD._id.clock + rightD.len) { // we only consider the case where we resize the node - const d = id.clock + length - rightD._id.clock - rightD._id = ID.createID(rightD._id.user, rightD._id.clock + d) - rightD.len -= d - } - } - // Now we only have to delete all inner marks - const deleteNodeIds = [] - this.iterate(id, upper, m => { - deleteNodeIds.push(m._id) - }) - for (let i = deleteNodeIds.length - 1; i >= 0; i--) { - this.delete(deleteNodeIds[i]) - } - let newMark = new DSNode(id, length, gc) - // Step 2. Check if we can extend left or right - if (leftD !== null && leftD._id.user === id.user && leftD._id.clock + leftD.len === id.clock && leftD.gc === gc) { - // We can extend left - leftD.len += length - newMark = leftD - } - const rightNext = this.find(ID.createID(id.user, id.clock + length)) - if (rightNext !== null && rightNext._id.user === id.user && id.clock + length === rightNext._id.clock && gc === rightNext.gc) { - // We can merge newMark and rightNext - newMark.len += rightNext.len - this.delete(rightNext._id) - } - if (leftD !== newMark) { - // only put if we didn't extend left - this.put(newMark) - } - } -} - -/** - * Stringifies a message-encoded Delete Set. - * - * @param {decoding.Decoder} decoder - * @return {string} - */ -export const stringifyDeleteStore = (decoder) => { - let str = '' - const dsLength = decoding.readUint32(decoder) - for (let i = 0; i < dsLength; i++) { - str += ' -' + decoding.readVarUint(decoder) + ':\n' // decodes user - const dvLength = decoding.readUint32(decoder) - for (let j = 0; j < dvLength; j++) { - str += `clock: ${decoding.readVarUint(decoder)}, length: ${decoding.readVarUint(decoder)}, gc: ${decoding.readUint8(decoder) === 1}\n` - } - } - return str -} - -/** - * Write the DeleteSet of a shared document to an Encoder. - * - * @param {encoding.Encoder} encoder - * @param {DeleteStore} ds - */ -export const writeDeleteStore = (encoder, ds) => { - let currentUser = null - let currentLength - let lastLenPos - let numberOfUsers = 0 - const laterDSLenPus = encoding.length(encoder) - encoding.writeUint32(encoder, 0) - ds.iterate(null, null, n => { - const user = n._id.user - const clock = n._id.clock - const len = n.len - const gc = n.gc - if (currentUser !== user) { - numberOfUsers++ - // a new user was found - if (currentUser !== null) { // happens on first iteration - encoding.setUint32(encoder, lastLenPos, currentLength) - } - currentUser = user - encoding.writeVarUint(encoder, user) - // pseudo-fill pos - lastLenPos = encoding.length(encoder) - encoding.writeUint32(encoder, 0) - currentLength = 0 - } - encoding.writeVarUint(encoder, clock) - encoding.writeVarUint(encoder, len) - encoding.writeUint8(encoder, gc ? 1 : 0) - currentLength++ - }) - if (currentUser !== null) { // happens on first iteration - encoding.setUint32(encoder, lastLenPos, currentLength) - } - encoding.setUint32(encoder, laterDSLenPus, numberOfUsers) -} - -/** - * Read delete store from Decoder and create a fresh DeleteStore - * - * @param {decoding.Decoder} decoder - * @return {DeleteStore} - */ -export const readFreshDeleteStore = decoder => { - const ds = new DeleteStore() - const dsLength = decoding.readUint32(decoder) - for (let i = 0; i < dsLength; i++) { - const user = decoding.readVarUint(decoder) - const dvLength = decoding.readUint32(decoder) - for (let j = 0; j < dvLength; j++) { - const from = decoding.readVarUint(decoder) - const len = decoding.readVarUint(decoder) - const gc = decoding.readUint8(decoder) - ds.put(new DSNode(ID.createID(user, from), len, gc)) - } - } - return ds -} - -/** - * Read delete set from Decoder and apply it to a shared document. - * - * @param {decoding.Decoder} decoder - * @param {Y} y - */ -export const readDeleteStore = (decoder, y) => { - const dsLength = decoding.readUint32(decoder) - for (let i = 0; i < dsLength; i++) { - const user = decoding.readVarUint(decoder) - const dv = [] - const dvLength = decoding.readUint32(decoder) - for (let j = 0; j < dvLength; j++) { - const from = decoding.readVarUint(decoder) - const len = decoding.readVarUint(decoder) - const gc = decoding.readUint8(decoder) === 1 - dv.push({from, len, gc}) - } - if (dvLength > 0) { - const deletions = [] - let pos = 0 - let d = dv[pos] - y.ds.iterate(ID.createID(user, 0), ID.createID(user, Number.MAX_VALUE), n => { - // cases: - // 1. d deletes something to the right of n - // => go to next n (break) - // 2. d deletes something to the left of n - // => create deletions - // => reset d accordingly - // *)=> if d doesn't delete anything anymore, go to next d (continue) - // 3. not 2) and d deletes something that also n deletes - // => reset d so that it doesn't contain n's deletion - // *)=> if d does not delete anything anymore, go to next d (continue) - while (d != null) { - var diff = 0 // describe the diff of length in 1) and 2) - if (n._id.clock + n.len <= d.from) { - // 1) - break - } else if (d.from < n._id.clock) { - // 2) - // delete maximum the len of d - // else delete as much as possible - diff = Math.min(n._id.clock - d.from, d.len) - // deleteItemRange(y, user, d.from, diff, true) - deletions.push([user, d.from, diff]) - } else { - // 3) - diff = n._id.clock + n.len - d.from // never null (see 1) - if (d.gc && !n.gc) { - // d marks as gc'd but n does not - // then delete either way - // deleteItemRange(y, user, d.from, Math.min(diff, d.len), true) - deletions.push([user, d.from, Math.min(diff, d.len)]) - } - } - if (d.len <= diff) { - // d doesn't delete anything anymore - d = dv[++pos] - } else { - d.from = d.from + diff // reset pos - d.len = d.len - diff // reset length - } - } - }) - // TODO: It would be more performant to apply the deletes in the above loop - // Adapt the Tree implementation to support delete while iterating - for (let i = deletions.length - 1; i >= 0; i--) { - const del = deletions[i] - deleteItemRange(y, del[0], del[1], del[2], true) - } - // for the rest.. just apply it - for (; pos < dv.length; pos++) { - d = dv[pos] - deleteItemRange(y, user, d.from, d.len, true) - // deletions.push([user, d.from, d.len, d.gc) - } - } - } -} diff --git a/utils/StateStore.js b/utils/StateStore.js deleted file mode 100644 index 2c201f6f..00000000 --- a/utils/StateStore.js +++ /dev/null @@ -1,111 +0,0 @@ -/** - * @module utils - */ - -import * as ID from '../utils/ID.js' - -import * as encoding from '../lib/encoding.js' -import * as decoding from '../lib/decoding.js' - -const writeStateStore = (encoder, ss) => { - -} - -/** - * @typedef {Map} StateMap - */ - -/** - * Read StateMap from Decoder and return as Map - * - * @param {decoding.Decoder} decoder - * @return {StateMap} - */ -export const readStateMap = decoder => { - const ss = new Map() - const ssLength = decoding.readUint32(decoder) - for (let i = 0; i < ssLength; i++) { - const user = decoding.readVarUint(decoder) - const clock = decoding.readVarUint(decoder) - ss.set(user, clock) - } - return ss -} - -/** - * Write StateMap to Encoder - * - * @param {encoding.Encoder} encoder - * @param {StateMap} state - */ -export const writeStateMap = (encoder, state) => { - // write as fixed-size number to stay consistent with the other encode functions. - // => anytime we write the number of objects that follow, encode as fixed-size number. - encoding.writeUint32(encoder, state.size) - state.forEach((clock, user) => { - encoding.writeVarUint(encoder, user) - encoding.writeVarUint(encoder, clock) - }) -} - -/** - * Read a StateMap from Decoder and return it as string. - * - * @param {decoding.Decoder} decoder - * @return {string} - */ -export const stringifyStateMap = decoder => { - let s = 'State Set: ' - readStateMap(decoder).forEach((clock, user) => { - s += `(${user}: ${clock}), ` - }) - return s -} - -/** - */ -export class StateStore { - constructor (y) { - this.y = y - this.state = new Map() - } - logTable () { - const entries = [] - for (let [user, state] of this.state) { - entries.push({ - user, state - }) - } - console.table(entries) - } - getNextID (len) { - const user = this.y.userID - const state = this.getState(user) - this.setState(user, state + len) - return ID.createID(user, state) - } - updateRemoteState (struct) { - let user = struct._id.user - let userState = this.state.get(user) - while (struct !== null && struct._id.clock === userState) { - userState += struct._length - struct = this.y.os.get(ID.createID(user, userState)) - } - this.state.set(user, userState) - } - getState (user) { - let state = this.state.get(user) - if (state == null) { - return 0 - } - return state - } - setState (user, state) { - // TODO: modify missingi structs here - const beforeState = this.y._transaction.beforeState - if (!beforeState.has(user)) { - beforeState.set(user, this.getState(user)) - } - this.state.set(user, state) - } -} diff --git a/utils/generateRandomUint32.js b/utils/generateRandomUint32.js deleted file mode 100644 index 8d007c15..00000000 --- a/utils/generateRandomUint32.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * @module utils - */ - -/* global crypto */ - -export const generateRandomUint32 = () => { - if (typeof crypto !== 'undefined' && crypto.getRandomValues != null) { - // browser - let arr = new Uint32Array(1) - crypto.getRandomValues(arr) - return arr[0] - } else if (typeof crypto !== 'undefined' && crypto.randomBytes != null) { - // node - let buf = crypto.randomBytes(4) - return new Uint32Array(buf.buffer)[0] - } else { - return Math.ceil(Math.random() * 0xFFFFFFFF) - } -} diff --git a/utils/structReferences.js b/utils/structReferences.js deleted file mode 100644 index fce9453a..00000000 --- a/utils/structReferences.js +++ /dev/null @@ -1,34 +0,0 @@ -/** - * @module utils - */ - -const structs = new Map() -const references = new Map() - -/** - * Register a new Yjs types. The same type must be defined with the same - * reference on all clients! - * - * @param {Number} reference - * @param {Function} structConstructor - * - * @public - */ -export const registerStruct = (reference, structConstructor) => { - structs.set(reference, structConstructor) - references.set(structConstructor, reference) -} - -/** - * @private - */ -export const getStruct = (reference) => { - return structs.get(reference) -} - -/** - * @private - */ -export const getStructReference = (typeConstructor) => { - return references.get(typeConstructor) -} diff --git a/utils/structStringify.js b/utils/structStringify.js deleted file mode 100644 index 9747f889..00000000 --- a/utils/structStringify.js +++ /dev/null @@ -1,46 +0,0 @@ - -import * as ID from './ID.js' - -/** - * Stringify an item id. - * - * @param {ID.ID | ID.RootID} id - * @return {string} - */ -export const stringifyID = id => id instanceof ID.ID ? `(${id.user},${id.clock})` : `(${id.name},${id.type})` - -/** - * Stringify an item as ID. HHere, an item could also be a Yjs instance (e.g. item._parent). - * - * @param {Item | Y | null} item - * @return {string} - */ -export const stringifyItemID = item => { - let result - if (item === null) { - result = '()' - } else if (item._id != null) { - result = stringifyID(item._id) - } else { - // must be a Yjs instance - // Don't include Y in this module, so we prevent circular dependencies. - result = 'y' - } - return result -} - -/** - * Helper utility to convert an item to a readable format. - * - * @param {String} name The name of the item class (YText, ItemString, ..). - * @param {Item} item The item instance. - * @param {String} [append] Additional information to append to the returned - * string. - * @return {String} A readable string that represents the item object. - * - */ -export const logItemHelper = (name, item, append) => { - const left = item._left !== null ? stringifyID(item._left._lastId) : '()' - const origin = item._origin !== null ? stringifyID(item._origin._lastId) : '()' - return `${name}(id:${stringifyItemID(item)},left:${left},origin:${origin},right:${stringifyItemID(item._right)},parent:${stringifyItemID(item._parent)},parentSub:${item._parentSub}${append !== undefined ? ' - ' + append : ''})` -}