From 8a0709b1bfbfef0c10e292f17f5268daac258b72 Mon Sep 17 00:00:00 2001 From: Curran Date: Fri, 3 Mar 2023 23:14:29 -0500 Subject: [PATCH 1/5] Add source for JSON1 with presence --- src/json1Presence/cursor.ts | 467 +++++ src/json1Presence/deepClone.ts | 16 + src/json1Presence/deepEqual.ts | 33 + src/json1Presence/index.ts | 7 + src/json1Presence/json1.release.ts | 4 + src/json1Presence/json1.ts | 2967 ++++++++++++++++++++++++++++ src/json1Presence/log.ts | 17 + src/json1Presence/types.ts | 84 + 8 files changed, 3595 insertions(+) create mode 100644 src/json1Presence/cursor.ts create mode 100644 src/json1Presence/deepClone.ts create mode 100644 src/json1Presence/deepEqual.ts create mode 100644 src/json1Presence/index.ts create mode 100644 src/json1Presence/json1.release.ts create mode 100644 src/json1Presence/json1.ts create mode 100644 src/json1Presence/log.ts create mode 100644 src/json1Presence/types.ts diff --git a/src/json1Presence/cursor.ts b/src/json1Presence/cursor.ts new file mode 100644 index 00000000..0f8cd46b --- /dev/null +++ b/src/json1Presence/cursor.ts @@ -0,0 +1,467 @@ +// This file exposes a cursor interface for iterating over an operation. +// +// The cursor interface supports both reading and writing (appending). + +import {Key, JSONOp, JSONOpComponent, JSONOpList, Path} from './types.js' + +// const log = require('./log') +const log = (...args: any) => {} +function assert(pred: boolean, msg?: string): asserts pred {if (!pred) throw new Error(msg)} + +const isObject = (o: any) => (o != null && typeof o === 'object' && !Array.isArray(o)) + +const isGreaterKey = (a: Key, b: Key) => ( + // All the numbers, then all the letters. Just as the gods of ascii intended. + (typeof a === typeof b) ? + a > b : typeof a === 'string' && typeof b === 'number' +) + +function copyAll(c: JSONOpComponent, w: WriteCursor) { + for (let _k in c) { + const k = _k as keyof JSONOpComponent // bleh typescript. + w.write(k, c[k]) + } +} + +// It might be dangerous to allow __proto__ as a key path because of +// javascript's prototype semantics, especially because most apps won't +// check operations thoroughly enough. +export const isValidPathItem = (k: Key) => ( + typeof k === 'number' || (typeof k === 'string' && k !== '__proto__') +) + +class Cursor { + // Each time we descend we store the parent and the index of the child. The + // indexes list is appended twice each time we descend - once with the last + // key index of the parent, and once with the index of the array we're + // descending down. + parents: JSONOp[] = [] + indexes: number[] = [] + + // The index of the last listy child we visited for write cursors. This is used + // to ensure we are traversing the op in order. + lcIdx = -1 + + // The way we handle the root of the op is a big hack - its like that because + // the root of an op is different from the rest; it can contain a component + // immediately. + // Except at the root (which could be null), container will always be a list. + container: JSONOp + idx = -1 + + constructor(op: JSONOp = null) { + this.container = op + } + + ascend() { + assert(this.parents.length === this.indexes.length / 2) + + if (this.idx === 0) { + if (this.parents.length) { + this.lcIdx = this.indexes.pop()! + this.container = this.parents.pop()! + this.idx = this.indexes.pop()! + } else { + this.lcIdx = 0 + this.idx = -1 + } + } else { + assert(this.idx > 0) + this.idx-- + if (isObject(this.container![this.idx])) this.idx-- + } + } + + getPath() { + const path: Path = [] + let c = this.container + let p = this.parents.length - 1 + let i = this.idx + while (i >= 0) { + path.unshift(c![i] as Key) + if (i === 0) { + i = this.indexes[p*2] + c = this.parents[p--] + } else { + i -= isObject(c![i-1]) ? 2 : 1 + } + } + return path + } +} + +export class ReadCursor extends Cursor { + get() { return this.container ? this.container.slice(this.idx + 1) : null } + + // Its only valid to call this after descending into a child. + getKey() { + assert(this.container != null, 'Invalid call to getKey before cursor descended') + return this.container[this.idx] as Key + } + + getComponent() { + let c + if (this.container && this.container.length > this.idx + 1 && isObject((c = this.container[this.idx + 1]))) { + return c as JSONOpComponent + } else { + return null + } + } + + descendFirst() { + let i = this.idx + 1 + + // Only valid to call this if hasChildren() returns true. + if (!this.container + || i >= this.container.length + || (isObject(this.container[i]) && (i + 1) >= this.container.length)) { + return false + } + + if (isObject(this.container[i])) i++ // Skip component + const firstChild = this.container[i] + + if (Array.isArray(firstChild)) { + this.indexes.push(this.idx) + this.parents.push(this.container) + this.indexes.push(i) + this.idx = 0 + this.container = firstChild + } else { + this.idx = i + } + return true + } + + nextSibling() { + assert(this.parents.length === this.indexes.length / 2) + // Children are inline or we're at the root + if (this.idx > 0 || this.parents.length === 0) return false + + const i = this.indexes[this.indexes.length - 1] + 1 + const c = this.parents[this.parents.length - 1]! + if (i >= c.length) return false // Past the end of the listy children. + + assert(!isNaN(i)) + this.indexes[this.indexes.length - 1] = i + this.container = c[i] as JSONOpList + // idx = 0 but it should be zero anyway. + return true + } + + private _init(_container: JSONOp, _idx: number, _parents: JSONOp[], _indexes: number[]) { // Internal for clone + // parents.length = indexes.length = 0 + this.container = _container + this.idx = _idx + + // Only need these for getPath() + this.parents = _parents.slice() + this.indexes = _indexes.slice() + } + + clone() { + const c = new ReadCursor() + c._init(this.container, this.idx, this.parents, this.indexes) + return c + } + + *[Symbol.iterator]() { + // Iterate through the child keys. At each key the cursor will be + // pointing at the child the key represents. + if (!this.descendFirst()) return + + do yield this.getKey() + while (this.nextSibling()) + + this.ascend() + } + + + // TODO(cleanup): Consider moving these functions out of cursor, since + // they're really just helper methods. + + // It'd be really nice to do this using generators. + traverse(w: W, fn: (c: JSONOpComponent, w: W) => void) { + const c = this.getComponent() + if (c) fn(c, w) + + for (const key of this) { + if (w) w.descend(key) + this.traverse(w, fn) + if (w) w.ascend() + } + } + + eachPick(w: W, fn: (slot: number, w: W) => void) { + this.traverse(w, (c, w) => { if (c.p != null) fn(c.p, w) }) + } + eachDrop(w: W, fn: (slot: number, w: W) => void) { + this.traverse(w, (c, w) => { if (c.d != null) fn(c.d, w) }) + } +} + +export class WriteCursor extends Cursor { + pendingDescent: Path = [] + + private _op: JSONOp // The op we're writing to. Returned via .get() + + constructor(op: JSONOp = null) { + super(op) + this._op = op + } + + private flushDescent() { + // After flushDescent is called, container will always be non-null. + assert(this.parents.length === this.indexes.length / 2) + + if (this.container === null) this._op = this.container = [] + + for (let j = 0; j < this.pendingDescent.length; j++) { + const k = this.pendingDescent[j] + // log('fd', k) + let i = this.idx + 1 + + // Skip the component here, if any. + if (i < this.container.length && isObject(this.container[i])) i++ + + assert(i === this.container.length || !isObject(this.container[i])) + + if (i === this.container.length) { + // Just append a child here. + this.container.push(k) + this.idx = i + + } else if (this.container[i] === k) { + // Traverse right... + this.idx = i + + } else { + if (!Array.isArray(this.container[i])) { + // Its not an array. Its not the same as me. I must have a new child! + // log('bundle inside', i, lcIdx) + const oldChild = this.container.splice(i, this.container.length - i) + this.container.push(oldChild) + if (this.lcIdx > -1) this.lcIdx = i + } + + this.indexes.push(this.idx) + this.parents.push(this.container) + + if (this.lcIdx !== -1) { + assert(isGreaterKey(k, (this.container[this.lcIdx] as JSONOpList)[0] as Key)) + i = this.lcIdx + 1 + this.lcIdx = -1 + } + + // Skip until k. + while (i < this.container.length && isGreaterKey(k, (this.container[i] as JSONOpList)[0] as Key)) i++ + + this.indexes.push(i) + this.idx = 0 + if (i < this.container.length && (this.container[i] as JSONOpList)[0] === k) { + this.container = this.container[i] as JSONOpList + } else { + // Insert a new child and descend into it. + const child = [k] + this.container.splice(i, 0, child) + // log('-> container', container) + this.container = child as JSONOpList + } + } + } + this.pendingDescent.length = 0 + } + + reset() { this.lcIdx = -1 } + + // Creates and returns a component, creating one if need be. You should + // probably write to it immediately - ops are not valid with empty + // components. + getComponent(): JSONOpComponent { + this.flushDescent() + + const i = this.idx + 1 + if (i < this.container!.length && isObject(this.container![i])) { + return this.container![i] as JSONOpComponent + } else { + const component = {} + this.container!.splice(i, 0, component) + return component + } + } + + write(key: K, value: JSONOpComponent[K]) { + const component = this.getComponent() + assert(component[key] == null || component[key] === value, + 'Internal consistency error: Overwritten component. File a bug') + component[key] = value + } + + get() { return this._op } + + descend(key: Key) { + if (!isValidPathItem(key)) throw Error('Invalid JSON key') + // log('descend', key) + this.pendingDescent.push(key) + } + + descendPath(path: Path) { + this.pendingDescent.push(...path) + return this + } + + ascend() { + if (this.pendingDescent.length) this.pendingDescent.pop() + else super.ascend() + } + + mergeTree(data: JSONOp, mergeFn = copyAll) { + if (data === null) return + assert(Array.isArray(data)) + if (data === this._op) throw Error('Cannot merge into my own tree') + + // Backup our position... + const _lcIdx = this.lcIdx + const oldDepth = this.parents.length + + let depth = 0 + for (let i = 0; i < data.length; i++) { + const c = data[i] + if (typeof c === 'string' || typeof c === 'number') { + depth++ + this.descend(c) + } else if (Array.isArray(c)) { + this.mergeTree(c, mergeFn) + } else if (typeof c === 'object') { + mergeFn(c, this) + } + } + + // And reset. + while (depth--) this.ascend() + + // We might have had pending descents that we've flushed out. Reset + // to the start of the descent. + this.lcIdx = (this.parents.length === oldDepth) ? _lcIdx : -1 + } + + at(path: Path, fn: (w: WriteCursor) => void) { + this.descendPath(path) + fn(this) + for (let i = 0; i < path.length; i++) this.ascend() + return this + } + + // This is used by helpers, so the strict ordering guarantees are + // relaxed. + writeAtPath(path: Path, key: K, value: JSONOpComponent[K]) { + this.at(path, () => this.write(key, value)) + + // Allows for multiple writeAtPath calls to relax ordering. + this.reset() + return this + } + + writeMove(path1: Path, path2: Path, slot = 0) { + return this + .writeAtPath(path1, 'p', slot) + .writeAtPath(path2, 'd', slot) + } + + getPath() { + const path = super.getPath() + path.push(...this.pendingDescent) + return path + } +} + +// ReadCursor / WriteCursor didn't used to be exported. These are old helpers for them. +// TODO: Remove in 1.0 +export const writeCursor = () => (new WriteCursor()) +export const readCursor = (op: JSONOp) => new ReadCursor(op) + +// onSkipped is called for any children of r which are never returned by adv. +export function advancer(r: ReadCursor | null, + listMap?: (k: number, c: JSONOpComponent | null) => number, + listAdv?: (k: number, c: JSONOpComponent | null) => void) { + let didDescend: boolean, valid: boolean + // Could use r.children() here instead... though this is might be simpler. + valid = didDescend = r ? r.descendFirst() : false + + // Advance to key k. If k is null, advance all the way to the end. + // let last = undefined + function adv(ktarget: Key) { + // if (last === ktarget) throw Error('duplicate descent') + // last = ktarget + let k2_ + while (valid) { + const k2 = k2_ = r!.getKey() + + if (ktarget != null) { + let skip = false + if (listMap && typeof k2 === 'number') { + k2_ = listMap(k2, r!.getComponent()) + if (k2_ < 0) { + k2_ = ~k2_ + skip = true + } + } + + // 3 cases: + // - ktarget > k2. onSkip and iterate. + // - ktarget = k and !skip. return r + // - ktarget = k and skip or ktarget < k2. stop. + + if (isGreaterKey(k2_, ktarget)) return null + else if (k2_ === ktarget && !skip) return r! + } + + // Skip this item and continue. + if (listAdv && typeof k2_ === 'number') listAdv(k2_, r!.getComponent()) + valid = r!.nextSibling() + } + return null + } + + adv.end = () => { + // Advance to the end of the child items. Technically only needed if onSkipped is defined + if (didDescend) r!.ascend() + } + return adv +} + +// Walk two cursors together. +export function eachChildOf( + r1: ReadCursor | null, r2: ReadCursor | null, + fn: (k: Key, r1: ReadCursor | null, r2: ReadCursor | null) => void) { + let hasChild1, descended1, hasChild2, descended2 + hasChild1 = descended1 = r1 && r1.descendFirst() + hasChild2 = descended2 = r2 && r2.descendFirst() + + while (hasChild1 || hasChild2) { + let k1 = hasChild1 ? r1!.getKey() : null + let k2 = hasChild2 ? r2!.getKey() : null + + if (k1 !== null && k2 !== null) { + // Use the smallest key + if (isGreaterKey(k2, k1)) k2 = null + else if (k1 !== k2) k1 = null + } + + // fn(key, r1 or null, r2 or null) + fn((k1 == null ? k2! : k1), + (k1 != null ? r1 : null), + (k2 != null ? r2 : null) + ) + + if (k1 != null && hasChild1) { + hasChild1 = r1!.nextSibling() + } + if (k2 != null && hasChild2) { + hasChild2 = r2!.nextSibling() + } + } + + if (descended1) r1!.ascend() + if (descended2) r2!.ascend() +} diff --git a/src/json1Presence/deepClone.ts b/src/json1Presence/deepClone.ts new file mode 100644 index 00000000..8e422cca --- /dev/null +++ b/src/json1Presence/deepClone.ts @@ -0,0 +1,16 @@ +import { Doc } from "./types" + +export default function deepClone(old: Doc): Doc { + if (old === null) return null + + if (Array.isArray(old)) { + return old.map(deepClone) + } else if (typeof old === 'object') { + const o: Doc = {} + for (let k in old) o[k] = deepClone(old[k]) + return o + } else { + // Everything else in JS is immutable. + return old + } +} diff --git a/src/json1Presence/deepEqual.ts b/src/json1Presence/deepEqual.ts new file mode 100644 index 00000000..5a8e9f3f --- /dev/null +++ b/src/json1Presence/deepEqual.ts @@ -0,0 +1,33 @@ +import { Doc } from "./types" + +function eqObj(a: {[k: string]: Doc}, b: Doc[] | {[k: string]: Doc}) { + if (Array.isArray(b)) return false + + for (let k in a) { + if (!deepEqual(a[k], b[k])) return false + } + for (let k in b) { + if (a[k] === undefined) return false + } + return true +} + +function eqArr(a: Doc[], b: Doc) { + if (!Array.isArray(b)) return false + if (a.length !== b.length) return false + + for (let i = 0; i < a.length; i++) { + if (!deepEqual(a[i], b[i])) return false + } + + return true +} + +export default function deepEqual(a: Doc, b: Doc) { + // This will cover null, bools, string and number + if (a === b) return true + if (a === null || b === null) return false + if (typeof a !== 'object' || typeof b !== 'object') return false + + return Array.isArray(a) ? eqArr(a, b) : eqObj(a, b) +} diff --git a/src/json1Presence/index.ts b/src/json1Presence/index.ts new file mode 100644 index 00000000..fcade079 --- /dev/null +++ b/src/json1Presence/index.ts @@ -0,0 +1,7 @@ +export * from './json1.release.js' +export {ReadCursor, WriteCursor} from './cursor.js' +export { + Doc, + JSONOp, JSONOpList, JSONOpComponent, + Key, Path, Conflict, ConflictType, +} from './types' diff --git a/src/json1Presence/json1.release.ts b/src/json1Presence/json1.release.ts new file mode 100644 index 00000000..6ba80388 --- /dev/null +++ b/src/json1Presence/json1.release.ts @@ -0,0 +1,4 @@ +// This is a placeholder which will be replaced in the dist directory by the +// minified version of json1. + +export * from './json1' \ No newline at end of file diff --git a/src/json1Presence/json1.ts b/src/json1Presence/json1.ts new file mode 100644 index 00000000..ab05fe49 --- /dev/null +++ b/src/json1Presence/json1.ts @@ -0,0 +1,2967 @@ +// This is the JSON OT type implementation. It is a successor to JSON0 +// (https://github.com/ottypes/json0), better in a bunch of ways. +// +// This type follows after the text type. Instead of operations being a list of +// small changes, an operation is a tree mirroring the structure of the document +// itself. Operations contain three things: +// - Edits +// - Pick up operations +// - Drop operations +// +// The complete spec is in spec.md alongside this file. +// +// +// A tiny todo list (new issues should go on github): +// - compose should preserve invertible data +// - Conversion between json1 ops and json0, json-patch. +// - Conflict resolving methods to make a backup move location in the operation +// - remove N items in a list - it'd be nice to be able to express this in O(1) instead of O(n). + +// import log from './log' +import deepEqual from './deepEqual.js' +import deepClone from './deepClone.js' +import { ReadCursor, WriteCursor, readCursor, writeCursor, advancer, eachChildOf, isValidPathItem } from './cursor.js' +import { Doc, JSONOpComponent, Path, Presence, Key, JSONOp, JSONOpList, Conflict, ConflictType } from './types.js' + +const RELEASE_MODE = process.env.JSON1_RELEASE_MODE +const log: (...args: any) => void = RELEASE_MODE ? (() => {}) : require('./log').default + +// Not using assert to decrease how much stuff we pull in in the browser +// const assert = !RELEASE_MODE ? require('assert') : (pred: boolean, msg?: string) => {if (!pred) throw new Error(msg)} +function assert(pred: any, msg?: string): asserts pred { + if (!RELEASE_MODE) require('assert')(pred, msg) // For nicer error messages in node. + else if (!pred) throw new Error(msg) +} + +let debugMode = false + +export const type = { + name: 'json1', + uri: "http://sharejs.org/types/JSONv1", + readCursor, + writeCursor, + + // The document must be immutable. Create by default sets a document at + // `undefined`. You should follow up by initializing the document using a + // {i:....} operation. + create(data: Doc) { return data }, + + isNoop(op: JSONOp) { return op == null }, + + setDebug(val: boolean) { debugMode = val; (log as any).quiet = !val }, + + registerSubtype, + + checkValidOp, + normalize, + + apply, + transformPosition, + transformPresence, + compose, + tryTransform, + transform, + + makeInvertible, + invert, + invertWithDoc, + + // TODO: Consider simply exporting ConflictType. + RM_UNEXPECTED_CONTENT: ConflictType.RM_UNEXPECTED_CONTENT, + DROP_COLLISION: ConflictType.DROP_COLLISION, + BLACKHOLE: ConflictType.BLACKHOLE, + + + + // Mmmm I'm not sure the best way to expose this. + transformNoConflict: (op1: JSONOp, op2: JSONOp, side: 'left' | 'right') => ( + transformWithConflictsPred(() => true, op1, op2, side) + ), + + typeAllowingConflictsPred: (allowConflict: AllowConflictPred) => ({ + ...type, + transform(op1: JSONOp, op2: JSONOp, side: 'left' | 'right') { + return transformWithConflictsPred(allowConflict, op1, op2, side) + } + }) +} + + +const getComponent = (r?: ReadCursor | WriteCursor | null) => r ? r.getComponent() : null + +function isObject(o: any): o is {[k: string]: any} { + return o && typeof(o) === 'object' && !Array.isArray(o) +} + +// Can't figure out how to make a generic type +const shallowClone = (obj: Doc) => ( + Array.isArray(obj) + ? obj.slice() + : obj !== null && typeof obj === 'object' ? Object.assign({}, obj) + : obj +) + +const hasPick = (c?: JSONOpComponent | null) => c && (c.p != null || c.r !== undefined) +const hasDrop = (c?: JSONOpComponent | null) => c && (c.d != null || c.i !== undefined) + +// **** Helpers to make operations. +// +// TODO(cleanup): Move these out into another file. +export const removeOp = (path: Path, value = true) => ( + writeCursor() + .writeAtPath(path, 'r', value) + .get() +) + +export const moveOp = (from: Path, to: Path) => ( + writeCursor() + .writeMove(from, to) + .get() +) + +export const insertOp = (path: Path, value: Doc) => ( + writeCursor() + .writeAtPath(path, 'i', value) + .get() +) + +export const replaceOp = (path: Path, oldVal: Doc, newVal: Doc) => ( + writeCursor().at(path, (w) => { + w.write('r', oldVal) + w.write('i', newVal) + }).get() +) + +export const editOp = (path: Path, type: Subtype | string, subOp: any, preserveNoop: boolean = false) => ( + writeCursor() + .at(path, w => writeEdit(w, type, subOp, preserveNoop)) + .get() +) + +type DocObj = {[k: string]: Doc} +// Remove key from container. Return container with key removed +function removeChild(container: Doc[] | DocObj, key: Key) { + assert(container != null) + if (typeof key === 'number') { // Removing from a list + assert(Array.isArray(container), 'Invalid key - child is not an array') + container = container.slice() + container.splice(key, 1) + } else { // Removing from an object + assert(isObject(container), 'Invalid key - child is not an object') + container = Object.assign({}, container) + delete (container as DocObj)[key] + } + return container +} + +// Insert value at key in container. Return container with new value +function insertChildMut(container: Doc[] | DocObj, key: Key, value: Doc) { + if (typeof key === 'number') { + assert(container != null, 'Container is missing for key') + assert(Array.isArray(container), 'Cannot use numerical key for object container') + assert(container.length >= key, 'Cannot insert into out of bounds index') + //container = container.slice() + container.splice(key, 0, value) + } else { + assert(isObject(container), 'Cannot insert into missing item') + assert((containerĀ as DocObj)[key] === undefined, 'Trying to overwrite value at key. Your op needs to remove it first') + ;(container as DocObj)[key] = value + //container = Object.assign({[key]:value}, container) + } + + return value +} + +const replaceChild = (obj: Doc[] | DocObj, k: Key, v: any) => { + obj = shallowClone(obj) as typeof obj + ;(obj as any)[k] = v // Could add an assert here... + return obj +} + +const isValidKey = (container: Doc | undefined, key: Key): boolean => ( + container == null + ? false + : typeof key === 'number' + ? Array.isArray(container) + : typeof container === 'object' +) + +const maybeGetChild = (container: Doc | undefined, key: Key): Doc | undefined => ( + isValidKey(container, key) ? (container as any)[key] : undefined +) + +// ******* + +type Subtype = { + name: string, + uri?: string, + apply(doc: any, op: any): any, // want to buy: Rust's associated types. + compose(op1: any, op2: any): any, + transform(op1: any, op2: any, by: 'left' | 'right'): any + isNoop?: (op: any) => boolean + invert?: (op: any) => any + makeInvertible?: (op: any, doc: any) => any + + [k: string]: any, +} + +const subtypes: {[k: string]: Subtype} = {} +function registerSubtype(subtype: Subtype | {type: Subtype, [k: string]: any}) { + let _subtype = subtype.type ? subtype.type : subtype + if (_subtype.name) subtypes[_subtype.name] = _subtype + if (_subtype.uri) subtypes[_subtype.uri] = _subtype +} + +const typeOrThrow = (name: string) => { + const type = subtypes[name] + if (!type) throw Error('Missing type: ' + name) + else return type +} + +registerSubtype(require('ot-text-unicode')) + +const add = (a: number, b: number) => a + b +registerSubtype({ + name: 'number', + apply: add, + compose: add, + invert: (n: number) => -n, + transform: a => a, +}) + +const getEditType = (c?: JSONOpComponent | null) => ( + (c == null) ? null + : c.et ? typeOrThrow(c.et) + : c.es ? subtypes['text-unicode'] + : c.ena != null ? subtypes.number + : null +) + +// I'm using terneries here because .ena might be 0. +const getEdit = (c: JSONOpComponent) => c.es ? c.es : c.ena != null ? c.ena : c.e + +// Type is an object or a string name. +const writeEdit = (w: WriteCursor, typeOrName: Subtype | string, edit: any, preserveNoop: boolean = false) => { + // I hope v8 doesn't make a temporary array here. + const [type, name] = (typeof typeOrName === 'string') + ? [typeOrThrow(typeOrName), typeOrName] + : [typeOrName, typeOrName.name] + + // Discard embedded noops. + if (!preserveNoop && type.isNoop && type.isNoop(edit)) return + + if (name === 'number') { + w.write('ena', edit) + } else if (name === 'text-unicode') { + w.write('es', edit) // Text edit-string operation. + } else { // TODO: Also if t1 === subtypes.number then write ena... + w.write('et', name) // Generic embedded edit. + w.write('e', edit) + } +} + + +// ***** Check code + + +function checkNonNegInteger(n: any): asserts n is number { + assert(typeof n === 'number') + assert(n >= 0) + assert(n === (n|0)) +} + +function checkScalar(s: any): asserts s is number | string { + if (typeof s === 'number') { + checkNonNegInteger(s) + } else { + assert(typeof s === 'string') + } +} + +function checkValidOp(op: JSONOp) { + //console.log 'check', op + + if (op === null) return // Empty operation. undefined is not a valid operation and should crash. + + // Both sets of used IDs. + const pickedSlots = new Set + const droppedSlots = new Set + + const checkComponent = (e: JSONOpComponent) => { + let empty = true + let hasEdit = false + for (let k in e) { + const v = e[k as keyof JSONOpComponent] + empty = false + + // Thanks coffeescript compiler! + assert(k === 'p' || k === 'r' || k === 'd' || k === 'i' + || k === 'e' || k === 'es' || k === 'ena' || k === 'et', + "Invalid component item '" + k + "'") + + if (k === 'p') { + checkNonNegInteger(v) + assert(!pickedSlots.has(v)) + pickedSlots.add(v) + assert(e.r === undefined) + } else if (k === 'd') { + checkNonNegInteger(v) + assert(!droppedSlots.has(v)) + droppedSlots.add(v) + assert(e.i === undefined) + } else if (k === 'e' || k === 'es' || k === 'ena') { + assert(!hasEdit) + hasEdit = true + const t = getEditType(e) + assert(t, 'Missing type in edit') + if (t.checkValidOp) t.checkValidOp(getEdit(e)) + } + } + + assert(!empty) + } + + const SCALAR=1, EDIT=2, DESCENT=3 + const checkDescent = (descent: JSONOpList, isRoot: boolean, removed: Doc) => { + if (!Array.isArray(descent)) throw Error('Op must be null or a list') + if (descent.length === 0) throw Error('Empty descent') + + if (!isRoot) checkScalar(descent[0]) + + let last = SCALAR + // let lastScalarType = 'string' // or 'number' or 'string'. + let numDescents = 0 + let lastKey: Key = 0 + + for (let i = 0; i < descent.length; i++) { + const d = descent[i] + assert(d != null) + + if (Array.isArray(d)) { + // Recursive descent inside children + const key = checkDescent(d, false, removed) + if (numDescents) { + const t1 = typeof lastKey + const t2 = typeof key + if (t1 === t2) assert(lastKey < key, 'descent keys are not in order') + else assert(t1 === 'number' && t2 === 'string') + } + + lastKey = key + numDescents++ + last = DESCENT + } else if (typeof d === 'object') { + // Component + assert(last === SCALAR, `Prev not scalar - instead ${last}`) + checkComponent(d) + + // const hasP = d.r !== undefined || d.p != null + // const hasD = d.i !== undefined || d.d != null + // if (removed && hasD) throw Error('Cannot drop in deleted object') + + // if (hasD) removed = false + // else if (hasP && lastScalarType === 'string') removed = true + + // if (removed && getEditType(d) != null) throw Error('Cannot edit missing object') + last = EDIT + } else { + // Descent key + assert(last !== DESCENT) + checkScalar(d) + assert(isValidPathItem(d), 'Invalid path key') // They're trying to use __proto__ or something. + last = SCALAR + // lastScalarType = typeof d + } + } + + assert(numDescents !== 1, 'Operation makes multiple descents. Remove some []') + assert(last === EDIT || last === DESCENT) + + return descent[0] as Key + } + + checkDescent(op, true, false) + + assert(pickedSlots.size === droppedSlots.size, 'Mismatched picks and drops in op') + + for (let i = 0; i < pickedSlots.size; i++) { + assert(pickedSlots.has(i)) + assert(droppedSlots.has(i)) + } +} + +function normalize(op: JSONOp) { + let nextSlot = 0 + let slotMap: number[] = [] + const getSlot = (inSlot: number) => { + if (slotMap[inSlot] == null) slotMap[inSlot] = nextSlot++ + return slotMap[inSlot] + } + + const w = writeCursor() + w.mergeTree(op, (c, w) => { + const t = getEditType(c) + if (t) { + const op = getEdit(c) + writeEdit(w, t, t.normalize ? t.normalize(op) : op) + } + + // Copy everything else in like normal. + for (const k of ['r', 'p', 'i', 'd'] as (keyof JSONOpComponent)[]) { + if (c[k] !== undefined) { + const r = (k === 'p' || k === 'd') ? getSlot(c[k]!) : c[k] + w.write(k, r) + } + } + }) + return w.get() +} + + +// ***** Apply + +/** + * Apply op to snapshot. Return the new snapshot. + * + * The snapshot is shallow copied as its edited, so the previous snapshot + * reference is still valid. + */ +function apply(snapshot: Doc | undefined, op: JSONOp) { + // Apply doesn't make use of cursors. It might be a little simpler to + // implement apply using them. The reason they aren't used here is merely + // historical - the cursor implementation was written after apply() was + // already working great. + + ;(log as any).quiet = !debugMode + //log.quiet = false + //log('*******************************************') + if (!RELEASE_MODE) { + log('apply') + log('snapshot', snapshot) + log('op', op) + log(`repro: apply(${JSON.stringify(snapshot)}, ${JSON.stringify(op)})`) + } + + checkValidOp(op) + + // The snapshot can be undefined if the document is being made with this + // operation. Its up to the application if they want to use OT to support + // that, or if they want to disallow the document from being undefined. + + if (op === null) return snapshot + + const held: Doc[] = [] // Indexed by the pick #. Autoexpand. + + // The op is made up of lists. Each list item is one of three things: + // - A descent (a scalar value to descend into either a list or an object at + // the current location). Except for at the root there is always at least 1 of these. + // - An edit - which is a pick, a drop or an edit. + // - A child descent. These are always at the end of the list. We recurse for these. + + // Phase 1: Pick. Returns updated subdocument. + function pick(subDoc: Doc | undefined, descent: JSONOpList) { + //log('pick', subDoc, descent) + + // const stack: (DocObj | Doc[] | undefined)[] = [] + const stack: (Doc | undefined)[] = [] + + let i = 0 + for (; i < descent.length; i++) { + const d = descent[i] + if (Array.isArray(d)) break + if (typeof d === 'object') continue + stack.push(subDoc) + // Its valid to descend into a null space - just we can't pick there, so + // we'll set subdoc to undefined in those cases. + subDoc = maybeGetChild(subDoc, d) + } + + // Children. These need to be traversed in reverse order here. + // Note all children after the local op component are child descents. + for (let j = descent.length - 1; j >= i; j--) { + // This returns the new subDoc, or undefined if the child should be removed. + subDoc = pick(subDoc, descent[j] as JSONOpList) + } + + // Then back again. + for (--i; i >= 0; i--) { + const d = descent[i] as Key | JSONOpComponent + if (typeof d !== 'object') { + const container = stack.pop() + + // Mirrored from above, which is a little gross. + const expectedChild = maybeGetChild(container, d) + // Have fuuuuunnnn! (TODO: Clean this mess up. And what should / does this do if container is a string / number?) + subDoc = (subDoc === expectedChild) + ? container + : (subDoc === undefined) + ? removeChild(container as Doc[] | DocObj, d) + : replaceChild(container as Doc[] | DocObj, d, subDoc) + } else if (hasPick(d)) { + assert(subDoc !== undefined, 'Cannot pick up or remove undefined') + if (d.p != null) held[d.p] = subDoc + subDoc = undefined + } + } + + return subDoc + } + + snapshot = pick(snapshot, op) as Doc + + log('--- after pick phase ---') + log('held', held, 'snapshot', snapshot) + + function drop(root: Doc, descent: JSONOpList) { + // The document might have already been cloned in-place. If it has we'll + // still throw it out and make a new one. This should be pretty quick but + // if its a problem we could optimize it by making a editable Set with + // everything we've cloned (and thus is safe to edit) + // + // I think it should be safe anyway because short lived objects are pretty + // cheap in a modern generational GC. + + let subDoc = root, i = 0 // For reading + let rootContainer: Doc = {root} // This is an avoidable allocation. + let m = 0, container: Doc = rootContainer, key: Key = 'root' // For writing + + function mut() { + for (; m < i; m++) { + let d = descent[m] + if (typeof d === 'object') continue + + //log('descent', d, container, key) + assert(isValidKey(container, key)) + container = (container as any)[key] = shallowClone((container as any)[key]) + key = d + } + } + + for (; i < descent.length; i++) { + const d = descent[i] + + if (Array.isArray(d)) { + const child = drop(subDoc, d) + if (child !== subDoc && child !== undefined) { + mut(); subDoc = container[key] = child + } + } else if (typeof d === 'object') { + if (d.d != null) { // Drop + mut(); subDoc = insertChildMut(container, key, held[d.d]) + } else if (d.i !== undefined) { // Insert + mut(); subDoc = insertChildMut(container, key, d.i) + } + + const t = getEditType(d) + if (t) { // Edit. Ok because its illegal to drop inside mixed region + mut(); subDoc = container[key] = t.apply(subDoc, getEdit(d)) + } else if (d.e !== undefined) { + throw Error("Subtype " + d.et + " undefined") + } + + } else { + // Scalar. Descend. + // Actually we should check that the types match. + + // TODO: Use isValidKey to check this. + subDoc = maybeGetChild(subDoc, d) as Doc + // subDoc = subDoc != null ? (subDoc as any)[d] : undefined + } + } + + return rootContainer.root + } + + //log('drop', snapshot, op) + snapshot = drop(snapshot, op) + + log('-> apply returning snapshot', snapshot) + return snapshot +} + +const incPrefix = () => { if (!RELEASE_MODE) (log as any).prefix++ } +const decPrefix = () => { if (!RELEASE_MODE) (log as any).prefix-- } + +/** + * Transforms a path by an operation. Eg: + * + * ```javascript + * newPath = transformPosition([3, 'address'], [2, {r: true}]) + * // newPath is [2, 'address'] + * ``` + * + * This is useful for cursors, for tracking annotations and various other + * markers which should stick to a single element of the JSON document. + * + * transformPosition returns null if the item stored at the path no longer + * exists in the document after the operation has been applied. Eg: + * + * ```javascript + * assert(transformPosition([2, 'address', 'street'], [2, {r: true}]) === null) + * ``` + */ +function transformPosition(path: Path, op: JSONOp): Path | null { + log('transformPosition', path, op) + incPrefix() + path = path.slice() + checkValidOp(op) + + const r = readCursor(op) + + // First picks. There's 3 results here: + // 1. The object at the path (or a parent) was removed + // 2. Some parent was picked up + // 3. The op doesn't modify this path, although it might insert before / after. + + let removed = false + let pickedAtSlot: number + let pickIndex: number // index in path where pick happened + + const advStack = [] + + for (let i = 0;; i++) { + const k = path[i] + const c = r.getComponent() + log('pick phase', i, ':', k, c) + if (c) { + // Object / object parent removed + if (c.r !== undefined) removed = true + else if (c.p != null) { + removed = false + pickedAtSlot = c.p + pickIndex = i + } + } + + if (i >= path.length) break + + let pickOffset = 0 + const pickAdv = advancer(r, undefined, (k, c) => { + if (hasPick(c)) { + pickOffset++ + // log('p2Pick++', p2PickOff, k, c) + } + }) + advStack.unshift(pickAdv) + + const hasNext = pickAdv(k) + if (typeof k === 'number') (path[i] as number) -= pickOffset + if (!hasNext) break + } + + // Instead of this bookkeeping, consider just making a new read cursor for handleDrop. + advStack.forEach(pickAdv => pickAdv.end()) + + decPrefix() + log('after pick phase. Remove', removed, + 'pi', pickIndex!, + 'pas', pickedAtSlot!, + 'po', path) + + // If we've been removed there's nothing else to do. + if (removed) { + log('item removed. Bailing!') + return null + } + + // If we've been moved into a slot, we 100% don't care about anything above + // the slot we've been dropped in. + + const handleDrop = () => { + incPrefix() + let i = 0 + if (pickedAtSlot != null) { + // We've been dropped at some path. We teleport to the drop + // location and scan from there. + const rPath = r.getPath() + i = rPath.length + if (!RELEASE_MODE) log('path', path, 'pi', pickIndex, 'r', rPath) + path = rPath.concat(path.slice(pickIndex)) + } + + if (!RELEASE_MODE) log('handleDrop at path', path, 'read cursor', r.getPath()) + + // We need to walk down to the end of the path and adjust indicies on the + // way based on earlier list drops and edits. + for (; i < path.length; i++) { + const k = path[i] + // We don't actually care about drops here... they shouldn't happen in + // general; but if they do then we'll silently ignore them. + + const c = getComponent(r) + const et = getEditType(c) + if (et) { + const e = getEdit(c!) + log('Embedded edit', e, et) + if (et.transformPosition) { + path[i] = et.transformPosition(path[i], e) + } else if (et.transformCursor) { + path[i] = et.transformCursor(path[i], e) + } + // Its invalid for the operation to have drops inside the embedded edit here. + break + } + + let dropOffset = 0 + + const dropAdv = advancer(r, (k, c) => ( + hasDrop(c) ? ~(k - dropOffset) : k - dropOffset + ), (k, c) => { + if (hasDrop(c)) dropOffset++ + }) + + const hasNext = dropAdv(k) + + // I'm not keeping the advancer here because it doesn't matter if we + // ascend back up after this. + if (typeof k === 'number') (path[i] as number) += dropOffset + if (!hasNext) break + } + decPrefix() + } + + if (pickedAtSlot! != null) { + r.eachDrop(null, (slot) => { + if (slot === pickedAtSlot) handleDrop() + }) + } else handleDrop() + + log('-> transformPosition returning', path) + return path +} + +function transformPresence(presence: Presence | null, op: JSONOp, isOwnOperation: boolean): Presence | null { + if (!presence || !presence.start || !presence.end) { + return null + } + + const start = transformPosition(presence.start, op) + const end = transformPosition(presence.end, op) + + if (start && end) { + return { ...presence, start, end } + } else if (start) { + return { ...presence, start, end: start } + } else if (end) { + return { ...presence, start: end, end } + } + + return null +} + + +// ****** Compose + +const setOrRemoveChild = (container: Doc[] | DocObj, key: Key, child?: Doc) => { + // Mutate child, setting container[key] = child. Its a shame apply works + // slightly differently from this and we can't reuse the code at the top of + // the file. + // + // TODO: Refactor helper functions so they share the work. + // + // This function will never *insert* - it will only either remove an item + // (child === undefined) or replace it. + + if (typeof key === 'number') { + assert(Array.isArray(container)) + assert(key < container.length) + } else { + assert(!Array.isArray(container)) + assert(container[key] !== undefined) + } + + if (child === undefined) { + if (typeof key === 'number') (container as Doc[]).splice(key, 1) + else delete (container as DocObj)[key] + } else { + (container as any)[key] = child + } +} + +function compose(op1: JSONOp, op2: JSONOp) { + if (debugMode && !RELEASE_MODE) { + log("composing:") + log(' op1:', op1) + log(' op2:', op2) + } + + checkValidOp(op1) + checkValidOp(op2) + + if (op1 == null) return op2 + if (op2 == null) return op1 + + let nextSlot = 0 + + const r1 = readCursor(op1), r2 = readCursor(op2) + const w = writeCursor() + + // Compose has fundamentally three contexts we need to consider: + // + // - The context of the original document (op1 picks) + // - The context of the document after op1 but before op2 (op1 drops & edits, op2 picks) + // - The context of the final document (op2 drops & edits) + // + // We need to transform everything in that middle context out: + // - op2 picks need to be transformed back by op1 + // - op1 drops and edits need to be transformed by op2 + // + // Everything else (op1 picks and op2 drops) gets copied directly into the output. + + // This is a set of write cursors for locations in the output indexed by op1 + // and op2's moves, respectively. The content of these cursors is copied into + // the output. + const heldPickWrites: WriteCursor[] = [] + const heldDropWrites: WriteCursor[] = [] + + // Held read cursors in the outside pick and drop locations, so we can + // teleport our cursors while reading. + const held1Pick: ReadCursor[] = [] + const held2Drop: ReadCursor[] = [] + + // Map from op1 slots -> output slots + const p1SlotMap: number[] = [] + const p2SlotMap: number[] = [] + + const visitedOp2EditCs = new Set() + + // This is just for debugging. + const uniqPaths: {[k: string]: Set} = {} + const uniqDesc = (key: string, reader: ReadCursor | null | undefined) => { + if (reader == null) return + if (uniqPaths[key] == null) uniqPaths[key] = new Set() + + // log('uniqDesc', key, reader.getPath()) + const path = reader.getPath().map((k,i) => `${i}:${k}`).join('.') + if (uniqPaths[key].has(path)) throw Error(`non-unique descent: ${key} ${path}`) + uniqPaths[key].add(path) + } + + // First scan the outside operations to populate held1Pick and held2Drop. + r1.traverse(null, c => { + if (c.p != null) held1Pick[c.p] = r1.clone() + }) + + r2.traverse(null, c => { + // Clone the read cursor + if (c.d != null) held2Drop[c.d] = r2.clone() + }) + + + // Next we can scan the inside edge following op1 drops then op2 picks, + // transforming and writing the output to w. + // + // At least one of r1Drop or r2Pick must not be null. + // + // This function writes r1 drops to w1 and r2 picks to wp. + function xfBoundary( + r1Pick: ReadCursor | null, r1Drop: ReadCursor | null, + r2Pick: ReadCursor | null, r2Drop: ReadCursor | null, + litIn: Doc | undefined, rmParent: boolean, + wd: WriteCursor, wp: WriteCursor) { + assert(r1Drop || r2Pick) + if (!RELEASE_MODE) log('xfBoundary', litIn, + !!r1Pick && r1Pick.getPath(), !!r1Drop && r1Drop.getPath(), + !!r2Pick && r2Pick.getPath(), !!r2Drop && r2Drop.getPath(), + 'lit:', litIn, 'rmParent:', rmParent) + incPrefix() + + const c1d = getComponent(r1Drop) + const c2p = getComponent(r2Pick) + + const rmHere = c2p ? c2p.r !== undefined : false + const insHere = c1d ? c1d.i !== undefined : false + const drop1Slot = c1d ? c1d.d : null + const pick2Slot = c2p ? c2p.p : null + + log('pick2Slot', pick2Slot, 'drop1Slot', drop1Slot) + + // rmParent arg for when we recurse. + const rmChildren = (rmParent || rmHere) && (pick2Slot == null) + + if (pick2Slot != null) { + log('teleporting via c2 pick2Slot', pick2Slot) + r2Drop = held2Drop[pick2Slot] + if (!RELEASE_MODE) log('r2Drop', r2Drop && r2Drop.getPath()) + + // Writes here need to be transformed to op2's drop location. + wd = heldDropWrites[pick2Slot] = new WriteCursor() + // rmParent = null + } else if (c2p && c2p.r !== undefined) { + r2Drop = null + } else { + // We're processing the drop when we see the corresponding pickup. + const c2d = getComponent(r2Drop) + if (c2d && c2d.d != null) r2Drop = null + } + + const c2d = getComponent(r2Drop) + + if (drop1Slot != null) { // Drop here. + log('teleporting via c1 drop1Slot', drop1Slot) + r1Pick = held1Pick[drop1Slot] + + // Writes here need to be transformed to op2's drop location. + wp = heldPickWrites[drop1Slot] = new WriteCursor() + + if (!rmChildren) { + const slot = p1SlotMap[drop1Slot] = nextSlot++ + log('assigning slot', slot, 'to op1 slot', drop1Slot) + wd.write('d', slot) + } else { + log('Cancelling op1 move', drop1Slot, 'rmParent', rmParent, 'rmHere', rmHere) + // TODO: merge removed subdocuments here. This logic should mirror compose on insert. + if (rmParent && !rmHere) wp.write('r', true) + } + } else if (c1d && c1d.i !== undefined) { + r1Pick = null + } else { + // TODO: ??? Why is this also necessary? + const c1p = getComponent(r1Pick) + if (c1p && c1p.p != null) r1Pick = null + } + + if (!RELEASE_MODE) { + uniqDesc('r1Pick', r1Pick) + uniqDesc('r1Drop', r1Drop) + uniqDesc('r2Pick', r2Pick) + uniqDesc('r2Drop', r2Drop) + } + + // Note that insIn might also be set if the insert is chained. + let litOut: Doc | undefined + if (insHere) { + assert(litIn === undefined) + litOut = c1d!.i + } else litOut = litIn + + if (litOut !== undefined) log('litOut', litOut) + + // Will we insert in the output? If so hold the component, and we'll write + // the insert at the end of this function. + const insComponent = (pick2Slot == null + ? (insHere && !rmParent && !rmHere) + : litOut !== undefined + ) ? wd.getComponent() : null + + if (insComponent) log('insComponent', + (insHere && !rmParent && !rmHere && pick2Slot == null), + (pick2Slot != null && litOut !== undefined)) + else log('no insComponent - this insert will be discarded', + (insHere && !rmParent && !rmHere && pick2Slot == null), + (pick2Slot != null && litOut !== undefined) + ) + + + if (pick2Slot != null) { + // .... + if (litIn !== undefined || insHere) { + // Move the literal. + } else { + // If we pick here and drop here, join the two moves at the hip. + // TODO(cleanup): ... Can't we insert this immediately in the block + // above where we write the drop? + const slot = drop1Slot != null ? p1SlotMap[drop1Slot] : nextSlot++ + + p2SlotMap[pick2Slot] = slot + log('assigning slot', slot, 'to op2 slot', drop1Slot) + wp.write('p', slot) + } + } else if (rmHere) { + if (!insHere && litIn === undefined) { + log('keeping write here r=', c2p!.r) + wp.write('r', c2p!.r) + } + } + + // Edits! + // + // For edits, we basically want to grab the edit at r1Drop and r2Drop and + // just compose them together into the output. + const type1 = !rmChildren ? getEditType(c1d) : null + const type2 = getEditType(c2d) + + log('components', c1d, c2d) + if (type1 || type2) log('types', type1 && type1.name, type2 && type2.name) + + if (type1 && type2) { + assert(type1 === type2) + // Compose. + const e1 = getEdit(c1d!) + const e2 = getEdit(c2d!) + const r = type1.compose(e1, e2) + log('compose ->', r) + // Only write if its not a no-op. + writeEdit(wd, type1, r) + visitedOp2EditCs.add(c2d!) + } else if (type1) { + log('copying edit type1', c1d) + writeEdit(wd, type1, getEdit(c1d!)) + } else if (type2) { + log('copying edit type2', c2d) + writeEdit(wd, type2, getEdit(c2d!)) + visitedOp2EditCs.add(c2d!) + } + + // We follow strict immutability semantics, so we have to clone the insert + // if it gets modified. We'll only clone the object literal if we have to. + const hasContainerLiteral = typeof litOut === 'object' && litOut != null + + let isCloned = false + + let p1PickOff = 0, p1DropOff = 0 + let p2PickOff = 0, p2DropOff = 0 + let litOff = 0 + + const p2DropAdv = advancer(r2Drop, + (k, c) => hasDrop(c) ? (p2DropOff - k - 1) : k - p2DropOff, + (k, c) => { if (hasDrop(c)) p2DropOff++ }) + + const p1PickAdv = advancer(r1Pick, + (k, c) => hasPick(c) ? (p1PickOff - k - 1) : k - p1PickOff, + (k, c) => { if (hasPick(c)) p1PickOff++ }) + + eachChildOf(r1Drop, r2Pick, (inKey, _p1Drop, _p2Pick) => { + // The key we've been passed is the context between the two operations. + // We need to transform it to each of the starting (p1 pick) context and + // final (p2 drop) contexts. + let p1PickKey = inKey, p2DropKey = inKey, litKey = inKey + let _p1Pick, _p2Drop + + if (typeof inKey === 'number') { + let p2Mid = inKey + p2PickOff + _p2Drop = p2DropAdv(p2Mid) + p2DropKey = p2Mid + p2DropOff + + let p1Mid = inKey + p1DropOff + _p1Pick = p1PickAdv(p1Mid) + if (hasDrop(getComponent(_p2Drop))) _p1Pick = null // But still advance! + p1PickKey = p1Mid + p1PickOff + + // If the literal is an array, we're modifying that array as we go. + litKey = inKey + litOff + + log('p1PickOff', p1PickOff, 'p1DropOff', p1DropOff, + 'p2PickOff', p2PickOff, 'p2DropOff', p2DropOff, + 'litOff', litOff) + log('inKey', inKey, '-> p1 mid', p1Mid, '-> p1 final', p1PickKey) + log('inKey', inKey, '-> p2 mid', p2Mid, '-> p2 final', p2DropKey) + + assert(p1PickKey >= 0, 'p1PickKey is negative') + assert(p2DropKey >= 0, 'p2DropKey is negative') + + // Update these keys as we move past. This will be used for subsequent + // descents, but not this one. + const hd1 = hasDrop(getComponent(_p1Drop)) + const hp2 = hasPick(getComponent(_p2Pick)) + if (hd1 || (hp2 && !rmChildren)) litOff-- + if (hd1) p1DropOff-- + if (hp2) p2PickOff-- + } else { + _p1Pick = p1PickAdv(inKey) + _p2Drop = p2DropAdv(inKey) + } + + log('->', 'p1pick', p1PickKey, 'inkey', inKey, 'p2drop', p2DropKey, 'litKey', litKey) + wp.descend(p1PickKey) + wd.descend(p2DropKey) + + // TODO: Check litOut matches expected shape here using isValidKey + const _lit = hasContainerLiteral && !hasDrop(getComponent(_p1Drop)) + ? (litOut as any)[litKey] : undefined + + log('_lit', _lit, litOut, litKey) + const _litResult = xfBoundary(_p1Pick, _p1Drop, _p2Pick, _p2Drop, + _lit, rmChildren, wd, wp) + + if (hasContainerLiteral && !rmChildren) { + log('_litResult', _litResult) + if (_lit !== _litResult) { + if (!isCloned) { + litOut = Array.isArray(litOut) + ? litOut.slice() : Object.assign({}, litOut) + isCloned = true + } + + setOrRemoveChild(litOut as Doc[] | DocObj, litKey, _litResult) + log('litOut ->', litOut) + } + } else assert(_litResult === undefined) + + wd.ascend() + wp.ascend() + }) + p1PickAdv.end() + p2DropAdv.end() + + decPrefix() + + // Ok, the cases: + // - If there's a literal here, write the literal and return nothing. + // - If there's a lit in and a literal, write literal, return lit in. + // - If there's a literal here and a pick, the literal got taken by the + // pick. Return litIn. + // - If there's a litIn and a pick, the litIn got taken by the pick. Return + // undefined. + // - If there's a literal, a lit in and a pick (all 3), the pick takes the + // literal and we return the lit in. + if (insComponent != null) { + insComponent.i = litOut + // litOut = litIn + //log('a', litIn) + } else if (!rmParent && !rmHere && pick2Slot == null) { + //log('b', litOut) + return litOut + } + } + + + const w2 = writeCursor() + xfBoundary(r1, r1.clone(), r2, r2.clone(), undefined, false, w, w2) + + w.reset() + w.mergeTree(w2.get()) + w.reset() + + log('intermediate op', w.get()) + log('heldPickWrites', heldPickWrites.map(w => w.get())) + log('heldDropWrites', heldDropWrites.map(w => w.get())) + + // Ok, now we need to walk along the picks and copy them in. + + // Write op1 picks + r1.traverse(w, (c, w) => { + const slot1 = c.p + if (slot1 != null) { + // TODO: Cancelled moves seems redundant all of a sudden. + const slot = p1SlotMap[slot1] + if (slot != null) { + log('writing pick slot', slot) + w.write('p', slot) + } + const _w = heldPickWrites[slot1] + if (_w) log('merge pick write', _w.get()) + if (_w) w.mergeTree(_w.get()) + } else if (c.r !== undefined) { + w.write('r', c.r) + } + }) + + w.reset() + log('intermediate op with picks', w.get()) + + // Write op2 drops + r2.traverse(w, (c, w) => { + const slot2 = c.d + if (slot2 != null) { + const slot = p2SlotMap[slot2] + if (slot != null) { + w.write('d', slot) + } + const _w = heldDropWrites[slot2] + if (_w) w.mergeTree(_w.get()) + } else if (c.i !== undefined) { + w.write('i', c.i) + } + + // xfBoundary above will copy any composed edits in. But we also need to + // copy any edits in op2 which aren't at the boundary. + const t = getEditType(c) + if (t && !visitedOp2EditCs.has(c)) { + writeEdit(w, t, getEdit(c)) + } + }) + + + const result = w.get() + log('-> compose returning', result) + + if (!RELEASE_MODE) checkValidOp(result) + return result +} + +// ***** Invert + + + +/** + * Invert the given operation. Operation must be invertible - which is to say, + * all removes in the operation must contain all removed content. Note any + * operation returned from transform() and compose() will have this content + * stripped. + * + * Unless you know what you're doing, I highly recommend calling invertWithDoc() + * instead. + */ +function invert(op: JSONOp): JSONOp { + // This is pretty simple: + // - All r -> i, i -> r + // - d -> p, p -> d. (Although note this may make the slot order + // non-canonical.) + // - And edits (unfortunately) need to be reverse-transformed by the + // operation. + // + // WIP: This implementation is not correct yet. Inserts also need to be + // modified by the operation's embedded edits. This does not currently work + // correctly. + + if (op == null) return null + + const r = new ReadCursor(op) + const w = new WriteCursor() + + // let hasEdit = false // We'll only do the second edit pass if this is true. + + // I could just duplicate the logic for deciding which edits need to be + // inspected in the second pass, but its much simpler to just mark the + // components in the read. + let editsToTransform: undefined | Set + + const heldPick: ReadCursor[] = [] + const heldWrites: WriteCursor[] = [] + + log('inverting', op) + + // There's a few goals for this first traversal: + // - For any edits inside an insert, bake the edit + // - Decide if there are embedded edits we need to reverse-transform to the pick site + // - For picks and drops, simply rewrite them. + // + // Note subDoc is always in drop context (in the original document). + function invertSimple(r: ReadCursor, w: WriteCursor, subDoc: Doc | undefined) { + if (!RELEASE_MODE) log('invertSimple', r.getPath(), subDoc) + incPrefix() + const c = r.getComponent() + let insertHere, subdocModified = false + + if (c) { + // TODO: Consider using a slot map so invert returns the same operations as + // normalize(). + if (c.p != null) { + w.write('d', c.p) // p -> d + heldPick[c.p] = r.clone() + } + if (c.r !== undefined) w.write('i', c.r) // r -> i + + if (c.d != null) { + w.write('p', c.d) // d -> p + subDoc = undefined + } + if (c.i !== undefined) { + // If subdoc is set (not undefined), we're either an insert or a direct + // child of an insert. In this case this method will return either + // undefined (no change to the insert) or it will return the updated + // insert child. We ripple up in this case making shallow copies and + // replace the remove with that. + subDoc = insertHere = c.i // i -> r, written out below. + } + + const t = getEditType(c) + if (t) { + log('found edit', c, subDoc) + if (subDoc === undefined) { + if (!editsToTransform) editsToTransform = new Set() + editsToTransform.add(c) + } else { + // Mark that we want to modify the subdoc with the edit. There's a + // potential corner case where the operation has an edit, and then + // inside the edited content we do a drop / insert. But this is + // invalid, so I'm not going to worry too much about it here. Ideally + // checkOp should pick that sort of thing up. + log('baking edit into subDoc', subDoc, getEdit(c)) + subDoc = t.apply(subDoc, getEdit(c)) + subdocModified = true + } + } + } + + // This is to handle drops / inserts while we're inside an insert, to access + // indexes correctly in subDoc. + let dropOff = 0 + + for (const key of r) { + w.descend(key) + + const raw = typeof key === 'number' ? key - dropOff : key + const childIn = maybeGetChild(subDoc, raw) + // childOut is undefined if either we aren't in an insert or the subdoc wasn't mutated. + if (hasDrop(r.getComponent())) dropOff++ + + const childOut = invertSimple(r, w, childIn) + log('key', key, 'raw', raw, childIn, childOut, 'subdoc', subDoc) + // TODO: subDoc !== undefined check here might be redundant. + if (subDoc !== undefined && childOut !== undefined) { + if (!subdocModified) { + subdocModified = true + // And shallow clone + subDoc = shallowClone(subDoc) + } + if (!isValidKey(subDoc, raw)) throw Error('Cannot modify child - invalid operation') + ;(subDoc as any)[raw] = childOut + } + + w.ascend() + } + + decPrefix() + if (insertHere !== undefined) { + w.write('r', subDoc) + } else { + return subdocModified ? subDoc : undefined + } + } + + invertSimple(r, w, undefined) + if (!RELEASE_MODE) log('invert after pass 1', w.get()) + + if (editsToTransform) { + // Ok, we need to reverse-transform the edit here by the operation. We're + // traversing in the drop context (so rDrop is never null) but we also need + // to know the pick context to be able to fix indexes. + // + // w is in the pick context. + function transformEdits(rPick: ReadCursor | null, rDrop: ReadCursor, w: WriteCursor) { + if (!RELEASE_MODE) log('invXE', rPick?.getPath(), rDrop.getPath(), w.getPath()) + incPrefix() + + const cd = rDrop.getComponent() + if (cd) { + const dropSlot = cd.d + if (dropSlot != null) { + log('teleporting to drop slot', dropSlot) + rPick = heldPick[dropSlot] + w = heldWrites[dropSlot] = writeCursor() + } + + if (editsToTransform!.has(cd)) { + const t = getEditType(cd)! + // TODO: Add support for types which only have invertWithDoc. + if (!t.invert) throw Error(`Cannot invert subtype ${t.name}`) + if (!RELEASE_MODE) log('inverting subtype', t.name, getEdit(cd)) + writeEdit(w, t, t.invert(getEdit(cd))) + } + } + + // And recurse. + let pickOff = 0, dropOff = 0 + const ap = advancer(rPick, + (k, c) => hasPick(c) ? (pickOff - k - 1) : k - pickOff, + (k, c) => { if (hasPick(c)) pickOff++ }) + + for (const key of rDrop) { + if (typeof key === 'number') { + const mid = key - dropOff + const _rPick = ap(mid) + const raw = mid + pickOff + log('descend', key, mid, raw, 'dropOff', dropOff, 'pickOff', pickOff) + w.descend(raw) + transformEdits(_rPick, rDrop, w) + if (hasDrop(rDrop.getComponent())) dropOff++ + w.ascend() + } else { + w.descend(key) + const _rPick = ap(key) + transformEdits(_rPick, rDrop, w) + w.ascend() + } + + } + + ap.end() + decPrefix() + } + + w.reset() + transformEdits(r.clone(), r, w) + + if (heldWrites.length) { + if (!RELEASE_MODE) log('Merging held writes', heldWrites.map(w => w.get())) + w.reset() + + // Merge held writes + r.traverse(w, (c, w) => { + log('traverse', c) + const slot = c.p + if (slot != null) { + log('merging slot', slot) + const _w = heldWrites[slot] + if (_w) log('merge', _w.get()) + if (_w) w.mergeTree(_w.get()) + } + }) + } + } + + const result = w.get() + log('-> invert returning:', result) + if (!RELEASE_MODE) checkValidOp(result) + return result +} + +// Does an operation contain a remove? +const anyComponent = (op: JSONOpList, fn: (c: JSONOpComponent) => boolean): boolean => ( + op.some(c => ( + typeof c === 'object' + ? (Array.isArray(c) ? anyComponent(c, fn) : fn(c)) + : false + )) +) + +/** + * Make an operation invertible. (So, you can call invert(op) after this). + * + * This is needed because r:XX contents are optional in an operation, and may be + * optional in subtypes as well. (Eg text-unicode). + * + * This method does two main things: + * + * - Fills in r:{} bodies in components by copying data in from the document + * - Recursively calls makeInvertible on any types embedded in the operation. + * + * Note transform (and compose?) discards remove information, so if you call + * transform on an invertible operation you will need to call makeInvertible + * again before inverting. + */ +function makeInvertible(op: JSONOp, doc: Doc) { + // Sooo, this method is a bit funny. Its become in many ways a + // reimplementation of apply(), because it needs to deeply understand how the + // operation will be overlaid on top of the document. Both functions could + // easily be merged - although because apply was written much earlier, it + // doesn't use the cursor utility code. Its also probably a little bit faster + // as a result. + + log('makeInvertible', op, doc) + if (op == null || !anyComponent(op, c => ( + c.r !== undefined || getEditType(c)?.makeInvertible != null + ))) return op + + const r = new ReadCursor(op) + const w = new WriteCursor() + + // Basically we're going to copy r to w and traverse doc along the way. When + // we run into r: components we'll clone from doc. + + // TODO: Currently this only shallow clones the removed sections in, which is + // probably not safe given how people will use this library. + + // Unfortunately this needs to be a lot more complicated for edits. For edits + // we need to effectively partially unapply / reverse transform the operation + // to be able to figure out where in the original document the edited content + // comes from. We'll only run that extra pass if we determine its needed. + let hasEdits = false + const heldPick: ReadCursor[] = [] + const heldDoc: Doc[] = [] + + const traversePick = (r: ReadCursor, w: WriteCursor, subDoc: Doc | undefined) => { + if (!RELEASE_MODE) log('traversePick', r.getPath(), subDoc) + incPrefix() + const c = r.getComponent() + let modified = false + + if (c) { + // These two can just be copied directly. Its arguably cleaner to copy + // them in the second pass, but this is fine. + if (c.d != null) w.write('d', c.d) + if (c.i !== undefined) w.write('i', c.i) + + const pickSlot = c.p + if (pickSlot != null) { + heldPick[pickSlot] = r.clone() // for second pass. + assert(subDoc !== undefined, 'Operation picks up at an invalid key') + heldDoc[pickSlot] = subDoc + w.write('p', c.p) + } + + // Copy remove - but deep clone from document + if (c.r !== undefined) { + if (subDoc === undefined) throw Error('Invalid doc / op in makeInvertible: removed item missing from doc') + // Actual remove logic handled after the traversal. + // w.getComponent() + } + + // We'll need to run the second pass to find out where in the resulting + // document this edit will apply. + const t = getEditType(c) + if (t) { + // This feels a little dangerous. If the subtype doesn't need + // makeInvertible called, we don't need to do the second pass to copy it + // in... right? + if (t.makeInvertible) hasEdits = true + else writeEdit(w, t, getEdit(c), true) + } + } + + // There's a tricky problem here. If we're traversing a list and some of the child + // keys need to be removed from subdoc, we'll mess up the list's order. + let listOff = 0 + + // Recurse to children + for (const key of r) { + w.descend(key) + + const keyRaw = typeof key === 'number' + ? key - listOff + : key + const childIn = maybeGetChild(subDoc, keyRaw) + const childOut = traversePick(r, w, childIn) + + if (childIn !== childOut) { + log('childOut != childIn', childIn, childOut, key, keyRaw) + if (!modified) { + modified = true + subDoc = shallowClone(subDoc as Doc) + } + + // removeChild also does a shallow clone, which isn't strictly needed here. + if (childOut === undefined) { + subDoc = removeChild(subDoc as any, keyRaw) + if (typeof key === 'number') listOff++ + } else (subDoc as any)[keyRaw] = childOut + log('subDoc ->', subDoc) + } + w.ascend() + } + + if (c) { + if (c.r !== undefined) { + log('write r', subDoc) + + // Sooo I'm not sure whether we should clone here. The function is + // correct without the call to clone - but making deep references to + // parts of the document from the returned operation is risky and error + // prone. There's probably some neat tricks we could do in this method + // to avoid the clone here - but I think this a fine solution (since + // usually removed sections will be small anyway) + w.write('r', deepClone(subDoc as Doc)) + subDoc = undefined + } else if (c.p != null) { + // Its gone somewhere else. Note that anything that was picked up cannot + // have also been removed, so this is safe. + subDoc = undefined + } + } + + log('tp returning', subDoc) + decPrefix() + return subDoc + } + + traversePick(r, w, doc) + log('after traversePick', w.get()) + + if (hasEdits) { + w.reset() + + function traverseDrop(rPick: ReadCursor | null, rDrop: ReadCursor, + w: WriteCursor, subDoc: Doc | undefined, isLiteral: boolean) { + + if (!RELEASE_MODE) log('traverseDrop', rPick?.getPath(), rDrop.getPath(), w.getPath(), subDoc) + incPrefix() + + const c = rDrop.getComponent() + + if (c) { + if (c.i !== undefined) { + subDoc = c.i + isLiteral = true + log('using inserted subdoc', subDoc) + } else if (c.d != null) { + subDoc = heldDoc[c.d] + rPick = heldPick[c.d] + isLiteral = false + log('teleporting to pick', c.d, subDoc) + } + + let t = getEditType(c) + if (t && t.makeInvertible) { + const edit = getEdit(c) + log('makeInvertible on child', edit, subDoc) + writeEdit(w, t, t.makeInvertible(edit, subDoc), true) + } + } + + // Recurse. + let pickOff = 0, dropOff = 0 + const ap = advancer(rPick, + (k, c) => hasPick(c) ? (pickOff - k - 1) : k - pickOff, + (k, c) => { if (hasPick(c)) pickOff++ }) + + for (const key of rDrop) { + if (typeof key === 'number') { + const mid = key - dropOff + const _rPick = ap(mid) + const raw = mid + pickOff + log('key', key, 'mid', mid, 'raw', raw, 'isLiteral', isLiteral) + + const child = maybeGetChild(subDoc, isLiteral ? mid : raw) + w.descend(key) + traverseDrop(_rPick, rDrop, w, child, isLiteral) + if (hasDrop(rDrop.getComponent())) dropOff++ + w.ascend() + } else { + const child = maybeGetChild(subDoc, key) + w.descend(key) + traverseDrop(ap(key), rDrop, w, child, isLiteral) + w.ascend() + } + } + + ap.end() + decPrefix() + } + + traverseDrop(r.clone(), r, w, doc, false) + } + + const result = w.get() + log('-> makeInvertible returning', result) + if (!RELEASE_MODE) checkValidOp(result) + return result +} + +/** + * Invert the given operation in the context of the passed document. The + * specified document must be the document *before* the operation has been + * applied. + */ +function invertWithDoc(op: JSONOp, doc: Doc) { + return invert(makeInvertible(op, doc)) +} + +// ***** Transform + +const LEFT = 0, RIGHT = 1 + +// This makes a shallow clone of an operation so a reader can read a partially +// written operation. The components aren't copied - which means we don't deep +// copy any inserted objects, and the components can be edited directly, which +// is a bit of a hack that transform takes advantage of. +const shallowCloneOp = (op: JSONOp) => { + if (op == null) return null + const result = op.slice() + for (let i = 0; i < op.length; i++) { + const c = result[i] + if (Array.isArray(c)) result[i] = shallowCloneOp(c) as JSONOpList + } + return result +} + + + +// Returns {ok: bool, result or conflicts}. Conflicts should be symmetric - +// transform(a, b, left) should generate the same list of conflicts as +// transform(b, a, right). But obviously with path1 and path2 swapped. +// +// To keep this function as simple as possible, we bail once we find +// conflicts. Thus running this method a single time does *not* find all +// conflicts in the two operations. I could write a version of this method +// which had that behaviour, but I don't see the point. Most operations are +// simple and conflicts are rare. +// +// Conflicts have {type, op1: (slice), op2: (slice)}. +function tryTransform(op1: JSONOp, op2: JSONOp, direction: 'left' | 'right'): { + ok: true, result: JSONOp +} | { + ok: false, conflict: Conflict +} { + assert(direction === 'left' || direction === 'right', 'Direction must be left or right') + const side = direction === 'left' ? LEFT : RIGHT + ;(log as any).quiet = !debugMode + ;(log as any).prefix = 0 + + if (op2 == null) return {ok:true, result:op1} + + if (!RELEASE_MODE && debugMode) { + log("transforming " + direction + ":") + log('op1', op1) + log('op2', op2) + + log(`repro: transform(${[op1, op2, direction].map(x => JSON.stringify(x)).join(', ')})`) + } + + checkValidOp(op1) + checkValidOp(op2) + + // This is for debugging, to verify that we only descend once. + const uniqPaths: {[k: string]: Set} = {} // key -> set + const uniqDesc = (key: string, reader?: ReadCursor | null) => { + if (reader == null) return + if (uniqPaths[key] == null) uniqPaths[key] = new Set() + + // log('uniqDesc', key, reader.getPath()) + const path = reader.getPath().map((k,i) => `${i}:${k}`).join('.') + if (uniqPaths[key].has(path)) throw Error(`non-unique descent: ${key} ${path}`) + uniqPaths[key].add(path) + } + + // Transform is a super fun function to write. + // + // Semantically, we have 4 mini operations between the two ops: + // - op1 picks + // - op1 drops + // + // - op2 picks + // - op2 drops + // + // And we generate two more mini operations: + // - result picks + // - result drops + // + // You can consider each of the mini operations to live in one of these contexts: + // + // - The context of the original document (which op1 picks and op2 picks live in) + // - The context of after op1 has been applied (op1 drops) + // - The context of after op2 has been applied (op2 drops, resulting op picks) + // - And the resulting document context (resulting op drops) + // + // There's effectively 2 transformations that have to take place: + // 1. op1's picks. Effectively, this needs to be transformed by [op2 pick, + // op2 drop]. + // 2. op1's drops. This is where it gets fun. Because the positions in op1's + // drops track positions already modified by op1's picks, we need to figure + // out where they *would* be without op1's drops. Then transform through + // op2's picks and drops, and finally transform by the resulting picks to get + // the final drop locations. + // + // We do this work in several steps: + // - Scan op2 picks + // - Scan op2 drops + // - Scan op1 picks. Transform by op2 and write into result + // - Scan op1 drops. Transform back by op1 picks, by op2, then by op1 picks + // again. Write into result. + // - Scan op2 drops again in their native context, writing removes when op2 + // is dropped. + // - Scan op2 drops a final time, transforming into the resulting document. + // Write any contents of op1 that was transformed by an op2 move. + + let conflict: null | Conflict = null + + // These arrays hold read cursors into the op1 picks & op2 drops so we can + // read the right value through teleporting, and so we can reconstruct paths + // for conflicts. + + // TODO: Actually some of these aren't sparse, but filled to the brim by the + // time we read them. + type SparseRCList = (ReadCursor | null)[] + const heldOp1PickByOp1: ReadCursor[] = [] + const heldOp1DropByOp1: SparseRCList = [] + + const heldOp2PickByOp2: SparseRCList = [] // Used in scanOp2Drop + const heldOp2DropByOp2: SparseRCList = [] // Used in writeOp1Pick + + const heldOp1PickByOp2: SparseRCList = [] + + const heldOp2PickByOp1: SparseRCList = [] // Used in writeOp1Drop + const heldOp2DropByOp1: ReadCursor[] = [] // Used in writeOp1Drop + + const heldOp2RmForOp1: SparseRCList = [] // held op1 read cursor to a remove of a slot2. + + // TODO(cleanup): Consider just storing the path instead of the read cursor + const heldOp1RmForOp2: SparseRCList = [] // Set to the remove op if cancelledOp2 is set + + // Map from op2's slots -> true if op2 is moving an object out of a location + // that op1 removes. In this case we need to add a remove at op2's + // destination. + // TODO(cleanup): Can I remove cancelledOp2 and just use heldOp1RmForOp2? + const cancelledOp2: (true | undefined)[] = [] + + // Map from op2 slot -> true if op2's drop destination is removed by op1. + // This is used to discard some removes in the output + const discardedOp2Drop: (true | undefined)[] = [] + + // These hold write cursors, indexed by op2 slot numbers. This is used to + // stage any op1 operations that have been moved by op2. Anything in the + // designated held write will be written out at the end + const heldPickWrites: WriteCursor[] = [] + const heldDropWrites: WriteCursor[] = [] + + // slot2 number -> slot1 number when both ops pick up the same value. + const op1PickAtOp2Pick: number[] = [] + + // For blackhole detection. Each entry here is a list of op2 drops that the + // indexing slot1 contains. Consider inverting this. + const op1PicksOp2DropSlots: number[][] = [] + + // const op2MoveContainingOutDrop = [] // indexed by op1 slot. Slot of op2 that moves us + + let nextSlot = 0 // Move slot numbers for the output. + + const r1 = readCursor(op1) + const r2 = readCursor(op2) + const w = writeCursor() // Output. + + // --------------------------------------------------------------------- + // Before anything else we need to do a preliminary scan of op2's pick for + // bookkeeping, marking: + // - op2 moves of items which op1 has removed + // - shared pickup locations between the two ops. + // + // This scan is in the context of the original doc. + function scanOp2Pick(r2Pick: ReadCursor, r1Pick: ReadCursor | null = null, removed1: ReadCursor | null) { // r1Pick might be null. + if (!RELEASE_MODE) log('scanOp2Pick', 'r1Pick', r1Pick && r1Pick.getPath(), 'r2Pick', r2Pick.getPath(), 'removed1:', removed1 && removed1.getPath()) + incPrefix() + + const c1 = getComponent(r1Pick) + if (c1) { + if (c1.r !== undefined) removed1 = r1Pick!.clone() + else if (c1.p != null) { + removed1 = null + heldOp2PickByOp1[c1.p] = r2Pick.clone() + } + } + + const c2 = r2Pick.getComponent() + let slot2 + if (c2 && ((slot2 = c2.p) != null)) { + log('c2', c2, 'rm', !!removed1) + // log(slot2, c1, c2) + heldOp1PickByOp2[slot2] = r1Pick ? r1Pick.clone() : null + heldOp2PickByOp2[slot2] = r2Pick.clone() + if (removed1) { + // op1 removes something op2 picks up in slot2. + cancelledOp2[slot2] = true + heldOp1RmForOp2[slot2] = removed1 + log('Cancelled op2 slot', slot2) + } + + // If this happens it means both operations picked up the same element. + // One operation's move will be cancelled based on symmetry. + if (c1 && c1.p != null) op1PickAtOp2Pick[slot2] = c1.p + } + + // ... And recurse. + const ap1 = advancer(r1Pick) + for (const key of r2Pick) { + log('->', key) + scanOp2Pick(r2Pick, ap1(key), removed1) + } + ap1.end() + + decPrefix() + } + + scanOp2Pick(r2, r1, null) + + if (!RELEASE_MODE) { + log('op1PickAtOp2Pick', op1PickAtOp2Pick) + log('cancelledOp2', cancelledOp2) + log('held op2 pick', heldOp2PickByOp1.map(x => !!x)) + } + + // --------------------------------------------------------------------- + // This traverses op2's drop locations for bookkeeping, marking: + // - Fetch a (cloned) read cursor at each op2 drop location + // - Any drops which have been moved into something that was deleted by op1 + function scanOp2Drop( + r1Pick: ReadCursor | null, r2Pick: ReadCursor | null, + r2Drop: ReadCursor, pickSlot1: number | null, removed1: ReadCursor | null) { // r1Pick, r2Pick could be null. + if (!RELEASE_MODE) log('scanOp2Drop', + 'r1Pick', r1Pick && r1Pick.getPath(), r2Pick && r2Pick.getPath(), 'r2Drop', r2Drop.getPath(), + 'pickSlot1', pickSlot1, 'removed:', removed1 && removed1.getPath()) + incPrefix() + + const c2d = r2Drop.getComponent() + let droppedHere = false + + let slot2 + if (c2d) { + if (((slot2 = c2d.d) != null)) { + // Clone the read cursor so we can come back here later. + heldOp2DropByOp2[slot2] = r2Drop.clone() + + if (pickSlot1 != null) { + if (op1PicksOp2DropSlots[pickSlot1] == null) op1PicksOp2DropSlots[pickSlot1] = [] + // op1PicksOp2DropSlots[slot2] = pickSlot1 //pickSlot1.length ? pickSlot1.slice() : null + op1PicksOp2DropSlots[pickSlot1].push(slot2) + } + + // Teleport r2Pick + log('tele r2Pick to op2 slot2', slot2, !!cancelledOp2[slot2]) + r1Pick = heldOp1PickByOp2[slot2] || null + r2Pick = heldOp2PickByOp2[slot2] || null // TODO: Or are these always set here? + + if (cancelledOp2[slot2]) { + if (removed1) discardedOp2Drop[slot2] = true + removed1 = heldOp1RmForOp2[slot2] || null + } else if (removed1 && (side === RIGHT || op1PickAtOp2Pick[slot2] == null)) { + // We have a write conflict. + log('conflicting op2 move because drop destination removed', slot2) + if (!RELEASE_MODE) log('path', r2Drop.getPath(), r2Pick!.getPath()) + if (conflict == null) conflict = { + type: ConflictType.RM_UNEXPECTED_CONTENT, + op1: removeOp(removed1.getPath()), + op2: moveOp(r2Pick!.getPath(), r2Drop.getPath()) + } + } + + droppedHere = true + } else if (c2d.i !== undefined) { + // if (r1Pick != null || r2Pick != null) debugger + // assert(r1Pick == null && r2Pick == null) + r1Pick = r2Pick = null + droppedHere = true + + if (removed1) { + // TODO: It'd be nice to merge this with the conflict raise above + log('Conflicting op2 drop because op1 remove') + if (conflict == null) conflict = { + type: ConflictType.RM_UNEXPECTED_CONTENT, + op1: removeOp(removed1.getPath()), + op2: insertOp(r2Drop.getPath(), c2d.i) + } + } + } + } + + const c1p = getComponent(r1Pick) + if (c1p) { + if (c1p.r !== undefined) removed1 = r1Pick!.clone() + else if (c1p.p != null) { + log('Marking pickSlot1', c1p.p) + // pickSlot1.push(c1p.p) + pickSlot1 = c1p.p + removed1 = null + } + } + + const t2 = getEditType(c2d) + if (t2 && removed1) { + // TODO(cleanup): Merge this with the big block above if I can. Might be + // able to move the code rewriting removed1 to the top of the function. + log('rm / edit conflict') + if (conflict == null) conflict = { + type: ConflictType.RM_UNEXPECTED_CONTENT, + op1: removeOp(removed1.getPath()), + op2: editOp(r2Drop.getPath(), t2, getEdit(c2d!), true), + } + } + + // And recurse... + let p2PickOff = 0, p2DropOff = 0 + const ap2 = advancer(r2Pick, + (k, c) => hasPick(c) ? (p2PickOff - k - 1) : k - p2PickOff, + (k, c) => { if (hasPick(c)) p2PickOff++ }) + + // This is trivial since it matches the pick2 context. + const ap1 = advancer(r1Pick) + + for (const key of r2Drop) { + log('key ->', key) + if (typeof key === 'number') { + + const p2Mid = key - p2DropOff + log('p2Mid', p2Mid) + const _p2Pick = ap2(p2Mid) + const raw = p2Mid + p2PickOff + log('raw', raw) + const _p1Pick = ap1(raw) + + log('key', key, 'p2DropOff', p2DropOff, 'p2PickOff', p2PickOff) + log('-> p2drop', key, 'p2Mid', p2Mid, !!_p2Pick, 'raw', raw, !!_p1Pick) + + p2DropOff += +scanOp2Drop(_p1Pick, _p2Pick, r2Drop, pickSlot1, removed1) + } else { + log('-> k', key) + + const _p2Pick = ap2(key) + const _p1Pick = ap1(key) + scanOp2Drop(_p1Pick, _p2Pick, r2Drop, pickSlot1, removed1) + } + } + + ap2.end() + ap1.end() + decPrefix() + + // if (c1p && c1p.p != null) pickSlot1.pop() + + return droppedHere + } + + scanOp2Drop(r1, r2, r2.clone(), null, null) + log('held op2 drop', heldOp2DropByOp2.map(x => x && x.get())) + if (conflict) { + log('returning conflict 4', conflict) + return {ok:false, conflict} + } + log('discarded op2 drop', discardedOp2Drop.map(x => !!x)) + + log('op1PicksOp2DropSlots', op1PicksOp2DropSlots) + + // --------------------------------------------------------------------- + + // We could almost (but not quite) double up on pickComponents and + // heldOp1PickByOp1 --- heldOp1PickByOp1 shows us the original component, but + // pickComponents are actually write components, so its no good. + const pickComponents: (null | JSONOpComponent)[] = [] + + let cancelledRemoves: null | Set = null // Lazy initialized set of components. + + // ==== A brief note on blackholes: + // + // We need to look out for instances where the two operations try to mutually + // move data into the other. + // + // This always looks the same: + // op1 pickup contains an op2 drop as a child + // op2 pickup contains an op1 drop as a child + // ... With matching slot numbers. + + // This handles the pick phase of op (r1). Picks and removes in r1Pick are + // transformed via op2 (r2Pick and r2Drop) and written into writer w. + // + // removed tracks op2's removals. + function writeOp1Pick( + r1Pick: ReadCursor, r2Pick: ReadCursor | null, + r2Drop: ReadCursor | null, w: WriteCursor, removed2: ReadCursor | null) { + if (!RELEASE_MODE) log('writeOp1Pick', 'r1Pick', r1Pick.getPath(), 'r2Pick', r2Pick && r2Pick.getPath(), + 'r2Drop', r2Drop && r2Drop.getPath(), + 'w', w.getPath(), + 'removed2', removed2 && removed2.getPath() + ) + incPrefix() + // p1Pick and w are always defined. Either or both of r2Pick and r2Drop + // could be null. + let iAmMoved = false + + const c2p = getComponent(r2Pick) + if (hasPick(c2p)) { + const slot2 = c2p!.p + if (slot2 != null) { + // op2 has moved the item we're descending into. Instead of taking + // picks from the original (source) location, take them from the + // destination location. + if (!RELEASE_MODE) log('teleporting via op2 slot2', slot2, 'to', heldOp2DropByOp2[slot2]!.getPath()) + + // This breaks the holding logic. Might still need it though - might + // need an au naturale version of r2Drop or something?? I'm not sure + // what this logic should be. + r2Drop = heldOp2DropByOp2[slot2]! + + w = heldPickWrites[slot2] = writeCursor() + // TODO: Does w need to be reset here? + // if (!(w = heldPickWrites[slot2])) w = heldPickWrites[slot2] = writeCursor() + iAmMoved = true + removed2 = null + } else { // op2 is a remove (c2p.r !== undefined) + // op2 deleted the item we're trying to move. + r2Drop = null + removed2 = r2Pick!.clone() + } + } else if (hasDrop(getComponent(r2Drop))) { + r2Drop = null + } + + // TODO: Could check w as well, but we'd need the destination path + // including tele somehow. + if (!RELEASE_MODE) { + uniqDesc('op1pick r1Pick', r1Pick) + uniqDesc('op1pick r2Pick', r2Pick) + uniqDesc('op1pick r2Drop', r2Drop) + } + + const c1 = r1Pick.getComponent() + if (c1) { + const slot1 = c1.p + if (slot1 != null) { + if (removed2) heldOp2RmForOp1[slot1] = removed2 + + log('considering slot', slot1, 'removed2', !!removed2, 'iAmMoved', iAmMoved) + if (removed2 || (side === RIGHT && iAmMoved)) { + log("cancelling p1 move", slot1) + // A move from a subtree that was removed2 by op2. + pickComponents[slot1] = null // redundant, but we can check this below. + } else { + log('holding pick component') + // TODO: Consider swapping this back to pickComponents + pickComponents[slot1] = w.getComponent()//w.clone() + } + + // This is used by the op2Drop scan, below. + heldOp1PickByOp1[slot1] = r1Pick.clone() + + // And this is used by writeOp1Drop + // heldOp2DropByOp1[slot1] = r2Drop ? r2Drop.clone() : null // Wrong for some reason? + if (r2Drop) heldOp2DropByOp1[slot1] = r2Drop.clone() + } else if (c1.r !== undefined) { + // Copy the remove from op1. + if (!removed2) { + log('copying remove') + w.write('r', true) //c1.r + } + + if (removed2 || iAmMoved) { + log('Cancelling remove', c1, !!removed2, iAmMoved) + if (cancelledRemoves == null) cancelledRemoves = new Set + cancelledRemoves.add(c1) + } + } + } + + // Ok, now to scan the children. We need these offsets to track how array + // offsets are changed by the transform. + let p2PickOff = 0, p2DropOff = 0 + + // advancer(cursor, listMapFn, listAdvanceFn). + const ap2Pick = advancer(r2Pick, undefined, (k, c) => { + if (hasPick(c)) { + p2PickOff++ + log('r2Pick++', p2PickOff, k, c) + } + }) + // ap2Pick._name = '2pick' + + const ap2Drop = advancer(r2Drop, (k, c) => ( + // Sneaking a second bit in here via 2s compliment. + hasDrop(c) ? ~(k - p2DropOff) : k - p2DropOff + ), (k, c) => { + if (hasDrop(c)) { + p2DropOff++ + log('r2Drop++', p2DropOff, k, c) + } + }) + // ap2Drop._name = '2drop' + + log('children', '2p', !!r2Pick, '2d', !!r2Drop) + if (r1Pick) for (const key of r1Pick) { + log('-> k', key) + if (typeof key === 'string') { + const p2Pick_ = ap2Pick(key) + const p2Drop_ = ap2Drop(key) + + w.descend(key) + writeOp1Pick(r1Pick, p2Pick_, p2Drop_, w, removed2) + w.ascend() + } else { + const p2Pick_ = ap2Pick(key) + const p2Mid = key - p2PickOff + const p2Drop_ = hasPick(getComponent(p2Pick_)) ? null : ap2Drop(p2Mid) + const finalKey = p2Mid + p2DropOff + + log("k" + key + " -> m" + p2Mid + " -> f" + finalKey) + assert(finalKey >= 0) + + w.descend(finalKey) + writeOp1Pick(r1Pick, p2Pick_, p2Drop_, w, removed2) + w.ascend() + } + } + ap2Pick.end() + ap2Drop.end() + decPrefix() + } + + log('---- pick ----') + writeOp1Pick(r1, r2, r2.clone(), w, null) + w.reset() + + if (!RELEASE_MODE) { + log('intermediate result', w.get()) + log('held picks', heldOp1PickByOp1.map(x => x.getComponent() || !!x)) + log('held op2 drops by op1', heldOp2DropByOp1.map(x => x.getComponent() || !!x)) + log('held pick writes', heldPickWrites.map(w => w.get())) + log('pc', pickComponents) + } + + // --------------------------------------------------------------------- + + // Set of op2 components with an insert or drop which is being overwritten by + // op1. Initialized lazily. Only used on LEFT. + // let overwrittenDrops = null + + // Mapping from output move slot -> op1 move slot + let outputSlotMap: number[] = [] + + // Finally the big one. This handles the semantics of op1's drop phase (move + // drop and insert). This is complicated because the drop phase inside the op + // is pre-transformed by op1's pick phase, which we have to account for when + // we're transforming. + // + // Removed tracks op2's pick removals. + function writeOp1Drop( + p1Pick: ReadCursor | null, p1Drop: ReadCursor, + p2Pick: ReadCursor | null, p2Drop: ReadCursor | null, + w: WriteCursor, removed2: ReadCursor | null /*, insidePick2*/) { + assert(p1Drop) + const c1d = p1Drop.getComponent() + if (!RELEASE_MODE) log('drop', + p1Pick && p1Pick.getPath(), p1Drop.getPath(), + p2Pick && p2Pick.getPath(), p2Drop && p2Drop.getPath(), + 'c:', c1d, 'r:', !!removed2 /*, 'ip2', insidePick2*/) + incPrefix() + + let c2d = getComponent(p2Drop) // non-const for teleporting. + let droppedHere = false + + const insOrMv = (r1: ReadCursor | null, r2: ReadCursor, c: JSONOpComponent) => ( + r1 ? moveOp(r1.getPath(), r2.getPath()) : insertOp(r2.getPath(), c.i) + ) + + // TODO: Reconsider the entire logic of this code block. It seems like + // there's a bunch of different cases based on what kind of edit we have, + // whether we conflict and whether the output is identical. There's + // probably nicer ways to write this. + if (hasDrop(c1d)) { + const slot1 = c1d!.d + + // Used to fill blackhole conflict objects + if (slot1 != null) heldOp1DropByOp1[slot1] = p1Drop.clone() + + // pc is null if the item we're moving was removed by op2 or moved & + // we're 'right'. + const pc = slot1 != null ? pickComponents[slot1] : null + + let identical = false + + if (c1d!.i !== undefined || ((slot1 != null) && pc)) { + // Times to write: + // - There is no c2 drop + // - The other drop has been cancelled + // - We are left (and then we also need to move or remove) + // If we are identical, skip everything. Neither write nor remove. + + // Doing this with logical statements was too hard. This just sets up + // some state variables which are used in conditionals below. + // let write = true + log('looking to write', c1d, c2d, cancelledOp2) + let slot2 + if (c2d && (c2d.i !== undefined || ((slot2 = c2d.d) != null && !cancelledOp2[slot2]))) { + identical = slot2 != null + ? (slot1 != null) && slot1 === op1PickAtOp2Pick[slot2] + : deepEqual(c2d.i, c1d!.i) + + // If we're the left op, we can avoid a collision if we're trying to + // move op2's drop somewhere else. + if (!identical && (slot2 == null || side === RIGHT || op1PickAtOp2Pick[slot2] == null)) { + log("Overlapping drop conflict!") + + if (conflict == null) conflict = { + type: ConflictType.DROP_COLLISION, + op1: insOrMv(slot1 != null ? heldOp1PickByOp1[slot1] : null, p1Drop, c1d!), + op2: insOrMv(slot2 != null ? heldOp2PickByOp2[slot2]! : null, p2Drop!, c2d), + } + } + } + + // log('write', write, 'identical', identical, 'removed2', removed2) + log('write', 'identical', identical, 'removed2', !!removed2) + + if (!identical) { + if (removed2) { + log("Drop into remove conflict!") + // if (pc) pc['r'] = true + // log(slot1, heldOp1PickByOp1[slot1].getPath()) + if (conflict == null) conflict = { + type: ConflictType.RM_UNEXPECTED_CONTENT, + op1: insOrMv(slot1 != null ? heldOp1PickByOp1[slot1] : null, p1Drop, c1d!), + op2: removeOp(removed2.getPath()), + } + } else { + // Copy the drops. + if (slot1 != null) { + log('copying the drop', slot1, 'using new slot', nextSlot)//, 'inside', insidePick2) + outputSlotMap[nextSlot] = slot1 + // op2MoveContainingOutDrop[nextSlot] = insidePick2 + w.write('d', pc!.p = nextSlot++) + } else { + log('copying insert') + w.write('i', deepClone(c1d!.i)) // TODO: Only clone based on opts.consume. + } + droppedHere = true + } + } + + } else if ((slot1 != null) && !pc) { + // pc is null if either slot1 was removed at the source, or if our + // move has been cancelled because op2 picked it up as well and we're + // right. + + // I'm not 100% sure this is the right implementation of the logic I want here. + // TODO: This seems straight out wrong. + // log('xxxx r2', slot1, !!heldOp2RmForOp1[slot1]) + // removed2 = p2Pick.clone() + const h = heldOp2RmForOp1[slot1] + if (h) removed2 = h.clone() + // else if (!removed2) log('BEHAVIOUR CHANGE') + } + + if (slot1 != null) { + log('teleporting p1Pick and op2Pick via op1 slot', slot1) + p1Pick = heldOp1PickByOp1[slot1] + p2Pick = heldOp2PickByOp1[slot1] + p2Drop = heldOp2DropByOp1[slot1] // Note: Potentially overwritten again below. + + if (!RELEASE_MODE) log('p1Pick', p1Pick && p1Pick.getPath(), + 'p2Pick', p2Pick && p2Pick.getPath(), + 'p2Drop', p2Drop && p2Drop.getPath()) + + } else if (c1d!.i !== undefined) { + // We're descending into an edit of an insert. Nobody else speaks to us. + // + // Consider moving this into the identical{} block above. + p1Pick = p2Pick = null + if (!identical) p2Drop = null // If identical, we'll merge the p2 child drops. + // } else if (hasPick(getComponent(p1Pick)) && identical) { + // p1Pick = p2Pick = null + } + + } else if (hasPick(getComponent(p1Pick))) { + p1Pick = p2Pick = p2Drop = null + } + + // We can't check this for writes because we teleport & hold the write cursor sometimes. + // uniqDesc('op1drop write', w) + if (!RELEASE_MODE) { + uniqDesc('op1drop op2Pick', p2Pick) + uniqDesc('op1drop op1Pick', p1Pick) + uniqDesc('op1drop op1Drop', p1Drop) + } + + const c1p = getComponent(p1Pick) + const c2p = getComponent(p2Pick) + + if (hasPick(c2p)) { + const slot2 = c2p!.p + + if ((c2p!.r !== undefined && (!c1p || c1p.r === undefined)) || cancelledOp2[slot2!]) { + // In this case the op2 move moves the children *into* something that + // we've removed in op1. If we both remove the object, then allow my + // inserts into the resulting output as if the other op didn't remove + // at all. + log('flagging remove') + p2Drop = null + removed2 = p2Pick!.clone() + } else if (slot2 != null) { + // Teleport. + log('teleporting p2Drop via c2 slot2', slot2) + p2Drop = heldOp2DropByOp2[slot2] + // insidePick2 = slot2 + // if (op1PicksOp2DropSlots[slot2] != null) log('inside pick 1', op1PicksOp2DropSlots[slot2]) + if (!RELEASE_MODE) log('p2Drop', p2Drop && p2Drop.getPath()) + if (side === RIGHT || op1PickAtOp2Pick[slot2] == null) { + log('teleporting write') + if (!(w = heldDropWrites[slot2])) w = heldDropWrites[slot2] = writeCursor() + w.reset() + removed2 = null + } + } + } else if (!hasDrop(c1d) && hasDrop(c2d)) p2Drop = null + + if (!RELEASE_MODE) uniqDesc('op1drop op2Drop', p2Drop) + + // Now we want to read both teleports for the edit component. This is just + // used for edits at this point, and it takes into account both teleports. + // I look for your edit in the place *I moved from* and *you moved to*. + c2d = p2Drop != null ? p2Drop.getComponent() : null + + // Embedded edits of subtrees. This is tricky because the embedded edit + // needs to be transformed by op2's drop (if any) + const t1 = getEditType(c1d) + if (t1) { + log('edit:', !!removed2, c1d, c2d) + const e1 = getEdit(c1d!) + if (!removed2) { + const t2 = getEditType(c2d) + let e + if (t2) { + // TODO: Should this be t1.name vs t2.name ? + if (t1 !== t2) throw Error("Transforming incompatible types") + const e2 = getEdit(c2d!) + e = t1.transform(e1, e2, direction) + } else { + // TODO: ... check opts.consume here. + e = deepClone(e1) + } + log('write edit', e) + writeEdit(w, t1, e) + + //writeEdit(w, t1, getEdit(c1d)) + } else { + // The embedded edit is trying to edit something that has been removed + // by op2. + // + // This branch also triggers if the embedded edit is a no-op. I'm not + // sure what the expected behaviour should be in this case, because both + // options are bad: + // - We can return `null` as op1, even though this wouldn't trigger the + // conflict if we re-ran transform on the returned result + // - We preserve the no-op in the conflict, returning a non-normalized + // result (current behaviour) + if (conflict == null) conflict = { + type: ConflictType.RM_UNEXPECTED_CONTENT, + op1: editOp(p1Drop.getPath(), t1, e1, true), + op2: removeOp(removed2.getPath()), + } + } + } + + // Ok, now for descent crazy town. This is all needed to deal with keeping + // track of the indexes of list children. + let p1PickOff = 0, p1DropOff = 0 + let p2PickOff = 0, p2DropOff = 0 + let outPickOff = 0, outDropOff = 0 + + let p1pValid = p1Pick != null ? p1Pick.descendFirst() : false + let p1pDidDescend = p1pValid + + const ap2p = advancer(p2Pick, undefined, (k, c) => { + // Spoilers: There's going to be more logic here before this is correct. + // Probably also need to take into account op1PickAtOp2Pick. + // if (c && (c.r !== undefined || (c.p != null && !cancelledOp2[c.p]))) { + if (hasPick(c)) { + p2PickOff++ + log('p2Pick++', p2PickOff, k, c) + } + }) + // ap2p._name = '2p' + + // I'm only using one advancer here because it was too hard tracing bugs + // through two advancers. Simply using bare variables & advancing here + // results in more code, but its slightly more straightforward because all + // the state variables are local. + + let p2dValid = p2Drop != null ? p2Drop.descendFirst() : false + let p2dDidDescend = p2dValid + + log('children.', '1p:', !!p1Pick, '1d:', !!p1Drop, '2p:', !!p2Pick) + + for (const key of p1Drop) { + log('-> p1Drop looking at child', key) + // Ok... time for crazy. + if (typeof key === 'number') { + // *********** Array children. + + let _p1Pick + log('p1DropEachChild k:', key, + 'p1d:', p1DropOff, 'p1p:', p1PickOff, 'raw: -', + 'p2p:', p2PickOff, 'p2d:', p2DropOff, + 'op:', outPickOff, 'od:', outDropOff) + + const hd1 = hasDrop(p1Drop.getComponent()) + + const k1Mid = key - p1DropOff // Position between pick and drop phase + + { // Calculate _p1Pick and outPickOff + let p1k + log('Looking for p2 Pick at k1Mid', k1Mid) + incPrefix() + while (p1pValid && typeof (p1k = p1Pick!.getKey()) === 'number') { + p1k += p1PickOff + const c = p1Pick!.getComponent()! + const hp = hasPick(c) + log('p1k', p1k, 'k1mid', k1Mid, 'hp', hp) + + if (p1k > k1Mid || (p1k === k1Mid && (!hp || (side === LEFT && hd1)))) break + // break if p1k === k1Mid and hd1 + // increment if p2k < k1Mid + + if (hp) { + log('p1PickOff--') + p1PickOff-- + + // We want to increment outPickOff if a pick is going to appear + // here in the output. That means sort of reimplementing the + // logic above :( + const slot1 = c.p + log('considering outPickOff--', c, cancelledRemoves, pickComponents[slot1!], op1PickAtOp2Pick.includes(slot1!)) + log(c.d != null, getComponent(heldDropWrites[c.d!]), hasPick(getComponent(heldDropWrites[c.d!]))) + + // TODO: This looks wrong - we aren't taking into account identical pick/drop. + if ((c.r !== undefined && (!cancelledRemoves || !cancelledRemoves.has(c))) + || (slot1 != null && pickComponents[slot1] && (side === RIGHT || !op1PickAtOp2Pick.includes(slot1)))) { + log('outPickOff-- from pickup') + outPickOff-- + } + } + p1pValid = p1Pick!.nextSibling() + } + _p1Pick = p1pValid && p1k === k1Mid ? p1Pick : null + decPrefix() + log('_p1Pick', !!_p1Pick) + } + + const raw = k1Mid - p1PickOff // Position of the child before op1 has changed it + log('ap2p', raw) + let _p2Pick = ap2p(raw) // Advances p2PickOff. + log('_p2Pick', !!_p2Pick, 'p2PickOff', p2PickOff) + + const k2Mid = raw - p2PickOff + + let _p2Drop: ReadCursor | null = null + { // Calculate _p2Drop. + let p2dk, op2Mid + log('Looking for p2 Drop at p2mid', k2Mid) + incPrefix() + while (p2dValid && typeof (p2dk = p2Drop!.getKey()) === 'number') { + op2Mid = p2dk - p2DropOff + const c = p2Drop!.getComponent()! + const hd2 = hasDrop(c) + log('p2d looking at child', p2dk, c, 'at op2mid', op2Mid, 'target', k2Mid, + ' / raw', raw, p2DropOff, hd2, hd1) + + if (op2Mid > k2Mid) break + + if (op2Mid === k2Mid) { + if (hd2) { + if (side === LEFT && hd1) { + _p2Drop = p2Drop! + break + } + + // I'm not convinced this logic is correct.. :/ + // log('hp2 break', _p2Pick && hasPick(_p2Pick.getComponent()) + const hp2 = _p2Pick && hasPick(_p2Pick.getComponent()) + if (side === LEFT && hp2) break + } else { + _p2Drop = p2Drop! + break + } + } + + // log('p2d continuing') + + if (hd2) { + // Ugh to reimplementing the logic of figuring out where we'll keep picks + const slot2 = c.d! + log('considering p2Drop', c, slot2, cancelledOp2[slot2], op1PickAtOp2Pick[slot2]) + if (c.i !== undefined + || (!cancelledOp2[slot2] && ((op1PickAtOp2Pick[slot2] == null) || side === RIGHT))) { + log('p2DropOff++ from drop') + p2DropOff++ + } else if (cancelledOp2[slot2] || (op1PickAtOp2Pick[slot2] != null && side === LEFT)) { + // Skip it, but this drop won't appear in the output either so ignore it. + log('skipped because the drop was cancelled') + p2DropOff++ + outDropOff-- + } + + // log('p2Drop++', p2DropOff, op2Mid, c) + } + p2dValid = p2Drop!.nextSibling() + } + //_p2Drop = p2dValid && op2Mid === k2Mid ? p2Drop : null + decPrefix() + log('_p2Drop', !!_p2Drop) + } + + const k2Final = k2Mid + p2DropOff + + log('->DropEachChild k:', key, + 'p1d:', p1DropOff, 'p1p:', p1PickOff, 'raw:', raw, + 'p2p:', p2PickOff, 'p2d:', p2DropOff, + 'op:', outPickOff, 'od:', outDropOff) + + const descend = k2Final + outPickOff + outDropOff + + log("k: " + key + " -> mid " + k1Mid + + " -> raw " + raw + + " -> k2Mid " + k2Mid + " -> k2Final " + k2Final + + " -> descend " + descend) + + assert(descend >= 0, 'trying to descend to a negative index') + w.descend(descend) + + if (hd1) { + // If op1 is dropping here, we'll insert a new item in the list. + // Because op2 doesn't know about the new item yet, there's nothing + // we need to transform the drop against. + _p1Pick = _p2Pick = _p2Drop = null + log('omg p1dropoff', p1DropOff) + p1DropOff++ + } + + if (writeOp1Drop(_p1Pick, p1Drop, _p2Pick, _p2Drop, w, removed2)) outDropOff++ + w.ascend() + + } else { // String keys - descending from an object. + let p1k + while (p1pValid) { // Skip items in p1Pick until key + p1k = p1Pick!.getKey() + if (typeof p1k === 'string' && (p1k > key || p1k === key)) break + p1pValid = p1Pick!.nextSibling() + } + const _p1Pick = (p1pValid && p1k === key) ? p1Pick : null + const _p2Pick = ap2p(key) + + let p2dk + while (p2dValid) { + p2dk = p2Drop!.getKey() + if (typeof p2dk === 'string' && (p2dk > key || p2dk === key)) break + p2dValid = p2Drop!.nextSibling() + } + const _p2Drop = p2dValid && p2dk === key ? p2Drop : null + // aka, _p2Drop = ap2d(key) + + // And recurse. + w.descend(key) + writeOp1Drop(_p1Pick, p1Drop, _p2Pick, _p2Drop, w, removed2) + w.ascend() + } + } + + ap2p.end() + if (p1pDidDescend) p1Pick!.ascend() + if (p2dDidDescend) p2Drop!.ascend() + decPrefix() + return droppedHere + } + + log('----- drop -----') + writeOp1Drop(r1, r1.clone(), r2, r2.clone(), w, null) + if (conflict) { + log('returning conflict 3', conflict) + return {ok:false, conflict} + } + w.reset() + + if (!RELEASE_MODE) { + log('output slot map', outputSlotMap) + log('merging picks into partial output', w.get()) + log('held pick writes', heldPickWrites.map(w => w.get())) + } + + const eachDrop = (r: ReadCursor, w: W, + fn: (slot: number, r: ReadCursor, w: W) => void) => r.traverse(w, (c, w) => { + if (c.d != null) fn(c.d, r, w) + }) + + if (cancelledOp2.length || heldPickWrites.length) { + log('merging') + eachDrop(r2, w, (slot2, r, w) => { + if (cancelledOp2[slot2] && !discardedOp2Drop[slot2]) { + log('removing at held drop2', slot2) + w.write('r', true) + } + + if (heldPickWrites[slot2]) { + w.mergeTree(heldPickWrites[slot2].get()) + } + }) + w.reset() + if (!RELEASE_MODE) log('after merge', w.get()) + } + + // --------------------------------------------------------------------- + // Finally copy in the moved writes. Here we need to pass the other readers + // because we sometimes need to adjust array indexes. + + + // const outDropPath = [] + const heldOutDropRead: ReadCursor[] = [] + + // Its like a wave slowly breaking on the beach... + const heldOutDropWrites: WriteCursor[] = [] + + + // This iterates through p2Drop + // + // Writes are transformed to the context of the output drops. + function writeHeldOp2Drop( + p2Drop: ReadCursor, outPick: ReadCursor | null, outDrop: ReadCursor | null, + w: WriteCursor, parentC: JSONOpComponent | null, removedOut: Doc) { + // TODO(cleanup): parentC is unused. Remove it. + if (!RELEASE_MODE) log('writeHeldOp2Drop', 'p2Drop', p2Drop.getPath(), + 'outPick', outPick && outPick.getPath(), 'outDrop', outDrop && outDrop.getPath(), + 'pc', parentC, 'rm out', removedOut) + ;(log as any).prefix++ + + const coutp = getComponent(outPick) + if (coutp && hasPick(coutp)) { + if (coutp.p != null) { + parentC = coutp + + const slot = coutp.p + log('teleporting writes to output slot', slot, heldOutDropRead[slot].getPath()) + outDrop = heldOutDropRead[slot] + w = heldOutDropWrites[slot] = writeCursor() + // picksOut.push(coutp.p) + } else if (coutp.r !== undefined) { + outDrop = null + removedOut = true + } + log('coutp', coutp) + } else if (hasDrop(getComponent(outDrop))) { + outDrop = null + } + + + // For drops we need to transform the location by the transform *result*. + const c2 = p2Drop.getComponent() + if (c2) { + let slot2 + if ((slot2 = c2.d) != null) { + log('c2 drop slot', slot2) + const _w = heldDropWrites[slot2] + if (_w) { + log('merge tree', _w.get()) + w.mergeTree(_w.get()) + outDrop = readCursor(_w.get()) + } + } + } + + if (!RELEASE_MODE) { + uniqDesc('outDrop p2Drop', p2Drop) + uniqDesc('outDrop outPick', outPick) + + // When outDrop is inside merged reads it won't be guaranteed to have + // unique paths. + //uniqDesc('outDrop outDrop', outDrop) + } + + let outPickOff = 0, outDropOff = 0 + + const oPickAdv = advancer(outPick, undefined, (k, c) => { + if (hasPick(c)) { + outPickOff-- + log('outPickOff++') + } + }) + + const oDropAdv = advancer(outDrop, (k, c) => ( + hasDrop(c) ? -(k - outDropOff) - 1 : k - outDropOff + ), (k, c) => { + if (hasDrop(c)) { + outDropOff++ + log('outDropOff++') + } + }) + + for (const o2dk of p2Drop) { + if (typeof o2dk === 'number') { + const _outPick = oPickAdv(o2dk) + const rmid = o2dk + outPickOff + const _outDrop = oDropAdv(rmid) + const rfinal = rmid + outDropOff + + log('->', o2dk, 'outPickOff', outPickOff, '-> rmid', rmid, 'dropOff', outDropOff, 'final', rfinal) + + w.descend(rfinal) + writeHeldOp2Drop(p2Drop, _outPick, _outDrop, w, parentC, removedOut) + w.ascend() + } else { + log('->', o2dk) + + w.descend(o2dk) + writeHeldOp2Drop(p2Drop, oPickAdv(o2dk), oDropAdv(o2dk), w, parentC, removedOut) + w.ascend() + } + } + decPrefix() + + // if (coutp && coutp.p != null) picksOut.pop() + + oPickAdv.end() + oDropAdv.end() + } + + if ((heldDropWrites.length || cancelledOp2.length) && !conflict) { + log('------ write held picks and drops -----') + if (!RELEASE_MODE) log('held drop writes', heldDropWrites.map(h => h.get())) + + // This is super nasty. The problem is that we need a read cursor over the + // output so far, but the output is still mutable (we're going to mutate + // it). I could avoid the clone entirely by doing the full transform work + // that op1 drop above does. + + // TODO: I have a feeling I might need to loop this stuff until we don't + // have any held operations. + const rOut = readCursor(shallowCloneOp(w.get())) + eachDrop(rOut, null, (slotOut, r) => { + heldOutDropRead[slotOut] = r.clone() + }) + heldDropWrites.forEach(hdw => { + if (hdw) eachDrop(readCursor(hdw.get()), null, (slotOut, r) => { + heldOutDropRead[slotOut] = r.clone() + }) + }) + + if (!RELEASE_MODE) { + log('merging writes into', rOut.get()) + log('heldOutDropRead', heldOutDropRead.map(r => r && r.getPath())) + } + writeHeldOp2Drop(r2, rOut, rOut.clone(), w, null, false) + w.reset() + + if (conflict) { + log('-> xf returning conflict 2', conflict) + return {ok:false, conflict} + } + + log('-- after writeHeldOp2Drop', w.get()) + if (!RELEASE_MODE) log('held output writes', heldOutDropWrites.map(h => h.get())) + if (heldOutDropWrites.length) { + const heldOutDropContent = heldOutDropWrites.map(w => w ? w.get() : null) + + const rOut2 = readCursor(shallowCloneOp(w.get())) + eachDrop(rOut2, w, (slotOut, r, w) => { + const data = heldOutDropContent[slotOut] + if (data) { + w.mergeTree(data) + heldOutDropContent[slotOut] = null + } + }) + + if (heldOutDropContent.find(x => x)) { + log('BLACKHOLE') + // If there's nowhere we can naturally place a drop, it must be + // blackholed. Blackholes can have multiple picks & drops, and we need + // all the places in op1 and op2 where the drops happened to + // reconstruct it. + + const w1 = writeCursor(), w2 = writeCursor() + let nextSlot1 = 0, nextSlot2 = 0 + + // log('hdo', heldOutDropContent, 'mcod', op2MoveContainingOutDrop) + heldOutDropContent.forEach((data, slotOut) => { + if (data != null) { + eachDrop(readCursor(data), null, c => { + const slot1 = outputSlotMap[c] + w1.writeMove(heldOp1PickByOp1[slot1].getPath(), heldOp1DropByOp1[slot1]!.getPath(), nextSlot1++) + + log('blackhole slot1', slot1) + const slot2s = op1PicksOp2DropSlots[slot1] + if (slot2s) slot2s.forEach(slot2 => { + if (!cancelledOp2[slot2] && (side === RIGHT || op1PickAtOp2Pick[slot2] == null)) { + w2.writeMove(heldOp2PickByOp2[slot2]!.getPath(), heldOp2DropByOp2[slot2]!.getPath(), nextSlot2++) + } + }) + }) + + // const slot2 = op2MoveContainingOutDrop[slotOut] + // assert(slot2 != null) + + // log(slot2, heldOp2PickByOp2[slot2] && heldOp2PickByOp2[slot2].getPath(), heldOp2DropByOp2[slot2] && heldOp2PickByOp2[slot2].getPath()) + // w2.writeMove(heldOp2PickByOp2[slot2].getPath(), heldOp2DropByOp2[slot2].getPath(), nextSlot2++) + } + }) + + conflict = { + type: ConflictType.BLACKHOLE, + op1: w1.get(), + op2: w2.get(), + } + } + } + } + + + if (conflict) { + log('-> xf returning conflict 5', conflict) + log() + return {ok:false, conflict} + + } else { + const result = w.get() + log('-> xf returning', result) + log() + if (!RELEASE_MODE) checkValidOp(result) + return {ok:true, result} + } +} + +const throwConflictErr = (conflict: Conflict) => { + const err = new Error('Transform detected write conflict') + ;(err as any).conflict = conflict + ;(err as any).type = err.name = 'writeConflict' + throw err +} + +// Transform variant which throws on conflict. +function transform(op1: JSONOp, op2: JSONOp, side: 'left' | 'right') { + const res = tryTransform(op1, op2, side) + if (res.ok) return res.result + else throwConflictErr(res.conflict) +} + +/** Make an op that removes the content at the drop and edit destinations of the passed op */ +const opThatRemovesDE = (op: JSONOp) => { + const w = writeCursor() + readCursor(op).traverse(w, (c, w) => { + if (hasDrop(c) || getEditType(c)) w.write('r', true) + }) + return w.get() +} + +const resolveConflict = (conflict: Conflict, side: 'left' | 'right') => { + const {type, op1, op2} = conflict + log('resolving conflict of type', type) + + switch (type) { + case ConflictType.DROP_COLLISION: + // log(c2, removeOp(c2.drop)) + + return side === 'left' + ? [null, opThatRemovesDE(op2)] + : [opThatRemovesDE(op1), null] + + case ConflictType.RM_UNEXPECTED_CONTENT: + let op1HasRemove = false + readCursor(op1).traverse(null, c => {if (c.r !== undefined) op1HasRemove = true}) + return op1HasRemove ? [null, opThatRemovesDE(op2)] : [opThatRemovesDE(op1), null] + + case ConflictType.BLACKHOLE: + // The conflict will have moves from each operation + // + // There's no good automatic resolution for blackholed content. Both + // objects are expected to be removed from the source and there's + // nowhere we can really put them. We'll just delete everything. + // + // Note that c2 has *already* moved the blackholed content inside c1. We + // only need to remove c1. + return [opThatRemovesDE(op1), opThatRemovesDE(op2)] + + default: throw Error('Unrecognised conflict: ' + type) + } +} + + +const invConflict = ({type, op1, op2}: Conflict) => ({type, op1:op2, op2:op1}) +const normalizeConflict = ({type, op1, op2}: Conflict) => ({type, op1:normalize(op1), op2: normalize(op2)}) + +type AllowConflictPred = (c: Conflict) => boolean +function transformWithConflictsPred(allowConflict: AllowConflictPred, op1: JSONOp, op2: JSONOp, side: 'left' | 'right') { + // debugMode = t.debug + + let r2Aggregate = null + + while (true) { + const res = tryTransform(op1, op2, side) + if (res.ok) return compose(r2Aggregate, res.result) + else { + const {conflict} = res + log('detected conflict', conflict) + if (!allowConflict(conflict)) throwConflictErr(conflict) + + if (!RELEASE_MODE && conflict.type === ConflictType.BLACKHOLE) { + const res2 = tryTransform(op2, op1, side === 'left' ? 'right' : 'left') + // const {ok: ok2, result: result2, conflict: conflict2} = res2 + assert(!res2.ok) + try { + assert(deepEqual(normalizeConflict(res2.conflict) as any, normalizeConflict(invConflict(conflict)) as any)) + } catch (e) { + ;(log as any).debug = true + log('Inverted transform conflict', op1, op2, conflict, res2.conflict) + throw e + } + } + + // Recover from the conflict + const [r1, r2] = resolveConflict(conflict, side) + log('Resolve ops', r1, r2) + // I'm normalizing here to work around a bug where a non-normalized op (eg + // `[{"es":[]}]`) generates a conflict but cannot ever be resolved because + // the conflict object returned by transform loses information about the + // conflicting part of the operation. + // + // This shouldn't come up with well formed operations in general, but + // without this the code here ends up in an infinite loop in these cases. + op1 = compose(normalize(op1), r1) + op2 = compose(normalize(op2), r2) + r2Aggregate = compose(r2Aggregate, r2) + log('recover from conflict', conflict) + log('op1 ->', op1) + log('op2 ->', op2) + } + } +} diff --git a/src/json1Presence/log.ts b/src/json1Presence/log.ts new file mode 100644 index 00000000..69bf38c6 --- /dev/null +++ b/src/json1Presence/log.ts @@ -0,0 +1,17 @@ +// This is a simple logging function which prints things a lot prettier than +// console.log in node. The signature is the same. + +export default function log(...args: any) { + if (log.quiet) return + + const {inspect} = require('util') + + const f = (a: any) => (typeof a === 'string') ? + a : inspect(a, {depth:10, colors:true}) + + const prefix = Array(log.prefix).fill(' ').join('') + console.log(prefix + args.map(f).join(' ')) +} + +log.quiet = true +log.prefix = 0 diff --git a/src/json1Presence/types.ts b/src/json1Presence/types.ts new file mode 100644 index 00000000..de7412d4 --- /dev/null +++ b/src/json1Presence/types.ts @@ -0,0 +1,84 @@ +import {TextOp} from 'ot-text-unicode' + +/** The text op component from text-unicode */ +export {TextOp, TextOpComponent} from 'ot-text-unicode' + +export type JSONOpComponent = { + /** insert */ + i?: any, + /** remove */ + r?: any, + /** pick */ + p?: number, + /** drop */ + d?: number, + + /** Edit string (directly uses text-unicode) */ + es?: TextOp, + /** Edit number */ + ena?: number, + + /** Arbitrary edit. If the element has e: it must also have et: with a registered type name */ + e?: any + /** Edit type name. */ + et?: string +} + +export type JSONOpList = (number | string | JSONOpComponent | JSONOpList)[] + +/** + * A JSON operation. + * + * There's some aspects of valid operations which aren't captured by the type: + * + * - The last element of any list must be an object (the op component) + * - Except at the root, every inner list must have a length at least 2, and the + * first element must be a number or string. + * - Operation components cannot be empty + * - No two adjacent list elements can be operation components. + * - If a component has listy children (it descends), those descenders are + * sorted and come after any local op component. So, [, , , , ...] + * - Picks and drops must be matched, and use low numbers (0, 1, 2, ....) + * + * noop is represented by 'null'. + * + * Valid operations: null, [{i:5}], ['a', {i:5}], [['a', {p:0}], ['b', {d:0}]], + * [10, {i:5}, 'b', {r:true}] + * + * Not valid: [], ['a', [{i:5}]], [[{i:5}]], [{}], ['a', {}], ['a', ['b', {i:5}], {r:true}] + * + * Many 'invalid' operations can be cleaned up into their canonical form by the + * normalize() function. + * + * If you want some examples, take a look at the test suite. + */ +export type JSONOp = null | JSONOpList + +export type Key = number | string +export type Path = Key[] +export type Presence = { + start: Path, + end: Path, + [key: string]: any +} + +/** + * JSON documents must be able to round-trip through JSON.stringify / + * JSON.parse. So this means they can't contain: + * + * - undefined + * - Circular references + * - Objects of classes + * - Functions + * - Sets, Maps, Dates, DOM nodes, etc + */ +export type Doc = null | boolean | number | string | Doc[] | {[k: string]: Doc} + +export enum ConflictType { + RM_UNEXPECTED_CONTENT = 1, + DROP_COLLISION = 2, + BLACKHOLE = 3, +} + +export interface Conflict { type: ConflictType, op1: JSONOp, op2: JSONOp } From ea7a34464e33ff4f4bc8062b01fc32e70816d964 Mon Sep 17 00:00:00 2001 From: Curran Date: Fri, 3 Mar 2023 23:36:03 -0500 Subject: [PATCH 2/5] De-conflict naming --- src/json1Presence/json1.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/json1Presence/json1.ts b/src/json1Presence/json1.ts index ab05fe49..27cda98e 100644 --- a/src/json1Presence/json1.ts +++ b/src/json1Presence/json1.ts @@ -36,8 +36,8 @@ function assert(pred: any, msg?: string): asserts pred { let debugMode = false export const type = { - name: 'json1', - uri: "http://sharejs.org/types/JSONv1", + name: 'json1-presence', + uri: "http://sharejs.org/types/JSONv1-presence", readCursor, writeCursor, From c352e7c6ac3eccfaa9b255d592905059a4cc0d94 Mon Sep 17 00:00:00 2001 From: Curran Date: Sat, 4 Mar 2023 04:16:27 -0500 Subject: [PATCH 3/5] Failed attempt to import --- cli.js | 2 +- src/App.jsx | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/cli.js b/cli.js index 0c8dc9cf..63c2534e 100644 --- a/cli.js +++ b/cli.js @@ -2,7 +2,7 @@ import http from 'http'; import express from 'express'; import ShareDB from 'sharedb'; -import json1 from 'ot-json1'; +import json1 from './src/json1Presence'; import { WebSocketServer } from 'ws'; import WebSocketJSONStream from '@teamwork/websocket-json-stream'; import fs from 'fs'; diff --git a/src/App.jsx b/src/App.jsx index 17d37be6..70ca9880 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -2,8 +2,11 @@ import { useState, useEffect, useMemo, useCallback } from 'react'; import ShareDBClient from 'sharedb-client-browser/sharedb-client-json1-browser.js'; import { CodeEditor } from './CodeEditor'; import { diff } from './diff'; +import json1 from './json1Presence'; import './style.css'; +ShareDBClient.types.register(json1.type); + const { Connection } = ShareDBClient; const socket = new WebSocket('ws://' + window.location.host + '/ws'); const connection = new Connection(socket); From 50c9698b1179ca23bf6d910a485a8e1cc96e41bf Mon Sep 17 00:00:00 2001 From: Curran Date: Sat, 4 Mar 2023 04:44:08 -0500 Subject: [PATCH 4/5] Add JSON1 with presence --- .gitignore | 3 +- cli.js | 2 +- src/App.jsx | 4 +- src/json1presence/.gitignore | 2 + src/json1presence/.travis.yml | 5 + src/json1presence/CHANGELOG.md | 9 + src/json1presence/Makefile | 26 + src/json1presence/README.md | 117 + src/json1presence/dist/cursor.d.ts | 52 + src/json1presence/dist/cursor.js | 418 ++ src/json1presence/dist/cursor.js.map | 1 + src/json1presence/dist/deepClone.d.ts | 2 + src/json1presence/dist/deepClone.js | 21 + src/json1presence/dist/deepClone.js.map | 1 + src/json1presence/dist/deepEqual.d.ts | 2 + src/json1presence/dist/deepEqual.js | 38 + src/json1presence/dist/deepEqual.js.map | 1 + src/json1presence/dist/index.d.ts | 3 + src/json1presence/dist/index.js | 19 + src/json1presence/dist/index.js.map | 1 + src/json1presence/dist/json1.d.ts | 144 + src/json1presence/dist/json1.js | 2580 +++++++++++++ src/json1presence/dist/json1.js.map | 1 + src/json1presence/dist/json1.release.d.ts | 1 + src/json1presence/dist/json1.release.js | 875 +++++ src/json1presence/dist/json1.release.js.map | 1 + src/json1presence/dist/log.d.ts | 6 + src/json1presence/dist/log.js | 17 + src/json1presence/dist/log.js.map | 1 + src/json1presence/dist/types.d.ts | 75 + src/json1presence/dist/types.js | 10 + src/json1presence/dist/types.js.map | 1 + .../lib}/cursor.ts | 0 .../lib}/deepClone.ts | 0 .../lib}/deepEqual.ts | 0 .../lib}/index.ts | 0 .../lib}/json1.release.ts | 0 .../lib}/json1.ts | 34 +- .../lib}/log.ts | 0 .../lib}/types.ts | 5 - src/json1presence/package-lock.json | 2342 +++++++++++ src/json1presence/package.json | 46 + src/json1presence/spec.md | 308 ++ src/json1presence/test/cursor.js | 111 + src/json1presence/test/fuzzer.js | 18 + src/json1presence/test/genOp.js | 278 ++ src/json1presence/test/genTextOp.js | 85 + src/json1presence/test/immutable.js | 68 + src/json1presence/test/ops.json | 48 + src/json1presence/test/test.js | 3421 +++++++++++++++++ src/json1presence/tracer.js | 86 + src/json1presence/tsconfig.json | 69 + src/json1presence/yarn.lock | 782 ++++ 53 files changed, 12102 insertions(+), 38 deletions(-) create mode 100644 src/json1presence/.gitignore create mode 100644 src/json1presence/.travis.yml create mode 100644 src/json1presence/CHANGELOG.md create mode 100644 src/json1presence/Makefile create mode 100644 src/json1presence/README.md create mode 100644 src/json1presence/dist/cursor.d.ts create mode 100644 src/json1presence/dist/cursor.js create mode 100644 src/json1presence/dist/cursor.js.map create mode 100644 src/json1presence/dist/deepClone.d.ts create mode 100644 src/json1presence/dist/deepClone.js create mode 100644 src/json1presence/dist/deepClone.js.map create mode 100644 src/json1presence/dist/deepEqual.d.ts create mode 100644 src/json1presence/dist/deepEqual.js create mode 100644 src/json1presence/dist/deepEqual.js.map create mode 100644 src/json1presence/dist/index.d.ts create mode 100644 src/json1presence/dist/index.js create mode 100644 src/json1presence/dist/index.js.map create mode 100644 src/json1presence/dist/json1.d.ts create mode 100644 src/json1presence/dist/json1.js create mode 100644 src/json1presence/dist/json1.js.map create mode 100644 src/json1presence/dist/json1.release.d.ts create mode 100644 src/json1presence/dist/json1.release.js create mode 100644 src/json1presence/dist/json1.release.js.map create mode 100644 src/json1presence/dist/log.d.ts create mode 100644 src/json1presence/dist/log.js create mode 100644 src/json1presence/dist/log.js.map create mode 100644 src/json1presence/dist/types.d.ts create mode 100644 src/json1presence/dist/types.js create mode 100644 src/json1presence/dist/types.js.map rename src/{json1Presence => json1presence/lib}/cursor.ts (100%) rename src/{json1Presence => json1presence/lib}/deepClone.ts (100%) rename src/{json1Presence => json1presence/lib}/deepEqual.ts (100%) rename src/{json1Presence => json1presence/lib}/index.ts (100%) rename src/{json1Presence => json1presence/lib}/json1.release.ts (100%) rename src/{json1Presence => json1presence/lib}/json1.ts (98%) rename src/{json1Presence => json1presence/lib}/log.ts (100%) rename src/{json1Presence => json1presence/lib}/types.ts (96%) create mode 100644 src/json1presence/package-lock.json create mode 100644 src/json1presence/package.json create mode 100644 src/json1presence/spec.md create mode 100644 src/json1presence/test/cursor.js create mode 100644 src/json1presence/test/fuzzer.js create mode 100644 src/json1presence/test/genOp.js create mode 100644 src/json1presence/test/genTextOp.js create mode 100644 src/json1presence/test/immutable.js create mode 100644 src/json1presence/test/ops.json create mode 100644 src/json1presence/test/test.js create mode 100644 src/json1presence/tracer.js create mode 100644 src/json1presence/tsconfig.json create mode 100644 src/json1presence/yarn.lock diff --git a/.gitignore b/.gitignore index 85cf88c7..93e28912 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ node_modules -dist -dist-ssr +/dist *.swp diff --git a/cli.js b/cli.js index 63c2534e..91bd5fbb 100644 --- a/cli.js +++ b/cli.js @@ -2,7 +2,7 @@ import http from 'http'; import express from 'express'; import ShareDB from 'sharedb'; -import json1 from './src/json1Presence'; +import json1 from './src/json1presence/dist/index.js'; import { WebSocketServer } from 'ws'; import WebSocketJSONStream from '@teamwork/websocket-json-stream'; import fs from 'fs'; diff --git a/src/App.jsx b/src/App.jsx index 70ca9880..a0e1d2ac 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -2,9 +2,11 @@ import { useState, useEffect, useMemo, useCallback } from 'react'; import ShareDBClient from 'sharedb-client-browser/sharedb-client-json1-browser.js'; import { CodeEditor } from './CodeEditor'; import { diff } from './diff'; -import json1 from './json1Presence'; +import * as json1 from './json1presence/dist/index.js'; import './style.css'; +console.log(json1) + ShareDBClient.types.register(json1.type); const { Connection } = ShareDBClient; diff --git a/src/json1presence/.gitignore b/src/json1presence/.gitignore new file mode 100644 index 00000000..3101bd74 --- /dev/null +++ b/src/json1presence/.gitignore @@ -0,0 +1,2 @@ +.*.swp +node_modules diff --git a/src/json1presence/.travis.yml b/src/json1presence/.travis.yml new file mode 100644 index 00000000..1abefd30 --- /dev/null +++ b/src/json1presence/.travis.yml @@ -0,0 +1,5 @@ +language: node_js + +node_js: + - 10 + diff --git a/src/json1presence/CHANGELOG.md b/src/json1presence/CHANGELOG.md new file mode 100644 index 00000000..3a9e2a3b --- /dev/null +++ b/src/json1presence/CHANGELOG.md @@ -0,0 +1,9 @@ +# HEAD + +- Fixed generated sourcemap for release build + +# 0.3.0 + +- Moved main project to typescript +- Fixed some weird issues in apply where it would try to descend inside primitive values +- Decaffinate tests (#8 - Thanks @curran) diff --git a/src/json1presence/Makefile b/src/json1presence/Makefile new file mode 100644 index 00000000..0b55c6c9 --- /dev/null +++ b/src/json1presence/Makefile @@ -0,0 +1,26 @@ +.PHONY: clean all dist/json1.release.js + +#terser -m -d process.env.JSON1_RELEASE_MODE=true -c pure_funcs=log,keep_fargs=false,passes=2 --wrap fn < lib/json1.js + +all: dist/json1.release.js + +# Look, this isn't really necessary, but it allows me to add the RELEASE flag +# to the library and add a lot of additional (computationally + code size) +# expensive checks throughout transform which should otherwise be removed. In +# release mode we also remove some debugging output libraries which we don't +# want webpack/browserify to end up pulling in. This can shave off a nontrivial +# amount of space for the bundler. + +# Basically what this is doing is hard-setting the RELEASE_MODE flag in the +# code so the minifier can elide all if(!RELEASE_MODE) {...} blocks. +# lib/json1.release.js: lib/json1.js +# npx terser -d process.env.JSON1_RELEASE_MODE=true -c pure_funcs=log,keep_fargs=false,passes=2 -b --ecma 7 -o $@ -- $< + +dist/json1.js: lib/json1.ts + npx tsc +dist/json1.release.js: dist/json1.js + npx terser -d process.env.JSON1_RELEASE_MODE=true -c pure_funcs=log,keep_fargs=false,passes=2 -b --ecma 7 -o $@ -- $< + + +clean: + rm -rf dist diff --git a/src/json1presence/README.md b/src/json1presence/README.md new file mode 100644 index 00000000..d17aa2cf --- /dev/null +++ b/src/json1presence/README.md @@ -0,0 +1,117 @@ +# JSON1 + +> Status: Usable in practice, but contains a couple super obscure known bugs. See below for details. + +This is an operational transformation type for arbitrary JSON trees. It supports concurrently editing arbitrarily complex nested structures. Fancy features: + +- Support for arbitrarily moving objects in the JSON tree. You could, for example, implement [workflowy](https://workflowy.com) using this, allowing items to be collaboratively moved around and edited. Or Maya. Or google wave 2. Or ... Anything! +- Supports embedded subtypes. For example, you can embed rich text documents using quilljs or something inside your JSON tree +- Conflicts! Unlike CRDTs, this library can be configured to refuse to accept operations which would result in lost data. For example, if two users both insert into the same location, instead of silently choosing a winner arbitrarily, you can throw an exception and tell the user whats going on. + +This code it is written to replace [ottypes/json0](https://github.com/ottypes/json0). JSON1 implements a superset of JSON0's functionality. + +The spec for operations is in [spec.md](spec.md). + + +## Usage + +The JSON library has 2 main APIs: + +- The core OT API, which is a type with standard `apply`, `compose`, `transform`, etc functions. The standard API for this is [documented here](https://github.com/ottypes/docs). This is exposed via `require('ot-json').type`. +- A simple API for creating operations. + +```javascript +const json1 = require('ot-json1') + +const op1 = json1.moveOp(['a', 'x'], ['a', 'y']) + +// The easiest way to make compound operations is to just compose smaller operations +const op2 = [ + json1.moveOp(['a'], ['b']), + json1.insertOp(['b', 'z'], 'hi there') +].reduce(json1.type.compose, null) + +// op2 = [['a', {p:0}], ['b', {d:0}, 'x', {i: 'hi there'}]] + +const op1_ = json1.type.transform(op1, op2, 'left') +// op1_ now moves b.x -> b.y instead, because op2 moved 'a' to 'b'. + +let doc = {a: {x: 5}} +doc = json1.type.apply(doc, op2) // doc = {b: {x: 5, z: 'hi there'}} +doc = json1.type.apply(doc, op1_) // doc = {b: {y: 5, z: 'hi there'}} + + +// Using the CP1 diamond property, this is the same as: + +doc = {a: {x: 5}} +doc = json1.type.apply(doc, op1) // doc = {a: {y: 5}} +const op2_ = json1.type.transform(op2, op1, 'right') +doc = json1.type.apply(doc, op2) // doc = {b: {y: 5, z: 'hi there'}} +``` + +### Standard operation creation functions + +- `json1.removeOp(path, value?)`: Remove the value at the specified path. Becomes `[...path, {r: value | true}]` +- `json1.moveOp(fromPath, toPath)`: Moves the value at `fromPath` to `toPath`. +- `json1.insertOp(path, value)`: Insert the specified value at the specified path +- `json1.replaceOp(path, oldVal, newVal)`: Replace the object at path with `newVal`. If you don't care about invertibility, pass `true` for oldVal. +- `json1.editOp(path, subtype, op)`: Modify the value at the specified path `op`, using JSON type `subtype`. The type must be registered first using `json1.type.registerSubtype(typeObj)`. Eg, `json1.type.registerSubtype(require('rich-text'))`. It can be specified using the type name, the type URI or the type object. The [unicode text](https://github.com/ottypes/text-unicode) type and the simple number add type (TODO documentation) are registered by default. + +These functions all return very simple operations. The easiest way to make more complex operations is to combine these pieces using `compose`. For example: + +```javascript +const op = [ + json1.insertOp([], {title: '', contents: '', public: false}), + json1.editOp(['title'], 'text-unicode', ['My cool blog entry']), + json1.replaceOp(['public', false, true]) +].reduce(json1.type.compose, null) +``` + +### Conflicts + +> TODO: Describe how this works and how conflict handling is configured + + +## Interoperability with JSON0 + +This library supports a superset of the capabilities of JSON0, but the two types have some important differences: + +- JSON0 notably doesn't support moves between object keys +- JSON0 doesn't support moving child values from one object to another +- JSON0 and JSON1 use different embedded string types. JSON1 uses [text-unicode](https://github.com/ottypes/text-unicode) which uses proper unicode offsets. JSON0 uses the older [text](https://github.com/ottypes/text) type which uses UTF16 pair offsets. These are normally the same, but notably differ when embedding emoji characters. + +You can convert JSON0 operations to JSON1 operations using [json0-to-1](https://github.com/ottypes/json0-to-1). This is a work in progress and doesn't currently support converting string values. Please make noise & consider helping out if this conversion code is important to you. This conversion code guarantees that `json1.apply(doc, convert(json0_op)) === json0.apply(doc, json0_op)` but this invariant is not true through transform. `json1.transform(convert(op1), convert(op2)) !== convert(json0.transform(op1, op2))` in some cases due to slightly different handling of conflicting list indexes. + + +# Limitations + +Your document must only contain pure JSON-stringifyable content. No dates, functions or self-references allowed. Your object should be identical to `JSON.parse(JSON.stringify(obj))`. + +Note that this library is currently in preview release. In practical terms, this means: + +- The fuzzer finds convergence bugs in the transform function results after a million or so iterations. So, this code is usable, but there are some [extremely complex operations](https://github.com/ottypes/json1/blob/4a0741d402ca631710e4e27f4f34647954c1f7d8/test/test.coffee#L2230-L2246) that this library currently struggles with. You shouldn't run into any of these problems in everyday use. +- There may also be some reasonably minor API changes before 1.0 +- `applyPath` doesn't currently transform cursors inside an edited string document +- `applyPath` doesn't currently have fuzzer tests. There are probably a couple bugs there that the fuzzer will find as soon as we hook it up. +- `applyPath` may be renamed to something else (`transformCursor` is what the equivalent method is called in the string type, although its a bit of a weird name here). This function also currently doesn't transform anything inside a child edit, and it should. +- We're missing a conflict for situations when two operations both move the same object to different locations. Currently the left operation will silently 'win' and the other operation's move will be discarded. But this behaviour should be user configurable +- I haven't exposed the internal cursor API, which is used internally to traverse operations. I'd be happy to expose this if someone provides a good, clear use case for doing so. + + + +## License + +Copyright (c) 2013-2018, Joseph Gentle <me@josephg.com> + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. + diff --git a/src/json1presence/dist/cursor.d.ts b/src/json1presence/dist/cursor.d.ts new file mode 100644 index 00000000..9e052861 --- /dev/null +++ b/src/json1presence/dist/cursor.d.ts @@ -0,0 +1,52 @@ +import { Key, JSONOp, JSONOpComponent, JSONOpList, Path } from './types.js'; +declare function copyAll(c: JSONOpComponent, w: WriteCursor): void; +export declare const isValidPathItem: (k: Key) => boolean; +declare class Cursor { + parents: JSONOp[]; + indexes: number[]; + lcIdx: number; + container: JSONOp; + idx: number; + constructor(op?: JSONOp); + ascend(): void; + getPath(): Path; +} +export declare class ReadCursor extends Cursor { + get(): (string | number | JSONOpComponent | JSONOpList)[] | null; + getKey(): Key; + getComponent(): JSONOpComponent | null; + descendFirst(): boolean; + nextSibling(): boolean; + private _init; + clone(): ReadCursor; + [Symbol.iterator](): Generator; + traverse(w: W, fn: (c: JSONOpComponent, w: W) => void): void; + eachPick(w: W, fn: (slot: number, w: W) => void): void; + eachDrop(w: W, fn: (slot: number, w: W) => void): void; +} +export declare class WriteCursor extends Cursor { + pendingDescent: Path; + private _op; + constructor(op?: JSONOp); + private flushDescent; + reset(): void; + getComponent(): JSONOpComponent; + write(key: K, value: JSONOpComponent[K]): void; + get(): JSONOp; + descend(key: Key): void; + descendPath(path: Path): this; + ascend(): void; + mergeTree(data: JSONOp, mergeFn?: typeof copyAll): void; + at(path: Path, fn: (w: WriteCursor) => void): this; + writeAtPath(path: Path, key: K, value: JSONOpComponent[K]): this; + writeMove(path1: Path, path2: Path, slot?: number): this; + getPath(): Path; +} +export declare const writeCursor: () => WriteCursor; +export declare const readCursor: (op: JSONOp) => ReadCursor; +export declare function advancer(r: ReadCursor | null, listMap?: (k: number, c: JSONOpComponent | null) => number, listAdv?: (k: number, c: JSONOpComponent | null) => void): { + (ktarget: Key): ReadCursor | null; + end(): void; +}; +export declare function eachChildOf(r1: ReadCursor | null, r2: ReadCursor | null, fn: (k: Key, r1: ReadCursor | null, r2: ReadCursor | null) => void): void; +export {}; diff --git a/src/json1presence/dist/cursor.js b/src/json1presence/dist/cursor.js new file mode 100644 index 00000000..4014aa35 --- /dev/null +++ b/src/json1presence/dist/cursor.js @@ -0,0 +1,418 @@ +"use strict"; +// This file exposes a cursor interface for iterating over an operation. +// +// The cursor interface supports both reading and writing (appending). +Object.defineProperty(exports, "__esModule", { value: true }); +exports.eachChildOf = exports.advancer = exports.readCursor = exports.writeCursor = exports.WriteCursor = exports.ReadCursor = exports.isValidPathItem = void 0; +// const log = require('./log') +const log = (...args) => { }; +function assert(pred, msg) { if (!pred) + throw new Error(msg); } +const isObject = (o) => (o != null && typeof o === 'object' && !Array.isArray(o)); +const isGreaterKey = (a, b) => ( +// All the numbers, then all the letters. Just as the gods of ascii intended. +(typeof a === typeof b) ? + a > b : typeof a === 'string' && typeof b === 'number'); +function copyAll(c, w) { + for (let _k in c) { + const k = _k; // bleh typescript. + w.write(k, c[k]); + } +} +// It might be dangerous to allow __proto__ as a key path because of +// javascript's prototype semantics, especially because most apps won't +// check operations thoroughly enough. +exports.isValidPathItem = (k) => (typeof k === 'number' || (typeof k === 'string' && k !== '__proto__')); +class Cursor { + constructor(op = null) { + // Each time we descend we store the parent and the index of the child. The + // indexes list is appended twice each time we descend - once with the last + // key index of the parent, and once with the index of the array we're + // descending down. + this.parents = []; + this.indexes = []; + // The index of the last listy child we visited for write cursors. This is used + // to ensure we are traversing the op in order. + this.lcIdx = -1; + this.idx = -1; + this.container = op; + } + ascend() { + assert(this.parents.length === this.indexes.length / 2); + if (this.idx === 0) { + if (this.parents.length) { + this.lcIdx = this.indexes.pop(); + this.container = this.parents.pop(); + this.idx = this.indexes.pop(); + } + else { + this.lcIdx = 0; + this.idx = -1; + } + } + else { + assert(this.idx > 0); + this.idx--; + if (isObject(this.container[this.idx])) + this.idx--; + } + } + getPath() { + const path = []; + let c = this.container; + let p = this.parents.length - 1; + let i = this.idx; + while (i >= 0) { + path.unshift(c[i]); + if (i === 0) { + i = this.indexes[p * 2]; + c = this.parents[p--]; + } + else { + i -= isObject(c[i - 1]) ? 2 : 1; + } + } + return path; + } +} +class ReadCursor extends Cursor { + get() { return this.container ? this.container.slice(this.idx + 1) : null; } + // Its only valid to call this after descending into a child. + getKey() { + assert(this.container != null, 'Invalid call to getKey before cursor descended'); + return this.container[this.idx]; + } + getComponent() { + let c; + if (this.container && this.container.length > this.idx + 1 && isObject((c = this.container[this.idx + 1]))) { + return c; + } + else { + return null; + } + } + descendFirst() { + let i = this.idx + 1; + // Only valid to call this if hasChildren() returns true. + if (!this.container + || i >= this.container.length + || (isObject(this.container[i]) && (i + 1) >= this.container.length)) { + return false; + } + if (isObject(this.container[i])) + i++; // Skip component + const firstChild = this.container[i]; + if (Array.isArray(firstChild)) { + this.indexes.push(this.idx); + this.parents.push(this.container); + this.indexes.push(i); + this.idx = 0; + this.container = firstChild; + } + else { + this.idx = i; + } + return true; + } + nextSibling() { + assert(this.parents.length === this.indexes.length / 2); + // Children are inline or we're at the root + if (this.idx > 0 || this.parents.length === 0) + return false; + const i = this.indexes[this.indexes.length - 1] + 1; + const c = this.parents[this.parents.length - 1]; + if (i >= c.length) + return false; // Past the end of the listy children. + assert(!isNaN(i)); + this.indexes[this.indexes.length - 1] = i; + this.container = c[i]; + // idx = 0 but it should be zero anyway. + return true; + } + _init(_container, _idx, _parents, _indexes) { + // parents.length = indexes.length = 0 + this.container = _container; + this.idx = _idx; + // Only need these for getPath() + this.parents = _parents.slice(); + this.indexes = _indexes.slice(); + } + clone() { + const c = new ReadCursor(); + c._init(this.container, this.idx, this.parents, this.indexes); + return c; + } + *[Symbol.iterator]() { + // Iterate through the child keys. At each key the cursor will be + // pointing at the child the key represents. + if (!this.descendFirst()) + return; + do + yield this.getKey(); + while (this.nextSibling()); + this.ascend(); + } + // TODO(cleanup): Consider moving these functions out of cursor, since + // they're really just helper methods. + // It'd be really nice to do this using generators. + traverse(w, fn) { + const c = this.getComponent(); + if (c) + fn(c, w); + for (const key of this) { + if (w) + w.descend(key); + this.traverse(w, fn); + if (w) + w.ascend(); + } + } + eachPick(w, fn) { + this.traverse(w, (c, w) => { if (c.p != null) + fn(c.p, w); }); + } + eachDrop(w, fn) { + this.traverse(w, (c, w) => { if (c.d != null) + fn(c.d, w); }); + } +} +exports.ReadCursor = ReadCursor; +class WriteCursor extends Cursor { + constructor(op = null) { + super(op); + this.pendingDescent = []; + this._op = op; + } + flushDescent() { + // After flushDescent is called, container will always be non-null. + assert(this.parents.length === this.indexes.length / 2); + if (this.container === null) + this._op = this.container = []; + for (let j = 0; j < this.pendingDescent.length; j++) { + const k = this.pendingDescent[j]; + // log('fd', k) + let i = this.idx + 1; + // Skip the component here, if any. + if (i < this.container.length && isObject(this.container[i])) + i++; + assert(i === this.container.length || !isObject(this.container[i])); + if (i === this.container.length) { + // Just append a child here. + this.container.push(k); + this.idx = i; + } + else if (this.container[i] === k) { + // Traverse right... + this.idx = i; + } + else { + if (!Array.isArray(this.container[i])) { + // Its not an array. Its not the same as me. I must have a new child! + // log('bundle inside', i, lcIdx) + const oldChild = this.container.splice(i, this.container.length - i); + this.container.push(oldChild); + if (this.lcIdx > -1) + this.lcIdx = i; + } + this.indexes.push(this.idx); + this.parents.push(this.container); + if (this.lcIdx !== -1) { + assert(isGreaterKey(k, this.container[this.lcIdx][0])); + i = this.lcIdx + 1; + this.lcIdx = -1; + } + // Skip until k. + while (i < this.container.length && isGreaterKey(k, this.container[i][0])) + i++; + this.indexes.push(i); + this.idx = 0; + if (i < this.container.length && this.container[i][0] === k) { + this.container = this.container[i]; + } + else { + // Insert a new child and descend into it. + const child = [k]; + this.container.splice(i, 0, child); + // log('-> container', container) + this.container = child; + } + } + } + this.pendingDescent.length = 0; + } + reset() { this.lcIdx = -1; } + // Creates and returns a component, creating one if need be. You should + // probably write to it immediately - ops are not valid with empty + // components. + getComponent() { + this.flushDescent(); + const i = this.idx + 1; + if (i < this.container.length && isObject(this.container[i])) { + return this.container[i]; + } + else { + const component = {}; + this.container.splice(i, 0, component); + return component; + } + } + write(key, value) { + const component = this.getComponent(); + assert(component[key] == null || component[key] === value, 'Internal consistency error: Overwritten component. File a bug'); + component[key] = value; + } + get() { return this._op; } + descend(key) { + if (!exports.isValidPathItem(key)) + throw Error('Invalid JSON key'); + // log('descend', key) + this.pendingDescent.push(key); + } + descendPath(path) { + this.pendingDescent.push(...path); + return this; + } + ascend() { + if (this.pendingDescent.length) + this.pendingDescent.pop(); + else + super.ascend(); + } + mergeTree(data, mergeFn = copyAll) { + if (data === null) + return; + assert(Array.isArray(data)); + if (data === this._op) + throw Error('Cannot merge into my own tree'); + // Backup our position... + const _lcIdx = this.lcIdx; + const oldDepth = this.parents.length; + let depth = 0; + for (let i = 0; i < data.length; i++) { + const c = data[i]; + if (typeof c === 'string' || typeof c === 'number') { + depth++; + this.descend(c); + } + else if (Array.isArray(c)) { + this.mergeTree(c, mergeFn); + } + else if (typeof c === 'object') { + mergeFn(c, this); + } + } + // And reset. + while (depth--) + this.ascend(); + // We might have had pending descents that we've flushed out. Reset + // to the start of the descent. + this.lcIdx = (this.parents.length === oldDepth) ? _lcIdx : -1; + } + at(path, fn) { + this.descendPath(path); + fn(this); + for (let i = 0; i < path.length; i++) + this.ascend(); + return this; + } + // This is used by helpers, so the strict ordering guarantees are + // relaxed. + writeAtPath(path, key, value) { + this.at(path, () => this.write(key, value)); + // Allows for multiple writeAtPath calls to relax ordering. + this.reset(); + return this; + } + writeMove(path1, path2, slot = 0) { + return this + .writeAtPath(path1, 'p', slot) + .writeAtPath(path2, 'd', slot); + } + getPath() { + const path = super.getPath(); + path.push(...this.pendingDescent); + return path; + } +} +exports.WriteCursor = WriteCursor; +// ReadCursor / WriteCursor didn't used to be exported. These are old helpers for them. +// TODO: Remove in 1.0 +exports.writeCursor = () => (new WriteCursor()); +exports.readCursor = (op) => new ReadCursor(op); +// onSkipped is called for any children of r which are never returned by adv. +function advancer(r, listMap, listAdv) { + let didDescend, valid; + // Could use r.children() here instead... though this is might be simpler. + valid = didDescend = r ? r.descendFirst() : false; + // Advance to key k. If k is null, advance all the way to the end. + // let last = undefined + function adv(ktarget) { + // if (last === ktarget) throw Error('duplicate descent') + // last = ktarget + let k2_; + while (valid) { + const k2 = k2_ = r.getKey(); + if (ktarget != null) { + let skip = false; + if (listMap && typeof k2 === 'number') { + k2_ = listMap(k2, r.getComponent()); + if (k2_ < 0) { + k2_ = ~k2_; + skip = true; + } + } + // 3 cases: + // - ktarget > k2. onSkip and iterate. + // - ktarget = k and !skip. return r + // - ktarget = k and skip or ktarget < k2. stop. + if (isGreaterKey(k2_, ktarget)) + return null; + else if (k2_ === ktarget && !skip) + return r; + } + // Skip this item and continue. + if (listAdv && typeof k2_ === 'number') + listAdv(k2_, r.getComponent()); + valid = r.nextSibling(); + } + return null; + } + adv.end = () => { + // Advance to the end of the child items. Technically only needed if onSkipped is defined + if (didDescend) + r.ascend(); + }; + return adv; +} +exports.advancer = advancer; +// Walk two cursors together. +function eachChildOf(r1, r2, fn) { + let hasChild1, descended1, hasChild2, descended2; + hasChild1 = descended1 = r1 && r1.descendFirst(); + hasChild2 = descended2 = r2 && r2.descendFirst(); + while (hasChild1 || hasChild2) { + let k1 = hasChild1 ? r1.getKey() : null; + let k2 = hasChild2 ? r2.getKey() : null; + if (k1 !== null && k2 !== null) { + // Use the smallest key + if (isGreaterKey(k2, k1)) + k2 = null; + else if (k1 !== k2) + k1 = null; + } + // fn(key, r1 or null, r2 or null) + fn((k1 == null ? k2 : k1), (k1 != null ? r1 : null), (k2 != null ? r2 : null)); + if (k1 != null && hasChild1) { + hasChild1 = r1.nextSibling(); + } + if (k2 != null && hasChild2) { + hasChild2 = r2.nextSibling(); + } + } + if (descended1) + r1.ascend(); + if (descended2) + r2.ascend(); +} +exports.eachChildOf = eachChildOf; +//# sourceMappingURL=cursor.js.map \ No newline at end of file diff --git a/src/json1presence/dist/cursor.js.map b/src/json1presence/dist/cursor.js.map new file mode 100644 index 00000000..3690d2a5 --- /dev/null +++ b/src/json1presence/dist/cursor.js.map @@ -0,0 +1 @@ +{"version":3,"file":"cursor.js","sourceRoot":"","sources":["../lib/cursor.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/src/json1presence/dist/deepClone.d.ts b/src/json1presence/dist/deepClone.d.ts new file mode 100644 index 00000000..3cf4fd62 --- /dev/null +++ b/src/json1presence/dist/deepClone.d.ts @@ -0,0 +1,2 @@ +import { Doc } from "./types"; +export default function deepClone(old: Doc): Doc; diff --git a/src/json1presence/dist/deepClone.js b/src/json1presence/dist/deepClone.js new file mode 100644 index 00000000..5a6a3dca --- /dev/null +++ b/src/json1presence/dist/deepClone.js @@ -0,0 +1,21 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +function deepClone(old) { + if (old === null) + return null; + if (Array.isArray(old)) { + return old.map(deepClone); + } + else if (typeof old === 'object') { + const o = {}; + for (let k in old) + o[k] = deepClone(old[k]); + return o; + } + else { + // Everything else in JS is immutable. + return old; + } +} +exports.default = deepClone; +//# sourceMappingURL=deepClone.js.map \ No newline at end of file diff --git a/src/json1presence/dist/deepClone.js.map b/src/json1presence/dist/deepClone.js.map new file mode 100644 index 00000000..8ca0ed4c --- /dev/null +++ b/src/json1presence/dist/deepClone.js.map @@ -0,0 +1 @@ +{"version":3,"file":"deepClone.js","sourceRoot":"","sources":["../lib/deepClone.ts"],"names":[],"mappings":";;AAEA,SAAwB,SAAS,CAAC,GAAQ;IACxC,IAAI,GAAG,KAAK,IAAI;QAAE,OAAO,IAAI,CAAA;IAE7B,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE;QACtB,OAAO,GAAG,CAAC,GAAG,CAAC,SAAS,CAAC,CAAA;KAC1B;SAAM,IAAI,OAAO,GAAG,KAAK,QAAQ,EAAE;QAClC,MAAM,CAAC,GAAQ,EAAE,CAAA;QACjB,KAAK,IAAI,CAAC,IAAI,GAAG;YAAE,CAAC,CAAC,CAAC,CAAC,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAA;QAC3C,OAAO,CAAC,CAAA;KACT;SAAM;QACL,sCAAsC;QACtC,OAAO,GAAG,CAAA;KACX;AACH,CAAC;AAbD,4BAaC"} \ No newline at end of file diff --git a/src/json1presence/dist/deepEqual.d.ts b/src/json1presence/dist/deepEqual.d.ts new file mode 100644 index 00000000..3f710932 --- /dev/null +++ b/src/json1presence/dist/deepEqual.d.ts @@ -0,0 +1,2 @@ +import { Doc } from "./types"; +export default function deepEqual(a: Doc, b: Doc): boolean; diff --git a/src/json1presence/dist/deepEqual.js b/src/json1presence/dist/deepEqual.js new file mode 100644 index 00000000..1c8b5f94 --- /dev/null +++ b/src/json1presence/dist/deepEqual.js @@ -0,0 +1,38 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +function eqObj(a, b) { + if (Array.isArray(b)) + return false; + for (let k in a) { + if (!deepEqual(a[k], b[k])) + return false; + } + for (let k in b) { + if (a[k] === undefined) + return false; + } + return true; +} +function eqArr(a, b) { + if (!Array.isArray(b)) + return false; + if (a.length !== b.length) + return false; + for (let i = 0; i < a.length; i++) { + if (!deepEqual(a[i], b[i])) + return false; + } + return true; +} +function deepEqual(a, b) { + // This will cover null, bools, string and number + if (a === b) + return true; + if (a === null || b === null) + return false; + if (typeof a !== 'object' || typeof b !== 'object') + return false; + return Array.isArray(a) ? eqArr(a, b) : eqObj(a, b); +} +exports.default = deepEqual; +//# sourceMappingURL=deepEqual.js.map \ No newline at end of file diff --git a/src/json1presence/dist/deepEqual.js.map b/src/json1presence/dist/deepEqual.js.map new file mode 100644 index 00000000..15d88667 --- /dev/null +++ b/src/json1presence/dist/deepEqual.js.map @@ -0,0 +1 @@ +{"version":3,"file":"deepEqual.js","sourceRoot":"","sources":["../lib/deepEqual.ts"],"names":[],"mappings":";;AAEA,SAAS,KAAK,CAAC,CAAqB,EAAE,CAA6B;IACjE,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QAAE,OAAO,KAAK,CAAA;IAElC,KAAK,IAAI,CAAC,IAAI,CAAC,EAAE;QACf,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;YAAE,OAAO,KAAK,CAAA;KACzC;IACD,KAAK,IAAI,CAAC,IAAI,CAAC,EAAE;QACf,IAAI,CAAC,CAAC,CAAC,CAAC,KAAK,SAAS;YAAE,OAAO,KAAK,CAAA;KACrC;IACD,OAAO,IAAI,CAAA;AACb,CAAC;AAED,SAAS,KAAK,CAAC,CAAQ,EAAE,CAAM;IAC7B,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QAAE,OAAO,KAAK,CAAA;IACnC,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM;QAAE,OAAO,KAAK,CAAA;IAEvC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;QACjC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;YAAE,OAAO,KAAK,CAAA;KACzC;IAED,OAAO,IAAI,CAAA;AACb,CAAC;AAED,SAAwB,SAAS,CAAC,CAAM,EAAE,CAAM;IAC9C,iDAAiD;IACjD,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,CAAA;IACxB,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,IAAI;QAAE,OAAO,KAAK,CAAA;IAC1C,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAA;IAEhE,OAAO,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;AACrD,CAAC;AAPD,4BAOC"} \ No newline at end of file diff --git a/src/json1presence/dist/index.d.ts b/src/json1presence/dist/index.d.ts new file mode 100644 index 00000000..8bf87c69 --- /dev/null +++ b/src/json1presence/dist/index.d.ts @@ -0,0 +1,3 @@ +export * from './json1.release.js'; +export { ReadCursor, WriteCursor } from './cursor.js'; +export { Doc, JSONOp, JSONOpList, JSONOpComponent, Key, Path, Conflict, ConflictType, } from './types'; diff --git a/src/json1presence/dist/index.js b/src/json1presence/dist/index.js new file mode 100644 index 00000000..c5be7be4 --- /dev/null +++ b/src/json1presence/dist/index.js @@ -0,0 +1,19 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __exportStar = (this && this.__exportStar) || function(m, exports) { + for (var p in m) if (p !== "default" && !exports.hasOwnProperty(p)) __createBinding(exports, m, p); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +__exportStar(require("./json1.release.js"), exports); +var cursor_js_1 = require("./cursor.js"); +Object.defineProperty(exports, "ReadCursor", { enumerable: true, get: function () { return cursor_js_1.ReadCursor; } }); +Object.defineProperty(exports, "WriteCursor", { enumerable: true, get: function () { return cursor_js_1.WriteCursor; } }); +var types_1 = require("./types"); +Object.defineProperty(exports, "ConflictType", { enumerable: true, get: function () { return types_1.ConflictType; } }); +//# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/src/json1presence/dist/index.js.map b/src/json1presence/dist/index.js.map new file mode 100644 index 00000000..3121f7b3 --- /dev/null +++ b/src/json1presence/dist/index.js.map @@ -0,0 +1 @@ +{"version":3,"file":"index.js","sourceRoot":"","sources":["../lib/index.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,qDAAkC;AAClC,yCAAmD;AAA3C,uGAAA,UAAU,OAAA;AAAE,wGAAA,WAAW,OAAA;AAC/B,iCAIgB;AADO,qGAAA,YAAY,OAAA"} \ No newline at end of file diff --git a/src/json1presence/dist/json1.d.ts b/src/json1presence/dist/json1.d.ts new file mode 100644 index 00000000..edfeff57 --- /dev/null +++ b/src/json1presence/dist/json1.d.ts @@ -0,0 +1,144 @@ +import { ReadCursor, WriteCursor } from './cursor.js'; +import { Doc, Path, JSONOp, JSONOpList, Conflict, ConflictType } from './types.js'; +export declare const type: { + name: string; + uri: string; + readCursor: (op: JSONOp) => ReadCursor; + writeCursor: () => WriteCursor; + create(data: Doc): Doc; + isNoop(op: JSONOp): boolean; + setDebug(val: boolean): void; + registerSubtype: typeof registerSubtype; + checkValidOp: typeof checkValidOp; + normalize: typeof normalize; + apply: typeof apply; + transformPosition: typeof transformPosition; + compose: typeof compose; + tryTransform: typeof tryTransform; + transform: typeof transform; + makeInvertible: typeof makeInvertible; + invert: typeof invert; + invertWithDoc: typeof invertWithDoc; + RM_UNEXPECTED_CONTENT: ConflictType; + DROP_COLLISION: ConflictType; + BLACKHOLE: ConflictType; + transformNoConflict: (op1: JSONOp, op2: JSONOp, side: 'left' | 'right') => JSONOp; + typeAllowingConflictsPred: (allowConflict: AllowConflictPred) => { + transform(op1: JSONOp, op2: JSONOp, side: 'left' | 'right'): JSONOp; + name: string; + uri: string; + readCursor: (op: JSONOp) => ReadCursor; + writeCursor: () => WriteCursor; + create(data: Doc): Doc; + isNoop(op: JSONOp): boolean; + setDebug(val: boolean): void; + registerSubtype: typeof registerSubtype; + checkValidOp: typeof checkValidOp; + normalize: typeof normalize; + apply: typeof apply; + transformPosition: typeof transformPosition; + compose: typeof compose; + tryTransform: typeof tryTransform; + makeInvertible: typeof makeInvertible; + invert: typeof invert; + invertWithDoc: typeof invertWithDoc; + RM_UNEXPECTED_CONTENT: ConflictType; + DROP_COLLISION: ConflictType; + BLACKHOLE: ConflictType; + transformNoConflict: (op1: JSONOp, op2: JSONOp, side: 'left' | 'right') => JSONOp; + typeAllowingConflictsPred: any; + }; +}; +export declare const removeOp: (path: Path, value?: boolean) => JSONOp; +export declare const moveOp: (from: Path, to: Path) => JSONOp; +export declare const insertOp: (path: Path, value: Doc) => JSONOp; +export declare const replaceOp: (path: Path, oldVal: Doc, newVal: Doc) => JSONOp; +export declare const editOp: (path: Path, type: Subtype | string, subOp: any, preserveNoop?: boolean) => JSONOp; +declare type Subtype = { + name: string; + uri?: string; + apply(doc: any, op: any): any; + compose(op1: any, op2: any): any; + transform(op1: any, op2: any, by: 'left' | 'right'): any; + isNoop?: (op: any) => boolean; + invert?: (op: any) => any; + makeInvertible?: (op: any, doc: any) => any; + [k: string]: any; +}; +declare function registerSubtype(subtype: Subtype | { + type: Subtype; + [k: string]: any; +}): void; +declare function checkValidOp(op: JSONOp): void; +declare function normalize(op: JSONOp): JSONOp; +/** + * Apply op to snapshot. Return the new snapshot. + * + * The snapshot is shallow copied as its edited, so the previous snapshot + * reference is still valid. + */ +declare function apply(snapshot: Doc | undefined, op: JSONOp): string | number | boolean | Doc[] | { + [k: string]: Doc; +} | null | undefined; +/** + * Transforms a path by an operation. Eg: + * + * ```javascript + * newPath = transformPosition([3, 'address'], [2, {r: true}]) + * // newPath is [2, 'address'] + * ``` + * + * This is useful for cursors, for tracking annotations and various other + * markers which should stick to a single element of the JSON document. + * + * transformPosition returns null if the item stored at the path no longer + * exists in the document after the operation has been applied. Eg: + * + * ```javascript + * assert(transformPosition([2, 'address', 'street'], [2, {r: true}]) === null) + * ``` + */ +declare function transformPosition(path: Path, op: JSONOp): Path | null; +declare function compose(op1: JSONOp, op2: JSONOp): JSONOp; +/** + * Invert the given operation. Operation must be invertible - which is to say, + * all removes in the operation must contain all removed content. Note any + * operation returned from transform() and compose() will have this content + * stripped. + * + * Unless you know what you're doing, I highly recommend calling invertWithDoc() + * instead. + */ +declare function invert(op: JSONOp): JSONOp; +/** + * Make an operation invertible. (So, you can call invert(op) after this). + * + * This is needed because r:XX contents are optional in an operation, and may be + * optional in subtypes as well. (Eg text-unicode). + * + * This method does two main things: + * + * - Fills in r:{} bodies in components by copying data in from the document + * - Recursively calls makeInvertible on any types embedded in the operation. + * + * Note transform (and compose?) discards remove information, so if you call + * transform on an invertible operation you will need to call makeInvertible + * again before inverting. + */ +declare function makeInvertible(op: JSONOp, doc: Doc): JSONOp; +/** + * Invert the given operation in the context of the passed document. The + * specified document must be the document *before* the operation has been + * applied. + */ +declare function invertWithDoc(op: JSONOp, doc: Doc): JSONOp; +declare function tryTransform(op1: JSONOp, op2: JSONOp, direction: 'left' | 'right'): { + ok: true; + result: JSONOp; +} | { + ok: false; + conflict: Conflict; +}; +declare function transform(op1: JSONOp, op2: JSONOp, side: 'left' | 'right'): JSONOpList | null | undefined; +declare type AllowConflictPred = (c: Conflict) => boolean; +export {}; diff --git a/src/json1presence/dist/json1.js b/src/json1presence/dist/json1.js new file mode 100644 index 00000000..d1e0259d --- /dev/null +++ b/src/json1presence/dist/json1.js @@ -0,0 +1,2580 @@ +"use strict"; +// This is the JSON OT type implementation. It is a successor to JSON0 +// (https://github.com/ottypes/json0), better in a bunch of ways. +// +// This type follows after the text type. Instead of operations being a list of +// small changes, an operation is a tree mirroring the structure of the document +// itself. Operations contain three things: +// - Edits +// - Pick up operations +// - Drop operations +// +// The complete spec is in spec.md alongside this file. +// +// +// A tiny todo list (new issues should go on github): +// - compose should preserve invertible data +// - Conversion between json1 ops and json0, json-patch. +// - Conflict resolving methods to make a backup move location in the operation +// - remove N items in a list - it'd be nice to be able to express this in O(1) instead of O(n). +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.editOp = exports.replaceOp = exports.insertOp = exports.moveOp = exports.removeOp = exports.type = void 0; +// import log from './log' +const deepEqual_js_1 = __importDefault(require("./deepEqual.js")); +const deepClone_js_1 = __importDefault(require("./deepClone.js")); +const cursor_js_1 = require("./cursor.js"); +const types_js_1 = require("./types.js"); +const RELEASE_MODE = process.env.JSON1_RELEASE_MODE; +const log = RELEASE_MODE ? (() => { }) : require('./log').default; +// Not using assert to decrease how much stuff we pull in in the browser +// const assert = !RELEASE_MODE ? require('assert') : (pred: boolean, msg?: string) => {if (!pred) throw new Error(msg)} +function assert(pred, msg) { + if (!RELEASE_MODE) + require('assert')(pred, msg); // For nicer error messages in node. + else if (!pred) + throw new Error(msg); +} +let debugMode = false; +exports.type = { + name: 'json1presence', + uri: "http://sharejs.org/types/JSONv1presence", + readCursor: cursor_js_1.readCursor, + writeCursor: cursor_js_1.writeCursor, + // The document must be immutable. Create by default sets a document at + // `undefined`. You should follow up by initializing the document using a + // {i:....} operation. + create(data) { return data; }, + isNoop(op) { return op == null; }, + setDebug(val) { debugMode = val; log.quiet = !val; }, + registerSubtype, + checkValidOp, + normalize, + apply, + transformPosition, + compose, + tryTransform, + transform, + makeInvertible, + invert, + invertWithDoc, + // TODO: Consider simply exporting ConflictType. + RM_UNEXPECTED_CONTENT: types_js_1.ConflictType.RM_UNEXPECTED_CONTENT, + DROP_COLLISION: types_js_1.ConflictType.DROP_COLLISION, + BLACKHOLE: types_js_1.ConflictType.BLACKHOLE, + // Mmmm I'm not sure the best way to expose this. + transformNoConflict: (op1, op2, side) => (transformWithConflictsPred(() => true, op1, op2, side)), + typeAllowingConflictsPred: (allowConflict) => (Object.assign(Object.assign({}, exports.type), { transform(op1, op2, side) { + return transformWithConflictsPred(allowConflict, op1, op2, side); + } })) +}; +const getComponent = (r) => r ? r.getComponent() : null; +function isObject(o) { + return o && typeof (o) === 'object' && !Array.isArray(o); +} +// Can't figure out how to make a generic type +const shallowClone = (obj) => (Array.isArray(obj) + ? obj.slice() + : obj !== null && typeof obj === 'object' ? Object.assign({}, obj) + : obj); +const hasPick = (c) => c && (c.p != null || c.r !== undefined); +const hasDrop = (c) => c && (c.d != null || c.i !== undefined); +// **** Helpers to make operations. +// +// TODO(cleanup): Move these out into another file. +exports.removeOp = (path, value = true) => (cursor_js_1.writeCursor() + .writeAtPath(path, 'r', value) + .get()); +exports.moveOp = (from, to) => (cursor_js_1.writeCursor() + .writeMove(from, to) + .get()); +exports.insertOp = (path, value) => (cursor_js_1.writeCursor() + .writeAtPath(path, 'i', value) + .get()); +exports.replaceOp = (path, oldVal, newVal) => (cursor_js_1.writeCursor().at(path, (w) => { + w.write('r', oldVal); + w.write('i', newVal); +}).get()); +exports.editOp = (path, type, subOp, preserveNoop = false) => (cursor_js_1.writeCursor() + .at(path, w => writeEdit(w, type, subOp, preserveNoop)) + .get()); +// Remove key from container. Return container with key removed +function removeChild(container, key) { + assert(container != null); + if (typeof key === 'number') { // Removing from a list + assert(Array.isArray(container), 'Invalid key - child is not an array'); + container = container.slice(); + container.splice(key, 1); + } + else { // Removing from an object + assert(isObject(container), 'Invalid key - child is not an object'); + container = Object.assign({}, container); + delete container[key]; + } + return container; +} +// Insert value at key in container. Return container with new value +function insertChildMut(container, key, value) { + if (typeof key === 'number') { + assert(container != null, 'Container is missing for key'); + assert(Array.isArray(container), 'Cannot use numerical key for object container'); + assert(container.length >= key, 'Cannot insert into out of bounds index'); + //container = container.slice() + container.splice(key, 0, value); + } + else { + assert(isObject(container), 'Cannot insert into missing item'); + assert(container[key] === undefined, 'Trying to overwrite value at key. Your op needs to remove it first'); + container[key] = value; + //container = Object.assign({[key]:value}, container) + } + return value; +} +const replaceChild = (obj, k, v) => { + obj = shallowClone(obj); + obj[k] = v; // Could add an assert here... + return obj; +}; +const isValidKey = (container, key) => (container == null + ? false + : typeof key === 'number' + ? Array.isArray(container) + : typeof container === 'object'); +const maybeGetChild = (container, key) => (isValidKey(container, key) ? container[key] : undefined); +const subtypes = {}; +function registerSubtype(subtype) { + let _subtype = subtype.type ? subtype.type : subtype; + if (_subtype.name) + subtypes[_subtype.name] = _subtype; + if (_subtype.uri) + subtypes[_subtype.uri] = _subtype; +} +const typeOrThrow = (name) => { + const type = subtypes[name]; + if (!type) + throw Error('Missing type: ' + name); + else + return type; +}; +registerSubtype(require('ot-text-unicode')); +const add = (a, b) => a + b; +registerSubtype({ + name: 'number', + apply: add, + compose: add, + invert: (n) => -n, + transform: a => a, +}); +const getEditType = (c) => ((c == null) ? null + : c.et ? typeOrThrow(c.et) + : c.es ? subtypes['text-unicode'] + : c.ena != null ? subtypes.number + : null); +// I'm using terneries here because .ena might be 0. +const getEdit = (c) => c.es ? c.es : c.ena != null ? c.ena : c.e; +// Type is an object or a string name. +const writeEdit = (w, typeOrName, edit, preserveNoop = false) => { + // I hope v8 doesn't make a temporary array here. + const [type, name] = (typeof typeOrName === 'string') + ? [typeOrThrow(typeOrName), typeOrName] + : [typeOrName, typeOrName.name]; + // Discard embedded noops. + if (!preserveNoop && type.isNoop && type.isNoop(edit)) + return; + if (name === 'number') { + w.write('ena', edit); + } + else if (name === 'text-unicode') { + w.write('es', edit); // Text edit-string operation. + } + else { // TODO: Also if t1 === subtypes.number then write ena... + w.write('et', name); // Generic embedded edit. + w.write('e', edit); + } +}; +// ***** Check code +function checkNonNegInteger(n) { + assert(typeof n === 'number'); + assert(n >= 0); + assert(n === (n | 0)); +} +function checkScalar(s) { + if (typeof s === 'number') { + checkNonNegInteger(s); + } + else { + assert(typeof s === 'string'); + } +} +function checkValidOp(op) { + //console.log 'check', op + if (op === null) + return; // Empty operation. undefined is not a valid operation and should crash. + // Both sets of used IDs. + const pickedSlots = new Set; + const droppedSlots = new Set; + const checkComponent = (e) => { + let empty = true; + let hasEdit = false; + for (let k in e) { + const v = e[k]; + empty = false; + // Thanks coffeescript compiler! + assert(k === 'p' || k === 'r' || k === 'd' || k === 'i' + || k === 'e' || k === 'es' || k === 'ena' || k === 'et', "Invalid component item '" + k + "'"); + if (k === 'p') { + checkNonNegInteger(v); + assert(!pickedSlots.has(v)); + pickedSlots.add(v); + assert(e.r === undefined); + } + else if (k === 'd') { + checkNonNegInteger(v); + assert(!droppedSlots.has(v)); + droppedSlots.add(v); + assert(e.i === undefined); + } + else if (k === 'e' || k === 'es' || k === 'ena') { + assert(!hasEdit); + hasEdit = true; + const t = getEditType(e); + assert(t, 'Missing type in edit'); + if (t.checkValidOp) + t.checkValidOp(getEdit(e)); + } + } + assert(!empty); + }; + const SCALAR = 1, EDIT = 2, DESCENT = 3; + const checkDescent = (descent, isRoot, removed) => { + if (!Array.isArray(descent)) + throw Error('Op must be null or a list'); + if (descent.length === 0) + throw Error('Empty descent'); + if (!isRoot) + checkScalar(descent[0]); + let last = SCALAR; + // let lastScalarType = 'string' // or 'number' or 'string'. + let numDescents = 0; + let lastKey = 0; + for (let i = 0; i < descent.length; i++) { + const d = descent[i]; + assert(d != null); + if (Array.isArray(d)) { + // Recursive descent inside children + const key = checkDescent(d, false, removed); + if (numDescents) { + const t1 = typeof lastKey; + const t2 = typeof key; + if (t1 === t2) + assert(lastKey < key, 'descent keys are not in order'); + else + assert(t1 === 'number' && t2 === 'string'); + } + lastKey = key; + numDescents++; + last = DESCENT; + } + else if (typeof d === 'object') { + // Component + assert(last === SCALAR, `Prev not scalar - instead ${last}`); + checkComponent(d); + // const hasP = d.r !== undefined || d.p != null + // const hasD = d.i !== undefined || d.d != null + // if (removed && hasD) throw Error('Cannot drop in deleted object') + // if (hasD) removed = false + // else if (hasP && lastScalarType === 'string') removed = true + // if (removed && getEditType(d) != null) throw Error('Cannot edit missing object') + last = EDIT; + } + else { + // Descent key + assert(last !== DESCENT); + checkScalar(d); + assert(cursor_js_1.isValidPathItem(d), 'Invalid path key'); // They're trying to use __proto__ or something. + last = SCALAR; + // lastScalarType = typeof d + } + } + assert(numDescents !== 1, 'Operation makes multiple descents. Remove some []'); + assert(last === EDIT || last === DESCENT); + return descent[0]; + }; + checkDescent(op, true, false); + assert(pickedSlots.size === droppedSlots.size, 'Mismatched picks and drops in op'); + for (let i = 0; i < pickedSlots.size; i++) { + assert(pickedSlots.has(i)); + assert(droppedSlots.has(i)); + } +} +function normalize(op) { + let nextSlot = 0; + let slotMap = []; + const getSlot = (inSlot) => { + if (slotMap[inSlot] == null) + slotMap[inSlot] = nextSlot++; + return slotMap[inSlot]; + }; + const w = cursor_js_1.writeCursor(); + w.mergeTree(op, (c, w) => { + const t = getEditType(c); + if (t) { + const op = getEdit(c); + writeEdit(w, t, t.normalize ? t.normalize(op) : op); + } + // Copy everything else in like normal. + for (const k of ['r', 'p', 'i', 'd']) { + if (c[k] !== undefined) { + const r = (k === 'p' || k === 'd') ? getSlot(c[k]) : c[k]; + w.write(k, r); + } + } + }); + return w.get(); +} +// ***** Apply +/** + * Apply op to snapshot. Return the new snapshot. + * + * The snapshot is shallow copied as its edited, so the previous snapshot + * reference is still valid. + */ +function apply(snapshot, op) { + // Apply doesn't make use of cursors. It might be a little simpler to + // implement apply using them. The reason they aren't used here is merely + // historical - the cursor implementation was written after apply() was + // already working great. + ; + log.quiet = !debugMode; + //log.quiet = false + //log('*******************************************') + if (!RELEASE_MODE) { + log('apply'); + log('snapshot', snapshot); + log('op', op); + log(`repro: apply(${JSON.stringify(snapshot)}, ${JSON.stringify(op)})`); + } + checkValidOp(op); + // The snapshot can be undefined if the document is being made with this + // operation. Its up to the application if they want to use OT to support + // that, or if they want to disallow the document from being undefined. + if (op === null) + return snapshot; + const held = []; // Indexed by the pick #. Autoexpand. + // The op is made up of lists. Each list item is one of three things: + // - A descent (a scalar value to descend into either a list or an object at + // the current location). Except for at the root there is always at least 1 of these. + // - An edit - which is a pick, a drop or an edit. + // - A child descent. These are always at the end of the list. We recurse for these. + // Phase 1: Pick. Returns updated subdocument. + function pick(subDoc, descent) { + //log('pick', subDoc, descent) + // const stack: (DocObj | Doc[] | undefined)[] = [] + const stack = []; + let i = 0; + for (; i < descent.length; i++) { + const d = descent[i]; + if (Array.isArray(d)) + break; + if (typeof d === 'object') + continue; + stack.push(subDoc); + // Its valid to descend into a null space - just we can't pick there, so + // we'll set subdoc to undefined in those cases. + subDoc = maybeGetChild(subDoc, d); + } + // Children. These need to be traversed in reverse order here. + // Note all children after the local op component are child descents. + for (let j = descent.length - 1; j >= i; j--) { + // This returns the new subDoc, or undefined if the child should be removed. + subDoc = pick(subDoc, descent[j]); + } + // Then back again. + for (--i; i >= 0; i--) { + const d = descent[i]; + if (typeof d !== 'object') { + const container = stack.pop(); + // Mirrored from above, which is a little gross. + const expectedChild = maybeGetChild(container, d); + // Have fuuuuunnnn! (TODO: Clean this mess up. And what should / does this do if container is a string / number?) + subDoc = (subDoc === expectedChild) + ? container + : (subDoc === undefined) + ? removeChild(container, d) + : replaceChild(container, d, subDoc); + } + else if (hasPick(d)) { + assert(subDoc !== undefined, 'Cannot pick up or remove undefined'); + if (d.p != null) + held[d.p] = subDoc; + subDoc = undefined; + } + } + return subDoc; + } + snapshot = pick(snapshot, op); + log('--- after pick phase ---'); + log('held', held, 'snapshot', snapshot); + function drop(root, descent) { + // The document might have already been cloned in-place. If it has we'll + // still throw it out and make a new one. This should be pretty quick but + // if its a problem we could optimize it by making a editable Set with + // everything we've cloned (and thus is safe to edit) + // + // I think it should be safe anyway because short lived objects are pretty + // cheap in a modern generational GC. + let subDoc = root, i = 0; // For reading + let rootContainer = { root }; // This is an avoidable allocation. + let m = 0, container = rootContainer, key = 'root'; // For writing + function mut() { + for (; m < i; m++) { + let d = descent[m]; + if (typeof d === 'object') + continue; + //log('descent', d, container, key) + assert(isValidKey(container, key)); + container = container[key] = shallowClone(container[key]); + key = d; + } + } + for (; i < descent.length; i++) { + const d = descent[i]; + if (Array.isArray(d)) { + const child = drop(subDoc, d); + if (child !== subDoc && child !== undefined) { + mut(); + subDoc = container[key] = child; + } + } + else if (typeof d === 'object') { + if (d.d != null) { // Drop + mut(); + subDoc = insertChildMut(container, key, held[d.d]); + } + else if (d.i !== undefined) { // Insert + mut(); + subDoc = insertChildMut(container, key, d.i); + } + const t = getEditType(d); + if (t) { // Edit. Ok because its illegal to drop inside mixed region + mut(); + subDoc = container[key] = t.apply(subDoc, getEdit(d)); + } + else if (d.e !== undefined) { + throw Error("Subtype " + d.et + " undefined"); + } + } + else { + // Scalar. Descend. + // Actually we should check that the types match. + // TODO: Use isValidKey to check this. + subDoc = maybeGetChild(subDoc, d); + // subDoc = subDoc != null ? (subDoc as any)[d] : undefined + } + } + return rootContainer.root; + } + //log('drop', snapshot, op) + snapshot = drop(snapshot, op); + log('-> apply returning snapshot', snapshot); + return snapshot; +} +const incPrefix = () => { if (!RELEASE_MODE) + log.prefix++; }; +const decPrefix = () => { if (!RELEASE_MODE) + log.prefix--; }; +/** + * Transforms a path by an operation. Eg: + * + * ```javascript + * newPath = transformPosition([3, 'address'], [2, {r: true}]) + * // newPath is [2, 'address'] + * ``` + * + * This is useful for cursors, for tracking annotations and various other + * markers which should stick to a single element of the JSON document. + * + * transformPosition returns null if the item stored at the path no longer + * exists in the document after the operation has been applied. Eg: + * + * ```javascript + * assert(transformPosition([2, 'address', 'street'], [2, {r: true}]) === null) + * ``` + */ +function transformPosition(path, op) { + log('transformPosition', path, op); + incPrefix(); + path = path.slice(); + checkValidOp(op); + const r = cursor_js_1.readCursor(op); + // First picks. There's 3 results here: + // 1. The object at the path (or a parent) was removed + // 2. Some parent was picked up + // 3. The op doesn't modify this path, although it might insert before / after. + let removed = false; + let pickedAtSlot; + let pickIndex; // index in path where pick happened + const advStack = []; + for (let i = 0;; i++) { + const k = path[i]; + const c = r.getComponent(); + log('pick phase', i, ':', k, c); + if (c) { + // Object / object parent removed + if (c.r !== undefined) + removed = true; + else if (c.p != null) { + removed = false; + pickedAtSlot = c.p; + pickIndex = i; + } + } + if (i >= path.length) + break; + let pickOffset = 0; + const pickAdv = cursor_js_1.advancer(r, undefined, (k, c) => { + if (hasPick(c)) { + pickOffset++; + // log('p2Pick++', p2PickOff, k, c) + } + }); + advStack.unshift(pickAdv); + const hasNext = pickAdv(k); + if (typeof k === 'number') + path[i] -= pickOffset; + if (!hasNext) + break; + } + // Instead of this bookkeeping, consider just making a new read cursor for handleDrop. + advStack.forEach(pickAdv => pickAdv.end()); + decPrefix(); + log('after pick phase. Remove', removed, 'pi', pickIndex, 'pas', pickedAtSlot, 'po', path); + // If we've been removed there's nothing else to do. + if (removed) { + log('item removed. Bailing!'); + return null; + } + // If we've been moved into a slot, we 100% don't care about anything above + // the slot we've been dropped in. + const handleDrop = () => { + incPrefix(); + let i = 0; + if (pickedAtSlot != null) { + // We've been dropped at some path. We teleport to the drop + // location and scan from there. + const rPath = r.getPath(); + i = rPath.length; + if (!RELEASE_MODE) + log('path', path, 'pi', pickIndex, 'r', rPath); + path = rPath.concat(path.slice(pickIndex)); + } + if (!RELEASE_MODE) + log('handleDrop at path', path, 'read cursor', r.getPath()); + // We need to walk down to the end of the path and adjust indicies on the + // way based on earlier list drops and edits. + for (; i < path.length; i++) { + const k = path[i]; + // We don't actually care about drops here... they shouldn't happen in + // general; but if they do then we'll silently ignore them. + const c = getComponent(r); + const et = getEditType(c); + if (et) { + const e = getEdit(c); + log('Embedded edit', e, et); + if (et.transformPosition) + path[i] = et.transformPosition(path[i], e); + // Its invalid for the operation to have drops inside the embedded edit here. + break; + } + let dropOffset = 0; + const dropAdv = cursor_js_1.advancer(r, (k, c) => (hasDrop(c) ? ~(k - dropOffset) : k - dropOffset), (k, c) => { + if (hasDrop(c)) + dropOffset++; + }); + const hasNext = dropAdv(k); + // I'm not keeping the advancer here because it doesn't matter if we + // ascend back up after this. + if (typeof k === 'number') + path[i] += dropOffset; + if (!hasNext) + break; + } + decPrefix(); + }; + if (pickedAtSlot != null) { + r.eachDrop(null, (slot) => { + if (slot === pickedAtSlot) + handleDrop(); + }); + } + else + handleDrop(); + log('-> transformPosition returning', path); + return path; +} +// ****** Compose +const setOrRemoveChild = (container, key, child) => { + // Mutate child, setting container[key] = child. Its a shame apply works + // slightly differently from this and we can't reuse the code at the top of + // the file. + // + // TODO: Refactor helper functions so they share the work. + // + // This function will never *insert* - it will only either remove an item + // (child === undefined) or replace it. + if (typeof key === 'number') { + assert(Array.isArray(container)); + assert(key < container.length); + } + else { + assert(!Array.isArray(container)); + assert(container[key] !== undefined); + } + if (child === undefined) { + if (typeof key === 'number') + container.splice(key, 1); + else + delete container[key]; + } + else { + container[key] = child; + } +}; +function compose(op1, op2) { + if (debugMode && !RELEASE_MODE) { + log("composing:"); + log(' op1:', op1); + log(' op2:', op2); + } + checkValidOp(op1); + checkValidOp(op2); + if (op1 == null) + return op2; + if (op2 == null) + return op1; + let nextSlot = 0; + const r1 = cursor_js_1.readCursor(op1), r2 = cursor_js_1.readCursor(op2); + const w = cursor_js_1.writeCursor(); + // Compose has fundamentally three contexts we need to consider: + // + // - The context of the original document (op1 picks) + // - The context of the document after op1 but before op2 (op1 drops & edits, op2 picks) + // - The context of the final document (op2 drops & edits) + // + // We need to transform everything in that middle context out: + // - op2 picks need to be transformed back by op1 + // - op1 drops and edits need to be transformed by op2 + // + // Everything else (op1 picks and op2 drops) gets copied directly into the output. + // This is a set of write cursors for locations in the output indexed by op1 + // and op2's moves, respectively. The content of these cursors is copied into + // the output. + const heldPickWrites = []; + const heldDropWrites = []; + // Held read cursors in the outside pick and drop locations, so we can + // teleport our cursors while reading. + const held1Pick = []; + const held2Drop = []; + // Map from op1 slots -> output slots + const p1SlotMap = []; + const p2SlotMap = []; + const visitedOp2EditCs = new Set(); + // This is just for debugging. + const uniqPaths = {}; + const uniqDesc = (key, reader) => { + if (reader == null) + return; + if (uniqPaths[key] == null) + uniqPaths[key] = new Set(); + // log('uniqDesc', key, reader.getPath()) + const path = reader.getPath().map((k, i) => `${i}:${k}`).join('.'); + if (uniqPaths[key].has(path)) + throw Error(`non-unique descent: ${key} ${path}`); + uniqPaths[key].add(path); + }; + // First scan the outside operations to populate held1Pick and held2Drop. + r1.traverse(null, c => { + if (c.p != null) + held1Pick[c.p] = r1.clone(); + }); + r2.traverse(null, c => { + // Clone the read cursor + if (c.d != null) + held2Drop[c.d] = r2.clone(); + }); + // Next we can scan the inside edge following op1 drops then op2 picks, + // transforming and writing the output to w. + // + // At least one of r1Drop or r2Pick must not be null. + // + // This function writes r1 drops to w1 and r2 picks to wp. + function xfBoundary(r1Pick, r1Drop, r2Pick, r2Drop, litIn, rmParent, wd, wp) { + assert(r1Drop || r2Pick); + if (!RELEASE_MODE) + log('xfBoundary', litIn, !!r1Pick && r1Pick.getPath(), !!r1Drop && r1Drop.getPath(), !!r2Pick && r2Pick.getPath(), !!r2Drop && r2Drop.getPath(), 'lit:', litIn, 'rmParent:', rmParent); + incPrefix(); + const c1d = getComponent(r1Drop); + const c2p = getComponent(r2Pick); + const rmHere = c2p ? c2p.r !== undefined : false; + const insHere = c1d ? c1d.i !== undefined : false; + const drop1Slot = c1d ? c1d.d : null; + const pick2Slot = c2p ? c2p.p : null; + log('pick2Slot', pick2Slot, 'drop1Slot', drop1Slot); + // rmParent arg for when we recurse. + const rmChildren = (rmParent || rmHere) && (pick2Slot == null); + if (pick2Slot != null) { + log('teleporting via c2 pick2Slot', pick2Slot); + r2Drop = held2Drop[pick2Slot]; + if (!RELEASE_MODE) + log('r2Drop', r2Drop && r2Drop.getPath()); + // Writes here need to be transformed to op2's drop location. + wd = heldDropWrites[pick2Slot] = new cursor_js_1.WriteCursor(); + // rmParent = null + } + else if (c2p && c2p.r !== undefined) { + r2Drop = null; + } + else { + // We're processing the drop when we see the corresponding pickup. + const c2d = getComponent(r2Drop); + if (c2d && c2d.d != null) + r2Drop = null; + } + const c2d = getComponent(r2Drop); + if (drop1Slot != null) { // Drop here. + log('teleporting via c1 drop1Slot', drop1Slot); + r1Pick = held1Pick[drop1Slot]; + // Writes here need to be transformed to op2's drop location. + wp = heldPickWrites[drop1Slot] = new cursor_js_1.WriteCursor(); + if (!rmChildren) { + const slot = p1SlotMap[drop1Slot] = nextSlot++; + log('assigning slot', slot, 'to op1 slot', drop1Slot); + wd.write('d', slot); + } + else { + log('Cancelling op1 move', drop1Slot, 'rmParent', rmParent, 'rmHere', rmHere); + // TODO: merge removed subdocuments here. This logic should mirror compose on insert. + if (rmParent && !rmHere) + wp.write('r', true); + } + } + else if (c1d && c1d.i !== undefined) { + r1Pick = null; + } + else { + // TODO: ??? Why is this also necessary? + const c1p = getComponent(r1Pick); + if (c1p && c1p.p != null) + r1Pick = null; + } + if (!RELEASE_MODE) { + uniqDesc('r1Pick', r1Pick); + uniqDesc('r1Drop', r1Drop); + uniqDesc('r2Pick', r2Pick); + uniqDesc('r2Drop', r2Drop); + } + // Note that insIn might also be set if the insert is chained. + let litOut; + if (insHere) { + assert(litIn === undefined); + litOut = c1d.i; + } + else + litOut = litIn; + if (litOut !== undefined) + log('litOut', litOut); + // Will we insert in the output? If so hold the component, and we'll write + // the insert at the end of this function. + const insComponent = (pick2Slot == null + ? (insHere && !rmParent && !rmHere) + : litOut !== undefined) ? wd.getComponent() : null; + if (insComponent) + log('insComponent', (insHere && !rmParent && !rmHere && pick2Slot == null), (pick2Slot != null && litOut !== undefined)); + else + log('no insComponent - this insert will be discarded', (insHere && !rmParent && !rmHere && pick2Slot == null), (pick2Slot != null && litOut !== undefined)); + if (pick2Slot != null) { + // .... + if (litIn !== undefined || insHere) { + // Move the literal. + } + else { + // If we pick here and drop here, join the two moves at the hip. + // TODO(cleanup): ... Can't we insert this immediately in the block + // above where we write the drop? + const slot = drop1Slot != null ? p1SlotMap[drop1Slot] : nextSlot++; + p2SlotMap[pick2Slot] = slot; + log('assigning slot', slot, 'to op2 slot', drop1Slot); + wp.write('p', slot); + } + } + else if (rmHere) { + if (!insHere && litIn === undefined) { + log('keeping write here r=', c2p.r); + wp.write('r', c2p.r); + } + } + // Edits! + // + // For edits, we basically want to grab the edit at r1Drop and r2Drop and + // just compose them together into the output. + const type1 = !rmChildren ? getEditType(c1d) : null; + const type2 = getEditType(c2d); + log('components', c1d, c2d); + if (type1 || type2) + log('types', type1 && type1.name, type2 && type2.name); + if (type1 && type2) { + assert(type1 === type2); + // Compose. + const e1 = getEdit(c1d); + const e2 = getEdit(c2d); + const r = type1.compose(e1, e2); + log('compose ->', r); + // Only write if its not a no-op. + writeEdit(wd, type1, r); + visitedOp2EditCs.add(c2d); + } + else if (type1) { + log('copying edit type1', c1d); + writeEdit(wd, type1, getEdit(c1d)); + } + else if (type2) { + log('copying edit type2', c2d); + writeEdit(wd, type2, getEdit(c2d)); + visitedOp2EditCs.add(c2d); + } + // We follow strict immutability semantics, so we have to clone the insert + // if it gets modified. We'll only clone the object literal if we have to. + const hasContainerLiteral = typeof litOut === 'object' && litOut != null; + let isCloned = false; + let p1PickOff = 0, p1DropOff = 0; + let p2PickOff = 0, p2DropOff = 0; + let litOff = 0; + const p2DropAdv = cursor_js_1.advancer(r2Drop, (k, c) => hasDrop(c) ? (p2DropOff - k - 1) : k - p2DropOff, (k, c) => { if (hasDrop(c)) + p2DropOff++; }); + const p1PickAdv = cursor_js_1.advancer(r1Pick, (k, c) => hasPick(c) ? (p1PickOff - k - 1) : k - p1PickOff, (k, c) => { if (hasPick(c)) + p1PickOff++; }); + cursor_js_1.eachChildOf(r1Drop, r2Pick, (inKey, _p1Drop, _p2Pick) => { + // The key we've been passed is the context between the two operations. + // We need to transform it to each of the starting (p1 pick) context and + // final (p2 drop) contexts. + let p1PickKey = inKey, p2DropKey = inKey, litKey = inKey; + let _p1Pick, _p2Drop; + if (typeof inKey === 'number') { + let p2Mid = inKey + p2PickOff; + _p2Drop = p2DropAdv(p2Mid); + p2DropKey = p2Mid + p2DropOff; + let p1Mid = inKey + p1DropOff; + _p1Pick = p1PickAdv(p1Mid); + if (hasDrop(getComponent(_p2Drop))) + _p1Pick = null; // But still advance! + p1PickKey = p1Mid + p1PickOff; + // If the literal is an array, we're modifying that array as we go. + litKey = inKey + litOff; + log('p1PickOff', p1PickOff, 'p1DropOff', p1DropOff, 'p2PickOff', p2PickOff, 'p2DropOff', p2DropOff, 'litOff', litOff); + log('inKey', inKey, '-> p1 mid', p1Mid, '-> p1 final', p1PickKey); + log('inKey', inKey, '-> p2 mid', p2Mid, '-> p2 final', p2DropKey); + assert(p1PickKey >= 0, 'p1PickKey is negative'); + assert(p2DropKey >= 0, 'p2DropKey is negative'); + // Update these keys as we move past. This will be used for subsequent + // descents, but not this one. + const hd1 = hasDrop(getComponent(_p1Drop)); + const hp2 = hasPick(getComponent(_p2Pick)); + if (hd1 || (hp2 && !rmChildren)) + litOff--; + if (hd1) + p1DropOff--; + if (hp2) + p2PickOff--; + } + else { + _p1Pick = p1PickAdv(inKey); + _p2Drop = p2DropAdv(inKey); + } + log('->', 'p1pick', p1PickKey, 'inkey', inKey, 'p2drop', p2DropKey, 'litKey', litKey); + wp.descend(p1PickKey); + wd.descend(p2DropKey); + // TODO: Check litOut matches expected shape here using isValidKey + const _lit = hasContainerLiteral && !hasDrop(getComponent(_p1Drop)) + ? litOut[litKey] : undefined; + log('_lit', _lit, litOut, litKey); + const _litResult = xfBoundary(_p1Pick, _p1Drop, _p2Pick, _p2Drop, _lit, rmChildren, wd, wp); + if (hasContainerLiteral && !rmChildren) { + log('_litResult', _litResult); + if (_lit !== _litResult) { + if (!isCloned) { + litOut = Array.isArray(litOut) + ? litOut.slice() : Object.assign({}, litOut); + isCloned = true; + } + setOrRemoveChild(litOut, litKey, _litResult); + log('litOut ->', litOut); + } + } + else + assert(_litResult === undefined); + wd.ascend(); + wp.ascend(); + }); + p1PickAdv.end(); + p2DropAdv.end(); + decPrefix(); + // Ok, the cases: + // - If there's a literal here, write the literal and return nothing. + // - If there's a lit in and a literal, write literal, return lit in. + // - If there's a literal here and a pick, the literal got taken by the + // pick. Return litIn. + // - If there's a litIn and a pick, the litIn got taken by the pick. Return + // undefined. + // - If there's a literal, a lit in and a pick (all 3), the pick takes the + // literal and we return the lit in. + if (insComponent != null) { + insComponent.i = litOut; + // litOut = litIn + //log('a', litIn) + } + else if (!rmParent && !rmHere && pick2Slot == null) { + //log('b', litOut) + return litOut; + } + } + const w2 = cursor_js_1.writeCursor(); + xfBoundary(r1, r1.clone(), r2, r2.clone(), undefined, false, w, w2); + w.reset(); + w.mergeTree(w2.get()); + w.reset(); + log('intermediate op', w.get()); + log('heldPickWrites', heldPickWrites.map(w => w.get())); + log('heldDropWrites', heldDropWrites.map(w => w.get())); + // Ok, now we need to walk along the picks and copy them in. + // Write op1 picks + r1.traverse(w, (c, w) => { + const slot1 = c.p; + if (slot1 != null) { + // TODO: Cancelled moves seems redundant all of a sudden. + const slot = p1SlotMap[slot1]; + if (slot != null) { + log('writing pick slot', slot); + w.write('p', slot); + } + const _w = heldPickWrites[slot1]; + if (_w) + log('merge pick write', _w.get()); + if (_w) + w.mergeTree(_w.get()); + } + else if (c.r !== undefined) { + w.write('r', c.r); + } + }); + w.reset(); + log('intermediate op with picks', w.get()); + // Write op2 drops + r2.traverse(w, (c, w) => { + const slot2 = c.d; + if (slot2 != null) { + const slot = p2SlotMap[slot2]; + if (slot != null) { + w.write('d', slot); + } + const _w = heldDropWrites[slot2]; + if (_w) + w.mergeTree(_w.get()); + } + else if (c.i !== undefined) { + w.write('i', c.i); + } + // xfBoundary above will copy any composed edits in. But we also need to + // copy any edits in op2 which aren't at the boundary. + const t = getEditType(c); + if (t && !visitedOp2EditCs.has(c)) { + writeEdit(w, t, getEdit(c)); + } + }); + const result = w.get(); + log('-> compose returning', result); + if (!RELEASE_MODE) + checkValidOp(result); + return result; +} +// ***** Invert +/** + * Invert the given operation. Operation must be invertible - which is to say, + * all removes in the operation must contain all removed content. Note any + * operation returned from transform() and compose() will have this content + * stripped. + * + * Unless you know what you're doing, I highly recommend calling invertWithDoc() + * instead. + */ +function invert(op) { + // This is pretty simple: + // - All r -> i, i -> r + // - d -> p, p -> d. (Although note this may make the slot order + // non-canonical.) + // - And edits (unfortunately) need to be reverse-transformed by the + // operation. + // + // WIP: This implementation is not correct yet. Inserts also need to be + // modified by the operation's embedded edits. This does not currently work + // correctly. + if (op == null) + return null; + const r = new cursor_js_1.ReadCursor(op); + const w = new cursor_js_1.WriteCursor(); + // let hasEdit = false // We'll only do the second edit pass if this is true. + // I could just duplicate the logic for deciding which edits need to be + // inspected in the second pass, but its much simpler to just mark the + // components in the read. + let editsToTransform; + const heldPick = []; + const heldWrites = []; + log('inverting', op); + // There's a few goals for this first traversal: + // - For any edits inside an insert, bake the edit + // - Decide if there are embedded edits we need to reverse-transform to the pick site + // - For picks and drops, simply rewrite them. + // + // Note subDoc is always in drop context (in the original document). + function invertSimple(r, w, subDoc) { + if (!RELEASE_MODE) + log('invertSimple', r.getPath(), subDoc); + incPrefix(); + const c = r.getComponent(); + let insertHere, subdocModified = false; + if (c) { + // TODO: Consider using a slot map so invert returns the same operations as + // normalize(). + if (c.p != null) { + w.write('d', c.p); // p -> d + heldPick[c.p] = r.clone(); + } + if (c.r !== undefined) + w.write('i', c.r); // r -> i + if (c.d != null) { + w.write('p', c.d); // d -> p + subDoc = undefined; + } + if (c.i !== undefined) { + // If subdoc is set (not undefined), we're either an insert or a direct + // child of an insert. In this case this method will return either + // undefined (no change to the insert) or it will return the updated + // insert child. We ripple up in this case making shallow copies and + // replace the remove with that. + subDoc = insertHere = c.i; // i -> r, written out below. + } + const t = getEditType(c); + if (t) { + log('found edit', c, subDoc); + if (subDoc === undefined) { + if (!editsToTransform) + editsToTransform = new Set(); + editsToTransform.add(c); + } + else { + // Mark that we want to modify the subdoc with the edit. There's a + // potential corner case where the operation has an edit, and then + // inside the edited content we do a drop / insert. But this is + // invalid, so I'm not going to worry too much about it here. Ideally + // checkOp should pick that sort of thing up. + log('baking edit into subDoc', subDoc, getEdit(c)); + subDoc = t.apply(subDoc, getEdit(c)); + subdocModified = true; + } + } + } + // This is to handle drops / inserts while we're inside an insert, to access + // indexes correctly in subDoc. + let dropOff = 0; + for (const key of r) { + w.descend(key); + const raw = typeof key === 'number' ? key - dropOff : key; + const childIn = maybeGetChild(subDoc, raw); + // childOut is undefined if either we aren't in an insert or the subdoc wasn't mutated. + if (hasDrop(r.getComponent())) + dropOff++; + const childOut = invertSimple(r, w, childIn); + log('key', key, 'raw', raw, childIn, childOut, 'subdoc', subDoc); + // TODO: subDoc !== undefined check here might be redundant. + if (subDoc !== undefined && childOut !== undefined) { + if (!subdocModified) { + subdocModified = true; + // And shallow clone + subDoc = shallowClone(subDoc); + } + if (!isValidKey(subDoc, raw)) + throw Error('Cannot modify child - invalid operation'); + subDoc[raw] = childOut; + } + w.ascend(); + } + decPrefix(); + if (insertHere !== undefined) { + w.write('r', subDoc); + } + else { + return subdocModified ? subDoc : undefined; + } + } + invertSimple(r, w, undefined); + if (!RELEASE_MODE) + log('invert after pass 1', w.get()); + if (editsToTransform) { + // Ok, we need to reverse-transform the edit here by the operation. We're + // traversing in the drop context (so rDrop is never null) but we also need + // to know the pick context to be able to fix indexes. + // + // w is in the pick context. + function transformEdits(rPick, rDrop, w) { + if (!RELEASE_MODE) + log('invXE', rPick === null || rPick === void 0 ? void 0 : rPick.getPath(), rDrop.getPath(), w.getPath()); + incPrefix(); + const cd = rDrop.getComponent(); + if (cd) { + const dropSlot = cd.d; + if (dropSlot != null) { + log('teleporting to drop slot', dropSlot); + rPick = heldPick[dropSlot]; + w = heldWrites[dropSlot] = cursor_js_1.writeCursor(); + } + if (editsToTransform.has(cd)) { + const t = getEditType(cd); + // TODO: Add support for types which only have invertWithDoc. + if (!t.invert) + throw Error(`Cannot invert subtype ${t.name}`); + if (!RELEASE_MODE) + log('inverting subtype', t.name, getEdit(cd)); + writeEdit(w, t, t.invert(getEdit(cd))); + } + } + // And recurse. + let pickOff = 0, dropOff = 0; + const ap = cursor_js_1.advancer(rPick, (k, c) => hasPick(c) ? (pickOff - k - 1) : k - pickOff, (k, c) => { if (hasPick(c)) + pickOff++; }); + for (const key of rDrop) { + if (typeof key === 'number') { + const mid = key - dropOff; + const _rPick = ap(mid); + const raw = mid + pickOff; + log('descend', key, mid, raw, 'dropOff', dropOff, 'pickOff', pickOff); + w.descend(raw); + transformEdits(_rPick, rDrop, w); + if (hasDrop(rDrop.getComponent())) + dropOff++; + w.ascend(); + } + else { + w.descend(key); + const _rPick = ap(key); + transformEdits(_rPick, rDrop, w); + w.ascend(); + } + } + ap.end(); + decPrefix(); + } + w.reset(); + transformEdits(r.clone(), r, w); + if (heldWrites.length) { + if (!RELEASE_MODE) + log('Merging held writes', heldWrites.map(w => w.get())); + w.reset(); + // Merge held writes + r.traverse(w, (c, w) => { + log('traverse', c); + const slot = c.p; + if (slot != null) { + log('merging slot', slot); + const _w = heldWrites[slot]; + if (_w) + log('merge', _w.get()); + if (_w) + w.mergeTree(_w.get()); + } + }); + } + } + const result = w.get(); + log('-> invert returning:', result); + if (!RELEASE_MODE) + checkValidOp(result); + return result; +} +// Does an operation contain a remove? +const anyComponent = (op, fn) => (op.some(c => (typeof c === 'object' + ? (Array.isArray(c) ? anyComponent(c, fn) : fn(c)) + : false))); +/** + * Make an operation invertible. (So, you can call invert(op) after this). + * + * This is needed because r:XX contents are optional in an operation, and may be + * optional in subtypes as well. (Eg text-unicode). + * + * This method does two main things: + * + * - Fills in r:{} bodies in components by copying data in from the document + * - Recursively calls makeInvertible on any types embedded in the operation. + * + * Note transform (and compose?) discards remove information, so if you call + * transform on an invertible operation you will need to call makeInvertible + * again before inverting. + */ +function makeInvertible(op, doc) { + // Sooo, this method is a bit funny. Its become in many ways a + // reimplementation of apply(), because it needs to deeply understand how the + // operation will be overlaid on top of the document. Both functions could + // easily be merged - although because apply was written much earlier, it + // doesn't use the cursor utility code. Its also probably a little bit faster + // as a result. + log('makeInvertible', op, doc); + if (op == null || !anyComponent(op, c => { + var _a; + return (c.r !== undefined || ((_a = getEditType(c)) === null || _a === void 0 ? void 0 : _a.makeInvertible) != null); + })) + return op; + const r = new cursor_js_1.ReadCursor(op); + const w = new cursor_js_1.WriteCursor(); + // Basically we're going to copy r to w and traverse doc along the way. When + // we run into r: components we'll clone from doc. + // TODO: Currently this only shallow clones the removed sections in, which is + // probably not safe given how people will use this library. + // Unfortunately this needs to be a lot more complicated for edits. For edits + // we need to effectively partially unapply / reverse transform the operation + // to be able to figure out where in the original document the edited content + // comes from. We'll only run that extra pass if we determine its needed. + let hasEdits = false; + const heldPick = []; + const heldDoc = []; + const traversePick = (r, w, subDoc) => { + if (!RELEASE_MODE) + log('traversePick', r.getPath(), subDoc); + incPrefix(); + const c = r.getComponent(); + let modified = false; + if (c) { + // These two can just be copied directly. Its arguably cleaner to copy + // them in the second pass, but this is fine. + if (c.d != null) + w.write('d', c.d); + if (c.i !== undefined) + w.write('i', c.i); + const pickSlot = c.p; + if (pickSlot != null) { + heldPick[pickSlot] = r.clone(); // for second pass. + assert(subDoc !== undefined, 'Operation picks up at an invalid key'); + heldDoc[pickSlot] = subDoc; + w.write('p', c.p); + } + // Copy remove - but deep clone from document + if (c.r !== undefined) { + if (subDoc === undefined) + throw Error('Invalid doc / op in makeInvertible: removed item missing from doc'); + // Actual remove logic handled after the traversal. + // w.getComponent() + } + // We'll need to run the second pass to find out where in the resulting + // document this edit will apply. + const t = getEditType(c); + if (t) { + // This feels a little dangerous. If the subtype doesn't need + // makeInvertible called, we don't need to do the second pass to copy it + // in... right? + if (t.makeInvertible) + hasEdits = true; + else + writeEdit(w, t, getEdit(c), true); + } + } + // There's a tricky problem here. If we're traversing a list and some of the child + // keys need to be removed from subdoc, we'll mess up the list's order. + let listOff = 0; + // Recurse to children + for (const key of r) { + w.descend(key); + const keyRaw = typeof key === 'number' + ? key - listOff + : key; + const childIn = maybeGetChild(subDoc, keyRaw); + const childOut = traversePick(r, w, childIn); + if (childIn !== childOut) { + log('childOut != childIn', childIn, childOut, key, keyRaw); + if (!modified) { + modified = true; + subDoc = shallowClone(subDoc); + } + // removeChild also does a shallow clone, which isn't strictly needed here. + if (childOut === undefined) { + subDoc = removeChild(subDoc, keyRaw); + if (typeof key === 'number') + listOff++; + } + else + subDoc[keyRaw] = childOut; + log('subDoc ->', subDoc); + } + w.ascend(); + } + if (c) { + if (c.r !== undefined) { + log('write r', subDoc); + // Sooo I'm not sure whether we should clone here. The function is + // correct without the call to clone - but making deep references to + // parts of the document from the returned operation is risky and error + // prone. There's probably some neat tricks we could do in this method + // to avoid the clone here - but I think this a fine solution (since + // usually removed sections will be small anyway) + w.write('r', deepClone_js_1.default(subDoc)); + subDoc = undefined; + } + else if (c.p != null) { + // Its gone somewhere else. Note that anything that was picked up cannot + // have also been removed, so this is safe. + subDoc = undefined; + } + } + log('tp returning', subDoc); + decPrefix(); + return subDoc; + }; + traversePick(r, w, doc); + log('after traversePick', w.get()); + if (hasEdits) { + w.reset(); + function traverseDrop(rPick, rDrop, w, subDoc, isLiteral) { + if (!RELEASE_MODE) + log('traverseDrop', rPick === null || rPick === void 0 ? void 0 : rPick.getPath(), rDrop.getPath(), w.getPath(), subDoc); + incPrefix(); + const c = rDrop.getComponent(); + if (c) { + if (c.i !== undefined) { + subDoc = c.i; + isLiteral = true; + log('using inserted subdoc', subDoc); + } + else if (c.d != null) { + subDoc = heldDoc[c.d]; + rPick = heldPick[c.d]; + isLiteral = false; + log('teleporting to pick', c.d, subDoc); + } + let t = getEditType(c); + if (t && t.makeInvertible) { + const edit = getEdit(c); + log('makeInvertible on child', edit, subDoc); + writeEdit(w, t, t.makeInvertible(edit, subDoc), true); + } + } + // Recurse. + let pickOff = 0, dropOff = 0; + const ap = cursor_js_1.advancer(rPick, (k, c) => hasPick(c) ? (pickOff - k - 1) : k - pickOff, (k, c) => { if (hasPick(c)) + pickOff++; }); + for (const key of rDrop) { + if (typeof key === 'number') { + const mid = key - dropOff; + const _rPick = ap(mid); + const raw = mid + pickOff; + log('key', key, 'mid', mid, 'raw', raw, 'isLiteral', isLiteral); + const child = maybeGetChild(subDoc, isLiteral ? mid : raw); + w.descend(key); + traverseDrop(_rPick, rDrop, w, child, isLiteral); + if (hasDrop(rDrop.getComponent())) + dropOff++; + w.ascend(); + } + else { + const child = maybeGetChild(subDoc, key); + w.descend(key); + traverseDrop(ap(key), rDrop, w, child, isLiteral); + w.ascend(); + } + } + ap.end(); + decPrefix(); + } + traverseDrop(r.clone(), r, w, doc, false); + } + const result = w.get(); + log('-> makeInvertible returning', result); + if (!RELEASE_MODE) + checkValidOp(result); + return result; +} +/** + * Invert the given operation in the context of the passed document. The + * specified document must be the document *before* the operation has been + * applied. + */ +function invertWithDoc(op, doc) { + return invert(makeInvertible(op, doc)); +} +// ***** Transform +const LEFT = 0, RIGHT = 1; +// This makes a shallow clone of an operation so a reader can read a partially +// written operation. The components aren't copied - which means we don't deep +// copy any inserted objects, and the components can be edited directly, which +// is a bit of a hack that transform takes advantage of. +const shallowCloneOp = (op) => { + if (op == null) + return null; + const result = op.slice(); + for (let i = 0; i < op.length; i++) { + const c = result[i]; + if (Array.isArray(c)) + result[i] = shallowCloneOp(c); + } + return result; +}; +// Returns {ok: bool, result or conflicts}. Conflicts should be symmetric - +// transform(a, b, left) should generate the same list of conflicts as +// transform(b, a, right). But obviously with path1 and path2 swapped. +// +// To keep this function as simple as possible, we bail once we find +// conflicts. Thus running this method a single time does *not* find all +// conflicts in the two operations. I could write a version of this method +// which had that behaviour, but I don't see the point. Most operations are +// simple and conflicts are rare. +// +// Conflicts have {type, op1: (slice), op2: (slice)}. +function tryTransform(op1, op2, direction) { + assert(direction === 'left' || direction === 'right', 'Direction must be left or right'); + const side = direction === 'left' ? LEFT : RIGHT; + log.quiet = !debugMode; + log.prefix = 0; + if (op2 == null) + return { ok: true, result: op1 }; + if (!RELEASE_MODE && debugMode) { + log("transforming " + direction + ":"); + log('op1', op1); + log('op2', op2); + log(`repro: transform(${[op1, op2, direction].map(x => JSON.stringify(x)).join(', ')})`); + } + checkValidOp(op1); + checkValidOp(op2); + // This is for debugging, to verify that we only descend once. + const uniqPaths = {}; // key -> set + const uniqDesc = (key, reader) => { + if (reader == null) + return; + if (uniqPaths[key] == null) + uniqPaths[key] = new Set(); + // log('uniqDesc', key, reader.getPath()) + const path = reader.getPath().map((k, i) => `${i}:${k}`).join('.'); + if (uniqPaths[key].has(path)) + throw Error(`non-unique descent: ${key} ${path}`); + uniqPaths[key].add(path); + }; + // Transform is a super fun function to write. + // + // Semantically, we have 4 mini operations between the two ops: + // - op1 picks + // - op1 drops + // + // - op2 picks + // - op2 drops + // + // And we generate two more mini operations: + // - result picks + // - result drops + // + // You can consider each of the mini operations to live in one of these contexts: + // + // - The context of the original document (which op1 picks and op2 picks live in) + // - The context of after op1 has been applied (op1 drops) + // - The context of after op2 has been applied (op2 drops, resulting op picks) + // - And the resulting document context (resulting op drops) + // + // There's effectively 2 transformations that have to take place: + // 1. op1's picks. Effectively, this needs to be transformed by [op2 pick, + // op2 drop]. + // 2. op1's drops. This is where it gets fun. Because the positions in op1's + // drops track positions already modified by op1's picks, we need to figure + // out where they *would* be without op1's drops. Then transform through + // op2's picks and drops, and finally transform by the resulting picks to get + // the final drop locations. + // + // We do this work in several steps: + // - Scan op2 picks + // - Scan op2 drops + // - Scan op1 picks. Transform by op2 and write into result + // - Scan op1 drops. Transform back by op1 picks, by op2, then by op1 picks + // again. Write into result. + // - Scan op2 drops again in their native context, writing removes when op2 + // is dropped. + // - Scan op2 drops a final time, transforming into the resulting document. + // Write any contents of op1 that was transformed by an op2 move. + let conflict = null; + const heldOp1PickByOp1 = []; + const heldOp1DropByOp1 = []; + const heldOp2PickByOp2 = []; // Used in scanOp2Drop + const heldOp2DropByOp2 = []; // Used in writeOp1Pick + const heldOp1PickByOp2 = []; + const heldOp2PickByOp1 = []; // Used in writeOp1Drop + const heldOp2DropByOp1 = []; // Used in writeOp1Drop + const heldOp2RmForOp1 = []; // held op1 read cursor to a remove of a slot2. + // TODO(cleanup): Consider just storing the path instead of the read cursor + const heldOp1RmForOp2 = []; // Set to the remove op if cancelledOp2 is set + // Map from op2's slots -> true if op2 is moving an object out of a location + // that op1 removes. In this case we need to add a remove at op2's + // destination. + // TODO(cleanup): Can I remove cancelledOp2 and just use heldOp1RmForOp2? + const cancelledOp2 = []; + // Map from op2 slot -> true if op2's drop destination is removed by op1. + // This is used to discard some removes in the output + const discardedOp2Drop = []; + // These hold write cursors, indexed by op2 slot numbers. This is used to + // stage any op1 operations that have been moved by op2. Anything in the + // designated held write will be written out at the end + const heldPickWrites = []; + const heldDropWrites = []; + // slot2 number -> slot1 number when both ops pick up the same value. + const op1PickAtOp2Pick = []; + // For blackhole detection. Each entry here is a list of op2 drops that the + // indexing slot1 contains. Consider inverting this. + const op1PicksOp2DropSlots = []; + // const op2MoveContainingOutDrop = [] // indexed by op1 slot. Slot of op2 that moves us + let nextSlot = 0; // Move slot numbers for the output. + const r1 = cursor_js_1.readCursor(op1); + const r2 = cursor_js_1.readCursor(op2); + const w = cursor_js_1.writeCursor(); // Output. + // --------------------------------------------------------------------- + // Before anything else we need to do a preliminary scan of op2's pick for + // bookkeeping, marking: + // - op2 moves of items which op1 has removed + // - shared pickup locations between the two ops. + // + // This scan is in the context of the original doc. + function scanOp2Pick(r2Pick, r1Pick = null, removed1) { + if (!RELEASE_MODE) + log('scanOp2Pick', 'r1Pick', r1Pick && r1Pick.getPath(), 'r2Pick', r2Pick.getPath(), 'removed1:', removed1 && removed1.getPath()); + incPrefix(); + const c1 = getComponent(r1Pick); + if (c1) { + if (c1.r !== undefined) + removed1 = r1Pick.clone(); + else if (c1.p != null) { + removed1 = null; + heldOp2PickByOp1[c1.p] = r2Pick.clone(); + } + } + const c2 = r2Pick.getComponent(); + let slot2; + if (c2 && ((slot2 = c2.p) != null)) { + log('c2', c2, 'rm', !!removed1); + // log(slot2, c1, c2) + heldOp1PickByOp2[slot2] = r1Pick ? r1Pick.clone() : null; + heldOp2PickByOp2[slot2] = r2Pick.clone(); + if (removed1) { + // op1 removes something op2 picks up in slot2. + cancelledOp2[slot2] = true; + heldOp1RmForOp2[slot2] = removed1; + log('Cancelled op2 slot', slot2); + } + // If this happens it means both operations picked up the same element. + // One operation's move will be cancelled based on symmetry. + if (c1 && c1.p != null) + op1PickAtOp2Pick[slot2] = c1.p; + } + // ... And recurse. + const ap1 = cursor_js_1.advancer(r1Pick); + for (const key of r2Pick) { + log('->', key); + scanOp2Pick(r2Pick, ap1(key), removed1); + } + ap1.end(); + decPrefix(); + } + scanOp2Pick(r2, r1, null); + if (!RELEASE_MODE) { + log('op1PickAtOp2Pick', op1PickAtOp2Pick); + log('cancelledOp2', cancelledOp2); + log('held op2 pick', heldOp2PickByOp1.map(x => !!x)); + } + // --------------------------------------------------------------------- + // This traverses op2's drop locations for bookkeeping, marking: + // - Fetch a (cloned) read cursor at each op2 drop location + // - Any drops which have been moved into something that was deleted by op1 + function scanOp2Drop(r1Pick, r2Pick, r2Drop, pickSlot1, removed1) { + if (!RELEASE_MODE) + log('scanOp2Drop', 'r1Pick', r1Pick && r1Pick.getPath(), r2Pick && r2Pick.getPath(), 'r2Drop', r2Drop.getPath(), 'pickSlot1', pickSlot1, 'removed:', removed1 && removed1.getPath()); + incPrefix(); + const c2d = r2Drop.getComponent(); + let droppedHere = false; + let slot2; + if (c2d) { + if (((slot2 = c2d.d) != null)) { + // Clone the read cursor so we can come back here later. + heldOp2DropByOp2[slot2] = r2Drop.clone(); + if (pickSlot1 != null) { + if (op1PicksOp2DropSlots[pickSlot1] == null) + op1PicksOp2DropSlots[pickSlot1] = []; + // op1PicksOp2DropSlots[slot2] = pickSlot1 //pickSlot1.length ? pickSlot1.slice() : null + op1PicksOp2DropSlots[pickSlot1].push(slot2); + } + // Teleport r2Pick + log('tele r2Pick to op2 slot2', slot2, !!cancelledOp2[slot2]); + r1Pick = heldOp1PickByOp2[slot2] || null; + r2Pick = heldOp2PickByOp2[slot2] || null; // TODO: Or are these always set here? + if (cancelledOp2[slot2]) { + if (removed1) + discardedOp2Drop[slot2] = true; + removed1 = heldOp1RmForOp2[slot2] || null; + } + else if (removed1 && (side === RIGHT || op1PickAtOp2Pick[slot2] == null)) { + // We have a write conflict. + log('conflicting op2 move because drop destination removed', slot2); + if (!RELEASE_MODE) + log('path', r2Drop.getPath(), r2Pick.getPath()); + if (conflict == null) + conflict = { + type: types_js_1.ConflictType.RM_UNEXPECTED_CONTENT, + op1: exports.removeOp(removed1.getPath()), + op2: exports.moveOp(r2Pick.getPath(), r2Drop.getPath()) + }; + } + droppedHere = true; + } + else if (c2d.i !== undefined) { + // if (r1Pick != null || r2Pick != null) debugger + // assert(r1Pick == null && r2Pick == null) + r1Pick = r2Pick = null; + droppedHere = true; + if (removed1) { + // TODO: It'd be nice to merge this with the conflict raise above + log('Conflicting op2 drop because op1 remove'); + if (conflict == null) + conflict = { + type: types_js_1.ConflictType.RM_UNEXPECTED_CONTENT, + op1: exports.removeOp(removed1.getPath()), + op2: exports.insertOp(r2Drop.getPath(), c2d.i) + }; + } + } + } + const c1p = getComponent(r1Pick); + if (c1p) { + if (c1p.r !== undefined) + removed1 = r1Pick.clone(); + else if (c1p.p != null) { + log('Marking pickSlot1', c1p.p); + // pickSlot1.push(c1p.p) + pickSlot1 = c1p.p; + removed1 = null; + } + } + const t2 = getEditType(c2d); + if (t2 && removed1) { + // TODO(cleanup): Merge this with the big block above if I can. Might be + // able to move the code rewriting removed1 to the top of the function. + log('rm / edit conflict'); + if (conflict == null) + conflict = { + type: types_js_1.ConflictType.RM_UNEXPECTED_CONTENT, + op1: exports.removeOp(removed1.getPath()), + op2: exports.editOp(r2Drop.getPath(), t2, getEdit(c2d), true), + }; + } + // And recurse... + let p2PickOff = 0, p2DropOff = 0; + const ap2 = cursor_js_1.advancer(r2Pick, (k, c) => hasPick(c) ? (p2PickOff - k - 1) : k - p2PickOff, (k, c) => { if (hasPick(c)) + p2PickOff++; }); + // This is trivial since it matches the pick2 context. + const ap1 = cursor_js_1.advancer(r1Pick); + for (const key of r2Drop) { + log('key ->', key); + if (typeof key === 'number') { + const p2Mid = key - p2DropOff; + log('p2Mid', p2Mid); + const _p2Pick = ap2(p2Mid); + const raw = p2Mid + p2PickOff; + log('raw', raw); + const _p1Pick = ap1(raw); + log('key', key, 'p2DropOff', p2DropOff, 'p2PickOff', p2PickOff); + log('-> p2drop', key, 'p2Mid', p2Mid, !!_p2Pick, 'raw', raw, !!_p1Pick); + p2DropOff += +scanOp2Drop(_p1Pick, _p2Pick, r2Drop, pickSlot1, removed1); + } + else { + log('-> k', key); + const _p2Pick = ap2(key); + const _p1Pick = ap1(key); + scanOp2Drop(_p1Pick, _p2Pick, r2Drop, pickSlot1, removed1); + } + } + ap2.end(); + ap1.end(); + decPrefix(); + // if (c1p && c1p.p != null) pickSlot1.pop() + return droppedHere; + } + scanOp2Drop(r1, r2, r2.clone(), null, null); + log('held op2 drop', heldOp2DropByOp2.map(x => x && x.get())); + if (conflict) { + log('returning conflict 4', conflict); + return { ok: false, conflict }; + } + log('discarded op2 drop', discardedOp2Drop.map(x => !!x)); + log('op1PicksOp2DropSlots', op1PicksOp2DropSlots); + // --------------------------------------------------------------------- + // We could almost (but not quite) double up on pickComponents and + // heldOp1PickByOp1 --- heldOp1PickByOp1 shows us the original component, but + // pickComponents are actually write components, so its no good. + const pickComponents = []; + let cancelledRemoves = null; // Lazy initialized set of components. + // ==== A brief note on blackholes: + // + // We need to look out for instances where the two operations try to mutually + // move data into the other. + // + // This always looks the same: + // op1 pickup contains an op2 drop as a child + // op2 pickup contains an op1 drop as a child + // ... With matching slot numbers. + // This handles the pick phase of op (r1). Picks and removes in r1Pick are + // transformed via op2 (r2Pick and r2Drop) and written into writer w. + // + // removed tracks op2's removals. + function writeOp1Pick(r1Pick, r2Pick, r2Drop, w, removed2) { + if (!RELEASE_MODE) + log('writeOp1Pick', 'r1Pick', r1Pick.getPath(), 'r2Pick', r2Pick && r2Pick.getPath(), 'r2Drop', r2Drop && r2Drop.getPath(), 'w', w.getPath(), 'removed2', removed2 && removed2.getPath()); + incPrefix(); + // p1Pick and w are always defined. Either or both of r2Pick and r2Drop + // could be null. + let iAmMoved = false; + const c2p = getComponent(r2Pick); + if (hasPick(c2p)) { + const slot2 = c2p.p; + if (slot2 != null) { + // op2 has moved the item we're descending into. Instead of taking + // picks from the original (source) location, take them from the + // destination location. + if (!RELEASE_MODE) + log('teleporting via op2 slot2', slot2, 'to', heldOp2DropByOp2[slot2].getPath()); + // This breaks the holding logic. Might still need it though - might + // need an au naturale version of r2Drop or something?? I'm not sure + // what this logic should be. + r2Drop = heldOp2DropByOp2[slot2]; + w = heldPickWrites[slot2] = cursor_js_1.writeCursor(); + // TODO: Does w need to be reset here? + // if (!(w = heldPickWrites[slot2])) w = heldPickWrites[slot2] = writeCursor() + iAmMoved = true; + removed2 = null; + } + else { // op2 is a remove (c2p.r !== undefined) + // op2 deleted the item we're trying to move. + r2Drop = null; + removed2 = r2Pick.clone(); + } + } + else if (hasDrop(getComponent(r2Drop))) { + r2Drop = null; + } + // TODO: Could check w as well, but we'd need the destination path + // including tele somehow. + if (!RELEASE_MODE) { + uniqDesc('op1pick r1Pick', r1Pick); + uniqDesc('op1pick r2Pick', r2Pick); + uniqDesc('op1pick r2Drop', r2Drop); + } + const c1 = r1Pick.getComponent(); + if (c1) { + const slot1 = c1.p; + if (slot1 != null) { + if (removed2) + heldOp2RmForOp1[slot1] = removed2; + log('considering slot', slot1, 'removed2', !!removed2, 'iAmMoved', iAmMoved); + if (removed2 || (side === RIGHT && iAmMoved)) { + log("cancelling p1 move", slot1); + // A move from a subtree that was removed2 by op2. + pickComponents[slot1] = null; // redundant, but we can check this below. + } + else { + log('holding pick component'); + // TODO: Consider swapping this back to pickComponents + pickComponents[slot1] = w.getComponent(); //w.clone() + } + // This is used by the op2Drop scan, below. + heldOp1PickByOp1[slot1] = r1Pick.clone(); + // And this is used by writeOp1Drop + // heldOp2DropByOp1[slot1] = r2Drop ? r2Drop.clone() : null // Wrong for some reason? + if (r2Drop) + heldOp2DropByOp1[slot1] = r2Drop.clone(); + } + else if (c1.r !== undefined) { + // Copy the remove from op1. + if (!removed2) { + log('copying remove'); + w.write('r', true); //c1.r + } + if (removed2 || iAmMoved) { + log('Cancelling remove', c1, !!removed2, iAmMoved); + if (cancelledRemoves == null) + cancelledRemoves = new Set; + cancelledRemoves.add(c1); + } + } + } + // Ok, now to scan the children. We need these offsets to track how array + // offsets are changed by the transform. + let p2PickOff = 0, p2DropOff = 0; + // advancer(cursor, listMapFn, listAdvanceFn). + const ap2Pick = cursor_js_1.advancer(r2Pick, undefined, (k, c) => { + if (hasPick(c)) { + p2PickOff++; + log('r2Pick++', p2PickOff, k, c); + } + }); + // ap2Pick._name = '2pick' + const ap2Drop = cursor_js_1.advancer(r2Drop, (k, c) => ( + // Sneaking a second bit in here via 2s compliment. + hasDrop(c) ? ~(k - p2DropOff) : k - p2DropOff), (k, c) => { + if (hasDrop(c)) { + p2DropOff++; + log('r2Drop++', p2DropOff, k, c); + } + }); + // ap2Drop._name = '2drop' + log('children', '2p', !!r2Pick, '2d', !!r2Drop); + if (r1Pick) + for (const key of r1Pick) { + log('-> k', key); + if (typeof key === 'string') { + const p2Pick_ = ap2Pick(key); + const p2Drop_ = ap2Drop(key); + w.descend(key); + writeOp1Pick(r1Pick, p2Pick_, p2Drop_, w, removed2); + w.ascend(); + } + else { + const p2Pick_ = ap2Pick(key); + const p2Mid = key - p2PickOff; + const p2Drop_ = hasPick(getComponent(p2Pick_)) ? null : ap2Drop(p2Mid); + const finalKey = p2Mid + p2DropOff; + log("k" + key + " -> m" + p2Mid + " -> f" + finalKey); + assert(finalKey >= 0); + w.descend(finalKey); + writeOp1Pick(r1Pick, p2Pick_, p2Drop_, w, removed2); + w.ascend(); + } + } + ap2Pick.end(); + ap2Drop.end(); + decPrefix(); + } + log('---- pick ----'); + writeOp1Pick(r1, r2, r2.clone(), w, null); + w.reset(); + if (!RELEASE_MODE) { + log('intermediate result', w.get()); + log('held picks', heldOp1PickByOp1.map(x => x.getComponent() || !!x)); + log('held op2 drops by op1', heldOp2DropByOp1.map(x => x.getComponent() || !!x)); + log('held pick writes', heldPickWrites.map(w => w.get())); + log('pc', pickComponents); + } + // --------------------------------------------------------------------- + // Set of op2 components with an insert or drop which is being overwritten by + // op1. Initialized lazily. Only used on LEFT. + // let overwrittenDrops = null + // Mapping from output move slot -> op1 move slot + let outputSlotMap = []; + // Finally the big one. This handles the semantics of op1's drop phase (move + // drop and insert). This is complicated because the drop phase inside the op + // is pre-transformed by op1's pick phase, which we have to account for when + // we're transforming. + // + // Removed tracks op2's pick removals. + function writeOp1Drop(p1Pick, p1Drop, p2Pick, p2Drop, w, removed2 /*, insidePick2*/) { + assert(p1Drop); + const c1d = p1Drop.getComponent(); + if (!RELEASE_MODE) + log('drop', p1Pick && p1Pick.getPath(), p1Drop.getPath(), p2Pick && p2Pick.getPath(), p2Drop && p2Drop.getPath(), 'c:', c1d, 'r:', !!removed2 /*, 'ip2', insidePick2*/); + incPrefix(); + let c2d = getComponent(p2Drop); // non-const for teleporting. + let droppedHere = false; + const insOrMv = (r1, r2, c) => (r1 ? exports.moveOp(r1.getPath(), r2.getPath()) : exports.insertOp(r2.getPath(), c.i)); + // TODO: Reconsider the entire logic of this code block. It seems like + // there's a bunch of different cases based on what kind of edit we have, + // whether we conflict and whether the output is identical. There's + // probably nicer ways to write this. + if (hasDrop(c1d)) { + const slot1 = c1d.d; + // Used to fill blackhole conflict objects + if (slot1 != null) + heldOp1DropByOp1[slot1] = p1Drop.clone(); + // pc is null if the item we're moving was removed by op2 or moved & + // we're 'right'. + const pc = slot1 != null ? pickComponents[slot1] : null; + let identical = false; + if (c1d.i !== undefined || ((slot1 != null) && pc)) { + // Times to write: + // - There is no c2 drop + // - The other drop has been cancelled + // - We are left (and then we also need to move or remove) + // If we are identical, skip everything. Neither write nor remove. + // Doing this with logical statements was too hard. This just sets up + // some state variables which are used in conditionals below. + // let write = true + log('looking to write', c1d, c2d, cancelledOp2); + let slot2; + if (c2d && (c2d.i !== undefined || ((slot2 = c2d.d) != null && !cancelledOp2[slot2]))) { + identical = slot2 != null + ? (slot1 != null) && slot1 === op1PickAtOp2Pick[slot2] + : deepEqual_js_1.default(c2d.i, c1d.i); + // If we're the left op, we can avoid a collision if we're trying to + // move op2's drop somewhere else. + if (!identical && (slot2 == null || side === RIGHT || op1PickAtOp2Pick[slot2] == null)) { + log("Overlapping drop conflict!"); + if (conflict == null) + conflict = { + type: types_js_1.ConflictType.DROP_COLLISION, + op1: insOrMv(slot1 != null ? heldOp1PickByOp1[slot1] : null, p1Drop, c1d), + op2: insOrMv(slot2 != null ? heldOp2PickByOp2[slot2] : null, p2Drop, c2d), + }; + } + } + // log('write', write, 'identical', identical, 'removed2', removed2) + log('write', 'identical', identical, 'removed2', !!removed2); + if (!identical) { + if (removed2) { + log("Drop into remove conflict!"); + // if (pc) pc['r'] = true + // log(slot1, heldOp1PickByOp1[slot1].getPath()) + if (conflict == null) + conflict = { + type: types_js_1.ConflictType.RM_UNEXPECTED_CONTENT, + op1: insOrMv(slot1 != null ? heldOp1PickByOp1[slot1] : null, p1Drop, c1d), + op2: exports.removeOp(removed2.getPath()), + }; + } + else { + // Copy the drops. + if (slot1 != null) { + log('copying the drop', slot1, 'using new slot', nextSlot); //, 'inside', insidePick2) + outputSlotMap[nextSlot] = slot1; + // op2MoveContainingOutDrop[nextSlot] = insidePick2 + w.write('d', pc.p = nextSlot++); + } + else { + log('copying insert'); + w.write('i', deepClone_js_1.default(c1d.i)); // TODO: Only clone based on opts.consume. + } + droppedHere = true; + } + } + } + else if ((slot1 != null) && !pc) { + // pc is null if either slot1 was removed at the source, or if our + // move has been cancelled because op2 picked it up as well and we're + // right. + // I'm not 100% sure this is the right implementation of the logic I want here. + // TODO: This seems straight out wrong. + // log('xxxx r2', slot1, !!heldOp2RmForOp1[slot1]) + // removed2 = p2Pick.clone() + const h = heldOp2RmForOp1[slot1]; + if (h) + removed2 = h.clone(); + // else if (!removed2) log('BEHAVIOUR CHANGE') + } + if (slot1 != null) { + log('teleporting p1Pick and op2Pick via op1 slot', slot1); + p1Pick = heldOp1PickByOp1[slot1]; + p2Pick = heldOp2PickByOp1[slot1]; + p2Drop = heldOp2DropByOp1[slot1]; // Note: Potentially overwritten again below. + if (!RELEASE_MODE) + log('p1Pick', p1Pick && p1Pick.getPath(), 'p2Pick', p2Pick && p2Pick.getPath(), 'p2Drop', p2Drop && p2Drop.getPath()); + } + else if (c1d.i !== undefined) { + // We're descending into an edit of an insert. Nobody else speaks to us. + // + // Consider moving this into the identical{} block above. + p1Pick = p2Pick = null; + if (!identical) + p2Drop = null; // If identical, we'll merge the p2 child drops. + // } else if (hasPick(getComponent(p1Pick)) && identical) { + // p1Pick = p2Pick = null + } + } + else if (hasPick(getComponent(p1Pick))) { + p1Pick = p2Pick = p2Drop = null; + } + // We can't check this for writes because we teleport & hold the write cursor sometimes. + // uniqDesc('op1drop write', w) + if (!RELEASE_MODE) { + uniqDesc('op1drop op2Pick', p2Pick); + uniqDesc('op1drop op1Pick', p1Pick); + uniqDesc('op1drop op1Drop', p1Drop); + } + const c1p = getComponent(p1Pick); + const c2p = getComponent(p2Pick); + if (hasPick(c2p)) { + const slot2 = c2p.p; + if ((c2p.r !== undefined && (!c1p || c1p.r === undefined)) || cancelledOp2[slot2]) { + // In this case the op2 move moves the children *into* something that + // we've removed in op1. If we both remove the object, then allow my + // inserts into the resulting output as if the other op didn't remove + // at all. + log('flagging remove'); + p2Drop = null; + removed2 = p2Pick.clone(); + } + else if (slot2 != null) { + // Teleport. + log('teleporting p2Drop via c2 slot2', slot2); + p2Drop = heldOp2DropByOp2[slot2]; + // insidePick2 = slot2 + // if (op1PicksOp2DropSlots[slot2] != null) log('inside pick 1', op1PicksOp2DropSlots[slot2]) + if (!RELEASE_MODE) + log('p2Drop', p2Drop && p2Drop.getPath()); + if (side === RIGHT || op1PickAtOp2Pick[slot2] == null) { + log('teleporting write'); + if (!(w = heldDropWrites[slot2])) + w = heldDropWrites[slot2] = cursor_js_1.writeCursor(); + w.reset(); + removed2 = null; + } + } + } + else if (!hasDrop(c1d) && hasDrop(c2d)) + p2Drop = null; + if (!RELEASE_MODE) + uniqDesc('op1drop op2Drop', p2Drop); + // Now we want to read both teleports for the edit component. This is just + // used for edits at this point, and it takes into account both teleports. + // I look for your edit in the place *I moved from* and *you moved to*. + c2d = p2Drop != null ? p2Drop.getComponent() : null; + // Embedded edits of subtrees. This is tricky because the embedded edit + // needs to be transformed by op2's drop (if any) + const t1 = getEditType(c1d); + if (t1) { + log('edit:', !!removed2, c1d, c2d); + const e1 = getEdit(c1d); + if (!removed2) { + const t2 = getEditType(c2d); + let e; + if (t2) { + // TODO: Should this be t1.name vs t2.name ? + if (t1 !== t2) + throw Error("Transforming incompatible types"); + const e2 = getEdit(c2d); + e = t1.transform(e1, e2, direction); + } + else { + // TODO: ... check opts.consume here. + e = deepClone_js_1.default(e1); + } + log('write edit', e); + writeEdit(w, t1, e); + //writeEdit(w, t1, getEdit(c1d)) + } + else { + // The embedded edit is trying to edit something that has been removed + // by op2. + // + // This branch also triggers if the embedded edit is a no-op. I'm not + // sure what the expected behaviour should be in this case, because both + // options are bad: + // - We can return `null` as op1, even though this wouldn't trigger the + // conflict if we re-ran transform on the returned result + // - We preserve the no-op in the conflict, returning a non-normalized + // result (current behaviour) + if (conflict == null) + conflict = { + type: types_js_1.ConflictType.RM_UNEXPECTED_CONTENT, + op1: exports.editOp(p1Drop.getPath(), t1, e1, true), + op2: exports.removeOp(removed2.getPath()), + }; + } + } + // Ok, now for descent crazy town. This is all needed to deal with keeping + // track of the indexes of list children. + let p1PickOff = 0, p1DropOff = 0; + let p2PickOff = 0, p2DropOff = 0; + let outPickOff = 0, outDropOff = 0; + let p1pValid = p1Pick != null ? p1Pick.descendFirst() : false; + let p1pDidDescend = p1pValid; + const ap2p = cursor_js_1.advancer(p2Pick, undefined, (k, c) => { + // Spoilers: There's going to be more logic here before this is correct. + // Probably also need to take into account op1PickAtOp2Pick. + // if (c && (c.r !== undefined || (c.p != null && !cancelledOp2[c.p]))) { + if (hasPick(c)) { + p2PickOff++; + log('p2Pick++', p2PickOff, k, c); + } + }); + // ap2p._name = '2p' + // I'm only using one advancer here because it was too hard tracing bugs + // through two advancers. Simply using bare variables & advancing here + // results in more code, but its slightly more straightforward because all + // the state variables are local. + let p2dValid = p2Drop != null ? p2Drop.descendFirst() : false; + let p2dDidDescend = p2dValid; + log('children.', '1p:', !!p1Pick, '1d:', !!p1Drop, '2p:', !!p2Pick); + for (const key of p1Drop) { + log('-> p1Drop looking at child', key); + // Ok... time for crazy. + if (typeof key === 'number') { + // *********** Array children. + let _p1Pick; + log('p1DropEachChild k:', key, 'p1d:', p1DropOff, 'p1p:', p1PickOff, 'raw: -', 'p2p:', p2PickOff, 'p2d:', p2DropOff, 'op:', outPickOff, 'od:', outDropOff); + const hd1 = hasDrop(p1Drop.getComponent()); + const k1Mid = key - p1DropOff; // Position between pick and drop phase + { // Calculate _p1Pick and outPickOff + let p1k; + log('Looking for p2 Pick at k1Mid', k1Mid); + incPrefix(); + while (p1pValid && typeof (p1k = p1Pick.getKey()) === 'number') { + p1k += p1PickOff; + const c = p1Pick.getComponent(); + const hp = hasPick(c); + log('p1k', p1k, 'k1mid', k1Mid, 'hp', hp); + if (p1k > k1Mid || (p1k === k1Mid && (!hp || (side === LEFT && hd1)))) + break; + // break if p1k === k1Mid and hd1 + // increment if p2k < k1Mid + if (hp) { + log('p1PickOff--'); + p1PickOff--; + // We want to increment outPickOff if a pick is going to appear + // here in the output. That means sort of reimplementing the + // logic above :( + const slot1 = c.p; + log('considering outPickOff--', c, cancelledRemoves, pickComponents[slot1], op1PickAtOp2Pick.includes(slot1)); + log(c.d != null, getComponent(heldDropWrites[c.d]), hasPick(getComponent(heldDropWrites[c.d]))); + // TODO: This looks wrong - we aren't taking into account identical pick/drop. + if ((c.r !== undefined && (!cancelledRemoves || !cancelledRemoves.has(c))) + || (slot1 != null && pickComponents[slot1] && (side === RIGHT || !op1PickAtOp2Pick.includes(slot1)))) { + log('outPickOff-- from pickup'); + outPickOff--; + } + } + p1pValid = p1Pick.nextSibling(); + } + _p1Pick = p1pValid && p1k === k1Mid ? p1Pick : null; + decPrefix(); + log('_p1Pick', !!_p1Pick); + } + const raw = k1Mid - p1PickOff; // Position of the child before op1 has changed it + log('ap2p', raw); + let _p2Pick = ap2p(raw); // Advances p2PickOff. + log('_p2Pick', !!_p2Pick, 'p2PickOff', p2PickOff); + const k2Mid = raw - p2PickOff; + let _p2Drop = null; + { // Calculate _p2Drop. + let p2dk, op2Mid; + log('Looking for p2 Drop at p2mid', k2Mid); + incPrefix(); + while (p2dValid && typeof (p2dk = p2Drop.getKey()) === 'number') { + op2Mid = p2dk - p2DropOff; + const c = p2Drop.getComponent(); + const hd2 = hasDrop(c); + log('p2d looking at child', p2dk, c, 'at op2mid', op2Mid, 'target', k2Mid, ' / raw', raw, p2DropOff, hd2, hd1); + if (op2Mid > k2Mid) + break; + if (op2Mid === k2Mid) { + if (hd2) { + if (side === LEFT && hd1) { + _p2Drop = p2Drop; + break; + } + // I'm not convinced this logic is correct.. :/ + // log('hp2 break', _p2Pick && hasPick(_p2Pick.getComponent()) + const hp2 = _p2Pick && hasPick(_p2Pick.getComponent()); + if (side === LEFT && hp2) + break; + } + else { + _p2Drop = p2Drop; + break; + } + } + // log('p2d continuing') + if (hd2) { + // Ugh to reimplementing the logic of figuring out where we'll keep picks + const slot2 = c.d; + log('considering p2Drop', c, slot2, cancelledOp2[slot2], op1PickAtOp2Pick[slot2]); + if (c.i !== undefined + || (!cancelledOp2[slot2] && ((op1PickAtOp2Pick[slot2] == null) || side === RIGHT))) { + log('p2DropOff++ from drop'); + p2DropOff++; + } + else if (cancelledOp2[slot2] || (op1PickAtOp2Pick[slot2] != null && side === LEFT)) { + // Skip it, but this drop won't appear in the output either so ignore it. + log('skipped because the drop was cancelled'); + p2DropOff++; + outDropOff--; + } + // log('p2Drop++', p2DropOff, op2Mid, c) + } + p2dValid = p2Drop.nextSibling(); + } + //_p2Drop = p2dValid && op2Mid === k2Mid ? p2Drop : null + decPrefix(); + log('_p2Drop', !!_p2Drop); + } + const k2Final = k2Mid + p2DropOff; + log('->DropEachChild k:', key, 'p1d:', p1DropOff, 'p1p:', p1PickOff, 'raw:', raw, 'p2p:', p2PickOff, 'p2d:', p2DropOff, 'op:', outPickOff, 'od:', outDropOff); + const descend = k2Final + outPickOff + outDropOff; + log("k: " + key + " -> mid " + k1Mid + + " -> raw " + raw + + " -> k2Mid " + k2Mid + " -> k2Final " + k2Final + + " -> descend " + descend); + assert(descend >= 0, 'trying to descend to a negative index'); + w.descend(descend); + if (hd1) { + // If op1 is dropping here, we'll insert a new item in the list. + // Because op2 doesn't know about the new item yet, there's nothing + // we need to transform the drop against. + _p1Pick = _p2Pick = _p2Drop = null; + log('omg p1dropoff', p1DropOff); + p1DropOff++; + } + if (writeOp1Drop(_p1Pick, p1Drop, _p2Pick, _p2Drop, w, removed2)) + outDropOff++; + w.ascend(); + } + else { // String keys - descending from an object. + let p1k; + while (p1pValid) { // Skip items in p1Pick until key + p1k = p1Pick.getKey(); + if (typeof p1k === 'string' && (p1k > key || p1k === key)) + break; + p1pValid = p1Pick.nextSibling(); + } + const _p1Pick = (p1pValid && p1k === key) ? p1Pick : null; + const _p2Pick = ap2p(key); + let p2dk; + while (p2dValid) { + p2dk = p2Drop.getKey(); + if (typeof p2dk === 'string' && (p2dk > key || p2dk === key)) + break; + p2dValid = p2Drop.nextSibling(); + } + const _p2Drop = p2dValid && p2dk === key ? p2Drop : null; + // aka, _p2Drop = ap2d(key) + // And recurse. + w.descend(key); + writeOp1Drop(_p1Pick, p1Drop, _p2Pick, _p2Drop, w, removed2); + w.ascend(); + } + } + ap2p.end(); + if (p1pDidDescend) + p1Pick.ascend(); + if (p2dDidDescend) + p2Drop.ascend(); + decPrefix(); + return droppedHere; + } + log('----- drop -----'); + writeOp1Drop(r1, r1.clone(), r2, r2.clone(), w, null); + if (conflict) { + log('returning conflict 3', conflict); + return { ok: false, conflict }; + } + w.reset(); + if (!RELEASE_MODE) { + log('output slot map', outputSlotMap); + log('merging picks into partial output', w.get()); + log('held pick writes', heldPickWrites.map(w => w.get())); + } + const eachDrop = (r, w, fn) => r.traverse(w, (c, w) => { + if (c.d != null) + fn(c.d, r, w); + }); + if (cancelledOp2.length || heldPickWrites.length) { + log('merging'); + eachDrop(r2, w, (slot2, r, w) => { + if (cancelledOp2[slot2] && !discardedOp2Drop[slot2]) { + log('removing at held drop2', slot2); + w.write('r', true); + } + if (heldPickWrites[slot2]) { + w.mergeTree(heldPickWrites[slot2].get()); + } + }); + w.reset(); + if (!RELEASE_MODE) + log('after merge', w.get()); + } + // --------------------------------------------------------------------- + // Finally copy in the moved writes. Here we need to pass the other readers + // because we sometimes need to adjust array indexes. + // const outDropPath = [] + const heldOutDropRead = []; + // Its like a wave slowly breaking on the beach... + const heldOutDropWrites = []; + // This iterates through p2Drop + // + // Writes are transformed to the context of the output drops. + function writeHeldOp2Drop(p2Drop, outPick, outDrop, w, parentC, removedOut) { + // TODO(cleanup): parentC is unused. Remove it. + if (!RELEASE_MODE) + log('writeHeldOp2Drop', 'p2Drop', p2Drop.getPath(), 'outPick', outPick && outPick.getPath(), 'outDrop', outDrop && outDrop.getPath(), 'pc', parentC, 'rm out', removedOut); + log.prefix++; + const coutp = getComponent(outPick); + if (coutp && hasPick(coutp)) { + if (coutp.p != null) { + parentC = coutp; + const slot = coutp.p; + log('teleporting writes to output slot', slot, heldOutDropRead[slot].getPath()); + outDrop = heldOutDropRead[slot]; + w = heldOutDropWrites[slot] = cursor_js_1.writeCursor(); + // picksOut.push(coutp.p) + } + else if (coutp.r !== undefined) { + outDrop = null; + removedOut = true; + } + log('coutp', coutp); + } + else if (hasDrop(getComponent(outDrop))) { + outDrop = null; + } + // For drops we need to transform the location by the transform *result*. + const c2 = p2Drop.getComponent(); + if (c2) { + let slot2; + if ((slot2 = c2.d) != null) { + log('c2 drop slot', slot2); + const _w = heldDropWrites[slot2]; + if (_w) { + log('merge tree', _w.get()); + w.mergeTree(_w.get()); + outDrop = cursor_js_1.readCursor(_w.get()); + } + } + } + if (!RELEASE_MODE) { + uniqDesc('outDrop p2Drop', p2Drop); + uniqDesc('outDrop outPick', outPick); + // When outDrop is inside merged reads it won't be guaranteed to have + // unique paths. + //uniqDesc('outDrop outDrop', outDrop) + } + let outPickOff = 0, outDropOff = 0; + const oPickAdv = cursor_js_1.advancer(outPick, undefined, (k, c) => { + if (hasPick(c)) { + outPickOff--; + log('outPickOff++'); + } + }); + const oDropAdv = cursor_js_1.advancer(outDrop, (k, c) => (hasDrop(c) ? -(k - outDropOff) - 1 : k - outDropOff), (k, c) => { + if (hasDrop(c)) { + outDropOff++; + log('outDropOff++'); + } + }); + for (const o2dk of p2Drop) { + if (typeof o2dk === 'number') { + const _outPick = oPickAdv(o2dk); + const rmid = o2dk + outPickOff; + const _outDrop = oDropAdv(rmid); + const rfinal = rmid + outDropOff; + log('->', o2dk, 'outPickOff', outPickOff, '-> rmid', rmid, 'dropOff', outDropOff, 'final', rfinal); + w.descend(rfinal); + writeHeldOp2Drop(p2Drop, _outPick, _outDrop, w, parentC, removedOut); + w.ascend(); + } + else { + log('->', o2dk); + w.descend(o2dk); + writeHeldOp2Drop(p2Drop, oPickAdv(o2dk), oDropAdv(o2dk), w, parentC, removedOut); + w.ascend(); + } + } + decPrefix(); + // if (coutp && coutp.p != null) picksOut.pop() + oPickAdv.end(); + oDropAdv.end(); + } + if ((heldDropWrites.length || cancelledOp2.length) && !conflict) { + log('------ write held picks and drops -----'); + if (!RELEASE_MODE) + log('held drop writes', heldDropWrites.map(h => h.get())); + // This is super nasty. The problem is that we need a read cursor over the + // output so far, but the output is still mutable (we're going to mutate + // it). I could avoid the clone entirely by doing the full transform work + // that op1 drop above does. + // TODO: I have a feeling I might need to loop this stuff until we don't + // have any held operations. + const rOut = cursor_js_1.readCursor(shallowCloneOp(w.get())); + eachDrop(rOut, null, (slotOut, r) => { + heldOutDropRead[slotOut] = r.clone(); + }); + heldDropWrites.forEach(hdw => { + if (hdw) + eachDrop(cursor_js_1.readCursor(hdw.get()), null, (slotOut, r) => { + heldOutDropRead[slotOut] = r.clone(); + }); + }); + if (!RELEASE_MODE) { + log('merging writes into', rOut.get()); + log('heldOutDropRead', heldOutDropRead.map(r => r && r.getPath())); + } + writeHeldOp2Drop(r2, rOut, rOut.clone(), w, null, false); + w.reset(); + if (conflict) { + log('-> xf returning conflict 2', conflict); + return { ok: false, conflict }; + } + log('-- after writeHeldOp2Drop', w.get()); + if (!RELEASE_MODE) + log('held output writes', heldOutDropWrites.map(h => h.get())); + if (heldOutDropWrites.length) { + const heldOutDropContent = heldOutDropWrites.map(w => w ? w.get() : null); + const rOut2 = cursor_js_1.readCursor(shallowCloneOp(w.get())); + eachDrop(rOut2, w, (slotOut, r, w) => { + const data = heldOutDropContent[slotOut]; + if (data) { + w.mergeTree(data); + heldOutDropContent[slotOut] = null; + } + }); + if (heldOutDropContent.find(x => x)) { + log('BLACKHOLE'); + // If there's nowhere we can naturally place a drop, it must be + // blackholed. Blackholes can have multiple picks & drops, and we need + // all the places in op1 and op2 where the drops happened to + // reconstruct it. + const w1 = cursor_js_1.writeCursor(), w2 = cursor_js_1.writeCursor(); + let nextSlot1 = 0, nextSlot2 = 0; + // log('hdo', heldOutDropContent, 'mcod', op2MoveContainingOutDrop) + heldOutDropContent.forEach((data, slotOut) => { + if (data != null) { + eachDrop(cursor_js_1.readCursor(data), null, c => { + const slot1 = outputSlotMap[c]; + w1.writeMove(heldOp1PickByOp1[slot1].getPath(), heldOp1DropByOp1[slot1].getPath(), nextSlot1++); + log('blackhole slot1', slot1); + const slot2s = op1PicksOp2DropSlots[slot1]; + if (slot2s) + slot2s.forEach(slot2 => { + if (!cancelledOp2[slot2] && (side === RIGHT || op1PickAtOp2Pick[slot2] == null)) { + w2.writeMove(heldOp2PickByOp2[slot2].getPath(), heldOp2DropByOp2[slot2].getPath(), nextSlot2++); + } + }); + }); + // const slot2 = op2MoveContainingOutDrop[slotOut] + // assert(slot2 != null) + // log(slot2, heldOp2PickByOp2[slot2] && heldOp2PickByOp2[slot2].getPath(), heldOp2DropByOp2[slot2] && heldOp2PickByOp2[slot2].getPath()) + // w2.writeMove(heldOp2PickByOp2[slot2].getPath(), heldOp2DropByOp2[slot2].getPath(), nextSlot2++) + } + }); + conflict = { + type: types_js_1.ConflictType.BLACKHOLE, + op1: w1.get(), + op2: w2.get(), + }; + } + } + } + if (conflict) { + log('-> xf returning conflict 5', conflict); + log(); + return { ok: false, conflict }; + } + else { + const result = w.get(); + log('-> xf returning', result); + log(); + if (!RELEASE_MODE) + checkValidOp(result); + return { ok: true, result }; + } +} +const throwConflictErr = (conflict) => { + const err = new Error('Transform detected write conflict'); + err.conflict = conflict; + err.type = err.name = 'writeConflict'; + throw err; +}; +// Transform variant which throws on conflict. +function transform(op1, op2, side) { + const res = tryTransform(op1, op2, side); + if (res.ok) + return res.result; + else + throwConflictErr(res.conflict); +} +/** Make an op that removes the content at the drop and edit destinations of the passed op */ +const opThatRemovesDE = (op) => { + const w = cursor_js_1.writeCursor(); + cursor_js_1.readCursor(op).traverse(w, (c, w) => { + if (hasDrop(c) || getEditType(c)) + w.write('r', true); + }); + return w.get(); +}; +const resolveConflict = (conflict, side) => { + const { type, op1, op2 } = conflict; + log('resolving conflict of type', type); + switch (type) { + case types_js_1.ConflictType.DROP_COLLISION: + // log(c2, removeOp(c2.drop)) + return side === 'left' + ? [null, opThatRemovesDE(op2)] + : [opThatRemovesDE(op1), null]; + case types_js_1.ConflictType.RM_UNEXPECTED_CONTENT: + let op1HasRemove = false; + cursor_js_1.readCursor(op1).traverse(null, c => { if (c.r !== undefined) + op1HasRemove = true; }); + return op1HasRemove ? [null, opThatRemovesDE(op2)] : [opThatRemovesDE(op1), null]; + case types_js_1.ConflictType.BLACKHOLE: + // The conflict will have moves from each operation + // + // There's no good automatic resolution for blackholed content. Both + // objects are expected to be removed from the source and there's + // nowhere we can really put them. We'll just delete everything. + // + // Note that c2 has *already* moved the blackholed content inside c1. We + // only need to remove c1. + return [opThatRemovesDE(op1), opThatRemovesDE(op2)]; + default: throw Error('Unrecognised conflict: ' + type); + } +}; +const invConflict = ({ type, op1, op2 }) => ({ type, op1: op2, op2: op1 }); +const normalizeConflict = ({ type, op1, op2 }) => ({ type, op1: normalize(op1), op2: normalize(op2) }); +function transformWithConflictsPred(allowConflict, op1, op2, side) { + // debugMode = t.debug + let r2Aggregate = null; + while (true) { + const res = tryTransform(op1, op2, side); + if (res.ok) + return compose(r2Aggregate, res.result); + else { + const { conflict } = res; + log('detected conflict', conflict); + if (!allowConflict(conflict)) + throwConflictErr(conflict); + if (!RELEASE_MODE && conflict.type === types_js_1.ConflictType.BLACKHOLE) { + const res2 = tryTransform(op2, op1, side === 'left' ? 'right' : 'left'); + // const {ok: ok2, result: result2, conflict: conflict2} = res2 + assert(!res2.ok); + try { + assert(deepEqual_js_1.default(normalizeConflict(res2.conflict), normalizeConflict(invConflict(conflict)))); + } + catch (e) { + ; + log.debug = true; + log('Inverted transform conflict', op1, op2, conflict, res2.conflict); + throw e; + } + } + // Recover from the conflict + const [r1, r2] = resolveConflict(conflict, side); + log('Resolve ops', r1, r2); + // I'm normalizing here to work around a bug where a non-normalized op (eg + // `[{"es":[]}]`) generates a conflict but cannot ever be resolved because + // the conflict object returned by transform loses information about the + // conflicting part of the operation. + // + // This shouldn't come up with well formed operations in general, but + // without this the code here ends up in an infinite loop in these cases. + op1 = compose(normalize(op1), r1); + op2 = compose(normalize(op2), r2); + r2Aggregate = compose(r2Aggregate, r2); + log('recover from conflict', conflict); + log('op1 ->', op1); + log('op2 ->', op2); + } + } +} +//# sourceMappingURL=json1.js.map \ No newline at end of file diff --git a/src/json1presence/dist/json1.js.map b/src/json1presence/dist/json1.js.map new file mode 100644 index 00000000..fca57b7b --- /dev/null +++ b/src/json1presence/dist/json1.js.map @@ -0,0 +1 @@ +{"version":3,"file":"json1.js","sourceRoot":"","sources":["../lib/json1.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/src/json1presence/dist/json1.release.d.ts b/src/json1presence/dist/json1.release.d.ts new file mode 100644 index 00000000..8f4a4075 --- /dev/null +++ b/src/json1presence/dist/json1.release.d.ts @@ -0,0 +1 @@ +export * from './json1'; diff --git a/src/json1presence/dist/json1.release.js b/src/json1presence/dist/json1.release.js new file mode 100644 index 00000000..6b304a9a --- /dev/null +++ b/src/json1presence/dist/json1.release.js @@ -0,0 +1,875 @@ +"use strict"; + +var __importDefault = this && this.__importDefault || function(mod) { + return mod && mod.__esModule ? mod : { + default: mod + }; +}; + +Object.defineProperty(exports, "__esModule", { + value: !0 +}), exports.editOp = exports.replaceOp = exports.insertOp = exports.moveOp = exports.removeOp = exports.type = void 0; + +const deepEqual_js_1 = __importDefault(require("./deepEqual.js")), deepClone_js_1 = __importDefault(require("./deepClone.js")), cursor_js_1 = require("./cursor.js"), types_js_1 = require("./types.js"), RELEASE_MODE = !0, log = () => {}; + +function assert(pred, msg) { + if (!pred) throw new Error(msg); +} + +let debugMode = !1; + +exports.type = { + name: "json1presence", + uri: "http://sharejs.org/types/JSONv1presence", + readCursor: cursor_js_1.readCursor, + writeCursor: cursor_js_1.writeCursor, + create: data => data, + isNoop: op => null == op, + setDebug(val) { + debugMode = val, log.quiet = !val; + }, + registerSubtype, + checkValidOp, + normalize, + apply, + transformPosition, + compose, + tryTransform, + transform, + makeInvertible, + invert, + invertWithDoc, + RM_UNEXPECTED_CONTENT: types_js_1.ConflictType.RM_UNEXPECTED_CONTENT, + DROP_COLLISION: types_js_1.ConflictType.DROP_COLLISION, + BLACKHOLE: types_js_1.ConflictType.BLACKHOLE, + transformNoConflict: (op1, op2, side) => transformWithConflictsPred(() => !0, op1, op2, side), + typeAllowingConflictsPred: allowConflict => Object.assign(Object.assign({}, exports.type), { + transform: (op1, op2, side) => transformWithConflictsPred(allowConflict, op1, op2, side) + }) +}; + +const getComponent = r => r ? r.getComponent() : null; + +function isObject(o) { + return o && "object" == typeof o && !Array.isArray(o); +} + +const shallowClone = obj => Array.isArray(obj) ? obj.slice() : null !== obj && "object" == typeof obj ? Object.assign({}, obj) : obj, hasPick = c => c && (null != c.p || void 0 !== c.r), hasDrop = c => c && (null != c.d || void 0 !== c.i); + +function removeChild(container, key) { + return assert(null != container), "number" == typeof key ? (assert(Array.isArray(container), "Invalid key - child is not an array"), + (container = container.slice()).splice(key, 1)) : (assert(isObject(container), "Invalid key - child is not an object"), + delete (container = Object.assign({}, container))[key]), container; +} + +function insertChildMut(container, key, value) { + return "number" == typeof key ? (assert(null != container, "Container is missing for key"), + assert(Array.isArray(container), "Cannot use numerical key for object container"), + assert(container.length >= key, "Cannot insert into out of bounds index"), container.splice(key, 0, value)) : (assert(isObject(container), "Cannot insert into missing item"), + assert(void 0 === container[key], "Trying to overwrite value at key. Your op needs to remove it first"), + container[key] = value), value; +} + +exports.removeOp = (path, value = !0) => cursor_js_1.writeCursor().writeAtPath(path, "r", value).get(), +exports.moveOp = (from, to) => cursor_js_1.writeCursor().writeMove(from, to).get(), +exports.insertOp = (path, value) => cursor_js_1.writeCursor().writeAtPath(path, "i", value).get(), +exports.replaceOp = (path, oldVal, newVal) => cursor_js_1.writeCursor().at(path, w => { + w.write("r", oldVal), w.write("i", newVal); +}).get(), exports.editOp = (path, type, subOp, preserveNoop = !1) => cursor_js_1.writeCursor().at(path, w => writeEdit(w, type, subOp, preserveNoop)).get(); + +const replaceChild = (obj, k, v) => ((obj = shallowClone(obj))[k] = v, obj), isValidKey = (container, key) => null != container && ("number" == typeof key ? Array.isArray(container) : "object" == typeof container), maybeGetChild = (container, key) => isValidKey(container, key) ? container[key] : void 0, subtypes = {}; + +function registerSubtype(subtype) { + let _subtype = subtype.type ? subtype.type : subtype; + _subtype.name && (subtypes[_subtype.name] = _subtype), _subtype.uri && (subtypes[_subtype.uri] = _subtype); +} + +const typeOrThrow = name => { + const type = subtypes[name]; + if (type) return type; + throw Error("Missing type: " + name); +}; + +registerSubtype(require("ot-text-unicode")); + +const add = (a, b) => a + b; + +registerSubtype({ + name: "number", + apply: add, + compose: add, + invert: n => -n, + transform: a => a +}); + +const getEditType = c => null == c ? null : c.et ? typeOrThrow(c.et) : c.es ? subtypes["text-unicode"] : null != c.ena ? subtypes.number : null, getEdit = c => c.es ? c.es : null != c.ena ? c.ena : c.e, writeEdit = (w, typeOrName, edit, preserveNoop = !1) => { + const [type, name] = "string" == typeof typeOrName ? [ typeOrThrow(typeOrName), typeOrName ] : [ typeOrName, typeOrName.name ]; + !preserveNoop && type.isNoop && type.isNoop(edit) || ("number" === name ? w.write("ena", edit) : "text-unicode" === name ? w.write("es", edit) : (w.write("et", name), + w.write("e", edit))); +}; + +function checkNonNegInteger(n) { + assert("number" == typeof n), assert(n >= 0), assert(n === (0 | n)); +} + +function checkScalar(s) { + "number" == typeof s ? checkNonNegInteger(s) : assert("string" == typeof s); +} + +function checkValidOp(op) { + if (null === op) return; + const pickedSlots = new Set, droppedSlots = new Set, checkComponent = e => { + let empty = !0, hasEdit = !1; + for (let k in e) { + const v = e[k]; + if (empty = !1, assert("p" === k || "r" === k || "d" === k || "i" === k || "e" === k || "es" === k || "ena" === k || "et" === k, "Invalid component item '" + k + "'"), + "p" === k) checkNonNegInteger(v), assert(!pickedSlots.has(v)), pickedSlots.add(v), + assert(void 0 === e.r); else if ("d" === k) checkNonNegInteger(v), assert(!droppedSlots.has(v)), + droppedSlots.add(v), assert(void 0 === e.i); else if ("e" === k || "es" === k || "ena" === k) { + assert(!hasEdit), hasEdit = !0; + const t = getEditType(e); + assert(t, "Missing type in edit"), t.checkValidOp && t.checkValidOp(getEdit(e)); + } + } + assert(!empty); + }, checkDescent = (descent, isRoot, removed) => { + if (!Array.isArray(descent)) throw Error("Op must be null or a list"); + if (0 === descent.length) throw Error("Empty descent"); + isRoot || checkScalar(descent[0]); + let last = 1, numDescents = 0, lastKey = 0; + for (let i = 0; i < descent.length; i++) { + const d = descent[i]; + if (assert(null != d), Array.isArray(d)) { + const key = checkDescent(d, !1, removed); + if (numDescents) { + const t1 = typeof lastKey, t2 = typeof key; + t1 === t2 ? assert(lastKey < key, "descent keys are not in order") : assert("number" === t1 && "string" === t2); + } + lastKey = key, numDescents++, last = 3; + } else "object" == typeof d ? (assert(1 === last, `Prev not scalar - instead ${last}`), + checkComponent(d), last = 2) : (assert(3 !== last), checkScalar(d), assert(cursor_js_1.isValidPathItem(d), "Invalid path key"), + last = 1); + } + return assert(1 !== numDescents, "Operation makes multiple descents. Remove some []"), + assert(2 === last || 3 === last), descent[0]; + }; + checkDescent(op, !0, !1), assert(pickedSlots.size === droppedSlots.size, "Mismatched picks and drops in op"); + for (let i = 0; i < pickedSlots.size; i++) assert(pickedSlots.has(i)), assert(droppedSlots.has(i)); +} + +function normalize(op) { + let nextSlot = 0, slotMap = []; + const w = cursor_js_1.writeCursor(); + return w.mergeTree(op, (c, w) => { + const t = getEditType(c); + if (t) { + const op = getEdit(c); + writeEdit(w, t, t.normalize ? t.normalize(op) : op); + } + for (const k of [ "r", "p", "i", "d" ]) if (void 0 !== c[k]) { + const r = "p" === k || "d" === k ? (inSlot = c[k], null == slotMap[inSlot] && (slotMap[inSlot] = nextSlot++), + slotMap[inSlot]) : c[k]; + w.write(k, r); + } + var inSlot; + }), w.get(); +} + +function apply(snapshot, op) { + if (log.quiet = !debugMode, checkValidOp(op), null === op) return snapshot; + const held = []; + return function drop(root, descent) { + let subDoc = root, i = 0, rootContainer = { + root + }, m = 0, container = rootContainer, key = "root"; + function mut() { + for (;m < i; m++) { + let d = descent[m]; + "object" != typeof d && (assert(isValidKey(container, key)), container = container[key] = shallowClone(container[key]), + key = d); + } + } + for (;i < descent.length; i++) { + const d = descent[i]; + if (Array.isArray(d)) { + const child = drop(subDoc, d); + child !== subDoc && void 0 !== child && (mut(), subDoc = container[key] = child); + } else if ("object" == typeof d) { + null != d.d ? (mut(), subDoc = insertChildMut(container, key, held[d.d])) : void 0 !== d.i && (mut(), + subDoc = insertChildMut(container, key, d.i)); + const t = getEditType(d); + if (t) mut(), subDoc = container[key] = t.apply(subDoc, getEdit(d)); else if (void 0 !== d.e) throw Error("Subtype " + d.et + " undefined"); + } else subDoc = maybeGetChild(subDoc, d); + } + return rootContainer.root; + }(snapshot = function pick(subDoc, descent) { + const stack = []; + let i = 0; + for (;i < descent.length; i++) { + const d = descent[i]; + if (Array.isArray(d)) break; + "object" != typeof d && (stack.push(subDoc), subDoc = maybeGetChild(subDoc, d)); + } + for (let j = descent.length - 1; j >= i; j--) subDoc = pick(subDoc, descent[j]); + for (--i; i >= 0; i--) { + const d = descent[i]; + if ("object" != typeof d) { + const container = stack.pop(); + subDoc = subDoc === maybeGetChild(container, d) ? container : void 0 === subDoc ? removeChild(container, d) : (k = d, + v = subDoc, (obj = shallowClone(obj = container))[k] = v, obj); + } else hasPick(d) && (assert(void 0 !== subDoc, "Cannot pick up or remove undefined"), + null != d.p && (held[d.p] = subDoc), subDoc = void 0); + } + var obj, k, v; + return subDoc; + }(snapshot, op), op); +} + +const incPrefix = () => {}, decPrefix = () => {}; + +function transformPosition(path, op) { + path = path.slice(), checkValidOp(op); + const r = cursor_js_1.readCursor(op); + let pickedAtSlot, pickIndex, removed = !1; + const advStack = []; + for (let i = 0; ;i++) { + const k = path[i], c = r.getComponent(); + if (c && (void 0 !== c.r ? removed = !0 : null != c.p && (removed = !1, pickedAtSlot = c.p, + pickIndex = i)), i >= path.length) break; + let pickOffset = 0; + const pickAdv = cursor_js_1.advancer(r, void 0, (k, c) => { + hasPick(c) && pickOffset++; + }); + advStack.unshift(pickAdv); + const hasNext = pickAdv(k); + if ("number" == typeof k && (path[i] -= pickOffset), !hasNext) break; + } + if (advStack.forEach(pickAdv => pickAdv.end()), removed) return null; + const handleDrop = () => { + let i = 0; + if (null != pickedAtSlot) { + const rPath = r.getPath(); + i = rPath.length, path = rPath.concat(path.slice(pickIndex)); + } + for (;i < path.length; i++) { + const k = path[i], c = getComponent(r), et = getEditType(c); + if (et) { + const e = getEdit(c); + et.transformPosition && (path[i] = et.transformPosition(path[i], e)); + break; + } + let dropOffset = 0; + const hasNext = cursor_js_1.advancer(r, (k, c) => hasDrop(c) ? ~(k - dropOffset) : k - dropOffset, (k, c) => { + hasDrop(c) && dropOffset++; + })(k); + if ("number" == typeof k && (path[i] += dropOffset), !hasNext) break; + } + }; + return null != pickedAtSlot ? r.eachDrop(null, slot => { + slot === pickedAtSlot && handleDrop(); + }) : handleDrop(), path; +} + +const setOrRemoveChild = (container, key, child) => { + "number" == typeof key ? (assert(Array.isArray(container)), assert(key < container.length)) : (assert(!Array.isArray(container)), + assert(void 0 !== container[key])), void 0 === child ? "number" == typeof key ? container.splice(key, 1) : delete container[key] : container[key] = child; +}; + +function compose(op1, op2) { + if (checkValidOp(op1), checkValidOp(op2), null == op1) return op2; + if (null == op2) return op1; + let nextSlot = 0; + const r1 = cursor_js_1.readCursor(op1), r2 = cursor_js_1.readCursor(op2), w = cursor_js_1.writeCursor(), heldPickWrites = [], heldDropWrites = [], held1Pick = [], held2Drop = [], p1SlotMap = [], p2SlotMap = [], visitedOp2EditCs = new Set; + r1.traverse(null, c => { + null != c.p && (held1Pick[c.p] = r1.clone()); + }), r2.traverse(null, c => { + null != c.d && (held2Drop[c.d] = r2.clone()); + }); + const w2 = cursor_js_1.writeCursor(); + return function xfBoundary(r1Pick, r1Drop, r2Pick, r2Drop, litIn, rmParent, wd, wp) { + assert(r1Drop || r2Pick); + const c1d = getComponent(r1Drop), c2p = getComponent(r2Pick), rmHere = !!c2p && void 0 !== c2p.r, insHere = !!c1d && void 0 !== c1d.i, drop1Slot = c1d ? c1d.d : null, pick2Slot = c2p ? c2p.p : null, rmChildren = (rmParent || rmHere) && null == pick2Slot; + if (null != pick2Slot) r2Drop = held2Drop[pick2Slot], wd = heldDropWrites[pick2Slot] = new cursor_js_1.WriteCursor; else if (c2p && void 0 !== c2p.r) r2Drop = null; else { + const c2d = getComponent(r2Drop); + c2d && null != c2d.d && (r2Drop = null); + } + const c2d = getComponent(r2Drop); + if (null != drop1Slot) if (r1Pick = held1Pick[drop1Slot], wp = heldPickWrites[drop1Slot] = new cursor_js_1.WriteCursor, + rmChildren) rmParent && !rmHere && wp.write("r", !0); else { + const slot = p1SlotMap[drop1Slot] = nextSlot++; + wd.write("d", slot); + } else if (c1d && void 0 !== c1d.i) r1Pick = null; else { + const c1p = getComponent(r1Pick); + c1p && null != c1p.p && (r1Pick = null); + } + let litOut; + insHere ? (assert(void 0 === litIn), litOut = c1d.i) : litOut = litIn; + const insComponent = (null == pick2Slot ? !insHere || rmParent || rmHere : void 0 === litOut) ? null : wd.getComponent(); + if (null != pick2Slot) if (void 0 !== litIn || insHere) ; else { + const slot = null != drop1Slot ? p1SlotMap[drop1Slot] : nextSlot++; + p2SlotMap[pick2Slot] = slot, wp.write("p", slot); + } else rmHere && (insHere || void 0 !== litIn || (c2p.r, wp.write("r", c2p.r))); + const type1 = rmChildren ? null : getEditType(c1d), type2 = getEditType(c2d); + if ((type1 || type2) && (type1 && type1.name, type2 && type2.name), type1 && type2) { + assert(type1 === type2); + const e1 = getEdit(c1d), e2 = getEdit(c2d), r = type1.compose(e1, e2); + writeEdit(wd, type1, r), visitedOp2EditCs.add(c2d); + } else type1 ? writeEdit(wd, type1, getEdit(c1d)) : type2 && (writeEdit(wd, type2, getEdit(c2d)), + visitedOp2EditCs.add(c2d)); + const hasContainerLiteral = "object" == typeof litOut && null != litOut; + let isCloned = !1, p1PickOff = 0, p1DropOff = 0, p2PickOff = 0, p2DropOff = 0, litOff = 0; + const p2DropAdv = cursor_js_1.advancer(r2Drop, (k, c) => hasDrop(c) ? p2DropOff - k - 1 : k - p2DropOff, (k, c) => { + hasDrop(c) && p2DropOff++; + }), p1PickAdv = cursor_js_1.advancer(r1Pick, (k, c) => hasPick(c) ? p1PickOff - k - 1 : k - p1PickOff, (k, c) => { + hasPick(c) && p1PickOff++; + }); + if (cursor_js_1.eachChildOf(r1Drop, r2Pick, (inKey, _p1Drop, _p2Pick) => { + let _p1Pick, _p2Drop, p1PickKey = inKey, p2DropKey = inKey, litKey = inKey; + if ("number" == typeof inKey) { + let p2Mid = inKey + p2PickOff; + _p2Drop = p2DropAdv(p2Mid), p2DropKey = p2Mid + p2DropOff; + let p1Mid = inKey + p1DropOff; + _p1Pick = p1PickAdv(p1Mid), hasDrop(getComponent(_p2Drop)) && (_p1Pick = null), + p1PickKey = p1Mid + p1PickOff, litKey = inKey + litOff, assert(p1PickKey >= 0, "p1PickKey is negative"), + assert(p2DropKey >= 0, "p2DropKey is negative"); + const hd1 = hasDrop(getComponent(_p1Drop)), hp2 = hasPick(getComponent(_p2Pick)); + (hd1 || hp2 && !rmChildren) && litOff--, hd1 && p1DropOff--, hp2 && p2PickOff--; + } else _p1Pick = p1PickAdv(inKey), _p2Drop = p2DropAdv(inKey); + wp.descend(p1PickKey), wd.descend(p2DropKey); + const _lit = hasContainerLiteral && !hasDrop(getComponent(_p1Drop)) ? litOut[litKey] : void 0, _litResult = xfBoundary(_p1Pick, _p1Drop, _p2Pick, _p2Drop, _lit, rmChildren, wd, wp); + var container, key, child; + hasContainerLiteral && !rmChildren ? _lit !== _litResult && (isCloned || (litOut = Array.isArray(litOut) ? litOut.slice() : Object.assign({}, litOut), + isCloned = !0), container = litOut, child = _litResult, "number" == typeof (key = litKey) ? (assert(Array.isArray(container)), + assert(key < container.length)) : (assert(!Array.isArray(container)), assert(void 0 !== container[key])), + void 0 === child ? "number" == typeof key ? container.splice(key, 1) : delete container[key] : container[key] = child) : assert(void 0 === _litResult), + wd.ascend(), wp.ascend(); + }), p1PickAdv.end(), p2DropAdv.end(), null != insComponent) insComponent.i = litOut; else if (!rmParent && !rmHere && null == pick2Slot) return litOut; + }(r1, r1.clone(), r2, r2.clone(), void 0, !1, w, w2), w.reset(), w.mergeTree(w2.get()), + w.reset(), w.get(), heldPickWrites.map(w => w.get()), heldDropWrites.map(w => w.get()), + r1.traverse(w, (c, w) => { + const slot1 = c.p; + if (null != slot1) { + const slot = p1SlotMap[slot1]; + null != slot && w.write("p", slot); + const _w = heldPickWrites[slot1]; + _w && _w.get(), _w && w.mergeTree(_w.get()); + } else void 0 !== c.r && w.write("r", c.r); + }), w.reset(), w.get(), r2.traverse(w, (c, w) => { + const slot2 = c.d; + if (null != slot2) { + const slot = p2SlotMap[slot2]; + null != slot && w.write("d", slot); + const _w = heldDropWrites[slot2]; + _w && w.mergeTree(_w.get()); + } else void 0 !== c.i && w.write("i", c.i); + const t = getEditType(c); + t && !visitedOp2EditCs.has(c) && writeEdit(w, t, getEdit(c)); + }), w.get(); +} + +function invert(op) { + if (null == op) return null; + const r = new cursor_js_1.ReadCursor(op), w = new cursor_js_1.WriteCursor; + let editsToTransform; + const heldPick = [], heldWrites = []; + return function invertSimple(r, w, subDoc) { + const c = r.getComponent(); + let insertHere, subdocModified = !1; + if (c) { + null != c.p && (w.write("d", c.p), heldPick[c.p] = r.clone()), void 0 !== c.r && w.write("i", c.r), + null != c.d && (w.write("p", c.d), subDoc = void 0), void 0 !== c.i && (subDoc = insertHere = c.i); + const t = getEditType(c); + t && (void 0 === subDoc ? (editsToTransform || (editsToTransform = new Set), editsToTransform.add(c)) : (getEdit(c), + subDoc = t.apply(subDoc, getEdit(c)), subdocModified = !0)); + } + let dropOff = 0; + for (const key of r) { + w.descend(key); + const raw = "number" == typeof key ? key - dropOff : key, childIn = maybeGetChild(subDoc, raw); + hasDrop(r.getComponent()) && dropOff++; + const childOut = invertSimple(r, w, childIn); + if (void 0 !== subDoc && void 0 !== childOut) { + if (subdocModified || (subdocModified = !0, subDoc = shallowClone(subDoc)), !isValidKey(subDoc, raw)) throw Error("Cannot modify child - invalid operation"); + subDoc[raw] = childOut; + } + w.ascend(); + } + if (void 0 === insertHere) return subdocModified ? subDoc : void 0; + w.write("r", subDoc); + }(r, w, void 0), editsToTransform && (w.reset(), function transformEdits(rPick, rDrop, w) { + const cd = rDrop.getComponent(); + if (cd) { + const dropSlot = cd.d; + if (null != dropSlot && (rPick = heldPick[dropSlot], w = heldWrites[dropSlot] = cursor_js_1.writeCursor()), + editsToTransform.has(cd)) { + const t = getEditType(cd); + if (!t.invert) throw Error(`Cannot invert subtype ${t.name}`); + writeEdit(w, t, t.invert(getEdit(cd))); + } + } + let pickOff = 0, dropOff = 0; + const ap = cursor_js_1.advancer(rPick, (k, c) => hasPick(c) ? pickOff - k - 1 : k - pickOff, (k, c) => { + hasPick(c) && pickOff++; + }); + for (const key of rDrop) if ("number" == typeof key) { + const mid = key - dropOff, _rPick = ap(mid), raw = mid + pickOff; + w.descend(raw), transformEdits(_rPick, rDrop, w), hasDrop(rDrop.getComponent()) && dropOff++, + w.ascend(); + } else w.descend(key), transformEdits(ap(key), rDrop, w), w.ascend(); + ap.end(); + }(r.clone(), r, w), heldWrites.length && (w.reset(), r.traverse(w, (c, w) => { + const slot = c.p; + if (null != slot) { + const _w = heldWrites[slot]; + _w && _w.get(), _w && w.mergeTree(_w.get()); + } + }))), w.get(); +} + +const anyComponent = (op, fn) => op.some(c => "object" == typeof c && (Array.isArray(c) ? anyComponent(c, fn) : fn(c))); + +function makeInvertible(op, doc) { + if (null == op || !anyComponent(op, c => { + var _a; + return void 0 !== c.r || null != (null === (_a = getEditType(c)) || void 0 === _a ? void 0 : _a.makeInvertible); + })) return op; + const r = new cursor_js_1.ReadCursor(op), w = new cursor_js_1.WriteCursor; + let hasEdits = !1; + const heldPick = [], heldDoc = [], traversePick = (r, w, subDoc) => { + const c = r.getComponent(); + let modified = !1; + if (c) { + null != c.d && w.write("d", c.d), void 0 !== c.i && w.write("i", c.i); + const pickSlot = c.p; + if (null != pickSlot && (heldPick[pickSlot] = r.clone(), assert(void 0 !== subDoc, "Operation picks up at an invalid key"), + heldDoc[pickSlot] = subDoc, w.write("p", c.p)), void 0 !== c.r && void 0 === subDoc) throw Error("Invalid doc / op in makeInvertible: removed item missing from doc"); + const t = getEditType(c); + t && (t.makeInvertible ? hasEdits = !0 : writeEdit(w, t, getEdit(c), !0)); + } + let listOff = 0; + for (const key of r) { + w.descend(key); + const keyRaw = "number" == typeof key ? key - listOff : key, childIn = maybeGetChild(subDoc, keyRaw), childOut = traversePick(r, w, childIn); + childIn !== childOut && (modified || (modified = !0, subDoc = shallowClone(subDoc)), + void 0 === childOut ? (subDoc = removeChild(subDoc, keyRaw), "number" == typeof key && listOff++) : subDoc[keyRaw] = childOut), + w.ascend(); + } + return c && (void 0 !== c.r ? (w.write("r", deepClone_js_1.default(subDoc)), subDoc = void 0) : null != c.p && (subDoc = void 0)), + subDoc; + }; + return traversePick(r, w, doc), w.get(), hasEdits && (w.reset(), function traverseDrop(rPick, rDrop, w, subDoc, isLiteral) { + const c = rDrop.getComponent(); + if (c) { + void 0 !== c.i ? (subDoc = c.i, isLiteral = !0) : null != c.d && (subDoc = heldDoc[c.d], + rPick = heldPick[c.d], isLiteral = !1, c.d); + let t = getEditType(c); + if (t && t.makeInvertible) { + const edit = getEdit(c); + writeEdit(w, t, t.makeInvertible(edit, subDoc), !0); + } + } + let pickOff = 0, dropOff = 0; + const ap = cursor_js_1.advancer(rPick, (k, c) => hasPick(c) ? pickOff - k - 1 : k - pickOff, (k, c) => { + hasPick(c) && pickOff++; + }); + for (const key of rDrop) if ("number" == typeof key) { + const mid = key - dropOff, _rPick = ap(mid), raw = mid + pickOff, child = maybeGetChild(subDoc, isLiteral ? mid : raw); + w.descend(key), traverseDrop(_rPick, rDrop, w, child, isLiteral), hasDrop(rDrop.getComponent()) && dropOff++, + w.ascend(); + } else { + const child = maybeGetChild(subDoc, key); + w.descend(key), traverseDrop(ap(key), rDrop, w, child, isLiteral), w.ascend(); + } + ap.end(); + }(r.clone(), r, w, doc, !1)), w.get(); +} + +function invertWithDoc(op, doc) { + return invert(makeInvertible(op, doc)); +} + +const LEFT = 0, RIGHT = 1, shallowCloneOp = op => { + if (null == op) return null; + const result = op.slice(); + for (let i = 0; i < op.length; i++) { + const c = result[i]; + Array.isArray(c) && (result[i] = shallowCloneOp(c)); + } + return result; +}; + +function tryTransform(op1, op2, direction) { + assert("left" === direction || "right" === direction, "Direction must be left or right"); + const side = "left" === direction ? 0 : 1; + if (log.quiet = !debugMode, log.prefix = 0, null == op2) return { + ok: !0, + result: op1 + }; + checkValidOp(op1), checkValidOp(op2); + let conflict = null; + const heldOp1PickByOp1 = [], heldOp1DropByOp1 = [], heldOp2PickByOp2 = [], heldOp2DropByOp2 = [], heldOp1PickByOp2 = [], heldOp2PickByOp1 = [], heldOp2DropByOp1 = [], heldOp2RmForOp1 = [], heldOp1RmForOp2 = [], cancelledOp2 = [], discardedOp2Drop = [], heldPickWrites = [], heldDropWrites = [], op1PickAtOp2Pick = [], op1PicksOp2DropSlots = []; + let nextSlot = 0; + const r1 = cursor_js_1.readCursor(op1), r2 = cursor_js_1.readCursor(op2), w = cursor_js_1.writeCursor(); + if (function scanOp2Pick(r2Pick, r1Pick = null, removed1) { + const c1 = getComponent(r1Pick); + c1 && (void 0 !== c1.r ? removed1 = r1Pick.clone() : null != c1.p && (removed1 = null, + heldOp2PickByOp1[c1.p] = r2Pick.clone())); + const c2 = r2Pick.getComponent(); + let slot2; + c2 && null != (slot2 = c2.p) && (heldOp1PickByOp2[slot2] = r1Pick ? r1Pick.clone() : null, + heldOp2PickByOp2[slot2] = r2Pick.clone(), removed1 && (cancelledOp2[slot2] = !0, + heldOp1RmForOp2[slot2] = removed1), c1 && null != c1.p && (op1PickAtOp2Pick[slot2] = c1.p)); + const ap1 = cursor_js_1.advancer(r1Pick); + for (const key of r2Pick) scanOp2Pick(r2Pick, ap1(key), removed1); + ap1.end(); + }(r2, r1, null), function scanOp2Drop(r1Pick, r2Pick, r2Drop, pickSlot1, removed1) { + const c2d = r2Drop.getComponent(); + let slot2, droppedHere = !1; + c2d && (null != (slot2 = c2d.d) ? (heldOp2DropByOp2[slot2] = r2Drop.clone(), null != pickSlot1 && (null == op1PicksOp2DropSlots[pickSlot1] && (op1PicksOp2DropSlots[pickSlot1] = []), + op1PicksOp2DropSlots[pickSlot1].push(slot2)), cancelledOp2[slot2], r1Pick = heldOp1PickByOp2[slot2] || null, + r2Pick = heldOp2PickByOp2[slot2] || null, cancelledOp2[slot2] ? (removed1 && (discardedOp2Drop[slot2] = !0), + removed1 = heldOp1RmForOp2[slot2] || null) : !removed1 || 1 !== side && null != op1PickAtOp2Pick[slot2] || null == conflict && (conflict = { + type: types_js_1.ConflictType.RM_UNEXPECTED_CONTENT, + op1: exports.removeOp(removed1.getPath()), + op2: exports.moveOp(r2Pick.getPath(), r2Drop.getPath()) + }), droppedHere = !0) : void 0 !== c2d.i && (r1Pick = r2Pick = null, droppedHere = !0, + removed1 && null == conflict && (conflict = { + type: types_js_1.ConflictType.RM_UNEXPECTED_CONTENT, + op1: exports.removeOp(removed1.getPath()), + op2: exports.insertOp(r2Drop.getPath(), c2d.i) + }))); + const c1p = getComponent(r1Pick); + c1p && (void 0 !== c1p.r ? removed1 = r1Pick.clone() : null != c1p.p && (c1p.p, + pickSlot1 = c1p.p, removed1 = null)); + const t2 = getEditType(c2d); + t2 && removed1 && null == conflict && (conflict = { + type: types_js_1.ConflictType.RM_UNEXPECTED_CONTENT, + op1: exports.removeOp(removed1.getPath()), + op2: exports.editOp(r2Drop.getPath(), t2, getEdit(c2d), !0) + }); + let p2PickOff = 0, p2DropOff = 0; + const ap2 = cursor_js_1.advancer(r2Pick, (k, c) => hasPick(c) ? p2PickOff - k - 1 : k - p2PickOff, (k, c) => { + hasPick(c) && p2PickOff++; + }), ap1 = cursor_js_1.advancer(r1Pick); + for (const key of r2Drop) if ("number" == typeof key) { + const p2Mid = key - p2DropOff, _p2Pick = ap2(p2Mid); + p2DropOff += +scanOp2Drop(ap1(p2Mid + p2PickOff), _p2Pick, r2Drop, pickSlot1, removed1); + } else { + const _p2Pick = ap2(key); + scanOp2Drop(ap1(key), _p2Pick, r2Drop, pickSlot1, removed1); + } + return ap2.end(), ap1.end(), droppedHere; + }(r1, r2, r2.clone(), null, null), heldOp2DropByOp2.map(x => x && x.get()), conflict) return { + ok: !1, + conflict + }; + discardedOp2Drop.map(x => !!x); + const pickComponents = []; + let cancelledRemoves = null; + !function writeOp1Pick(r1Pick, r2Pick, r2Drop, w, removed2) { + let iAmMoved = !1; + const c2p = getComponent(r2Pick); + if (hasPick(c2p)) { + const slot2 = c2p.p; + null != slot2 ? (r2Drop = heldOp2DropByOp2[slot2], w = heldPickWrites[slot2] = cursor_js_1.writeCursor(), + iAmMoved = !0, removed2 = null) : (r2Drop = null, removed2 = r2Pick.clone()); + } else hasDrop(getComponent(r2Drop)) && (r2Drop = null); + const c1 = r1Pick.getComponent(); + if (c1) { + const slot1 = c1.p; + null != slot1 ? (removed2 && (heldOp2RmForOp1[slot1] = removed2), pickComponents[slot1] = removed2 || 1 === side && iAmMoved ? null : w.getComponent(), + heldOp1PickByOp1[slot1] = r1Pick.clone(), r2Drop && (heldOp2DropByOp1[slot1] = r2Drop.clone())) : void 0 !== c1.r && (removed2 || w.write("r", !0), + (removed2 || iAmMoved) && (null == cancelledRemoves && (cancelledRemoves = new Set), + cancelledRemoves.add(c1))); + } + let p2PickOff = 0, p2DropOff = 0; + const ap2Pick = cursor_js_1.advancer(r2Pick, void 0, (k, c) => { + hasPick(c) && p2PickOff++; + }), ap2Drop = cursor_js_1.advancer(r2Drop, (k, c) => hasDrop(c) ? ~(k - p2DropOff) : k - p2DropOff, (k, c) => { + hasDrop(c) && p2DropOff++; + }); + if (r1Pick) for (const key of r1Pick) if ("string" == typeof key) { + const p2Pick_ = ap2Pick(key), p2Drop_ = ap2Drop(key); + w.descend(key), writeOp1Pick(r1Pick, p2Pick_, p2Drop_, w, removed2), w.ascend(); + } else { + const p2Pick_ = ap2Pick(key), p2Mid = key - p2PickOff, p2Drop_ = hasPick(getComponent(p2Pick_)) ? null : ap2Drop(p2Mid), finalKey = p2Mid + p2DropOff; + assert(finalKey >= 0), w.descend(finalKey), writeOp1Pick(r1Pick, p2Pick_, p2Drop_, w, removed2), + w.ascend(); + } + ap2Pick.end(), ap2Drop.end(); + }(r1, r2, r2.clone(), w, null), w.reset(); + let outputSlotMap = []; + if (function writeOp1Drop(p1Pick, p1Drop, p2Pick, p2Drop, w, removed2) { + assert(p1Drop); + const c1d = p1Drop.getComponent(); + let c2d = getComponent(p2Drop), droppedHere = !1; + const insOrMv = (r1, r2, c) => r1 ? exports.moveOp(r1.getPath(), r2.getPath()) : exports.insertOp(r2.getPath(), c.i); + if (hasDrop(c1d)) { + const slot1 = c1d.d; + null != slot1 && (heldOp1DropByOp1[slot1] = p1Drop.clone()); + const pc = null != slot1 ? pickComponents[slot1] : null; + let identical = !1; + if (void 0 !== c1d.i || null != slot1 && pc) { + let slot2; + c2d && (void 0 !== c2d.i || null != (slot2 = c2d.d) && !cancelledOp2[slot2]) && (identical = null != slot2 ? null != slot1 && slot1 === op1PickAtOp2Pick[slot2] : deepEqual_js_1.default(c2d.i, c1d.i), + identical || null != slot2 && 1 !== side && null != op1PickAtOp2Pick[slot2] || null == conflict && (conflict = { + type: types_js_1.ConflictType.DROP_COLLISION, + op1: insOrMv(null != slot1 ? heldOp1PickByOp1[slot1] : null, p1Drop, c1d), + op2: insOrMv(null != slot2 ? heldOp2PickByOp2[slot2] : null, p2Drop, c2d) + })), identical || (removed2 ? null == conflict && (conflict = { + type: types_js_1.ConflictType.RM_UNEXPECTED_CONTENT, + op1: insOrMv(null != slot1 ? heldOp1PickByOp1[slot1] : null, p1Drop, c1d), + op2: exports.removeOp(removed2.getPath()) + }) : (null != slot1 ? (outputSlotMap[nextSlot] = slot1, w.write("d", pc.p = nextSlot++)) : w.write("i", deepClone_js_1.default(c1d.i)), + droppedHere = !0)); + } else if (null != slot1 && !pc) { + const h = heldOp2RmForOp1[slot1]; + h && (removed2 = h.clone()); + } + null != slot1 ? (p1Pick = heldOp1PickByOp1[slot1], p2Pick = heldOp2PickByOp1[slot1], + p2Drop = heldOp2DropByOp1[slot1]) : void 0 !== c1d.i && (p1Pick = p2Pick = null, + identical || (p2Drop = null)); + } else hasPick(getComponent(p1Pick)) && (p1Pick = p2Pick = p2Drop = null); + const c1p = getComponent(p1Pick), c2p = getComponent(p2Pick); + if (hasPick(c2p)) { + const slot2 = c2p.p; + void 0 !== c2p.r && (!c1p || void 0 === c1p.r) || cancelledOp2[slot2] ? (p2Drop = null, + removed2 = p2Pick.clone()) : null != slot2 && (p2Drop = heldOp2DropByOp2[slot2], + 1 !== side && null != op1PickAtOp2Pick[slot2] || ((w = heldDropWrites[slot2]) || (w = heldDropWrites[slot2] = cursor_js_1.writeCursor()), + w.reset(), removed2 = null)); + } else !hasDrop(c1d) && hasDrop(c2d) && (p2Drop = null); + c2d = null != p2Drop ? p2Drop.getComponent() : null; + const t1 = getEditType(c1d); + if (t1) { + const e1 = getEdit(c1d); + if (removed2) null == conflict && (conflict = { + type: types_js_1.ConflictType.RM_UNEXPECTED_CONTENT, + op1: exports.editOp(p1Drop.getPath(), t1, e1, !0), + op2: exports.removeOp(removed2.getPath()) + }); else { + const t2 = getEditType(c2d); + let e; + if (t2) { + if (t1 !== t2) throw Error("Transforming incompatible types"); + const e2 = getEdit(c2d); + e = t1.transform(e1, e2, direction); + } else e = deepClone_js_1.default(e1); + writeEdit(w, t1, e); + } + } + let p1PickOff = 0, p1DropOff = 0, p2PickOff = 0, p2DropOff = 0, outPickOff = 0, outDropOff = 0, p1pValid = null != p1Pick && p1Pick.descendFirst(), p1pDidDescend = p1pValid; + const ap2p = cursor_js_1.advancer(p2Pick, void 0, (k, c) => { + hasPick(c) && p2PickOff++; + }); + let p2dValid = null != p2Drop && p2Drop.descendFirst(), p2dDidDescend = p2dValid; + for (const key of p1Drop) if ("number" == typeof key) { + let _p1Pick; + const hd1 = hasDrop(p1Drop.getComponent()), k1Mid = key - p1DropOff; + { + let p1k; + for (;p1pValid && "number" == typeof (p1k = p1Pick.getKey()); ) { + p1k += p1PickOff; + const c = p1Pick.getComponent(), hp = hasPick(c); + if (p1k > k1Mid || p1k === k1Mid && (!hp || 0 === side && hd1)) break; + if (hp) { + p1PickOff--; + const slot1 = c.p; + pickComponents[slot1], op1PickAtOp2Pick.includes(slot1), c.d, getComponent(heldDropWrites[c.d]), + hasPick(getComponent(heldDropWrites[c.d])), (void 0 === c.r || cancelledRemoves && cancelledRemoves.has(c)) && (null == slot1 || !pickComponents[slot1] || 1 !== side && op1PickAtOp2Pick.includes(slot1)) || outPickOff--; + } + p1pValid = p1Pick.nextSibling(); + } + _p1Pick = p1pValid && p1k === k1Mid ? p1Pick : null; + } + const raw = k1Mid - p1PickOff; + let _p2Pick = ap2p(raw); + const k2Mid = raw - p2PickOff; + let _p2Drop = null; + { + let p2dk, op2Mid; + for (;p2dValid && "number" == typeof (p2dk = p2Drop.getKey()); ) { + op2Mid = p2dk - p2DropOff; + const c = p2Drop.getComponent(), hd2 = hasDrop(c); + if (op2Mid > k2Mid) break; + if (op2Mid === k2Mid) { + if (!hd2) { + _p2Drop = p2Drop; + break; + } + { + if (0 === side && hd1) { + _p2Drop = p2Drop; + break; + } + const hp2 = _p2Pick && hasPick(_p2Pick.getComponent()); + if (0 === side && hp2) break; + } + } + if (hd2) { + const slot2 = c.d; + cancelledOp2[slot2], op1PickAtOp2Pick[slot2], void 0 === c.i && (cancelledOp2[slot2] || null != op1PickAtOp2Pick[slot2] && 1 !== side) ? (cancelledOp2[slot2] || null != op1PickAtOp2Pick[slot2] && 0 === side) && (p2DropOff++, + outDropOff--) : p2DropOff++; + } + p2dValid = p2Drop.nextSibling(); + } + } + const descend = k2Mid + p2DropOff + outPickOff + outDropOff; + assert(descend >= 0, "trying to descend to a negative index"), w.descend(descend), + hd1 && (_p1Pick = _p2Pick = _p2Drop = null, p1DropOff++), writeOp1Drop(_p1Pick, p1Drop, _p2Pick, _p2Drop, w, removed2) && outDropOff++, + w.ascend(); + } else { + let p1k; + for (;p1pValid && (p1k = p1Pick.getKey(), "string" != typeof p1k || !(p1k > key || p1k === key)); ) p1pValid = p1Pick.nextSibling(); + const _p1Pick = p1pValid && p1k === key ? p1Pick : null, _p2Pick = ap2p(key); + let p2dk; + for (;p2dValid && (p2dk = p2Drop.getKey(), "string" != typeof p2dk || !(p2dk > key || p2dk === key)); ) p2dValid = p2Drop.nextSibling(); + const _p2Drop = p2dValid && p2dk === key ? p2Drop : null; + w.descend(key), writeOp1Drop(_p1Pick, p1Drop, _p2Pick, _p2Drop, w, removed2), w.ascend(); + } + return ap2p.end(), p1pDidDescend && p1Pick.ascend(), p2dDidDescend && p2Drop.ascend(), + droppedHere; + }(r1, r1.clone(), r2, r2.clone(), w, null), conflict) return { + ok: !1, + conflict + }; + w.reset(); + const eachDrop = (r, w, fn) => r.traverse(w, (c, w) => { + null != c.d && fn(c.d, r, w); + }); + (cancelledOp2.length || heldPickWrites.length) && (eachDrop(r2, w, (slot2, r, w) => { + cancelledOp2[slot2] && !discardedOp2Drop[slot2] && w.write("r", !0), heldPickWrites[slot2] && w.mergeTree(heldPickWrites[slot2].get()); + }), w.reset()); + const heldOutDropRead = [], heldOutDropWrites = []; + if ((heldDropWrites.length || cancelledOp2.length) && !conflict) { + const rOut = cursor_js_1.readCursor(shallowCloneOp(w.get())); + if (eachDrop(rOut, null, (slotOut, r) => { + heldOutDropRead[slotOut] = r.clone(); + }), heldDropWrites.forEach(hdw => { + hdw && eachDrop(cursor_js_1.readCursor(hdw.get()), null, (slotOut, r) => { + heldOutDropRead[slotOut] = r.clone(); + }); + }), function writeHeldOp2Drop(p2Drop, outPick, outDrop, w, parentC, removedOut) { + log.prefix++; + const coutp = getComponent(outPick); + if (coutp && hasPick(coutp)) if (null != coutp.p) { + parentC = coutp; + const slot = coutp.p; + heldOutDropRead[slot].getPath(), outDrop = heldOutDropRead[slot], w = heldOutDropWrites[slot] = cursor_js_1.writeCursor(); + } else void 0 !== coutp.r && (outDrop = null, removedOut = !0); else hasDrop(getComponent(outDrop)) && (outDrop = null); + const c2 = p2Drop.getComponent(); + if (c2) { + let slot2; + if (null != (slot2 = c2.d)) { + const _w = heldDropWrites[slot2]; + _w && (_w.get(), w.mergeTree(_w.get()), outDrop = cursor_js_1.readCursor(_w.get())); + } + } + let outPickOff = 0, outDropOff = 0; + const oPickAdv = cursor_js_1.advancer(outPick, void 0, (k, c) => { + hasPick(c) && outPickOff--; + }), oDropAdv = cursor_js_1.advancer(outDrop, (k, c) => hasDrop(c) ? -(k - outDropOff) - 1 : k - outDropOff, (k, c) => { + hasDrop(c) && outDropOff++; + }); + for (const o2dk of p2Drop) if ("number" == typeof o2dk) { + const _outPick = oPickAdv(o2dk), rmid = o2dk + outPickOff, _outDrop = oDropAdv(rmid), rfinal = rmid + outDropOff; + w.descend(rfinal), writeHeldOp2Drop(p2Drop, _outPick, _outDrop, w, parentC, removedOut), + w.ascend(); + } else w.descend(o2dk), writeHeldOp2Drop(p2Drop, oPickAdv(o2dk), oDropAdv(o2dk), w, parentC, removedOut), + w.ascend(); + oPickAdv.end(), oDropAdv.end(); + }(r2, rOut, rOut.clone(), w, null, !1), w.reset(), conflict) return { + ok: !1, + conflict + }; + if (w.get(), heldOutDropWrites.length) { + const heldOutDropContent = heldOutDropWrites.map(w => w ? w.get() : null), rOut2 = cursor_js_1.readCursor(shallowCloneOp(w.get())); + if (eachDrop(rOut2, w, (slotOut, r, w) => { + const data = heldOutDropContent[slotOut]; + data && (w.mergeTree(data), heldOutDropContent[slotOut] = null); + }), heldOutDropContent.find(x => x)) { + const w1 = cursor_js_1.writeCursor(), w2 = cursor_js_1.writeCursor(); + let nextSlot1 = 0, nextSlot2 = 0; + heldOutDropContent.forEach(data => { + null != data && eachDrop(cursor_js_1.readCursor(data), null, c => { + const slot1 = outputSlotMap[c]; + w1.writeMove(heldOp1PickByOp1[slot1].getPath(), heldOp1DropByOp1[slot1].getPath(), nextSlot1++); + const slot2s = op1PicksOp2DropSlots[slot1]; + slot2s && slot2s.forEach(slot2 => { + cancelledOp2[slot2] || 1 !== side && null != op1PickAtOp2Pick[slot2] || w2.writeMove(heldOp2PickByOp2[slot2].getPath(), heldOp2DropByOp2[slot2].getPath(), nextSlot2++); + }); + }); + }), conflict = { + type: types_js_1.ConflictType.BLACKHOLE, + op1: w1.get(), + op2: w2.get() + }; + } + } + } + if (conflict) return { + ok: !1, + conflict + }; + return { + ok: !0, + result: w.get() + }; +} + +const throwConflictErr = conflict => { + const err = new Error("Transform detected write conflict"); + throw err.conflict = conflict, err.type = err.name = "writeConflict", err; +}; + +function transform(op1, op2, side) { + const res = tryTransform(op1, op2, side); + if (res.ok) return res.result; + throwConflictErr(res.conflict); +} + +const opThatRemovesDE = op => { + const w = cursor_js_1.writeCursor(); + return cursor_js_1.readCursor(op).traverse(w, (c, w) => { + (hasDrop(c) || getEditType(c)) && w.write("r", !0); + }), w.get(); +}, resolveConflict = (conflict, side) => { + const {type, op1, op2} = conflict; + switch (type) { + case types_js_1.ConflictType.DROP_COLLISION: + return "left" === side ? [ null, opThatRemovesDE(op2) ] : [ opThatRemovesDE(op1), null ]; + + case types_js_1.ConflictType.RM_UNEXPECTED_CONTENT: + let op1HasRemove = !1; + return cursor_js_1.readCursor(op1).traverse(null, c => { + void 0 !== c.r && (op1HasRemove = !0); + }), op1HasRemove ? [ null, opThatRemovesDE(op2) ] : [ opThatRemovesDE(op1), null ]; + + case types_js_1.ConflictType.BLACKHOLE: + return [ opThatRemovesDE(op1), opThatRemovesDE(op2) ]; + + default: + throw Error("Unrecognised conflict: " + type); + } +}, invConflict = ({type, op1, op2}) => ({ + type, + op1: op2, + op2: op1 +}), normalizeConflict = ({type, op1, op2}) => ({ + type, + op1: normalize(op1), + op2: normalize(op2) +}); + +function transformWithConflictsPred(allowConflict, op1, op2, side) { + let r2Aggregate = null; + for (;;) { + const res = tryTransform(op1, op2, side); + if (res.ok) return compose(r2Aggregate, res.result); + { + const {conflict} = res; + allowConflict(conflict) || throwConflictErr(conflict); + const [r1, r2] = resolveConflict(conflict, side); + op1 = compose(normalize(op1), r1), op2 = compose(normalize(op2), r2), r2Aggregate = compose(r2Aggregate, r2); + } + } +} \ No newline at end of file diff --git a/src/json1presence/dist/json1.release.js.map b/src/json1presence/dist/json1.release.js.map new file mode 100644 index 00000000..7bd1d1a6 --- /dev/null +++ b/src/json1presence/dist/json1.release.js.map @@ -0,0 +1 @@ +{"version":3,"file":"json1.release.js","sourceRoot":"","sources":["../lib/json1.release.ts"],"names":[],"mappings":";AAAA,4EAA4E;AAC5E,6BAA6B;;;;;;;;;;;;AAE7B,0CAAuB"} \ No newline at end of file diff --git a/src/json1presence/dist/log.d.ts b/src/json1presence/dist/log.d.ts new file mode 100644 index 00000000..3daf0532 --- /dev/null +++ b/src/json1presence/dist/log.d.ts @@ -0,0 +1,6 @@ +declare function log(...args: any): void; +declare namespace log { + var quiet: boolean; + var prefix: number; +} +export default log; diff --git a/src/json1presence/dist/log.js b/src/json1presence/dist/log.js new file mode 100644 index 00000000..4df536f0 --- /dev/null +++ b/src/json1presence/dist/log.js @@ -0,0 +1,17 @@ +"use strict"; +// This is a simple logging function which prints things a lot prettier than +// console.log in node. The signature is the same. +Object.defineProperty(exports, "__esModule", { value: true }); +function log(...args) { + if (log.quiet) + return; + const { inspect } = require('util'); + const f = (a) => (typeof a === 'string') ? + a : inspect(a, { depth: 10, colors: true }); + const prefix = Array(log.prefix).fill(' ').join(''); + console.log(prefix + args.map(f).join(' ')); +} +exports.default = log; +log.quiet = true; +log.prefix = 0; +//# sourceMappingURL=log.js.map \ No newline at end of file diff --git a/src/json1presence/dist/log.js.map b/src/json1presence/dist/log.js.map new file mode 100644 index 00000000..9f2d5625 --- /dev/null +++ b/src/json1presence/dist/log.js.map @@ -0,0 +1 @@ +{"version":3,"file":"log.js","sourceRoot":"","sources":["../lib/log.ts"],"names":[],"mappings":";AAAA,4EAA4E;AAC5E,kDAAkD;;AAElD,SAAwB,GAAG,CAAC,GAAG,IAAS;IACtC,IAAI,GAAG,CAAC,KAAK;QAAE,OAAM;IAErB,MAAM,EAAC,OAAO,EAAC,GAAG,OAAO,CAAC,MAAM,CAAC,CAAA;IAEjC,MAAM,CAAC,GAAG,CAAC,CAAM,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC;QAC7C,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,EAAC,KAAK,EAAC,EAAE,EAAE,MAAM,EAAC,IAAI,EAAC,CAAC,CAAA;IAEzC,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;IACpD,OAAO,CAAC,GAAG,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;AAC7C,CAAC;AAVD,sBAUC;AAED,GAAG,CAAC,KAAK,GAAG,IAAI,CAAA;AAChB,GAAG,CAAC,MAAM,GAAG,CAAC,CAAA"} \ No newline at end of file diff --git a/src/json1presence/dist/types.d.ts b/src/json1presence/dist/types.d.ts new file mode 100644 index 00000000..971b4277 --- /dev/null +++ b/src/json1presence/dist/types.d.ts @@ -0,0 +1,75 @@ +import { TextOp } from 'ot-text-unicode'; +/** The text op component from text-unicode */ +export { TextOp, TextOpComponent } from 'ot-text-unicode'; +export declare type JSONOpComponent = { + /** insert */ + i?: any; + /** remove */ + r?: any; + /** pick */ + p?: number; + /** drop */ + d?: number; + /** Edit string (directly uses text-unicode) */ + es?: TextOp; + /** Edit number */ + ena?: number; + /** Arbitrary edit. If the element has e: it must also have et: with a registered type name */ + e?: any; + /** Edit type name. */ + et?: string; +}; +export declare type JSONOpList = (number | string | JSONOpComponent | JSONOpList)[]; +/** + * A JSON operation. + * + * There's some aspects of valid operations which aren't captured by the type: + * + * - The last element of any list must be an object (the op component) + * - Except at the root, every inner list must have a length at least 2, and the + * first element must be a number or string. + * - Operation components cannot be empty + * - No two adjacent list elements can be operation components. + * - If a component has listy children (it descends), those descenders are + * sorted and come after any local op component. So, [, , , , ...] + * - Picks and drops must be matched, and use low numbers (0, 1, 2, ....) + * + * noop is represented by 'null'. + * + * Valid operations: null, [{i:5}], ['a', {i:5}], [['a', {p:0}], ['b', {d:0}]], + * [10, {i:5}, 'b', {r:true}] + * + * Not valid: [], ['a', [{i:5}]], [[{i:5}]], [{}], ['a', {}], ['a', ['b', {i:5}], {r:true}] + * + * Many 'invalid' operations can be cleaned up into their canonical form by the + * normalize() function. + * + * If you want some examples, take a look at the test suite. + */ +export declare type JSONOp = null | JSONOpList; +export declare type Key = number | string; +export declare type Path = Key[]; +/** + * JSON documents must be able to round-trip through JSON.stringify / + * JSON.parse. So this means they can't contain: + * + * - undefined + * - Circular references + * - Objects of classes + * - Functions + * - Sets, Maps, Dates, DOM nodes, etc + */ +export declare type Doc = null | boolean | number | string | Doc[] | { + [k: string]: Doc; +}; +export declare enum ConflictType { + RM_UNEXPECTED_CONTENT = 1, + DROP_COLLISION = 2, + BLACKHOLE = 3 +} +export interface Conflict { + type: ConflictType; + op1: JSONOp; + op2: JSONOp; +} diff --git a/src/json1presence/dist/types.js b/src/json1presence/dist/types.js new file mode 100644 index 00000000..acc0f1be --- /dev/null +++ b/src/json1presence/dist/types.js @@ -0,0 +1,10 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ConflictType = void 0; +var ConflictType; +(function (ConflictType) { + ConflictType[ConflictType["RM_UNEXPECTED_CONTENT"] = 1] = "RM_UNEXPECTED_CONTENT"; + ConflictType[ConflictType["DROP_COLLISION"] = 2] = "DROP_COLLISION"; + ConflictType[ConflictType["BLACKHOLE"] = 3] = "BLACKHOLE"; +})(ConflictType = exports.ConflictType || (exports.ConflictType = {})); +//# sourceMappingURL=types.js.map \ No newline at end of file diff --git a/src/json1presence/dist/types.js.map b/src/json1presence/dist/types.js.map new file mode 100644 index 00000000..b400454c --- /dev/null +++ b/src/json1presence/dist/types.js.map @@ -0,0 +1 @@ +{"version":3,"file":"types.js","sourceRoot":"","sources":["../lib/types.ts"],"names":[],"mappings":";;;AAwEA,IAAY,YAIX;AAJD,WAAY,YAAY;IACtB,iFAAyB,CAAA;IACzB,mEAAkB,CAAA;IAClB,yDAAa,CAAA;AACf,CAAC,EAJW,YAAY,GAAZ,oBAAY,KAAZ,oBAAY,QAIvB"} \ No newline at end of file diff --git a/src/json1Presence/cursor.ts b/src/json1presence/lib/cursor.ts similarity index 100% rename from src/json1Presence/cursor.ts rename to src/json1presence/lib/cursor.ts diff --git a/src/json1Presence/deepClone.ts b/src/json1presence/lib/deepClone.ts similarity index 100% rename from src/json1Presence/deepClone.ts rename to src/json1presence/lib/deepClone.ts diff --git a/src/json1Presence/deepEqual.ts b/src/json1presence/lib/deepEqual.ts similarity index 100% rename from src/json1Presence/deepEqual.ts rename to src/json1presence/lib/deepEqual.ts diff --git a/src/json1Presence/index.ts b/src/json1presence/lib/index.ts similarity index 100% rename from src/json1Presence/index.ts rename to src/json1presence/lib/index.ts diff --git a/src/json1Presence/json1.release.ts b/src/json1presence/lib/json1.release.ts similarity index 100% rename from src/json1Presence/json1.release.ts rename to src/json1presence/lib/json1.release.ts diff --git a/src/json1Presence/json1.ts b/src/json1presence/lib/json1.ts similarity index 98% rename from src/json1Presence/json1.ts rename to src/json1presence/lib/json1.ts index 27cda98e..06d6888b 100644 --- a/src/json1Presence/json1.ts +++ b/src/json1presence/lib/json1.ts @@ -20,8 +20,8 @@ // import log from './log' import deepEqual from './deepEqual.js' import deepClone from './deepClone.js' -import { ReadCursor, WriteCursor, readCursor, writeCursor, advancer, eachChildOf, isValidPathItem } from './cursor.js' -import { Doc, JSONOpComponent, Path, Presence, Key, JSONOp, JSONOpList, Conflict, ConflictType } from './types.js' +import {ReadCursor, WriteCursor, readCursor, writeCursor, advancer, eachChildOf, isValidPathItem} from './cursor.js' +import { Doc, JSONOpComponent, Path, Key, JSONOp, JSONOpList, Conflict, ConflictType } from './types.js' const RELEASE_MODE = process.env.JSON1_RELEASE_MODE const log: (...args: any) => void = RELEASE_MODE ? (() => {}) : require('./log').default @@ -36,8 +36,8 @@ function assert(pred: any, msg?: string): asserts pred { let debugMode = false export const type = { - name: 'json1-presence', - uri: "http://sharejs.org/types/JSONv1-presence", + name: 'json1presence', + uri: "http://sharejs.org/types/JSONv1presence", readCursor, writeCursor, @@ -57,7 +57,6 @@ export const type = { apply, transformPosition, - transformPresence, compose, tryTransform, transform, @@ -693,11 +692,7 @@ function transformPosition(path: Path, op: JSONOp): Path | null { if (et) { const e = getEdit(c!) log('Embedded edit', e, et) - if (et.transformPosition) { - path[i] = et.transformPosition(path[i], e) - } else if (et.transformCursor) { - path[i] = et.transformCursor(path[i], e) - } + if (et.transformPosition) path[i] = et.transformPosition(path[i], e) // Its invalid for the operation to have drops inside the embedded edit here. break } @@ -730,25 +725,6 @@ function transformPosition(path: Path, op: JSONOp): Path | null { return path } -function transformPresence(presence: Presence | null, op: JSONOp, isOwnOperation: boolean): Presence | null { - if (!presence || !presence.start || !presence.end) { - return null - } - - const start = transformPosition(presence.start, op) - const end = transformPosition(presence.end, op) - - if (start && end) { - return { ...presence, start, end } - } else if (start) { - return { ...presence, start, end: start } - } else if (end) { - return { ...presence, start: end, end } - } - - return null -} - // ****** Compose diff --git a/src/json1Presence/log.ts b/src/json1presence/lib/log.ts similarity index 100% rename from src/json1Presence/log.ts rename to src/json1presence/lib/log.ts diff --git a/src/json1Presence/types.ts b/src/json1presence/lib/types.ts similarity index 96% rename from src/json1Presence/types.ts rename to src/json1presence/lib/types.ts index de7412d4..30b4bad2 100644 --- a/src/json1Presence/types.ts +++ b/src/json1presence/lib/types.ts @@ -57,11 +57,6 @@ export type JSONOp = null | JSONOpList export type Key = number | string export type Path = Key[] -export type Presence = { - start: Path, - end: Path, - [key: string]: any -} /** * JSON documents must be able to round-trip through JSON.stringify / diff --git a/src/json1presence/package-lock.json b/src/json1presence/package-lock.json new file mode 100644 index 00000000..84c845f0 --- /dev/null +++ b/src/json1presence/package-lock.json @@ -0,0 +1,2342 @@ +{ + "name": "ot-json1", + "version": "1.0.1", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "ot-json1", + "version": "1.0.1", + "license": "ISC", + "dependencies": { + "ot-text-unicode": "4" + }, + "devDependencies": { + "@types/node": "^14.0.13", + "mocha": "^7.1.1", + "ot-fuzzer": "1.3", + "ot-simple": "^1.0.0", + "terser": "^4.6.7", + "typescript": "^3.9.5" + } + }, + "node_modules/@types/node": { + "version": "14.0.23", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.23.tgz", + "integrity": "sha512-Z4U8yDAl5TFkmYsZdFPdjeMa57NOvnaf1tljHzhouaPEp7LCj2JKkejpI1ODviIAQuW4CcQmxkQ77rnLsOOoKw==", + "dev": true + }, + "node_modules/ansi-colors": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "node_modules/binary-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", + "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "node_modules/buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "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, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chokidar": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz", + "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.2.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.1.1" + } + }, + "node_modules/cli-progress": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-2.1.1.tgz", + "integrity": "sha512-TSJw3LY9ZRSis7yYzQ7flIdtQMbacd9oYoiFphJhI4SzgmqF0zErO+uNv0lbUjk1L4AGfHQJ4OVYYzW+JV66KA==", + "dev": true, + "dependencies": { + "colors": "^1.1.2", + "string-width": "^2.1.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "dependencies": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true, + "engines": { + "node": ">=0.1.90" + } + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "node_modules/debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "dependencies": { + "object-keys": "^1.0.12" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "node_modules/es-abstract": { + "version": "1.17.5", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", + "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", + "dev": true, + "dependencies": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.1.5", + "is-regex": "^1.0.5", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimleft": "^2.1.1", + "string.prototype.trimright": "^2.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/flat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", + "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", + "deprecated": "Fixed a prototype pollution security issue in 4.1.0, please upgrade to ^4.1.1 or ^5.0.1.", + "dev": true, + "dependencies": { + "is-buffer": "~2.0.3" + }, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "node_modules/fsevents": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", + "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", + "deprecated": "\"Please update to latest v2.3 or v2.2\"", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": true, + "dependencies": { + "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" + }, + "engines": { + "node": "*" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true, + "engines": { + "node": ">=4.x" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-buffer": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/is-callable": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", + "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-regex": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "node_modules/js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/log-symbols": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", + "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", + "dev": true, + "dependencies": { + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "node_modules/mkdirp": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.3.tgz", + "integrity": "sha512-P+2gwrFqx8lhew375MQHHeTlY8AuOJSrGf0R5ddkEndUkmwpgUob/vQuBD1V22/Cw1/lJr4x+EjllSezBThzBg==", + "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", + "dev": true, + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mocha": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.1.1.tgz", + "integrity": "sha512-3qQsu3ijNS3GkWcccT5Zw0hf/rWvu1fTN9sPvEd81hlwsr30GX2GcDSSoBxo24IR8FelmrAydGC6/1J5QQP4WA==", + "dev": true, + "dependencies": { + "ansi-colors": "3.2.3", + "browser-stdout": "1.3.1", + "chokidar": "3.3.0", + "debug": "3.2.6", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "find-up": "3.0.0", + "glob": "7.1.3", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.13.1", + "log-symbols": "3.0.0", + "minimatch": "3.0.4", + "mkdirp": "0.5.3", + "ms": "2.1.1", + "node-environment-flags": "1.0.6", + "object.assign": "4.1.0", + "strip-json-comments": "2.0.1", + "supports-color": "6.0.0", + "which": "1.3.1", + "wide-align": "1.1.3", + "yargs": "13.3.2", + "yargs-parser": "13.1.2", + "yargs-unparser": "1.6.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha" + }, + "engines": { + "node": ">= 8.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" + } + }, + "node_modules/ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "node_modules/node-environment-flags": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", + "integrity": "sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==", + "dev": true, + "dependencies": { + "object.getownpropertydescriptors": "^2.0.3", + "semver": "^5.7.0" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.getownpropertydescriptors": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", + "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/ot-fuzzer": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ot-fuzzer/-/ot-fuzzer-1.3.0.tgz", + "integrity": "sha512-cZNdwrLkJ+p+/go94KFFmuiXsXy66eot+TBrStEMTxs4tGCQl43EKeqpTe9GpgVlftpWqzbzetIuZsg2s98QqQ==", + "dev": true, + "dependencies": { + "cli-progress": "^2.1.1", + "seedrandom": "^2.4.4" + } + }, + "node_modules/ot-simple": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ot-simple/-/ot-simple-1.0.0.tgz", + "integrity": "sha1-B42ED4HqOq04y+aUdfgbLGdU+1A=", + "dev": true + }, + "node_modules/ot-text-unicode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ot-text-unicode/-/ot-text-unicode-4.0.0.tgz", + "integrity": "sha512-W7ZLU8QXesY2wagYFv47zErXud3E93FGImmSGJsQnBzE+idcPPyo2u2KMilIrTwBh4pbCizy71qRjmmV6aDhcQ==", + "dependencies": { + "unicount": "1.1" + } + }, + "node_modules/p-limit": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", + "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/readdirp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz", + "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==", + "dev": true, + "dependencies": { + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "node_modules/seedrandom": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-2.4.4.tgz", + "integrity": "sha512-9A+PDmgm+2du77B5i0Ip2cxOqqHjgNxnBgglxLcX78A2D6c2rTo61z4jnVABpF4cKeDMDG+cmXXvdnqse2VqMA==", + "dev": true + }, + "node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "node_modules/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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz", + "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "node_modules/string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "dependencies": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/string.prototype.trimleft": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz", + "integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimright": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz", + "integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/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=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", + "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/terser": { + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.6.7.tgz", + "integrity": "sha512-fmr7M1f7DBly5cX2+rFDvmGBAaaZyPrHYK4mMdHEDAdNTqXSZgSOfqsfGq2HqPGT/1V0foZZuCZFx8CHKgAk3g==", + "dev": true, + "dependencies": { + "commander": "^2.20.0", + "source-map": "~0.6.1", + "source-map-support": "~0.5.12" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/typescript": { + "version": "3.9.7", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz", + "integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/unicount": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unicount/-/unicount-1.1.0.tgz", + "integrity": "sha512-RlwWt1ywVW4WErPGAVHw/rIuJ2+MxvTME0siJ6lk9zBhpDfExDbspe6SRlWT3qU6AucNjotPl9qAJRVjP7guCQ==" + }, + "node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "node_modules/wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "dependencies": { + "string-width": "^1.0.2 || 2" + } + }, + "node_modules/wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "node_modules/y18n": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", + "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", + "dev": true + }, + "node_modules/yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "dependencies": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + } + }, + "node_modules/yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "node_modules/yargs-unparser": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", + "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", + "dev": true, + "dependencies": { + "flat": "^4.1.0", + "lodash": "^4.17.15", + "yargs": "^13.3.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + } + }, + "dependencies": { + "@types/node": { + "version": "14.0.23", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.23.tgz", + "integrity": "sha512-Z4U8yDAl5TFkmYsZdFPdjeMa57NOvnaf1tljHzhouaPEp7LCj2JKkejpI1ODviIAQuW4CcQmxkQ77rnLsOOoKw==", + "dev": true + }, + "ansi-colors": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "dev": true + }, + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "anymatch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", + "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "binary-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", + "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "chalk": { + "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", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "chokidar": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz", + "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", + "dev": true, + "requires": { + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "fsevents": "~2.1.1", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.2.0" + } + }, + "cli-progress": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-2.1.1.tgz", + "integrity": "sha512-TSJw3LY9ZRSis7yYzQ7flIdtQMbacd9oYoiFphJhI4SzgmqF0zErO+uNv0lbUjk1L4AGfHQJ4OVYYzW+JV66KA==", + "dev": true, + "requires": { + "colors": "^1.1.2", + "string-width": "^2.1.1" + } + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "es-abstract": { + "version": "1.17.5", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", + "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.1.5", + "is-regex": "^1.0.5", + "object-inspect": "^1.7.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.0", + "string.prototype.trimleft": "^2.1.1", + "string.prototype.trimright": "^2.1.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "flat": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", + "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", + "dev": true, + "requires": { + "is-buffer": "~2.0.3" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "fsevents": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", + "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dev": 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-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-buffer": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", + "dev": true + }, + "is-callable": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", + "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", + "dev": true + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "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=", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-regex": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", + "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "log-symbols": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", + "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", + "dev": true, + "requires": { + "chalk": "^2.4.2" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "mkdirp": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.3.tgz", + "integrity": "sha512-P+2gwrFqx8lhew375MQHHeTlY8AuOJSrGf0R5ddkEndUkmwpgUob/vQuBD1V22/Cw1/lJr4x+EjllSezBThzBg==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "mocha": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.1.1.tgz", + "integrity": "sha512-3qQsu3ijNS3GkWcccT5Zw0hf/rWvu1fTN9sPvEd81hlwsr30GX2GcDSSoBxo24IR8FelmrAydGC6/1J5QQP4WA==", + "dev": true, + "requires": { + "ansi-colors": "3.2.3", + "browser-stdout": "1.3.1", + "chokidar": "3.3.0", + "debug": "3.2.6", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "find-up": "3.0.0", + "glob": "7.1.3", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.13.1", + "log-symbols": "3.0.0", + "minimatch": "3.0.4", + "mkdirp": "0.5.3", + "ms": "2.1.1", + "node-environment-flags": "1.0.6", + "object.assign": "4.1.0", + "strip-json-comments": "2.0.1", + "supports-color": "6.0.0", + "which": "1.3.1", + "wide-align": "1.1.3", + "yargs": "13.3.2", + "yargs-parser": "13.1.2", + "yargs-unparser": "1.6.0" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + }, + "node-environment-flags": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", + "integrity": "sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==", + "dev": true, + "requires": { + "object.getownpropertydescriptors": "^2.0.3", + "semver": "^5.7.0" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "object-inspect": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", + "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dev": true, + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.getownpropertydescriptors": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", + "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "ot-fuzzer": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/ot-fuzzer/-/ot-fuzzer-1.3.0.tgz", + "integrity": "sha512-cZNdwrLkJ+p+/go94KFFmuiXsXy66eot+TBrStEMTxs4tGCQl43EKeqpTe9GpgVlftpWqzbzetIuZsg2s98QqQ==", + "dev": true, + "requires": { + "cli-progress": "^2.1.1", + "seedrandom": "^2.4.4" + } + }, + "ot-simple": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/ot-simple/-/ot-simple-1.0.0.tgz", + "integrity": "sha1-B42ED4HqOq04y+aUdfgbLGdU+1A=", + "dev": true + }, + "ot-text-unicode": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ot-text-unicode/-/ot-text-unicode-4.0.0.tgz", + "integrity": "sha512-W7ZLU8QXesY2wagYFv47zErXud3E93FGImmSGJsQnBzE+idcPPyo2u2KMilIrTwBh4pbCizy71qRjmmV6aDhcQ==", + "requires": { + "unicount": "1.1" + } + }, + "p-limit": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", + "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "picomatch": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", + "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", + "dev": true + }, + "readdirp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz", + "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==", + "dev": true, + "requires": { + "picomatch": "^2.0.4" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "seedrandom": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-2.4.4.tgz", + "integrity": "sha512-9A+PDmgm+2du77B5i0Ip2cxOqqHjgNxnBgglxLcX78A2D6c2rTo61z4jnVABpF4cKeDMDG+cmXXvdnqse2VqMA==", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "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.16", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz", + "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "string-width": { + "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" + } + }, + "string.prototype.trimleft": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz", + "integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, + "string.prototype.trimright": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz", + "integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "function-bind": "^1.1.1" + } + }, + "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" + } + }, + "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=", + "dev": true + }, + "supports-color": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", + "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "terser": { + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/terser/-/terser-4.6.7.tgz", + "integrity": "sha512-fmr7M1f7DBly5cX2+rFDvmGBAaaZyPrHYK4mMdHEDAdNTqXSZgSOfqsfGq2HqPGT/1V0foZZuCZFx8CHKgAk3g==", + "dev": true, + "requires": { + "commander": "^2.20.0", + "source-map": "~0.6.1", + "source-map-support": "~0.5.12" + } + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "typescript": { + "version": "3.9.7", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz", + "integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==", + "dev": true + }, + "unicount": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unicount/-/unicount-1.1.0.tgz", + "integrity": "sha512-RlwWt1ywVW4WErPGAVHw/rIuJ2+MxvTME0siJ6lk9zBhpDfExDbspe6SRlWT3qU6AucNjotPl9qAJRVjP7guCQ==" + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": 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==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "y18n": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", + "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", + "dev": true + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "yargs-unparser": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", + "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", + "dev": true, + "requires": { + "flat": "^4.1.0", + "lodash": "^4.17.15", + "yargs": "^13.3.0" + } + } + } +} diff --git a/src/json1presence/package.json b/src/json1presence/package.json new file mode 100644 index 00000000..43e2333f --- /dev/null +++ b/src/json1presence/package.json @@ -0,0 +1,46 @@ +{ + "name": "ot-json1", + "version": "1.0.1", + "description": "JSON OT type", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "dependencies": { + "ot-text-unicode": "4" + }, + "devDependencies": { + "@types/node": "^14.0.13", + "mocha": "^7.1.1", + "ot-fuzzer": "1.3", + "ot-simple": "^1.0.0", + "terser": "^4.6.7", + "typescript": "^3.9.5" + }, + "scripts": { + "test": "mocha test/cursor.js test/test.js test/immutable.js", + "fuzzer": "node test/fuzzer.js", + "prepare": "rm -rf dist && npx tsc && terser -d process.env.JSON1_RELEASE_MODE=true -c pure_funcs=log,keep_fargs=false,passes=2 -b --source-map url -o dist/json1.release.js -- dist/json1.js" + }, + "mocha": { + "checkLeaks": true, + "reporter": "spec" + }, + "files": [ + "dist/", + "lib/", + "test/genOp.js" + ], + "repository": { + "type": "git", + "url": "git+https://github.com/josephg/json1.git" + }, + "keywords": [ + "ot", + "json" + ], + "author": "Joseph Gentle ", + "license": "ISC", + "bugs": { + "url": "https://github.com/josephg/json1/issues" + }, + "homepage": "https://github.com/josephg/json1#readme" +} diff --git a/src/json1presence/spec.md b/src/json1presence/spec.md new file mode 100644 index 00000000..02e89524 --- /dev/null +++ b/src/json1presence/spec.md @@ -0,0 +1,308 @@ +# JSON1 OT Spec + +The JSON OT type is designed to allow concurrent edits in an arbitrary JSON document. + +The JSON1 OT type will replace the JSON0 OT type. I recommend using it over JSON0, although it is less well tested. JSON1 is simpler, faster and more capable in every way compared to JSON0. (And at some point it'd be great to have a tool to automatically convert JSON0 to JSON1 edits.) + +Like its predecessor, JSON1 requires that the document is a valid JSON structure. That is: + +- Each object or list can only be referenced once in the tree. +- Cycles are not allowed +- The value `undefined` is not allowed anywhere but at the root. +- If you serialize the document to JSON using `JSON.serialize` and back, you should get back an identical document. So, the structure can only contain lists, objects, booleans, nulls, numbers and strings. Functions, maps, sets, dates are all disallowed. (Except through embedded types) + +The root of the document can be any valid JSON value (including a string, a number, `true`, `false` or `null`). The document can also be `undefined`. This allows the JSON operations to describe insertions and deletions of the entire document. + +Replacing the [big table of operations in JSON0](https://github.com/ottypes/json0/blob/master/README.md#summary-of-operations), JSON1 only supports three essential operation components: + +- Pick up subtree (to be moved or discarded) +- Insert subtree (moved from elsewhere, or as a literal copied from the operation) +- Edit embedded object at a location in the subtree using an operation from another registered OT/CRDT type. + +The type is designed to contain other embedded OT documents (rich text documents or whatever). The subdocuments can be inserted, deleted or moved around the tree directly by the JSON type. However, if you want to edit subdocuments themselves, you should use the document's own native operations. These operations can be embedded inside a JSON1 operation. + +For plain text edits the [text OT type](https://github.com/ottypes/text-unicode) is bundled & always available. + + +## Why? Advantages compared to JSON0 + +The JSON0 OT type has some weaknesses: + +- You can't move an object key or move an item between two different lists +- The semantics for reordering list items is really confusing - and its slightly different depending on whether you're inserting or moving +- JSON0 is O(N*M) if you transform an operation with M components by an operation with N components. +- JSON0 has no way to safely initialize data before editing +- Doing multiple inserts or multiple removes in a list is quite common, and its quite awkward in JSON0. (You need an operation component for each insert and each remove). +- The new type should support conversion between JSON operations and [JSON Patch (RFC 6902)](https://tools.ietf.org/html/rfc6902). I want those conversions to be bidirectional if possible, although converting embedded custom OT types to JSON Patch won't work. + +*tldr;* JSON1 is designed to be simpler and easier to maintain, faster, more feature rich and more widely useful. + + +## Operations + +Operations are expressed in a compact JSON form containing instructions for up to three traversals of the JSON document. The phases are executed on the document in order: + +1. **Pick up phase**: In the pickup phase we take subtrees out of the document. They are either put in a temporary holding area or discarded. +2. **Drop phase**: In the drop phase we insert new subtrees in the document. These subtrees either come from the holding area (from the pickup phase) or they're literals embedded in the operation. The drop phase must place every item from the holding area back into the document. +3. **Edit phase**: In the edit phase we make changes to embedded documents in the tree. + +Instructions for all three phases are overlaid over the top of one another inside the operation structure. + +The operation describes a traversal of the document's tree. The way to interpret the operation is: + +- The traversal starts at the root of the document. +- The `[` symbol saves the current location in the document to a stack. (*stack.push(location)*) +- The `]` symbol pops the previously stored location from the stack. (*location = stack.pop()*) +- Strings and numbers inside the operation will descend into the JSON tree using the specified index. Strings are exclusively used to descend into the key of an object, and numbers to descend into a particular index of a list. +- Objects `{...}` contain instructions to be performed at the current location *as the stack is being unwound*. These objects are internally called *operation components*. They can contain instructions: + - `p` for pick up into storage + - `r` for remove + - `d` for drop from storage + - `i` for insert literal + - `e` for edit, with accompanying `et` for describing the edit type. `es` / `ena` are shorthands for string and number edits). + See below for more detail. + +Consider this operation, which moves the string at `doc.x` to `doc.y` then edits it: + + [['x', {p:0}], ['y', {d:0, es:[5, 'hi']}]] + +This operation contains instructions for all 3 phases: + +1. `['x', {p:0}]`: Go to the item at key 'x' inside the document and pick it up, storing it in holding location 0. +2. `['y', {d:0}]`: Go to the key 'y' in the document and place the item in storage location 0. +3. `['y', {es:[5, 'hi']}]`: Go to key 'y' in the document and edit the value using an embedded string edit. (This uses the text-unicode type internally, inserts 'hi' at position 5 in the string.) + +When an operation is applied, the phases are executed in sequence. During traversal, any parts of the operation not related to the current phase are ignored. + +Just like JSON0 and string edits, when you insert or remove items from a list the list is reordered with no holes. So for example, if you have a list with `[1,2,3]` and discard the second element, the list will become `[1,3]`. If you then insert 5 at the start of the list, the list will become `[5,1,3]`. + +There are many equivalent ways the same operation could be written (by wrapping everything with extra [], reordering parts of the traveral, etc). The JSON1 library strictly requires all operations follow some canonicalization rules: + +- There are no unnecessary lists `[...]` in the operation. `['x', {r:0}]` rather than `[['x', [{r:0}]]]`. +- The traversal happens in numeric / alphabetic order through all keys. `[['x', {p:0}], ['y', {d:0}]]` rather than `[['y', {d:0}], ['x', {p:0}]]`. When relevent, all list descents should appear before all object descents. +- Components are collapsed when possible. (So, `['x', {r:0, i:1}]` rather than `['x', {r:0}, {i:1}]`). +- Pick up and drop slot identifiers should be numbers counting from 0. +- There are no empty operation components. (`['x', {r:0}]` is valid. `[{}, 'x', {r:0}]` is not.) + +Slot identifiers should be ordered within the operation, but this is not strictly enforced in the library. + +Constructing operations which follow all these rules in code can be quite tricky. Most users should either: + +- Construct complex operations out of simple operations, then use *compose* to merge the simple operations together, or +- Use the *WriteCursor* API to build complex operations. WriteCursor will take care of formatting the operation. + +Noop is written as `null`. + + +### Some examples. + +Given a document of: + + {x:5, y:['happy', 'apple']} + +#### Insert z:6: + + ['z', {i:6}] + +Translation: Descend into key 'z'. In the drop phase insert immediate value 6. + +#### Rename (move) doc.x to doc.z: + + [['x', {p:0}], ['z', {d:0}]] + +Translation: Descend into the object properties. Pick up the `x` subtree and put it in slot 0. Go to the `z` subtree and place the contents of slot 0. + +#### Move x into the list between 'happy' and 'apple' + + [['x',{p:0}], ['y',1,{d:0}]] + +Translation: Descend into `x`. Pick up the value and put it in slot 0. Descend into the y subtree. At position 1, place the item in slot 0. + + +## Parts of an operation + +A simple operation is a list of *descents* with some *edit components* at the end. Descents contain strings and numbers, for descending into objects and lists respectively. Edit components are an object describing what to do when we've traversed to the target item in the object. + +The edit component object can have one or more of the following values: + +- **p** (pickup): During the pickup phase, pick up the subtree from this position and put it in the specified slot. Eg `p:10`. +- **r** (remove): During the pickup phase, remove the subtree at the current position. The value is ignored, but you can store the removed value if you want the op to be invertible. +- **d** (drop): During the drop phase, insert the subtree in the specified slot here. Eg `d:10`. +- **i** (insert): During the drop phase, insert the immediate value here. Eg `i:"cabbage"` +- **e** (edit): Apply the specified edit to the subdocument that is here. Eg `et:'typename', e:`. There are two special cases: You can simply write `es:[...op]` to use the [ot-text-unicode type](https://github.com/ottypes/text-unicode). And you can use `ena:X` to add X to the number at that location in the JSON tree. + +Operations can also contain a list of child operations. For example, suppose you want to set `obj.a.x = 1` and `obj.a.y = 2`. You could write an operation: + + [['a', 'x', {i:1}], ['a', 'y', {i:2}]] + +For compactness, the common path prefix can be pulled out into the parent list. As such, this is canonically written like this: + + ['a', ['x', {i:1}], ['y', {i:2}]] + +You can read this operation as follows: + +1. Descend to key `a` +2. At this position, perform the following child operations: + 1. Descend to `x` and insert value `1` + 2. Descend to `y` and insert value `2` + + +## A note on order + +Semantically, each phase of the operation is applied in reverse order, from the deepest parts of the document tree to the shallowest parts. Consider this operation: + + [['x', {p:0}, 'y', {r:true}], ['z', {d:0}]] + +The order of operations is: + +1. Remove `doc.x.y` +2. Pick up `doc.x` into slot 0 +3. Place the contents of slot 0 into `doc.z`. + +This rule is important, because it means: + +- The path to any pick up or remove operations exactly matches the location in the document before the operation is applied. It doesn't matter if multiple parts of the operation insert / remove other list indexes. +- The path to any drop, insert or edit operations exactly matches the location in the document *after* the operation has been applied. + +This lets you (for example) move an item and move its children at the same time, but you need to pick up an item's children relative to the parent's original location and then drop those items relative to the parent's new location. + +So given the document `{x: {y: {}}}`, this operation will capitalize the keys (x->X, y->Y): + +``` +[ + ['x',{p:0},'y',{p:1}], + ['X',{d:0},'Y',{d:1}] +] +``` + +Note for *Y* the data is picked up at `x.y` and dropped at `X.Y`. + +The operation is carried out in the following order: + +- Pick phase: + 1. Descend into *x* + 2. Descend into *y* + 3. Pick up *y* in slot 1, value `{}` + 4. Return. Document is now `{x: {}}`. + 5. Pick up *x* in slot 0, value `{}` + 6. Return. Document is now `{}`. +- Drop phase: + 1. Descend into *X* + 2. Drop data in slot 0 with data `{}`. Document is now `{X: {}}`. + 3. Descend into *Y* + 4. Drop data in slot 1 with value `{}`. Document is now `{X: {Y:{}}}` + 5. Return + 6. Return + +Note that the ordering is the same for *drop immediate* (`i:..`) operation components. This allows you to place a new object / array in the document and immediately fill it with stuff moved from elsewhere in the document. + + +# Fancier examples + +Convert the document {x:10,y:20,z:30} to an array [10,20,30]: + +``` +[ + {r:{},i:[]}, + [0,{d:0}],[1,{d:1}],[2,{d:2}], + ['x',{p:0}],['y',{p:1}],['z',{p:2}] +] +``` + +Rewrite the document {x:{y:{secret:"data"}}} as {y:{x:{secret:"data"}}}: + +``` +[ + ['x', {r:{}}, 'y', {p:0}], + ['y', {i:{}}, 'x', {d:0}] +] +``` + +(Translation: Go into x.y and pick up the data (slot 0). Set doc.y = {}. Set doc.y.x = data (slot 0).) + + +## Initializing data ('set null') + +Its quite common to want to initialize data containers on first use. This can cause problems - what happens if another user tries to initialize the data at the same time? + +For example, if the two clients try to insert `{tags:['rock']}` and `{tags:['roll']}`, one client's insert will win and the other client's edit would be lost. The document will either contain 'rock' or 'roll', but not both. + +In JSON1 the semantics of `insert` are specially chosen to allow this. + +The rules are: + +- If two concurrent operations insert *identical* content at the same location in an object, they 'both win', and both inserts are kept in the final document. +- Inserts, drops and edits can be embedded inside another insert. + +Together, these rules allow clients to concurrently initialize a document without bumping heads. Eg if these two operations are run concurrently: + + [{i:{tags:[]}}, 'tags', 0, {i:'rock'}] + +and + + [{i:{tags:[]}}, 'tags', 0, {i:'roll'}] + +then the document will end up with the contents `{tags:['rock', 'roll']}`. + +This can also be used for collaboratively editing a string that might not exist: + + [{i:'', es:["aaa"]}] + +and + + [{i:'', es:["bbb"]}] + +Will result in the document containing either "aaabbb" (or "bbbaaa" depending on transform symmetry). + + +## Inverting operations + +The JSON1 OT type allows optional invertible. An operation is invertible if all `r:` components contain the complete contents of any removed data. + +In this case, an operation can be inverted by: + +- Swapping `i` and `r` components +- Swapping `p` and `d` components +- Moving all edits to the item's source location +- Recursively inverting all embedded edits + +The contents of `r:` components is ignored in all other situations outside invert. + +Most users should use the builtin `type.invertWithDoc(op, doc) => op'` to invert an operation, using the content of the passed document to provide information on deleted content. The passed document should be a snapshot of the document *before* the operation was applied. (Otherwise removed content will have already been lost!) + +Internally invertWithDoc is implemented using two methods: + +- `type.makeInvertible(op, doc) => op'`: This method fills in any information missing in `r:` components based on the contents of the passed document. +- `type.invert(op) => op'`: This method inverts an operation, as described above. + +But note that information needed to invert an operation is lost when calling *compose* or *transform*. + + +### Invertible operations and setNull behaviour + +Consider this operation from earlier which uses the setNull behaviour to initialize a field, before inserting content into it: + + [{i:'', es:["hi"]}] + +Perhaps counterintuitively, the inverse of this operation will be: + + [{r:'hi'}] + +Note that this operation will remove both the content, and the entire field that was inserted into. If this is used to undo user edits, this may also remove the edits made by other users. + +To work around this issue, it is recommended that the operation be semantically split into the field initialization (`[{i:''}]`) and the user's edit (`[{es: ['hi]}]`). These two parts can be composed, but undoing the user's action should only invert the user's part of that operation. + + +## Conflicts + +There are many ways concurrent changes can incidentally lose user data: + +- One user moves data into a location that another user is deleting +- Two concurrent edits move or insert into the same location in a document +- Concurrent edits move items inside one another. (Move A inside B / Move B inside A) + +By default, these issuesĀ will be resolved automatically by deleting the offending content. This may not be what users want. The JSON1 library allows users to make their own decisions about specificity: + +- When simply calling `transform`, any of these issues will throw an exception. (Or `tryTransform`, which returns any conflicts instead of throwing them). +- You can use `transformNoConflict`. This will resolve all of these issues automatically, which may lose user data. +- For more control, `typeAllowingConflictsPred` allows you to specify a predicate function which will be passed any conflicts which occur. The predicate returns true if the library should attempt to automatically resolve the specified conflict. diff --git a/src/json1presence/test/cursor.js b/src/json1presence/test/cursor.js new file mode 100644 index 00000000..31a9cb8f --- /dev/null +++ b/src/json1presence/test/cursor.js @@ -0,0 +1,111 @@ +const { writeCursor, readCursor } = require('../dist/cursor') +const assert = require('assert') + +const data = require('fs') + .readFileSync(__dirname + '/ops.json', 'utf8') + .split('\n') + .filter(x => x !== '') + .map(JSON.parse) + +describe('cursors', function() { + describe('writeCursor duplicates', function() { + const test = op => { + const w = writeCursor() + + const f = l => { + assert(Array.isArray(l)) + let depth = 0 + for (let c of l) { + if (['string', 'number'].includes(typeof c)) { + depth++ + w.descend(c) + } else if (Array.isArray(c)) { + f(c) + } else if (typeof c === 'object') { + for (let k in c) { + const v = c[k] + w.write(k, v) + } + } + } + + return __range__(0, depth, false).map(i => w.ascend()) + } + + if (op !== null) { + f(op) + } + return assert.deepEqual(op, w.get()) + } + + return data.map(d => (d => it(`${JSON.stringify(d)}`, () => test(d)))(d)) + }) + + describe('copy using read cursors', function() { + const test = op => { + const r = readCursor(op) + const w = writeCursor() + const path = [] + const f = () => { + const component = r.getComponent() + if (component) { + // console.log 'component', component + for (let k in component) { + const v = component[k] + w.write(k, v) + } + } + + assert.deepStrictEqual(r.getPath(), path) + assert.deepStrictEqual(w.getPath(), path) + + const result = [] + for (let k of r) { + path.push(k) + w.descend(k) + f() + w.ascend() + result.push(path.pop()) + } + return result + } + + f() + + return assert.deepEqual(op, w.get()) + } + // console.log op + // console.log w.get() + return data.map(d => it(`${JSON.stringify(d)}`, () => test(d))) + }) + + return describe('fuzzer', () => + it('cleans up position after mergeTree', () => { + const a = [1, 'c', { d: 1 }] + const w = writeCursor(a) + + w.descend(0) + + w.descend('a') + w.write('p', 1) + w.ascend() + w.ascend() + + w.descend(1) + w.mergeTree([{ r: true }]) + w.descend('b') + w.write('p', 0) // Crash! + w.ascend() + return w.ascend() + })) +}) + +function __range__(left, right, inclusive) { + const range = [] + const ascending = left < right + const end = !inclusive ? right : ascending ? right + 1 : right - 1 + for (let i = left; ascending ? i < end : i > end; ascending ? i++ : i--) { + range.push(i) + } + return range +} diff --git a/src/json1presence/test/fuzzer.js b/src/json1presence/test/fuzzer.js new file mode 100644 index 00000000..f1e52e0d --- /dev/null +++ b/src/json1presence/test/fuzzer.js @@ -0,0 +1,18 @@ +const assert = require('assert') +// const {type} = require('../index') +const { type } = require('../dist/json1') + +const run = (module.exports = () => { + // require('./lib/log').quiet = true + // type.debug = true + const fuzzer = require('ot-fuzzer') + const genOp = require('./genOp') + + const _t = type.typeAllowingConflictsPred(() => true) + + // const tracer = require('./tracer')(_t, genOp) + //fuzzer(tracer, tracer.genOp, 100000) + fuzzer(_t, genOp, 10000000) +}) + +if (require.main === module) run() diff --git a/src/json1presence/test/genOp.js b/src/json1presence/test/genOp.js new file mode 100644 index 00000000..6a188042 --- /dev/null +++ b/src/json1presence/test/genOp.js @@ -0,0 +1,278 @@ +let genRandomOp +const { randomInt, randomReal, randomWord } = require('ot-fuzzer') + +const assert = require('assert') +const { writeCursor } = require('../dist/cursor') +const log = require('../dist/log').default +const { type } = require('..') + +// This is an awful function to clone a document snapshot for use by the random +// op generator. .. Since we don't want to corrupt the original object with +// the changes the op generator will make. +const clone = o => (typeof o === 'object' ? JSON.parse(JSON.stringify(o)) : o) + +const randomKey = obj => { + // this works on arrays too! + if (Array.isArray(obj)) { + return obj.length === 0 ? undefined : randomInt(obj.length) + } else { + const keys = Object.keys(obj) + return keys.length === 0 ? undefined : keys[randomInt(keys.length)] + } +} + +const letters = 'abxyz' + +// Generate a random new key for a value in obj. +const randomNewKey = obj => { + if (Array.isArray(obj)) { + return randomInt(obj.length + 1) + } else { + let key + while (true) { + // Mostly try just use a small letter + key = + randomInt(20) === 0 ? randomWord() : letters[randomInt(letters.length)] + if (obj[key] === undefined) { + break + } + } + return key + } +} + +// Generate a random object +const randomThing = () => { + switch (randomInt(7)) { + case 0: + return null + case 1: + return '' + case 2: + return randomWord() + case 3: + const obj = {} + for ( + let i = 1, end = randomInt(2), asc = 1 <= end; + asc ? i <= end : i >= end; + asc ? i++ : i-- + ) { + obj[randomNewKey(obj)] = randomThing() + } + return obj + case 4: + return __range__(1, randomInt(2), true).map(j => randomThing()) + case 5: + return 0 // bias toward zeros to find accidental truthy checks + case 6: + return randomInt(50) + } +} + +// Pick a random path to something in the object. +const randomPath = data => { + if (data == null || typeof data !== 'object') { + return [] + } + + const path = [] + while (randomReal() < 0.9 && data != null && typeof data === 'object') { + const key = randomKey(data) + if (key == null) { + break + } + + path.push(key) + data = data[key] + } + + return path +} + +const randomWalkPick = (w, container) => { + const path = randomPath(container.data) + let parent = container + let key = 'data' + + for (let p of Array.from(path)) { + parent = parent[key] + key = p + w.descend(key) + } + const operand = parent[key] + return [path, parent, key, operand] +} + +// Returns [path, parent, key] if we can drop here, +// or null if no drop is possible +const randomWalkDrop = (w, container) => { + if (container.data === undefined) { + return [[], container, 'data'] + } else if (typeof container.data !== 'object' || container.data === null) { + return null // Can't insert into a document that is a string or number + } + + let [path, parent, key, operand] = randomWalkPick(w, container) + if (typeof operand === 'object' && operand !== null) { + parent = operand + } else { + w.ascend() + w.reset() + path.pop() + } + key = randomNewKey(parent) + return [path, parent, key] +} + +const set = (container, key, value) => + value === undefined + ? Array.isArray(container) + ? container.splice(key, 1) + : delete container[key] + : Array.isArray(container) + ? container.splice(key, 0, value) + : (container[key] = value) + +const genRandomOpPart = data => { + // log 'genRandomOp', data + + let key1, parent1, path1 + const container = { data } + const w = writeCursor() + + // Remove something + + // Things we can do: + // 0. remove something + // 1. move something + // 2. insert something + // 3. edit a string + const mode = data === undefined && randomReal() < 0.9 ? 2 : randomInt(4) + //mode = 1 + //log 'mode', mode + switch (mode) { + case 0: + case 1: + const [path, parent, key, operand] = randomWalkPick(w, container) + //log 'ppko', path, parent, key, operand + if (mode === 1 && typeof operand === 'string') { + // Edit it! + const genString = require('./genTextOp') + const [stringOp, result] = genString(operand) + w.write('es', stringOp) + parent[key] = result + } else if (mode === 1 && typeof operand === 'number') { + const increment = randomInt(10) + w.write('ena', increment) + parent[key] += increment + } else { + // Remove it + if (operand !== undefined) { + w.write('r', true) //operand + set(parent, key, undefined) + } + } + break + + case 2: + // insert something + const walk = randomWalkDrop(w, container) + if (walk !== null) { + const [path, parent, key] = walk + //log 'walk', walk + const val = randomThing() + if (parent !== container) { + w.descend(key) + } + w.write('i', val) + set(parent, key, clone(val)) + } + break + + case 3: + // Move something. We'll pick up the current operand... + const [path1, parent1, key1, operand1] = randomWalkPick(w, container) + if (operand1 !== undefined) { + set(parent1, key1, undefined) // remove it from the result... + + if (parent1 === container) { + // We're removing the whole thing. + w.write('r', true) + } else { + w.write('p', 0) + + // ... and find another place to insert it! + for ( + let i = 0, end = path1.length, asc = 0 <= end; + asc ? i < end : i > end; + asc ? i++ : i-- + ) { + w.ascend() + } + w.reset() + const [path2, parent2, key2] = Array.from( + randomWalkDrop(w, container) + ) + w.descend(key2) + set(parent2, key2, operand1) + w.write('d', 0) + } + } + break + } + + const doc = container.data + const op = w.get() + + type.checkValidOp(op) + + // assert.deepEqual doc, type.apply clone(data), op + + return [op, doc] +} + +module.exports = genRandomOp = doc => { + doc = clone(doc) + + // 90% chance of adding an op the first time through, then 50% each successive time. + let chance = 0.99 + + let op = null // Aggregate op + + while (randomReal() < chance) { + let opc + ;[opc, doc] = genRandomOpPart(doc) + log(opc) + // type.setDebug false + op = type.compose( + op, + opc + ) + chance = 0.5 + } + + // log.quiet = false + log('-> generated op', op, 'doc', doc) + return [op, doc] +} + +if (require.main === module) { + // log genRandomOp {} + // log genRandomOp({x:5, y:7, z:[1,2,3]}) for [1..10] + for (let i = 1; i <= 10; i++) { + type.debug = true + log.quiet = false + log(genRandomOp({ x: 'hi', y: 'omg', z: [1, 'whoa', 3] })) + } +} +// log genRandomOp(undefined) + +function __range__(left, right, inclusive) { + const range = [] + const ascending = left < right + const end = !inclusive ? right : ascending ? right + 1 : right - 1 + for (let i = left; ascending ? i < end : i > end; ascending ? i++ : i--) { + range.push(i) + } + return range +} diff --git a/src/json1presence/test/genTextOp.js b/src/json1presence/test/genTextOp.js new file mode 100644 index 00000000..9214939a --- /dev/null +++ b/src/json1presence/test/genTextOp.js @@ -0,0 +1,85 @@ +// This was stolen from ot-text-unicode 's sourcecode. Its copied in full here +// because it won't change much, and because the npm module doesn't export this +// file. TODO: export this from the npm module and delete this. +const {randomInt, randomWord} = require('ot-fuzzer') +const {type} = require('ot-text-unicode') + +const emoji = 'šŸ˜…šŸ¤–šŸ‘»šŸ¤ŸšŸ’ƒ' + +const strToList = s => { + const list = [] + let i = 0 + + while (i < s.length) { + const code = s.charCodeAt(i) + + if (code >= 0xd800) { + list.push(s[i] + s[i+1]) + i += 2 + } else list.push(s[i++]) + } + + return list +} + + +module.exports = function genOp(docStr) { + docStr = strToList(docStr) + const initialLen = docStr.length + + const op = [] + let expectedDoc = '' + + const consume = len => { + expectedDoc += docStr.slice(0, len).join('') + docStr = docStr.slice(len) + } + + const addInsert = () => { + // Insert a random word from the list somewhere in the document + let word + const skip = randomInt(Math.min(docStr.length, 5)) + + if (randomInt(5)) { // Usually just use normal ascii characters + word = randomWord() + ' ' + } else { + const p = randomInt(emoji.length/2) + word = emoji.slice(p*2, (p*2) + 2) + } + + op.push(skip) + consume(skip) + + op.push(word) + expectedDoc += word + } + + const addDelete = function() { + const skip = randomInt(Math.min(docStr.length, 5)) + + op.push(skip) + consume(skip) + + const length = randomInt(Math.min(docStr.length, 10)) + op.push({d:length}) + return docStr = docStr.slice(length) + } + + while (docStr.length > 0) { + // If the document is long, we'll bias it toward deletes + const chance = initialLen > 100 ? 3 : 2 + switch (randomInt(chance)) { + case 0: addInsert(); break + case 1: case 2: addDelete(); break + } + + if (randomInt(7) === 0) break + } + + // The code above will never insert at the end of the document. Its important to do that + // sometimes. + if (randomInt(docStr.length === 0 ? 2 : 10) === 0) addInsert() + + expectedDoc += docStr.join('') + return [type.normalize(op), expectedDoc] +} \ No newline at end of file diff --git a/src/json1presence/test/immutable.js b/src/json1presence/test/immutable.js new file mode 100644 index 00000000..cc1da99e --- /dev/null +++ b/src/json1presence/test/immutable.js @@ -0,0 +1,68 @@ +const assert = require('assert') +const { type } = require('../dist/json1.release') +const log = require('../dist/log').default +const genOp = require('./genOp') +const deepClone = require('../dist/deepClone').default + +// This tests that none of apply / compose / transform / genOp mutate their input +describe('immutable guarantees', function() { + const origDoc = { x: 'hi', y: 'omg', z: [1, 'whoa', 3] } + const expectDoc = deepClone(origDoc) + const n = 1000 + // These tests are only slow in debug mode. In release mode they're pretty snappy. + this.slow(n * 5) + this.timeout(n * 10) + + it('apply does not mutate', () => { + const result = [] + for (let i = 0; i < n; i++) { + const [op, doc] = genOp(origDoc) + assert.deepStrictEqual(origDoc, expectDoc) + + const expectOp = deepClone(op) + try { + type.apply(origDoc, op) + } catch (e) { + console.log(`Apply failed! Repro apply( ${JSON.stringify(origDoc)}, ${JSON.stringify(op)} )`) + throw e + } + + assert.deepStrictEqual(origDoc, expectDoc) + result.push(assert.deepStrictEqual(op, expectOp)) + } + return result + }) + + it('compose does not mutate', () => { + for (let i = 0; i < n; i++) { + let op2 + let [op1, doc] = genOp(origDoc) + ;[op2, doc] = genOp(doc) + + const expectOp1 = deepClone(op1) + const expectOp2 = deepClone(op2) + type.compose( + op1, + op2 + ) + + assert.deepStrictEqual(op1, expectOp1) + assert.deepStrictEqual(op2, expectOp2) + } + }) + + it('transform does not mutate', () => { + for (let i = 0; i < n; i++) { + const [op1, doc1] = genOp(origDoc) + const [op2, doc2] = genOp(origDoc) + + const expectOp1 = deepClone(op1) + const expectOp2 = deepClone(op2) + + type.transformNoConflict(op1, op2, 'left') + type.transformNoConflict(op2, op1, 'right') + assert.deepStrictEqual(op1, expectOp1) + assert.deepStrictEqual(op2, expectOp2) + } + }) +}) diff --git a/src/json1presence/test/ops.json b/src/json1presence/test/ops.json new file mode 100644 index 00000000..ff96e44c --- /dev/null +++ b/src/json1presence/test/ops.json @@ -0,0 +1,48 @@ +null +["a",{"es":["hi"]}] +["a",{"r":true}] +["x",["a",{"p":0}],["b",{"d":0}]] +["x",{"es":["hi"]}] +["x",{"i":"one"}] +["x",{"i":"two"}] +["x",{"i":5}] +["x",{"r":true}] +["y",["a",{"p":0}],["b",{"d":0}]] +["y",{"r":true}] +[0,{"i":"oh hi"}] +[0,{"i":17}] +[0,{"r":true}] +[1,{"r":true}] +[10,{"es":["edit"]}] +[2,"x",{"i":"hi"}] +[2,{"i":"hi"}] +[2,{"i":"other"}] +[2,{"r":true,"i":"other"}] +[2,{"r":true}] +[5,{"e":false,"et":"simple"}] +[5,{"e":null,"et":"simple"}] +[["X",{"d":0},"Y",{"d":1}],["x",{"p":0},"y",{"p":1}]] +[["_x",{"p":0}],["y",{"d":0}]] +[["a",{"d":0}],["x",{"r":true},"a",{"p":0}]] +[["a",{"p":0},"b",{"p":1}],["b",{"d":1},"a",{"d":0}]] +[["a",{"p":0}],["b",{"d":0}],["x",{"p":1}],["y",{"d":1}]] +[["x",["a",{"p":0}],["b",{"d":0}]],["y",["a",{"p":1}],["b",{"d":1}]]] +[["x",{"p":0}],["y",{"d":0}]] +[[0,{"i":"a"}],[2,{"i":"b"}]] +[[0,{"p":0}],[10,{"d":0}]] +[[0,{"r":true}],[1,{"i":"hi"}]] +[[1,{"p":0}],[2,{"d":0}]] +[[1,{"r":{},"i":11}],[2,{"r":{},"i":12}]] +[[11,{"i":1}],[12,{"i":2}],[13,{"i":3}]] +[{"e":"hi","et":"simple"}] +[{"e":false,"et":"simple"}] +[{"e":null,"et":"simple"}] +[{"e":{"position":2,"text":"wai"},"et":"simple"}] +[{"e":{},"et":"simple"}] +[{"es":"hi"}] +[{"es":[2,"maghe"]}] +[{"i":5}] +[{"i":[1,2,3]}] +[{"i":[],"r":true},[0,{"d":0}],["a",{"p":0}]] +[{"r":{},"i":[1,2,3]}] +[{"r":{}}] diff --git a/src/json1presence/test/test.js b/src/json1presence/test/test.js new file mode 100644 index 00000000..f422ad91 --- /dev/null +++ b/src/json1presence/test/test.js @@ -0,0 +1,3421 @@ +// Unit tests for the JSON1 OT type. +// +// These tests are quite unstructured. You can see the skeletons of a few +// organizing systems, but ultimately there's just lots of test cases to run. +// +// Cleanups welcome, so long as you don't remove any tests. + +const assert = require('assert') +const { type } = require('../dist/json1') +const log = require('../dist/log').default +const genOp = require('./genOp') +const deepClone = require('../dist/deepClone').default + +const { transform } = type +const { DROP_COLLISION, RM_UNEXPECTED_CONTENT, BLACKHOLE } = type + +const apply = ({ doc: snapshot, op, expect }) => { + // type.setDebug(false) + + const orig = deepClone(snapshot) + try { + const result = type.apply(snapshot, op) + assert.deepStrictEqual(snapshot, orig, 'Original snapshot was mutated') + return assert.deepStrictEqual(result, expect) + } catch (e) { + console.log( + [ + 'Apply failed! Repro ', + `apply( ${JSON.stringify(snapshot)}, ${JSON.stringify(op)} )` + ].join('') + ) + console.log(`expected output: ${JSON.stringify(expect)}`) + throw e + } +} + +const d = fn => { + type.setDebug(true) + fn() + return type.setDebug(false) +} + +const compose = ({ op1, op2, expect }) => { + try { + const result = type.compose( + op1, + op2 + ) + return assert.deepStrictEqual(result, expect) + } catch (e) { + d(() => { + console.error('FAIL! Repro with:') + console.log(`compose( ${JSON.stringify(op1)}, ${JSON.stringify(op2)} )`) + console.log(`expected output: ${JSON.stringify(expect)}`) + return type.compose( + op1, + op2 + ) + }) + throw e + } +} + +const invConflict = ({ type, op1, op2 }) => ({ type, op1: op2, op2: op1 }) + +const otherSide = side => (side === 'left' ? 'right' : 'left') + +const deepFreeze = obj => { + if (typeof obj === 'object' && obj != null) { + Object.freeze(obj) + if (Array.isArray(obj)) obj.forEach(deepFreeze) + else for (const k in obj) { + deepFreeze(obj[k]) + } + } + return obj // chaining +} + +const checkConflict = ({ + op1, + op2, + side, + conflict: expectConflict, + expect +}) => { + // We should get the same conflict with xf(op1, op2, left) and xf(op2, op1, right). + if (expectConflict != null) { + if (!expectConflict.op1) { + expectConflict.op1 = type.normalize(op1) + } + if (!expectConflict.op2) { + expectConflict.op2 = type.normalize(op2) + } + } + + const result = [] + for (let [side_, op1_, op2_, ec] of [ + [side, op1, op2, expectConflict], + [ + otherSide(side), + op2, + op1, + expectConflict ? invConflict(expectConflict) : null + ] + ]) { + try { + // d -> log('tryTransform', side_, op1_, op2_) + const { ok, conflict } = type.tryTransform(op1_, op2_, side_) + if (ec == null) { + // We don't care what the result is here; just that it doesn't conflict. + result.push(assert(ok)) + } else { + assert(!ok, `Conflict erroneously succeeded (${side_})`) + // d -> log('conflict', conflict) + conflict.op1 = type.normalize(conflict.op1) + conflict.op2 = type.normalize(conflict.op2) + result.push(assert.deepStrictEqual(conflict, ec)) + } + } catch (e) { + d(() => { + console.error('FAIL! Repro with:') + console.log( + `tryTransform(${JSON.stringify(op1_)}, ${JSON.stringify( + op2_ + )}, '${side_}')` + ) + return type.tryTransform(op1_, op2_, side_) + }) + throw e + } + } +} + +const xf = ({ + op1, + op2, + conflict, + conflictLeft, + conflictRight, + expect, + expectLeft, + expectRight +}) => { + if (expect !== undefined) { + expectLeft = expectRight = expect + } + if (conflict !== undefined) { + conflictLeft = conflictRight = conflict + } + + for (let [side, e, c] of [ + ['left', expectLeft, conflictLeft], + ['right', expectRight, conflictRight] + ]) { + checkConflict({ op1, op2, side, conflict: c, expect: e }) + + try { + const result = + c != null + ? type.transformNoConflict(op1, op2, side) + : transform(op1, op2, side) + assert.deepStrictEqual(result, e) + } catch (error) { + e = error + d(() => { + console.error('FAIL! Repro with:') + console.log( + `transform(${JSON.stringify(op1)}, ${JSON.stringify(op2)}, '${side}')` + ) + }) + // if c? then type.transformNoConflict op1, op2, side else transform op1, op2, side + throw e + } + } +} + +const diamond = ({ doc, op1, op2 }) => { + let doc1, doc12, doc2, doc21, op1_, op2_ + type.setDebug(false) + + try { + // Test that the diamond property holds + op1_ = transform(op1, op2, 'left') + op2_ = transform(op2, op1, 'right') + + doc1 = type.apply(doc, op1) + doc2 = type.apply(doc, op2) + + doc12 = type.apply(doc1, op2_) + doc21 = type.apply(doc2, op1_) + + return assert.deepStrictEqual(doc12, doc21) + } catch (e) { + log.quiet = false + log('\nOops! Diamond property does not hold. Given document', doc) + log('op1 ', op1, ' / op2', op2) + log('op1_', op1_, ' / op2_', op2_) + log('---- 1') + log('op1', op1, '->', doc1) + log('op2', op2_, '->', doc12) + log('---- 2') + log('op2', op2, '->', doc2) + log('op1', op1_, '->', doc21) + log('----------') + log(doc12, '!=', doc21) + throw e + } +} + +const path = (path, { op, expect }) => { + if (expect === undefined) { + expect = path.slice() + } + + const result = type.transformPosition(path, op) + assert.deepStrictEqual(result, expect) + + // Also check that path+X = expect+X + const path2 = path.concat('x') + const expect2 = expect != null ? expect.concat('x') : null + + const result2 = type.transformPosition(path2, op) + assert.deepStrictEqual(result2, expect2) +} + +describe('json1', () => { + before(() => { + type.registerSubtype(require('ot-simple')) + // type.setDebug(true) + }) + after(() => type.setDebug(false)) + + describe('checkOp', () => { + const pass = op => { + try { + return type.checkValidOp(op) + } catch (e) { + console.log(`FAIL! Repro with:\ncheckOp( ${JSON.stringify(op)} )`) + throw e + } + } + + const fail = op => { + try { + return assert.throws(() => type.checkValidOp(op)) + } catch (e) { + console.log(`FAIL! Repro with:\ncheckOp( ${JSON.stringify(op)} )`) + console.log('Should throw!') + throw e + } + } + + it('allows some simple valid ops', () => { + pass(null) + pass([{ i: [1, 2, 3] }]) + pass([{ r: {} }]) + pass([['x', { p: 0 }], ['y', { d: 0 }]]) + pass([[0, { p: 0 }], [10, { d: 0 }]]) + pass([['a', { p: 0 }], ['b', { d: 0 }], ['x', { p: 1 }], ['y', { d: 1 }]]) + pass([{ e: 'hi', et: 'simple' }]) + pass([{ es: ['hi'] }]) + pass([{ ena: 5 }]) + }) + + it('disallows invalid syntax', () => { + fail(undefined) + fail({}) + fail('hi') + fail(true) + fail(false) + fail(0) + fail(10) + fail([{}]) + fail([{ invalid: true }]) + fail([10, {}]) + fail([10, { invalid: true }]) + fail([10, 'hi']) + fail([[{ i: 123 }]]) + }) + + it('throws if there is any empty leaves', () => { + fail([]) + fail(['x']) + fail(['x', {}]) + fail(['x', []]) + fail([10]) + fail([10, {}]) + fail([10, []]) + }) + + it('ensures path components are non-zero integers or strings', () => { + fail([-1, { r: {} }]) + fail([0.5, { r: {} }]) + fail([true, { r: {} }]) + fail([false, { r: {} }]) + fail([null, { r: {} }]) + fail([undefined, { r: {} }]) + }) + + it('disallows __proto__', () => { + fail(['__proto__', { r: {} }]) + }) + + it('does not allow two pickups or two drops in a component', () => { + fail([{ p: 0, r: {} }]) + fail([{ p: 1, r: {} }]) + fail(['x', { p: 0, r: {} }]) + fail(['x', { p: 1, r: {} }]) + + fail([{ d: 0, i: 'hi' }]) + fail([{ d: 1, i: 'hi' }]) + fail([10, { d: 0, i: 'hi' }]) + fail([10, { d: 1, i: 'hi' }]) + }) + + it('throws if there are mismatched pickups / drops', () => { + fail([{ p: 0 }]) + fail([{ d: 0 }]) + fail(['x', { p: 0 }]) + fail([10, { p: 0 }]) + fail(['x', { d: 0 }]) + fail([10, { d: 0 }]) + }) + + it('throws if pick/drop indexes dont start at 0', () => { + fail([['x', { p: 1 }], ['y', { d: 1 }]]) + fail([[10, { p: 1 }], [20, { d: 1 }]]) + }) + + it('throws if a descent starts with an edit', () => + fail([10, [{ i: 'hi' }]])) + + it('throws if descents are out of order', () => { + fail(['x', ['b', { r: {} }], ['a', { r: {} }]]) + fail(['x', [10, { r: {} }], [5, { r: {} }]]) + fail(['x', ['a', { r: {} }], [5, { r: {} }]]) + fail(['x', ['a', { r: {} }], ['a', { r: {} }]]) + fail(['x', [10, { r: {} }], [10, { r: {} }]]) + }) + + it('throws if descents start with the same scalar', () => + fail(['x', ['a', { r: {} }], ['a', { es: ['hi'] }]])) + + it('throws if descents have two adjacent edits', () => { + fail([{ r: {} }, { p: 0 }]) + fail(['x', { r: {} }, { p: 0 }]) + fail(['x', { r: {} }, { p: 0 }, 'y', { r: {} }]) + }) + + it.skip('does not allow ops to overwrite their own inserted data', () => { + fail([{ i: { x: 5 } }, 'x', { i: 6 }]) + fail([{ i: ['hi'] }, 0, { i: 'omg' }]) // Erm this looks ok... + }) + + it('allows immediate data directly parented in other immediate data', () => { + // TODO: We want to explicitly allow this for setNull behaviour. + pass([{ i: {} }, 'x', { i: 5 }]) + pass([{ i: { x: {} } }, 'x', 'y', { i: 6 }]) + pass([{ i: [] }, 0, { i: 5 }]) + }) + + it('does not allow the final item to be a single descent', () => + fail(['a', ['b', { r: {} }]])) // It should be ['a', 'b', r:{}] + + it('does not allow anything after the descents at the end', () => { + fail([[1, { r: {} }], [2, { r: {} }], 5]) + fail([[1, { r: {} }], [2, { r: {} }], 5, { r: {} }]) + fail([[1, { r: {} }], [2, { r: {} }], { r: {} }]) + }) + + it('allows removes inside removes', () => { + pass(['x', { r: true }, 'y', { r: true }]) + pass(['x', { r: {} }, 'y', { r: true }]) + pass([ + ['x', { r: true }, 'y', { p: 0 }, 'z', { r: true }], + ['y', { d: 0 }] + ]) + pass([['x', { r: {} }, 'y', { p: 0 }, 'z', { r: true }], ['y', { d: 0 }]]) + }) + + it('allows inserts inside inserts', () => { + pass([1, { i: {} }, 'x', { i: 10 }]) + pass([[0, 'x', { p: 0 }], [1, { i: {} }, 'x', { d: 0 }, 'y', { i: 10 }]]) + }) + + it.skip('fails if the operation drops items inside something it picked up', () => { + fail(['x', { r: true }, 1, { i: 'hi' }]) + fail(['x', { d: 0 }, 1, { p: 0 }]) + fail([{ r: true }, 1, { p: 0, d: 0 }]) + }) + + describe('edit', () => { + it('requires all edits to specify their type', () => { + fail([{ e: {} }]) + fail([5, { e: {} }]) + pass([{ e: {}, et: 'simple' }]) + }) + + it('allows edits to have null or false for the operation', () => { + // These aren't valid operations according to the simple type, but the + // type doesn't define a checkValidOp so we wouldn't be able to tell + // anyway. + pass([{ e: null, et: 'simple' }]) + pass([5, { e: null, et: 'simple' }]) + pass([{ e: false, et: 'simple' }]) + pass([5, { e: false, et: 'simple' }]) + }) + + it('does not allow an edit to use an unregistered type', () => { + fail([{ e: {}, et: 'an undefined type' }]) + fail([{ e: null, et: 'an undefined type' }]) + }) + + it('does not allow two edits in the same component', () => { + fail([{ e: {}, et: 'simple', es: [1, 2, 3] }]) + fail([{ es: ['hi'], ena: 5 }]) + fail([{ e: {}, et: 'simple', ena: 5 }]) + }) + + it('fails if the type is missing', () => fail([{ et: 'missing', e: {} }])) + + it('does not allow anything inside an edited subtree') + + it.skip('does not allow an edit inside removed or picked up content', () => { + fail([{ r: true }, 1, { es: ['hi'] }]) + pass([1, { r: true }, 1, { es: ['hi'] }]) + fail(['x', { r: true }, 1, { es: ['hi'] }]) + pass([[1, { p: 0 }, 1, { es: ['hi'] }], [2, { d: 0 }]]) + fail([['x', { p: 0 }, 1, { es: ['hi'] }], ['y', { d: 0 }]]) + + // This is actually ok. + pass([0, { p: 0 }, ['a', { es: ['hi'], r: true }], ['x', { d: 0 }]]) + }) + + it.skip('does not allow you to drop inside something that was removed', () => { + // These insert into the next list item + pass([[1, { r: true }, 1, { d: 0 }], [2, { p: 0 }]]) + pass([1, { p: 0 }, 'x', { d: 0 }]) + + // But this is not ok. + fail(['x', { p: 0 }, 'a', { d: 0 }]) + }) + }) + }) + + describe('isNoop', () => { + it('works', () => { + assert.strictEqual(type.isNoop(null), true) + assert.strictEqual(type.isNoop([{r: true}]), false) + }) + }) + + describe('normalize', () => { + const n = (opIn, expect) => { + if (expect === undefined) { + expect = opIn + } + const op = type.normalize(opIn) + assert.deepStrictEqual(op, expect) + } + + it('does the right thing for noops', () => { + n(null) + n([], null) + }) + + it('normalizes some regular ops', () => { + n([{ i: 'hi' }]) + n([{ i: 'hi' }, 1, 2, 3], [{ i: 'hi' }]) + n([[1, 2, 3, { p: 0 }], [1, 2, 3, { d: 0 }]], [1, 2, 3, { p: 0, d: 0 }]) + n( + [[1, 2, 3, { p: 0 }], [1, 2, 30, { d: 0 }]], + [1, 2, [3, { p: 0 }], [30, { d: 0 }]] + ) + n( + [[1, 2, 30, { p: 0 }], [1, 2, 3, { d: 0 }]], + [1, 2, [3, { d: 0 }], [30, { p: 0 }]] + ) + }) + + it('will let you insert null', () => n([{ i: null }])) + + it('normalizes embedded ops when available', () => { + n([{ es: [0, 'hi'] }], [{ es: ['hi'] }]) + n([{ et: 'text-unicode', e: ['hi'] }], [{ es: ['hi'] }]) + n([{ et: 'text-unicode', e: [0, 'hi'] }], [{ es: ['hi'] }]) + n([{ et: 'simple', e: {} }]) + n([{ et: 'number', e: 5 }], [{ ena: 5 }]) + n([{ ena: 5 }]) + }) + + it('removes embedded noops', () => { + n([{ es: []}], null) + n([['x', { es: []}], ['y', {r:true}]], ['y', {r:true}]) + }) + + it.skip('normalizes embedded removes', () => { + n([1, { r: true }, 2, { r: true }], [1, { r: true }]) + n([{ r: true }, 2, { r: true }], [{ r: true }]) + }) + + it('throws if the type is missing', () => + // Not sure if this is the best behaviour but ... eh. + assert.throws(() => n([{ et: 'missing', e: {} }]))) + + it('corrects weird pick and drop ids', () => + n([['x', { p: 1 }], ['y', { d: 1 }]], [['x', { p: 0 }], ['y', { d: 0 }]])) + }) + + // ****** Apply ****** + + describe('apply', () => { + it('Can set properties', () => { + apply({ + doc: [], + op: [0, { i: 17 }], + expect: [17] + }) + + apply({ + doc: {}, + op: ['x', { i: 5 }], + expect: { x: 5 } + }) + }) + + it('can edit the root', () => { + apply({ + doc: { x: 5 }, + op: [{ r: true }], + expect: undefined + }) + + apply({ + doc: '', + op: [{ r: true }], + expect: undefined + }) + + apply({ + doc: 'hi', + op: [{ r: true, i: null }], + expect: null + }) + + apply({ + doc: 'hi', + op: [{ es: [2, ' there'] }], + expect: 'hi there' + }) + + assert.throws(() => type.apply(null, [{ i: 5 }])) + + apply({ + doc: undefined, + op: [{ i: 5 }], + expect: 5 + }) + + apply({ + doc: { x: 5 }, + op: [{ r: {}, i: [1, 2, 3] }], + expect: [1, 2, 3] + }) + }) + + // TODO: And an edit of the root. + + it('can move 1', () => + apply({ + doc: { x: 5 }, + op: [['x', { p: 0 }], ['y', { d: 0 }]], + expect: { y: 5 } + })) + + it('can move 2', () => + apply({ + doc: [0, 1, 2], + op: [[1, { p: 0 }], [2, { d: 0 }]], + expect: [0, 2, 1] + })) + + it('can handle complex list index stuff', () => + apply({ + doc: [0, 1, 2, 3, 4, 5], + op: [[1, { r: {}, i: 11 }], [2, { r: {}, i: 12 }]], + expect: [0, 11, 12, 3, 4, 5] + })) + + it('correctly handles interspersed descent and edits', () => + apply({ + doc: { x: { y: { was: 'y' }, was: 'x' } }, + op: [['X', { d: 0 }, 'Y', { d: 1 }], ['x', { p: 0 }, 'y', { p: 1 }]], + expect: { X: { Y: { was: 'y' }, was: 'x' } } + })) + + it('can edit strings', () => + apply({ + doc: 'errd', + op: [{ es: [2, 'maghe'] }], + expect: 'ermagherd' + })) + + it('can edit numbers', () => + apply({ + doc: 5, + op: [{ ena: 10 }], + expect: 15 + })) + + it('can edit child numbers', () => + apply({ + doc: [20], + op: [0, { ena: -100 }], + expect: [-80] + })) + + it('can edit subdocuments using an embedded type', () => + apply({ + doc: { str: 'hai' }, + op: [{ e: { position: 2, text: 'wai' }, et: 'simple' }], + expect: { str: 'hawaii' } + })) + + it('applies edits after drops', () => + apply({ + doc: { x: 'yooo' }, + op: [['x', { p: 0 }], ['y', { d: 0, es: ['sup'] }]], + expect: { y: 'supyooo' } + })) + + it('throws when the op traverses missing items', () => { + assert.throws(() => type.apply([0, 'hi'], [1, { p: 0 }, 'x', { d: 0 }])) + assert.throws(() => type.apply({}, [{ p: 0 }, 'a', { d: 0 }])) + }) + + it('throws if the type is missing', () => + assert.throws(() => type.apply({}, [{ et: 'missing', e: {} }]))) + + it('throws when the op inserts in out of bound index', () => { + assert.throws(() => type.apply([0], [2, { i: 2 }])) + }) + }) + + describe('apply path', () => { + it('does not modify path when op is unrelated', () => { + path(['a', 'b', 'c'], { op: null }) + path(['a', 'b', 'c'], { op: ['x', { i: 5 }] }) + path(['a', 'b', 'c'], { op: ['x', { r: true }] }) + path(['a', 'b', 'c'], { op: [['x', { p: 0 }], ['y', { d: 0 }]] }) + path([1, 2, 3], { op: [2, { i: 5 }] }) + path([1, 2, 3], { op: [1, 2, 4, { i: 5 }] }) + path([1], { op: [1, 2, { r: true }] }) + path(['x'], { op: ['x', 'y', { r: true }] }) + }) + + it('adjusts list indicies', () => { + path([2], { op: [1, { i: 5 }], expect: [3] }) + path([2], { op: [2, { i: 5 }], expect: [3] }) + path([2], { op: [1, { r: true }], expect: [1] }) + path([2], { op: [[1, { p: 0 }], [3, { d: 0 }]], expect: [1] }) + path([2], { op: [[1, { d: 0 }], [3, { p: 0 }]], expect: [3] }) + path([2], { op: [[2, { d: 0 }], [3, { p: 0 }]], expect: [3] }) + }) + + it('returns null when the object at the path was removed', () => { + path(['x'], { op: [{ r: true }], expect: null }) + path(['x'], { op: ['x', { r: true }], expect: null }) + path([1], { op: [{ r: true }], expect: null }) + path([1], { op: [1, { r: true }], expect: null }) + }) + + it('moves the path', () => { + path(['a', 'z'], { + op: [['a', { p: 0 }], ['y', { d: 0 }]], + expect: ['y', 'z'] + }) + path(['a', 'b'], { + op: [['a', 'b', { p: 0 }], ['z', { d: 0 }]], + expect: ['z'] + }) + path(['a', 'b'], { op: [['a', 'b', 'c', { p: 0 }], ['z', { d: 0 }]] }) + path([1, 2], { op: [[1, { p: 0 }], [10, { d: 0 }]], expect: [10, 2] }) + path([1, 2], { op: [[1, 2, { p: 0 }], [10, { d: 0 }]], expect: [10] }) + path([1, 2], { op: [1, [1, { d: 0 }], [2, { p: 0 }]], expect: [1, 1] }) + path([1, 2], { op: [[1, 2, 3, { p: 0 }], [10, { d: 0 }]] }) + }) + + it('handles pick parent and move', () => + path(['a', 'b', 'c'], { + op: [['a', { r: true }, 'b', { p: 0 }], ['x', { d: 0 }]], + expect: ['x', 'c'] + })) + + it('adjusts indicies under a pick', () => + path(['a', 'b', 10], { + op: [['a', { p: 0 }, 'b', 1, { r: true }], ['x', { d: 0 }]], + expect: ['x', 'b', 9] + })) + + it.skip('gen ops', () => {}) + // This should do something like: + // - Generate a document + // - Generate op, a random operation + // - Generate a path to somewhere in the document and an edit we can do there -> op2 + // - Check that transform(op2, op) == op2 at transformPosition(path) or something like that. + + it('calls transformPosition with embedded string edits if available', () => { + // For embedded string operations (and other things that have + // transformPosition or transformPosition or whatever) we should call that. + path(['x', 'y', 'z', 1], { + op: ['x', 'y', 'z', { es: ['abc'] }], + expect: ['x', 'y', 'z', 4] + }) + path(['x', 'y', 'z', 1], { + op: ['x', 'y', 'z', { es: ['šŸ’ƒ'] }], + expect: ['x', 'y', 'z', 2] + }) + path(['x', 'y', 'z'], { + op: ['x', 'y', 'z', { es: ['šŸ’ƒ'] }], + expect: ['x', 'y', 'z'] + }) + }) + + it('works correctly with picks and drops', () => { + // From https://github.com/ottypes/json1/issues/27 + path(['na', 0, 'nb', 0], { + op: [ + { r: true, d: 0 }, + [ "na", 0, { p: 0} ], + [ "nb", 0, { i: { object: "four" } } ] + ], + expect: ['nb', 1] + }) + }) + }) + + // ***** Invert ***** + + describe('invert', () => { + describe('makeInvertible', () => { + const mi = (op, doc = null, expect = op) => { + const invertible = type.makeInvertible(op, doc) + assert.deepStrictEqual(invertible, expect) + } + + it('makeInvertible is sane', () => { + mi(null) + mi([{i:'hi'}]) + mi([2, {en:50}]) + mi([{r:true}], 'hi', [{r: 'hi'}]) + mi([2, {r:true}], [0, 1, 'hi'], [2, {r: 'hi'}]) + }) + + it('ignores descents into other parts of the op', () => + mi( + [['x', {r:true}], ['y', {i:5}]], + {x: 'hi'}, + [['x', {r:'hi'}], ['y', {i:5}]] + )) + + it('recursively calls makeInvertible on embedded operations', () => + mi( + [{es:[1, {d:5}]}], + 'abcdef', + [{es:[1, {d:'bcdef'}]}] + )) + + it('only embeds remove data not covered by child removes (obj)', () => mi( + [{r: true}, 'y', {r: true}], + {x: 5, y: 6}, + [{r: {x: 5}}, 'y', {r: 6}], + )) + + it('only embeds remove data not covered by child removes (list)', () => mi( + [{r: true}, 1, {r: true}], + ['a', 'b', 'c'], + [{r: ['a', 'c']}, 1, {r: 'b'}], + )) + + it('adjusts list offsets correctly', () => mi( + [{r: true}, [0, {r: true}], [2, {r: true}]], + ['a', 'b', 'c'], + [{r: ['b']}, [0, {r: 'a'}], [2, {r: 'c'}]] + )) + + it('calls makeInvertible on moved children', () => mi( + [['x', {p:0}], ['y', {d:0, es:[{d:5}]}]], + {x: 'abcde'}, + [['x', {p:0}], ['y', {d:0, es:[{d:'abcde'}]}]], + )) + + it('calls makeInvertible on inserted children', () => mi( + ['x', {i: 'hi', es: [{d:2}]}], + {}, + ['x', {i: 'hi', es: [{d:'hi'}]}] + )) + + it('adjusts position for makeInvertible based on removes', () => mi( + [[0, {r:true}], [1, {es: [{d:2}]}]], + ['a', 'b', 'hi'], + [[0, {r:'a'}], [1, {es: [{d:'hi'}]}]] + )) + + it('adjusts position for makeInvertible based on removes 2', () => mi( + [0, {r:true, es: [{d:2}]}], + ['a', 'hi'], + [0, {r:'a', es: [{d:'hi'}]}] + )) + + it('adjusts position for makeInvertible based on inserts', () => mi( + [[0, {i:'a'}], [2, {es: [{d:2}]}]], + ['b', 'hi'], + [[0, {i:'a'}], [2, {es: [{d:'hi'}]}]], + )) + + it('repro 1', () => mi( + [ [ 'b', { i: null } ], [ 'y', { r: true } ] ], + { y: 'omg' }, + [ [ 'b', { i: null } ], [ 'y', { r: 'omg' } ] ] + )) + + it('repro 2', () => mi( + [ + [ 'b', { d: 0 } ], + [ 'z', { r: true }, 1, { p: 0 } ] + ], + { z: [ 1, 'whoa', 3 ] }, + [ + [ 'b', { d: 0 } ], + [ 'z', { r: [ 1, 3 ] }, 1, { p: 0 } ] + ] + )) + + it('ignores rm based index offsets when iterating through an insert literal', () => mi( + [ 'z', { r: true, i: [ 'mimsy' ] }, 0, { r: true, es: [ { d: 3 } ]} ], + { z: [ 'x' ] }, + [ 'z', { r: [], i: [ 'mimsy' ] }, 0, { r: 'x', es: [ { d: 'mim' } ] } ] + )) + + it('takes into account insert index offsets through an insert literal', () => mi( + [ { i: [ 'abc' ], r: true }, + [ 0, { i: 'a' } ], + [ 1, { es: [ { d: 3 } ] } ] ], + true, + [ { i: [ 'abc' ], r: true }, + [ 0, { i: 'a' } ], + [ 1, { es: [ { d: 'abc' } ] } ] + ] + )) + }) + + describe('invert', () => { + const i = (op, expected, doc) => { + const actual = type.invert(op) + assert.deepStrictEqual(actual, expected) + + // Also for sanity, make sure if we apply the op then undo it we end + // up with the same doc. + const doc1 = type.apply(doc, op) // after the op is applied + const doc2 = type.apply(doc1, expected) // and then undone + + assert.deepStrictEqual(doc, doc2) + + // Check the inverted operation normalizes normally. + const normalized = type.normalize(actual) + assert.deepStrictEqual(actual, normalized, 'inverted op not canonical') + } + + it('inverts child operations', () => i( + [{es: ['x']}], + [{es: [{d:'x'}]}], + '_' + )) + + it('inverts child operations 2', () => i( + [2, {es: ['x']}], + [2, {es: [{d:'x'}]}], + [0, 1, '_'] + )) + + it('swaps pick and drop locations', () => i( + [['x', {p:0}], ['y', {d:0}]], + [['x', {d:0}], ['y', {p:0}]], + {x:5} + )) + + it('swaps pick and drop locations in a way that normalizes canonically', () => i( + [['a', {p:0}], ['b', {p:1}], ['c', {d:0}], ['d', {d:1}]], + [['a', {d:0}], ['b', {d:1}], ['c', {p:0}], ['d', {p:1}]], + {a: 5, b: 6} + )) + + it('teleports embedded edits based on the parent move', () => i( + [['x', {p:0}], ['y', {d:0}, 0, {es:['hi']}]], + [['x', {d:0}, 0, {es: [{d:'hi'}]}], ['y', {p:0}]], + {x: ['_']} + )) + + it('transforms indexes based on pick locations', () => i( + [[0, {r:true}], [1, {es:['hi']}]], + [[0, {i:true}], [2, {es:[{d:'hi'}]}]], + [true, 123, '_'] + )) + + it('transforms indexes based on drop locations', () => i( + [[0, {i:true}], [1, {es:['hi']}]], // -> [true, 'hi_'] + [0, {r:true, es:[{d:'hi'}]}], + ['_'] + )) + + it('an operation thats immediately edited is removed entirely', () => i( + [{i: 'hi', es: ['x']}], + [{r: 'xhi'}], + )) + + it('an operation thats immediately edited internally is removed entirely', () => i( + [{i: {x: 'hi'}}, 'x', {es: ['x']}], + [{r: {x: 'xhi'}}], + )) + + it('ignores edits that have been baked', () => i( + [['a', {i: 'hi', es: ['x']}], ['b', {es: ['y']}]], + [['a', {r: 'xhi'}], ['b', {es: [{d:'y'}]}]], + {b: '_'} + )) + + it('ins, ins-edit', () => i( + [{i: {}}, 'x', {i: '_', es: ['x']}], + [{r: {}}, 'x', {r: 'x_'}] + )) + + it('ins, ins, edit', () => i( + [{i: {}}, 'x', {i: ['_']}, 0, {es: ['x']}], + [{r: {}}, 'x', {r: ['x_']}] + )) + + it('ins, drop-edit', () => i( + [['a', {p:0}], ['b', {i: {}}, 'a', {d: 0, es:['x']}]], + [['a', {d:0, es:[{d:'x'}]}], ['b', {r: {}}, 'a', {p:0}]], + {a: '_'} + )) + + it('ins, drop, edit', () => i( + [['a', {p:0}], ['b', {i: {}}, 'a', {d: 0}, 'x', {es:['x']}]], + [['a', {d:0}, 'x', {es:[{d:'x'}]}], ['b', {r: {}}, 'a', {p:0}]], + {a: {x: '_'}} + )) + + it('adjusts indexes when it bakes based off inline inserts', () => i( // regression + [ {i: [ 99 ]}, [ 0, { i: null } ], [ 1, { ena: 1 } ] ], + [ { r: [ 100 ] }, 0, { r: null } ] + )) + + it('traversal picks the right index in complex situations', () => i( // fuzzer nonsense + [ + [ 'a', { i: { } }, 'b', { d: 0 } ], + [ 'z', [ 0, { i: '', r: 1 } ], [ 1, { p: 0, ena: 9 } ] ] + ], + [ + [ 'a', { r: { } }, 'b', { p: 0 } ], + [ 'z', [ 0, { i: 1, r: '' } ], [ 1, { d: 0 } ], [2, {ena: -9} ] ] + ], + { x: 'hi', y: 'omg', z: [1, 'whoa', 3] } + // { x: 'hi', y: 'omg', z: [ '', 12 ], a: { b: 'whoa' } } + )) + }) + + describe('invert fuzzer', () => { + // This is a tiny version of the normal fuzzer's tests, just for invert. + + it('invert fuzz', function () { + // Its a bit weird having this here. Consider moving it to immutable.js. + let n = 1000 + // this.timeout(0) + this.timeout(n * 5) + this.slow(n * 2) + const origDoc = deepFreeze({ x: 'hi', y: 'omg', z: [1, 'whoa', 3] }) + + for (let i = 0; i < n; i++) { + // if (i % 10000 == 0) console.log(i) + log(`\n\n======== iter ${i} =========`) + const [op, doc] = genOp(origDoc) + deepFreeze(op) // make sure nothing in the original operation gets mutated. + + try { + // invertWithDoc is just a wrapper around makeInvertible then invert + // anyway. Breaking them out helps in debugging. + // const opInv = type.invertWithDoc(op, origDoc) + const op1 = type.makeInvertible(op, origDoc) + const opInv = type.invert(op1) + + log('op', op1) + log('opInv', opInv) + + const docLoop = type.apply(doc, opInv) + assert.deepStrictEqual(docLoop, origDoc) + } catch (e) { + console.error('Crashed at iteration', i) + type.setDebug(true) + log('op', op) + log('doc', doc) + throw e + } + } + }) + }) + }) + + // ******* Compose ******* + + describe('compose', () => { + it('composes empty ops to nothing', () => + compose({ + op1: null, + op2: null, + expect: null + })) + + describe('op1 drop', () => { + it('vs remove', () => + compose({ + op1: [['x', { p: 0 }], ['y', { d: 0 }]], + op2: ['y', { r: true }], + expect: ['x', { r: true }] + })) + + it('vs remove parent', () => + compose({ + op1: [['x', { p: 0 }], ['y', 0, { d: 0 }]], + op2: ['y', { r: true }], + expect: [['x', { r: true }], ['y', { r: true }]] + })) + + it('vs remove child', () => + compose({ + op1: [['x', { p: 0 }], ['y', { d: 0 }]], + op2: ['y', 'a', { r: true }], + expect: [['x', { p: 0 }, 'a', { r: true }], ['y', { d: 0 }]] + })) + + it('vs remove and pick child', () => + compose({ + op1: [['x', { p: 0 }], ['y', { d: 0 }]], + op2: [['y', { r: true }, 'a', { p: 0 }], ['z', { d: 0 }]], + expect: [['x', { r: true }, 'a', { p: 0 }], ['z', { d: 0 }]] + })) + + it('vs pick', () => + compose({ + op1: [['x', { p: 0 }], ['y', { d: 0 }]], + op2: [['y', { p: 0 }], ['z', { d: 0 }]], + expect: [['x', { p: 0 }], ['z', { d: 0 }]] + })) + + it('is transformed by op2 picks', () => + compose({ + op1: [['x', { p: 0 }], ['y', 10, { d: 0 }]], + op2: ['y', 0, { r: true }], + expect: [['x', { p: 0 }], ['y', [0, { r: true }], [9, { d: 0 }]]] + })) + }) + + describe('op1 insert', () => { + it('vs remove', () => + compose({ + op1: ['x', { i: { a: 'hi' } }], + op2: ['x', { r: true }], + expect: null + })) + + it('vs remove parent', () => + compose({ + op1: ['x', 0, { i: { a: 'hi' } }], + op2: ['x', { r: true }], + expect: ['x', { r: true }] + })) + + it('vs remove child', () => + compose({ + op1: ['x', { i: { a: 'hi', b: 'woo' } }], + op2: ['x', 'a', { r: true }], + expect: ['x', { i: { b: 'woo' } }] + })) + + it('vs remove and pick child', () => + compose({ + op1: ['x', { i: { a: 'hi', b: 'woo' } }], + op2: [['x', { r: true }, 'a', { p: 0 }], ['y', { d: 0 }]], + expect: ['y', { i: 'hi' }] + })) + + it('vs remove an embedded insert', () => + compose({ + op1: ['x', { i: {} }, 'y', { i: 'hi' }], + op2: ['x', 'y', { r: true }], + expect: ['x', { i: {} }] + })) + + it('vs remove from an embedded insert', () => + compose({ + op1: ['x', { i: {} }, 'y', { i: [1, 2, 3] }], + op2: ['x', 'y', 1, { r: true }], + expect: ['x', { i: {} }, 'y', { i: [1, 3] }] + })) + + it('picks the correct element of an embedded insert', () => + compose({ + op1: ['x', { i: ['a', 'b', 'c'] }, 1, { i: 'XX' }], + op2: [['x', 1, { p: 0 }], ['y', { d: 0 }]], + expect: [['x', { i: ['a', 'b', 'c'] }], ['y', { i: 'XX' }]] + })) + + it('picks the correct element of an embedded insert 2', () => + compose({ + op1: ['x', { i: ['a', 'b', 'c'] }, 1, { i: 'XX' }], + op2: [['x', 3, { p: 0 }], ['y', { d: 0 }]], // should grab 'c'. + expect: [['x', { i: ['a', 'b'] }, 1, { i: 'XX' }], ['y', { i: 'c' }]] + })) + + it('moves all children', () => + compose({ + op1: ['x', { i: {} }, 'y', { i: [1, 2, 3] }], + op2: [['x', { p: 0 }], ['z', { d: 0 }]], + expect: ['z', { i: {} }, 'y', { i: [1, 2, 3] }] + })) + + it('removes all children', () => + compose({ + op1: ['x', { i: {} }, 'y', { i: [1, 2, 3] }], + op2: ['x', { r: true }], + expect: null + })) + + it('removes all children when removed at the destination', () => + compose({ + op1: [['x', { p: 0 }], ['y', { d: 0 }, 0, { i: 'hi' }]], + op2: ['y', { r: true }], + expect: ['x', { r: true }] + })) + + it('vs op2 insert', () => + compose({ + // Inserts aren't folded together. + op1: [{ i: {} }], + op2: ['x', { i: 'hi' }], + expect: [{ i: {} }, 'x', { i: 'hi' }] + })) + + it('vs op2 string edit', () => + compose({ + op1: [{ i: 'hi' }], + op2: [{ es: [2, ' there'] }], + expect: [{ i: 'hi', es: [2, ' there'] }] + })) + + it('vs op2 number edit', () => + compose({ + op1: [{ i: 10 }], + op2: [{ ena: 20 }], + expect: [{ i: 10, ena: 20 }] + })) + }) + + describe('op1 edit', () => { + it('removes the edit if the edited object is deleted', () => + compose({ + op1: ['x', { es: ['hi'] }], + op2: ['x', { r: true }], + expect: ['x', { r: true }] + })) + + it('removes the edit in an embedded insert 1', () => + compose({ + op1: ['x', { i: '', es: ['hi'] }], + op2: ['x', { r: true }], + expect: null + })) + + it('removes the edit in an embedded insert 2', () => + compose({ + op1: ['x', { i: [''] }, 0, { es: ['hi'] }], + op2: ['x', 0, { r: true }], + expect: ['x', { i: [] }] + })) + + it('composes string edits', () => + compose({ + op1: [{ es: ['hi'] }], + op2: [{ es: [2, ' there'] }], + expect: [{ es: ['hi there'] }] + })) + + it('composes number edits', () => + compose({ + op1: [{ ena: 10 }], + op2: [{ ena: -8 }], + expect: [{ ena: 2 }] + })) + + it('collapses noops at the operation level', () => + compose({ + op1: [{ es: ['hi'] }], + op2: [{ es: [{d:'hi'}] }], + expect: null + })) + + it('transforms and composes edits', () => + compose({ + op1: ['x', { es: ['hi'] }], + op2: [['x', { p: 0 }], ['y', { d: 0, es: [2, ' there'] }]], + expect: [['x', { p: 0 }], ['y', { d: 0, es: ['hi there'] }]] + })) + + it('preserves inserts with edits', () => + compose({ + op1: ['x', { i: 'hi' }], + op2: [['x', { p: 0 }], ['y', { d: 0, es: [' there'] }]], + expect: ['y', { i: 'hi', es: [' there'] }] + })) + + it('allows a different edit in the same location', () => + compose({ + op1: ['x', { es: ['hi'] }], + op2: ['x', { r: true, i: 'yo', es: [2, ' there'] }], + expect: ['x', { r: true, i: 'yo', es: [2, ' there'] }] + })) + + it('throws if the type is missing', () => + assert.throws(() => + type.compose( + [{ et: 'missing', e: {} }], + [{ et: 'missing', e: {} }] + ) + )) + }) + + describe('op2 pick', () => + it('gets untransformed by op1 drops', () => ({ + op1: [5, { i: 'hi' }], + op2: [6, { r: true }], + expect: [5, { r: true, i: 'hi' }] + }))) + + describe('op1 insert containing a drop', () => + it('vs pick at insert', () => + compose({ + op1: [['x', { p: 0 }], ['y', { i: {} }, 'x', { d: 0 }]], + op2: [['y', { p: 0 }], ['z', { d: 0 }]], + expect: [['x', { p: 0 }], ['z', { i: {} }, 'x', { d: 0 }]] + }))) + + describe('fuzzer tests', () => + it('complicated transform of indicies', () => + compose({ + op1: [0, { p: 0 }, 'x', 2, { d: 0 }], + op2: [0, 'x', 0, { r: true }], + expect: [[0, { p: 0 }, 'x', 1, { d: 0 }], [1, 'x', 0, { r: true }]] + }))) + + describe('setnull interaction', () => { + // Currently failing. + it('reorders items inside a setnull region', () => + compose({ + op1: [{ i: [] }, [0, { i: 'a' }], [1, { i: 'b' }]], + op2: [[0, { p: 0 }], [1, { d: 0 }]], + expect: [{ i: [] }, [0, { i: 'b' }], [1, { i: 'a' }]] + })) + + it('lets a setnull child be moved', () => + compose({ + op1: ['list', { i: [] }, 0, { i: 'hi' }], + op2: [['list', 0, { p: 0 }], ['z', { d: 0 }]], + expect: [['list', { i: [] }], ['z', { i: 'hi' }]] + })) + + it('lets a setnull child get modified', () => + compose({ + op1: [{ i: [] }, 0, { i: ['a'] }], + op2: [0, 0, { r: 'a', i: 'b' }], + expect: [{ i: [] }, 0, { i: [] }, 0, { i: 'b' }] + })) + }) + //expect: [{i:[]}, 0, {i:['b']}] # Maybe better?? + + describe('regression', () => { + it('skips op2 drops when calculating op1 drop index simple', () => + compose({ + op1: [[0, { p: 0 }], [2, { d: 0 }]], + op2: [[0, { p: 0 }], [1, { d: 0 }]], + expect: [[0, { p: 1 }], [1, { p: 0, d: 0 }], [2, { d: 1 }]] + })) + + it('skips op2 drops when calculating op1 drop index complex', () => + compose({ + op1: [[0, { p: 0, d: 1 }], [1, { p: 1 }], [2, { d: 0 }]], + op2: [[0, { p: 0 }], [1, { d: 0 }]], + // expect: [[0, {p:1}], [1, {d:0, p:0}], [2, d:1]] + expect: [[0, { p: 1 }], [1, { p: 0, d: 0 }], [2, { d: 1 }]] + })) + + it('3', () => + compose({ + op1: [{ i: [null, []] }, 0, { i: '' }], + op2: [1, { p: 0 }, 0, { d: 0 }], + // ... it'd be way more consistent to drop the null separately rather than merging it?? + expect: [{ i: [[]] }, [0, { i: '' }], [1, 0, { i: null }]] + })) + + it('4', () => + compose({ + // This one triggered a bug in cursor! + op1: [0, [0, ['a', { r: true }], ['b', { d: 0 }]], [2, { p: 0 }]], + op2: [0, 0, 'c', { i: 'd' }], + expect: [ + 0, + [0, ['a', { r: true }], ['b', { d: 0 }], ['c', { i: 'd' }]], + [2, { p: 0 }] + ] + })) + }) + }) + + // *** Old stuff + describe('old compose', () => { + it('gloms together unrelated edits', () => { + compose({ + op1: [['a', { p: 0 }], ['b', { d: 0 }]], + op2: [['x', { p: 0 }], ['y', { d: 0 }]], + expect: [ + ['a', { p: 0 }], + ['b', { d: 0 }], + ['x', { p: 1 }], + ['y', { d: 1 }] + ] + }) + + compose({ + op1: [2, { i: 'hi' }], + op2: [0, 'x', { r: true }], + expect: [[0, 'x', { r: true }], [2, { i: 'hi' }]] + }) + }) + + it('translates drops in objects', () => + compose({ + op1: ['x', ['a', { p: 0 }], ['b', { d: 0 }]], // x.a -> x.b + op2: [['x', { p: 0 }], ['y', { d: 0 }]], // x -> y + expect: [['x', { p: 0 }, 'a', { p: 1 }], ['y', { d: 0 }, 'b', { d: 1 }]] + })) // x.a -> y.b, x -> y + + it('untranslates picks in objects', () => + compose({ + op1: [['x', { p: 0 }], ['y', { d: 0 }]], // x -> y + op2: [['y', 'a', { p: 0 }], ['z', { d: 0 }]], // y.a -> z + expect: [ + ['x', { p: 0 }, 'a', { p: 1 }], + ['y', { d: 0 }], + ['z', { d: 1 }] + ] + })) // x.a -> z, x -> y + + it('insert gets carried wholesale', () => + compose({ + op1: ['x', { i: 'hi there' }], + op2: [['x', { p: 0 }], ['y', { d: 0 }]], // x -> y + expect: ['y', { i: 'hi there' }] + })) + + it('insert gets edited by the op', () => + compose({ + op1: ['x', { i: { a: 1, b: 2, c: 3 } }], + op2: [['x', 'a', { p: 0 }], ['y', { d: 0 }]], + expect: [['x', { i: { b: 2, c: 3 } }], ['y', { i: 1 }]] + })) + + it('does not merge mutual inserts', () => + compose({ + op1: [{ i: {} }], + op2: ['x', { i: 'hi' }], + expect: [{ i: {} }, 'x', { i: 'hi' }] + })) + }) + + // TODO: List nonsense. + + // TODO: Edits. + + // ****** Transform ****** + + describe('transform', () => { + describe('op1 pick', () => { + it('vs delete', () => + xf({ + op1: [['x', { p: 0 }], ['y', { d: 0 }]], + op2: ['x', { r: true }], + expect: null + })) + it('vs delete parent', () => + xf({ + op1: [['x', 'a', { p: 0 }], ['y', { d: 0 }]], + op2: ['x', { r: true }], + expect: null + })) + it('vs delete parent 2', () => + xf({ + op1: ['x', ['a', { p: 0 }], ['b', { d: 0 }]], + op2: ['x', { r: true }], + expect: null + })) + + it('vs pick', () => + xf({ + op1: [['x', { p: 0 }], ['z', { d: 0 }]], + op2: [['x', { p: 0 }], ['y', { d: 0 }]], + // Consider adding a conflict for this case. + expectLeft: [['y', { p: 0 }], ['z', { d: 0 }]], + expectRight: null + })) + it('vs pick parent', () => + xf({ + op1: [['x', 'a', { p: 0 }], ['z', { d: 0 }]], + op2: [['x', { p: 0 }], ['y', { d: 0 }]], + expect: [['y', 'a', { p: 0 }], ['z', { d: 0 }]] + })) + + it('vs pick and pick child', () => + xf({ + // regression + op1: [ + // a -> xa, a.c -> xc + ['a', { p: 0 }, 'c', { p: 1 }], + ['xa', { d: 0 }], + ['xc', { d: 1 }] + ], + op2: [['a', { p: 0 }], ['b', { d: 0 }]], // a -> b + expectLeft: [ + ['b', { p: 0 }, 'c', { p: 1 }], + ['xa', { d: 0 }], + ['xc', { d: 1 }] + ], + expectRight: [['b', 'c', { p: 0 }], ['xc', { d: 0 }]] + })) + + it('vs edit', () => + xf({ + op1: [['x', { p: 0 }], ['z', { d: 0 }]], + op2: ['x', { es: ['hi'] }], + expect: [['x', { p: 0 }], ['z', { d: 0 }]] + })) + + it('vs delete, drop', () => + xf({ + op1: [['x', { p: 0 }], ['y', { d: 0 }]], + op2: [['a', { p: 0 }], ['x', { r: 0, d: 0 }]], + expect: null + })) + + it('vs delete, insert', () => + xf({ + op1: [['x', { p: 0 }], ['y', { d: 0 }]], + op2: ['x', { r: 0, i: 5 }], + expect: null + })) + + it( + 'vs pick, drop to self', + () => + xf({ + op1: [['x', { p: 0 }], ['y', { d: 0 }]], + op2: [['x', { p: 0 }], ['y', { d: 0 }]], + expect: null + }), + + () => + xf({ + op1: [['a', 1, { p: 0 }], ['y', { d: 0 }]], + op2: [['a', 1, { p: 0 }], ['y', { d: 0 }]], + expect: null + }) + ) + + it('vs pick, drop', () => + xf({ + op1: [['x', { p: 0 }], ['z', { d: 0 }]], // x->z + op2: [['a', { p: 0 }], ['x', { p: 1, d: 0 }], ['y', { d: 1 }]], // a->x, x->y + expectLeft: [['y', { p: 0 }], ['z', { d: 0 }]], + expectRight: null + })) + + it('vs pick, insert', () => + xf({ + op1: [['x', { p: 0 }], ['z', { d: 0 }]], + op2: [['x', { p: 0, i: 5 }], ['y', { d: 0 }]], + expectLeft: [['y', { p: 0 }], ['z', { d: 0 }]], + expectRight: null + })) + + it('vs pick, edit', () => ({ + op1: [['x', { p: 0 }], ['z', { d: 0 }]], + op2: [['x', { es: ['hi'], p: 0 }], ['y', { d: 0 }]], + expectLeft: [['y', { p: 0 }], ['z', { d: 0 }]], + expectRight: null + })) + }) + + describe('op1 delete', () => { + it('vs delete', () => + xf({ + op1: ['x', { r: true }], + op2: ['x', { r: true }], + expect: null + })) + it('vs delete parent', () => + xf({ + op1: ['x', 'a', { r: true }], + op2: ['x', { r: true }], + expect: null + })) + + it('vs pick', () => + xf({ + op1: ['x', { r: true }], + op2: [['x', { p: 0 }], ['y', { d: 0 }]], + expect: ['y', { r: true }] + })) + it('vs pick parent', () => + xf({ + op1: ['x', 'a', { r: true }], + op2: [['x', { p: 0 }], ['y', { d: 0 }]], + expect: ['y', 'a', { r: true }] + })) + + it('vs pick and drop', () => + xf({ + op1: ['x', { r: true }], + op2: [['a', { p: 0 }], ['x', { d: 0, p: 1 }], ['z', { d: 1 }]], + expect: ['z', { r: true }] + })) + + it('vs edit', () => + xf({ + op1: ['x', { r: true }], + op2: ['x', { es: ['hi'] }], + conflict: { type: RM_UNEXPECTED_CONTENT }, + expect: ['x', { r: true }] + })) + + it('vs move and insert', () => + xf({ + op1: ['a', 1, { r: true }], + op2: [['a', { p: 0 }], ['b', { d: 0 }, [0, { i: 5 }], [1, { i: 5 }]]], + expect: ['b', 3, { r: true }] + })) + + return describe('vs pick child', function() { + it('move in', () => + xf({ + op1: ['x', { r: true }], + op2: [['a', { p: 0 }], ['x', 'y', { d: 0 }]], + conflict: { type: RM_UNEXPECTED_CONTENT }, + expect: ['x', { r: true }, 'y', { r: true }] + })) // Also ok if its just x, r:true + + it('move across', () => + xf({ + op1: ['x', { r: true }], // delete doc.x + op2: ['x', ['y', { p: 0 }], ['z', { d: 0 }]], + expect: ['x', { r: true }] + })) + + it('move out', () => + xf({ + op1: ['x', { r: true }], + op2: [['x', 'y', { p: 0 }], ['y', { d: 0 }]], // move doc.x.y -> doc.y + expect: [['x', { r: true }], ['y', { r: true }]] + })) // delete doc.x and doc.y + + it('multiple out', () => + xf({ + op1: ['x', { r: true }], + op2: [ + ['x', 'y', { p: 0 }, 'z', { p: 1 }], + ['y', { d: 0 }], + ['z', { d: 1 }] + ], + expect: [['x', { r: true }], ['y', { r: true }], ['z', { r: true }]] + })) + + it('chain out', () => + xf({ + op1: ['x', { r: true }], + op2: [ + ['x', 'y', { p: 0 }], + ['y', { p: 1 }], + ['z', { d: 0 }, 'a', { d: 1 }] + ], + conflict: { + type: RM_UNEXPECTED_CONTENT, + op2: [['y', { p: 0 }], ['z', 'a', { d: 0 }]] + }, // cMv(['y'], ['z', 'a']) + expect: [['x', { r: true }], ['z', { r: true }, 'a', { r: true }]] + })) + + it('mess', () => + xf({ + // yeesh + op1: [['x', { r: true }, 'y', 'z', { p: 0 }], ['z', { d: 0 }]], + op2: [['x', 'y', { p: 0 }], ['y', { d: 0 }]], + expect: [ + ['x', { r: true }], + ['y', { r: true }, 'z', { p: 0 }], + ['z', { d: 0 }] + ] + })) + }) + }) + + describe('op1 drop', () => { + it('vs delete parent', () => + xf({ + op1: [['x', { p: 0 }], ['y', 'a', { d: 0 }]], + op2: ['y', { r: true }], + conflict: { type: RM_UNEXPECTED_CONTENT }, + expect: ['x', { r: true }] + })) + + it('vs a cancelled parent', () => + xf({ + // This is actually a really complicated case. + op1: [ + ['x', 'y', { p: 0 }], + ['y', { p: 1 }], + ['z', { d: 0 }, 'a', { d: 1 }] + ], + op2: ['x', { r: true }], + conflict: { + type: RM_UNEXPECTED_CONTENT, + op1: [['y', { p: 0 }], ['z', 'a', { d: 0 }]] + }, // c1: cMv(['y'], ['z', 'a']) + expect: ['y', { r: true }] + })) + + it('vs pick parent', () => + xf({ + op1: [['x', { p: 0 }], ['y', 'a', { d: 0 }]], + op2: [['y', { p: 0 }], ['z', { d: 0 }]], + expect: [['x', { p: 0 }], ['z', 'a', { d: 0 }]] + })) + + it('vs drop', () => + xf({ + op1: [['x', { p: 0 }], ['z', { d: 0 }]], + op2: [['y', { p: 0 }], ['z', { d: 0 }]], + conflict: { type: DROP_COLLISION }, + expectLeft: [['x', { p: 0 }], ['z', { r: true, d: 0 }]], + expectRight: ['x', { r: true }] + })) + + it('vs drop (list)', () => + xf({ + op1: [[0, { p: 0 }], [4, { d: 0 }]], + op2: [[5, { d: 0 }], [10, { p: 0 }]], + expectLeft: [[0, { p: 0 }], [4, { d: 0 }]], + expectRight: [[0, { p: 0 }], [5, { d: 0 }]] + })) + + it('vs drop (chained)', () => + xf({ + op1: [ + ['a', { p: 1 }], + ['x', { p: 0 }], + ['z', { d: 0 }, 'a', { d: 1 }] + ], + op2: [['y', { p: 0 }], ['z', { d: 0 }]], + conflict: { + type: DROP_COLLISION, + op1: [['x', { p: 0 }], ['z', { d: 0 }]] + }, //cMv(['x'], ['z']) + expectLeft: [ + ['a', { p: 0 }], + ['x', { p: 1 }], + ['z', { r: true, d: 1 }, 'a', { d: 0 }] + ], + expectRight: [['a', { r: true }], ['x', { r: true }]] + })) + + it('vs insert', () => + xf({ + op1: [['x', { p: 0 }], ['z', { d: 0 }]], + op2: ['z', { i: 5 }], + conflict: { type: DROP_COLLISION }, + expectLeft: [['x', { p: 0 }], ['z', { r: true, d: 0 }]], + expectRight: ['x', { r: true }] + })) + + it('vs pick (a->b->c vs b->x)', () => + xf({ + op1: [['a', { p: 0 }], ['b', { p: 1, d: 0 }], ['c', { d: 1 }]], + op2: [['b', { p: 0 }], ['x', { d: 0 }]], + expectLeft: [ + ['a', { p: 0 }], + ['b', { d: 0 }], + ['c', { d: 1 }], + ['x', { p: 1 }] + ], + expectRight: [['a', { p: 0 }], ['b', { d: 0 }]] + })) + + return describe.skip('vs move inside me', function() { + // Note: This is *not* blackholeing! The edits are totally fine; we + // just need one edit to win. + // The current behaviour just nukes both. + it('in objects', () => + xf({ + op1: [['x', { p: 0 }], ['y', 'a', { d: 0 }]], + op2: [['x', 'a', { d: 0 }], ['y', { p: 0 }]], + expectLeft: [ + ['x', { p: 0 }, 'a', { p: 1 }], + ['y', { d: 1 }, 'x', { d: 0 }] + ], + expectRight: null + })) + + it('in lists', () => + xf({ + op1: [0, { p: 0 }, 'x', { d: 0 }], + op2: [[0, 'y', { d: 0 }], [1, { p: 0 }]], + expectLeft: [0, { p: 0, d: 1 }, ['x', { d: 0 }], ['y', { p: 1 }]], + expectRight: null + })) + + it('multiple', () => + xf({ + // a->x.a, b->x.b + op1: [ + ['a', { p: 0 }], + ['b', { p: 1 }], + ['x', 'a', { d: 0 }, 'b', { d: 1 }] + ], + op2: [['a', 'x', { d: 0 }], ['x', { p: 0 }]], // x->a.x + expectLeft: [ + ['a', { p: 0 }, 'x', { p: 1 }], + ['b', { p: 2 }], + ['x', { d: 1 }, ['a', { d: 0 }], ['b', { d: 2 }]] + ], + expectRight: null + })) + }) + }) + + describe('op1 insert', () => { + it('vs delete parent', () => + xf({ + op1: ['y', 'a', { i: 5 }], + op2: ['y', { r: true }], + conflict: { type: RM_UNEXPECTED_CONTENT }, + expect: null + })) + + it('vs pick parent', () => + xf({ + op1: ['y', 'a', { i: 5 }], + op2: [['y', { p: 0 }], ['z', { d: 0 }]], + expect: ['z', 'a', { i: 5 }] + })) + + it('vs drop', () => + xf({ + op1: ['z', { i: 5 }], + op2: [['y', { p: 0 }], ['z', { d: 0 }]], + conflict: { type: DROP_COLLISION }, + expectLeft: ['z', { r: true, i: 5 }], + expectRight: null + })) + + it('vs insert', () => + xf({ + op1: ['z', { i: 5 }], + op2: ['z', { i: 10 }], + conflict: { type: DROP_COLLISION }, + expectLeft: ['z', { r: true, i: 5 }], + expectRight: null + })) + + it('vs insert at list position', () => + xf({ + op1: [5, { i: 'hi' }], + op2: [5, { i: 'there' }], + expectLeft: [5, { i: 'hi' }], + expectRight: [6, { i: 'hi' }] + })) + + it('vs identical insert', () => + xf({ + op1: ['z', { i: 5 }], + op2: ['z', { i: 5 }], + expect: null + })) + + // This is the new setNull for setting up schemas + it('vs embedded inserts', function() { + xf({ + op1: ['x', { i: {} }], + op2: ['x', { i: {} }, 'y', { i: 5 }], + expect: null + }) + + xf({ + op1: ['x', { i: {} }, 'y', { i: 5 }], + op2: ['x', { i: {} }], + expect: ['x', 'y', { i: 5 }] + }) + + xf({ + op1: ['x', { i: {} }, 'y', { i: 5 }], + op2: ['x', { i: {} }, 'y', { i: 5 }], + expect: null + }) + + return xf({ + op1: ['x', { i: {} }, 'y', { i: 5 }], + op2: ['x', { i: {} }, 'y', { i: 6 }], + conflict: { + type: DROP_COLLISION, + op1: ['x', 'y', { i: 5 }], + op2: ['x', 'y', { i: 6 }] + }, + expectLeft: ['x', 'y', { r: true, i: 5 }], + expectRight: null + }) + }) + + it('with embedded edits', () => + xf({ + op1: [{ i: '', es: ['aaa'] }], + op2: [{ i: '', es: ['bbb'] }], + expectLeft: [{ es: ['aaa'] }], + expectRight: [{ es: [3, 'aaa'] }] + })) + }) + + describe('op1 edit', () => { + it('vs delete', () => + xf({ + op1: ['x', { es: ['hi'] }], + op2: ['x', { r: true }], + conflict: { type: RM_UNEXPECTED_CONTENT }, + expect: null + })) + + it('vs delete parent', () => + xf({ + op1: ['x', 'y', { es: ['hi'] }], + op2: ['x', { r: true }], + conflict: { type: RM_UNEXPECTED_CONTENT }, + expect: null + })) + + it('vs pick', () => + xf({ + op1: ['x', { es: ['hi'] }], + op2: [['x', { p: 0 }], ['y', { d: 0 }]], + expect: ['y', { es: ['hi'] }] + })) + + it('vs edit string', () => + xf({ + op1: ['x', { es: ['ab'] }], + op2: ['x', { es: ['cd'] }], + expectLeft: ['x', { es: ['ab'] }], + expectRight: ['x', { es: [2, 'ab'] }] + })) + + it('vs edit number', () => + xf({ + op1: [{ ena: 5 }], + op2: [{ ena: 100 }], + expect: [{ ena: 5 }] + })) + + it('throws if edit types arent compatible', () => + assert.throws(() => type.transform([{ es: ['hi'] }], [{ ena: 5 }], 'left'))) + + it('vs move and edit', () => + xf({ + op1: ['x', { es: [1, 'ab'] }], + op2: [['x', { p: 0 }], ['y', { d: 0, es: [{ d: 1 }, 'cd'] }]], + expectLeft: ['y', { es: ['ab'] }], + expectRight: ['y', { es: [2, 'ab'] }] + })) + + it('throws if the type is missing', () => + assert.throws(() => + type.transform( + [{ et: 'missing', e: {} }], + [{ et: 'missing', e: {} }], + 'left' + ) + )) + }) + + describe('op2 cancel move', () => { + it('and insert', () => + xf({ + op1: ['x', { r: true }], + op2: [['x', 'a', { p: 0 }], ['y', { d: 0 }, 'b', { i: 5 }]], + conflict: { + type: RM_UNEXPECTED_CONTENT, + op2: ['y', 'b', { i: 5 }] + }, + expect: [['x', { r: true }], ['y', { r: true }, 'b', { r: true }]] + })) + + it('and another move (rm x vs x.a -> y, q -> y.b)', () => + xf({ + op1: ['x', { r: true }], + op2: [ + ['q', { p: 1 }], + ['x', 'a', { p: 0 }], + ['y', { d: 0 }, 'b', { d: 1 }] + ], + conflict: { + type: RM_UNEXPECTED_CONTENT, + op2: [['q', { p: 0 }], ['y', 'b', { d: 0 }]] + }, + expect: [['x', { r: true }], ['y', { r: true }, 'b', { r: true }]] + })) + }) + + describe('op2 list move an op1 drop', () => { + it('vs op1 remove', () => + xf({ + op1: [[0, { r: true }, 'a', { i: 'hi' }], [5, { r: true }]], + op2: [[1, { p: 0 }], [4, { d: 0 }]], + expect: [[0, { r: true }], [3, 'a', { i: 'hi' }], [5, { r: true }]] + })) + + it('vs op1 remove 2', () => + xf({ + op1: [ + [0, { r: true }, 'a', { i: 'hi' }], + [1, { r: true }], + [2, { r: true }] + ], + op2: [[3, { p: 0 }], [4, { d: 0 }]], + expect: [ + [0, { r: true }], + [1, { r: true }, 'a', { i: 'hi' }], + [2, { r: true }] + ] + })) + + it('vs op1 insert before', () => + xf({ + op1: [[0, { i: 'a' }], [1, { i: 'b' }], [2, 'a', { i: 'hi' }]], + op2: [[0, { p: 0 }], [1, { d: 0 }]], + expect: [[0, { i: 'a' }], [1, { i: 'b' }], [3, 'a', { i: 'hi' }]] + })) + + it('vs op1 insert before and replace', () => + xf({ + op1: [[0, { i: 'xx' }, 'a', { r: true }], [1, 'a', { i: 'hi' }]], + op2: [[0, { p: 0 }], [3, { d: 0 }]], + expect: [ + [0, { i: 'xx' }], + [3, 'a', { r: true }], + [4, 'a', { i: 'hi' }] + ] + })) + }) + + describe('list', () => + describe('drop', () => { + it('transforms by p1 drops', () => + xf({ + op1: [[5, { i: 5 }], [10, { i: 10 }]], + op2: [9, { i: 9 }], + expectLeft: [[5, { i: 5 }], [10, { i: 10 }]], + expectRight: [[5, { i: 5 }], [11, { i: 10 }]] + })) + + it('transforms by p1 picks') + it('transforms by p2 picks') + it('transforms by p2 drops') + })) + }) + + describe('conflicts', () => { + describe('drop into remove / rm unexpected', () => { + // xfConflict does both xf(op1, op2, left) and xf(op2, op1, right), and + // uses invConflict. So this also tests RM_UNEXPECTED_CONTENT with each + // test case. + it('errors if you insert', () => + xf({ + op1: ['a', 'b', { i: 5 }], + op2: ['a', { r: true }], + conflict: { type: RM_UNEXPECTED_CONTENT }, + expect: null + })) + + it('errors if you drop', () => + xf({ + op1: [['a', { p: 0 }], ['x', 'b', { d: 0 }]], + op2: ['x', { r: true }], + conflict: { type: RM_UNEXPECTED_CONTENT }, + expect: ['a', { r: true }] + })) + + it('errors if you rm then insert in a child', () => + xf({ + op1: ['a', 'b', { r: true, i: 5 }], + op2: ['a', { r: true }], + conflict: { + type: RM_UNEXPECTED_CONTENT, + op1: ['a', 'b', { i: 5 }] + }, + expect: null + })) + + it('errors if the object is replaced', () => + xf({ + op1: ['a', 'b', { i: 5 }], + op2: ['a', { r: true, i: 10 }], + conflict: { + type: RM_UNEXPECTED_CONTENT, + op2: ['a', { r: true }] + }, + expect: null + })) + + it('handles a delete of the source parent by op2', () => + xf({ + op1: [['a', { p: 0 }], ['b', 'b', { d: 0 }]], + op2: [['a', { p: 0 }], ['b', { r: true }, 'c', { d: 0 }]], + conflictLeft: { + type: RM_UNEXPECTED_CONTENT, + op2: ['b', { r: true }] + }, + expectLeft: ['b', 'c', { r: true }], + expectRight: null + })) + + it.skip('returns symmetric errors when both ops delete the other', () => + xf({ + // The problem here is that there's two conflicts we want to return. + // Which one should be returned first? It'd be nice for the order of + // conflict returning to be symmetric - that is, if we know multiple + // conflicts happen, order them based on left/right. But I haven't done + // that, so we get different conflicts out of this in a first pass. + op1: [['x', { r: true }], ['y', 'a', { i: {} }]], + op2: [['x', 'a', { i: {} }], ['y', { r: true }]], + conflict: { type: RM_UNEXPECTED_CONTENT }, + expect: ['x', { r: true }] + })) + }) + + describe('overlapping drop', () => { + it('errors if two ops insert different content into the same place in an object', () => + xf({ + op1: ['x', { i: 'hi' }], + op2: ['x', { i: 'yo' }], + conflict: { type: DROP_COLLISION }, + expectLeft: ['x', { r: true, i: 'hi' }], + expectRight: null + })) + + it('does not conflict if inserts are identical', () => + xf({ + op1: ['x', { i: 'hi' }], + op2: ['x', { i: 'hi' }], + expectLeft: null, + expectRight: null + })) + + it('does not conflict if the two operations make identical moves', () => + xf({ + op1: [['a', { p: 0 }], ['x', { d: 0 }]], + op2: [['a', { p: 0 }], ['x', { d: 0 }]], + expect: null + })) // ??? Also ok for left: ['x', p:0, d:0] + + it('does not conflict if inserts are into a list', () => + xf({ + op1: [1, { i: 'hi' }], + op2: [1, { i: 'yo' }], + expectLeft: [1, { i: 'hi' }], + expectRight: [2, { i: 'hi' }] + })) + + it('errors if the inserts are at the root', () => + xf({ + op1: [{ i: 1 }], + op2: [{ i: 2 }], + conflict: { type: DROP_COLLISION }, + expectLeft: [{ r: true, i: 1 }], + expectRight: null + })) + + it('errors with insert vs drop', () => + xf({ + op1: ['x', { i: 'hi' }], + op2: [['a', { p: 0 }], ['x', { d: 0 }]], + // ???? + conflict: { type: DROP_COLLISION }, + expectLeft: ['x', { r: true, i: 'hi' }], + expectRight: null + })) + + it('errors with drop vs insert', () => + xf({ + op1: [['a', { p: 0 }], ['x', { d: 0 }]], + op2: ['x', { i: 'hi' }], + conflict: { type: DROP_COLLISION }, + expectLeft: [['a', { p: 0 }], ['x', { r: true, d: 0 }]], + expectRight: ['a', { r: true }] + })) + + it('errors with drop vs drop', () => + xf({ + op1: [['a', { p: 0 }], ['x', { d: 0 }]], + op2: [['b', { p: 0 }], ['x', { d: 0 }]], + conflict: { type: DROP_COLLISION }, + expectLeft: [['a', { p: 0 }], ['x', { r: true, d: 0 }]], + expectRight: ['a', { r: true }] + })) + + it('errors if the two sides insert in the vacuum', () => + xf({ + op1: [['a', { p: 0 }], ['b', { d: 0 }], ['c', { i: 5 }]], + op2: [['a', { p: 0 }], ['b', { i: 6 }], ['c', { d: 0 }]], + conflictLeft: { + type: DROP_COLLISION, + op1: [['a', { p: 0 }], ['b', { d: 0 }]], + op2: ['b', { i: 6 }] + }, + expectLeft: [['b', { r: true, d: 0 }], ['c', { p: 0, i: 5 }]], + conflictRight: { + type: DROP_COLLISION, + op1: ['c', { i: 5 }], + op2: [['a', { p: 0 }], ['c', { d: 0 }]] + }, + expectRight: null + })) + }) + + describe('discarded edit', () => { + it('conflicts with a no-op', () => // regression. + xf({ + op1: ['a', { es: [] }], + op2: ['a', { r: true }], + conflict: { type: RM_UNEXPECTED_CONTENT }, + expect: null + })) + + it('edit removed directly', () => + xf({ + op1: ['a', { es: ['hi'] }], + op2: ['a', { r: true }], + conflict: { type: RM_UNEXPECTED_CONTENT }, + expect: null + })) + + it('edit inside new content throws RM_UNEXPECTED_CONTENT', () => + xf({ + op1: ['a', 'b', { i: 'hi', es: ['hi'] }], + op2: ['a', { r: true }], + conflict: { + type: RM_UNEXPECTED_CONTENT, + op1: ['a', 'b', { i: 'hi' }] + }, + expect: null + })) + }) + + describe('blackhole', () => { + it('detects and errors', () => + xf({ + op1: [['x', { p: 0 }], ['y', 'a', { d: 0 }]], + op2: [['x', 'a', { d: 0 }], ['y', { p: 0 }]], + conflict: { type: BLACKHOLE }, + expect: ['x', { r: true }, 'a', { r: true }] + })) // Also equivalent: ['x', r:true] + + it('blackhole logic does not apply when op2 removes parent', () => + xf({ + // TODO: Although you wouldn't know it, since this result is very similar. + op1: [['x', { p: 0 }], ['y', 'xx', 'a', { d: 0 }]], + op2: [['x', 'a', { d: 0 }], ['y', { p: 0 }, 'xx', { r: true }]], + conflict: { + type: RM_UNEXPECTED_CONTENT, + op2: ['y', 'xx', { r: true }] + }, + expect: ['x', { r: true }, 'a', { r: true }] + })) // Also ok: ['x', r:true] + + it('blackhole logic still applies when op2 inserts', () => + xf({ + op1: [['x', { p: 0 }], ['y', 'a', { i: {} }, 'b', { d: 0 }]], + op2: [['x', 'a', { i: {} }, 'b', { d: 0 }], ['y', { p: 0 }]], + conflict: { + type: BLACKHOLE, + op1: [['x', { p: 0 }], ['y', 'a', 'b', { d: 0 }]], + op2: [['x', 'a', 'b', { d: 0 }], ['y', { p: 0 }]] + }, + expect: ['x', { r: true }, 'a', { r: true }, 'b', { r: true }] + })) + + it('blackholes items in lists correctly', () => + xf({ + op1: [1, { p: 0 }, 'a', { d: 0 }], + op2: [[1, 'b', { d: 0 }], [2, { p: 0 }]], + conflict: { type: BLACKHOLE }, + expect: [1, { r: true }, 'b', { r: true }] + })) + + it('blackholes items despite scrambled pick and drop slots', () => + xf({ + op1: [['a', { p: 1, d: 1 }], ['x', { p: 0 }], ['y', 'a', { d: 0 }]], + op2: [['x', 'a', { d: 0 }], ['y', { p: 0 }]], + conflict: { + type: BLACKHOLE, + op1: [['x', { p: 0 }], ['y', 'a', { d: 0 }]] + }, + expect: [['a', { p: 0, d: 0 }], ['x', { r: true }, 'a', { r: true }]] + })) + + it('handles chained blackholes', () => + xf({ + op1: [ + ['a', { p: 0 }], // a->b.b, c->d.d + ['b', 'b', { d: 0 }], + ['c', { p: 1 }], + ['d', 'd', { d: 1 }] + ], + op2: [ + ['a', 'a', { d: 1 }], // b->c.c, d->a.a + ['b', { p: 0 }], + ['c', 'c', { d: 0 }], + ['d', { p: 1 }] + ], + conflict: { type: BLACKHOLE }, + // c1: cMv(['a'], ['b', 'b']) + // c2: cMv(['b'], ['c', 'c']) + expect: [ + ['a', { r: true }, 'a', { r: true }], + ['c', { r: true }, 'c', { r: true }] + ] + })) + + it('creates conflict return values with valid slot ids', () => + xf({ + op1: [ + ['a', { p: 0 }], + ['b', { d: 0 }], + ['x', { p: 1 }], + ['y', 'a', { d: 1 }] + ], + op2: [['x', 'a', { d: 0 }], ['y', { p: 0 }]], + conflict: { + type: BLACKHOLE, + op1: [['x', { p: 0 }], ['y', 'a', { d: 0 }]] + }, + expect: [ + ['a', { p: 0 }], + ['b', { d: 0 }], + ['x', { r: true }, 'a', { r: true }] + ] + })) + }) + }) + + describe('transform-old', () => { + it('foo', () => + xf({ + op1: [ + ['x', ['a', { p: 0 }], ['b', { d: 0 }]], + ['y', ['a', { p: 1 }], ['b', { d: 1 }]] + ], + op2: ['x', { r: true }], + expect: ['y', ['a', { p: 0 }], ['b', { d: 0 }]] + })) + + // it 'hard', -> + // op1: ['x', [1, r:true], [2, r:true, es:['hi']]] # Edit at index 4 originally. + // # move the edited string to .y[4] which + // op2: [['x', 4, p:0], ['y', [2, r:true], [4, d:0]]] + // expect: + + describe('object edits', () => + it('can reparent with some extra junk', () => + xf({ + op1: [['x', { p: 0 }], ['y', { d: 0 }]], + op2: [ + ['_a', { d: 1 }], + ['_x', { d: 0 }], + ['x', { p: 0 }, 'a', { p: 1 }] + ], + expectLeft: [['_x', { p: 0 }], ['y', { d: 0 }]], + expectRight: null + }))) // the object was moved fair and square. + + describe('deletes', () => { + it.skip('delete parent of a move', () => + xf({ + // The current logic of transform actually just burns everything (in a + // consistant way of course). I'm not sure if this is better or worse - + // basically we'd be saying that if a move could end up in one of two places, + // put it in the place where it won't be killed forever. But that introduces new + // complexity, so I'm going to skip this for now. + + // x.a -> a, delete x + op1: [['x', { r: true }, 'a', { p: 0 }], ['z', { d: 0 }]], + // x.a -> x.b. + op2: ['x', ['a', { p: 0 }], ['b', { d: 0 }]], + expect: [['x', { r: true }, 'b', { p: 0 }], ['z', { d: 0 }]] + })) // TODO: It would be better to do this in both cases. + //expectRight: ['x', r:true] + + it('awful delete nonsense', () => { + xf({ + op1: [['x', { r: true }], ['y', { i: 'hi' }]], // delete doc.x, insert doc.y + op2: [['x', 'a', { p: 0 }], ['y', { d: 0 }]], // move doc.x.a -> doc.y + expect: [['x', { r: true }], ['y', { r: true, i: 'hi' }]] + }) // del doc.x and doc.y, insert doc.y + + xf({ + op1: [['x', 'a', { p: 0 }], ['y', { d: 0 }]], // x.a -> y + op2: [['x', { r: true }], ['y', { i: 'hi' }]], // delete x, ins y + expect: null + }) + + xf({ + op1: [10, { r: true }], + op2: [[5, { d: 0 }], [10, 1, { p: 0 }]], + expect: [[5, { r: true }], [11, { r: true }]] + }) + }) + }) + // And how do those indexes interact with pick / drop operations?? + + describe('swap', () => { + const swap = [ + ['a', { p: 0 }, 'b', { p: 1 }], + ['b', { d: 1 }, 'a', { d: 0 }] + ] + + it('noop vs swap', () => + xf({ + op1: null, + op2: swap, + expect: null + })) + + it('can swap two edits', () => + xf({ + op1: ['a', { es: ['a edit'] }, 'b', { es: ['b edit'] }], + op2: swap, + expect: ['b', { es: ['b edit'] }, 'a', { es: ['a edit'] }] + })) + }) + + describe('lists', () => { + it('can rewrite simple list indexes', () => { + xf({ + op1: [10, { es: ['edit'] }], + op2: [0, { i: 'oh hi' }], + expect: [11, { es: ['edit'] }] + }) + + xf({ + op1: [10, { r: true }], + op2: [0, { i: 'oh hi' }], + expect: [11, { r: true }] + }) + + xf({ + op1: [10, { i: {} }], + op2: [0, { i: 'oh hi' }], + expect: [11, { i: {} }] + }) + }) + + it('can change the root from an object to a list', () => + xf({ + op1: ['a', { es: ['hi'] }], + op2: [{ i: [], r: true }, [0, { d: 0 }], ['a', { p: 0 }]], + expect: [0, { es: ['hi'] }] + })) + + it('can handle adjacent drops', () => + xf({ + op1: [[11, { i: 1 }], [12, { i: 2 }], [13, { i: 3 }]], + op2: [0, { r: true }], + expect: [[10, { i: 1 }], [11, { i: 2 }], [12, { i: 3 }]] + })) + + it('fixes drop indexes correctly 1', () => + xf({ + op1: [[0, { r: true }], [1, { i: 'hi' }]], + op2: [1, { r: true }], + expect: [0, { r: true, i: 'hi' }] + })) + + it('list drop vs delete uses the correct result index', () => { + xf({ + op1: [2, { i: 'hi' }], + op2: [2, { r: true }], + expect: [2, { i: 'hi' }] + }) + + xf({ + op1: [3, { i: 'hi' }], + op2: [2, { r: true }], + expect: [2, { i: 'hi' }] + }) + }) + + it('list drop vs drop uses the correct result index', () => + xf({ + op1: [2, { i: 'hi' }], + op2: [2, { i: 'other' }], + expectLeft: [2, { i: 'hi' }], + expectRight: [3, { i: 'hi' }] + })) + + it('list drop vs delete and drop', () => { + xf({ + op1: [2, { i: 'hi' }], + op2: [2, { r: true, i: 'other' }], + expectLeft: [2, { i: 'hi' }], + expectRight: [3, { i: 'hi' }] + }) + + xf({ + op1: [3, { i: 'hi' }], + op2: [[2, { r: true }], [3, { i: 'other' }]], + expect: [2, { i: 'hi' }] + }) + + xf({ + op1: [4, { i: 'hi' }], + op2: [[2, { r: true }], [3, { i: 'other' }]], + expectLeft: [3, { i: 'hi' }], + expectRight: [4, { i: 'hi' }] + }) + }) + + it('list delete vs drop', () => { + xf({ + op1: [1, { r: true }], + op2: [2, { i: 'hi' }], + expect: [1, { r: true }] + }) + + xf({ + op1: [2, { r: true }], + op2: [2, { i: 'hi' }], + expect: [3, { r: true }] + }) + + xf({ + op1: [3, { r: true }], + op2: [2, { i: 'hi' }], + expect: [4, { r: true }] + }) + }) + + it('list delete vs delete', () => + xf({ + op1: [1, { r: true }], + op2: [1, { r: true }], + expect: null + })) // It was already deleted. + + it('fixes drop indexes correctly 2', () => + xf({ + op1: [[0, { r: true }], [1, { i: 'hi' }]], + op2: [2, { r: true }], // Shouldn't affect the op. + expect: [[0, { r: true }], [1, { i: 'hi' }]] + })) + + it('insert vs delete parent', () => + xf({ + op1: [2, 'x', { i: 'hi' }], + op2: [2, { r: true }], + conflict: { type: RM_UNEXPECTED_CONTENT }, + expect: null + })) + + it('transforms against inserts in my own list', () => + xf({ + //[0,1,2,3] -> [a,0,b,1,2,3...] + op1: [[0, { i: 'a' }], [2, { i: 'b' }]], + op2: [1, { r: true }], + expect: [[0, { i: 'a' }], [2, { i: 'b' }]] + })) + + it('vs cancelled op2 drop', () => + xf({ + doc: { x: { a: 'x.a' }, y: ['a', 'b', 'c'] }, + op1: [['x', { r: true }], ['y', 3, { i: 5 }]], + op2: [['x', 'a', { p: 0 }], ['y', 2, { d: 0 }]], + expect: [['x', { r: true }], ['y', [2, { r: true }], [3, { i: 5 }]]] + })) + + it('vs cancelled op1 drop', () => + xf({ + op1: [['x', { p: 0 }], ['y', [3, { d: 0 }], [4, { i: 5 }]]], + op2: ['x', { r: true }], + expect: ['y', 3, { i: 5 }] + })) + + it('vs cancelled op1 pick', () => + xf({ + doc: Array.from('abcdefg'), + op1: [[1, { p: 0 }], [4, { r: true, i: 4 }], [6, { d: 0 }]], + op2: [1, { r: true }], + expect: [[3, { r: true }], [4, { i: 4 }]] + })) + + it('xxxxx 1', () => + diamond({ + // TODO Regression. + doc: Array.from('abcdef'), + op1: [[1, { p: 0, i: 'AAA' }], [3, { i: 'BBB' }], [5, { d: 0 }]], + op2: [1, { r: true }] + })) + + it('xxxxx 2', () => + diamond({ + doc: Array.from('abcdef'), + op1: [[1, { p: 0, i: 'AAA' }], [3, { d: 0 }], [5, { i: 'CCC' }]], + op2: [1, { r: true }] + })) + }) + + describe('edit', () => { + it('transforms edits by one another', () => + xf({ + op1: [1, { es: [2, 'hi'] }], + op2: [1, { es: ['yo'] }], + expect: [1, { es: [4, 'hi'] }] + })) + + it('copies in ops otherwise', () => + xf({ + op1: ['x', { e: { position: 2, text: 'wai' }, et: 'simple' }], + op2: ['y', { r: true }], + expect: ['x', { e: { position: 2, text: 'wai' }, et: 'simple' }] + })) + + it('allows edits at the root', () => + xf({ + op1: [{ e: { position: 2, text: 'wai' }, et: 'simple' }], + op2: [{ e: { position: 0, text: 'omg' }, et: 'simple' }], + expect: [{ e: { position: 5, text: 'wai' }, et: 'simple' }] + })) + + it('applies edits in the right order', () => + xf({ + // Edits happen *after* the drop phase. + op1: [1, { es: [2, 'hi'] }], + op2: [[1, { i: {} }], [2, { es: ['yo'] }]], + expect: [2, { es: [4, 'hi'] }] + })) + + it('an edit on a deleted object goes away', () => + xf({ + op1: [1, { es: [2, 'hi'] }], + op2: [1, { r: 'yo' }], + conflict: { + type: RM_UNEXPECTED_CONTENT, + op2: [1, { r: true }] + }, // .... It'd be better if this copied the remove. + expect: null + })) + }) + }) + + // TODO Numbers + + // ***** Test cases found by the fuzzer which have caused issues + describe('fuzzer tests', () => { + it('asdf', () => + apply({ + doc: { the: '', Twas: 'the' }, + op: ['the', { es: ['hi'] }], + expect: { the: 'hi', Twas: 'the' } + })) + + it('does not duplicate list items from edits', () => + apply({ + doc: ['eyes'], + op: [0, { es: ['hi'] }], + expect: ['hieyes'] + })) + + it('will edit the root document', () => + apply({ + doc: '', + op: [{ es: ['hi'] }], + expect: 'hi' + })) + + // ------ These have nothing to do with apply. TODO: Move them out of this grouping. + + it('diamond', () => + // TODO: Do this for all combinations. + diamond({ + doc: Array.from('abcde'), + op1: [[0, { p: 0 }], [1, { d: 0 }]], + op2: [[0, { p: 0 }], [4, { d: 0 }]] + })) + + it('shuffles lists correctly', () => + xf({ + op1: [[0, { p: 0 }], [1, { d: 0 }]], + op2: [[0, { p: 0 }], [10, { d: 0 }]], + expectLeft: [[1, { d: 0 }], [10, { p: 0 }]], + expectRight: null + })) + + it('inserts before edits', () => { + xf({ + op1: [0, 'x', { i: 5 }], + op2: [0, { i: 35 }], + expect: [1, 'x', { i: 5 }] + }) + + xf({ + op1: [0, { es: ['hi'] }], + op2: [0, { i: 35 }], + expect: [1, { es: ['hi'] }] + }) + }) + + it( + 'duplicates become noops in a list', + () => + xf({ + op1: [0, { p: 0, d: 0 }], + op2: [0, { p: 0, d: 0 }], + expectLeft: [0, { p: 0, d: 0 }], // This is a bit weird. + expectRight: null + }), + + () => + xf({ + op1: [0, { r: true, i: 'a' }], + op2: [0, { i: 'b' }], + expectLeft: [[0, { i: 'a' }], [1, { r: true }]], + expectRight: [1, { r: true, i: 'a' }] + }), + + () => + xf({ + op1: [0, { r: true, i: 5 }], + op2: [0, { r: true }], + expect: [0, { i: 5 }] + }) + ) + + it('p1 pick descends correctly', () => { + xf({ + op1: [2, { r: true }, 1, { es: ['hi'] }], + op2: [3, 1, { r: true }], + conflict: { + type: RM_UNEXPECTED_CONTENT, + op1: [2, 1, { es: ['hi'] }] + }, + expect: [2, { r: true }] + }) + + xf({ + op1: [[2, { r: true }, 1, { es: ['hi'] }], [3, 1, { r: true }]], + op2: [3, 2, { r: true }], + conflict: { + type: RM_UNEXPECTED_CONTENT, + op1: [2, 1, { es: ['hi'] }] + }, + expect: [[2, { r: true }], [3, 1, { r: true }]] + }) + }) + + it('transforms picks correctly', () => + xf({ + op1: [1, 1, { r: true }], + op2: [0, { p: 0, d: 0 }], + expect: [1, 1, { r: true }] + })) + + it('pick & drop vs insert after the picked item', () => + xf({ + op1: [0, { p: 0, d: 0 }], // Remove / insert works the same. + op2: [1, { i: 'hi' }], + expectLeft: [0, { p: 0, d: 0 }], + expectRight: [[0, { p: 0 }], [1, { d: 0 }]] + })) + + it('pick same item vs shuffle list', () => + xf({ + op1: [1, ['x', { p: 0 }], ['y', { d: 0 }]], + op2: [1, { d: 0 }, 'x', { p: 0 }], + expectLeft: [1, { p: 0 }, 'y', { d: 0 }], + expectRight: null + })) + + it('remove the same item in a list', () => + xf({ + op1: [0, { r: true }], + op2: [0, { r: true }], + expect: null + })) + + it('rm vs hold item', () => + xf({ + op1: [0, { r: true }], + op2: [0, { p: 0, d: 0 }], + expect: [0, { r: true }] + })) + + it('moves child elements correctly', () => + xf({ + doc: ['a', [0, 10, 20], 'c'], + op1: [1, 0, { p: 0, d: 0 }], + op2: [[1, { d: 0 }], [2, { p: 0 }]], + expect: [2, 0, { d: 0, p: 0 }] + })) + + it('moves list indexes', () => + xf({ + doc: [[], 'b', 'c'], + op1: [[0, 'hi', { d: 0 }], [1, { p: 0 }]], + op2: [[0, { p: 0 }], [20, { d: 0 }]], + expect: [[0, { p: 0 }], [19, 'hi', { d: 0 }]] + })) + + it('insert empty string vs insert null', () => + xf({ + doc: undefined, + op1: [{ i: 'hi' }], + op2: [{ i: null }], + conflict: { type: DROP_COLLISION }, + expectLeft: [{ r: true, i: 'hi' }], + expectRight: null + })) + + it('move vs emplace', () => + xf({ + doc: ['a', 'b'], + op1: [[0, { p: 0 }], [1, { d: 0 }]], + op2: [1, { p: 0, d: 0 }], + expectLeft: [0, { p: 0, d: 0 }], + expectRight: [[0, { p: 0 }], [1, { d: 0 }]] + })) + + it('rm chases a subdocument that was moved out', () => + xf({ + doc: [['aaa']], + op1: [0, { r: true }], + op2: [0, { d: 0 }, 0, { p: 0 }], // Valid because lists. + expect: [[0, { r: true }], [1, { r: true }]] + })) + + it('colliding drops', () => + xf({ + doc: ['a', 'b', {}], + op1: [[0, { p: 0 }], [1, 'x', { d: 0 }]], // -> ['b', x:'a'] + op2: [1, { p: 0 }, 'x', { d: 0 }], // -> ['a', x:'b'] + conflict: { type: DROP_COLLISION }, + expectLeft: [[0, { p: 0 }, 'x', { d: 0 }], [1, 'x', { r: true }]], + expectRight: [0, { r: true }] + })) + + it('transform crash', () => + xf({ + op1: [['the', { r: true, d: 0 }], ['whiffling', { p: 0 }]], + op2: ['the', { p: 0, d: 0 }], + expect: [['the', { d: 0, r: true }], ['whiffling', { p: 0 }]] + })) + + it('transforms drops when the parent is moved by a remove', () => + xf({ + op1: [['a', { p: 0 }], ['b', { d: 0 }, 1, { i: 2 }]], + op2: ['a', 0, { r: 1 }], + expect: [['a', { p: 0 }], ['b', { d: 0 }, 0, { i: 2 }]] + })) + + it('transforms drops when the parent is moved by a drop', () => + xf({ + op1: [['a', { p: 0 }], ['b', { d: 0 }, 1, { i: 2 }]], + op2: ['a', 0, { i: 1 }], + expect: [['a', { p: 0 }], ['b', { d: 0 }, 2, { i: 2 }]] + })) + + it('transforms conflicting drops obfuscated by a move', () => + xf({ + op1: [['a', { p: 0 }], ['b', { d: 0 }, 1, { i: 2 }]], + op2: ['a', 1, { i: 1 }], + expectLeft: [['a', { p: 0 }], ['b', { d: 0 }, 1, { i: 2 }]], + expectRight: [['a', { p: 0 }], ['b', { d: 0 }, 2, { i: 2 }]] + })) + + it('transforms edits when the parent is moved', () => + xf({ + op1: [['x', { p: 0 }], ['y', { d: 0, es: [1, 'xxx'] }]], + op2: ['x', { es: [{ d: 1 }, 'Z'] }], + expectLeft: [['x', { p: 0 }], ['y', { d: 0, es: ['xxx'] }]], + expectRight: [['x', { p: 0 }], ['y', { d: 0, es: [1, 'xxx'] }]] + })) + + it('xf lots', () => + xf({ + op1: [['a', { p: 0 }], ['b', { d: 0, es: ['hi'] }]], + op2: [['a', { p: 0 }], ['c', { d: 0 }]], + expectLeft: [['b', { d: 0, es: ['hi'] }], ['c', { p: 0 }]], + expectRight: ['c', { es: ['hi'] }] + })) + + it('inserts are moved back by the other op', () => + xf({ + op1: [['a', { p: 0 }], ['b', { d: 0 }, 'x', { i: 'hi' }]], + op2: [['a', { p: 0 }], ['c', { d: 0 }]], + expectLeft: [['b', { d: 0 }, 'x', { i: 'hi' }], ['c', { p: 0 }]], + expectRight: ['c', 'x', { i: 'hi' }] + })) + + it('more awful edit moves', () => + xf({ + op1: [['a', { p: 0 }], ['c', { d: 0 }, 'x', { es: ['hi'] }]], + op2: ['a', ['b', { d: 0 }], ['x', { p: 0 }]], + expect: [['a', { p: 0 }], ['c', { d: 0 }, 'b', { es: ['hi'] }]] + })) + + it('inserts null', () => + xf({ + op1: ['x', 'a', { i: null }], + op2: [['x', { p: 0 }], ['y', { d: 0 }]], + expect: ['y', 'a', { i: null }] + })) + + it('preserves local insert if both sides delete', () => + xf({ + op1: [{ i: {}, r: true }, 'x', { i: 'yo' }], + op2: [{ r: true }], + expect: [{ i: {} }, 'x', { i: 'yo' }] + })) + + it('handles insert/delete vs move', () => + xf({ + op1: ['a', { i: {}, r: true }, 'x', { i: 'yo' }], + op2: [['a', { p: 0 }], ['b', { d: 0 }]], + expect: [['a', { i: {} }, 'x', { i: 'yo' }], ['b', { r: true }]] + })) + + it('insert pushes edit target', () => + xf({ + op1: [[0, { i: 'yo' }], [1, 'a', { es: ['hi'] }]], + op2: [0, ['a', { p: 0 }], ['b', { d: 0 }]], + expect: [[0, { i: 'yo' }], [1, 'b', { es: ['hi'] }]] + })) + + it('composes simple regression', () => { + compose({ + op1: [0, { p: 0, d: 0 }], + op2: [{ r: true }], + expect: [{ r: true }, 0, { r: true }] + }) + + compose({ + op1: ['a', 1, { r: true }], + op2: ['a', { r: true }], + expect: ['a', { r: true }, 1, { r: true }] + }) + }) + + it('ignores op2 inserts for index position after op1 insert', () => + xf({ + op1: [{ r: true, i: [] }, 0, { i: '' }], + op2: [0, { i: 0 }], + conflict: { + type: RM_UNEXPECTED_CONTENT, + op1: [{ r: true }] + }, + expect: [{ r: true, i: [] }, 0, { r: true, i: '' }] + })) + + it('edit moved inside a removed area should be removed', () => + xf({ + op1: [[0, { r: true }], [2, { es: ['hi'] }]], + op2: [[0, 'x', { d: 0 }], [3, { p: 0 }]], + conflict: { + type: RM_UNEXPECTED_CONTENT, + op1: [0, { r: true }] + }, + expect: [0, { r: true }, 'x', { r: true }] + })) + + it('advances indexes correctly with mixed numbers', () => + xf({ + op1: [ + ['x', [0, { p: 0 }], [1, { d: 1 }]], + ['y', { p: 1 }], + ['zzz', { d: 0 }] + ], + op2: [['x', 2, { i: 'hi' }], ['y', { p: 0 }], ['z', { d: 0 }]], + expectLeft: [ + ['x', [0, { p: 1 }], [1, { d: 0 }]], + ['z', { p: 0 }], + ['zzz', { d: 1 }] + ], + expectRight: [['x', 0, { p: 0 }], ['zzz', { d: 0 }]] + })) + + it('handles index positions past cancelled drops 1', () => + xf({ + op1: [0, { r: true, i: [''] }], + op2: [[0, { p: 0, d: 0 }], [1, { i: 23 }]], + expectLeft: [0, { r: true, i: [''] }], + expectRight: [[0, { r: true }], [1, { i: [''] }]] + })) + + it('handles index positions past cancelled drops 2', () => + xf({ + // This looks more complicated, but its a simpler version of the above test. + op1: [['a', { r: true }], ['b', 0, { i: 'hi' }]], + op2: [['a', { p: 0 }], ['b', [0, { d: 0 }], [1, { i: 'yo' }]]], + expectLeft: ['b', 0, { i: 'hi', r: true }], + expectRight: ['b', [0, { r: true }], [1, { i: 'hi' }]] + })) + + it('calculates removed drop indexes correctly', () => + xf({ + op1: [[0, { i: 'hi', p: 0 }], [1, 1, { d: 0 }], [2, { r: true }]], + op2: [[0, { i: 'yo', p: 0 }], [1, 1, { d: 0 }]], + expectLeft: [ + [0, { i: 'hi' }], + [1, 1, { p: 0 }], + [2, { r: true }, 1, { d: 0 }] + ], + expectRight: [[1, { i: 'hi' }], [2, { r: true }]] + })) + + it('removed drop indexes calc regression', () => + xf({ + op1: [[1, { p: 0 }, 'burbled', { d: 0 }], [3, { r: true }]], + op2: [ + [0, { i: 'to', r: true }], + [1, { p: 1 }, ['its', { d: 0 }], ['thought', { d: 1 }]], + [3, { p: 0 }] + ], + expectLeft: [ + 1, + ['burbled', { d: 0 }], + ['its', { r: true }], + ['thought', { p: 0 }] + ], + expectRight: [1, 'its', { r: true }] + })) + + it('removed drop indexes tele to op1 pick', () => + xf({ + op1: ['a', 0, [0, { es: ['hi'] }], [2, { r: true }]], + op2: [ + ['a', { p: 0 }, 0, 0, { p: 1 }], + ['b', { d: 0 }, 0, 1, 0, { d: 1 }] + ], + conflict: { + type: RM_UNEXPECTED_CONTENT, + op1: ['a', 0, 2, { r: true }], + op2: [['a', 0, 0, { p: 0 }], ['b', 0, 1, 0, { d: 0 }]] + }, + expect: ['b', 0, 1, { r: true }, 0, { r: true }] + })) + + it('tracks removed drop index teleports', () => + xf({ + // rm 0.a, move 0.b -> 0.c + doc: [{ a: ['a'], b: 'b' }], + op1: [0, ['a', { r: true }], ['b', { p: 0 }], ['c', { d: 0 }]], // [{c:'b'}] + op2: [0, { d: 0, p: 1 }, [0, { d: 1 }], ['a', { p: 0 }]], // [[{b:'b'}, 'a']] + conflict: { + type: RM_UNEXPECTED_CONTENT, + op1: [0, 'a', { r: true }], + op2: [0, { p: 0 }, 0, { d: 0 }] + }, + expect: [0, { r: true }, 0, { r: true }] + })) + + it('handles transforming past cancelled move', () => + xf({ + op1: [[0, { r: true }], [10, { i: [''] }]], + op2: [0, { p: 0, d: 0 }], + expect: [[0, { r: true }], [10, { i: [''] }]] + })) + + it('correctly adjusts indexes in another fuzzer great', () => + xf({ + op1: [[0, { d: 0, r: true }], [3, { p: 0 }]], + op2: [[0, { p: 0 }], [3, { d: 0 }]], + expect: [[0, { d: 0 }], [2, { p: 0 }], [3, { r: true }]] + })) + + it('op2 moves into something op1 removes and op1 moves into that', () => + xf({ + op1: [['a', { r: true }, 'aa', { p: 0 }], ['b', 'x', { d: 0 }]], + op2: [['a', 'bb', { d: 0 }], ['b', { p: 0 }]], + conflict: { + type: RM_UNEXPECTED_CONTENT, + op1: ['a', { r: true }] + }, + expect: ['a', { r: true }, ['aa', { r: true }], ['bb', { r: true }]] + })) // Also ok if we miss the second rs. + + it('op2 moves into op1 remove edge cases', () => { + // Sorry not minified. + xf({ + op1: [ + 'Came', + 0, + [0, { r: true }, 'he', { p: 0 }], + [1, { d: 0 }, 0, { i: 'time' }] + ], + op2: [ + 'Came', + 0, + [0, 'he', [0, { d: 0 }], [1, { es: ['hi'] }]], + [1, { p: 0 }] + ], + expectLeft: [ + 'Came', + 0, + 0, + { r: true, d: 0 }, + [0, { i: 'time' }], + ['he', { p: 0 }] + ], + expectRight: [ + 'Came', + 0, + 0, + { r: true, d: 0 }, + [1, { i: 'time' }], + ['he', { p: 0 }] + ] + }) + + xf({ + op1: [[0, [1, { p: 0 }], [2, { r: true }]], [1, 'xxx', { d: 0 }]], + op2: [0, 1, { i: {}, p: 0 }, 'b', { d: 0 }], + expectLeft: [ + [0, [1, 'b', { p: 0 }], [2, { r: true }]], + [1, 'xxx', { d: 0 }] + ], + expectRight: [0, 2, { r: true }] + }) + }) + + it('translates indexes correctly in this fuzzer find', () => + xf({ + op1: [0, { p: 0 }, 'x', { d: 0 }], + op2: [[0, { p: 0, d: 0 }], [1, { i: 'y' }]], + expectLeft: [[0, { p: 0 }], [1, 'x', { d: 0 }]], + expectRight: null + })) + + it('buries children of blackholed values', () => + xf({ + op1: [ + [0, ['a', { p: 0 }], ['b', { d: 0 }], ['c', { d: 1 }]], + [1, { p: 1 }] + ], + op2: [0, { p: 0 }, 'x', { d: 0 }], + // This is a bit interesting. The question is, which op2 picks and drops + // should we include in the output? For now the answer is that we include + // anything in both ops thats going to end up inside the blackholed + // content. + conflict: { type: BLACKHOLE }, + + // op1: [[0, 'c', d:0], [1, p:0]] + expect: [0, { r: true }, 'x', { r: true }] + })) + + it('does not conflict when removed target gets moved inside removed container', () => { + // This edge case is interesting because we don't generate the same + // conflicts on left and right. We want our move of a.x to escape the + // object before removing it, but when we're right, the other operation's + // move holds the object and we get an unexpected rm conflict. + xf({ + op1: [['a', { r: true }, 'x', { p: 0 }], ['b', { d: 0 }]], + op2: ['a', ['x', { p: 0 }], ['y', { d: 0 }]], + conflictRight: { + type: RM_UNEXPECTED_CONTENT, + op1: ['a', { r: true }] + }, + expectLeft: [['a', { r: true }, 'y', { p: 0 }], ['b', { d: 0 }]], + expectRight: ['a', { r: true }, 'y', { r: true }] + }) + + xf({ + op1: [['a', { r: true }, 1, { p: 0 }], ['b', { d: 0 }]], + op2: ['a', [0, { d: 0 }], [1, { p: 0 }]], + expectLeft: [['a', { r: true }, 0, { p: 0 }], ['b', { d: 0 }]], + conflictRight: { + type: RM_UNEXPECTED_CONTENT, + op1: ['a', { r: true }] + }, + expectRight: ['a', { r: true }, 0, { r: true }] + }) + + // TODO have a look at this - doesn't seem right to just define an object + { + expect: [['a', { r: true }, 0, { p: 0 }], ['b', { d: 0 }]] + } + }) + + it('compose copies op2 edit data', () => + compose({ + op1: ['a', { r: true }], + op2: [['x', { p: 0 }], ['y', { d: 0 }, 'b', { es: ['x'] }]], + expect: [ + ['a', { r: true }], + ['x', { p: 0 }], + ['y', { d: 0 }, 'b', { es: ['x'] }] + ] + })) + + it('does not conflict when the dest is salvaged', () => + xf({ + op1: [['a', { p: 0 }], ['b', { i: 'hi' }], ['c', { d: 0 }]], + op2: [['a', { p: 0 }], ['b', { d: 0 }]], + expectLeft: [['b', { p: 0, i: 'hi' }], ['c', { d: 0 }]], + conflictRight: { + type: DROP_COLLISION, + op1: ['b', { i: 'hi' }] + }, + expectRight: null + })) + + it('does not conflict on identical r/i pairs', () => + xf({ + op1: [{ i: [], r: true }], + op2: [{ i: [], r: true }], + expect: null + })) + + it('allows embedded edits in identical r/i', () => + xf({ + op1: [{ r: true, i: '', es: ['hi'] }], + op2: [{ r: true, i: '' }], + expect: [{ es: ['hi'] }] + })) + + it('does not conflict on identical r/i pairs with identical drops inside', () => + xf({ + op1: [{ i: {}, r: true }, 'a', { i: 'a' }], + op2: [{ i: {}, r: true }, 'a', { i: 'a' }], + expect: null + })) + + it('generates a DROP_COLLISION on children', () => + xf({ + op1: [{ i: {}, r: true }, 'a', { i: 'a' }], + op2: [{ i: {}, r: true }, 'a', { i: 'b' }], + conflict: { + type: DROP_COLLISION, + op1: ['a', { i: 'a' }], + op2: ['a', { i: 'b' }] + }, + expectLeft: ['a', { r: true, i: 'a' }], + expectRight: null + })) + + it('Transforms edit moves into the right dest', () => + xf({ + op1: [ + 0, + { p: 0, d: 0 }, + // These parts are all needed for some reason. + [0, { i: 1 }], + [1, { r: true }], + [3, { es: ['hi'] }] + ], + op2: [0, [0, { d: 0 }], [3, { p: 0 }]], + expectLeft: [ + 0, + { p: 0, d: 0 }, + [0, { i: 1 }], + [1, { es: ['hi'] }], + [2, { r: true }] + ], + expectRight: [ + 0, + { p: 0, d: 0 }, + [0, { es: ['hi'] }], + [1, { i: 1 }], + [2, { r: true }] + ] + })) + + it('adjusts indexes of pick -> drop', () => + xf({ + op1: [0, { p: 0, d: 0 }], + op2: [[0, { i: 'yo', p: 0 }], [1, { d: 0 }]], + expectLeft: [[0, { d: 0 }], [1, { p: 0 }]], + expectRight: null + })) + + it('clears output outDrop when theres no pick', () => + xf({ + // Again, not minimized. We return the right data, we were just double- + // descending into outDrop. + op1: [['the', { d: 0, p: 0 }], ['toves', { r: true }]], + op2: [ + ['bird', { d: 0 }], + ['slain', { d: 1 }], + ['the', { p: 1 }], + ['toves', { p: 0 }] + ], + expectLeft: [ + ['bird', { r: true }], + ['slain', { p: 0 }], + ['the', { d: 0 }] + ], + expectRight: ['bird', { r: true }] + })) + + it('pushes drop indexes by other held items', () => + xf({ + op1: [[0, { p: 0 }], [1, [0, { i: 'hi' }], [1, { d: 0, es: ['xx'] }]]], + op2: [[0, { p: 1 }, 1, { d: 0 }, 2, { d: 1 }], [2, { p: 0 }]], + expectLeft: [ + 0, + 1, + [0, { i: 'hi' }], + [1, { d: 0, es: ['xx'] }], + [2, { p: 0 }] + ], + expectRight: [0, 1, [0, { i: 'hi' }], [3, { es: ['xx'] }]] + })) + + it('composes correctly with lots of removes', () => + compose({ + op1: [3, 1, { r: true }], + op2: [[0, { es: ['xx'] }], [1, { r: true, es: ['yy'] }], [2, { r: true }]], + expect: [ + [0, { es: ['xx'] }], + [1, { es: ['yy'], r: true }], + [2, { r: true }], + [3, 1, { r: true }] + ] + })) + + it('does not descend twice when p/r on an identical insert', () => + xf({ + op1: [['a', { p: 0, i: '' }], ['b', { d: 0 }]], + op2: ['a', { r: true, i: '' }], + expect: null + })) + + it('conflicts underneath a moved / inserted child', () => + xf({ + op1: [['a', { p: 0, i: {} }, 'x', { i: 5 }], ['b', { d: 0 }]], + op2: ['a', { r: true, i: {} }, 'x', { i: 6 }], + conflict: { + type: DROP_COLLISION, + op1: ['a', 'x', { i: 5 }], + op2: ['a', 'x', { i: 6 }] + }, + expectLeft: ['a', 'x', { r: true, i: 5 }], + expectRight: null + })) + + it('clears drop2 in transform moves', () => + xf({ + doc: [{ b: { a: 'hi' } }], + op1: [0, { d: 0 }, ['a', { es: ['xx'] }], ['b', { p: 0 }]], + op2: [0, 'b', ['a', { p: 0 }], ['b', { d: 0 }]], + expect: [0, { d: 0 }, 'b', { p: 0, es: ['xx'] }] + })) + + it('descends correctly when op2 picks and drops', () => + xf({ + op1: [ + ['b', { d: 0 }, [1, { es: ['hi'] }], [2, { i: null }]], + ['e', { p: 0 }] + ], + op2: [{ p: 0, d: 0 }, 'e', 1, { p: 1, d: 1 }], + expectLeft: [ + ['b', { d: 0 }, [1, { i: null }], [2, { es: ['hi'] }]], + ['e', { p: 0 }] + ], + expectRight: [ + ['b', { d: 0 }, [1, { es: ['hi'] }], [2, { i: null }]], + ['e', { p: 0 }] + ] + })) + + it('composes a pick out of the insert', () => + compose({ + op1: [{ i: [5, { x: 6 }] }], + op2: [[0, { r: true }, 'c', { d: 0 }], [1, 'x', { p: 0 }]], + // expect: [{i: [{c: 6}]}] + expect: [{ i: [{}] }, 0, 'c', { i: 6 }] + })) + + it('is not overeager to remove intermediate literal array items', () => + compose({ + op1: [[0, { i: ['a', 'b'] }, 0, { p: 0 }], [1, 0, { d: 0 }]], + op2: [0, { r: ['a'] }, 1, { r: 'b' }], + expect: [0, 0, { d: 0, p: 0 }] + })) + + it('descends down insert indexes correctly', () => + compose({ + op1: [{ i: [{}, 'a'] }, 1, { i: 'b' }], + op2: [[1, { r: 'b' }], [2, { r: 'a' }]], + expect: [{ i: [{}] }] + })) + + it('handles composes with ena: 0', () => + compose({ + op1: [{ i: 10 }], + op2: [{ ena: 0 }], + expect: [{ i: 10, ena: 0 }] + })) // Also ok: just discarding the ena:0. + + it('handles rm parent with cross move', () => + compose({ + op1: [['a', { p: 0 }], ['b', 1, { d: 0 }]], + op2: [['b', { r: true }, 1, { p: 0 }], ['c', { d: 0 }]], + expect: [['a', { p: 0 }], ['b', { r: true }], ['c', { d: 0 }]] + })) + + it('lets you remove children of an op at 2 levels', () => + compose({ + op1: [{ i: ['a', { x: 'hi' }] }], + op2: [{ r: true }, 1, 'x', { r: true }], + expect: null + })) + + it('discards op1 inserts inside a removed chunk', () => + compose({ + op1: ['y', [1, { i: 'x' }], [2, { i: ['a', 'b'] }]], + op2: [{ r: true }, 'y', 2, 0, { r: true }], + expect: [{ r: true }] + })) + + it('handles deeply nested blackhole operations', () => + xf({ + op1: [ + ['x', { p: 0 }], + ['y', ['a', ['j', { p: 1 }], ['k', { d: 1 }]], ['b', { d: 0 }]] + ], + op2: [ + ['x', 'xx', { d: 0 }, 'j', 'jj', { d: 1 }], + ['y', { p: 1 }, 'a', { p: 0 }] + ], + conflict: { type: BLACKHOLE }, + expect: ['x', { r: true }, 'xx', { r: true }, 'j', 'jj', { r: true }] + })) + + it('does not list removed op1 moves in the blackhole info', () => + xf({ + op1: [ + ['a', ['j', { d: 0 }], ['k', { d: 1 }]], + ['b', { p: 0 }, 'z', 0, { p: 1 }] + ], + op2: [['a', { p: 0 }], ['b', ['y', { d: 0 }], ['z', { r: true }]]], + conflict: { + type: BLACKHOLE, + op1: [['a', 'j', { d: 0 }], ['b', { p: 0 }]], + op2: [['a', { p: 0 }], ['b', 'y', { d: 0 }]] + }, + expect: ['b', { r: true }, 'y', { r: true }] + })) + + it('handles overlapping pick in blackholes', () => + xf({ + // This looks complicated, but its really not so bad. Its: + // a->b.0, a.x -> z + // vs + // b -> a.x -> a.y + // + // Its a bit twisty because we're both picking up the same element and + // putting it in different places. This is why we have different left and + // right results. + op1: [ + ['a', { p: 1 }, 'x', { p: 0 }], + ['b', 0, { d: 1 }], + ['z', { d: 0 }] + ], + op2: [['a', ['x', { d: 0, p: 1 }], ['y', { d: 1 }]], ['b', { p: 0 }]], + conflictLeft: { + type: BLACKHOLE, + op1: [['a', { p: 0 }], ['b', 0, { d: 0 }]], + op2: [['a', 'x', { d: 0 }], ['b', { p: 0 }]] + }, + expectLeft: [ + ['a', { r: true }, ['x', { r: true }], ['y', { p: 0 }]], + ['z', { d: 0 }] + ], + conflictRight: { + type: BLACKHOLE, + op1: [['a', { p: 0 }], ['b', 0, { d: 0 }]] + }, + expectRight: ['a', { r: true }, ['x', { r: true }], ['y', { r: true }]] + })) + + it('descends into a string correctly in apply', () => + apply({ + doc: { y: 'omg', z: [ 1, 'whoa', 3 ] }, + op: [ + [ 'y', { i: [0], r: true }, 0, { i: 16 } ], + [ 'z', { r: true } ] + ], + expect: { y: [ 16, 0 ] } + })) + }) +}) diff --git a/src/json1presence/tracer.js b/src/json1presence/tracer.js new file mode 100644 index 00000000..6c548fac --- /dev/null +++ b/src/json1presence/tracer.js @@ -0,0 +1,86 @@ +// This is a simple little OT library which wraps another OT library, but +// traces all calls so you can work out where everything came from. + +const type = require('./lib/json1') +const {inspect} = require('util') +const log = require('./lib/log') + +const deps = new Map // map from artifact to [...deps] +const meta = new Map // artifact to user information + +const printInfo = (item, prefix = '', depth = 5) => { + const m = meta.get(item) + if (m == null) return log(prefix + 'NO DATA', item) + log(prefix + m.fn + ' ->', item, m) + + if (depth > 0) { + const ds = deps.get(item) + if (ds == null) return + log(prefix + 'deps:', ...ds) + + ds.forEach(d => printInfo(d, prefix + ' ', depth - 1)) + } +} + +module.exports = (type, genOp) => ({ + ...type, + + create(data) { return type.create(data) }, + + apply(snapshot, op) { + try { + const result = type.apply(snapshot, op) + if (result !== snapshot) { + deps.set(result, [snapshot, op]) + meta.set(result, {fn:'apply'}) + } + + return result + } catch (e) { + console.error('************************************* APPLY FAILED!') + console.error(e.stack) + log.quiet = false + printInfo(snapshot) + printInfo(op) + throw e + } + }, + + transform(op1, op2, side) { + try { + const result = type.transform(op1, op2, side) + if (result !== op1) { + deps.set(result, [op1, op2]) + meta.set(result, {fn:'transform', side}) + } + + return result + } catch (e) { + console.error('************************************* TRANSFORM FAILED!') + console.error(e.stack) + log.quiet = false + printInfo(op1) + printInfo(op2) + throw e + } + + }, + + genOp(snapshot) { + try { + const [op, result] = genOp(snapshot) + deps.set(op, [snapshot]) + deps.set(result, [snapshot]) + meta.set(op, {fn:'genop', result}) + meta.set(result, {fn:'genop', op}) + return [op, result] + } catch (e) { + console.error('************************************* OOPSIE!') + console.error(e.stack) + printInfo(snapshot) + throw e + } + + }, +}) + diff --git a/src/json1presence/tsconfig.json b/src/json1presence/tsconfig.json new file mode 100644 index 00000000..5657d437 --- /dev/null +++ b/src/json1presence/tsconfig.json @@ -0,0 +1,69 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig.json to read more about this file */ + + /* Basic Options */ + // "incremental": true, /* Enable incremental compilation */ + "target": "ES2017", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ + "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ + // "lib": [], /* Specify library files to be included in the compilation. */ + // "allowJs": true, /* Allow javascript files to be compiled. */ + // "checkJs": true, /* Report errors in .js files. */ + // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ + "declaration": true, /* Generates corresponding '.d.ts' file. */ + // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ + "sourceMap": true, /* Generates corresponding '.map' file. */ + // "outFile": "./", /* Concatenate and emit output to single file. */ + "outDir": "./dist", /* Redirect output structure to the directory. */ + // "rootDir": "./lib", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ + // "composite": true, /* Enable project compilation */ + // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ + // "removeComments": true, /* Do not emit comments to output. */ + // "noEmit": true, /* Do not emit outputs. */ + // "importHelpers": true, /* Import emit helpers from 'tslib'. */ + // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ + // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ + + /* Strict Type-Checking Options */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ + // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + + /* Additional Checks */ + // "noUnusedLocals": true, /* Report errors on unused locals. */ + // "noUnusedParameters": true, /* Report errors on unused parameters. */ + // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ + // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ + + /* Module Resolution Options */ + // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ + // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ + // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ + // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ + // "typeRoots": [], /* List of folders to include type definitions from. */ + // "types": [], /* Type declaration files to be included in compilation. */ + // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ + "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ + // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + + /* Source Map Options */ + // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ + // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ + + /* Experimental Options */ + // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ + // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ + + /* Advanced Options */ + "skipLibCheck": true, /* Skip type checking of declaration files. */ + "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ + } +} diff --git a/src/json1presence/yarn.lock b/src/json1presence/yarn.lock new file mode 100644 index 00000000..c9868215 --- /dev/null +++ b/src/json1presence/yarn.lock @@ -0,0 +1,782 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@types/node@^14.0.13": + "integrity" "sha512-Z4U8yDAl5TFkmYsZdFPdjeMa57NOvnaf1tljHzhouaPEp7LCj2JKkejpI1ODviIAQuW4CcQmxkQ77rnLsOOoKw==" + "resolved" "https://registry.npmjs.org/@types/node/-/node-14.0.23.tgz" + "version" "14.0.23" + +"ansi-colors@3.2.3": + "integrity" "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==" + "resolved" "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz" + "version" "3.2.3" + +"ansi-regex@^3.0.0": + "integrity" "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + "resolved" "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz" + "version" "3.0.0" + +"ansi-regex@^4.1.0": + "integrity" "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==" + "resolved" "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz" + "version" "4.1.0" + +"ansi-styles@^3.2.0", "ansi-styles@^3.2.1": + "integrity" "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==" + "resolved" "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz" + "version" "3.2.1" + dependencies: + "color-convert" "^1.9.0" + +"anymatch@~3.1.1": + "integrity" "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==" + "resolved" "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz" + "version" "3.1.1" + dependencies: + "normalize-path" "^3.0.0" + "picomatch" "^2.0.4" + +"argparse@^1.0.7": + "integrity" "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==" + "resolved" "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz" + "version" "1.0.10" + dependencies: + "sprintf-js" "~1.0.2" + +"balanced-match@^1.0.0": + "integrity" "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "resolved" "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz" + "version" "1.0.0" + +"binary-extensions@^2.0.0": + "integrity" "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==" + "resolved" "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz" + "version" "2.0.0" + +"brace-expansion@^1.1.7": + "integrity" "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==" + "resolved" "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz" + "version" "1.1.11" + dependencies: + "balanced-match" "^1.0.0" + "concat-map" "0.0.1" + +"braces@~3.0.2": + "integrity" "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==" + "resolved" "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz" + "version" "3.0.2" + dependencies: + "fill-range" "^7.0.1" + +"browser-stdout@1.3.1": + "integrity" "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==" + "resolved" "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz" + "version" "1.3.1" + +"buffer-from@^1.0.0": + "integrity" "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + "resolved" "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz" + "version" "1.1.1" + +"camelcase@^5.0.0": + "integrity" "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + "resolved" "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz" + "version" "5.3.1" + +"chalk@^2.4.2": + "integrity" "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==" + "resolved" "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz" + "version" "2.4.2" + dependencies: + "ansi-styles" "^3.2.1" + "escape-string-regexp" "^1.0.5" + "supports-color" "^5.3.0" + +"chokidar@3.3.0": + "integrity" "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==" + "resolved" "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz" + "version" "3.3.0" + dependencies: + "anymatch" "~3.1.1" + "braces" "~3.0.2" + "glob-parent" "~5.1.0" + "is-binary-path" "~2.1.0" + "is-glob" "~4.0.1" + "normalize-path" "~3.0.0" + "readdirp" "~3.2.0" + optionalDependencies: + "fsevents" "~2.1.1" + +"cli-progress@^2.1.1": + "integrity" "sha512-TSJw3LY9ZRSis7yYzQ7flIdtQMbacd9oYoiFphJhI4SzgmqF0zErO+uNv0lbUjk1L4AGfHQJ4OVYYzW+JV66KA==" + "resolved" "https://registry.npmjs.org/cli-progress/-/cli-progress-2.1.1.tgz" + "version" "2.1.1" + dependencies: + "colors" "^1.1.2" + "string-width" "^2.1.1" + +"cliui@^5.0.0": + "integrity" "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==" + "resolved" "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz" + "version" "5.0.0" + dependencies: + "string-width" "^3.1.0" + "strip-ansi" "^5.2.0" + "wrap-ansi" "^5.1.0" + +"color-convert@^1.9.0": + "integrity" "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==" + "resolved" "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz" + "version" "1.9.3" + dependencies: + "color-name" "1.1.3" + +"color-name@1.1.3": + "integrity" "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + "resolved" "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz" + "version" "1.1.3" + +"colors@^1.1.2": + "integrity" "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==" + "resolved" "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz" + "version" "1.4.0" + +"commander@^2.20.0": + "integrity" "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + "resolved" "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz" + "version" "2.20.3" + +"concat-map@0.0.1": + "integrity" "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + "resolved" "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" + "version" "0.0.1" + +"debug@3.2.6": + "integrity" "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==" + "resolved" "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz" + "version" "3.2.6" + dependencies: + "ms" "^2.1.1" + +"decamelize@^1.2.0": + "integrity" "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + "resolved" "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz" + "version" "1.2.0" + +"define-properties@^1.1.2", "define-properties@^1.1.3": + "integrity" "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==" + "resolved" "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz" + "version" "1.1.3" + dependencies: + "object-keys" "^1.0.12" + +"diff@3.5.0": + "integrity" "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==" + "resolved" "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz" + "version" "3.5.0" + +"emoji-regex@^7.0.1": + "integrity" "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" + "resolved" "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz" + "version" "7.0.3" + +"es-abstract@^1.17.0-next.1": + "integrity" "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==" + "resolved" "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz" + "version" "1.17.5" + dependencies: + "es-to-primitive" "^1.2.1" + "function-bind" "^1.1.1" + "has" "^1.0.3" + "has-symbols" "^1.0.1" + "is-callable" "^1.1.5" + "is-regex" "^1.0.5" + "object-inspect" "^1.7.0" + "object-keys" "^1.1.1" + "object.assign" "^4.1.0" + "string.prototype.trimleft" "^2.1.1" + "string.prototype.trimright" "^2.1.1" + +"es-to-primitive@^1.2.1": + "integrity" "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==" + "resolved" "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz" + "version" "1.2.1" + dependencies: + "is-callable" "^1.1.4" + "is-date-object" "^1.0.1" + "is-symbol" "^1.0.2" + +"escape-string-regexp@^1.0.5", "escape-string-regexp@1.0.5": + "integrity" "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + "resolved" "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" + "version" "1.0.5" + +"esprima@^4.0.0": + "integrity" "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + "resolved" "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz" + "version" "4.0.1" + +"fill-range@^7.0.1": + "integrity" "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==" + "resolved" "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz" + "version" "7.0.1" + dependencies: + "to-regex-range" "^5.0.1" + +"find-up@^3.0.0", "find-up@3.0.0": + "integrity" "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==" + "resolved" "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz" + "version" "3.0.0" + dependencies: + "locate-path" "^3.0.0" + +"flat@^4.1.0": + "integrity" "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==" + "resolved" "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz" + "version" "4.1.0" + dependencies: + "is-buffer" "~2.0.3" + +"fs.realpath@^1.0.0": + "integrity" "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + "resolved" "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" + "version" "1.0.0" + +"function-bind@^1.1.1": + "integrity" "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "resolved" "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz" + "version" "1.1.1" + +"get-caller-file@^2.0.1": + "integrity" "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + "resolved" "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz" + "version" "2.0.5" + +"glob-parent@~5.1.0": + "integrity" "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==" + "resolved" "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz" + "version" "5.1.2" + dependencies: + "is-glob" "^4.0.1" + +"glob@7.1.3": + "integrity" "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==" + "resolved" "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz" + "version" "7.1.3" + dependencies: + "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" + +"growl@1.10.5": + "integrity" "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==" + "resolved" "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz" + "version" "1.10.5" + +"has-flag@^3.0.0": + "integrity" "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + "resolved" "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz" + "version" "3.0.0" + +"has-symbols@^1.0.0", "has-symbols@^1.0.1": + "integrity" "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" + "resolved" "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz" + "version" "1.0.1" + +"has@^1.0.3": + "integrity" "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==" + "resolved" "https://registry.npmjs.org/has/-/has-1.0.3.tgz" + "version" "1.0.3" + dependencies: + "function-bind" "^1.1.1" + +"he@1.2.0": + "integrity" "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" + "resolved" "https://registry.npmjs.org/he/-/he-1.2.0.tgz" + "version" "1.2.0" + +"inflight@^1.0.4": + "integrity" "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=" + "resolved" "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz" + "version" "1.0.6" + dependencies: + "once" "^1.3.0" + "wrappy" "1" + +"inherits@2": + "integrity" "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + "resolved" "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz" + "version" "2.0.4" + +"is-binary-path@~2.1.0": + "integrity" "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==" + "resolved" "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz" + "version" "2.1.0" + dependencies: + "binary-extensions" "^2.0.0" + +"is-buffer@~2.0.3": + "integrity" "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==" + "resolved" "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz" + "version" "2.0.4" + +"is-callable@^1.1.4", "is-callable@^1.1.5": + "integrity" "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==" + "resolved" "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz" + "version" "1.1.5" + +"is-date-object@^1.0.1": + "integrity" "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==" + "resolved" "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz" + "version" "1.0.2" + +"is-extglob@^2.1.1": + "integrity" "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" + "resolved" "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz" + "version" "2.1.1" + +"is-fullwidth-code-point@^2.0.0": + "integrity" "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" + "resolved" "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz" + "version" "2.0.0" + +"is-glob@^4.0.1", "is-glob@~4.0.1": + "integrity" "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==" + "resolved" "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz" + "version" "4.0.1" + dependencies: + "is-extglob" "^2.1.1" + +"is-number@^7.0.0": + "integrity" "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + "resolved" "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz" + "version" "7.0.0" + +"is-regex@^1.0.5": + "integrity" "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==" + "resolved" "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz" + "version" "1.0.5" + dependencies: + "has" "^1.0.3" + +"is-symbol@^1.0.2": + "integrity" "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==" + "resolved" "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz" + "version" "1.0.3" + dependencies: + "has-symbols" "^1.0.1" + +"isexe@^2.0.0": + "integrity" "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + "resolved" "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz" + "version" "2.0.0" + +"js-yaml@3.13.1": + "integrity" "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==" + "resolved" "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz" + "version" "3.13.1" + dependencies: + "argparse" "^1.0.7" + "esprima" "^4.0.0" + +"locate-path@^3.0.0": + "integrity" "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==" + "resolved" "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz" + "version" "3.0.0" + dependencies: + "p-locate" "^3.0.0" + "path-exists" "^3.0.0" + +"lodash@^4.17.15": + "integrity" "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "resolved" "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz" + "version" "4.17.21" + +"log-symbols@3.0.0": + "integrity" "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==" + "resolved" "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz" + "version" "3.0.0" + dependencies: + "chalk" "^2.4.2" + +"minimatch@^3.0.4", "minimatch@3.0.4": + "integrity" "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==" + "resolved" "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz" + "version" "3.0.4" + dependencies: + "brace-expansion" "^1.1.7" + +"minimist@^1.2.5": + "integrity" "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + "resolved" "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz" + "version" "1.2.5" + +"mkdirp@0.5.3": + "integrity" "sha512-P+2gwrFqx8lhew375MQHHeTlY8AuOJSrGf0R5ddkEndUkmwpgUob/vQuBD1V22/Cw1/lJr4x+EjllSezBThzBg==" + "resolved" "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.3.tgz" + "version" "0.5.3" + dependencies: + "minimist" "^1.2.5" + +"mocha@^7.1.1": + "integrity" "sha512-3qQsu3ijNS3GkWcccT5Zw0hf/rWvu1fTN9sPvEd81hlwsr30GX2GcDSSoBxo24IR8FelmrAydGC6/1J5QQP4WA==" + "resolved" "https://registry.npmjs.org/mocha/-/mocha-7.1.1.tgz" + "version" "7.1.1" + dependencies: + "ansi-colors" "3.2.3" + "browser-stdout" "1.3.1" + "chokidar" "3.3.0" + "debug" "3.2.6" + "diff" "3.5.0" + "escape-string-regexp" "1.0.5" + "find-up" "3.0.0" + "glob" "7.1.3" + "growl" "1.10.5" + "he" "1.2.0" + "js-yaml" "3.13.1" + "log-symbols" "3.0.0" + "minimatch" "3.0.4" + "mkdirp" "0.5.3" + "ms" "2.1.1" + "node-environment-flags" "1.0.6" + "object.assign" "4.1.0" + "strip-json-comments" "2.0.1" + "supports-color" "6.0.0" + "which" "1.3.1" + "wide-align" "1.1.3" + "yargs" "13.3.2" + "yargs-parser" "13.1.2" + "yargs-unparser" "1.6.0" + +"ms@^2.1.1", "ms@2.1.1": + "integrity" "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + "resolved" "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz" + "version" "2.1.1" + +"node-environment-flags@1.0.6": + "integrity" "sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==" + "resolved" "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz" + "version" "1.0.6" + dependencies: + "object.getownpropertydescriptors" "^2.0.3" + "semver" "^5.7.0" + +"normalize-path@^3.0.0", "normalize-path@~3.0.0": + "integrity" "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + "resolved" "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz" + "version" "3.0.0" + +"object-inspect@^1.7.0": + "integrity" "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==" + "resolved" "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz" + "version" "1.7.0" + +"object-keys@^1.0.11", "object-keys@^1.0.12", "object-keys@^1.1.1": + "integrity" "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + "resolved" "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz" + "version" "1.1.1" + +"object.assign@^4.1.0", "object.assign@4.1.0": + "integrity" "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==" + "resolved" "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz" + "version" "4.1.0" + dependencies: + "define-properties" "^1.1.2" + "function-bind" "^1.1.1" + "has-symbols" "^1.0.0" + "object-keys" "^1.0.11" + +"object.getownpropertydescriptors@^2.0.3": + "integrity" "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==" + "resolved" "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz" + "version" "2.1.0" + dependencies: + "define-properties" "^1.1.3" + "es-abstract" "^1.17.0-next.1" + +"once@^1.3.0": + "integrity" "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=" + "resolved" "https://registry.npmjs.org/once/-/once-1.4.0.tgz" + "version" "1.4.0" + dependencies: + "wrappy" "1" + +"ot-fuzzer@1.3": + "integrity" "sha512-cZNdwrLkJ+p+/go94KFFmuiXsXy66eot+TBrStEMTxs4tGCQl43EKeqpTe9GpgVlftpWqzbzetIuZsg2s98QqQ==" + "resolved" "https://registry.npmjs.org/ot-fuzzer/-/ot-fuzzer-1.3.0.tgz" + "version" "1.3.0" + dependencies: + "cli-progress" "^2.1.1" + "seedrandom" "^2.4.4" + +"ot-simple@^1.0.0": + "integrity" "sha1-B42ED4HqOq04y+aUdfgbLGdU+1A=" + "resolved" "https://registry.npmjs.org/ot-simple/-/ot-simple-1.0.0.tgz" + "version" "1.0.0" + +"ot-text-unicode@4": + "integrity" "sha512-W7ZLU8QXesY2wagYFv47zErXud3E93FGImmSGJsQnBzE+idcPPyo2u2KMilIrTwBh4pbCizy71qRjmmV6aDhcQ==" + "resolved" "https://registry.npmjs.org/ot-text-unicode/-/ot-text-unicode-4.0.0.tgz" + "version" "4.0.0" + dependencies: + "unicount" "1.1" + +"p-limit@^2.0.0": + "integrity" "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==" + "resolved" "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz" + "version" "2.2.2" + dependencies: + "p-try" "^2.0.0" + +"p-locate@^3.0.0": + "integrity" "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==" + "resolved" "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz" + "version" "3.0.0" + dependencies: + "p-limit" "^2.0.0" + +"p-try@^2.0.0": + "integrity" "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + "resolved" "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz" + "version" "2.2.0" + +"path-exists@^3.0.0": + "integrity" "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + "resolved" "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz" + "version" "3.0.0" + +"path-is-absolute@^1.0.0": + "integrity" "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + "resolved" "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz" + "version" "1.0.1" + +"picomatch@^2.0.4": + "integrity" "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==" + "resolved" "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz" + "version" "2.2.2" + +"readdirp@~3.2.0": + "integrity" "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==" + "resolved" "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz" + "version" "3.2.0" + dependencies: + "picomatch" "^2.0.4" + +"require-directory@^2.1.1": + "integrity" "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + "resolved" "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz" + "version" "2.1.1" + +"require-main-filename@^2.0.0": + "integrity" "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + "resolved" "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz" + "version" "2.0.0" + +"seedrandom@^2.4.4": + "integrity" "sha512-9A+PDmgm+2du77B5i0Ip2cxOqqHjgNxnBgglxLcX78A2D6c2rTo61z4jnVABpF4cKeDMDG+cmXXvdnqse2VqMA==" + "resolved" "https://registry.npmjs.org/seedrandom/-/seedrandom-2.4.4.tgz" + "version" "2.4.4" + +"semver@^5.7.0": + "integrity" "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + "resolved" "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz" + "version" "5.7.1" + +"set-blocking@^2.0.0": + "integrity" "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + "resolved" "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz" + "version" "2.0.0" + +"source-map-support@~0.5.12": + "integrity" "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==" + "resolved" "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz" + "version" "0.5.16" + dependencies: + "buffer-from" "^1.0.0" + "source-map" "^0.6.0" + +"source-map@^0.6.0", "source-map@~0.6.1": + "integrity" "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" + "resolved" "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz" + "version" "0.6.1" + +"sprintf-js@~1.0.2": + "integrity" "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" + "resolved" "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz" + "version" "1.0.3" + +"string-width@^1.0.2 || 2", "string-width@^2.1.1": + "integrity" "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==" + "resolved" "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz" + "version" "2.1.1" + dependencies: + "is-fullwidth-code-point" "^2.0.0" + "strip-ansi" "^4.0.0" + +"string-width@^3.0.0": + "integrity" "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==" + "resolved" "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz" + "version" "3.1.0" + dependencies: + "emoji-regex" "^7.0.1" + "is-fullwidth-code-point" "^2.0.0" + "strip-ansi" "^5.1.0" + +"string-width@^3.1.0": + "integrity" "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==" + "resolved" "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz" + "version" "3.1.0" + dependencies: + "emoji-regex" "^7.0.1" + "is-fullwidth-code-point" "^2.0.0" + "strip-ansi" "^5.1.0" + +"string.prototype.trimleft@^2.1.1": + "integrity" "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==" + "resolved" "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz" + "version" "2.1.1" + dependencies: + "define-properties" "^1.1.3" + "function-bind" "^1.1.1" + +"string.prototype.trimright@^2.1.1": + "integrity" "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==" + "resolved" "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz" + "version" "2.1.1" + dependencies: + "define-properties" "^1.1.3" + "function-bind" "^1.1.1" + +"strip-ansi@^4.0.0": + "integrity" "sha1-qEeQIusaw2iocTibY1JixQXuNo8=" + "resolved" "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz" + "version" "4.0.0" + dependencies: + "ansi-regex" "^3.0.0" + +"strip-ansi@^5.0.0", "strip-ansi@^5.1.0", "strip-ansi@^5.2.0": + "integrity" "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==" + "resolved" "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz" + "version" "5.2.0" + dependencies: + "ansi-regex" "^4.1.0" + +"strip-json-comments@2.0.1": + "integrity" "sha1-PFMZQukIwml8DsNEhYwobHygpgo=" + "resolved" "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz" + "version" "2.0.1" + +"supports-color@^5.3.0": + "integrity" "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==" + "resolved" "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz" + "version" "5.5.0" + dependencies: + "has-flag" "^3.0.0" + +"supports-color@6.0.0": + "integrity" "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==" + "resolved" "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz" + "version" "6.0.0" + dependencies: + "has-flag" "^3.0.0" + +"terser@^4.6.7": + "integrity" "sha512-fmr7M1f7DBly5cX2+rFDvmGBAaaZyPrHYK4mMdHEDAdNTqXSZgSOfqsfGq2HqPGT/1V0foZZuCZFx8CHKgAk3g==" + "resolved" "https://registry.npmjs.org/terser/-/terser-4.6.7.tgz" + "version" "4.6.7" + dependencies: + "commander" "^2.20.0" + "source-map" "~0.6.1" + "source-map-support" "~0.5.12" + +"to-regex-range@^5.0.1": + "integrity" "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==" + "resolved" "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz" + "version" "5.0.1" + dependencies: + "is-number" "^7.0.0" + +"typescript@^3.9.5": + "integrity" "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==" + "resolved" "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz" + "version" "3.9.7" + +"unicount@1.1": + "integrity" "sha512-RlwWt1ywVW4WErPGAVHw/rIuJ2+MxvTME0siJ6lk9zBhpDfExDbspe6SRlWT3qU6AucNjotPl9qAJRVjP7guCQ==" + "resolved" "https://registry.npmjs.org/unicount/-/unicount-1.1.0.tgz" + "version" "1.1.0" + +"which-module@^2.0.0": + "integrity" "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + "resolved" "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz" + "version" "2.0.0" + +"which@1.3.1": + "integrity" "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==" + "resolved" "https://registry.npmjs.org/which/-/which-1.3.1.tgz" + "version" "1.3.1" + dependencies: + "isexe" "^2.0.0" + +"wide-align@1.1.3": + "integrity" "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==" + "resolved" "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz" + "version" "1.1.3" + dependencies: + "string-width" "^1.0.2 || 2" + +"wrap-ansi@^5.1.0": + "integrity" "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==" + "resolved" "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz" + "version" "5.1.0" + dependencies: + "ansi-styles" "^3.2.0" + "string-width" "^3.0.0" + "strip-ansi" "^5.0.0" + +"wrappy@1": + "integrity" "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "resolved" "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz" + "version" "1.0.2" + +"y18n@^4.0.0": + "integrity" "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==" + "resolved" "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz" + "version" "4.0.1" + +"yargs-parser@^13.1.2", "yargs-parser@13.1.2": + "integrity" "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==" + "resolved" "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz" + "version" "13.1.2" + dependencies: + "camelcase" "^5.0.0" + "decamelize" "^1.2.0" + +"yargs-unparser@1.6.0": + "integrity" "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==" + "resolved" "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz" + "version" "1.6.0" + dependencies: + "flat" "^4.1.0" + "lodash" "^4.17.15" + "yargs" "^13.3.0" + +"yargs@^13.3.0", "yargs@13.3.2": + "integrity" "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==" + "resolved" "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz" + "version" "13.3.2" + dependencies: + "cliui" "^5.0.0" + "find-up" "^3.0.0" + "get-caller-file" "^2.0.1" + "require-directory" "^2.1.1" + "require-main-filename" "^2.0.0" + "set-blocking" "^2.0.0" + "string-width" "^3.0.0" + "which-module" "^2.0.0" + "y18n" "^4.0.0" + "yargs-parser" "^13.1.2" From 9857aad9398e5b67ee787a1418eff8b77056d4d2 Mon Sep 17 00:00:00 2001 From: Curran Date: Sat, 4 Mar 2023 05:48:12 -0500 Subject: [PATCH 5/5] Yet another failed attempt at json1presence --- package-lock.json | 1172 ++++++++++++++++++++++++++++++-- package.json | 1 + src/App.jsx | 2 +- src/json1presence/lib/index.ts | 2 +- vite.config.js | 5 +- 5 files changed, 1123 insertions(+), 59 deletions(-) diff --git a/package-lock.json b/package-lock.json index 682872c3..0038e982 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,7 @@ "vzcode": "cli.js" }, "devDependencies": { + "@originjs/vite-plugin-commonjs": "^1.0.3", "@types/react": "^18.0.28", "@types/react-dom": "^18.0.11", "@vitejs/plugin-react": "^3.1.0", @@ -540,6 +541,246 @@ "w3c-keyname": "^2.2.4" } }, + "node_modules/@esbuild/android-arm": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.16.17.tgz", + "integrity": "sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.16.17.tgz", + "integrity": "sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.16.17.tgz", + "integrity": "sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.17.tgz", + "integrity": "sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.16.17.tgz", + "integrity": "sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.17.tgz", + "integrity": "sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.16.17.tgz", + "integrity": "sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.16.17.tgz", + "integrity": "sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.16.17.tgz", + "integrity": "sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.16.17.tgz", + "integrity": "sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.14.54.tgz", + "integrity": "sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.16.17.tgz", + "integrity": "sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.16.17.tgz", + "integrity": "sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.16.17.tgz", + "integrity": "sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.16.17.tgz", + "integrity": "sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@esbuild/linux-x64": { "version": "0.16.17", "cpu": [ @@ -555,6 +796,102 @@ "node": ">=12" } }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.16.17.tgz", + "integrity": "sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.16.17.tgz", + "integrity": "sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.16.17.tgz", + "integrity": "sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.16.17.tgz", + "integrity": "sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.16.17.tgz", + "integrity": "sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.17.tgz", + "integrity": "sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.1.1", "dev": true, @@ -648,6 +985,51 @@ "@lezer/highlight": "^1.0.0" } }, + "node_modules/@originjs/vite-plugin-commonjs": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@originjs/vite-plugin-commonjs/-/vite-plugin-commonjs-1.0.3.tgz", + "integrity": "sha512-KuEXeGPptM2lyxdIEJ4R11+5ztipHoE7hy8ClZt3PYaOVQ/pyngd2alaSrPnwyFeOW1UagRBaQ752aA1dTMdOQ==", + "dev": true, + "dependencies": { + "esbuild": "^0.14.14" + } + }, + "node_modules/@originjs/vite-plugin-commonjs/node_modules/esbuild": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.54.tgz", + "integrity": "sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/linux-loong64": "0.14.54", + "esbuild-android-64": "0.14.54", + "esbuild-android-arm64": "0.14.54", + "esbuild-darwin-64": "0.14.54", + "esbuild-darwin-arm64": "0.14.54", + "esbuild-freebsd-64": "0.14.54", + "esbuild-freebsd-arm64": "0.14.54", + "esbuild-linux-32": "0.14.54", + "esbuild-linux-64": "0.14.54", + "esbuild-linux-arm": "0.14.54", + "esbuild-linux-arm64": "0.14.54", + "esbuild-linux-mips64le": "0.14.54", + "esbuild-linux-ppc64le": "0.14.54", + "esbuild-linux-riscv64": "0.14.54", + "esbuild-linux-s390x": "0.14.54", + "esbuild-netbsd-64": "0.14.54", + "esbuild-openbsd-64": "0.14.54", + "esbuild-sunos-64": "0.14.54", + "esbuild-windows-32": "0.14.54", + "esbuild-windows-64": "0.14.54", + "esbuild-windows-arm64": "0.14.54" + } + }, "node_modules/@teamwork/websocket-json-stream": { "version": "2.0.0", "license": "MIT" @@ -946,75 +1328,411 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/depd": { - "version": "2.0.0", - "license": "MIT", + "node_modules/depd": { + "version": "2.0.0", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/diff-match-patch": { + "version": "1.0.5", + "license": "Apache-2.0" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "license": "MIT" + }, + "node_modules/electron-to-chromium": { + "version": "1.4.284", + "dev": true, + "license": "ISC" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/esbuild": { + "version": "0.16.17", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.16.17", + "@esbuild/android-arm64": "0.16.17", + "@esbuild/android-x64": "0.16.17", + "@esbuild/darwin-arm64": "0.16.17", + "@esbuild/darwin-x64": "0.16.17", + "@esbuild/freebsd-arm64": "0.16.17", + "@esbuild/freebsd-x64": "0.16.17", + "@esbuild/linux-arm": "0.16.17", + "@esbuild/linux-arm64": "0.16.17", + "@esbuild/linux-ia32": "0.16.17", + "@esbuild/linux-loong64": "0.16.17", + "@esbuild/linux-mips64el": "0.16.17", + "@esbuild/linux-ppc64": "0.16.17", + "@esbuild/linux-riscv64": "0.16.17", + "@esbuild/linux-s390x": "0.16.17", + "@esbuild/linux-x64": "0.16.17", + "@esbuild/netbsd-x64": "0.16.17", + "@esbuild/openbsd-x64": "0.16.17", + "@esbuild/sunos-x64": "0.16.17", + "@esbuild/win32-arm64": "0.16.17", + "@esbuild/win32-ia32": "0.16.17", + "@esbuild/win32-x64": "0.16.17" + } + }, + "node_modules/esbuild-android-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.54.tgz", + "integrity": "sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-android-arm64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.54.tgz", + "integrity": "sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-darwin-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.54.tgz", + "integrity": "sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-darwin-arm64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.54.tgz", + "integrity": "sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-freebsd-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.54.tgz", + "integrity": "sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-freebsd-arm64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.54.tgz", + "integrity": "sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-32": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.54.tgz", + "integrity": "sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.54.tgz", + "integrity": "sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-arm": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.54.tgz", + "integrity": "sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-arm64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.54.tgz", + "integrity": "sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-mips64le": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.54.tgz", + "integrity": "sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-ppc64le": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.54.tgz", + "integrity": "sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-riscv64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.54.tgz", + "integrity": "sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-s390x": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.54.tgz", + "integrity": "sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-netbsd-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.54.tgz", + "integrity": "sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-openbsd-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.54.tgz", + "integrity": "sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-sunos-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.54.tgz", + "integrity": "sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], "engines": { - "node": ">= 0.8" + "node": ">=12" } }, - "node_modules/destroy": { - "version": "1.2.0", - "license": "MIT", + "node_modules/esbuild-windows-32": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.54.tgz", + "integrity": "sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "node": ">=12" } }, - "node_modules/diff-match-patch": { - "version": "1.0.5", - "license": "Apache-2.0" - }, - "node_modules/ee-first": { - "version": "1.1.1", - "license": "MIT" - }, - "node_modules/electron-to-chromium": { - "version": "1.4.284", + "node_modules/esbuild-windows-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.54.tgz", + "integrity": "sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==", + "cpu": [ + "x64" + ], "dev": true, - "license": "ISC" + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } }, - "node_modules/encodeurl": { - "version": "1.0.2", - "license": "MIT", + "node_modules/esbuild-windows-arm64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.54.tgz", + "integrity": "sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">= 0.8" + "node": ">=12" } }, - "node_modules/esbuild": { + "node_modules/esbuild/node_modules/@esbuild/linux-loong64": { "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.16.17.tgz", + "integrity": "sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==", + "cpu": [ + "loong64" + ], "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/android-arm": "0.16.17", - "@esbuild/android-arm64": "0.16.17", - "@esbuild/android-x64": "0.16.17", - "@esbuild/darwin-arm64": "0.16.17", - "@esbuild/darwin-x64": "0.16.17", - "@esbuild/freebsd-arm64": "0.16.17", - "@esbuild/freebsd-x64": "0.16.17", - "@esbuild/linux-arm": "0.16.17", - "@esbuild/linux-arm64": "0.16.17", - "@esbuild/linux-ia32": "0.16.17", - "@esbuild/linux-loong64": "0.16.17", - "@esbuild/linux-mips64el": "0.16.17", - "@esbuild/linux-ppc64": "0.16.17", - "@esbuild/linux-riscv64": "0.16.17", - "@esbuild/linux-s390x": "0.16.17", - "@esbuild/linux-x64": "0.16.17", - "@esbuild/netbsd-x64": "0.16.17", - "@esbuild/openbsd-x64": "0.16.17", - "@esbuild/sunos-x64": "0.16.17", - "@esbuild/win32-arm64": "0.16.17", - "@esbuild/win32-ia32": "0.16.17", - "@esbuild/win32-x64": "0.16.17" } }, "node_modules/escalade": { @@ -1481,14 +2199,16 @@ }, "node_modules/ot-json1": { "version": "1.0.2", - "license": "ISC", + "resolved": "https://registry.npmjs.org/ot-json1/-/ot-json1-1.0.2.tgz", + "integrity": "sha512-IhxkqVWQqlkWULoi/Q2AdzKk0N5vQRbUMUwubFXFCPcY4TsOZjmp2YKrk0/z1TeiECPadWEK060sdFdQ3Grokg==", "dependencies": { "ot-text-unicode": "4" } }, "node_modules/ot-text-unicode": { "version": "4.0.0", - "license": "ISC", + "resolved": "https://registry.npmjs.org/ot-text-unicode/-/ot-text-unicode-4.0.0.tgz", + "integrity": "sha512-W7ZLU8QXesY2wagYFv47zErXud3E93FGImmSGJsQnBzE+idcPPyo2u2KMilIrTwBh4pbCizy71qRjmmV6aDhcQ==", "dependencies": { "unicount": "1.1" } @@ -2336,11 +3056,158 @@ "w3c-keyname": "^2.2.4" } }, + "@esbuild/android-arm": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.16.17.tgz", + "integrity": "sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.16.17.tgz", + "integrity": "sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==", + "dev": true, + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.16.17.tgz", + "integrity": "sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.16.17.tgz", + "integrity": "sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.16.17.tgz", + "integrity": "sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.16.17.tgz", + "integrity": "sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.16.17.tgz", + "integrity": "sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.16.17.tgz", + "integrity": "sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.16.17.tgz", + "integrity": "sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.16.17.tgz", + "integrity": "sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.14.54.tgz", + "integrity": "sha512-bZBrLAIX1kpWelV0XemxBZllyRmM6vgFQQG2GdNb+r3Fkp0FOh1NJSvekXDs7jq70k4euu1cryLMfU+mTXlEpw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.16.17.tgz", + "integrity": "sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.16.17.tgz", + "integrity": "sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==", + "dev": true, + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.16.17.tgz", + "integrity": "sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.16.17.tgz", + "integrity": "sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==", + "dev": true, + "optional": true + }, "@esbuild/linux-x64": { "version": "0.16.17", "dev": true, "optional": true }, + "@esbuild/netbsd-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.16.17.tgz", + "integrity": "sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.16.17.tgz", + "integrity": "sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==", + "dev": true, + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.16.17.tgz", + "integrity": "sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==", + "dev": true, + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.16.17.tgz", + "integrity": "sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==", + "dev": true, + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.16.17.tgz", + "integrity": "sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==", + "dev": true, + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.16.17.tgz", + "integrity": "sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==", + "dev": true, + "optional": true + }, "@jridgewell/gen-mapping": { "version": "0.1.1", "dev": true, @@ -2413,6 +3280,46 @@ "@lezer/highlight": "^1.0.0" } }, + "@originjs/vite-plugin-commonjs": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@originjs/vite-plugin-commonjs/-/vite-plugin-commonjs-1.0.3.tgz", + "integrity": "sha512-KuEXeGPptM2lyxdIEJ4R11+5ztipHoE7hy8ClZt3PYaOVQ/pyngd2alaSrPnwyFeOW1UagRBaQ752aA1dTMdOQ==", + "dev": true, + "requires": { + "esbuild": "^0.14.14" + }, + "dependencies": { + "esbuild": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.54.tgz", + "integrity": "sha512-Cy9llcy8DvET5uznocPyqL3BFRrFXSVqbgpMJ9Wz8oVjZlh/zUSNbPRbov0VX7VxN2JH1Oa0uNxZ7eLRb62pJA==", + "dev": true, + "requires": { + "@esbuild/linux-loong64": "0.14.54", + "esbuild-android-64": "0.14.54", + "esbuild-android-arm64": "0.14.54", + "esbuild-darwin-64": "0.14.54", + "esbuild-darwin-arm64": "0.14.54", + "esbuild-freebsd-64": "0.14.54", + "esbuild-freebsd-arm64": "0.14.54", + "esbuild-linux-32": "0.14.54", + "esbuild-linux-64": "0.14.54", + "esbuild-linux-arm": "0.14.54", + "esbuild-linux-arm64": "0.14.54", + "esbuild-linux-mips64le": "0.14.54", + "esbuild-linux-ppc64le": "0.14.54", + "esbuild-linux-riscv64": "0.14.54", + "esbuild-linux-s390x": "0.14.54", + "esbuild-netbsd-64": "0.14.54", + "esbuild-openbsd-64": "0.14.54", + "esbuild-sunos-64": "0.14.54", + "esbuild-windows-32": "0.14.54", + "esbuild-windows-64": "0.14.54", + "esbuild-windows-arm64": "0.14.54" + } + } + } + }, "@teamwork/websocket-json-stream": { "version": "2.0.0" }, @@ -2657,8 +3564,157 @@ "@esbuild/win32-arm64": "0.16.17", "@esbuild/win32-ia32": "0.16.17", "@esbuild/win32-x64": "0.16.17" + }, + "dependencies": { + "@esbuild/linux-loong64": { + "version": "0.16.17", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.16.17.tgz", + "integrity": "sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==", + "dev": true, + "optional": true + } } }, + "esbuild-android-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.54.tgz", + "integrity": "sha512-Tz2++Aqqz0rJ7kYBfz+iqyE3QMycD4vk7LBRyWaAVFgFtQ/O8EJOnVmTOiDWYZ/uYzB4kvP+bqejYdVKzE5lAQ==", + "dev": true, + "optional": true + }, + "esbuild-android-arm64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.54.tgz", + "integrity": "sha512-F9E+/QDi9sSkLaClO8SOV6etqPd+5DgJje1F9lOWoNncDdOBL2YF59IhsWATSt0TLZbYCf3pNlTHvVV5VfHdvg==", + "dev": true, + "optional": true + }, + "esbuild-darwin-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.54.tgz", + "integrity": "sha512-jtdKWV3nBviOd5v4hOpkVmpxsBy90CGzebpbO9beiqUYVMBtSc0AL9zGftFuBon7PNDcdvNCEuQqw2x0wP9yug==", + "dev": true, + "optional": true + }, + "esbuild-darwin-arm64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.54.tgz", + "integrity": "sha512-OPafJHD2oUPyvJMrsCvDGkRrVCar5aVyHfWGQzY1dWnzErjrDuSETxwA2HSsyg2jORLY8yBfzc1MIpUkXlctmw==", + "dev": true, + "optional": true + }, + "esbuild-freebsd-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.54.tgz", + "integrity": "sha512-OKwd4gmwHqOTp4mOGZKe/XUlbDJ4Q9TjX0hMPIDBUWWu/kwhBAudJdBoxnjNf9ocIB6GN6CPowYpR/hRCbSYAg==", + "dev": true, + "optional": true + }, + "esbuild-freebsd-arm64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.54.tgz", + "integrity": "sha512-sFwueGr7OvIFiQT6WeG0jRLjkjdqWWSrfbVwZp8iMP+8UHEHRBvlaxL6IuKNDwAozNUmbb8nIMXa7oAOARGs1Q==", + "dev": true, + "optional": true + }, + "esbuild-linux-32": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.54.tgz", + "integrity": "sha512-1ZuY+JDI//WmklKlBgJnglpUL1owm2OX+8E1syCD6UAxcMM/XoWd76OHSjl/0MR0LisSAXDqgjT3uJqT67O3qw==", + "dev": true, + "optional": true + }, + "esbuild-linux-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.54.tgz", + "integrity": "sha512-EgjAgH5HwTbtNsTqQOXWApBaPVdDn7XcK+/PtJwZLT1UmpLoznPd8c5CxqsH2dQK3j05YsB3L17T8vE7cp4cCg==", + "dev": true, + "optional": true + }, + "esbuild-linux-arm": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.54.tgz", + "integrity": "sha512-qqz/SjemQhVMTnvcLGoLOdFpCYbz4v4fUo+TfsWG+1aOu70/80RV6bgNpR2JCrppV2moUQkww+6bWxXRL9YMGw==", + "dev": true, + "optional": true + }, + "esbuild-linux-arm64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.54.tgz", + "integrity": "sha512-WL71L+0Rwv+Gv/HTmxTEmpv0UgmxYa5ftZILVi2QmZBgX3q7+tDeOQNqGtdXSdsL8TQi1vIaVFHUPDe0O0kdig==", + "dev": true, + "optional": true + }, + "esbuild-linux-mips64le": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.54.tgz", + "integrity": "sha512-qTHGQB8D1etd0u1+sB6p0ikLKRVuCWhYQhAHRPkO+OF3I/iSlTKNNS0Lh2Oc0g0UFGguaFZZiPJdJey3AGpAlw==", + "dev": true, + "optional": true + }, + "esbuild-linux-ppc64le": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.54.tgz", + "integrity": "sha512-j3OMlzHiqwZBDPRCDFKcx595XVfOfOnv68Ax3U4UKZ3MTYQB5Yz3X1mn5GnodEVYzhtZgxEBidLWeIs8FDSfrQ==", + "dev": true, + "optional": true + }, + "esbuild-linux-riscv64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.54.tgz", + "integrity": "sha512-y7Vt7Wl9dkOGZjxQZnDAqqn+XOqFD7IMWiewY5SPlNlzMX39ocPQlOaoxvT4FllA5viyV26/QzHtvTjVNOxHZg==", + "dev": true, + "optional": true + }, + "esbuild-linux-s390x": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.54.tgz", + "integrity": "sha512-zaHpW9dziAsi7lRcyV4r8dhfG1qBidQWUXweUjnw+lliChJqQr+6XD71K41oEIC3Mx1KStovEmlzm+MkGZHnHA==", + "dev": true, + "optional": true + }, + "esbuild-netbsd-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.54.tgz", + "integrity": "sha512-PR01lmIMnfJTgeU9VJTDY9ZerDWVFIUzAtJuDHwwceppW7cQWjBBqP48NdeRtoP04/AtO9a7w3viI+PIDr6d+w==", + "dev": true, + "optional": true + }, + "esbuild-openbsd-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.54.tgz", + "integrity": "sha512-Qyk7ikT2o7Wu76UsvvDS5q0amJvmRzDyVlL0qf5VLsLchjCa1+IAvd8kTBgUxD7VBUUVgItLkk609ZHUc1oCaw==", + "dev": true, + "optional": true + }, + "esbuild-sunos-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.54.tgz", + "integrity": "sha512-28GZ24KmMSeKi5ueWzMcco6EBHStL3B6ubM7M51RmPwXQGLe0teBGJocmWhgwccA1GeFXqxzILIxXpHbl9Q/Kw==", + "dev": true, + "optional": true + }, + "esbuild-windows-32": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.54.tgz", + "integrity": "sha512-T+rdZW19ql9MjS7pixmZYVObd9G7kcaZo+sETqNH4RCkuuYSuv9AGHUVnPoP9hhuE1WM1ZimHz1CIBHBboLU7w==", + "dev": true, + "optional": true + }, + "esbuild-windows-64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.54.tgz", + "integrity": "sha512-AoHTRBUuYwXtZhjXZbA1pGfTo8cJo3vZIcWGLiUcTNgHpJJMC1rVA44ZereBHMJtotyN71S8Qw0npiCIkW96cQ==", + "dev": true, + "optional": true + }, + "esbuild-windows-arm64": { + "version": "0.14.54", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.54.tgz", + "integrity": "sha512-M0kuUvXhot1zOISQGXwWn6YtS+Y/1RT9WrVIOywZnJHo3jCDyewAc79aKNQWFCQm+xNHVTq9h8dZKvygoXQQRg==", + "dev": true, + "optional": true + }, "escalade": { "version": "3.1.1", "dev": true @@ -2928,12 +3984,16 @@ }, "ot-json1": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/ot-json1/-/ot-json1-1.0.2.tgz", + "integrity": "sha512-IhxkqVWQqlkWULoi/Q2AdzKk0N5vQRbUMUwubFXFCPcY4TsOZjmp2YKrk0/z1TeiECPadWEK060sdFdQ3Grokg==", "requires": { "ot-text-unicode": "4" } }, "ot-text-unicode": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/ot-text-unicode/-/ot-text-unicode-4.0.0.tgz", + "integrity": "sha512-W7ZLU8QXesY2wagYFv47zErXud3E93FGImmSGJsQnBzE+idcPPyo2u2KMilIrTwBh4pbCizy71qRjmmV6aDhcQ==", "requires": { "unicount": "1.1" } diff --git a/package.json b/package.json index 40aecc38..395593ef 100644 --- a/package.json +++ b/package.json @@ -59,6 +59,7 @@ "ws": "^8.12.1" }, "devDependencies": { + "@originjs/vite-plugin-commonjs": "^1.0.3", "@types/react": "^18.0.28", "@types/react-dom": "^18.0.11", "@vitejs/plugin-react": "^3.1.0", diff --git a/src/App.jsx b/src/App.jsx index a0e1d2ac..d1f73550 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -2,7 +2,7 @@ import { useState, useEffect, useMemo, useCallback } from 'react'; import ShareDBClient from 'sharedb-client-browser/sharedb-client-json1-browser.js'; import { CodeEditor } from './CodeEditor'; import { diff } from './diff'; -import * as json1 from './json1presence/dist/index.js'; +import json1 from './json1presence/dist/'; import './style.css'; console.log(json1) diff --git a/src/json1presence/lib/index.ts b/src/json1presence/lib/index.ts index fcade079..58cc4ed6 100644 --- a/src/json1presence/lib/index.ts +++ b/src/json1presence/lib/index.ts @@ -1,6 +1,6 @@ export * from './json1.release.js' export {ReadCursor, WriteCursor} from './cursor.js' -export { +export type{ Doc, JSONOp, JSONOpList, JSONOpComponent, Key, Path, Conflict, ConflictType, diff --git a/vite.config.js b/vite.config.js index e55cf3e3..07738d33 100644 --- a/vite.config.js +++ b/vite.config.js @@ -1,9 +1,12 @@ import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; +// Temporary for JSON1presence import +import { viteCommonjs } from '@originjs/vite-plugin-commonjs'; + // https://vitejs.dev/config/ export default defineConfig({ - plugins: [react()], + plugins: [react(),viteCommonjs()], server: { proxy: { '/ws': {