├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Makefile ├── README.md ├── lib ├── cursor.ts ├── deepClone.ts ├── deepEqual.ts ├── index.ts ├── json1.release.ts ├── json1.ts ├── log.ts └── types.ts ├── package-lock.json ├── package.json ├── spec.md ├── test ├── cursor.js ├── fuzzer.js ├── genOp.js ├── genTextOp.js ├── immutable.js ├── ops.json └── test.js ├── tracer.js ├── tsconfig.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | .*.swp 2 | node_modules 3 | dist -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - 10 5 | 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # HEAD 2 | 3 | - Fixed generated sourcemap for release build 4 | 5 | # 0.3.0 6 | 7 | - Moved main project to typescript 8 | - Fixed some weird issues in apply where it would try to descend inside primitive values 9 | - Decaffinate tests (#8 - Thanks @curran) 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean all dist/json1.release.js 2 | 3 | #terser -m -d process.env.JSON1_RELEASE_MODE=true -c pure_funcs=log,keep_fargs=false,passes=2 --wrap fn < lib/json1.js 4 | 5 | all: dist/json1.release.js 6 | 7 | # Look, this isn't really necessary, but it allows me to add the RELEASE flag 8 | # to the library and add a lot of additional (computationally + code size) 9 | # expensive checks throughout transform which should otherwise be removed. In 10 | # release mode we also remove some debugging output libraries which we don't 11 | # want webpack/browserify to end up pulling in. This can shave off a nontrivial 12 | # amount of space for the bundler. 13 | 14 | # Basically what this is doing is hard-setting the RELEASE_MODE flag in the 15 | # code so the minifier can elide all if(!RELEASE_MODE) {...} blocks. 16 | # lib/json1.release.js: lib/json1.js 17 | # npx terser -d process.env.JSON1_RELEASE_MODE=true -c pure_funcs=log,keep_fargs=false,passes=2 -b --ecma 7 -o $@ -- $< 18 | 19 | dist/json1.js: lib/json1.ts 20 | npx tsc 21 | dist/json1.release.js: dist/json1.js 22 | npx terser -d process.env.JSON1_RELEASE_MODE=true -c pure_funcs=log,keep_fargs=false,passes=2 -b --ecma 7 -o $@ -- $< 23 | 24 | 25 | clean: 26 | rm -rf dist 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JSON1 2 | 3 | > Status: Usable in practice, but contains a couple super obscure known bugs. See below for details. 4 | 5 | This is an operational transformation type for arbitrary JSON trees. It supports concurrently editing arbitrarily complex nested structures. Fancy features: 6 | 7 | - 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! 8 | - Supports embedded subtypes. For example, you can embed rich text documents using quilljs or something inside your JSON tree 9 | - 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. 10 | 11 | This code it is written to replace [ottypes/json0](https://github.com/ottypes/json0). JSON1 implements a superset of JSON0's functionality. 12 | 13 | The spec for operations is in [spec.md](spec.md). 14 | 15 | 16 | ## Usage 17 | 18 | The JSON library has 2 main APIs: 19 | 20 | - 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`. 21 | - A simple API for creating operations. 22 | 23 | ```javascript 24 | const json1 = require('ot-json1') 25 | 26 | const op1 = json1.moveOp(['a', 'x'], ['a', 'y']) 27 | 28 | // The easiest way to make compound operations is to just compose smaller operations 29 | const op2 = [ 30 | json1.moveOp(['a'], ['b']), 31 | json1.insertOp(['b', 'z'], 'hi there') 32 | ].reduce(json1.type.compose, null) 33 | 34 | // op2 = [['a', {p:0}], ['b', {d:0}, 'x', {i: 'hi there'}]] 35 | 36 | const op1_ = json1.type.transform(op1, op2, 'left') 37 | // op1_ now moves b.x -> b.y instead, because op2 moved 'a' to 'b'. 38 | 39 | let doc = {a: {x: 5}} 40 | doc = json1.type.apply(doc, op2) // doc = {b: {x: 5, z: 'hi there'}} 41 | doc = json1.type.apply(doc, op1_) // doc = {b: {y: 5, z: 'hi there'}} 42 | 43 | 44 | // Using the CP1 diamond property, this is the same as: 45 | 46 | doc = {a: {x: 5}} 47 | doc = json1.type.apply(doc, op1) // doc = {a: {y: 5}} 48 | const op2_ = json1.type.transform(op2, op1, 'right') 49 | doc = json1.type.apply(doc, op2) // doc = {b: {y: 5, z: 'hi there'}} 50 | ``` 51 | 52 | ### Standard operation creation functions 53 | 54 | - `json1.removeOp(path, value?)`: Remove the value at the specified path. Becomes `[...path, {r: value | true}]` 55 | - `json1.moveOp(fromPath, toPath)`: Moves the value at `fromPath` to `toPath`. 56 | - `json1.insertOp(path, value)`: Insert the specified value at the specified path 57 | - `json1.replaceOp(path, oldVal, newVal)`: Replace the object at path with `newVal`. If you don't care about invertibility, pass `true` for oldVal. 58 | - `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. 59 | 60 | These functions all return very simple operations. The easiest way to make more complex operations is to combine these pieces using `compose`. For example: 61 | 62 | ```javascript 63 | const op = [ 64 | json1.insertOp([], {title: '', contents: '', public: false}), 65 | json1.editOp(['title'], 'text-unicode', ['My cool blog entry']), 66 | json1.replaceOp(['public', false, true]) 67 | ].reduce(json1.type.compose, null) 68 | ``` 69 | 70 | ### Conflicts 71 | 72 | > TODO: Describe how this works and how conflict handling is configured 73 | 74 | 75 | ## Interoperability with JSON0 76 | 77 | This library supports a superset of the capabilities of JSON0, but the two types have some important differences: 78 | 79 | - JSON0 notably doesn't support moves between object keys 80 | - JSON0 doesn't support moving child values from one object to another 81 | - 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. 82 | 83 | 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. 84 | 85 | 86 | # Limitations 87 | 88 | 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))`. 89 | 90 | Note that this library is currently in preview release. In practical terms, this means: 91 | 92 | - 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. 93 | - There may also be some reasonably minor API changes before 1.0 94 | - `applyPath` doesn't currently transform cursors inside an edited string document 95 | - `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. 96 | - `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. 97 | - 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 98 | - 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. 99 | 100 | 101 | 102 | ## License 103 | 104 | Copyright (c) 2013-2018, Joseph Gentle <me@josephg.com> 105 | 106 | Permission to use, copy, modify, and/or distribute this software for any 107 | purpose with or without fee is hereby granted, provided that the above 108 | copyright notice and this permission notice appear in all copies. 109 | 110 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 111 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 112 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 113 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 114 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 115 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 116 | PERFORMANCE OF THIS SOFTWARE. 117 | 118 | -------------------------------------------------------------------------------- /lib/cursor.ts: -------------------------------------------------------------------------------- 1 | // This file exposes a cursor interface for iterating over an operation. 2 | // 3 | // The cursor interface supports both reading and writing (appending). 4 | 5 | import {Key, JSONOp, JSONOpComponent, JSONOpList, Path} from './types.js' 6 | 7 | // const log = require('./log') 8 | const log = (...args: any) => {} 9 | function assert(pred: boolean, msg?: string): asserts pred {if (!pred) throw new Error(msg)} 10 | 11 | const isObject = (o: any) => (o != null && typeof o === 'object' && !Array.isArray(o)) 12 | 13 | const isGreaterKey = (a: Key, b: Key) => ( 14 | // All the numbers, then all the letters. Just as the gods of ascii intended. 15 | (typeof a === typeof b) ? 16 | a > b : typeof a === 'string' && typeof b === 'number' 17 | ) 18 | 19 | function copyAll(c: JSONOpComponent, w: WriteCursor) { 20 | for (let _k in c) { 21 | const k = _k as keyof JSONOpComponent // bleh typescript. 22 | w.write(k, c[k]) 23 | } 24 | } 25 | 26 | // It might be dangerous to allow __proto__ as a key path because of 27 | // javascript's prototype semantics, especially because most apps won't 28 | // check operations thoroughly enough. 29 | export const isValidPathItem = (k: Key) => ( 30 | typeof k === 'number' || (typeof k === 'string' && k !== '__proto__') 31 | ) 32 | 33 | class Cursor { 34 | // Each time we descend we store the parent and the index of the child. The 35 | // indexes list is appended twice each time we descend - once with the last 36 | // key index of the parent, and once with the index of the array we're 37 | // descending down. 38 | parents: JSONOp[] = [] 39 | indexes: number[] = [] 40 | 41 | // The index of the last listy child we visited for write cursors. This is used 42 | // to ensure we are traversing the op in order. 43 | lcIdx = -1 44 | 45 | // The way we handle the root of the op is a big hack - its like that because 46 | // the root of an op is different from the rest; it can contain a component 47 | // immediately. 48 | // Except at the root (which could be null), container will always be a list. 49 | container: JSONOp 50 | idx = -1 51 | 52 | constructor(op: JSONOp = null) { 53 | this.container = op 54 | } 55 | 56 | ascend() { 57 | assert(this.parents.length === this.indexes.length / 2) 58 | 59 | if (this.idx === 0) { 60 | if (this.parents.length) { 61 | this.lcIdx = this.indexes.pop()! 62 | this.container = this.parents.pop()! 63 | this.idx = this.indexes.pop()! 64 | } else { 65 | this.lcIdx = 0 66 | this.idx = -1 67 | } 68 | } else { 69 | assert(this.idx > 0) 70 | this.idx-- 71 | if (isObject(this.container![this.idx])) this.idx-- 72 | } 73 | } 74 | 75 | getPath() { 76 | const path: Path = [] 77 | let c = this.container 78 | let p = this.parents.length - 1 79 | let i = this.idx 80 | while (i >= 0) { 81 | path.unshift(c![i] as Key) 82 | if (i === 0) { 83 | i = this.indexes[p*2] 84 | c = this.parents[p--] 85 | } else { 86 | i -= isObject(c![i-1]) ? 2 : 1 87 | } 88 | } 89 | return path 90 | } 91 | } 92 | 93 | export class ReadCursor extends Cursor { 94 | get() { return this.container ? this.container.slice(this.idx + 1) : null } 95 | 96 | // Its only valid to call this after descending into a child. 97 | getKey() { 98 | assert(this.container != null, 'Invalid call to getKey before cursor descended') 99 | return this.container[this.idx] as Key 100 | } 101 | 102 | getComponent() { 103 | let c 104 | if (this.container && this.container.length > this.idx + 1 && isObject((c = this.container[this.idx + 1]))) { 105 | return c as JSONOpComponent 106 | } else { 107 | return null 108 | } 109 | } 110 | 111 | descendFirst() { 112 | let i = this.idx + 1 113 | 114 | // Only valid to call this if hasChildren() returns true. 115 | if (!this.container 116 | || i >= this.container.length 117 | || (isObject(this.container[i]) && (i + 1) >= this.container.length)) { 118 | return false 119 | } 120 | 121 | if (isObject(this.container[i])) i++ // Skip component 122 | const firstChild = this.container[i] 123 | 124 | if (Array.isArray(firstChild)) { 125 | this.indexes.push(this.idx) 126 | this.parents.push(this.container) 127 | this.indexes.push(i) 128 | this.idx = 0 129 | this.container = firstChild 130 | } else { 131 | this.idx = i 132 | } 133 | return true 134 | } 135 | 136 | nextSibling() { 137 | assert(this.parents.length === this.indexes.length / 2) 138 | // Children are inline or we're at the root 139 | if (this.idx > 0 || this.parents.length === 0) return false 140 | 141 | const i = this.indexes[this.indexes.length - 1] + 1 142 | const c = this.parents[this.parents.length - 1]! 143 | if (i >= c.length) return false // Past the end of the listy children. 144 | 145 | assert(!isNaN(i)) 146 | this.indexes[this.indexes.length - 1] = i 147 | this.container = c[i] as JSONOpList 148 | // idx = 0 but it should be zero anyway. 149 | return true 150 | } 151 | 152 | private _init(_container: JSONOp, _idx: number, _parents: JSONOp[], _indexes: number[]) { // Internal for clone 153 | // parents.length = indexes.length = 0 154 | this.container = _container 155 | this.idx = _idx 156 | 157 | // Only need these for getPath() 158 | this.parents = _parents.slice() 159 | this.indexes = _indexes.slice() 160 | } 161 | 162 | clone() { 163 | const c = new ReadCursor() 164 | c._init(this.container, this.idx, this.parents, this.indexes) 165 | return c 166 | } 167 | 168 | *[Symbol.iterator]() { 169 | // Iterate through the child keys. At each key the cursor will be 170 | // pointing at the child the key represents. 171 | if (!this.descendFirst()) return 172 | 173 | do yield this.getKey() 174 | while (this.nextSibling()) 175 | 176 | this.ascend() 177 | } 178 | 179 | 180 | // TODO(cleanup): Consider moving these functions out of cursor, since 181 | // they're really just helper methods. 182 | 183 | // It'd be really nice to do this using generators. 184 | traverse(w: W, fn: (c: JSONOpComponent, w: W) => void) { 185 | const c = this.getComponent() 186 | if (c) fn(c, w) 187 | 188 | for (const key of this) { 189 | if (w) w.descend(key) 190 | this.traverse(w, fn) 191 | if (w) w.ascend() 192 | } 193 | } 194 | 195 | eachPick(w: W, fn: (slot: number, w: W) => void) { 196 | this.traverse(w, (c, w) => { if (c.p != null) fn(c.p, w) }) 197 | } 198 | eachDrop(w: W, fn: (slot: number, w: W) => void) { 199 | this.traverse(w, (c, w) => { if (c.d != null) fn(c.d, w) }) 200 | } 201 | } 202 | 203 | export class WriteCursor extends Cursor { 204 | pendingDescent: Path = [] 205 | 206 | private _op: JSONOp // The op we're writing to. Returned via .get() 207 | 208 | constructor(op: JSONOp = null) { 209 | super(op) 210 | this._op = op 211 | } 212 | 213 | private flushDescent() { 214 | // After flushDescent is called, container will always be non-null. 215 | assert(this.parents.length === this.indexes.length / 2) 216 | 217 | if (this.container === null) this._op = this.container = [] 218 | 219 | for (let j = 0; j < this.pendingDescent.length; j++) { 220 | const k = this.pendingDescent[j] 221 | // log('fd', k) 222 | let i = this.idx + 1 223 | 224 | // Skip the component here, if any. 225 | if (i < this.container.length && isObject(this.container[i])) i++ 226 | 227 | assert(i === this.container.length || !isObject(this.container[i])) 228 | 229 | if (i === this.container.length) { 230 | // Just append a child here. 231 | this.container.push(k) 232 | this.idx = i 233 | 234 | } else if (this.container[i] === k) { 235 | // Traverse right... 236 | this.idx = i 237 | 238 | } else { 239 | if (!Array.isArray(this.container[i])) { 240 | // Its not an array. Its not the same as me. I must have a new child! 241 | // log('bundle inside', i, lcIdx) 242 | const oldChild = this.container.splice(i, this.container.length - i) 243 | this.container.push(oldChild) 244 | if (this.lcIdx > -1) this.lcIdx = i 245 | } 246 | 247 | this.indexes.push(this.idx) 248 | this.parents.push(this.container) 249 | 250 | if (this.lcIdx !== -1) { 251 | assert(isGreaterKey(k, (this.container[this.lcIdx] as JSONOpList)[0] as Key)) 252 | i = this.lcIdx + 1 253 | this.lcIdx = -1 254 | } 255 | 256 | // Skip until k. 257 | while (i < this.container.length && isGreaterKey(k, (this.container[i] as JSONOpList)[0] as Key)) i++ 258 | 259 | this.indexes.push(i) 260 | this.idx = 0 261 | if (i < this.container.length && (this.container[i] as JSONOpList)[0] === k) { 262 | this.container = this.container[i] as JSONOpList 263 | } else { 264 | // Insert a new child and descend into it. 265 | const child = [k] 266 | this.container.splice(i, 0, child) 267 | // log('-> container', container) 268 | this.container = child as JSONOpList 269 | } 270 | } 271 | } 272 | this.pendingDescent.length = 0 273 | } 274 | 275 | reset() { this.lcIdx = -1 } 276 | 277 | // Creates and returns a component, creating one if need be. You should 278 | // probably write to it immediately - ops are not valid with empty 279 | // components. 280 | getComponent(): JSONOpComponent { 281 | this.flushDescent() 282 | 283 | const i = this.idx + 1 284 | if (i < this.container!.length && isObject(this.container![i])) { 285 | return this.container![i] as JSONOpComponent 286 | } else { 287 | const component = {} 288 | this.container!.splice(i, 0, component) 289 | return component 290 | } 291 | } 292 | 293 | write(key: K, value: JSONOpComponent[K]) { 294 | const component = this.getComponent() 295 | assert(component[key] == null || component[key] === value, 296 | 'Internal consistency error: Overwritten component. File a bug') 297 | component[key] = value 298 | } 299 | 300 | get() { return this._op } 301 | 302 | descend(key: Key) { 303 | if (!isValidPathItem(key)) throw Error('Invalid JSON key') 304 | // log('descend', key) 305 | this.pendingDescent.push(key) 306 | } 307 | 308 | descendPath(path: Path) { 309 | this.pendingDescent.push(...path) 310 | return this 311 | } 312 | 313 | ascend() { 314 | if (this.pendingDescent.length) this.pendingDescent.pop() 315 | else super.ascend() 316 | } 317 | 318 | mergeTree(data: JSONOp, mergeFn = copyAll) { 319 | if (data === null) return 320 | assert(Array.isArray(data)) 321 | if (data === this._op) throw Error('Cannot merge into my own tree') 322 | 323 | // Backup our position... 324 | const _lcIdx = this.lcIdx 325 | const oldDepth = this.parents.length 326 | 327 | let depth = 0 328 | for (let i = 0; i < data.length; i++) { 329 | const c = data[i] 330 | if (typeof c === 'string' || typeof c === 'number') { 331 | depth++ 332 | this.descend(c) 333 | } else if (Array.isArray(c)) { 334 | this.mergeTree(c, mergeFn) 335 | } else if (typeof c === 'object') { 336 | mergeFn(c, this) 337 | } 338 | } 339 | 340 | // And reset. 341 | while (depth--) this.ascend() 342 | 343 | // We might have had pending descents that we've flushed out. Reset 344 | // to the start of the descent. 345 | this.lcIdx = (this.parents.length === oldDepth) ? _lcIdx : -1 346 | } 347 | 348 | at(path: Path, fn: (w: WriteCursor) => void) { 349 | this.descendPath(path) 350 | fn(this) 351 | for (let i = 0; i < path.length; i++) this.ascend() 352 | return this 353 | } 354 | 355 | // This is used by helpers, so the strict ordering guarantees are 356 | // relaxed. 357 | writeAtPath(path: Path, key: K, value: JSONOpComponent[K]) { 358 | this.at(path, () => this.write(key, value)) 359 | 360 | // Allows for multiple writeAtPath calls to relax ordering. 361 | this.reset() 362 | return this 363 | } 364 | 365 | writeMove(path1: Path, path2: Path, slot = 0) { 366 | return this 367 | .writeAtPath(path1, 'p', slot) 368 | .writeAtPath(path2, 'd', slot) 369 | } 370 | 371 | getPath() { 372 | const path = super.getPath() 373 | path.push(...this.pendingDescent) 374 | return path 375 | } 376 | } 377 | 378 | // ReadCursor / WriteCursor didn't used to be exported. These are old helpers for them. 379 | // TODO: Remove in 1.0 380 | export const writeCursor = () => (new WriteCursor()) 381 | export const readCursor = (op: JSONOp) => new ReadCursor(op) 382 | 383 | // onSkipped is called for any children of r which are never returned by adv. 384 | export function advancer(r: ReadCursor | null, 385 | listMap?: (k: number, c: JSONOpComponent | null) => number, 386 | listAdv?: (k: number, c: JSONOpComponent | null) => void) { 387 | let didDescend: boolean, valid: boolean 388 | // Could use r.children() here instead... though this is might be simpler. 389 | valid = didDescend = r ? r.descendFirst() : false 390 | 391 | // Advance to key k. If k is null, advance all the way to the end. 392 | // let last = undefined 393 | function adv(ktarget: Key) { 394 | // if (last === ktarget) throw Error('duplicate descent') 395 | // last = ktarget 396 | let k2_ 397 | while (valid) { 398 | const k2 = k2_ = r!.getKey() 399 | 400 | if (ktarget != null) { 401 | let skip = false 402 | if (listMap && typeof k2 === 'number') { 403 | k2_ = listMap(k2, r!.getComponent()) 404 | if (k2_ < 0) { 405 | k2_ = ~k2_ 406 | skip = true 407 | } 408 | } 409 | 410 | // 3 cases: 411 | // - ktarget > k2. onSkip and iterate. 412 | // - ktarget = k and !skip. return r 413 | // - ktarget = k and skip or ktarget < k2. stop. 414 | 415 | if (isGreaterKey(k2_, ktarget)) return null 416 | else if (k2_ === ktarget && !skip) return r! 417 | } 418 | 419 | // Skip this item and continue. 420 | if (listAdv && typeof k2_ === 'number') listAdv(k2_, r!.getComponent()) 421 | valid = r!.nextSibling() 422 | } 423 | return null 424 | } 425 | 426 | adv.end = () => { 427 | // Advance to the end of the child items. Technically only needed if onSkipped is defined 428 | if (didDescend) r!.ascend() 429 | } 430 | return adv 431 | } 432 | 433 | // Walk two cursors together. 434 | export function eachChildOf( 435 | r1: ReadCursor | null, r2: ReadCursor | null, 436 | fn: (k: Key, r1: ReadCursor | null, r2: ReadCursor | null) => void) { 437 | let hasChild1, descended1, hasChild2, descended2 438 | hasChild1 = descended1 = r1 && r1.descendFirst() 439 | hasChild2 = descended2 = r2 && r2.descendFirst() 440 | 441 | while (hasChild1 || hasChild2) { 442 | let k1 = hasChild1 ? r1!.getKey() : null 443 | let k2 = hasChild2 ? r2!.getKey() : null 444 | 445 | if (k1 !== null && k2 !== null) { 446 | // Use the smallest key 447 | if (isGreaterKey(k2, k1)) k2 = null 448 | else if (k1 !== k2) k1 = null 449 | } 450 | 451 | // fn(key, r1 or null, r2 or null) 452 | fn((k1 == null ? k2! : k1), 453 | (k1 != null ? r1 : null), 454 | (k2 != null ? r2 : null) 455 | ) 456 | 457 | if (k1 != null && hasChild1) { 458 | hasChild1 = r1!.nextSibling() 459 | } 460 | if (k2 != null && hasChild2) { 461 | hasChild2 = r2!.nextSibling() 462 | } 463 | } 464 | 465 | if (descended1) r1!.ascend() 466 | if (descended2) r2!.ascend() 467 | } 468 | -------------------------------------------------------------------------------- /lib/deepClone.ts: -------------------------------------------------------------------------------- 1 | import { Doc } from "./types" 2 | 3 | export default function deepClone(old: Doc): Doc { 4 | if (old === null) return null 5 | 6 | if (Array.isArray(old)) { 7 | return old.map(deepClone) 8 | } else if (typeof old === 'object') { 9 | const o: Doc = {} 10 | for (let k in old) o[k] = deepClone(old[k]) 11 | return o 12 | } else { 13 | // Everything else in JS is immutable. 14 | return old 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /lib/deepEqual.ts: -------------------------------------------------------------------------------- 1 | import { Doc } from "./types" 2 | 3 | function eqObj(a: {[k: string]: Doc}, b: Doc[] | {[k: string]: Doc}) { 4 | if (Array.isArray(b)) return false 5 | 6 | for (let k in a) { 7 | if (!deepEqual(a[k], b[k])) return false 8 | } 9 | for (let k in b) { 10 | if (a[k] === undefined) return false 11 | } 12 | return true 13 | } 14 | 15 | function eqArr(a: Doc[], b: Doc) { 16 | if (!Array.isArray(b)) return false 17 | if (a.length !== b.length) return false 18 | 19 | for (let i = 0; i < a.length; i++) { 20 | if (!deepEqual(a[i], b[i])) return false 21 | } 22 | 23 | return true 24 | } 25 | 26 | export default function deepEqual(a: Doc, b: Doc) { 27 | // This will cover null, bools, string and number 28 | if (a === b) return true 29 | if (a === null || b === null) return false 30 | if (typeof a !== 'object' || typeof b !== 'object') return false 31 | 32 | return Array.isArray(a) ? eqArr(a, b) : eqObj(a, b) 33 | } 34 | -------------------------------------------------------------------------------- /lib/index.ts: -------------------------------------------------------------------------------- 1 | export * from './json1.release.js' 2 | export {ReadCursor, WriteCursor} from './cursor.js' 3 | export { 4 | Doc, 5 | JSONOp, JSONOpList, JSONOpComponent, 6 | Key, Path, Conflict, ConflictType, 7 | } from './types' 8 | -------------------------------------------------------------------------------- /lib/json1.release.ts: -------------------------------------------------------------------------------- 1 | // This is a placeholder which will be replaced in the dist directory by the 2 | // minified version of json1. 3 | 4 | export * from './json1' -------------------------------------------------------------------------------- /lib/log.ts: -------------------------------------------------------------------------------- 1 | // This is a simple logging function which prints things a lot prettier than 2 | // console.log in node. The signature is the same. 3 | 4 | export default function log(...args: any) { 5 | if (log.quiet) return 6 | 7 | const {inspect} = require('util') 8 | 9 | const f = (a: any) => (typeof a === 'string') ? 10 | a : inspect(a, {depth:10, colors:true}) 11 | 12 | const prefix = Array(log.prefix).fill(' ').join('') 13 | console.log(prefix + args.map(f).join(' ')) 14 | } 15 | 16 | log.quiet = true 17 | log.prefix = 0 18 | -------------------------------------------------------------------------------- /lib/types.ts: -------------------------------------------------------------------------------- 1 | import {TextOp} from 'ot-text-unicode' 2 | 3 | /** The text op component from text-unicode */ 4 | export {TextOp, TextOpComponent} from 'ot-text-unicode' 5 | 6 | export type JSONOpComponent = { 7 | /** insert */ 8 | i?: any, 9 | /** remove */ 10 | r?: any, 11 | /** pick */ 12 | p?: number, 13 | /** drop */ 14 | d?: number, 15 | 16 | /** Edit string (directly uses text-unicode) */ 17 | es?: TextOp, 18 | /** Edit number */ 19 | ena?: number, 20 | 21 | /** Arbitrary edit. If the element has e: it must also have et: with a registered type name */ 22 | e?: any 23 | /** Edit type name. */ 24 | et?: string 25 | } 26 | 27 | export type JSONOpList = (number | string | JSONOpComponent | JSONOpList)[] 28 | 29 | /** 30 | * A JSON operation. 31 | * 32 | * There's some aspects of valid operations which aren't captured by the type: 33 | * 34 | * - The last element of any list must be an object (the op component) 35 | * - Except at the root, every inner list must have a length at least 2, and the 36 | * first element must be a number or string. 37 | * - Operation components cannot be empty 38 | * - No two adjacent list elements can be operation components. 39 | * - If a component has listy children (it descends), those descenders are 40 | * sorted and come after any local op component. So, [, , , , ...] 42 | * - Picks and drops must be matched, and use low numbers (0, 1, 2, ....) 43 | * 44 | * noop is represented by 'null'. 45 | * 46 | * Valid operations: null, [{i:5}], ['a', {i:5}], [['a', {p:0}], ['b', {d:0}]], 47 | * [10, {i:5}, 'b', {r:true}] 48 | * 49 | * Not valid: [], ['a', [{i:5}]], [[{i:5}]], [{}], ['a', {}], ['a', ['b', {i:5}], {r:true}] 50 | * 51 | * Many 'invalid' operations can be cleaned up into their canonical form by the 52 | * normalize() function. 53 | * 54 | * If you want some examples, take a look at the test suite. 55 | */ 56 | export type JSONOp = null | JSONOpList 57 | 58 | export type Key = number | string 59 | export type Path = Key[] 60 | 61 | /** 62 | * JSON documents must be able to round-trip through JSON.stringify / 63 | * JSON.parse. So this means they can't contain: 64 | * 65 | * - undefined 66 | * - Circular references 67 | * - Objects of classes 68 | * - Functions 69 | * - Sets, Maps, Dates, DOM nodes, etc 70 | */ 71 | export type Doc = null | boolean | number | string | Doc[] | {[k: string]: Doc} 72 | 73 | export enum ConflictType { 74 | RM_UNEXPECTED_CONTENT = 1, 75 | DROP_COLLISION = 2, 76 | BLACKHOLE = 3, 77 | } 78 | 79 | export interface Conflict { type: ConflictType, op1: JSONOp, op2: JSONOp } 80 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ot-json1", 3 | "version": "1.0.1", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/node": { 8 | "version": "14.0.23", 9 | "resolved": "https://registry.npmjs.org/@types/node/-/node-14.0.23.tgz", 10 | "integrity": "sha512-Z4U8yDAl5TFkmYsZdFPdjeMa57NOvnaf1tljHzhouaPEp7LCj2JKkejpI1ODviIAQuW4CcQmxkQ77rnLsOOoKw==", 11 | "dev": true 12 | }, 13 | "ansi-colors": { 14 | "version": "3.2.3", 15 | "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", 16 | "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", 17 | "dev": true 18 | }, 19 | "ansi-regex": { 20 | "version": "3.0.0", 21 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", 22 | "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", 23 | "dev": true 24 | }, 25 | "ansi-styles": { 26 | "version": "3.2.1", 27 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 28 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 29 | "dev": true, 30 | "requires": { 31 | "color-convert": "^1.9.0" 32 | } 33 | }, 34 | "anymatch": { 35 | "version": "3.1.1", 36 | "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", 37 | "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", 38 | "dev": true, 39 | "requires": { 40 | "normalize-path": "^3.0.0", 41 | "picomatch": "^2.0.4" 42 | } 43 | }, 44 | "argparse": { 45 | "version": "1.0.10", 46 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", 47 | "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", 48 | "dev": true, 49 | "requires": { 50 | "sprintf-js": "~1.0.2" 51 | } 52 | }, 53 | "balanced-match": { 54 | "version": "1.0.0", 55 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 56 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 57 | "dev": true 58 | }, 59 | "binary-extensions": { 60 | "version": "2.0.0", 61 | "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz", 62 | "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", 63 | "dev": true 64 | }, 65 | "brace-expansion": { 66 | "version": "1.1.11", 67 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 68 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 69 | "dev": true, 70 | "requires": { 71 | "balanced-match": "^1.0.0", 72 | "concat-map": "0.0.1" 73 | } 74 | }, 75 | "braces": { 76 | "version": "3.0.2", 77 | "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", 78 | "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", 79 | "dev": true, 80 | "requires": { 81 | "fill-range": "^7.0.1" 82 | } 83 | }, 84 | "browser-stdout": { 85 | "version": "1.3.1", 86 | "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", 87 | "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", 88 | "dev": true 89 | }, 90 | "buffer-from": { 91 | "version": "1.1.1", 92 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", 93 | "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", 94 | "dev": true 95 | }, 96 | "camelcase": { 97 | "version": "5.3.1", 98 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", 99 | "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", 100 | "dev": true 101 | }, 102 | "chalk": { 103 | "version": "2.4.2", 104 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", 105 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", 106 | "dev": true, 107 | "requires": { 108 | "ansi-styles": "^3.2.1", 109 | "escape-string-regexp": "^1.0.5", 110 | "supports-color": "^5.3.0" 111 | }, 112 | "dependencies": { 113 | "supports-color": { 114 | "version": "5.5.0", 115 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 116 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 117 | "dev": true, 118 | "requires": { 119 | "has-flag": "^3.0.0" 120 | } 121 | } 122 | } 123 | }, 124 | "chokidar": { 125 | "version": "3.3.0", 126 | "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.0.tgz", 127 | "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", 128 | "dev": true, 129 | "requires": { 130 | "anymatch": "~3.1.1", 131 | "braces": "~3.0.2", 132 | "fsevents": "~2.1.1", 133 | "glob-parent": "~5.1.0", 134 | "is-binary-path": "~2.1.0", 135 | "is-glob": "~4.0.1", 136 | "normalize-path": "~3.0.0", 137 | "readdirp": "~3.2.0" 138 | } 139 | }, 140 | "cli-progress": { 141 | "version": "2.1.1", 142 | "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-2.1.1.tgz", 143 | "integrity": "sha512-TSJw3LY9ZRSis7yYzQ7flIdtQMbacd9oYoiFphJhI4SzgmqF0zErO+uNv0lbUjk1L4AGfHQJ4OVYYzW+JV66KA==", 144 | "dev": true, 145 | "requires": { 146 | "colors": "^1.1.2", 147 | "string-width": "^2.1.1" 148 | } 149 | }, 150 | "cliui": { 151 | "version": "5.0.0", 152 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", 153 | "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", 154 | "dev": true, 155 | "requires": { 156 | "string-width": "^3.1.0", 157 | "strip-ansi": "^5.2.0", 158 | "wrap-ansi": "^5.1.0" 159 | }, 160 | "dependencies": { 161 | "ansi-regex": { 162 | "version": "4.1.0", 163 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", 164 | "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", 165 | "dev": true 166 | }, 167 | "string-width": { 168 | "version": "3.1.0", 169 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", 170 | "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", 171 | "dev": true, 172 | "requires": { 173 | "emoji-regex": "^7.0.1", 174 | "is-fullwidth-code-point": "^2.0.0", 175 | "strip-ansi": "^5.1.0" 176 | } 177 | }, 178 | "strip-ansi": { 179 | "version": "5.2.0", 180 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", 181 | "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", 182 | "dev": true, 183 | "requires": { 184 | "ansi-regex": "^4.1.0" 185 | } 186 | } 187 | } 188 | }, 189 | "color-convert": { 190 | "version": "1.9.3", 191 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 192 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 193 | "dev": true, 194 | "requires": { 195 | "color-name": "1.1.3" 196 | } 197 | }, 198 | "color-name": { 199 | "version": "1.1.3", 200 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 201 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", 202 | "dev": true 203 | }, 204 | "colors": { 205 | "version": "1.4.0", 206 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", 207 | "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", 208 | "dev": true 209 | }, 210 | "commander": { 211 | "version": "2.20.3", 212 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", 213 | "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", 214 | "dev": true 215 | }, 216 | "concat-map": { 217 | "version": "0.0.1", 218 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 219 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 220 | "dev": true 221 | }, 222 | "debug": { 223 | "version": "3.2.6", 224 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", 225 | "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", 226 | "dev": true, 227 | "requires": { 228 | "ms": "^2.1.1" 229 | } 230 | }, 231 | "decamelize": { 232 | "version": "1.2.0", 233 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", 234 | "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", 235 | "dev": true 236 | }, 237 | "define-properties": { 238 | "version": "1.1.3", 239 | "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", 240 | "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", 241 | "dev": true, 242 | "requires": { 243 | "object-keys": "^1.0.12" 244 | } 245 | }, 246 | "diff": { 247 | "version": "3.5.0", 248 | "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", 249 | "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", 250 | "dev": true 251 | }, 252 | "emoji-regex": { 253 | "version": "7.0.3", 254 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", 255 | "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", 256 | "dev": true 257 | }, 258 | "es-abstract": { 259 | "version": "1.17.5", 260 | "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", 261 | "integrity": "sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg==", 262 | "dev": true, 263 | "requires": { 264 | "es-to-primitive": "^1.2.1", 265 | "function-bind": "^1.1.1", 266 | "has": "^1.0.3", 267 | "has-symbols": "^1.0.1", 268 | "is-callable": "^1.1.5", 269 | "is-regex": "^1.0.5", 270 | "object-inspect": "^1.7.0", 271 | "object-keys": "^1.1.1", 272 | "object.assign": "^4.1.0", 273 | "string.prototype.trimleft": "^2.1.1", 274 | "string.prototype.trimright": "^2.1.1" 275 | } 276 | }, 277 | "es-to-primitive": { 278 | "version": "1.2.1", 279 | "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", 280 | "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", 281 | "dev": true, 282 | "requires": { 283 | "is-callable": "^1.1.4", 284 | "is-date-object": "^1.0.1", 285 | "is-symbol": "^1.0.2" 286 | } 287 | }, 288 | "escape-string-regexp": { 289 | "version": "1.0.5", 290 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 291 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 292 | "dev": true 293 | }, 294 | "esprima": { 295 | "version": "4.0.1", 296 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", 297 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", 298 | "dev": true 299 | }, 300 | "fill-range": { 301 | "version": "7.0.1", 302 | "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", 303 | "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", 304 | "dev": true, 305 | "requires": { 306 | "to-regex-range": "^5.0.1" 307 | } 308 | }, 309 | "find-up": { 310 | "version": "3.0.0", 311 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", 312 | "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", 313 | "dev": true, 314 | "requires": { 315 | "locate-path": "^3.0.0" 316 | } 317 | }, 318 | "flat": { 319 | "version": "4.1.0", 320 | "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz", 321 | "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==", 322 | "dev": true, 323 | "requires": { 324 | "is-buffer": "~2.0.3" 325 | } 326 | }, 327 | "fs.realpath": { 328 | "version": "1.0.0", 329 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 330 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 331 | "dev": true 332 | }, 333 | "fsevents": { 334 | "version": "2.1.2", 335 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz", 336 | "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==", 337 | "dev": true, 338 | "optional": true 339 | }, 340 | "function-bind": { 341 | "version": "1.1.1", 342 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 343 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 344 | "dev": true 345 | }, 346 | "get-caller-file": { 347 | "version": "2.0.5", 348 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", 349 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", 350 | "dev": true 351 | }, 352 | "glob": { 353 | "version": "7.1.3", 354 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", 355 | "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", 356 | "dev": true, 357 | "requires": { 358 | "fs.realpath": "^1.0.0", 359 | "inflight": "^1.0.4", 360 | "inherits": "2", 361 | "minimatch": "^3.0.4", 362 | "once": "^1.3.0", 363 | "path-is-absolute": "^1.0.0" 364 | } 365 | }, 366 | "glob-parent": { 367 | "version": "5.1.2", 368 | "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 369 | "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 370 | "dev": true, 371 | "requires": { 372 | "is-glob": "^4.0.1" 373 | } 374 | }, 375 | "growl": { 376 | "version": "1.10.5", 377 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", 378 | "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", 379 | "dev": true 380 | }, 381 | "has": { 382 | "version": "1.0.3", 383 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 384 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 385 | "dev": true, 386 | "requires": { 387 | "function-bind": "^1.1.1" 388 | } 389 | }, 390 | "has-flag": { 391 | "version": "3.0.0", 392 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 393 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", 394 | "dev": true 395 | }, 396 | "has-symbols": { 397 | "version": "1.0.1", 398 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", 399 | "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", 400 | "dev": true 401 | }, 402 | "he": { 403 | "version": "1.2.0", 404 | "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", 405 | "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", 406 | "dev": true 407 | }, 408 | "inflight": { 409 | "version": "1.0.6", 410 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 411 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 412 | "dev": true, 413 | "requires": { 414 | "once": "^1.3.0", 415 | "wrappy": "1" 416 | } 417 | }, 418 | "inherits": { 419 | "version": "2.0.4", 420 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 421 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 422 | "dev": true 423 | }, 424 | "is-binary-path": { 425 | "version": "2.1.0", 426 | "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", 427 | "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", 428 | "dev": true, 429 | "requires": { 430 | "binary-extensions": "^2.0.0" 431 | } 432 | }, 433 | "is-buffer": { 434 | "version": "2.0.4", 435 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", 436 | "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", 437 | "dev": true 438 | }, 439 | "is-callable": { 440 | "version": "1.1.5", 441 | "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.5.tgz", 442 | "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", 443 | "dev": true 444 | }, 445 | "is-date-object": { 446 | "version": "1.0.2", 447 | "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", 448 | "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", 449 | "dev": true 450 | }, 451 | "is-extglob": { 452 | "version": "2.1.1", 453 | "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 454 | "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", 455 | "dev": true 456 | }, 457 | "is-fullwidth-code-point": { 458 | "version": "2.0.0", 459 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", 460 | "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", 461 | "dev": true 462 | }, 463 | "is-glob": { 464 | "version": "4.0.1", 465 | "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", 466 | "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", 467 | "dev": true, 468 | "requires": { 469 | "is-extglob": "^2.1.1" 470 | } 471 | }, 472 | "is-number": { 473 | "version": "7.0.0", 474 | "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", 475 | "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", 476 | "dev": true 477 | }, 478 | "is-regex": { 479 | "version": "1.0.5", 480 | "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.5.tgz", 481 | "integrity": "sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ==", 482 | "dev": true, 483 | "requires": { 484 | "has": "^1.0.3" 485 | } 486 | }, 487 | "is-symbol": { 488 | "version": "1.0.3", 489 | "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", 490 | "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", 491 | "dev": true, 492 | "requires": { 493 | "has-symbols": "^1.0.1" 494 | } 495 | }, 496 | "isexe": { 497 | "version": "2.0.0", 498 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 499 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", 500 | "dev": true 501 | }, 502 | "js-yaml": { 503 | "version": "3.13.1", 504 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", 505 | "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", 506 | "dev": true, 507 | "requires": { 508 | "argparse": "^1.0.7", 509 | "esprima": "^4.0.0" 510 | } 511 | }, 512 | "locate-path": { 513 | "version": "3.0.0", 514 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", 515 | "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", 516 | "dev": true, 517 | "requires": { 518 | "p-locate": "^3.0.0", 519 | "path-exists": "^3.0.0" 520 | } 521 | }, 522 | "lodash": { 523 | "version": "4.17.21", 524 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", 525 | "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", 526 | "dev": true 527 | }, 528 | "log-symbols": { 529 | "version": "3.0.0", 530 | "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-3.0.0.tgz", 531 | "integrity": "sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ==", 532 | "dev": true, 533 | "requires": { 534 | "chalk": "^2.4.2" 535 | } 536 | }, 537 | "minimatch": { 538 | "version": "3.0.4", 539 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 540 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 541 | "dev": true, 542 | "requires": { 543 | "brace-expansion": "^1.1.7" 544 | } 545 | }, 546 | "minimist": { 547 | "version": "1.2.5", 548 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", 549 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", 550 | "dev": true 551 | }, 552 | "mkdirp": { 553 | "version": "0.5.3", 554 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.3.tgz", 555 | "integrity": "sha512-P+2gwrFqx8lhew375MQHHeTlY8AuOJSrGf0R5ddkEndUkmwpgUob/vQuBD1V22/Cw1/lJr4x+EjllSezBThzBg==", 556 | "dev": true, 557 | "requires": { 558 | "minimist": "^1.2.5" 559 | } 560 | }, 561 | "mocha": { 562 | "version": "7.1.1", 563 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-7.1.1.tgz", 564 | "integrity": "sha512-3qQsu3ijNS3GkWcccT5Zw0hf/rWvu1fTN9sPvEd81hlwsr30GX2GcDSSoBxo24IR8FelmrAydGC6/1J5QQP4WA==", 565 | "dev": true, 566 | "requires": { 567 | "ansi-colors": "3.2.3", 568 | "browser-stdout": "1.3.1", 569 | "chokidar": "3.3.0", 570 | "debug": "3.2.6", 571 | "diff": "3.5.0", 572 | "escape-string-regexp": "1.0.5", 573 | "find-up": "3.0.0", 574 | "glob": "7.1.3", 575 | "growl": "1.10.5", 576 | "he": "1.2.0", 577 | "js-yaml": "3.13.1", 578 | "log-symbols": "3.0.0", 579 | "minimatch": "3.0.4", 580 | "mkdirp": "0.5.3", 581 | "ms": "2.1.1", 582 | "node-environment-flags": "1.0.6", 583 | "object.assign": "4.1.0", 584 | "strip-json-comments": "2.0.1", 585 | "supports-color": "6.0.0", 586 | "which": "1.3.1", 587 | "wide-align": "1.1.3", 588 | "yargs": "13.3.2", 589 | "yargs-parser": "13.1.2", 590 | "yargs-unparser": "1.6.0" 591 | } 592 | }, 593 | "ms": { 594 | "version": "2.1.1", 595 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 596 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", 597 | "dev": true 598 | }, 599 | "node-environment-flags": { 600 | "version": "1.0.6", 601 | "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.6.tgz", 602 | "integrity": "sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw==", 603 | "dev": true, 604 | "requires": { 605 | "object.getownpropertydescriptors": "^2.0.3", 606 | "semver": "^5.7.0" 607 | } 608 | }, 609 | "normalize-path": { 610 | "version": "3.0.0", 611 | "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", 612 | "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", 613 | "dev": true 614 | }, 615 | "object-inspect": { 616 | "version": "1.7.0", 617 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz", 618 | "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==", 619 | "dev": true 620 | }, 621 | "object-keys": { 622 | "version": "1.1.1", 623 | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", 624 | "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", 625 | "dev": true 626 | }, 627 | "object.assign": { 628 | "version": "4.1.0", 629 | "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", 630 | "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", 631 | "dev": true, 632 | "requires": { 633 | "define-properties": "^1.1.2", 634 | "function-bind": "^1.1.1", 635 | "has-symbols": "^1.0.0", 636 | "object-keys": "^1.0.11" 637 | } 638 | }, 639 | "object.getownpropertydescriptors": { 640 | "version": "2.1.0", 641 | "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", 642 | "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", 643 | "dev": true, 644 | "requires": { 645 | "define-properties": "^1.1.3", 646 | "es-abstract": "^1.17.0-next.1" 647 | } 648 | }, 649 | "once": { 650 | "version": "1.4.0", 651 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 652 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 653 | "dev": true, 654 | "requires": { 655 | "wrappy": "1" 656 | } 657 | }, 658 | "ot-fuzzer": { 659 | "version": "1.3.0", 660 | "resolved": "https://registry.npmjs.org/ot-fuzzer/-/ot-fuzzer-1.3.0.tgz", 661 | "integrity": "sha512-cZNdwrLkJ+p+/go94KFFmuiXsXy66eot+TBrStEMTxs4tGCQl43EKeqpTe9GpgVlftpWqzbzetIuZsg2s98QqQ==", 662 | "dev": true, 663 | "requires": { 664 | "cli-progress": "^2.1.1", 665 | "seedrandom": "^2.4.4" 666 | } 667 | }, 668 | "ot-simple": { 669 | "version": "1.0.0", 670 | "resolved": "https://registry.npmjs.org/ot-simple/-/ot-simple-1.0.0.tgz", 671 | "integrity": "sha1-B42ED4HqOq04y+aUdfgbLGdU+1A=", 672 | "dev": true 673 | }, 674 | "ot-text-unicode": { 675 | "version": "4.0.0", 676 | "resolved": "https://registry.npmjs.org/ot-text-unicode/-/ot-text-unicode-4.0.0.tgz", 677 | "integrity": "sha512-W7ZLU8QXesY2wagYFv47zErXud3E93FGImmSGJsQnBzE+idcPPyo2u2KMilIrTwBh4pbCizy71qRjmmV6aDhcQ==", 678 | "requires": { 679 | "unicount": "1.1" 680 | } 681 | }, 682 | "p-limit": { 683 | "version": "2.2.2", 684 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", 685 | "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", 686 | "dev": true, 687 | "requires": { 688 | "p-try": "^2.0.0" 689 | } 690 | }, 691 | "p-locate": { 692 | "version": "3.0.0", 693 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", 694 | "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", 695 | "dev": true, 696 | "requires": { 697 | "p-limit": "^2.0.0" 698 | } 699 | }, 700 | "p-try": { 701 | "version": "2.2.0", 702 | "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", 703 | "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", 704 | "dev": true 705 | }, 706 | "path-exists": { 707 | "version": "3.0.0", 708 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", 709 | "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", 710 | "dev": true 711 | }, 712 | "path-is-absolute": { 713 | "version": "1.0.1", 714 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 715 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 716 | "dev": true 717 | }, 718 | "picomatch": { 719 | "version": "2.2.2", 720 | "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", 721 | "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", 722 | "dev": true 723 | }, 724 | "readdirp": { 725 | "version": "3.2.0", 726 | "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.2.0.tgz", 727 | "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==", 728 | "dev": true, 729 | "requires": { 730 | "picomatch": "^2.0.4" 731 | } 732 | }, 733 | "require-directory": { 734 | "version": "2.1.1", 735 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", 736 | "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", 737 | "dev": true 738 | }, 739 | "require-main-filename": { 740 | "version": "2.0.0", 741 | "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", 742 | "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", 743 | "dev": true 744 | }, 745 | "seedrandom": { 746 | "version": "2.4.4", 747 | "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-2.4.4.tgz", 748 | "integrity": "sha512-9A+PDmgm+2du77B5i0Ip2cxOqqHjgNxnBgglxLcX78A2D6c2rTo61z4jnVABpF4cKeDMDG+cmXXvdnqse2VqMA==", 749 | "dev": true 750 | }, 751 | "semver": { 752 | "version": "5.7.1", 753 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", 754 | "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", 755 | "dev": true 756 | }, 757 | "set-blocking": { 758 | "version": "2.0.0", 759 | "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", 760 | "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", 761 | "dev": true 762 | }, 763 | "source-map": { 764 | "version": "0.6.1", 765 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 766 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 767 | "dev": true 768 | }, 769 | "source-map-support": { 770 | "version": "0.5.16", 771 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz", 772 | "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==", 773 | "dev": true, 774 | "requires": { 775 | "buffer-from": "^1.0.0", 776 | "source-map": "^0.6.0" 777 | } 778 | }, 779 | "sprintf-js": { 780 | "version": "1.0.3", 781 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", 782 | "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", 783 | "dev": true 784 | }, 785 | "string-width": { 786 | "version": "2.1.1", 787 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", 788 | "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", 789 | "dev": true, 790 | "requires": { 791 | "is-fullwidth-code-point": "^2.0.0", 792 | "strip-ansi": "^4.0.0" 793 | } 794 | }, 795 | "string.prototype.trimleft": { 796 | "version": "2.1.1", 797 | "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz", 798 | "integrity": "sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag==", 799 | "dev": true, 800 | "requires": { 801 | "define-properties": "^1.1.3", 802 | "function-bind": "^1.1.1" 803 | } 804 | }, 805 | "string.prototype.trimright": { 806 | "version": "2.1.1", 807 | "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz", 808 | "integrity": "sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g==", 809 | "dev": true, 810 | "requires": { 811 | "define-properties": "^1.1.3", 812 | "function-bind": "^1.1.1" 813 | } 814 | }, 815 | "strip-ansi": { 816 | "version": "4.0.0", 817 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", 818 | "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", 819 | "dev": true, 820 | "requires": { 821 | "ansi-regex": "^3.0.0" 822 | } 823 | }, 824 | "strip-json-comments": { 825 | "version": "2.0.1", 826 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", 827 | "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", 828 | "dev": true 829 | }, 830 | "supports-color": { 831 | "version": "6.0.0", 832 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", 833 | "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", 834 | "dev": true, 835 | "requires": { 836 | "has-flag": "^3.0.0" 837 | } 838 | }, 839 | "terser": { 840 | "version": "4.6.7", 841 | "resolved": "https://registry.npmjs.org/terser/-/terser-4.6.7.tgz", 842 | "integrity": "sha512-fmr7M1f7DBly5cX2+rFDvmGBAaaZyPrHYK4mMdHEDAdNTqXSZgSOfqsfGq2HqPGT/1V0foZZuCZFx8CHKgAk3g==", 843 | "dev": true, 844 | "requires": { 845 | "commander": "^2.20.0", 846 | "source-map": "~0.6.1", 847 | "source-map-support": "~0.5.12" 848 | } 849 | }, 850 | "to-regex-range": { 851 | "version": "5.0.1", 852 | "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", 853 | "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", 854 | "dev": true, 855 | "requires": { 856 | "is-number": "^7.0.0" 857 | } 858 | }, 859 | "typescript": { 860 | "version": "3.9.7", 861 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz", 862 | "integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==", 863 | "dev": true 864 | }, 865 | "unicount": { 866 | "version": "1.1.0", 867 | "resolved": "https://registry.npmjs.org/unicount/-/unicount-1.1.0.tgz", 868 | "integrity": "sha512-RlwWt1ywVW4WErPGAVHw/rIuJ2+MxvTME0siJ6lk9zBhpDfExDbspe6SRlWT3qU6AucNjotPl9qAJRVjP7guCQ==" 869 | }, 870 | "which": { 871 | "version": "1.3.1", 872 | "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", 873 | "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", 874 | "dev": true, 875 | "requires": { 876 | "isexe": "^2.0.0" 877 | } 878 | }, 879 | "which-module": { 880 | "version": "2.0.0", 881 | "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", 882 | "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", 883 | "dev": true 884 | }, 885 | "wide-align": { 886 | "version": "1.1.3", 887 | "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", 888 | "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", 889 | "dev": true, 890 | "requires": { 891 | "string-width": "^1.0.2 || 2" 892 | } 893 | }, 894 | "wrap-ansi": { 895 | "version": "5.1.0", 896 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", 897 | "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", 898 | "dev": true, 899 | "requires": { 900 | "ansi-styles": "^3.2.0", 901 | "string-width": "^3.0.0", 902 | "strip-ansi": "^5.0.0" 903 | }, 904 | "dependencies": { 905 | "ansi-regex": { 906 | "version": "4.1.0", 907 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", 908 | "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", 909 | "dev": true 910 | }, 911 | "string-width": { 912 | "version": "3.1.0", 913 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", 914 | "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", 915 | "dev": true, 916 | "requires": { 917 | "emoji-regex": "^7.0.1", 918 | "is-fullwidth-code-point": "^2.0.0", 919 | "strip-ansi": "^5.1.0" 920 | } 921 | }, 922 | "strip-ansi": { 923 | "version": "5.2.0", 924 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", 925 | "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", 926 | "dev": true, 927 | "requires": { 928 | "ansi-regex": "^4.1.0" 929 | } 930 | } 931 | } 932 | }, 933 | "wrappy": { 934 | "version": "1.0.2", 935 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 936 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 937 | "dev": true 938 | }, 939 | "y18n": { 940 | "version": "4.0.1", 941 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz", 942 | "integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==", 943 | "dev": true 944 | }, 945 | "yargs": { 946 | "version": "13.3.2", 947 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", 948 | "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", 949 | "dev": true, 950 | "requires": { 951 | "cliui": "^5.0.0", 952 | "find-up": "^3.0.0", 953 | "get-caller-file": "^2.0.1", 954 | "require-directory": "^2.1.1", 955 | "require-main-filename": "^2.0.0", 956 | "set-blocking": "^2.0.0", 957 | "string-width": "^3.0.0", 958 | "which-module": "^2.0.0", 959 | "y18n": "^4.0.0", 960 | "yargs-parser": "^13.1.2" 961 | }, 962 | "dependencies": { 963 | "ansi-regex": { 964 | "version": "4.1.0", 965 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", 966 | "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", 967 | "dev": true 968 | }, 969 | "string-width": { 970 | "version": "3.1.0", 971 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", 972 | "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", 973 | "dev": true, 974 | "requires": { 975 | "emoji-regex": "^7.0.1", 976 | "is-fullwidth-code-point": "^2.0.0", 977 | "strip-ansi": "^5.1.0" 978 | } 979 | }, 980 | "strip-ansi": { 981 | "version": "5.2.0", 982 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", 983 | "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", 984 | "dev": true, 985 | "requires": { 986 | "ansi-regex": "^4.1.0" 987 | } 988 | } 989 | } 990 | }, 991 | "yargs-parser": { 992 | "version": "13.1.2", 993 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", 994 | "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", 995 | "dev": true, 996 | "requires": { 997 | "camelcase": "^5.0.0", 998 | "decamelize": "^1.2.0" 999 | } 1000 | }, 1001 | "yargs-unparser": { 1002 | "version": "1.6.0", 1003 | "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", 1004 | "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", 1005 | "dev": true, 1006 | "requires": { 1007 | "flat": "^4.1.0", 1008 | "lodash": "^4.17.15", 1009 | "yargs": "^13.3.0" 1010 | } 1011 | } 1012 | } 1013 | } 1014 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ot-json1", 3 | "version": "1.0.1", 4 | "description": "JSON OT type", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "dependencies": { 8 | "ot-text-unicode": "4" 9 | }, 10 | "devDependencies": { 11 | "@types/node": "^14.0.13", 12 | "mocha": "^7.1.1", 13 | "ot-fuzzer": "1.3", 14 | "ot-simple": "^1.0.0", 15 | "terser": "^4.6.7", 16 | "typescript": "^3.9.5" 17 | }, 18 | "scripts": { 19 | "test": "mocha test/cursor.js test/test.js test/immutable.js", 20 | "fuzzer": "node test/fuzzer.js", 21 | "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" 22 | }, 23 | "mocha": { 24 | "checkLeaks": true, 25 | "reporter": "spec" 26 | }, 27 | "files": [ 28 | "dist/", 29 | "lib/", 30 | "test/genOp.js" 31 | ], 32 | "repository": { 33 | "type": "git", 34 | "url": "git+https://github.com/josephg/json1.git" 35 | }, 36 | "keywords": [ 37 | "ot", 38 | "json" 39 | ], 40 | "author": "Joseph Gentle ", 41 | "license": "ISC", 42 | "bugs": { 43 | "url": "https://github.com/josephg/json1/issues" 44 | }, 45 | "homepage": "https://github.com/josephg/json1#readme" 46 | } 47 | -------------------------------------------------------------------------------- /spec.md: -------------------------------------------------------------------------------- 1 | # JSON1 OT Spec 2 | 3 | The JSON OT type is designed to allow concurrent edits in an arbitrary JSON document. 4 | 5 | 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.) 6 | 7 | Like its predecessor, JSON1 requires that the document is a valid JSON structure. That is: 8 | 9 | - Each object or list can only be referenced once in the tree. 10 | - Cycles are not allowed 11 | - The value `undefined` is not allowed anywhere but at the root. 12 | - 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) 13 | 14 | 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. 15 | 16 | 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: 17 | 18 | - Pick up subtree (to be moved or discarded) 19 | - Insert subtree (moved from elsewhere, or as a literal copied from the operation) 20 | - Edit embedded object at a location in the subtree using an operation from another registered OT/CRDT type. 21 | 22 | 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. 23 | 24 | For plain text edits the [text OT type](https://github.com/ottypes/text-unicode) is bundled & always available. 25 | 26 | 27 | ## Why? Advantages compared to JSON0 28 | 29 | The JSON0 OT type has some weaknesses: 30 | 31 | - You can't move an object key or move an item between two different lists 32 | - The semantics for reordering list items is really confusing - and its slightly different depending on whether you're inserting or moving 33 | - JSON0 is O(N*M) if you transform an operation with M components by an operation with N components. 34 | - JSON0 has no way to safely initialize data before editing 35 | - 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). 36 | - 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. 37 | 38 | *tldr;* JSON1 is designed to be simpler and easier to maintain, faster, more feature rich and more widely useful. 39 | 40 | 41 | ## Operations 42 | 43 | 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: 44 | 45 | 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. 46 | 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. 47 | 3. **Edit phase**: In the edit phase we make changes to embedded documents in the tree. 48 | 49 | Instructions for all three phases are overlaid over the top of one another inside the operation structure. 50 | 51 | The operation describes a traversal of the document's tree. The way to interpret the operation is: 52 | 53 | - The traversal starts at the root of the document. 54 | - The `[` symbol saves the current location in the document to a stack. (*stack.push(location)*) 55 | - The `]` symbol pops the previously stored location from the stack. (*location = stack.pop()*) 56 | - 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. 57 | - 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: 58 | - `p` for pick up into storage 59 | - `r` for remove 60 | - `d` for drop from storage 61 | - `i` for insert literal 62 | - `e` for edit, with accompanying `et` for describing the edit type. `es` / `ena` are shorthands for string and number edits). 63 | See below for more detail. 64 | 65 | Consider this operation, which moves the string at `doc.x` to `doc.y` then edits it: 66 | 67 | [['x', {p:0}], ['y', {d:0, es:[5, 'hi']}]] 68 | 69 | This operation contains instructions for all 3 phases: 70 | 71 | 1. `['x', {p:0}]`: Go to the item at key 'x' inside the document and pick it up, storing it in holding location 0. 72 | 2. `['y', {d:0}]`: Go to the key 'y' in the document and place the item in storage location 0. 73 | 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.) 74 | 75 | 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. 76 | 77 | 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]`. 78 | 79 | 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: 80 | 81 | - There are no unnecessary lists `[...]` in the operation. `['x', {r:0}]` rather than `[['x', [{r:0}]]]`. 82 | - 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. 83 | - Components are collapsed when possible. (So, `['x', {r:0, i:1}]` rather than `['x', {r:0}, {i:1}]`). 84 | - Pick up and drop slot identifiers should be numbers counting from 0. 85 | - There are no empty operation components. (`['x', {r:0}]` is valid. `[{}, 'x', {r:0}]` is not.) 86 | 87 | Slot identifiers should be ordered within the operation, but this is not strictly enforced in the library. 88 | 89 | Constructing operations which follow all these rules in code can be quite tricky. Most users should either: 90 | 91 | - Construct complex operations out of simple operations, then use *compose* to merge the simple operations together, or 92 | - Use the *WriteCursor* API to build complex operations. WriteCursor will take care of formatting the operation. 93 | 94 | Noop is written as `null`. 95 | 96 | 97 | ### Some examples. 98 | 99 | Given a document of: 100 | 101 | {x:5, y:['happy', 'apple']} 102 | 103 | #### Insert z:6: 104 | 105 | ['z', {i:6}] 106 | 107 | Translation: Descend into key 'z'. In the drop phase insert immediate value 6. 108 | 109 | #### Rename (move) doc.x to doc.z: 110 | 111 | [['x', {p:0}], ['z', {d:0}]] 112 | 113 | 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. 114 | 115 | #### Move x into the list between 'happy' and 'apple' 116 | 117 | [['x',{p:0}], ['y',1,{d:0}]] 118 | 119 | 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. 120 | 121 | 122 | ## Parts of an operation 123 | 124 | 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. 125 | 126 | The edit component object can have one or more of the following values: 127 | 128 | - **p** (pickup): During the pickup phase, pick up the subtree from this position and put it in the specified slot. Eg `p:10`. 129 | - **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. 130 | - **d** (drop): During the drop phase, insert the subtree in the specified slot here. Eg `d:10`. 131 | - **i** (insert): During the drop phase, insert the immediate value here. Eg `i:"cabbage"` 132 | - **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. 133 | 134 | 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: 135 | 136 | [['a', 'x', {i:1}], ['a', 'y', {i:2}]] 137 | 138 | For compactness, the common path prefix can be pulled out into the parent list. As such, this is canonically written like this: 139 | 140 | ['a', ['x', {i:1}], ['y', {i:2}]] 141 | 142 | You can read this operation as follows: 143 | 144 | 1. Descend to key `a` 145 | 2. At this position, perform the following child operations: 146 | 1. Descend to `x` and insert value `1` 147 | 2. Descend to `y` and insert value `2` 148 | 149 | 150 | ## A note on order 151 | 152 | 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: 153 | 154 | [['x', {p:0}, 'y', {r:true}], ['z', {d:0}]] 155 | 156 | The order of operations is: 157 | 158 | 1. Remove `doc.x.y` 159 | 2. Pick up `doc.x` into slot 0 160 | 3. Place the contents of slot 0 into `doc.z`. 161 | 162 | This rule is important, because it means: 163 | 164 | - 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. 165 | - The path to any drop, insert or edit operations exactly matches the location in the document *after* the operation has been applied. 166 | 167 | 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. 168 | 169 | So given the document `{x: {y: {}}}`, this operation will capitalize the keys (x->X, y->Y): 170 | 171 | ``` 172 | [ 173 | ['x',{p:0},'y',{p:1}], 174 | ['X',{d:0},'Y',{d:1}] 175 | ] 176 | ``` 177 | 178 | Note for *Y* the data is picked up at `x.y` and dropped at `X.Y`. 179 | 180 | The operation is carried out in the following order: 181 | 182 | - Pick phase: 183 | 1. Descend into *x* 184 | 2. Descend into *y* 185 | 3. Pick up *y* in slot 1, value `{}` 186 | 4. Return. Document is now `{x: {}}`. 187 | 5. Pick up *x* in slot 0, value `{}` 188 | 6. Return. Document is now `{}`. 189 | - Drop phase: 190 | 1. Descend into *X* 191 | 2. Drop data in slot 0 with data `{}`. Document is now `{X: {}}`. 192 | 3. Descend into *Y* 193 | 4. Drop data in slot 1 with value `{}`. Document is now `{X: {Y:{}}}` 194 | 5. Return 195 | 6. Return 196 | 197 | 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. 198 | 199 | 200 | # Fancier examples 201 | 202 | Convert the document {x:10,y:20,z:30} to an array [10,20,30]: 203 | 204 | ``` 205 | [ 206 | {r:{},i:[]}, 207 | [0,{d:0}],[1,{d:1}],[2,{d:2}], 208 | ['x',{p:0}],['y',{p:1}],['z',{p:2}] 209 | ] 210 | ``` 211 | 212 | Rewrite the document {x:{y:{secret:"data"}}} as {y:{x:{secret:"data"}}}: 213 | 214 | ``` 215 | [ 216 | ['x', {r:{}}, 'y', {p:0}], 217 | ['y', {i:{}}, 'x', {d:0}] 218 | ] 219 | ``` 220 | 221 | (Translation: Go into x.y and pick up the data (slot 0). Set doc.y = {}. Set doc.y.x = data (slot 0).) 222 | 223 | 224 | ## Initializing data ('set null') 225 | 226 | 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? 227 | 228 | 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. 229 | 230 | In JSON1 the semantics of `insert` are specially chosen to allow this. 231 | 232 | The rules are: 233 | 234 | - 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. 235 | - Inserts, drops and edits can be embedded inside another insert. 236 | 237 | Together, these rules allow clients to concurrently initialize a document without bumping heads. Eg if these two operations are run concurrently: 238 | 239 | [{i:{tags:[]}}, 'tags', 0, {i:'rock'}] 240 | 241 | and 242 | 243 | [{i:{tags:[]}}, 'tags', 0, {i:'roll'}] 244 | 245 | then the document will end up with the contents `{tags:['rock', 'roll']}`. 246 | 247 | This can also be used for collaboratively editing a string that might not exist: 248 | 249 | [{i:'', es:["aaa"]}] 250 | 251 | and 252 | 253 | [{i:'', es:["bbb"]}] 254 | 255 | Will result in the document containing either "aaabbb" (or "bbbaaa" depending on transform symmetry). 256 | 257 | 258 | ## Inverting operations 259 | 260 | The JSON1 OT type allows optional invertible. An operation is invertible if all `r:` components contain the complete contents of any removed data. 261 | 262 | In this case, an operation can be inverted by: 263 | 264 | - Swapping `i` and `r` components 265 | - Swapping `p` and `d` components 266 | - Moving all edits to the item's source location 267 | - Recursively inverting all embedded edits 268 | 269 | The contents of `r:` components is ignored in all other situations outside invert. 270 | 271 | 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!) 272 | 273 | Internally invertWithDoc is implemented using two methods: 274 | 275 | - `type.makeInvertible(op, doc) => op'`: This method fills in any information missing in `r:` components based on the contents of the passed document. 276 | - `type.invert(op) => op'`: This method inverts an operation, as described above. 277 | 278 | But note that information needed to invert an operation is lost when calling *compose* or *transform*. 279 | 280 | 281 | ### Invertible operations and setNull behaviour 282 | 283 | Consider this operation from earlier which uses the setNull behaviour to initialize a field, before inserting content into it: 284 | 285 | [{i:'', es:["hi"]}] 286 | 287 | Perhaps counterintuitively, the inverse of this operation will be: 288 | 289 | [{r:'hi'}] 290 | 291 | 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. 292 | 293 | 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. 294 | 295 | 296 | ## Conflicts 297 | 298 | There are many ways concurrent changes can incidentally lose user data: 299 | 300 | - One user moves data into a location that another user is deleting 301 | - Two concurrent edits move or insert into the same location in a document 302 | - Concurrent edits move items inside one another. (Move A inside B / Move B inside A) 303 | 304 | 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: 305 | 306 | - When simply calling `transform`, any of these issues will throw an exception. (Or `tryTransform`, which returns any conflicts instead of throwing them). 307 | - You can use `transformNoConflict`. This will resolve all of these issues automatically, which may lose user data. 308 | - 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. 309 | -------------------------------------------------------------------------------- /test/cursor.js: -------------------------------------------------------------------------------- 1 | const { writeCursor, readCursor } = require('../dist/cursor') 2 | const assert = require('assert') 3 | 4 | const data = require('fs') 5 | .readFileSync(__dirname + '/ops.json', 'utf8') 6 | .split('\n') 7 | .filter(x => x !== '') 8 | .map(JSON.parse) 9 | 10 | describe('cursors', function() { 11 | describe('writeCursor duplicates', function() { 12 | const test = op => { 13 | const w = writeCursor() 14 | 15 | const f = l => { 16 | assert(Array.isArray(l)) 17 | let depth = 0 18 | for (let c of l) { 19 | if (['string', 'number'].includes(typeof c)) { 20 | depth++ 21 | w.descend(c) 22 | } else if (Array.isArray(c)) { 23 | f(c) 24 | } else if (typeof c === 'object') { 25 | for (let k in c) { 26 | const v = c[k] 27 | w.write(k, v) 28 | } 29 | } 30 | } 31 | 32 | return __range__(0, depth, false).map(i => w.ascend()) 33 | } 34 | 35 | if (op !== null) { 36 | f(op) 37 | } 38 | return assert.deepEqual(op, w.get()) 39 | } 40 | 41 | return data.map(d => (d => it(`${JSON.stringify(d)}`, () => test(d)))(d)) 42 | }) 43 | 44 | describe('copy using read cursors', function() { 45 | const test = op => { 46 | const r = readCursor(op) 47 | const w = writeCursor() 48 | const path = [] 49 | const f = () => { 50 | const component = r.getComponent() 51 | if (component) { 52 | // console.log 'component', component 53 | for (let k in component) { 54 | const v = component[k] 55 | w.write(k, v) 56 | } 57 | } 58 | 59 | assert.deepStrictEqual(r.getPath(), path) 60 | assert.deepStrictEqual(w.getPath(), path) 61 | 62 | const result = [] 63 | for (let k of r) { 64 | path.push(k) 65 | w.descend(k) 66 | f() 67 | w.ascend() 68 | result.push(path.pop()) 69 | } 70 | return result 71 | } 72 | 73 | f() 74 | 75 | return assert.deepEqual(op, w.get()) 76 | } 77 | // console.log op 78 | // console.log w.get() 79 | return data.map(d => it(`${JSON.stringify(d)}`, () => test(d))) 80 | }) 81 | 82 | return describe('fuzzer', () => 83 | it('cleans up position after mergeTree', () => { 84 | const a = [1, 'c', { d: 1 }] 85 | const w = writeCursor(a) 86 | 87 | w.descend(0) 88 | 89 | w.descend('a') 90 | w.write('p', 1) 91 | w.ascend() 92 | w.ascend() 93 | 94 | w.descend(1) 95 | w.mergeTree([{ r: true }]) 96 | w.descend('b') 97 | w.write('p', 0) // Crash! 98 | w.ascend() 99 | return w.ascend() 100 | })) 101 | }) 102 | 103 | function __range__(left, right, inclusive) { 104 | const range = [] 105 | const ascending = left < right 106 | const end = !inclusive ? right : ascending ? right + 1 : right - 1 107 | for (let i = left; ascending ? i < end : i > end; ascending ? i++ : i--) { 108 | range.push(i) 109 | } 110 | return range 111 | } 112 | -------------------------------------------------------------------------------- /test/fuzzer.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert') 2 | // const {type} = require('../index') 3 | const { type } = require('../dist/json1') 4 | 5 | const run = (module.exports = () => { 6 | // require('./lib/log').quiet = true 7 | // type.debug = true 8 | const fuzzer = require('ot-fuzzer') 9 | const genOp = require('./genOp') 10 | 11 | const _t = type.typeAllowingConflictsPred(() => true) 12 | 13 | // const tracer = require('./tracer')(_t, genOp) 14 | //fuzzer(tracer, tracer.genOp, 100000) 15 | fuzzer(_t, genOp, 10000000) 16 | }) 17 | 18 | if (require.main === module) run() 19 | -------------------------------------------------------------------------------- /test/genOp.js: -------------------------------------------------------------------------------- 1 | let genRandomOp 2 | const { randomInt, randomReal, randomWord } = require('ot-fuzzer') 3 | 4 | const assert = require('assert') 5 | const { writeCursor } = require('../dist/cursor') 6 | const log = require('../dist/log').default 7 | const { type } = require('..') 8 | 9 | // This is an awful function to clone a document snapshot for use by the random 10 | // op generator. .. Since we don't want to corrupt the original object with 11 | // the changes the op generator will make. 12 | const clone = o => (typeof o === 'object' ? JSON.parse(JSON.stringify(o)) : o) 13 | 14 | const randomKey = obj => { 15 | // this works on arrays too! 16 | if (Array.isArray(obj)) { 17 | return obj.length === 0 ? undefined : randomInt(obj.length) 18 | } else { 19 | const keys = Object.keys(obj) 20 | return keys.length === 0 ? undefined : keys[randomInt(keys.length)] 21 | } 22 | } 23 | 24 | const letters = 'abxyz' 25 | 26 | // Generate a random new key for a value in obj. 27 | const randomNewKey = obj => { 28 | if (Array.isArray(obj)) { 29 | return randomInt(obj.length + 1) 30 | } else { 31 | let key 32 | while (true) { 33 | // Mostly try just use a small letter 34 | key = 35 | randomInt(20) === 0 ? randomWord() : letters[randomInt(letters.length)] 36 | if (obj[key] === undefined) { 37 | break 38 | } 39 | } 40 | return key 41 | } 42 | } 43 | 44 | // Generate a random object 45 | const randomThing = () => { 46 | switch (randomInt(7)) { 47 | case 0: 48 | return null 49 | case 1: 50 | return '' 51 | case 2: 52 | return randomWord() 53 | case 3: 54 | const obj = {} 55 | for ( 56 | let i = 1, end = randomInt(2), asc = 1 <= end; 57 | asc ? i <= end : i >= end; 58 | asc ? i++ : i-- 59 | ) { 60 | obj[randomNewKey(obj)] = randomThing() 61 | } 62 | return obj 63 | case 4: 64 | return __range__(1, randomInt(2), true).map(j => randomThing()) 65 | case 5: 66 | return 0 // bias toward zeros to find accidental truthy checks 67 | case 6: 68 | return randomInt(50) 69 | } 70 | } 71 | 72 | // Pick a random path to something in the object. 73 | const randomPath = data => { 74 | if (data == null || typeof data !== 'object') { 75 | return [] 76 | } 77 | 78 | const path = [] 79 | while (randomReal() < 0.9 && data != null && typeof data === 'object') { 80 | const key = randomKey(data) 81 | if (key == null) { 82 | break 83 | } 84 | 85 | path.push(key) 86 | data = data[key] 87 | } 88 | 89 | return path 90 | } 91 | 92 | const randomWalkPick = (w, container) => { 93 | const path = randomPath(container.data) 94 | let parent = container 95 | let key = 'data' 96 | 97 | for (let p of Array.from(path)) { 98 | parent = parent[key] 99 | key = p 100 | w.descend(key) 101 | } 102 | const operand = parent[key] 103 | return [path, parent, key, operand] 104 | } 105 | 106 | // Returns [path, parent, key] if we can drop here, 107 | // or null if no drop is possible 108 | const randomWalkDrop = (w, container) => { 109 | if (container.data === undefined) { 110 | return [[], container, 'data'] 111 | } else if (typeof container.data !== 'object' || container.data === null) { 112 | return null // Can't insert into a document that is a string or number 113 | } 114 | 115 | let [path, parent, key, operand] = randomWalkPick(w, container) 116 | if (typeof operand === 'object' && operand !== null) { 117 | parent = operand 118 | } else { 119 | w.ascend() 120 | w.reset() 121 | path.pop() 122 | } 123 | key = randomNewKey(parent) 124 | return [path, parent, key] 125 | } 126 | 127 | const set = (container, key, value) => 128 | value === undefined 129 | ? Array.isArray(container) 130 | ? container.splice(key, 1) 131 | : delete container[key] 132 | : Array.isArray(container) 133 | ? container.splice(key, 0, value) 134 | : (container[key] = value) 135 | 136 | const genRandomOpPart = data => { 137 | // log 'genRandomOp', data 138 | 139 | let key1, parent1, path1 140 | const container = { data } 141 | const w = writeCursor() 142 | 143 | // Remove something 144 | 145 | // Things we can do: 146 | // 0. remove something 147 | // 1. move something 148 | // 2. insert something 149 | // 3. edit a string 150 | const mode = data === undefined && randomReal() < 0.9 ? 2 : randomInt(4) 151 | //mode = 1 152 | //log 'mode', mode 153 | switch (mode) { 154 | case 0: 155 | case 1: 156 | const [path, parent, key, operand] = randomWalkPick(w, container) 157 | //log 'ppko', path, parent, key, operand 158 | if (mode === 1 && typeof operand === 'string') { 159 | // Edit it! 160 | const genString = require('./genTextOp') 161 | const [stringOp, result] = genString(operand) 162 | w.write('es', stringOp) 163 | parent[key] = result 164 | } else if (mode === 1 && typeof operand === 'number') { 165 | const increment = randomInt(10) 166 | w.write('ena', increment) 167 | parent[key] += increment 168 | } else { 169 | // Remove it 170 | if (operand !== undefined) { 171 | w.write('r', true) //operand 172 | set(parent, key, undefined) 173 | } 174 | } 175 | break 176 | 177 | case 2: 178 | // insert something 179 | const walk = randomWalkDrop(w, container) 180 | if (walk !== null) { 181 | const [path, parent, key] = walk 182 | //log 'walk', walk 183 | const val = randomThing() 184 | if (parent !== container) { 185 | w.descend(key) 186 | } 187 | w.write('i', val) 188 | set(parent, key, clone(val)) 189 | } 190 | break 191 | 192 | case 3: 193 | // Move something. We'll pick up the current operand... 194 | const [path1, parent1, key1, operand1] = randomWalkPick(w, container) 195 | if (operand1 !== undefined) { 196 | set(parent1, key1, undefined) // remove it from the result... 197 | 198 | if (parent1 === container) { 199 | // We're removing the whole thing. 200 | w.write('r', true) 201 | } else { 202 | w.write('p', 0) 203 | 204 | // ... and find another place to insert it! 205 | for ( 206 | let i = 0, end = path1.length, asc = 0 <= end; 207 | asc ? i < end : i > end; 208 | asc ? i++ : i-- 209 | ) { 210 | w.ascend() 211 | } 212 | w.reset() 213 | const [path2, parent2, key2] = Array.from( 214 | randomWalkDrop(w, container) 215 | ) 216 | w.descend(key2) 217 | set(parent2, key2, operand1) 218 | w.write('d', 0) 219 | } 220 | } 221 | break 222 | } 223 | 224 | const doc = container.data 225 | const op = w.get() 226 | 227 | type.checkValidOp(op) 228 | 229 | // assert.deepEqual doc, type.apply clone(data), op 230 | 231 | return [op, doc] 232 | } 233 | 234 | module.exports = genRandomOp = doc => { 235 | doc = clone(doc) 236 | 237 | // 90% chance of adding an op the first time through, then 50% each successive time. 238 | let chance = 0.99 239 | 240 | let op = null // Aggregate op 241 | 242 | while (randomReal() < chance) { 243 | let opc 244 | ;[opc, doc] = genRandomOpPart(doc) 245 | log(opc) 246 | // type.setDebug false 247 | op = type.compose( 248 | op, 249 | opc 250 | ) 251 | chance = 0.5 252 | } 253 | 254 | // log.quiet = false 255 | log('-> generated op', op, 'doc', doc) 256 | return [op, doc] 257 | } 258 | 259 | if (require.main === module) { 260 | // log genRandomOp {} 261 | // log genRandomOp({x:5, y:7, z:[1,2,3]}) for [1..10] 262 | for (let i = 1; i <= 10; i++) { 263 | type.debug = true 264 | log.quiet = false 265 | log(genRandomOp({ x: 'hi', y: 'omg', z: [1, 'whoa', 3] })) 266 | } 267 | } 268 | // log genRandomOp(undefined) 269 | 270 | function __range__(left, right, inclusive) { 271 | const range = [] 272 | const ascending = left < right 273 | const end = !inclusive ? right : ascending ? right + 1 : right - 1 274 | for (let i = left; ascending ? i < end : i > end; ascending ? i++ : i--) { 275 | range.push(i) 276 | } 277 | return range 278 | } 279 | -------------------------------------------------------------------------------- /test/genTextOp.js: -------------------------------------------------------------------------------- 1 | // This was stolen from ot-text-unicode 's sourcecode. Its copied in full here 2 | // because it won't change much, and because the npm module doesn't export this 3 | // file. TODO: export this from the npm module and delete this. 4 | const {randomInt, randomWord} = require('ot-fuzzer') 5 | const {type} = require('ot-text-unicode') 6 | 7 | const emoji = '😅🤖👻🤟💃' 8 | 9 | const strToList = s => { 10 | const list = [] 11 | let i = 0 12 | 13 | while (i < s.length) { 14 | const code = s.charCodeAt(i) 15 | 16 | if (code >= 0xd800) { 17 | list.push(s[i] + s[i+1]) 18 | i += 2 19 | } else list.push(s[i++]) 20 | } 21 | 22 | return list 23 | } 24 | 25 | 26 | module.exports = function genOp(docStr) { 27 | docStr = strToList(docStr) 28 | const initialLen = docStr.length 29 | 30 | const op = [] 31 | let expectedDoc = '' 32 | 33 | const consume = len => { 34 | expectedDoc += docStr.slice(0, len).join('') 35 | docStr = docStr.slice(len) 36 | } 37 | 38 | const addInsert = () => { 39 | // Insert a random word from the list somewhere in the document 40 | let word 41 | const skip = randomInt(Math.min(docStr.length, 5)) 42 | 43 | if (randomInt(5)) { // Usually just use normal ascii characters 44 | word = randomWord() + ' ' 45 | } else { 46 | const p = randomInt(emoji.length/2) 47 | word = emoji.slice(p*2, (p*2) + 2) 48 | } 49 | 50 | op.push(skip) 51 | consume(skip) 52 | 53 | op.push(word) 54 | expectedDoc += word 55 | } 56 | 57 | const addDelete = function() { 58 | const skip = randomInt(Math.min(docStr.length, 5)) 59 | 60 | op.push(skip) 61 | consume(skip) 62 | 63 | const length = randomInt(Math.min(docStr.length, 10)) 64 | op.push({d:length}) 65 | return docStr = docStr.slice(length) 66 | } 67 | 68 | while (docStr.length > 0) { 69 | // If the document is long, we'll bias it toward deletes 70 | const chance = initialLen > 100 ? 3 : 2 71 | switch (randomInt(chance)) { 72 | case 0: addInsert(); break 73 | case 1: case 2: addDelete(); break 74 | } 75 | 76 | if (randomInt(7) === 0) break 77 | } 78 | 79 | // The code above will never insert at the end of the document. Its important to do that 80 | // sometimes. 81 | if (randomInt(docStr.length === 0 ? 2 : 10) === 0) addInsert() 82 | 83 | expectedDoc += docStr.join('') 84 | return [type.normalize(op), expectedDoc] 85 | } -------------------------------------------------------------------------------- /test/immutable.js: -------------------------------------------------------------------------------- 1 | const assert = require('assert') 2 | const { type } = require('../dist/json1.release') 3 | const log = require('../dist/log').default 4 | const genOp = require('./genOp') 5 | const deepClone = require('../dist/deepClone').default 6 | 7 | // This tests that none of apply / compose / transform / genOp mutate their input 8 | describe('immutable guarantees', function() { 9 | const origDoc = { x: 'hi', y: 'omg', z: [1, 'whoa', 3] } 10 | const expectDoc = deepClone(origDoc) 11 | const n = 1000 12 | // These tests are only slow in debug mode. In release mode they're pretty snappy. 13 | this.slow(n * 5) 14 | this.timeout(n * 10) 15 | 16 | it('apply does not mutate', () => { 17 | const result = [] 18 | for (let i = 0; i < n; i++) { 19 | const [op, doc] = genOp(origDoc) 20 | assert.deepStrictEqual(origDoc, expectDoc) 21 | 22 | const expectOp = deepClone(op) 23 | try { 24 | type.apply(origDoc, op) 25 | } catch (e) { 26 | console.log(`Apply failed! Repro apply( ${JSON.stringify(origDoc)}, ${JSON.stringify(op)} )`) 27 | throw e 28 | } 29 | 30 | assert.deepStrictEqual(origDoc, expectDoc) 31 | result.push(assert.deepStrictEqual(op, expectOp)) 32 | } 33 | return result 34 | }) 35 | 36 | it('compose does not mutate', () => { 37 | for (let i = 0; i < n; i++) { 38 | let op2 39 | let [op1, doc] = genOp(origDoc) 40 | ;[op2, doc] = genOp(doc) 41 | 42 | const expectOp1 = deepClone(op1) 43 | const expectOp2 = deepClone(op2) 44 | type.compose( 45 | op1, 46 | op2 47 | ) 48 | 49 | assert.deepStrictEqual(op1, expectOp1) 50 | assert.deepStrictEqual(op2, expectOp2) 51 | } 52 | }) 53 | 54 | it('transform does not mutate', () => { 55 | for (let i = 0; i < n; i++) { 56 | const [op1, doc1] = genOp(origDoc) 57 | const [op2, doc2] = genOp(origDoc) 58 | 59 | const expectOp1 = deepClone(op1) 60 | const expectOp2 = deepClone(op2) 61 | 62 | type.transformNoConflict(op1, op2, 'left') 63 | type.transformNoConflict(op2, op1, 'right') 64 | assert.deepStrictEqual(op1, expectOp1) 65 | assert.deepStrictEqual(op2, expectOp2) 66 | } 67 | }) 68 | }) 69 | -------------------------------------------------------------------------------- /test/ops.json: -------------------------------------------------------------------------------- 1 | null 2 | ["a",{"es":["hi"]}] 3 | ["a",{"r":true}] 4 | ["x",["a",{"p":0}],["b",{"d":0}]] 5 | ["x",{"es":["hi"]}] 6 | ["x",{"i":"one"}] 7 | ["x",{"i":"two"}] 8 | ["x",{"i":5}] 9 | ["x",{"r":true}] 10 | ["y",["a",{"p":0}],["b",{"d":0}]] 11 | ["y",{"r":true}] 12 | [0,{"i":"oh hi"}] 13 | [0,{"i":17}] 14 | [0,{"r":true}] 15 | [1,{"r":true}] 16 | [10,{"es":["edit"]}] 17 | [2,"x",{"i":"hi"}] 18 | [2,{"i":"hi"}] 19 | [2,{"i":"other"}] 20 | [2,{"r":true,"i":"other"}] 21 | [2,{"r":true}] 22 | [5,{"e":false,"et":"simple"}] 23 | [5,{"e":null,"et":"simple"}] 24 | [["X",{"d":0},"Y",{"d":1}],["x",{"p":0},"y",{"p":1}]] 25 | [["_x",{"p":0}],["y",{"d":0}]] 26 | [["a",{"d":0}],["x",{"r":true},"a",{"p":0}]] 27 | [["a",{"p":0},"b",{"p":1}],["b",{"d":1},"a",{"d":0}]] 28 | [["a",{"p":0}],["b",{"d":0}],["x",{"p":1}],["y",{"d":1}]] 29 | [["x",["a",{"p":0}],["b",{"d":0}]],["y",["a",{"p":1}],["b",{"d":1}]]] 30 | [["x",{"p":0}],["y",{"d":0}]] 31 | [[0,{"i":"a"}],[2,{"i":"b"}]] 32 | [[0,{"p":0}],[10,{"d":0}]] 33 | [[0,{"r":true}],[1,{"i":"hi"}]] 34 | [[1,{"p":0}],[2,{"d":0}]] 35 | [[1,{"r":{},"i":11}],[2,{"r":{},"i":12}]] 36 | [[11,{"i":1}],[12,{"i":2}],[13,{"i":3}]] 37 | [{"e":"hi","et":"simple"}] 38 | [{"e":false,"et":"simple"}] 39 | [{"e":null,"et":"simple"}] 40 | [{"e":{"position":2,"text":"wai"},"et":"simple"}] 41 | [{"e":{},"et":"simple"}] 42 | [{"es":"hi"}] 43 | [{"es":[2,"maghe"]}] 44 | [{"i":5}] 45 | [{"i":[1,2,3]}] 46 | [{"i":[],"r":true},[0,{"d":0}],["a",{"p":0}]] 47 | [{"r":{},"i":[1,2,3]}] 48 | [{"r":{}}] 49 | -------------------------------------------------------------------------------- /tracer.js: -------------------------------------------------------------------------------- 1 | // This is a simple little OT library which wraps another OT library, but 2 | // traces all calls so you can work out where everything came from. 3 | 4 | const type = require('./lib/json1') 5 | const {inspect} = require('util') 6 | const log = require('./lib/log') 7 | 8 | const deps = new Map // map from artifact to [...deps] 9 | const meta = new Map // artifact to user information 10 | 11 | const printInfo = (item, prefix = '', depth = 5) => { 12 | const m = meta.get(item) 13 | if (m == null) return log(prefix + 'NO DATA', item) 14 | log(prefix + m.fn + ' ->', item, m) 15 | 16 | if (depth > 0) { 17 | const ds = deps.get(item) 18 | if (ds == null) return 19 | log(prefix + 'deps:', ...ds) 20 | 21 | ds.forEach(d => printInfo(d, prefix + ' ', depth - 1)) 22 | } 23 | } 24 | 25 | module.exports = (type, genOp) => ({ 26 | ...type, 27 | 28 | create(data) { return type.create(data) }, 29 | 30 | apply(snapshot, op) { 31 | try { 32 | const result = type.apply(snapshot, op) 33 | if (result !== snapshot) { 34 | deps.set(result, [snapshot, op]) 35 | meta.set(result, {fn:'apply'}) 36 | } 37 | 38 | return result 39 | } catch (e) { 40 | console.error('************************************* APPLY FAILED!') 41 | console.error(e.stack) 42 | log.quiet = false 43 | printInfo(snapshot) 44 | printInfo(op) 45 | throw e 46 | } 47 | }, 48 | 49 | transform(op1, op2, side) { 50 | try { 51 | const result = type.transform(op1, op2, side) 52 | if (result !== op1) { 53 | deps.set(result, [op1, op2]) 54 | meta.set(result, {fn:'transform', side}) 55 | } 56 | 57 | return result 58 | } catch (e) { 59 | console.error('************************************* TRANSFORM FAILED!') 60 | console.error(e.stack) 61 | log.quiet = false 62 | printInfo(op1) 63 | printInfo(op2) 64 | throw e 65 | } 66 | 67 | }, 68 | 69 | genOp(snapshot) { 70 | try { 71 | const [op, result] = genOp(snapshot) 72 | deps.set(op, [snapshot]) 73 | deps.set(result, [snapshot]) 74 | meta.set(op, {fn:'genop', result}) 75 | meta.set(result, {fn:'genop', op}) 76 | return [op, result] 77 | } catch (e) { 78 | console.error('************************************* OOPSIE!') 79 | console.error(e.stack) 80 | printInfo(snapshot) 81 | throw e 82 | } 83 | 84 | }, 85 | }) 86 | 87 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Basic Options */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | "target": "ES2017", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ 8 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ 9 | // "lib": [], /* Specify library files to be included in the compilation. */ 10 | // "allowJs": true, /* Allow javascript files to be compiled. */ 11 | // "checkJs": true, /* Report errors in .js files. */ 12 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 13 | "declaration": true, /* Generates corresponding '.d.ts' file. */ 14 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 15 | "sourceMap": true, /* Generates corresponding '.map' file. */ 16 | // "outFile": "./", /* Concatenate and emit output to single file. */ 17 | "outDir": "./dist", /* Redirect output structure to the directory. */ 18 | // "rootDir": "./lib", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 19 | // "composite": true, /* Enable project compilation */ 20 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 21 | // "removeComments": true, /* Do not emit comments to output. */ 22 | // "noEmit": true, /* Do not emit outputs. */ 23 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 24 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 25 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 26 | 27 | /* Strict Type-Checking Options */ 28 | "strict": true, /* Enable all strict type-checking options. */ 29 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 30 | // "strictNullChecks": true, /* Enable strict null checks. */ 31 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 32 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 33 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 34 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 35 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 36 | 37 | /* Additional Checks */ 38 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 39 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 40 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 41 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 42 | 43 | /* Module Resolution Options */ 44 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 45 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 46 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 47 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 48 | // "typeRoots": [], /* List of folders to include type definitions from. */ 49 | // "types": [], /* Type declaration files to be included in compilation. */ 50 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 51 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 52 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 53 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 54 | 55 | /* Source Map Options */ 56 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 57 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 58 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 59 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 60 | 61 | /* Experimental Options */ 62 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 63 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 64 | 65 | /* Advanced Options */ 66 | "skipLibCheck": true, /* Skip type checking of declaration files. */ 67 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@types/node@^14.0.13": 6 | version "14.0.13" 7 | resolved "https://registry.yarnpkg.com/@types/node/-/node-14.0.13.tgz#ee1128e881b874c371374c1f72201893616417c9" 8 | integrity sha512-rouEWBImiRaSJsVA+ITTFM6ZxibuAlTuNOCyxVbwreu6k6+ujs7DfnU9o+PShFhET78pMBl3eH+AGSI5eOTkPA== 9 | 10 | ansi-colors@3.2.3: 11 | version "3.2.3" 12 | resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.3.tgz#57d35b8686e851e2cc04c403f1c00203976a1813" 13 | integrity sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw== 14 | 15 | ansi-regex@^3.0.0: 16 | version "3.0.0" 17 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" 18 | integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= 19 | 20 | ansi-regex@^4.1.0: 21 | version "4.1.0" 22 | resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" 23 | integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== 24 | 25 | ansi-styles@^3.2.0, ansi-styles@^3.2.1: 26 | version "3.2.1" 27 | resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" 28 | integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== 29 | dependencies: 30 | color-convert "^1.9.0" 31 | 32 | anymatch@~3.1.1: 33 | version "3.1.1" 34 | resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142" 35 | integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg== 36 | dependencies: 37 | normalize-path "^3.0.0" 38 | picomatch "^2.0.4" 39 | 40 | argparse@^1.0.7: 41 | version "1.0.10" 42 | resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" 43 | integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== 44 | dependencies: 45 | sprintf-js "~1.0.2" 46 | 47 | balanced-match@^1.0.0: 48 | version "1.0.0" 49 | resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" 50 | integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= 51 | 52 | binary-extensions@^2.0.0: 53 | version "2.0.0" 54 | resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.0.0.tgz#23c0df14f6a88077f5f986c0d167ec03c3d5537c" 55 | integrity sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow== 56 | 57 | brace-expansion@^1.1.7: 58 | version "1.1.11" 59 | resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" 60 | integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== 61 | dependencies: 62 | balanced-match "^1.0.0" 63 | concat-map "0.0.1" 64 | 65 | braces@~3.0.2: 66 | version "3.0.2" 67 | resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" 68 | integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== 69 | dependencies: 70 | fill-range "^7.0.1" 71 | 72 | browser-stdout@1.3.1: 73 | version "1.3.1" 74 | resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" 75 | integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== 76 | 77 | buffer-from@^1.0.0: 78 | version "1.1.1" 79 | resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" 80 | integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== 81 | 82 | camelcase@^5.0.0: 83 | version "5.3.1" 84 | resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" 85 | integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== 86 | 87 | chalk@^2.4.2: 88 | version "2.4.2" 89 | resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" 90 | integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== 91 | dependencies: 92 | ansi-styles "^3.2.1" 93 | escape-string-regexp "^1.0.5" 94 | supports-color "^5.3.0" 95 | 96 | chokidar@3.3.0: 97 | version "3.3.0" 98 | resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.3.0.tgz#12c0714668c55800f659e262d4962a97faf554a6" 99 | integrity sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A== 100 | dependencies: 101 | anymatch "~3.1.1" 102 | braces "~3.0.2" 103 | glob-parent "~5.1.0" 104 | is-binary-path "~2.1.0" 105 | is-glob "~4.0.1" 106 | normalize-path "~3.0.0" 107 | readdirp "~3.2.0" 108 | optionalDependencies: 109 | fsevents "~2.1.1" 110 | 111 | cli-progress@^2.1.1: 112 | version "2.1.1" 113 | resolved "https://registry.yarnpkg.com/cli-progress/-/cli-progress-2.1.1.tgz#45ee1b143487c19043a3262131ccb4676f87f032" 114 | integrity sha512-TSJw3LY9ZRSis7yYzQ7flIdtQMbacd9oYoiFphJhI4SzgmqF0zErO+uNv0lbUjk1L4AGfHQJ4OVYYzW+JV66KA== 115 | dependencies: 116 | colors "^1.1.2" 117 | string-width "^2.1.1" 118 | 119 | cliui@^5.0.0: 120 | version "5.0.0" 121 | resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" 122 | integrity sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA== 123 | dependencies: 124 | string-width "^3.1.0" 125 | strip-ansi "^5.2.0" 126 | wrap-ansi "^5.1.0" 127 | 128 | color-convert@^1.9.0: 129 | version "1.9.3" 130 | resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" 131 | integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== 132 | dependencies: 133 | color-name "1.1.3" 134 | 135 | color-name@1.1.3: 136 | version "1.1.3" 137 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" 138 | integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= 139 | 140 | colors@^1.1.2: 141 | version "1.4.0" 142 | resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78" 143 | integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA== 144 | 145 | commander@^2.20.0: 146 | version "2.20.3" 147 | resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" 148 | integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== 149 | 150 | concat-map@0.0.1: 151 | version "0.0.1" 152 | resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" 153 | integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= 154 | 155 | debug@3.2.6: 156 | version "3.2.6" 157 | resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" 158 | integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== 159 | dependencies: 160 | ms "^2.1.1" 161 | 162 | decamelize@^1.2.0: 163 | version "1.2.0" 164 | resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" 165 | integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= 166 | 167 | define-properties@^1.1.2, define-properties@^1.1.3: 168 | version "1.1.3" 169 | resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" 170 | integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== 171 | dependencies: 172 | object-keys "^1.0.12" 173 | 174 | diff@3.5.0: 175 | version "3.5.0" 176 | resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" 177 | integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== 178 | 179 | emoji-regex@^7.0.1: 180 | version "7.0.3" 181 | resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" 182 | integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== 183 | 184 | es-abstract@^1.17.0-next.1: 185 | version "1.17.5" 186 | resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.17.5.tgz#d8c9d1d66c8981fb9200e2251d799eee92774ae9" 187 | integrity sha512-BR9auzDbySxOcfog0tLECW8l28eRGpDpU3Dm3Hp4q/N+VtLTmyj4EUN088XZWQDW/hzj6sYRDXeOFsaAODKvpg== 188 | dependencies: 189 | es-to-primitive "^1.2.1" 190 | function-bind "^1.1.1" 191 | has "^1.0.3" 192 | has-symbols "^1.0.1" 193 | is-callable "^1.1.5" 194 | is-regex "^1.0.5" 195 | object-inspect "^1.7.0" 196 | object-keys "^1.1.1" 197 | object.assign "^4.1.0" 198 | string.prototype.trimleft "^2.1.1" 199 | string.prototype.trimright "^2.1.1" 200 | 201 | es-to-primitive@^1.2.1: 202 | version "1.2.1" 203 | resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a" 204 | integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA== 205 | dependencies: 206 | is-callable "^1.1.4" 207 | is-date-object "^1.0.1" 208 | is-symbol "^1.0.2" 209 | 210 | escape-string-regexp@1.0.5, escape-string-regexp@^1.0.5: 211 | version "1.0.5" 212 | resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" 213 | integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= 214 | 215 | esprima@^4.0.0: 216 | version "4.0.1" 217 | resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" 218 | integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== 219 | 220 | fill-range@^7.0.1: 221 | version "7.0.1" 222 | resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" 223 | integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== 224 | dependencies: 225 | to-regex-range "^5.0.1" 226 | 227 | find-up@3.0.0, find-up@^3.0.0: 228 | version "3.0.0" 229 | resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" 230 | integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== 231 | dependencies: 232 | locate-path "^3.0.0" 233 | 234 | flat@^4.1.0: 235 | version "4.1.0" 236 | resolved "https://registry.yarnpkg.com/flat/-/flat-4.1.0.tgz#090bec8b05e39cba309747f1d588f04dbaf98db2" 237 | integrity sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw== 238 | dependencies: 239 | is-buffer "~2.0.3" 240 | 241 | fs.realpath@^1.0.0: 242 | version "1.0.0" 243 | resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" 244 | integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= 245 | 246 | fsevents@~2.1.1: 247 | version "2.1.2" 248 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.2.tgz#4c0a1fb34bc68e543b4b82a9ec392bfbda840805" 249 | integrity sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA== 250 | 251 | function-bind@^1.1.1: 252 | version "1.1.1" 253 | resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" 254 | integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== 255 | 256 | get-caller-file@^2.0.1: 257 | version "2.0.5" 258 | resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" 259 | integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== 260 | 261 | glob-parent@~5.1.0: 262 | version "5.1.2" 263 | resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" 264 | integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== 265 | dependencies: 266 | is-glob "^4.0.1" 267 | 268 | glob@7.1.3: 269 | version "7.1.3" 270 | resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" 271 | integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== 272 | dependencies: 273 | fs.realpath "^1.0.0" 274 | inflight "^1.0.4" 275 | inherits "2" 276 | minimatch "^3.0.4" 277 | once "^1.3.0" 278 | path-is-absolute "^1.0.0" 279 | 280 | growl@1.10.5: 281 | version "1.10.5" 282 | resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" 283 | integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== 284 | 285 | has-flag@^3.0.0: 286 | version "3.0.0" 287 | resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" 288 | integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= 289 | 290 | has-symbols@^1.0.0, has-symbols@^1.0.1: 291 | version "1.0.1" 292 | resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8" 293 | integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg== 294 | 295 | has@^1.0.3: 296 | version "1.0.3" 297 | resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" 298 | integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== 299 | dependencies: 300 | function-bind "^1.1.1" 301 | 302 | he@1.2.0: 303 | version "1.2.0" 304 | resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" 305 | integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== 306 | 307 | inflight@^1.0.4: 308 | version "1.0.6" 309 | resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" 310 | integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= 311 | dependencies: 312 | once "^1.3.0" 313 | wrappy "1" 314 | 315 | inherits@2: 316 | version "2.0.4" 317 | resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" 318 | integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== 319 | 320 | is-binary-path@~2.1.0: 321 | version "2.1.0" 322 | resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" 323 | integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== 324 | dependencies: 325 | binary-extensions "^2.0.0" 326 | 327 | is-buffer@~2.0.3: 328 | version "2.0.4" 329 | resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-2.0.4.tgz#3e572f23c8411a5cfd9557c849e3665e0b290623" 330 | integrity sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A== 331 | 332 | is-callable@^1.1.4, is-callable@^1.1.5: 333 | version "1.1.5" 334 | resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab" 335 | integrity sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q== 336 | 337 | is-date-object@^1.0.1: 338 | version "1.0.2" 339 | resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" 340 | integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== 341 | 342 | is-extglob@^2.1.1: 343 | version "2.1.1" 344 | resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" 345 | integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= 346 | 347 | is-fullwidth-code-point@^2.0.0: 348 | version "2.0.0" 349 | resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" 350 | integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= 351 | 352 | is-glob@^4.0.1, is-glob@~4.0.1: 353 | version "4.0.1" 354 | resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" 355 | integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== 356 | dependencies: 357 | is-extglob "^2.1.1" 358 | 359 | is-number@^7.0.0: 360 | version "7.0.0" 361 | resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" 362 | integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== 363 | 364 | is-regex@^1.0.5: 365 | version "1.0.5" 366 | resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.5.tgz#39d589a358bf18967f726967120b8fc1aed74eae" 367 | integrity sha512-vlKW17SNq44owv5AQR3Cq0bQPEb8+kF3UKZ2fiZNOWtztYE5i0CzCZxFDwO58qAOWtxdBRVO/V5Qin1wjCqFYQ== 368 | dependencies: 369 | has "^1.0.3" 370 | 371 | is-symbol@^1.0.2: 372 | version "1.0.3" 373 | resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.3.tgz#38e1014b9e6329be0de9d24a414fd7441ec61937" 374 | integrity sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ== 375 | dependencies: 376 | has-symbols "^1.0.1" 377 | 378 | isexe@^2.0.0: 379 | version "2.0.0" 380 | resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" 381 | integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= 382 | 383 | js-yaml@3.13.1: 384 | version "3.13.1" 385 | resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847" 386 | integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw== 387 | dependencies: 388 | argparse "^1.0.7" 389 | esprima "^4.0.0" 390 | 391 | locate-path@^3.0.0: 392 | version "3.0.0" 393 | resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" 394 | integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== 395 | dependencies: 396 | p-locate "^3.0.0" 397 | path-exists "^3.0.0" 398 | 399 | lodash@^4.17.15: 400 | version "4.17.21" 401 | resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" 402 | integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== 403 | 404 | log-symbols@3.0.0: 405 | version "3.0.0" 406 | resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-3.0.0.tgz#f3a08516a5dea893336a7dee14d18a1cfdab77c4" 407 | integrity sha512-dSkNGuI7iG3mfvDzUuYZyvk5dD9ocYCYzNU6CYDE6+Xqd+gwme6Z00NS3dUh8mq/73HaEtT7m6W+yUPtU6BZnQ== 408 | dependencies: 409 | chalk "^2.4.2" 410 | 411 | minimatch@3.0.4, minimatch@^3.0.4: 412 | version "3.0.4" 413 | resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" 414 | integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== 415 | dependencies: 416 | brace-expansion "^1.1.7" 417 | 418 | minimist@^1.2.5: 419 | version "1.2.5" 420 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" 421 | integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== 422 | 423 | mkdirp@0.5.3: 424 | version "0.5.3" 425 | resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.3.tgz#5a514b7179259287952881e94410ec5465659f8c" 426 | integrity sha512-P+2gwrFqx8lhew375MQHHeTlY8AuOJSrGf0R5ddkEndUkmwpgUob/vQuBD1V22/Cw1/lJr4x+EjllSezBThzBg== 427 | dependencies: 428 | minimist "^1.2.5" 429 | 430 | mocha@^7.1.1: 431 | version "7.1.1" 432 | resolved "https://registry.yarnpkg.com/mocha/-/mocha-7.1.1.tgz#89fbb30d09429845b1bb893a830bf5771049a441" 433 | integrity sha512-3qQsu3ijNS3GkWcccT5Zw0hf/rWvu1fTN9sPvEd81hlwsr30GX2GcDSSoBxo24IR8FelmrAydGC6/1J5QQP4WA== 434 | dependencies: 435 | ansi-colors "3.2.3" 436 | browser-stdout "1.3.1" 437 | chokidar "3.3.0" 438 | debug "3.2.6" 439 | diff "3.5.0" 440 | escape-string-regexp "1.0.5" 441 | find-up "3.0.0" 442 | glob "7.1.3" 443 | growl "1.10.5" 444 | he "1.2.0" 445 | js-yaml "3.13.1" 446 | log-symbols "3.0.0" 447 | minimatch "3.0.4" 448 | mkdirp "0.5.3" 449 | ms "2.1.1" 450 | node-environment-flags "1.0.6" 451 | object.assign "4.1.0" 452 | strip-json-comments "2.0.1" 453 | supports-color "6.0.0" 454 | which "1.3.1" 455 | wide-align "1.1.3" 456 | yargs "13.3.2" 457 | yargs-parser "13.1.2" 458 | yargs-unparser "1.6.0" 459 | 460 | ms@2.1.1: 461 | version "2.1.1" 462 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" 463 | integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== 464 | 465 | ms@^2.1.1: 466 | version "2.1.2" 467 | resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" 468 | integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== 469 | 470 | node-environment-flags@1.0.6: 471 | version "1.0.6" 472 | resolved "https://registry.yarnpkg.com/node-environment-flags/-/node-environment-flags-1.0.6.tgz#a30ac13621f6f7d674260a54dede048c3982c088" 473 | integrity sha512-5Evy2epuL+6TM0lCQGpFIj6KwiEsGh1SrHUhTbNX+sLbBtjidPZFAnVK9y5yU1+h//RitLbRHTIMyxQPtxMdHw== 474 | dependencies: 475 | object.getownpropertydescriptors "^2.0.3" 476 | semver "^5.7.0" 477 | 478 | normalize-path@^3.0.0, normalize-path@~3.0.0: 479 | version "3.0.0" 480 | resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" 481 | integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== 482 | 483 | object-inspect@^1.7.0: 484 | version "1.7.0" 485 | resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.7.0.tgz#f4f6bd181ad77f006b5ece60bd0b6f398ff74a67" 486 | integrity sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw== 487 | 488 | object-keys@^1.0.11, object-keys@^1.0.12, object-keys@^1.1.1: 489 | version "1.1.1" 490 | resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" 491 | integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== 492 | 493 | object.assign@4.1.0, object.assign@^4.1.0: 494 | version "4.1.0" 495 | resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.0.tgz#968bf1100d7956bb3ca086f006f846b3bc4008da" 496 | integrity sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w== 497 | dependencies: 498 | define-properties "^1.1.2" 499 | function-bind "^1.1.1" 500 | has-symbols "^1.0.0" 501 | object-keys "^1.0.11" 502 | 503 | object.getownpropertydescriptors@^2.0.3: 504 | version "2.1.0" 505 | resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz#369bf1f9592d8ab89d712dced5cb81c7c5352649" 506 | integrity sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg== 507 | dependencies: 508 | define-properties "^1.1.3" 509 | es-abstract "^1.17.0-next.1" 510 | 511 | once@^1.3.0: 512 | version "1.4.0" 513 | resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" 514 | integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= 515 | dependencies: 516 | wrappy "1" 517 | 518 | ot-fuzzer@1.3: 519 | version "1.3.0" 520 | resolved "https://registry.yarnpkg.com/ot-fuzzer/-/ot-fuzzer-1.3.0.tgz#972a47672851eadc764622c25d39dbd92585cd83" 521 | integrity sha512-cZNdwrLkJ+p+/go94KFFmuiXsXy66eot+TBrStEMTxs4tGCQl43EKeqpTe9GpgVlftpWqzbzetIuZsg2s98QqQ== 522 | dependencies: 523 | cli-progress "^2.1.1" 524 | seedrandom "^2.4.4" 525 | 526 | ot-simple@^1.0.0: 527 | version "1.0.0" 528 | resolved "https://registry.yarnpkg.com/ot-simple/-/ot-simple-1.0.0.tgz#078d840f81ea3aad38cbe69475f81b2c6754fb50" 529 | integrity sha1-B42ED4HqOq04y+aUdfgbLGdU+1A= 530 | 531 | ot-text-unicode@4: 532 | version "4.0.0" 533 | resolved "https://registry.yarnpkg.com/ot-text-unicode/-/ot-text-unicode-4.0.0.tgz#778a327535c81ed265b36ebe1bd677f31bae1e32" 534 | integrity sha512-W7ZLU8QXesY2wagYFv47zErXud3E93FGImmSGJsQnBzE+idcPPyo2u2KMilIrTwBh4pbCizy71qRjmmV6aDhcQ== 535 | dependencies: 536 | unicount "1.1" 537 | 538 | p-limit@^2.0.0: 539 | version "2.2.2" 540 | resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.2.tgz#61279b67721f5287aa1c13a9a7fbbc48c9291b1e" 541 | integrity sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ== 542 | dependencies: 543 | p-try "^2.0.0" 544 | 545 | p-locate@^3.0.0: 546 | version "3.0.0" 547 | resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" 548 | integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== 549 | dependencies: 550 | p-limit "^2.0.0" 551 | 552 | p-try@^2.0.0: 553 | version "2.2.0" 554 | resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" 555 | integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== 556 | 557 | path-exists@^3.0.0: 558 | version "3.0.0" 559 | resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" 560 | integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= 561 | 562 | path-is-absolute@^1.0.0: 563 | version "1.0.1" 564 | resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" 565 | integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= 566 | 567 | picomatch@^2.0.4: 568 | version "2.2.2" 569 | resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad" 570 | integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg== 571 | 572 | readdirp@~3.2.0: 573 | version "3.2.0" 574 | resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.2.0.tgz#c30c33352b12c96dfb4b895421a49fd5a9593839" 575 | integrity sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ== 576 | dependencies: 577 | picomatch "^2.0.4" 578 | 579 | require-directory@^2.1.1: 580 | version "2.1.1" 581 | resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" 582 | integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= 583 | 584 | require-main-filename@^2.0.0: 585 | version "2.0.0" 586 | resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" 587 | integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== 588 | 589 | seedrandom@^2.4.4: 590 | version "2.4.4" 591 | resolved "https://registry.yarnpkg.com/seedrandom/-/seedrandom-2.4.4.tgz#b25ea98632c73e45f58b77cfaa931678df01f9ba" 592 | integrity sha512-9A+PDmgm+2du77B5i0Ip2cxOqqHjgNxnBgglxLcX78A2D6c2rTo61z4jnVABpF4cKeDMDG+cmXXvdnqse2VqMA== 593 | 594 | semver@^5.7.0: 595 | version "5.7.1" 596 | resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" 597 | integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ== 598 | 599 | set-blocking@^2.0.0: 600 | version "2.0.0" 601 | resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" 602 | integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= 603 | 604 | source-map-support@~0.5.12: 605 | version "0.5.16" 606 | resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.16.tgz#0ae069e7fe3ba7538c64c98515e35339eac5a042" 607 | integrity sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ== 608 | dependencies: 609 | buffer-from "^1.0.0" 610 | source-map "^0.6.0" 611 | 612 | source-map@^0.6.0, source-map@~0.6.1: 613 | version "0.6.1" 614 | resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" 615 | integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== 616 | 617 | sprintf-js@~1.0.2: 618 | version "1.0.3" 619 | resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" 620 | integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= 621 | 622 | "string-width@^1.0.2 || 2", string-width@^2.1.1: 623 | version "2.1.1" 624 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" 625 | integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== 626 | dependencies: 627 | is-fullwidth-code-point "^2.0.0" 628 | strip-ansi "^4.0.0" 629 | 630 | string-width@^3.0.0, string-width@^3.1.0: 631 | version "3.1.0" 632 | resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" 633 | integrity sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w== 634 | dependencies: 635 | emoji-regex "^7.0.1" 636 | is-fullwidth-code-point "^2.0.0" 637 | strip-ansi "^5.1.0" 638 | 639 | string.prototype.trimleft@^2.1.1: 640 | version "2.1.1" 641 | resolved "https://registry.yarnpkg.com/string.prototype.trimleft/-/string.prototype.trimleft-2.1.1.tgz#9bdb8ac6abd6d602b17a4ed321870d2f8dcefc74" 642 | integrity sha512-iu2AGd3PuP5Rp7x2kEZCrB2Nf41ehzh+goo8TV7z8/XDBbsvc6HQIlUl9RjkZ4oyrW1XM5UwlGl1oVEaDjg6Ag== 643 | dependencies: 644 | define-properties "^1.1.3" 645 | function-bind "^1.1.1" 646 | 647 | string.prototype.trimright@^2.1.1: 648 | version "2.1.1" 649 | resolved "https://registry.yarnpkg.com/string.prototype.trimright/-/string.prototype.trimright-2.1.1.tgz#440314b15996c866ce8a0341894d45186200c5d9" 650 | integrity sha512-qFvWL3/+QIgZXVmJBfpHmxLB7xsUXz6HsUmP8+5dRaC3Q7oKUv9Vo6aMCRZC1smrtyECFsIT30PqBJ1gTjAs+g== 651 | dependencies: 652 | define-properties "^1.1.3" 653 | function-bind "^1.1.1" 654 | 655 | strip-ansi@^4.0.0: 656 | version "4.0.0" 657 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" 658 | integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= 659 | dependencies: 660 | ansi-regex "^3.0.0" 661 | 662 | strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: 663 | version "5.2.0" 664 | resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" 665 | integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== 666 | dependencies: 667 | ansi-regex "^4.1.0" 668 | 669 | strip-json-comments@2.0.1: 670 | version "2.0.1" 671 | resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" 672 | integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= 673 | 674 | supports-color@6.0.0: 675 | version "6.0.0" 676 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.0.0.tgz#76cfe742cf1f41bb9b1c29ad03068c05b4c0e40a" 677 | integrity sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg== 678 | dependencies: 679 | has-flag "^3.0.0" 680 | 681 | supports-color@^5.3.0: 682 | version "5.5.0" 683 | resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" 684 | integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== 685 | dependencies: 686 | has-flag "^3.0.0" 687 | 688 | terser@^4.6.7: 689 | version "4.6.7" 690 | resolved "https://registry.yarnpkg.com/terser/-/terser-4.6.7.tgz#478d7f9394ec1907f0e488c5f6a6a9a2bad55e72" 691 | integrity sha512-fmr7M1f7DBly5cX2+rFDvmGBAaaZyPrHYK4mMdHEDAdNTqXSZgSOfqsfGq2HqPGT/1V0foZZuCZFx8CHKgAk3g== 692 | dependencies: 693 | commander "^2.20.0" 694 | source-map "~0.6.1" 695 | source-map-support "~0.5.12" 696 | 697 | to-regex-range@^5.0.1: 698 | version "5.0.1" 699 | resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" 700 | integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== 701 | dependencies: 702 | is-number "^7.0.0" 703 | 704 | typescript@^3.9.5: 705 | version "3.9.5" 706 | resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.5.tgz#586f0dba300cde8be52dd1ac4f7e1009c1b13f36" 707 | integrity sha512-hSAifV3k+i6lEoCJ2k6R2Z/rp/H3+8sdmcn5NrS3/3kE7+RyZXm9aqvxWqjEXHAd8b0pShatpcdMTvEdvAJltQ== 708 | 709 | unicount@1.1: 710 | version "1.1.0" 711 | resolved "https://registry.yarnpkg.com/unicount/-/unicount-1.1.0.tgz#396a3df661c19675a93861ac878c2c9c0042abf0" 712 | integrity sha512-RlwWt1ywVW4WErPGAVHw/rIuJ2+MxvTME0siJ6lk9zBhpDfExDbspe6SRlWT3qU6AucNjotPl9qAJRVjP7guCQ== 713 | 714 | which-module@^2.0.0: 715 | version "2.0.0" 716 | resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" 717 | integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= 718 | 719 | which@1.3.1: 720 | version "1.3.1" 721 | resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" 722 | integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== 723 | dependencies: 724 | isexe "^2.0.0" 725 | 726 | wide-align@1.1.3: 727 | version "1.1.3" 728 | resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" 729 | integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== 730 | dependencies: 731 | string-width "^1.0.2 || 2" 732 | 733 | wrap-ansi@^5.1.0: 734 | version "5.1.0" 735 | resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-5.1.0.tgz#1fd1f67235d5b6d0fee781056001bfb694c03b09" 736 | integrity sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q== 737 | dependencies: 738 | ansi-styles "^3.2.0" 739 | string-width "^3.0.0" 740 | strip-ansi "^5.0.0" 741 | 742 | wrappy@1: 743 | version "1.0.2" 744 | resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" 745 | integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= 746 | 747 | y18n@^4.0.0: 748 | version "4.0.1" 749 | resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.1.tgz#8db2b83c31c5d75099bb890b23f3094891e247d4" 750 | integrity sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ== 751 | 752 | yargs-parser@13.1.2, yargs-parser@^13.1.2: 753 | version "13.1.2" 754 | resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-13.1.2.tgz#130f09702ebaeef2650d54ce6e3e5706f7a4fb38" 755 | integrity sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg== 756 | dependencies: 757 | camelcase "^5.0.0" 758 | decamelize "^1.2.0" 759 | 760 | yargs-unparser@1.6.0: 761 | version "1.6.0" 762 | resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-1.6.0.tgz#ef25c2c769ff6bd09e4b0f9d7c605fb27846ea9f" 763 | integrity sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw== 764 | dependencies: 765 | flat "^4.1.0" 766 | lodash "^4.17.15" 767 | yargs "^13.3.0" 768 | 769 | yargs@13.3.2, yargs@^13.3.0: 770 | version "13.3.2" 771 | resolved "https://registry.yarnpkg.com/yargs/-/yargs-13.3.2.tgz#ad7ffefec1aa59565ac915f82dccb38a9c31a2dd" 772 | integrity sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw== 773 | dependencies: 774 | cliui "^5.0.0" 775 | find-up "^3.0.0" 776 | get-caller-file "^2.0.1" 777 | require-directory "^2.1.1" 778 | require-main-filename "^2.0.0" 779 | set-blocking "^2.0.0" 780 | string-width "^3.0.0" 781 | which-module "^2.0.0" 782 | y18n "^4.0.0" 783 | yargs-parser "^13.1.2" 784 | --------------------------------------------------------------------------------