diff --git a/README.md b/README.md index 086ac1b4..327eb5b0 100644 --- a/README.md +++ b/README.md @@ -248,7 +248,7 @@ The promise returns an instance of Y. We denote it with a lower case `y`. * y-websockets-client aways waits to sync with the server * y.connector.disconnect() * Force to disconnect this instance from the other instances -* y.connector.reconnect() +* y.connector.connect() * Try to reconnect to the other instances (needs to be supported by the connector) * Not supported by y-xmpp diff --git a/lib/binary.js b/lib/binary.js new file mode 100644 index 00000000..722eac4c --- /dev/null +++ b/lib/binary.js @@ -0,0 +1,7 @@ + +export const BITS32 = 0xFFFFFFFF +export const BITS21 = (1 << 21) - 1 +export const BITS16 = (1 << 16) - 1 + +export const BIT26 = 1 << 26 +export const BIT32 = 1 << 32 diff --git a/lib/decoding.js b/lib/decoding.js index eba0220b..fcbe3e7f 100644 --- a/lib/decoding.js +++ b/lib/decoding.js @@ -6,7 +6,7 @@ import * as globals from './globals.js' /** * A Decoder handles the decoding of an ArrayBuffer. */ -class Decoder { +export class Decoder { /** * @param {ArrayBuffer} buffer Binary data to decode */ @@ -166,23 +166,3 @@ export const peekVarString = decoder => { decoder.pos = pos return s } - -/** - * Read ID. - * * If first varUint read is 0xFFFFFF a RootID is returned. - * * Otherwise an ID is returned - * - * @param {Decoder} decoder - * @return {ID} - * -export const readID = decoder => { - let user = decoder.readVarUint() - if (user === RootFakeUserID) { - // read property name and type id - const rid = new RootID(decoder.readVarString(), null) - rid.type = decoder.readVarUint() - return rid - } - return new ID(user, decoder.readVarUint()) -} -*/ diff --git a/lib/encoding.js b/lib/encoding.js index 74680fc2..5b94c952 100644 --- a/lib/encoding.js +++ b/lib/encoding.js @@ -21,16 +21,15 @@ export const createEncoder = () => new Encoder() * The current length of the encoded data. */ export const length = encoder => { - let len = 0 + let len = encoder.cpos for (let i = 0; i < encoder.bufs.length; i++) { len += encoder.bufs[i].length } - len += encoder.cpos return len } /** - * Transform to ArrayBuffer. + * Transform to ArrayBuffer. TODO: rename to .toArrayBuffer * @param {Encoder} encoder * @return {ArrayBuffer} The created ArrayBuffer. */ @@ -185,12 +184,14 @@ export const writeVarString = (encoder, str) => { } /** - * Write the content of another biUint8Arr + * Write the content of another Encoder. + * + * TODO: can be improved! * * @param {Encoder} encoder The enUint8Arr - * @param encoderToAppend The BinaryEncoder to be written. + * @param {Encoder} append The BinaryEncoder to be written. */ -export const writeBinaryEncoder = (encoder, encoderToAppend) => writeArrayBuffer(encoder, toBuffer(encoder)) +export const writeBinaryEncoder = (encoder, append) => writeArrayBuffer(encoder, toBuffer(append)) /** * Append an arrayBuffer to the encoder. @@ -215,20 +216,3 @@ export const writePayload = (encoder, arrayBuffer) => { writeVarUint(encoder, arrayBuffer.byteLength) writeArrayBuffer(encoder, arrayBuffer) } - -/** - * Write an ID at the current position. - * - * @param {ID} id The ID that is to be written. - * -export const writeID = (encoder, id) => { - const user = id.user - writeVarUint(encoder, user) - if (user !== RootFakeUserID) { - writeVarUint(encoder, id.clock) - } else { - writeVarString(encoder, id.name) - writeVarUint(encoder, id.type) - } -} -*/ diff --git a/lib/math.js b/lib/math.js new file mode 100644 index 00000000..818d05be --- /dev/null +++ b/lib/math.js @@ -0,0 +1,2 @@ + +export const floor = Math.floor diff --git a/lib/mutualExclude.js b/lib/mutex.js similarity index 56% rename from lib/mutualExclude.js rename to lib/mutex.js index 2fe4e0a6..d35b1061 100644 --- a/lib/mutualExclude.js +++ b/lib/mutex.js @@ -1,32 +1,30 @@ -// TODO: rename mutex /** * Creates a mutual exclude function with the following property: * * @example - * const mutualExclude = createMutualExclude() - * mutualExclude(function () { + * const mutex = createMutex() + * mutex(function () { * // This function is immediately executed - * mutualExclude(function () { + * mutex(function () { * // This function is never executed, as it is called with the same - * // mutualExclude + * // mutex function * }) * }) * * @return {Function} A mutual exclude function * @public */ -export function createMutualExclude () { - var token = true - return function mutualExclude (f, g) { +export const createMutex = () => { + let token = true + return (f, g) => { if (token) { token = false try { f() - } catch (e) { - console.error(e) + } finally { + token = true } - token = true } else if (g !== undefined) { g() } diff --git a/lib/number.js b/lib/number.js new file mode 100644 index 00000000..56194de7 --- /dev/null +++ b/lib/number.js @@ -0,0 +1,2 @@ +export const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER +export const MIN_SAFE_INTEGER = Number.MIN_SAFE_INTEGER diff --git a/lib/random.js b/lib/random.js deleted file mode 100644 index e69de29b..00000000 diff --git a/lib/random/PRNG/Mt19937.js b/lib/random/PRNG/Mt19937.js new file mode 100644 index 00000000..78d3aca4 --- /dev/null +++ b/lib/random/PRNG/Mt19937.js @@ -0,0 +1,66 @@ +const N = 624 +const M = 397 + +function twist (u, v) { + return ((((u & 0x80000000) | (v & 0x7fffffff)) >>> 1) ^ ((v & 1) ? 0x9908b0df : 0)) +} + +function nextState (state) { + let p = 0 + let j + for (j = N - M + 1; --j; p++) { + state[p] = state[p + M] ^ twist(state[p], state[p + 1]) + } + for (j = M; --j; p++) { + state[p] = state[p + M - N] ^ twist(state[p], state[p + 1]) + } + state[p] = state[p + M - N] ^ twist(state[p], state[0]) +} + +/** + * This is a port of Shawn Cokus's implementation of the original Mersenne Twister algorithm (http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/MT2002/CODES/MTARCOK/mt19937ar-cok.c). + * MT has a very high period of 2^19937. Though the authors of xorshift describe that a high period is not + * very relevant (http://vigna.di.unimi.it/xorshift/). It is four times slower than xoroshiro128plus and + * needs to recompute its state after generating 624 numbers. + * + * @example + * const gen = new Mt19937(new Date().getTime()) + * console.log(gen.next()) + * + * @public + */ +export default class Mt19937 { + /** + * @param {Number} seed The starting point for the random number generation. If you use the same seed, the generator will return the same sequence of random numbers. + */ + constructor (seed) { + this.seed = seed + const state = new Uint32Array(N) + state[0] = seed + for (let i = 1; i < N; i++) { + state[i] = (Math.imul(1812433253, (state[i - 1] ^ (state[i - 1] >>> 30))) + i) & 0xFFFFFFFF + } + this._state = state + this._i = 0 + nextState(this._state) + } + + /** + * Generate a random signed integer. + * + * @return {Number} A 32 bit signed integer. + */ + next () { + if (this._i === N) { + // need to compute a new state + nextState(this._state) + this._i = 0 + } + let y = this._state[this._i++] + y ^= (y >>> 11) + y ^= (y << 7) & 0x9d2c5680 + y ^= (y << 15) & 0xefc60000 + y ^= (y >>> 18) + return y + } +} diff --git a/lib/random/PRNG/PRNG.tests.js b/lib/random/PRNG/PRNG.tests.js new file mode 100644 index 00000000..1979d66a --- /dev/null +++ b/lib/random/PRNG/PRNG.tests.js @@ -0,0 +1,48 @@ + +import Mt19937 from './Mt19937.js' +import Xoroshiro128plus from './Xoroshiro128plus.js' +import Xorshift32 from './Xorshift32.js' +import * as time from '../../time.js' + +const DIAMETER = 300 +const NUMBERS = 10000 + +function runPRNG (name, Gen) { + console.log('== ' + name + ' ==') + const gen = new Gen(1234) + let head = 0 + let tails = 0 + const date = time.getUnixTime() + const canvas = document.createElement('canvas') + canvas.height = DIAMETER + canvas.width = DIAMETER + const ctx = canvas.getContext('2d') + const vals = new Set() + ctx.fillStyle = 'blue' + for (let i = 0; i < NUMBERS; i++) { + const n = gen.next() & 0xFFFFFF + const x = (gen.next() >>> 0) % DIAMETER + const y = (gen.next() >>> 0) % DIAMETER + ctx.fillRect(x, y, 1, 2) + if ((n & 1) === 1) { + head++ + } else { + tails++ + } + if (vals.has(n)) { + console.warn(`The generator generated a duplicate`) + } + vals.add(n) + } + console.log('time: ', time.getUnixTime() - date) + console.log('head:', head, 'tails:', tails) + console.log('%c ', `font-size: 200px; background: url(${canvas.toDataURL()}) no-repeat;`) + const h1 = document.createElement('h1') + h1.insertBefore(document.createTextNode(name), null) + document.body.insertBefore(h1, null) + document.body.appendChild(canvas) +} + +runPRNG('mt19937', Mt19937) +runPRNG('xoroshiro128plus', Xoroshiro128plus) +runPRNG('xorshift32', Xorshift32) diff --git a/lib/random/PRNG/README.md b/lib/random/PRNG/README.md new file mode 100644 index 00000000..a7de088b --- /dev/null +++ b/lib/random/PRNG/README.md @@ -0,0 +1,5 @@ +# Pseudo Random Number Generators (PRNG) + +Given a seed a PRNG generates a sequence of numbers that cannot be reasonably predicted. Two PRNGs must generate the same random sequence of numbers if given the same seed. + +TODO: explain what POINT is \ No newline at end of file diff --git a/lib/random/PRNG/Xoroshiro128plus.js b/lib/random/PRNG/Xoroshiro128plus.js new file mode 100644 index 00000000..56ccf50c --- /dev/null +++ b/lib/random/PRNG/Xoroshiro128plus.js @@ -0,0 +1,98 @@ + +import Xorshift32 from './Xorshift32.js' + +/** + * This is a variant of xoroshiro128plus - the fastest full-period generator passing BigCrush without systematic failures. + * + * This implementation follows the idea of the original xoroshiro128plus implementation, + * but is optimized for the JavaScript runtime. I.e. + * * The operations are performed on 32bit integers (the original implementation works with 64bit values). + * * The initial 128bit state is computed based on a 32bit seed and Xorshift32. + * * This implementation returns two 32bit values based on the 64bit value that is computed by xoroshiro128plus. + * Caution: The last addition step works slightly different than in the original implementation - the add carry of the + * first 32bit addition is not carried over to the last 32bit. + * + * [Reference implementation](http://vigna.di.unimi.it/xorshift/xoroshiro128plus.c) + */ +export default class Xoroshiro128plus { + constructor (seed) { + this.seed = seed + // This is a variant of Xoroshiro128plus to fill the initial state + const xorshift32 = new Xorshift32(seed) + this.state = new Uint32Array(4) + for (let i = 0; i < 4; i++) { + this.state[i] = xorshift32.next() + } + this._fresh = true + } + next () { + const state = this.state + if (this._fresh) { + this._fresh = false + return (state[0] + state[2]) & 0xFFFFFFFF + } else { + this._fresh = true + const s0 = state[0] + const s1 = state[1] + const s2 = state[2] ^ s0 + const s3 = state[3] ^ s1 + // function js_rotl (x, k) { + // k = k - 32 + // const x1 = x[0] + // const x2 = x[1] + // x[0] = x2 << k | x1 >>> (32 - k) + // x[1] = x1 << k | x2 >>> (32 - k) + // } + // rotl(s0, 55) // k = 23 = 55 - 32; j = 9 = 32 - 23 + state[0] = (s1 << 23 | s0 >>> 9) ^ s2 ^ (s2 << 14 | s3 >>> 18) + state[1] = (s0 << 23 | s1 >>> 9) ^ s3 ^ (s3 << 14) + // rol(s1, 36) // k = 4 = 36 - 32; j = 23 = 32 - 9 + state[2] = s3 << 4 | s2 >>> 28 + state[3] = s2 << 4 | s3 >>> 28 + return (state[1] + state[3]) & 0xFFFFFFFF + } + } +} + +/* + +// reference implementation +#include +#include + +uint64_t s[2]; + +static inline uint64_t rotl(const uint64_t x, int k) { + return (x << k) | (x >> (64 - k)); +} + +uint64_t next(void) { + const uint64_t s0 = s[0]; + uint64_t s1 = s[1]; + s1 ^= s0; + s[0] = rotl(s0, 55) ^ s1 ^ (s1 << 14); // a, b + s[1] = rotl(s1, 36); // c + return (s[0] + s[1]) & 0xFFFFFFFF; +} + +int main(void) +{ + int i; + s[0] = 1111 | (1337ul << 32); + s[1] = 1234 | (9999ul << 32); + + printf("1000 outputs of genrand_int31()\n"); + for (i=0; i<100; i++) { + printf("%10lu ", i); + printf("%10lu ", next()); + printf("- %10lu ", s[0] >> 32); + printf("%10lu ", (s[0] << 32) >> 32); + printf("%10lu ", s[1] >> 32); + printf("%10lu ", (s[1] << 32) >> 32); + printf("\n"); + // if (i%5==4) printf("\n"); + } + return 0; +} + +*/ diff --git a/lib/random/PRNG/Xorshift32.js b/lib/random/PRNG/Xorshift32.js new file mode 100644 index 00000000..6b3ec9a0 --- /dev/null +++ b/lib/random/PRNG/Xorshift32.js @@ -0,0 +1,26 @@ + +/** + * Xorshift32 is a very simple but elegang PRNG with a period of `2^32-1`. + */ +export default class Xorshift32 { + /** + * @param {number} seed The starting point for the random number generation. If you use the same seed, the generator will return the same sequence of random numbers. + */ + constructor (seed) { + this.seed = seed + this._state = seed + } + /** + * Generate a random signed integer. + * + * @return {Number} A 32 bit signed integer. + */ + next () { + let x = this._state + x ^= x << 13 + x ^= x >> 17 + x ^= x << 5 + this._state = x + return x + } +} diff --git a/lib/random/random.js b/lib/random/random.js new file mode 100644 index 00000000..3e1f6062 --- /dev/null +++ b/lib/random/random.js @@ -0,0 +1,131 @@ + +import * as binary from '../binary.js' +import { fromCharCode, fromCodePoint } from '../string.js' +import { MAX_SAFE_INTEGER, MIN_SAFE_INTEGER } from '../number.js' +import * as math from '../math.js' + +import DefaultPRNG from './PRNG/Xoroshiro128plus.js' + +/** + * Description of the function + * @callback generatorNext + * @return {number} A 32bit integer + */ + +/** + * A random type generator. + * + * @typedef {Object} PRNG + * @property {generatorNext} next Generate new number + */ + +/** + * Create a Xoroshiro128plus Pseudo-Random-Number-Generator. + * This is the fastest full-period generator passing BigCrush without systematic failures. + * But there are more PRNGs available in ./PRNG/. + * + * @param {number} seed A positive 32bit integer. Do not use negative numbers. + * @return {PRNG} + */ +export const createPRNG = seed => new DefaultPRNG(Math.floor(seed < 1 ? seed * binary.BITS32 : seed)) + +/** + * Generates a single random bool. + * + * @param {PRNG} gen A random number generator. + * @return {Boolean} A random boolean + */ +export const bool = gen => (gen.next() & 2) === 2 // brackets are non-optional! + +/** + * Generates a random integer with 53 bit resolution. + * + * @param {PRNG} gen A random number generator. + * @param {Number} [min = MIN_SAFE_INTEGER] The lower bound of the allowed return values (inclusive). + * @param {Number} [max = MAX_SAFE_INTEGER] The upper bound of the allowed return values (inclusive). + * @return {Number} A random integer on [min, max] + */ +export const int53 = (gen, min = MIN_SAFE_INTEGER, max = MAX_SAFE_INTEGER) => math.floor(real53(gen) * (max + 1 - min) + min) + +/** + * Generates a random integer with 32 bit resolution. + * + * @param {PRNG} gen A random number generator. + * @param {Number} [min = MIN_SAFE_INTEGER] The lower bound of the allowed return values (inclusive). + * @param {Number} [max = MAX_SAFE_INTEGER] The upper bound of the allowed return values (inclusive). + * @return {Number} A random integer on [min, max] + */ +export const int32 = (gen, min = MIN_SAFE_INTEGER, max = MAX_SAFE_INTEGER) => min + ((gen.next() >>> 0) % (max + 1 - min)) + +/** + * Generates a random real on [0, 1) with 32 bit resolution. + * + * @param {PRNG} gen A random number generator. + * @return {Number} A random real number on [0, 1). + */ +export const real32 = gen => (gen.next() >>> 0) / binary.BITS32 + +/** + * Generates a random real on [0, 1) with 53 bit resolution. + * + * @param {PRNG} gen A random number generator. + * @return {Number} A random real number on [0, 1). + */ +export const real53 = gen => (((gen.next() >>> 5) * binary.BIT26) + (gen.next() >>> 6)) / MAX_SAFE_INTEGER + +/** + * Generates a random character from char code 32 - 126. I.e. Characters, Numbers, special characters, and Space: + * + * (Space)!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[/]^_`abcdefghijklmnopqrstuvwxyz{|}~ + */ +export const char = gen => fromCharCode(int32(gen, 32, 126)) + +/** + * @param {PRNG} gen + * @return {string} A single letter (a-z) + */ +export const letter = gen => fromCharCode(int32(gen, 97, 122)) + +/** + * @param {PRNG} gen + * @return {string} A random word without spaces consisting of letters (a-z) + */ +export const word = gen => { + const len = int32(gen, 0, 20) + let str = '' + for (let i = 0; i < len; i++) { + str += letter(gen) + } + return str +} + +/** + * TODO: this function produces invalid runes. Does not cover all of utf16!! + */ +export const utf16Rune = gen => { + const codepoint = int32(gen, 0, 256) + return fromCodePoint(codepoint) +} + +/** + * @param {PRNG} gen + * @param {number} [maxlen = 20] + */ +export const utf16String = (gen, maxlen = 20) => { + const len = int32(gen, 0, maxlen) + let str = '' + for (let i = 0; i < len; i++) { + str += utf16Rune(gen) + } + return str +} + +/** + * Returns one element of a given array. + * + * @param {PRNG} gen A random number generator. + * @param {Array} array Non empty Array of possible values. + * @return {T} One of the values of the supplied Array. + * @template T + */ +export const oneOf = (gen, array) => array[int32(gen, 0, array.length - 1)] diff --git a/lib/random/random.test.js b/lib/random/random.test.js new file mode 100644 index 00000000..69782150 --- /dev/null +++ b/lib/random/random.test.js @@ -0,0 +1,110 @@ +/** + *TODO: enable tests +import * as rt from '../rich-text/formatters.mjs' +import { test } from '../test/test.mjs' +import Xoroshiro128plus from './PRNG/Xoroshiro128plus.mjs' +import Xorshift32 from './PRNG/Xorshift32.mjs' +import MT19937 from './PRNG/Mt19937.mjs' +import { generateBool, generateInt, generateInt32, generateReal, generateChar } from './random.mjs' +import { MAX_SAFE_INTEGER } from '../number/constants.mjs' +import { BIT32 } from '../binary/constants.mjs' + +function init (Gen) { + return { + gen: new Gen(1234) + } +} + +const PRNGs = [ + { name: 'Xoroshiro128plus', Gen: Xoroshiro128plus }, + { name: 'Xorshift32', Gen: Xorshift32 }, + { name: 'MT19937', Gen: MT19937 } +] + +const ITERATONS = 1000000 + +for (const PRNG of PRNGs) { + const prefix = rt.orange`${PRNG.name}:` + + test(rt.plain`${prefix} generateBool`, function generateBoolTest (t) { + const { gen } = init(PRNG.Gen) + let head = 0 + let tail = 0 + let b + let i + + for (i = 0; i < ITERATONS; i++) { + b = generateBool(gen) + if (b) { + head++ + } else { + tail++ + } + } + t.log(`Generated ${head} heads and ${tail} tails.`) + t.assert(tail >= Math.floor(ITERATONS * 0.49), 'Generated enough tails.') + t.assert(head >= Math.floor(ITERATONS * 0.49), 'Generated enough heads.') + }) + + test(rt.plain`${prefix} generateInt integers average correctly`, function averageIntTest (t) { + const { gen } = init(PRNG.Gen) + let count = 0 + let i + + for (i = 0; i < ITERATONS; i++) { + count += generateInt(gen, 0, 100) + } + const average = count / ITERATONS + const expectedAverage = 100 / 2 + t.log(`Average is: ${average}. Expected average is ${expectedAverage}.`) + t.assert(Math.abs(average - expectedAverage) <= 1, 'Expected average is at most 1 off.') + }) + + test(rt.plain`${prefix} generateInt32 generates integer with 32 bits`, function generateLargeIntegers (t) { + const { gen } = init(PRNG.Gen) + let num = 0 + let i + let newNum + for (i = 0; i < ITERATONS; i++) { + newNum = generateInt32(gen, 0, MAX_SAFE_INTEGER) + if (newNum > num) { + num = newNum + } + } + t.log(`Largest number generated is ${num} (0b${num.toString(2)})`) + t.assert(num > (BIT32 >>> 0), 'Largest number is 32 bits long.') + }) + + test(rt.plain`${prefix} generateReal has 53 bit resolution`, function real53bitResolution (t) { + const { gen } = init(PRNG.Gen) + let num = 0 + let i + let newNum + for (i = 0; i < ITERATONS; i++) { + newNum = generateReal(gen) * MAX_SAFE_INTEGER + if (newNum > num) { + num = newNum + } + } + t.log(`Largest number generated is ${num}.`) + t.assert((MAX_SAFE_INTEGER - num) / MAX_SAFE_INTEGER < 0.01, 'Largest number is close to MAX_SAFE_INTEGER (at most 1% off).') + }) + + test(rt.plain`${prefix} generateChar generates all described characters`, function real53bitResolution (t) { + const { gen } = init(PRNG.Gen) + const charSet = new Set() + const chars = ' !"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[/]^_`abcdefghijklmnopqrstuvwxyz{|}~"' + let i + let char + for (i = chars.length - 1; i >= 0; i--) { + charSet.add(chars[i]) + } + for (i = 0; i < ITERATONS; i++) { + char = generateChar(gen) + charSet.delete(char) + } + t.log(`Charactes missing: ${charSet.size} - generating all of "${chars}"`) + t.assert(charSet.size === 0, 'Generated all documented characters.') + }) +} +*/ diff --git a/lib/string.js b/lib/string.js new file mode 100644 index 00000000..756829bb --- /dev/null +++ b/lib/string.js @@ -0,0 +1,2 @@ +export const fromCharCode = String.fromCharCode +export const fromCodePoint = String.fromCodePoint diff --git a/lib/time.js b/lib/time.js new file mode 100644 index 00000000..adcbb3b7 --- /dev/null +++ b/lib/time.js @@ -0,0 +1,3 @@ + +export const getDate = () => new Date() +export const getUnixTime = () => getDate().getTime() diff --git a/package-lock.json b/package-lock.json index f5b8865d..1872bda9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,19 +24,36 @@ "optional": true }, "accepts": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz", - "integrity": "sha1-w8p0NJOGSMPg2cHjKN1otiLChMo=", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", + "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", "dev": true, "requires": { - "mime-types": "~2.1.11", + "mime-types": "~2.1.18", "negotiator": "0.6.1" + }, + "dependencies": { + "mime-db": { + "version": "1.37.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", + "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==", + "dev": true + }, + "mime-types": { + "version": "2.1.21", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", + "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", + "dev": true, + "requires": { + "mime-db": "~1.37.0" + } + } } }, "acorn": { - "version": "4.0.13", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", - "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=", + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", + "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", "dev": true }, "acorn-globals": { @@ -227,11 +244,14 @@ "dev": true }, "asn1": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", "dev": true, - "optional": true + "optional": true, + "requires": { + "safer-buffer": "~2.1.0" + } }, "assert-plus": { "version": "1.0.0", @@ -266,33 +286,222 @@ "optional": true }, "aws4": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", - "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", "dev": true, "optional": true }, "babel-cli": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-cli/-/babel-cli-6.24.1.tgz", - "integrity": "sha1-IHzXBbumFImy6kG1MSNBz2rKIoM=", + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-cli/-/babel-cli-6.26.0.tgz", + "integrity": "sha1-UCq1SHTX24itALiHoGODzgPQAvE=", "dev": true, "requires": { - "babel-core": "^6.24.1", - "babel-polyfill": "^6.23.0", - "babel-register": "^6.24.1", - "babel-runtime": "^6.22.0", + "babel-core": "^6.26.0", + "babel-polyfill": "^6.26.0", + "babel-register": "^6.26.0", + "babel-runtime": "^6.26.0", "chokidar": "^1.6.1", - "commander": "^2.8.1", - "convert-source-map": "^1.1.0", + "commander": "^2.11.0", + "convert-source-map": "^1.5.0", "fs-readdir-recursive": "^1.0.0", - "glob": "^7.0.0", - "lodash": "^4.2.0", - "output-file-sync": "^1.1.0", - "path-is-absolute": "^1.0.0", + "glob": "^7.1.2", + "lodash": "^4.17.4", + "output-file-sync": "^1.1.2", + "path-is-absolute": "^1.0.1", "slash": "^1.0.0", - "source-map": "^0.5.0", - "v8flags": "^2.0.10" + "source-map": "^0.5.6", + "v8flags": "^2.1.1" + }, + "dependencies": { + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + } + }, + "babel-core": { + "version": "6.26.3", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", + "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", + "dev": true, + "requires": { + "babel-code-frame": "^6.26.0", + "babel-generator": "^6.26.0", + "babel-helpers": "^6.24.1", + "babel-messages": "^6.23.0", + "babel-register": "^6.26.0", + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "convert-source-map": "^1.5.1", + "debug": "^2.6.9", + "json5": "^0.5.1", + "lodash": "^4.17.4", + "minimatch": "^3.0.4", + "path-is-absolute": "^1.0.1", + "private": "^0.1.8", + "slash": "^1.0.0", + "source-map": "^0.5.7" + }, + "dependencies": { + "convert-source-map": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", + "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "babel-generator": { + "version": "6.26.1", + "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", + "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", + "dev": true, + "requires": { + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "detect-indent": "^4.0.0", + "jsesc": "^1.3.0", + "lodash": "^4.17.4", + "source-map": "^0.5.7", + "trim-right": "^1.0.1" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } + } + }, + "babel-register": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", + "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", + "dev": true, + "requires": { + "babel-core": "^6.26.0", + "babel-runtime": "^6.26.0", + "core-js": "^2.5.0", + "home-or-tmp": "^2.0.0", + "lodash": "^4.17.4", + "mkdirp": "^0.5.1", + "source-map-support": "^0.4.15" + }, + "dependencies": { + "core-js": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz", + "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==", + "dev": true + } + } + }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "babel-template": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", + "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "lodash": "^4.17.4" + } + }, + "babel-traverse": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", + "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", + "dev": true, + "requires": { + "babel-code-frame": "^6.26.0", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "debug": "^2.6.8", + "globals": "^9.18.0", + "invariant": "^2.2.2", + "lodash": "^4.17.4" + } + }, + "babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + } + }, + "babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "dev": true + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", + "dev": true + }, + "private": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", + "dev": true + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true + } } }, "babel-code-frame": { @@ -307,46 +516,144 @@ } }, "babel-core": { - "version": "6.25.0", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.25.0.tgz", - "integrity": "sha1-fdQrBGPHQunVKW3rPsZ6kyLa1yk=", + "version": "6.26.3", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", + "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", "dev": true, "requires": { - "babel-code-frame": "^6.22.0", - "babel-generator": "^6.25.0", + "babel-code-frame": "^6.26.0", + "babel-generator": "^6.26.0", "babel-helpers": "^6.24.1", "babel-messages": "^6.23.0", - "babel-register": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.25.0", - "babel-traverse": "^6.25.0", - "babel-types": "^6.25.0", - "babylon": "^6.17.2", - "convert-source-map": "^1.1.0", - "debug": "^2.1.1", - "json5": "^0.5.0", - "lodash": "^4.2.0", - "minimatch": "^3.0.2", - "path-is-absolute": "^1.0.0", - "private": "^0.1.6", + "babel-register": "^6.26.0", + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "convert-source-map": "^1.5.1", + "debug": "^2.6.9", + "json5": "^0.5.1", + "lodash": "^4.17.4", + "minimatch": "^3.0.4", + "path-is-absolute": "^1.0.1", + "private": "^0.1.8", "slash": "^1.0.0", - "source-map": "^0.5.0" + "source-map": "^0.5.7" + }, + "dependencies": { + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + } + }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + } + }, + "convert-source-map": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", + "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "private": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", + "dev": true + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } } }, "babel-generator": { - "version": "6.25.0", - "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.25.0.tgz", - "integrity": "sha1-M6GvcNXyiQrrRlpKd5PB32qeqfw=", + "version": "6.26.1", + "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", + "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", "dev": true, "requires": { "babel-messages": "^6.23.0", - "babel-runtime": "^6.22.0", - "babel-types": "^6.25.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", "detect-indent": "^4.0.0", "jsesc": "^1.3.0", - "lodash": "^4.2.0", - "source-map": "^0.5.0", + "lodash": "^4.17.4", + "source-map": "^0.5.7", "trim-right": "^1.0.1" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + } + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + } } }, "babel-helper-builder-binary-assignment-operator-visitor": { @@ -373,15 +680,45 @@ } }, "babel-helper-define-map": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.24.1.tgz", - "integrity": "sha1-epdH8ljYlH0y1RX2qhx70CIEoIA=", + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz", + "integrity": "sha1-pfVtq0GiX5fstJjH66ypgZ+Vvl8=", "dev": true, "requires": { "babel-helper-function-name": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1", - "lodash": "^4.2.0" + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "lodash": "^4.17.4" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + } + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true + } } }, "babel-helper-explode-assignable-expression": { @@ -439,14 +776,44 @@ } }, "babel-helper-regex": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helper-regex/-/babel-helper-regex-6.24.1.tgz", - "integrity": "sha1-024i+rEAjXnYhkjjIRaGgShFbOg=", + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz", + "integrity": "sha1-MlxZ+QL4LyS3T6zu0DY5VPZJXnI=", "dev": true, "requires": { - "babel-runtime": "^6.22.0", - "babel-types": "^6.24.1", - "lodash": "^4.2.0" + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "lodash": "^4.17.4" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + } + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true + } } }, "babel-helper-remap-async-to-generator": { @@ -561,16 +928,46 @@ } }, "babel-plugin-transform-es2015-block-scoping": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.24.1.tgz", - "integrity": "sha1-dsKV3DpHQbFmWt/TFnIV3P8ypXY=", + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz", + "integrity": "sha1-1w9SmcEwjQXBL0Y4E7CgnnOxiV8=", "dev": true, "requires": { - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-traverse": "^6.24.1", - "babel-types": "^6.24.1", - "lodash": "^4.2.0" + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "lodash": "^4.17.4" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + } + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true + } } }, "babel-plugin-transform-es2015-classes": { @@ -660,15 +1057,45 @@ } }, "babel-plugin-transform-es2015-modules-commonjs": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.24.1.tgz", - "integrity": "sha1-0+MQtA72ZKNmIiAAl8bUQCmPK/4=", + "version": "6.26.2", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz", + "integrity": "sha512-CV9ROOHEdrjcwhIaJNBGMBCodN+1cfkwtM1SbUHmvyy35KGT7fohbpOxkE2uLz1o6odKK2Ck/tz47z+VqQfi9Q==", "dev": true, "requires": { "babel-plugin-transform-strict-mode": "^6.24.1", - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1", - "babel-types": "^6.24.1" + "babel-runtime": "^6.26.0", + "babel-template": "^6.26.0", + "babel-types": "^6.26.0" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + } + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true + } } }, "babel-plugin-transform-es2015-modules-systemjs": { @@ -788,12 +1215,12 @@ } }, "babel-plugin-transform-regenerator": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.24.1.tgz", - "integrity": "sha1-uNowWtQ8PJm0hI5P5AN7dw0jxBg=", + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz", + "integrity": "sha1-4HA2lvveJ/Cj78rPi03KL3s6jy8=", "dev": true, "requires": { - "regenerator-transform": "0.9.11" + "regenerator-transform": "^0.10.0" } }, "babel-plugin-transform-runtime": { @@ -816,14 +1243,40 @@ } }, "babel-polyfill": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.23.0.tgz", - "integrity": "sha1-g2TKYt+Or7gwSZ9pkXdGbDsDSZ0=", + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz", + "integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=", "dev": true, "requires": { - "babel-runtime": "^6.22.0", - "core-js": "^2.4.0", - "regenerator-runtime": "^0.10.0" + "babel-runtime": "^6.26.0", + "core-js": "^2.5.0", + "regenerator-runtime": "^0.10.5" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + }, + "dependencies": { + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true + } + } + }, + "core-js": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz", + "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==", + "dev": true + } } }, "babel-preset-es2015": { @@ -889,18 +1342,42 @@ } }, "babel-register": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.24.1.tgz", - "integrity": "sha1-fhDhOi9xBlvfrVoXh7pFvKbe118=", + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", + "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", "dev": true, "requires": { - "babel-core": "^6.24.1", - "babel-runtime": "^6.22.0", - "core-js": "^2.4.0", + "babel-core": "^6.26.0", + "babel-runtime": "^6.26.0", + "core-js": "^2.5.0", "home-or-tmp": "^2.0.0", - "lodash": "^4.2.0", + "lodash": "^4.17.4", "mkdirp": "^0.5.1", - "source-map-support": "^0.4.2" + "source-map-support": "^0.4.15" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "core-js": { + "version": "2.5.7", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz", + "integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw==", + "dev": true + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true + } } }, "babel-runtime": { @@ -914,33 +1391,116 @@ } }, "babel-template": { - "version": "6.25.0", - "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.25.0.tgz", - "integrity": "sha1-ZlJBFmt8KqTGGdceGSlpVSsQwHE=", + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", + "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", "dev": true, "requires": { - "babel-runtime": "^6.22.0", - "babel-traverse": "^6.25.0", - "babel-types": "^6.25.0", - "babylon": "^6.17.2", - "lodash": "^4.2.0" + "babel-runtime": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "lodash": "^4.17.4" + }, + "dependencies": { + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + } + }, + "babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "dev": true + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true + } } }, "babel-traverse": { - "version": "6.25.0", - "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.25.0.tgz", - "integrity": "sha1-IldJfi/NGbie3BPEyROB+VEklvE=", + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", + "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", "dev": true, "requires": { - "babel-code-frame": "^6.22.0", + "babel-code-frame": "^6.26.0", "babel-messages": "^6.23.0", - "babel-runtime": "^6.22.0", - "babel-types": "^6.25.0", - "babylon": "^6.17.2", - "debug": "^2.2.0", - "globals": "^9.0.0", - "invariant": "^2.2.0", - "lodash": "^4.2.0" + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "debug": "^2.6.8", + "globals": "^9.18.0", + "invariant": "^2.2.2", + "lodash": "^4.17.4" + }, + "dependencies": { + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + } + }, + "babel-runtime": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "babel-types": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + } + }, + "babylon": { + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", + "dev": true + }, + "regenerator-runtime": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", + "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", + "dev": true + } } }, "babel-types": { @@ -956,9 +1516,9 @@ } }, "babylon": { - "version": "6.17.4", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.17.4.tgz", - "integrity": "sha512-kChlV+0SXkjE0vUn9OZ7pBMWRFd8uq3mZe8x1K6jhuNcAFAtEnjchFAqB+dYEXKyd+JpT6eppRR78QAr5gTsUw==", + "version": "6.18.0", + "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", + "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", "dev": true }, "balanced-match": { @@ -968,10 +1528,21 @@ "dev": true }, "basic-auth": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.1.0.tgz", - "integrity": "sha1-RSIe5Cn37h5QNb4/UVM/HN/SmIQ=", - "dev": true + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dev": true, + "requires": { + "safe-buffer": "5.1.2" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } + } }, "batch": { "version": "0.6.1", @@ -980,9 +1551,9 @@ "dev": true }, "bcrypt-pbkdf": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", - "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", "dev": true, "optional": true, "requires": { @@ -1007,16 +1578,6 @@ "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=", "dev": true }, - "boom": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", - "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", - "dev": true, - "optional": true, - "requires": { - "hoek": "4.x.x" - } - }, "brace-expansion": { "version": "1.1.8", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", @@ -1038,23 +1599,6 @@ "repeat-element": "^1.1.2" } }, - "browser-resolve": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.2.tgz", - "integrity": "sha1-j/CbCixCFxihBRwmCzLkj0QpOM4=", - "dev": true, - "requires": { - "resolve": "1.1.7" - }, - "dependencies": { - "resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", - "dev": true - } - } - }, "buffer-from": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.0.0.tgz", @@ -1144,12 +1688,6 @@ "supports-color": "^2.0.0" } }, - "chance": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/chance/-/chance-1.0.10.tgz", - "integrity": "sha1-A1ALBK2U53jdKJGwnsc6ath7GZY=", - "dev": true - }, "chardet": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", @@ -1230,9 +1768,9 @@ } }, "clone": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz", - "integrity": "sha1-0hfR6WERjjrJpLi7oyhVU79kfNs=", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", "dev": true }, "co": { @@ -1241,12 +1779,6 @@ "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", "dev": true }, - "codemirror": { - "version": "5.37.0", - "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.37.0.tgz", - "integrity": "sha512-dQaayDJCLU4UJcwg2RM44oFrs0dMNndTp6qxQJF6XI71l1xN3RB4IqiKES0b0rccbARbrD/UBB4t8DNknfaOTw==", - "dev": true - }, "color-convert": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", @@ -1269,22 +1801,19 @@ "dev": true }, "combined-stream": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", - "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz", + "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==", "dev": true, "requires": { "delayed-stream": "~1.0.0" } }, "commander": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.10.0.tgz", - "integrity": "sha512-q/r9trjmuikWDRJNTBHAVnWhuU6w+z80KgBq7j9YDclik5E7X4xi0KnlZBNFA1zOQ+SH/vHMWd2mC9QTOz7GpA==", - "dev": true, - "requires": { - "graceful-readlink": ">= 1.0.0" - } + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", + "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==", + "dev": true }, "concat-map": { "version": "0.0.1", @@ -1305,51 +1834,50 @@ } }, "concurrently": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-3.5.0.tgz", - "integrity": "sha1-jPG3cHppFqeKT/W3e7BN7FSzebI=", + "version": "3.6.1", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-3.6.1.tgz", + "integrity": "sha512-/+ugz+gwFSEfTGUxn0KHkY+19XPRTXR8+7oUK/HxgiN1n7FjeJmkrbSiXAJfyQ0zORgJYPaenmymwon51YXH9Q==", "dev": true, "requires": { - "chalk": "0.5.1", + "chalk": "^2.4.1", "commander": "2.6.0", "date-fns": "^1.23.0", "lodash": "^4.5.1", + "read-pkg": "^3.0.0", "rx": "2.3.24", "spawn-command": "^0.0.2-1", "supports-color": "^3.2.3", "tree-kill": "^1.1.0" }, "dependencies": { - "ansi-regex": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-0.2.1.tgz", - "integrity": "sha1-DY6UaWej2BQ/k+JOKYUl/BsiNfk=", - "dev": true - }, "ansi-styles": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-1.1.0.tgz", - "integrity": "sha1-6uy/Zs1waIJ2Cy9GkVgrj1XXp94=", - "dev": true - }, - "chalk": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-0.5.1.tgz", - "integrity": "sha1-Zjs6ZItotV0EaQ1JFnqoN4WPIXQ=", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "ansi-styles": "^1.1.0", - "escape-string-regexp": "^1.0.0", - "has-ansi": "^0.1.0", - "strip-ansi": "^0.3.0", - "supports-color": "^0.2.0" + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" }, "dependencies": { "supports-color": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-0.2.0.tgz", - "integrity": "sha1-2S3iaU6z9nMjlz1649i1W0wiGQo=", - "dev": true + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } } } }, @@ -1359,22 +1887,52 @@ "integrity": "sha1-nfflL7Kgyw+4kFjugMMQQiXzfh0=", "dev": true }, - "has-ansi": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-0.1.0.tgz", - "integrity": "sha1-hPJlqujA5qiKEtcCKJS3VoiUxi4=", + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", "dev": true, "requires": { - "ansi-regex": "^0.2.0" + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" } }, - "strip-ansi": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-0.3.0.tgz", - "integrity": "sha1-JfSOoiynkYfzF0pNuHWTR7sSYiA=", + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", "dev": true, "requires": { - "ansi-regex": "^0.2.1" + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "^3.0.0" + } + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" } }, "supports-color": { @@ -1384,6 +1942,14 @@ "dev": true, "requires": { "has-flag": "^1.0.0" + }, + "dependencies": { + "has-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", + "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "dev": true + } } } } @@ -1452,28 +2018,6 @@ "which": "^1.2.9" } }, - "cryptiles": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", - "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", - "dev": true, - "optional": true, - "requires": { - "boom": "5.x.x" - }, - "dependencies": { - "boom": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", - "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", - "dev": true, - "optional": true, - "requires": { - "hoek": "4.x.x" - } - } - } - }, "css-select": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", @@ -1539,15 +2083,15 @@ } }, "date-fns": { - "version": "1.28.5", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.28.5.tgz", - "integrity": "sha1-JXz8RdMi30XvVlhmWWfuhBzXP68=", + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.29.0.tgz", + "integrity": "sha512-lbTXWZ6M20cWH8N9S6afb0SBm6tMk+uUg6z3MqHPKE9atmsY3kJkTm8vKe93izJ2B2+q5MV990sM2CHgtAZaOw==", "dev": true }, "debug": { - "version": "2.6.8", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", - "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "requires": { "ms": "2.0.0" @@ -1623,9 +2167,9 @@ "dev": true }, "depd": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.0.tgz", - "integrity": "sha1-4b2Cxqq2ztlluXuIsX7T5SjKGMM=", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", "dev": true }, "destroy": { @@ -1702,13 +2246,14 @@ "dev": true }, "ecc-jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", - "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", "dev": true, "optional": true, "requires": { - "jsbn": "~0.1.0" + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" } }, "ee-first": { @@ -1816,22 +2361,22 @@ } }, "esdoc": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/esdoc/-/esdoc-1.0.4.tgz", - "integrity": "sha512-Hy5sg0Lec4EDHVem3gFqNi+o6ZptivmaiHYacZhmn3hzLnHSMg2C1L0XTsDIcb4Cxd9aUnWdLAu6a6ghH/LLug==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/esdoc/-/esdoc-1.1.0.tgz", + "integrity": "sha512-vsUcp52XJkOWg9m1vDYplGZN2iDzvmjDL5M/Mp8qkoDG3p2s0yIQCIjKR5wfPBaM3eV14a6zhQNYiNTCVzPnxA==", "dev": true, "requires": { - "babel-generator": "6.26.0", + "babel-generator": "6.26.1", "babel-traverse": "6.26.0", "babylon": "6.18.0", - "cheerio": "0.22.0", - "color-logger": "0.0.3", + "cheerio": "1.0.0-rc.2", + "color-logger": "0.0.6", "escape-html": "1.0.3", - "fs-extra": "1.0.0", + "fs-extra": "5.0.0", "ice-cap": "0.0.4", - "marked": "0.3.6", + "marked": "0.3.19", "minimist": "1.2.0", - "taffydb": "2.7.2" + "taffydb": "2.7.3" }, "dependencies": { "babel-code-frame": { @@ -1846,9 +2391,9 @@ } }, "babel-generator": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.0.tgz", - "integrity": "sha1-rBriAHC3n248odMmlhMFN3TyDcU=", + "version": "6.26.1", + "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", + "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", "dev": true, "requires": { "babel-messages": "^6.23.0", @@ -1857,7 +2402,7 @@ "detect-indent": "^4.0.0", "jsesc": "^1.3.0", "lodash": "^4.17.4", - "source-map": "^0.5.6", + "source-map": "^0.5.7", "trim-right": "^1.0.1" } }, @@ -1906,17 +2451,84 @@ "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", "dev": true }, + "cheerio": { + "version": "1.0.0-rc.2", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.2.tgz", + "integrity": "sha1-S59TqBsn5NXawxwP/Qz6A8xoMNs=", + "dev": true, + "requires": { + "css-select": "~1.2.0", + "dom-serializer": "~0.1.0", + "entities": "~1.1.1", + "htmlparser2": "^3.9.1", + "lodash": "^4.15.0", + "parse5": "^3.0.1" + } + }, + "color-logger": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/color-logger/-/color-logger-0.0.6.tgz", + "integrity": "sha1-5WJF7ymCJlcRDHy3WpzXhstp7Rs=", + "dev": true + }, + "fs-extra": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz", + "integrity": "sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "marked": { + "version": "0.3.19", + "resolved": "http://registry.npmjs.org/marked/-/marked-0.3.19.tgz", + "integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==", + "dev": true + }, "minimist": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, + "parse5": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-3.0.3.tgz", + "integrity": "sha512-rgO9Zg5LLLkfJF9E6CCmXlSE4UVceloys8JrFqCcHloC3usd/kJCyPDwH2SOlzix2j3xaP9sUX3e8+kvkuleAA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "regenerator-runtime": { "version": "0.11.1", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "taffydb": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/taffydb/-/taffydb-2.7.3.tgz", + "integrity": "sha1-KtNxaWKUmPylvIQkMJbTzeDsOjQ=", + "dev": true } } }, @@ -1969,9 +2581,9 @@ "dev": true }, "esdoc-publish-html-plugin": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/esdoc-publish-html-plugin/-/esdoc-publish-html-plugin-1.1.0.tgz", - "integrity": "sha1-CT+DN6yhaQIlcss4f/zD9HCwJRM=", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/esdoc-publish-html-plugin/-/esdoc-publish-html-plugin-1.1.2.tgz", + "integrity": "sha512-hG1fZmTcEp3P/Hv/qKiMdG1qSp8MjnVZMMkxL5P5ry7I2sX0HQ4P9lt2lms+90Lt0r340HHhSuVx107UL7dphg==", "dev": true, "requires": { "babel-generator": "6.11.4", @@ -1979,13 +2591,13 @@ "escape-html": "1.0.3", "fs-extra": "1.0.0", "ice-cap": "0.0.4", - "marked": "0.3.6", + "marked": "0.3.19", "taffydb": "2.7.2" }, "dependencies": { "babel-generator": { "version": "6.11.4", - "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.11.4.tgz", + "resolved": "http://registry.npmjs.org/babel-generator/-/babel-generator-6.11.4.tgz", "integrity": "sha1-FPaTOrsgxiZm0n47e59bncBxKpo=", "dev": true, "requires": { @@ -2016,7 +2628,7 @@ }, "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, @@ -2496,12 +3108,6 @@ "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", "dev": true }, - "ez-async": { - "version": "1.0.0-alpha.1", - "resolved": "https://registry.npmjs.org/ez-async/-/ez-async-1.0.0-alpha.1.tgz", - "integrity": "sha1-ysNCuPqJAm7+c6Jg/p9rgE9J5H8=", - "dev": true - }, "fast-deep-equal": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", @@ -2584,14 +3190,14 @@ "dev": true }, "fill-range": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz", - "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=", + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", + "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", "dev": true, "requires": { "is-number": "^2.1.0", "isobject": "^2.0.0", - "randomatic": "^1.1.3", + "randomatic": "^3.0.0", "repeat-element": "^1.1.2", "repeat-string": "^1.5.2" } @@ -2683,14 +3289,14 @@ "optional": true }, "form-data": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", - "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", "dev": true, "optional": true, "requires": { "asynckit": "^0.4.0", - "combined-stream": "1.0.6", + "combined-stream": "^1.0.6", "mime-types": "^2.1.12" } }, @@ -2718,9 +3324,9 @@ } }, "fs-readdir-recursive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.0.0.tgz", - "integrity": "sha1-jNF0XItPiinIyuw5JHaSG6GV9WA=", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", + "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", "dev": true }, "fs.realpath": { @@ -2730,39 +3336,29 @@ "dev": true }, "fsevents": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.3.tgz", - "integrity": "sha512-WIr7iDkdmdbxu/Gh6eKEZJL6KPE74/5MEsf2whTOFNxbIoIixogroLdKYqB6FDav4Wavh/lZdzzd3b2KxIXC5Q==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz", + "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==", "dev": true, "optional": true, "requires": { - "nan": "^2.3.0", - "node-pre-gyp": "^0.6.39" + "nan": "^2.9.2", + "node-pre-gyp": "^0.10.0" }, "dependencies": { "abbrev": { - "version": "1.1.0", + "version": "1.1.1", "bundled": true, "dev": true, "optional": true }, - "ajv": { - "version": "4.11.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "co": "^4.6.0", - "json-stable-stringify": "^1.0.1" - } - }, "ansi-regex": { "version": "2.1.1", "bundled": true, "dev": true }, "aproba": { - "version": "1.1.1", + "version": "1.2.0", "bundled": true, "dev": true, "optional": true @@ -2777,90 +3373,22 @@ "readable-stream": "^2.0.6" } }, - "asn1": { - "version": "0.2.3", - "bundled": true, - "dev": true, - "optional": true - }, - "assert-plus": { - "version": "0.2.0", - "bundled": true, - "dev": true, - "optional": true - }, - "asynckit": { - "version": "0.4.0", - "bundled": true, - "dev": true, - "optional": true - }, - "aws-sign2": { - "version": "0.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "aws4": { - "version": "1.6.0", - "bundled": true, - "dev": true, - "optional": true - }, "balanced-match": { - "version": "0.4.2", + "version": "1.0.0", "bundled": true, "dev": true }, - "bcrypt-pbkdf": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "tweetnacl": "^0.14.3" - } - }, - "block-stream": { - "version": "0.0.9", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "inherits": "~2.0.0" - } - }, - "boom": { - "version": "2.10.1", - "bundled": true, - "dev": true, - "requires": { - "hoek": "2.x.x" - } - }, "brace-expansion": { - "version": "1.1.7", + "version": "1.1.11", "bundled": true, "dev": true, "requires": { - "balanced-match": "^0.4.1", + "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, - "buffer-shims": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "caseless": { - "version": "0.12.0", - "bundled": true, - "dev": true, - "optional": true - }, - "co": { - "version": "4.6.0", + "chownr": { + "version": "1.0.1", "bundled": true, "dev": true, "optional": true @@ -2868,17 +3396,7 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true - }, - "combined-stream": { - "version": "1.0.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "delayed-stream": "~1.0.0" - } + "dev": true }, "concat-map": { "version": "0.0.1", @@ -2888,8 +3406,7 @@ "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -2897,34 +3414,8 @@ "dev": true, "optional": true }, - "cryptiles": { - "version": "2.0.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "boom": "2.x.x" - } - }, - "dashdash": { - "version": "1.14.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "assert-plus": "^1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, "debug": { - "version": "2.6.8", + "version": "2.6.9", "bundled": true, "dev": true, "optional": true, @@ -2933,13 +3424,7 @@ } }, "deep-extend": { - "version": "0.4.2", - "bundled": true, - "dev": true, - "optional": true - }, - "delayed-stream": { - "version": "1.0.0", + "version": "0.5.1", "bundled": true, "dev": true, "optional": true @@ -2951,75 +3436,25 @@ "optional": true }, "detect-libc": { - "version": "1.0.2", + "version": "1.0.3", "bundled": true, "dev": true, "optional": true }, - "ecc-jsbn": { - "version": "0.1.1", + "fs-minipass": { + "version": "1.2.5", "bundled": true, "dev": true, "optional": true, "requires": { - "jsbn": "~0.1.0" - } - }, - "extend": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "extsprintf": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "forever-agent": { - "version": "0.6.1", - "bundled": true, - "dev": true, - "optional": true - }, - "form-data": { - "version": "2.1.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.5", - "mime-types": "^2.1.12" + "minipass": "^2.2.1" } }, "fs.realpath": { "version": "1.0.0", "bundled": true, - "dev": true - }, - "fstream": { - "version": "1.0.11", - "bundled": true, "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "inherits": "~2.0.0", - "mkdirp": ">=0.5 0", - "rimraf": "2" - } - }, - "fstream-ignore": { - "version": "1.0.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "fstream": "^1.0.0", - "inherits": "2", - "minimatch": "^3.0.0" - } + "optional": true }, "gauge": { "version": "2.7.4", @@ -3037,27 +3472,11 @@ "wide-align": "^1.1.0" } }, - "getpass": { - "version": "0.1.7", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "assert-plus": "^1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, "glob": { "version": "7.1.2", "bundled": true, "dev": true, + "optional": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -3067,65 +3486,35 @@ "path-is-absolute": "^1.0.0" } }, - "graceful-fs": { - "version": "4.1.11", - "bundled": true, - "dev": true - }, - "har-schema": { - "version": "1.0.5", - "bundled": true, - "dev": true, - "optional": true - }, - "har-validator": { - "version": "4.2.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ajv": "^4.9.1", - "har-schema": "^1.0.5" - } - }, "has-unicode": { "version": "2.0.1", "bundled": true, "dev": true, "optional": true }, - "hawk": { - "version": "3.1.3", + "iconv-lite": { + "version": "0.4.21", "bundled": true, "dev": true, "optional": true, "requires": { - "boom": "2.x.x", - "cryptiles": "2.x.x", - "hoek": "2.x.x", - "sntp": "1.x.x" + "safer-buffer": "^2.1.0" } }, - "hoek": { - "version": "2.16.3", - "bundled": true, - "dev": true - }, - "http-signature": { - "version": "1.1.1", + "ignore-walk": { + "version": "3.0.1", "bundled": true, "dev": true, "optional": true, "requires": { - "assert-plus": "^0.2.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" + "minimatch": "^3.0.4" } }, "inflight": { "version": "1.0.6", "bundled": true, "dev": true, + "optional": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -3137,7 +3526,7 @@ "dev": true }, "ini": { - "version": "1.3.4", + "version": "1.3.5", "bundled": true, "dev": true, "optional": true @@ -3146,106 +3535,16 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } }, - "is-typedarray": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, "isarray": { "version": "1.0.0", "bundled": true, "dev": true, "optional": true }, - "isstream": { - "version": "0.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "jodid25519": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "jsbn": "~0.1.0" - } - }, - "jsbn": { - "version": "0.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "json-schema": { - "version": "0.2.3", - "bundled": true, - "dev": true, - "optional": true - }, - "json-stable-stringify": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "jsonify": "~0.0.0" - } - }, - "json-stringify-safe": { - "version": "5.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "jsonify": { - "version": "0.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "jsprim": { - "version": "1.4.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.0.2", - "json-schema": "0.2.3", - "verror": "1.3.6" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "mime-db": { - "version": "1.27.0", - "bundled": true, - "dev": true, - "optional": true - }, - "mime-types": { - "version": "2.1.15", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "mime-db": "~1.27.0" - } - }, "minimatch": { "version": "3.0.4", "bundled": true, @@ -3259,6 +3558,24 @@ "bundled": true, "dev": true }, + "minipass": { + "version": "2.2.4", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "^5.1.1", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, "mkdirp": { "version": "0.5.1", "bundled": true, @@ -3273,23 +3590,33 @@ "dev": true, "optional": true }, + "needle": { + "version": "2.2.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "^2.1.2", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + } + }, "node-pre-gyp": { - "version": "0.6.39", + "version": "0.10.0", "bundled": true, "dev": true, "optional": true, "requires": { "detect-libc": "^1.0.2", - "hawk": "3.1.3", "mkdirp": "^0.5.1", + "needle": "^2.2.0", "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", "npmlog": "^4.0.2", "rc": "^1.1.7", - "request": "2.81.0", "rimraf": "^2.6.1", "semver": "^5.3.0", - "tar": "^2.2.1", - "tar-pack": "^3.4.0" + "tar": "^4" } }, "nopt": { @@ -3302,8 +3629,24 @@ "osenv": "^0.1.4" } }, + "npm-bundled": { + "version": "1.0.3", + "bundled": true, + "dev": true, + "optional": true + }, + "npm-packlist": { + "version": "1.1.10", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, "npmlog": { - "version": "4.1.0", + "version": "4.1.2", "bundled": true, "dev": true, "optional": true, @@ -3317,14 +3660,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true - }, - "oauth-sign": { - "version": "0.8.2", - "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -3353,7 +3689,7 @@ "optional": true }, "osenv": { - "version": "0.1.4", + "version": "0.1.5", "bundled": true, "dev": true, "optional": true, @@ -3365,39 +3701,22 @@ "path-is-absolute": { "version": "1.0.1", "bundled": true, - "dev": true - }, - "performance-now": { - "version": "0.2.0", - "bundled": true, "dev": true, "optional": true }, "process-nextick-args": { - "version": "1.0.7", - "bundled": true, - "dev": true, - "optional": true - }, - "punycode": { - "version": "1.4.1", - "bundled": true, - "dev": true, - "optional": true - }, - "qs": { - "version": "6.4.0", + "version": "2.0.0", "bundled": true, "dev": true, "optional": true }, "rc": { - "version": "1.2.1", + "version": "1.2.7", "bundled": true, "dev": true, "optional": true, "requires": { - "deep-extend": "~0.4.0", + "deep-extend": "^0.5.1", "ini": "~1.3.0", "minimist": "^1.2.0", "strip-json-comments": "~2.0.1" @@ -3412,66 +3731,48 @@ } }, "readable-stream": { - "version": "2.2.9", + "version": "2.3.6", "bundled": true, "dev": true, "optional": true, "requires": { - "buffer-shims": "~1.0.0", "core-util-is": "~1.0.0", - "inherits": "~2.0.1", + "inherits": "~2.0.3", "isarray": "~1.0.0", - "process-nextick-args": "~1.0.6", - "string_decoder": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, - "request": { - "version": "2.81.0", + "rimraf": { + "version": "2.6.2", "bundled": true, "dev": true, "optional": true, - "requires": { - "aws-sign2": "~0.6.0", - "aws4": "^1.2.1", - "caseless": "~0.12.0", - "combined-stream": "~1.0.5", - "extend": "~3.0.0", - "forever-agent": "~0.6.1", - "form-data": "~2.1.1", - "har-validator": "~4.2.1", - "hawk": "~3.1.3", - "http-signature": "~1.1.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.7", - "oauth-sign": "~0.8.1", - "performance-now": "^0.2.0", - "qs": "~6.4.0", - "safe-buffer": "^5.0.1", - "stringstream": "~0.0.4", - "tough-cookie": "~2.3.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.0.0" - } - }, - "rimraf": { - "version": "2.6.1", - "bundled": true, - "dev": true, "requires": { "glob": "^7.0.5" } }, "safe-buffer": { - "version": "5.0.1", + "version": "5.1.1", + "bundled": true, + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sax": { + "version": "1.2.4", "bundled": true, "dev": true, "optional": true }, "semver": { - "version": "5.3.0", + "version": "5.5.0", "bundled": true, "dev": true, "optional": true @@ -3488,45 +3789,10 @@ "dev": true, "optional": true }, - "sntp": { - "version": "1.0.9", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "hoek": "2.x.x" - } - }, - "sshpk": { - "version": "1.13.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jodid25519": "^1.0.0", - "jsbn": "~0.1.0", - "tweetnacl": "~0.14.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, "string-width": { "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -3534,20 +3800,14 @@ } }, "string_decoder": { - "version": "1.0.1", + "version": "1.1.1", "bundled": true, "dev": true, "optional": true, "requires": { - "safe-buffer": "^5.0.1" + "safe-buffer": "~5.1.0" } }, - "stringstream": { - "version": "0.0.5", - "bundled": true, - "dev": true, - "optional": true - }, "strip-ansi": { "version": "3.0.1", "bundled": true, @@ -3563,83 +3823,26 @@ "optional": true }, "tar": { - "version": "2.2.1", + "version": "4.4.1", "bundled": true, "dev": true, "optional": true, "requires": { - "block-stream": "*", - "fstream": "^1.0.2", - "inherits": "2" + "chownr": "^1.0.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.2.4", + "minizlib": "^1.1.0", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.1", + "yallist": "^3.0.2" } }, - "tar-pack": { - "version": "3.4.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "debug": "^2.2.0", - "fstream": "^1.0.10", - "fstream-ignore": "^1.0.5", - "once": "^1.3.3", - "readable-stream": "^2.1.4", - "rimraf": "^2.5.1", - "tar": "^2.2.1", - "uid-number": "^0.0.6" - } - }, - "tough-cookie": { - "version": "2.3.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "punycode": "^1.4.1" - } - }, - "tunnel-agent": { - "version": "0.6.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "bundled": true, - "dev": true, - "optional": true - }, - "uid-number": { - "version": "0.0.6", - "bundled": true, - "dev": true, - "optional": true - }, "util-deprecate": { "version": "1.0.2", "bundled": true, "dev": true, "optional": true }, - "uuid": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "verror": { - "version": "1.3.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "extsprintf": "1.0.2" - } - }, "wide-align": { "version": "1.1.2", "bundled": true, @@ -3653,6 +3856,11 @@ "version": "1.0.2", "bundled": true, "dev": true + }, + "yallist": { + "version": "3.0.2", + "bundled": true, + "dev": true } } }, @@ -3765,12 +3973,6 @@ "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", "dev": true }, - "graceful-readlink": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", - "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", - "dev": true - }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -3779,29 +3981,14 @@ "optional": true }, "har-validator": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", - "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.0.tgz", + "integrity": "sha512-+qnmNjI4OfH2ipQ9VQOw23bBd/ibtfbVdK2fYbY4acTDqKTW/YDp9McimZdDbG8iV9fZizUqQMD5xvriB146TA==", "dev": true, "optional": true, "requires": { - "ajv": "^5.1.0", + "ajv": "^5.3.0", "har-schema": "^2.0.0" - }, - "dependencies": { - "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", - "dev": true, - "optional": true, - "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" - } - } } }, "has": { @@ -3823,9 +4010,9 @@ } }, "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", "dev": true }, "has-glob": { @@ -3837,25 +4024,6 @@ "is-glob": "^2.0.1" } }, - "hawk": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", - "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", - "dev": true, - "optional": true, - "requires": { - "boom": "4.x.x", - "cryptiles": "3.x.x", - "hoek": "4.x.x", - "sntp": "2.x.x" - } - }, - "hoek": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", - "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==", - "dev": true - }, "home-or-tmp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", @@ -3908,15 +4076,29 @@ } }, "http-errors": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.1.tgz", - "integrity": "sha1-X4uO2YrKVFZWv1cplzh/kEpyIlc=", + "version": "1.6.3", + "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", "dev": true, "requires": { - "depd": "1.1.0", + "depd": "~1.1.2", "inherits": "2.0.3", - "setprototypeof": "1.0.3", - "statuses": ">= 1.3.1 < 2" + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "dependencies": { + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "dev": true + } } }, "http-signature": { @@ -4058,9 +4240,9 @@ "dev": true }, "ini": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz", - "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "dev": true }, "inquirer": { @@ -4420,8 +4602,7 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true, - "optional": true + "dev": true }, "jsdom": { "version": "7.2.2", @@ -4740,9 +4921,9 @@ } }, "lodash": { - "version": "4.17.4", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", - "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=", + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", "dev": true }, "lodash.assignin": { @@ -4853,12 +5034,12 @@ } }, "magic-string": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.19.1.tgz", - "integrity": "sha1-FNdoATyvLsj96hakmvgvw3fnUgE=", + "version": "0.22.5", + "resolved": "http://registry.npmjs.org/magic-string/-/magic-string-0.22.5.tgz", + "integrity": "sha512-oreip9rJZkzvA8Qzk9HFs8fZGF/u7H/gtrE8EN6RjKJ9kh2HlC+yQ2QezifqTZfGyiuAV0dRv5a+y/8gBb1m9w==", "dev": true, "requires": { - "vlq": "^0.2.1" + "vlq": "^0.2.2" } }, "map-obj": { @@ -4874,9 +5055,9 @@ "dev": true }, "marked": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.6.tgz", - "integrity": "sha1-ssbGGPzOzk74bE/Gy4p8v1rtqNc=", + "version": "0.3.19", + "resolved": "http://registry.npmjs.org/marked/-/marked-0.3.19.tgz", + "integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==", "dev": true }, "matched": { @@ -4896,6 +5077,12 @@ "resolve-dir": "^0.1.0" } }, + "math-random": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.1.tgz", + "integrity": "sha1-izqsWIuKZuSXXjzepn97sylgH6w=", + "dev": true + }, "meow": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", @@ -4947,13 +5134,15 @@ "version": "1.27.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz", "integrity": "sha1-gg9XIpa70g7CXtVeW13oaeVDbrE=", - "dev": true + "dev": true, + "optional": true }, "mime-types": { "version": "2.1.15", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz", "integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0=", "dev": true, + "optional": true, "requires": { "mime-db": "~1.27.0" } @@ -4989,14 +5178,14 @@ } }, "morgan": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.8.2.tgz", - "integrity": "sha1-eErHc05KRTqcbm6GgKkyknXItoc=", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.9.1.tgz", + "integrity": "sha512-HQStPIV4y3afTiCYVxirakhlCfGkI161c76kKFca7Fk1JusM//Qeo1ej2XaMniiNeaZklMVrh3vTtIzpzwbpmA==", "dev": true, "requires": { - "basic-auth": "~1.1.0", - "debug": "2.6.8", - "depd": "~1.1.0", + "basic-auth": "~2.0.0", + "debug": "2.6.9", + "depd": "~1.1.2", "on-finished": "~2.3.0", "on-headers": "~1.0.1" } @@ -5014,9 +5203,9 @@ "dev": true }, "nan": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.9.2.tgz", - "integrity": "sha512-ltW65co7f3PQWBDbqVvaU1WtFJUsNW7sWWm4HINhbMQIyVyzIeyZ8toX5TC5eeooE6piZoaEh4cZkueSKG3KYw==", + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.11.1.tgz", + "integrity": "sha512-iji6k87OSXa0CcrLl9z+ZiYSuR2o+c0bGuNmXdrhTQTakxytAFsC56SArGYoiHlJlFoHSnvmhpceZJaXkVuOtA==", "dev": true, "optional": true }, @@ -5086,9 +5275,9 @@ "optional": true }, "oauth-sign": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", "dev": true, "optional": true }, @@ -5217,9 +5406,9 @@ "dev": true }, "parchment": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/parchment/-/parchment-1.1.3.tgz", - "integrity": "sha512-41Y+F8FejGa+URCuDTlS1zzzlYCwoZFTWpVwiQWDL82LFAAlIIiAo3JGJSLMiSPDeM3avFUivdXN3iY/i4mBXg==", + "version": "1.1.4", + "resolved": "http://registry.npmjs.org/parchment/-/parchment-1.1.4.tgz", + "integrity": "sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg==", "dev": true }, "parse-glob": { @@ -5468,6 +5657,13 @@ "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", "dev": true }, + "psl": { + "version": "1.1.29", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz", + "integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ==", + "dev": true, + "optional": true + }, "punycode": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", @@ -5475,30 +5671,30 @@ "dev": true }, "qs": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", "dev": true, "optional": true }, "quill": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/quill/-/quill-1.3.5.tgz", - "integrity": "sha512-08P1DqKz4OZPJSlwSiZQxQ1a0F56+KEz6MttlpDNE42+WpjGuOyvsEQepScpdeyilHWrQwh61M5C1KelP8I8IA==", + "version": "1.3.6", + "resolved": "http://registry.npmjs.org/quill/-/quill-1.3.6.tgz", + "integrity": "sha512-K0mvhimWZN6s+9OQ249CH2IEPZ9JmkFuCQeHAOQax3EZ2nDJ3wfGh59mnlQaZV2i7u8eFarx6wAtvQKgShojug==", "dev": true, "requires": { "clone": "^2.1.1", "deep-equal": "^1.0.1", "eventemitter3": "^2.0.3", "extend": "^3.0.1", - "parchment": "^1.1.3", + "parchment": "^1.1.4", "quill-delta": "^3.6.2" } }, "quill-cursors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/quill-cursors/-/quill-cursors-1.0.2.tgz", - "integrity": "sha512-mWkhOA9TvdFklG1QwVAOS70hOSpiHiJ+eoIbSeEXI6no6wNQLavYo3eWYHXgvi6Z5/SjS0oSn+NLdpYuXgdA8Q==", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/quill-cursors/-/quill-cursors-1.0.3.tgz", + "integrity": "sha512-rS81BZIlHdWXucf00qo5NIp3QrpT/4JHwoHTRlR48fO+k6AzBJvVyGoNrWkO5yzdlwUwHSw3jPoLWfxyzLOoxw==", "dev": true, "requires": { "rangefix": "^0.2.5", @@ -5506,54 +5702,46 @@ } }, "quill-delta": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-3.6.2.tgz", - "integrity": "sha512-grWEQq9woEidPDogtDNxQKmy2LFf9zBC0EU/YTSw6TwKmMjtihTxdnPtPRfrqazB2MSJ7YdCWxmsJ7aQKRSEgg==", + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-3.6.3.tgz", + "integrity": "sha512-wdIGBlcX13tCHOXGMVnnTVFtGRLoP0imqxM696fIPwIf5ODIYUHIvHbZcyvGlZFiFhK5XzDC2lpjbxRhnM05Tg==", "dev": true, "requires": { "deep-equal": "^1.0.1", - "extend": "^3.0.1", + "extend": "^3.0.2", "fast-diff": "1.1.2" + }, + "dependencies": { + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true + } } }, "randomatic": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", - "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.0.tgz", + "integrity": "sha512-KnGPVE0lo2WoXxIZ7cPR8YBpiol4gsSuOwDSg410oHh80ZMp5EiypNqL2K4Z77vJn6lB5rap7IkAmcUlalcnBQ==", "dev": true, "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" + "is-number": "^4.0.0", + "kind-of": "^6.0.0", + "math-random": "^1.0.1" }, "dependencies": { "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true }, "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true } } }, @@ -5664,9 +5852,9 @@ "dev": true }, "regenerator-transform": { - "version": "0.9.11", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.9.11.tgz", - "integrity": "sha1-On0GdSDLe3F2dp61/4aGkb7+EoM=", + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.10.1.tgz", + "integrity": "sha512-PJepbvDbuK1xgIgnau7Y90cwaAmO/LCLMI2mPvaXq2heGMR3aWW5/BQvYrhJ8jgmQjXewXvBjzfqKcVOmhjZ6Q==", "dev": true, "requires": { "babel-runtime": "^6.18.0", @@ -5746,52 +5934,82 @@ } }, "request": { - "version": "2.83.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.83.0.tgz", - "integrity": "sha512-lR3gD69osqm6EYLk9wB/G1W/laGWjzH90t1vEa2xuxHD5KUrSzp9pUSfTm+YC5Nxt2T8nMPEvKlhbQayU7bgFw==", + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", "dev": true, "optional": true, "requires": { "aws-sign2": "~0.7.0", - "aws4": "^1.6.0", + "aws4": "^1.8.0", "caseless": "~0.12.0", - "combined-stream": "~1.0.5", - "extend": "~3.0.1", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", "forever-agent": "~0.6.1", - "form-data": "~2.3.1", - "har-validator": "~5.0.3", - "hawk": "~6.0.2", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", "http-signature": "~1.2.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.17", - "oauth-sign": "~0.8.2", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", "performance-now": "^2.1.0", - "qs": "~6.5.1", - "safe-buffer": "^5.1.1", - "stringstream": "~0.0.5", - "tough-cookie": "~2.3.3", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", "tunnel-agent": "^0.6.0", - "uuid": "^3.1.0" + "uuid": "^3.3.2" }, "dependencies": { + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "dev": true, + "optional": true + }, "mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", + "version": "1.37.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", + "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==", "dev": true, "optional": true }, "mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "version": "2.1.21", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", + "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", "dev": true, "optional": true, "requires": { - "mime-db": "~1.33.0" + "mime-db": "~1.37.0" } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true, + "optional": true + }, + "tough-cookie": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", + "dev": true, + "optional": true, + "requires": { + "psl": "^1.1.24", + "punycode": "^1.4.1" + } + }, + "uuid": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", + "dev": true, + "optional": true } } }, @@ -5887,78 +6105,109 @@ } }, "rollup-plugin-commonjs": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/rollup-plugin-commonjs/-/rollup-plugin-commonjs-8.0.2.tgz", - "integrity": "sha1-mLFYm/4ypsD2d5C2DAtJmXKv7Yk=", + "version": "8.4.1", + "resolved": "http://registry.npmjs.org/rollup-plugin-commonjs/-/rollup-plugin-commonjs-8.4.1.tgz", + "integrity": "sha512-mg+WuD+jlwoo8bJtW3Mvx7Tz6TsIdMsdhuvCnDMoyjh0oxsVgsjB/N0X984RJCWwc5IIiqNVJhXeeITcc73++A==", "dev": true, "requires": { - "acorn": "^4.0.1", - "estree-walker": "^0.3.0", - "magic-string": "^0.19.0", - "resolve": "^1.1.7", + "acorn": "^5.2.1", + "estree-walker": "^0.5.0", + "magic-string": "^0.22.4", + "resolve": "^1.4.0", "rollup-pluginutils": "^2.0.1" }, "dependencies": { "estree-walker": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.3.1.tgz", - "integrity": "sha1-5rGlHPcpJSTnI3wxLl/mZgwc4ao=", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.5.2.tgz", + "integrity": "sha512-XpCnW/AE10ws/kDAs37cngSkvgIR8aN3G0MS85m7dUpuK2EREo9VJ00uvw6Dg/hXEpfsE1I1TvJOJr+Z+TL+ig==", "dev": true }, - "rollup-pluginutils": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.0.1.tgz", - "integrity": "sha1-fslbNXP2VDpGpkYb2afFRFJdD8A=", + "resolve": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", + "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", "dev": true, "requires": { - "estree-walker": "^0.3.0", + "path-parse": "^1.0.5" + } + }, + "rollup-pluginutils": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.3.3.tgz", + "integrity": "sha512-2XZwja7b6P5q4RZ5FhyX1+f46xi1Z3qBKigLRZ6VTZjwbN0K1IFGMlwm06Uu0Emcre2Z63l77nq/pzn+KxIEoA==", + "dev": true, + "requires": { + "estree-walker": "^0.5.2", "micromatch": "^2.3.11" } } } }, "rollup-plugin-inject": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-inject/-/rollup-plugin-inject-2.0.0.tgz", - "integrity": "sha1-iTTZilsNstBe/O4qwGoXCkxwsEQ=", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-inject/-/rollup-plugin-inject-2.2.0.tgz", + "integrity": "sha512-Wow9g+qkKbkK96wjLif2HqWOiuR6ZqkZbHSNt5r1bVUDQG96yzmuxlSl1grPzlTG5BbATUE7nA5HhQVfBXEigQ==", "dev": true, "requires": { - "acorn": "^4.0.3", - "estree-walker": "^0.2.0", - "magic-string": "^0.16.0", - "rollup-pluginutils": "^1.2.0" + "estree-walker": "^0.5.0", + "magic-string": "^0.25.0", + "rollup-pluginutils": "^2.0.1" }, "dependencies": { + "estree-walker": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.5.2.tgz", + "integrity": "sha512-XpCnW/AE10ws/kDAs37cngSkvgIR8aN3G0MS85m7dUpuK2EREo9VJ00uvw6Dg/hXEpfsE1I1TvJOJr+Z+TL+ig==", + "dev": true + }, "magic-string": { - "version": "0.16.0", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.16.0.tgz", - "integrity": "sha1-lw67DacZMwEoX7GqZQ85vdgetFo=", + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.1.tgz", + "integrity": "sha512-sCuTz6pYom8Rlt4ISPFn6wuFodbKMIHUMv4Qko9P17dpxb7s52KJTmRuZZqHdGmLCK9AOcDare039nRIcfdkEg==", "dev": true, "requires": { - "vlq": "^0.2.1" + "sourcemap-codec": "^1.4.1" + } + }, + "rollup-pluginutils": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.3.3.tgz", + "integrity": "sha512-2XZwja7b6P5q4RZ5FhyX1+f46xi1Z3qBKigLRZ6VTZjwbN0K1IFGMlwm06Uu0Emcre2Z63l77nq/pzn+KxIEoA==", + "dev": true, + "requires": { + "estree-walker": "^0.5.2", + "micromatch": "^2.3.11" } } } }, "rollup-plugin-multi-entry": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/rollup-plugin-multi-entry/-/rollup-plugin-multi-entry-2.0.1.tgz", - "integrity": "sha1-Szrqjdxa/Jt/n/v7FEHATvOQcbQ=", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/rollup-plugin-multi-entry/-/rollup-plugin-multi-entry-2.0.2.tgz", + "integrity": "sha512-TY72fCVJvcEAQBpBzkXykoYQx2fz0B20EVtcbh0WZaYr5eBu3U1dRPzgMt6aO8MePWWOdcmgoBtG6PhmYJr4Ew==", "dev": true, "requires": { - "matched": "^0.4.3" + "matched": "^0.4.4" } }, "rollup-plugin-node-resolve": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-3.0.0.tgz", - "integrity": "sha1-i4l8TDAw1QASd7BRSyXSygloPuA=", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/rollup-plugin-node-resolve/-/rollup-plugin-node-resolve-3.4.0.tgz", + "integrity": "sha512-PJcd85dxfSBWih84ozRtBkB731OjXk0KnzN0oGp7WOWcarAFkVa71cV5hTJg2qpVsV2U8EUwrzHP3tvy9vS3qg==", "dev": true, "requires": { - "browser-resolve": "^1.11.0", - "builtin-modules": "^1.1.0", + "builtin-modules": "^2.0.0", "is-module": "^1.0.0", "resolve": "^1.1.6" + }, + "dependencies": { + "builtin-modules": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-2.0.0.tgz", + "integrity": "sha512-3U5kUA5VPsRUA3nofm/BXX7GVHKfxz0hOBAPxXrIvHzlDRkQVqEn6yi8QJegxl4LzOHLdvb7XF5dVawa/VVYBg==", + "dev": true + } } }, "rollup-plugin-uglify": { @@ -6060,18 +6309,41 @@ "dev": true }, "serve-index": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.0.tgz", - "integrity": "sha1-0rKA/FYNYW7oG0i/D6gqvtJIXOc=", + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", + "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", "dev": true, "requires": { - "accepts": "~1.3.3", + "accepts": "~1.3.4", "batch": "0.6.1", - "debug": "2.6.8", + "debug": "2.6.9", "escape-html": "~1.0.3", - "http-errors": "~1.6.1", - "mime-types": "~2.1.15", - "parseurl": "~1.3.1" + "http-errors": "~1.6.2", + "mime-types": "~2.1.17", + "parseurl": "~1.3.2" + }, + "dependencies": { + "mime-db": { + "version": "1.37.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", + "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==", + "dev": true + }, + "mime-types": { + "version": "2.1.21", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", + "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", + "dev": true, + "requires": { + "mime-db": "~1.37.0" + } + }, + "parseurl": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=", + "dev": true + } } }, "set-getter": { @@ -6137,16 +6409,6 @@ "is-fullwidth-code-point": "^2.0.0" } }, - "sntp": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", - "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", - "dev": true, - "optional": true, - "requires": { - "hoek": "4.x.x" - } - }, "source-map": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", @@ -6162,6 +6424,12 @@ "source-map": "^0.5.6" } }, + "sourcemap-codec": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.3.tgz", + "integrity": "sha512-vFrY/x/NdsD7Yc8mpTJXuao9S8lq08Z/kOITHz6b7YbfI9xL8Spe5EvSQUHOI7SbpY8bRPr0U3kKSsPuqEGSfA==", + "dev": true + }, "spawn-command": { "version": "0.0.2-1", "resolved": "https://registry.npmjs.org/spawn-command/-/spawn-command-0.0.2-1.tgz", @@ -6205,9 +6473,9 @@ "dev": true }, "sshpk": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", - "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.15.1.tgz", + "integrity": "sha512-mSdgNUaidk+dRU5MhYtN9zebdzF2iG0cNPWy8HG+W8y+fT1JnSkh0fzzpjOa0L7P8i1Rscz38t0h4gPcKz43xA==", "dev": true, "optional": true, "requires": { @@ -6218,6 +6486,7 @@ "ecc-jsbn": "~0.1.1", "getpass": "^0.1.1", "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", "tweetnacl": "~0.14.0" } }, @@ -6345,13 +6614,6 @@ "safe-buffer": "~5.1.0" } }, - "stringstream": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", - "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=", - "dev": true, - "optional": true - }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", @@ -6460,18 +6722,6 @@ "integrity": "sha1-e/gQalwaSCUbPjvAoOFzJIn9Dcg=", "dev": true }, - "tag-dist-files": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/tag-dist-files/-/tag-dist-files-0.1.6.tgz", - "integrity": "sha1-h64FrBQw1H2m76Hrx7Bw+QxnTVQ=", - "dev": true, - "requires": { - "commander": "^2.9.0", - "ez-async": "^1.0.0-alpha.1", - "load-json-file": "^2.0.0", - "regenerator-runtime": "^0.10.1" - } - }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -6519,6 +6769,7 @@ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", "dev": true, + "optional": true, "requires": { "punycode": "^1.4.1" } @@ -6531,9 +6782,9 @@ "optional": true }, "tree-kill": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.1.0.tgz", - "integrity": "sha1-yWPc8DciiS7FnLpWnpQLcZVNFyk=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.0.tgz", + "integrity": "sha512-DlX6dR0lOIRDFxI0mjL9IYg6OTncLm/Zt+JiBhE5OlFcAR8yc9S7FFXU9so0oda47frdM/JFsk7UjNt9vscKcg==", "dev": true }, "trim-newlines": { @@ -6562,8 +6813,7 @@ "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true, - "optional": true + "dev": true }, "type-check": { "version": "0.3.2", @@ -6610,6 +6860,12 @@ "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=", "dev": true }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "dev": true + }, "unix-crypt-td-js": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unix-crypt-td-js/-/unix-crypt-td-js-1.0.0.tgz", @@ -6646,11 +6902,6 @@ "integrity": "sha512-DIWtzUkw04M4k3bf1IcpS2tngXEL26YUD2M0tMDUpnUrz2hgzUBlD55a4FjdLGPvfHxS6uluGWvaVEqgBcVa+g==", "dev": true }, - "uws": { - "version": "10.148.0", - "resolved": "https://registry.npmjs.org/uws/-/uws-10.148.0.tgz", - "integrity": "sha512-aJpFgMMyxubiE/ll4nj9nWoQbv0HzZZDWXfwyu78nuFObX0Zoyv3TWjkqKPQ1vb2sMPZoz67tri7QNE6dybNmQ==" - }, "v8flags": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-2.1.1.tgz", @@ -6689,9 +6940,9 @@ } }, "vlq": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/vlq/-/vlq-0.2.2.tgz", - "integrity": "sha1-4xbVJXtAuGu0PLjV/qXX9U1rDKE=", + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/vlq/-/vlq-0.2.3.tgz", + "integrity": "sha512-DRibZL6DsNhIgYQ+wNdWDL2SL3bKPlVrRiBqV5yuMm++op8W4kGFtaQfCs4KEJn0wBZcHVHJ3eoywX8983k1ow==", "dev": true }, "webidl-conversions": { diff --git a/package.json b/package.json index 740acf45..148c5a13 100644 --- a/package.json +++ b/package.json @@ -4,24 +4,16 @@ "description": "A framework for real-time p2p shared editing on any data", "main": "./y.node.js", "browser": "./y.js", - "module": "./src/y.js", + "module": "./src/index.js", "scripts": { - "start": "node --experimental-modules src/Connectors/WebsocketsConnector/server.js", "test": "npm run lint", "debug": "concurrently 'rollup -wc rollup.test.js' 'cutest-serve y.test.js -o'", - "lint": "standard src/**/*.mjs test/**/*.mjs tests-lib/**/*.js", + "lint": "standard src/**/*.js test/**/*.js tests-lib/**/*.js", "docs": "esdoc", "serve-docs": "npm run docs && serve ./docs/", "dist": "rollup -c rollup.browser.js; rollup -c rollup.node.js", "watch": "concurrently 'rollup -wc rollup.browser.js' 'rollup -wc rollup.node.js'", - "postversion": "npm run dist", - "postpublish": "tag-dist-files --overwrite-existing-tag", - "demos": "concurrently 'node --experimental-modules src/Connectors/WebsocketsConnector/server.js' 'http-server'" - }, - "now": { - "engines": { - "node": "10.x.x" - } + "postversion": "npm run dist" }, "files": [ "y.*", @@ -56,32 +48,26 @@ }, "homepage": "http://y-js.org", "devDependencies": { - "babel-cli": "^6.24.1", + "babel-cli": "^6.26.0", "babel-plugin-external-helpers": "^6.22.0", - "babel-plugin-transform-regenerator": "^6.24.1", + "babel-plugin-transform-regenerator": "^6.26.0", "babel-plugin-transform-runtime": "^6.23.0", "babel-preset-latest": "^6.24.1", - "chance": "^1.0.9", - "codemirror": "^5.37.0", - "concurrently": "^3.4.0", + "concurrently": "^3.6.1", "cutest": "^0.1.9", - "esdoc": "^1.0.4", + "esdoc": "^1.1.0", "esdoc-standard-plugin": "^1.0.0", - "quill": "^1.3.5", - "quill-cursors": "^1.0.2", + "quill": "^1.3.6", + "quill-cursors": "^1.0.3", "rollup": "^0.58.2", "rollup-plugin-babel": "^2.7.1", - "rollup-plugin-commonjs": "^8.0.2", - "rollup-plugin-inject": "^2.0.0", - "rollup-plugin-multi-entry": "^2.0.1", - "rollup-plugin-node-resolve": "^3.0.0", + "rollup-plugin-commonjs": "^8.4.1", + "rollup-plugin-inject": "^2.2.0", + "rollup-plugin-multi-entry": "^2.0.2", + "rollup-plugin-node-resolve": "^3.4.0", "rollup-plugin-uglify": "^1.0.2", "rollup-regenerator-runtime": "^6.23.1", "rollup-watch": "^3.2.2", - "standard": "^11.0.1", - "tag-dist-files": "^0.1.6" - }, - "dependencies": { - "uws": "^10.148.0" + "standard": "^11.0.1" } } diff --git a/src/Bindings/Binding.js b/src/Bindings/Binding.js index f24f54a3..34addfa3 100644 --- a/src/Bindings/Binding.js +++ b/src/Bindings/Binding.js @@ -1,5 +1,5 @@ -import { createMutualExclude } from '../../lib/mutualExclude.js' +import { createMutex } from '../../lib/mutex.js' /** * Abstract class for bindings. @@ -35,7 +35,7 @@ export default class Binding { /** * @private */ - this._mutualExclude = createMutualExclude() + this._mutualExclude = createMutex() } /** * Remove all data observers (both from the type and the target). diff --git a/src/Bindings/DomBinding/DomBinding.js b/src/Bindings/DomBinding/DomBinding.js index b2454609..fc68179c 100644 --- a/src/Bindings/DomBinding/DomBinding.js +++ b/src/Bindings/DomBinding/DomBinding.js @@ -8,6 +8,10 @@ import { defaultFilter, applyFilterOnType } from './filter.js' import typeObserver from './typeObserver.js' import domObserver from './domObserver.js' +/** + * @typedef {import('./filter.js').DomFilter} DomFilter + */ + /** * A binding that binds the children of a YXmlFragment to a DOM element. * @@ -26,7 +30,7 @@ export default class DomBinding extends Binding { * @param {Element} target The bind target. Mirrors the target. * @param {Object} [opts] Optional configurations - * @param {FilterFunction} [opts.filter=defaultFilter] The filter function to use. + * @param {DomFilter} [opts.filter=defaultFilter] The filter function to use. */ constructor (type, target, opts = {}) { // Binding handles textType as this.type and domTextarea as this.target @@ -48,7 +52,7 @@ export default class DomBinding extends Binding { /** * Defines which DOM attributes and elements to filter out. * Also filters remote changes. - * @type {FilterFunction} + * @type {DomFilter} */ this.filter = opts.filter || defaultFilter // set initial value @@ -57,7 +61,7 @@ export default class DomBinding extends Binding { target.insertBefore(child.toDom(opts.document, opts.hooks, this), null) }) this._typeObserver = typeObserver.bind(this) - this._domObserver = (mutations) => { + this._domObserver = mutations => { domObserver.call(this, mutations, opts.document) } type.observeDeep(this._typeObserver) @@ -119,7 +123,7 @@ export default class DomBinding extends Binding { /** * NOTE: currently does not apply filter to existing elements! - * @param {FilterFunction} filter The filter function to use from now on. + * @param {DomFilter} filter The filter function to use from now on. */ setFilter (filter) { this.filter = filter diff --git a/src/Bindings/DomBinding/domToType.js b/src/Bindings/DomBinding/domToType.js index c1d81d2a..ee685d67 100644 --- a/src/Bindings/DomBinding/domToType.js +++ b/src/Bindings/DomBinding/domToType.js @@ -1,60 +1,65 @@ - +/* eslint-env browser */ import YXmlText from '../../Types/YXml/YXmlText.js' import YXmlHook from '../../Types/YXml/YXmlHook.js' import YXmlElement from '../../Types/YXml/YXmlElement.js' import { createAssociation, domsToTypes } from './util.js' import { filterDomAttributes, defaultFilter } from './filter.js' +/** + * @typedef {import('./filter.js').DomFilter} DomFilter + * @typedef {import('./DomBinding.js').default} DomBinding + */ + /** * Creates a Yjs type (YXml) based on the contents of a DOM Element. * - * @param {Element|TextNode} element The DOM Element + * @param {Element|Text} element The DOM Element * @param {?Document} _document Optional. Provide the global document object - * @param {Hooks} [hooks = {}] Optional. Set of Yjs Hooks - * @param {Filter} [filter=defaultFilter] Optional. Dom element filter + * @param {Object} [hooks = {}] Optional. Set of Yjs Hooks + * @param {DomFilter} [filter=defaultFilter] Optional. Dom element filter * @param {?DomBinding} binding Warning: This property is for internal use only! - * @return {YXmlElement | YXmlText} + * @return {YXmlElement | YXmlText | false} */ export default function domToType (element, _document = document, hooks = {}, filter = defaultFilter, binding) { - let type - switch (element.nodeType) { - case _document.ELEMENT_NODE: - let hookName = null - let hook - // configure `hookName !== undefined` if element is a hook. - if (element.hasAttribute('data-yjs-hook')) { - hookName = element.getAttribute('data-yjs-hook') - hook = hooks[hookName] - if (hook === undefined) { - console.error(`Unknown hook "${hookName}". Deleting yjsHook dataset property.`) - delete element.removeAttribute('data-yjs-hook') - hookName = null - } + /** + * @type {any} + */ + let type = null + if (element instanceof Element) { + let hookName = null + let hook + // configure `hookName !== undefined` if element is a hook. + if (element.hasAttribute('data-yjs-hook')) { + hookName = element.getAttribute('data-yjs-hook') + hook = hooks[hookName] + if (hook === undefined) { + console.error(`Unknown hook "${hookName}". Deleting yjsHook dataset property.`) + element.removeAttribute('data-yjs-hook') + hookName = null } - if (hookName === null) { - // Not a hook - const attrs = filterDomAttributes(element, filter) - if (attrs === null) { - type = false - } else { - type = new YXmlElement(element.nodeName) - attrs.forEach((val, key) => { - type.setAttribute(key, val) - }) - type.insert(0, domsToTypes(element.childNodes, document, hooks, filter, binding)) - } + } + if (hookName === null) { + // Not a hook + const attrs = filterDomAttributes(element, filter) + if (attrs === null) { + type = false } else { - // Is a hook - type = new YXmlHook(hookName) - hook.fillType(element, type) + type = new YXmlElement(element.nodeName) + attrs.forEach((val, key) => { + type.setAttribute(key, val) + }) + type.insert(0, domsToTypes(element.childNodes, document, hooks, filter, binding)) } - break - case _document.TEXT_NODE: - type = new YXmlText() - type.insert(0, element.nodeValue) - break - default: - throw new Error('Can\'t transform this node type to a YXml type!') + } else { + // Is a hook + type = new YXmlHook(hookName) + hook.fillType(element, type) + } + } else if (element instanceof Text) { + type = new YXmlText() + type.insert(0, element.nodeValue) + } else { + throw new Error('Can\'t transform this node type to a YXml type!') } createAssociation(binding, element, type) return type diff --git a/src/Bindings/DomBinding/filter.js b/src/Bindings/DomBinding/filter.js index 08c208e7..273b997d 100644 --- a/src/Bindings/DomBinding/filter.js +++ b/src/Bindings/DomBinding/filter.js @@ -1,5 +1,12 @@ import isParentOf from '../../Util/isParentOf.js' +/** + * @callback DomFilter + * @param {string} nodeName + * @param {Map} attrs + * @return {Map | null} + */ + /** * Default filter method (does nothing). * diff --git a/src/Bindings/DomBinding/selection.js b/src/Bindings/DomBinding/selection.js index ab6760f5..d04a4e48 100644 --- a/src/Bindings/DomBinding/selection.js +++ b/src/Bindings/DomBinding/selection.js @@ -17,7 +17,7 @@ function _getCurrentRelativeSelection (domBinding) { return null } -export const getCurrentRelativeSelection = typeof getSelection !== 'undefined' ? _getCurrentRelativeSelection : () => null +export const getCurrentRelativeSelection = typeof getSelection !== 'undefined' ? _getCurrentRelativeSelection : domBinding => null export function beforeTransactionSelectionFixer (domBinding) { relativeSelection = getCurrentRelativeSelection(domBinding) diff --git a/src/Bindings/DomBinding/typeObserver.js b/src/Bindings/DomBinding/typeObserver.js index 762af70d..17510bab 100644 --- a/src/Bindings/DomBinding/typeObserver.js +++ b/src/Bindings/DomBinding/typeObserver.js @@ -1,3 +1,4 @@ +/* eslint-env browser */ /* global getSelection */ import YXmlText from '../../Types/YXml/YXmlText.js' @@ -17,11 +18,17 @@ function findScrollReference (scrollingElement) { } } } else { - if (anchor.nodeType === document.TEXT_NODE) { - anchor = anchor.parentElement + /** + * @type {Element} + */ + let elem = anchor.parentElement + if (anchor instanceof Element) { + elem = anchor + } + return { + elem, + top: elem.getBoundingClientRect().top } - const top = anchor.getBoundingClientRect().top - return { elem: anchor, top: top } } } return null diff --git a/src/Bindings/DomBinding/util.js b/src/Bindings/DomBinding/util.js index b4f6148d..664d0cb5 100644 --- a/src/Bindings/DomBinding/util.js +++ b/src/Bindings/DomBinding/util.js @@ -1,6 +1,13 @@ import domToType from './domToType.js' +/** + * @typedef {import('../../Types/YXml/YXmlText.js').default} YXmlText + * @typedef {import('../../Types/YXml/YXmlElement.js').default} YXmlElement + * @typedef {import('../../Types/YXml/YXmlHook.js').default} YXmlHook + * @typedef {import('./DomBinding.js').default} DomBinding + */ + /** * Iterates items until an undeleted item is found. * @@ -32,8 +39,8 @@ export function removeAssociation (domBinding, dom, type) { * type). * * @param {DomBinding} domBinding The binding object - * @param {Element} dom The dom that is to be associated with type - * @param {YXmlElement|YXmlHook} type The type that is to be associated with dom + * @param {DocumentFragment|Element|Text} dom The dom that is to be associated with type + * @param {YXmlElement|YXmlHook|YXmlText} type The type that is to be associated with dom * */ export function createAssociation (domBinding, dom, type) { diff --git a/src/MessageHandler/binaryEncode.js b/src/MessageHandler/binaryEncode.js deleted file mode 100644 index 27b69baa..00000000 --- a/src/MessageHandler/binaryEncode.js +++ /dev/null @@ -1,32 +0,0 @@ - -import { writeStructs } from './syncStep1.js' -import { integrateRemoteStructs } from './integrateRemoteStructs.js' -import { readDeleteSet, writeDeleteSet } from './deleteSet.js' -import BinaryEncoder from '../Util/Binary/Encoder.js' - -/** - * Read the Decoder and fill the Yjs instance with data in the decoder. - * - * @param {Y} y The Yjs instance - * @param {BinaryDecoder} decoder The BinaryDecoder to read from. - */ -export function fromBinary (y, decoder) { - y.transact(function () { - integrateRemoteStructs(y, decoder) - readDeleteSet(y, decoder) - }) -} - -/** - * Encode the Yjs model to binary format. - * - * @param {Y} y The Yjs instance - * @return {BinaryEncoder} The encoder instance that can be transformed - * to ArrayBuffer or Buffer. - */ -export function toBinary (y) { - let encoder = new BinaryEncoder() - writeStructs(y, encoder, new Map()) - writeDeleteSet(y, encoder) - return encoder -} diff --git a/src/MessageHandler/deleteSet.js b/src/MessageHandler/deleteSet.js deleted file mode 100644 index 2b54abfc..00000000 --- a/src/MessageHandler/deleteSet.js +++ /dev/null @@ -1,130 +0,0 @@ -import { deleteItemRange } from '../Struct/Delete.js' -import ID from '../Util/ID/ID.js' - -export function stringifyDeleteSet (y, decoder, strBuilder) { - let dsLength = decoder.readUint32() - for (let i = 0; i < dsLength; i++) { - let user = decoder.readVarUint() - strBuilder.push(' -' + user + ':') - let dvLength = decoder.readVarUint() - for (let j = 0; j < dvLength; j++) { - let from = decoder.readVarUint() - let len = decoder.readVarUint() - let gc = decoder.readUint8() === 1 - strBuilder.push(`clock: ${from}, length: ${len}, gc: ${gc}`) - } - } - return strBuilder -} - -export function writeDeleteSet (y, encoder) { - let currentUser = null - let currentLength - let lastLenPos - - let numberOfUsers = 0 - let laterDSLenPus = encoder.pos - encoder.writeUint32(0) - - y.ds.iterate(null, null, function (n) { - var user = n._id.user - var clock = n._id.clock - var len = n.len - var gc = n.gc - if (currentUser !== user) { - numberOfUsers++ - // a new user was found - if (currentUser !== null) { // happens on first iteration - encoder.setUint32(lastLenPos, currentLength) - } - currentUser = user - encoder.writeVarUint(user) - // pseudo-fill pos - lastLenPos = encoder.pos - encoder.writeUint32(0) - currentLength = 0 - } - encoder.writeVarUint(clock) - encoder.writeVarUint(len) - encoder.writeUint8(gc ? 1 : 0) - currentLength++ - }) - if (currentUser !== null) { // happens on first iteration - encoder.setUint32(lastLenPos, currentLength) - } - encoder.setUint32(laterDSLenPus, numberOfUsers) -} - -export function readDeleteSet (y, decoder) { - let dsLength = decoder.readUint32() - for (let i = 0; i < dsLength; i++) { - let user = decoder.readVarUint() - let dv = [] - let dvLength = decoder.readUint32() - for (let j = 0; j < dvLength; j++) { - let from = decoder.readVarUint() - let len = decoder.readVarUint() - let gc = decoder.readUint8() === 1 - dv.push([from, len, gc]) - } - if (dvLength > 0) { - let pos = 0 - let d = dv[pos] - let deletions = [] - y.ds.iterate(new ID(user, 0), new ID(user, Number.MAX_VALUE), function (n) { - // cases: - // 1. d deletes something to the right of n - // => go to next n (break) - // 2. d deletes something to the left of n - // => create deletions - // => reset d accordingly - // *)=> if d doesn't delete anything anymore, go to next d (continue) - // 3. not 2) and d deletes something that also n deletes - // => reset d so that it doesn't contain n's deletion - // *)=> if d does not delete anything anymore, go to next d (continue) - while (d != null) { - var diff = 0 // describe the diff of length in 1) and 2) - if (n._id.clock + n.len <= d[0]) { - // 1) - break - } else if (d[0] < n._id.clock) { - // 2) - // delete maximum the len of d - // else delete as much as possible - diff = Math.min(n._id.clock - d[0], d[1]) - // deleteItemRange(y, user, d[0], diff, true) - deletions.push([user, d[0], diff]) - } else { - // 3) - diff = n._id.clock + n.len - d[0] // never null (see 1) - if (d[2] && !n.gc) { - // d marks as gc'd but n does not - // then delete either way - // deleteItemRange(y, user, d[0], Math.min(diff, d[1]), true) - deletions.push([user, d[0], Math.min(diff, d[1])]) - } - } - if (d[1] <= diff) { - // d doesn't delete anything anymore - d = dv[++pos] - } else { - d[0] = d[0] + diff // reset pos - d[1] = d[1] - diff // reset length - } - } - }) - // TODO: It would be more performant to apply the deletes in the above loop - // Adapt the Tree implementation to support delete while iterating - for (let i = deletions.length - 1; i >= 0; i--) { - const del = deletions[i] - deleteItemRange(y, del[0], del[1], del[2], true) - } - // for the rest.. just apply it - for (; pos < dv.length; pos++) { - d = dv[pos] - deleteItemRange(y, user, d[0], d[1], true) - // deletions.push([user, d[0], d[1], d[2]]) - } - } - } -} diff --git a/src/MessageHandler/messageToString.js b/src/MessageHandler/messageToString.js deleted file mode 100644 index de31a020..00000000 --- a/src/MessageHandler/messageToString.js +++ /dev/null @@ -1,65 +0,0 @@ -import BinaryDecoder from '../Util/Binary/Decoder.js' -import { stringifyStructs } from './integrateRemoteStructs.js' -import { stringifySyncStep1 } from './syncStep1.js' -import { stringifySyncStep2 } from './syncStep2.js' -import ID from '../Util/ID/ID.js' -import RootID from '../Util/ID/RootID.js' -import Y from '../Y.js' - -export function messageToString ([y, buffer]) { - let decoder = new BinaryDecoder(buffer) - decoder.readVarString() // read roomname - let type = decoder.readVarString() - let strBuilder = [] - strBuilder.push('\n === ' + type + ' ===') - if (type === 'update') { - stringifyStructs(y, decoder, strBuilder) - } else if (type === 'sync step 1') { - stringifySyncStep1(y, decoder, strBuilder) - } else if (type === 'sync step 2') { - stringifySyncStep2(y, decoder, strBuilder) - } else { - strBuilder.push('-- Unknown message type - probably an encoding issue!!!') - } - return strBuilder.join('\n') -} - -export function messageToRoomname (buffer) { - let decoder = new BinaryDecoder(buffer) - decoder.readVarString() // roomname - return decoder.readVarString() // messageType -} - -export function logID (id) { - if (id !== null && id._id != null) { - id = id._id - } - if (id === null) { - return '()' - } else if (id instanceof ID) { - return `(${id.user},${id.clock})` - } else if (id instanceof RootID) { - return `(${id.name},${id.type})` - } else if (id.constructor === Y) { - return `y` - } else { - throw new Error('This is not a valid ID!') - } -} - -/** - * Helper utility to convert an item to a readable format. - * - * @param {String} name The name of the item class (YText, ItemString, ..). - * @param {Item} item The item instance. - * @param {String} [append] Additional information to append to the returned - * string. - * @return {String} A readable string that represents the item object. - * - * @private - */ -export function logItemHelper (name, item, append) { - const left = item._left !== null ? item._left._lastId : null - const origin = item._origin !== null ? item._origin._lastId : null - return `${name}(id:${logID(item._id)},left:${logID(left)},origin:${logID(origin)},right:${logID(item._right)},parent:${logID(item._parent)},parentSub:${item._parentSub}${append !== undefined ? ' - ' + append : ''})` -} diff --git a/src/MessageHandler/stateSet.js b/src/MessageHandler/stateSet.js deleted file mode 100644 index 3ec21c99..00000000 --- a/src/MessageHandler/stateSet.js +++ /dev/null @@ -1,23 +0,0 @@ - -export function readStateSet (decoder) { - let ss = new Map() - let ssLength = decoder.readUint32() - for (let i = 0; i < ssLength; i++) { - let user = decoder.readVarUint() - let clock = decoder.readVarUint() - ss.set(user, clock) - } - return ss -} - -export function writeStateSet (y, encoder) { - let lenPosition = encoder.pos - let len = 0 - encoder.writeUint32(0) - for (let [user, clock] of y.ss.state) { - encoder.writeVarUint(user) - encoder.writeVarUint(clock) - len++ - } - encoder.setUint32(lenPosition, len) -} diff --git a/src/MessageHandler/syncStep1.js b/src/MessageHandler/syncStep1.js deleted file mode 100644 index 046177c3..00000000 --- a/src/MessageHandler/syncStep1.js +++ /dev/null @@ -1,83 +0,0 @@ -import BinaryEncoder from '../Util/Binary/Encoder.js' -import { readStateSet, writeStateSet } from './stateSet.js' -import { writeDeleteSet } from './deleteSet.js' -import ID from '../Util/ID/ID.js' -import { RootFakeUserID } from '../Util/ID/RootID.js' - -export function stringifySyncStep1 (y, decoder, strBuilder) { - let auth = decoder.readVarString() - let protocolVersion = decoder.readVarUint() - strBuilder.push(` - auth: "${auth}"`) - strBuilder.push(` - protocolVersion: ${protocolVersion}`) - // write SS - let ssBuilder = [] - let len = decoder.readUint32() - for (let i = 0; i < len; i++) { - let user = decoder.readVarUint() - let clock = decoder.readVarUint() - ssBuilder.push(`(${user}:${clock})`) - } - strBuilder.push(' == SS: ' + ssBuilder.join(',')) -} - -export function sendSyncStep1 (connector, syncUser) { - let encoder = new BinaryEncoder() - encoder.writeVarString(connector.y.room) - encoder.writeVarString('sync step 1') - encoder.writeVarString(connector.authInfo || '') - encoder.writeVarUint(connector.protocolVersion) - writeStateSet(connector.y, encoder) - connector.send(syncUser, encoder.createBuffer()) -} - -/** - * @private - * Write all Items that are not not included in ss to - * the encoder object. - */ -export function writeStructs (y, encoder, ss) { - const lenPos = encoder.pos - encoder.writeUint32(0) - let len = 0 - for (let user of y.ss.state.keys()) { - let clock = ss.get(user) || 0 - if (user !== RootFakeUserID) { - const minBound = new ID(user, clock) - const overlappingLeft = y.os.findPrev(minBound) - const rightID = overlappingLeft === null ? null : overlappingLeft._id - if (rightID !== null && rightID.user === user && rightID.clock + overlappingLeft._length > clock) { - const struct = overlappingLeft._clonePartial(clock - rightID.clock) - struct._toBinary(encoder) - len++ - } - y.os.iterate(minBound, new ID(user, Number.MAX_VALUE), function (struct) { - struct._toBinary(encoder) - len++ - }) - } - } - encoder.setUint32(lenPos, len) -} - -export function readSyncStep1 (decoder, encoder, y, senderConn, sender) { - let protocolVersion = decoder.readVarUint() - // check protocol version - if (protocolVersion !== y.connector.protocolVersion) { - console.warn( - `You tried to sync with a Yjs instance that has a different protocol version - (You: ${protocolVersion}, Client: ${protocolVersion}). - `) - y.destroy() - } - // write sync step 2 - encoder.writeVarString('sync step 2') - encoder.writeVarString(y.connector.authInfo || '') - const ss = readStateSet(decoder) - writeStructs(y, encoder, ss) - writeDeleteSet(y, encoder) - y.connector.send(senderConn.uid, encoder.createBuffer()) - senderConn.receivedSyncStep2 = true - if (y.connector.role === 'slave') { - sendSyncStep1(y.connector, sender) - } -} diff --git a/src/MessageHandler/syncStep2.js b/src/MessageHandler/syncStep2.js deleted file mode 100644 index 2de4902a..00000000 --- a/src/MessageHandler/syncStep2.js +++ /dev/null @@ -1,28 +0,0 @@ -import { stringifyStructs, integrateRemoteStructs } from './integrateRemoteStructs.js' -import { readDeleteSet } from './deleteSet.js' - -export function stringifySyncStep2 (y, decoder, strBuilder) { - strBuilder.push(' - auth: ' + decoder.readVarString()) - strBuilder.push(' == OS:') - stringifyStructs(y, decoder, strBuilder) - // write DS to string - strBuilder.push(' == DS:') - let len = decoder.readUint32() - for (let i = 0; i < len; i++) { - let user = decoder.readVarUint() - strBuilder.push(` User: ${user}: `) - let len2 = decoder.readUint32() - for (let j = 0; j < len2; j++) { - let from = decoder.readVarUint() - let to = decoder.readVarUint() - let gc = decoder.readUint8() === 1 - strBuilder.push(`[${from}, ${to}, ${gc}]`) - } - } -} - -export function readSyncStep2 (decoder, encoder, y, senderConn, sender) { - integrateRemoteStructs(y, decoder) - readDeleteSet(y, decoder) - y.connector._setSyncedWith(sender) -} diff --git a/src/Persistences/FilePersistence.js b/src/Persistences/FilePersistence.js index 13952232..499cc591 100644 --- a/src/Persistences/FilePersistence.js +++ b/src/Persistences/FilePersistence.js @@ -1,8 +1,8 @@ import fs from 'fs' import path from 'path' -import BinaryDecoder from '../Util/Binary/Decoder.js' -import BinaryEncoder from '../Util/Binary/Encoder.js' -import { createMutualExclude } from '../Util/mutualExclude.js' +import * as encoding from '../../lib/encoding.js' +import * as decoding from '../../lib/decoding.js' +import { createMutualExclude } from '../../lib/mutualExclude.js' import { encodeUpdate, encodeStructsDS, decodePersisted } from './decodePersisted.js' function createFilePath (persistence, roomName) { @@ -23,9 +23,9 @@ export default class FilePersistence { return new Promise((resolve, reject) => { this._mutex(() => { const filePath = createFilePath(this, room) - const updateMessage = new BinaryEncoder() + const updateMessage = encoding.createEncoder() encodeUpdate(y, encodedStructs, updateMessage) - fs.appendFile(filePath, Buffer.from(updateMessage.createBuffer()), (err) => { + fs.appendFile(filePath, Buffer.from(encoding.toBuffer(updateMessage)), (err) => { if (err !== null) { reject(err) } else { @@ -37,10 +37,10 @@ export default class FilePersistence { } saveState (roomName, y) { return new Promise((resolve, reject) => { - const encoder = new BinaryEncoder() + const encoder = encoding.createEncoder() encodeStructsDS(y, encoder) const filePath = createFilePath(this, roomName) - fs.writeFile(filePath, Buffer.from(encoder.createBuffer()), (err) => { + fs.writeFile(filePath, Buffer.from(encoding.toBuffer(encoder)), (err) => { if (err !== null) { reject(err) } else { @@ -61,7 +61,7 @@ export default class FilePersistence { this._mutex(() => { console.info(`unpacking data (${data.length})`) console.time('unpacking') - decodePersisted(y, new BinaryDecoder(data)) + decodePersisted(y, decoding.createDecoder(data.buffer)) console.timeEnd('unpacking') }) resolve() diff --git a/src/Persistences/IndexedDBPersistence.js b/src/Persistences/IndexedDBPersistence.js index 44becafe..37f37757 100644 --- a/src/Persistences/IndexedDBPersistence.js +++ b/src/Persistences/IndexedDBPersistence.js @@ -1,12 +1,10 @@ /* global indexedDB, location, BroadcastChannel */ import Y from '../Y.js' -import { createMutualExclude } from '../Util/mutualExclude.js' -import { decodePersisted, encodeStructsDS, encodeUpdate } from './decodePersisted.js' -import BinaryDecoder from '../Util/Binary/Decoder.js' -import BinaryEncoder from '../Util/Binary/Encoder.js' -import { PERSIST_STRUCTS_DS } from './decodePersisted.js'; -import { PERSIST_UPDATE } from './decodePersisted.js'; +import { createMutualExclude } from '../../lib/mutualExclude.js' +import { decodePersisted, encodeStructsDS, encodeUpdate, PERSIST_STRUCTS_DS, PERSIST_UPDATE } from './decodePersisted.js' +import * as decoding from '../../lib/decoding.js' +import * as encoding from '../../lib/encoding.js' /* * Request to Promise transformer */ diff --git a/src/Persistences/decodePersisted.js b/src/Persistences/decodePersisted.js index 482b8e94..009de0bd 100644 --- a/src/Persistences/decodePersisted.js +++ b/src/Persistences/decodePersisted.js @@ -39,10 +39,10 @@ export function decodePersisted (y, decoder) { const contentType = decoder.readVarUint() switch (contentType) { case PERSIST_UPDATE: - integrateRemoteStructs(y, decoder) + integrateRemoteStructs(decoder, y) break case PERSIST_STRUCTS_DS: - integrateRemoteStructs(y, decoder) + integrateRemoteStructs(decoder, y) readDeleteSet(y, decoder) break } diff --git a/src/Store/DeleteStore.js b/src/Store/DeleteStore.js index e3155d04..a795653e 100644 --- a/src/Store/DeleteStore.js +++ b/src/Store/DeleteStore.js @@ -1,6 +1,6 @@ import Tree from '../../lib/Tree.js' -import ID from '../Util/ID/ID.js' +import * as ID from '../Util/ID.js' class DSNode { constructor (id, len, gc) { @@ -33,7 +33,7 @@ export default class DeleteStore extends Tree { mark (id, length, gc) { if (length === 0) return // Step 1. Unmark range - const leftD = this.findWithUpperBound(new ID(id.user, id.clock - 1)) + const leftD = this.findWithUpperBound(ID.createID(id.user, id.clock - 1)) // Resize left DSNode if necessary if (leftD !== null && leftD._id.user === id.user) { if (leftD._id.clock < id.clock && id.clock < leftD._id.clock + leftD.len) { @@ -41,19 +41,19 @@ export default class DeleteStore extends Tree { if (id.clock + length < leftD._id.clock + leftD.len) { // overlaps new mark range and some more // create another DSNode to the right of new mark - this.put(new DSNode(new ID(id.user, id.clock + length), leftD._id.clock + leftD.len - id.clock - length, leftD.gc)) + this.put(new DSNode(ID.createID(id.user, id.clock + length), leftD._id.clock + leftD.len - id.clock - length, leftD.gc)) } // resize left DSNode leftD.len = id.clock - leftD._id.clock } // Otherwise there is no overlapping } // Resize right DSNode if necessary - const upper = new ID(id.user, id.clock + length - 1) + const upper = ID.createID(id.user, id.clock + length - 1) const rightD = this.findWithUpperBound(upper) if (rightD !== null && rightD._id.user === id.user) { if (rightD._id.clock < id.clock + length && id.clock <= rightD._id.clock && id.clock + length < rightD._id.clock + rightD.len) { // we only consider the case where we resize the node const d = id.clock + length - rightD._id.clock - rightD._id = new ID(rightD._id.user, rightD._id.clock + d) + rightD._id = ID.createID(rightD._id.user, rightD._id.clock + d) rightD.len -= d } } @@ -72,7 +72,7 @@ export default class DeleteStore extends Tree { leftD.len += length newMark = leftD } - const rightNext = this.find(new ID(id.user, id.clock + length)) + const rightNext = this.find(ID.createID(id.user, id.clock + length)) if (rightNext !== null && rightNext._id.user === id.user && id.clock + length === rightNext._id.clock && gc === rightNext.gc) { // We can merge newMark and rightNext newMark.len += rightNext.len diff --git a/src/Store/OperationStore.js b/src/Store/OperationStore.js index 3ed7519b..89cf096d 100644 --- a/src/Store/OperationStore.js +++ b/src/Store/OperationStore.js @@ -1,7 +1,7 @@ import Tree from '../../lib/Tree.js' -import RootID from '../Util/ID/RootID.js' +import * as ID from '../Util/ID.js' import { getStruct } from '../Util/structReferences.js' -import { logID } from '../MessageHandler/messageToString.js' +import { stringifyID, stringifyItemID } from '../message.js' import GC from '../Struct/GC.js' export default class OperationStore extends Tree { @@ -14,18 +14,18 @@ export default class OperationStore extends Tree { this.iterate(null, null, function (item) { if (item.constructor === GC) { items.push({ - id: logID(item), + id: stringifyItemID(item), content: item._length, deleted: 'GC' }) } else { items.push({ - id: logID(item), - origin: logID(item._origin === null ? null : item._origin._lastId), - left: logID(item._left === null ? null : item._left._lastId), - right: logID(item._right), - right_origin: logID(item._right_origin), - parent: logID(item._parent), + id: stringifyItemID(item), + origin: item._origin === null ? '()' : stringifyID(item._origin._lastId), + left: item._left === null ? '()' : stringifyID(item._left._lastId), + right: stringifyItemID(item._right), + right_origin: stringifyItemID(item._right_origin), + parent: stringifyItemID(item._parent), parentSub: item._parentSub, deleted: item._deleted, content: JSON.stringify(item._content) @@ -36,7 +36,7 @@ export default class OperationStore extends Tree { } get (id) { let struct = this.find(id) - if (struct === null && id instanceof RootID) { + if (struct === null && id instanceof ID.RootID) { const Constr = getStruct(id.type) const y = this.y struct = new Constr() diff --git a/src/Store/StateStore.js b/src/Store/StateStore.js index c46ad072..43ef713d 100644 --- a/src/Store/StateStore.js +++ b/src/Store/StateStore.js @@ -1,4 +1,8 @@ -import ID from '../Util/ID/ID.js' +import * as ID from '../Util/ID.js' + +/** + * @typedef {Map} StateSet + */ export default class StateStore { constructor (y) { @@ -18,14 +22,14 @@ export default class StateStore { const user = this.y.userID const state = this.getState(user) this.setState(user, state + len) - return new ID(user, state) + return ID.createID(user, state) } updateRemoteState (struct) { let user = struct._id.user let userState = this.state.get(user) while (struct !== null && struct._id.clock === userState) { userState += struct._length - struct = this.y.os.get(new ID(user, userState)) + struct = this.y.os.get(ID.createID(user, userState)) } this.state.set(user, userState) } diff --git a/src/Struct/Delete.js b/src/Struct/Delete.js index 6bcf67e6..e952d847 100644 --- a/src/Struct/Delete.js +++ b/src/Struct/Delete.js @@ -1,31 +1,33 @@ import { getStructReference } from '../Util/structReferences.js' -import ID from '../Util/ID/ID.js' -import { logID } from '../MessageHandler/messageToString.js' +import * as ID from '../Util/ID.js' +import { stringifyID } from '../message.js' import { writeStructToTransaction } from '../Util/Transaction.js' +import * as decoding from '../../lib/decoding.js' +import * as encoding from '../../lib/encoding.js' /** * @private - * Delete all items in an ID-range - * TODO: implement getItemCleanStartNode for better performance (only one lookup) + * Delete all items in an ID-range. + * Does not create delete operations! + * TODO: implement getItemCleanStartNode for better performance (only one lookup). */ export function deleteItemRange (y, user, clock, range, gcChildren) { - const createDelete = y.connector !== null && y.connector._forwardAppliedStructs - let item = y.os.getItemCleanStart(new ID(user, clock)) + let item = y.os.getItemCleanStart(ID.createID(user, clock)) if (item !== null) { if (!item._deleted) { item._splitAt(y, range) - item._delete(y, createDelete, true) + item._delete(y, false, true) } let itemLen = item._length range -= itemLen clock += itemLen if (range > 0) { - let node = y.os.findNode(new ID(user, clock)) - while (node !== null && node.val !== null && range > 0 && node.val._id.equals(new ID(user, clock))) { + let node = y.os.findNode(ID.createID(user, clock)) + while (node !== null && node.val !== null && range > 0 && node.val._id.equals(ID.createID(user, clock))) { const nodeVal = node.val if (!nodeVal._deleted) { nodeVal._splitAt(y, range) - nodeVal._delete(y, createDelete, gcChildren) + nodeVal._delete(y, false, gcChildren) } const nodeLen = nodeVal._length range -= nodeLen @@ -44,6 +46,13 @@ export function deleteItemRange (y, user, clock, range, gcChildren) { */ export default class Delete { constructor () { + /** + * @type {ID.ID} + */ + this._targetID = null + /** + * @type {import('./Item.js').default} + */ this._target = null this._length = null } @@ -54,15 +63,18 @@ export default class Delete { * * This is called when data is received from a remote peer. * - * @param {Y} y The Yjs instance that this Item belongs to. - * @param {BinaryDecoder} decoder The decoder object to read data from. + * @param {import('../Y.js').default} y The Yjs instance that this Item belongs to. + * @param {decoding.Decoder} decoder The decoder object to read data from. */ _fromBinary (y, decoder) { // TODO: set target, and add it to missing if not found // There is an edge case in p2p networks! - const targetID = decoder.readID() + /** + * @type {any} + */ + const targetID = ID.decode(decoder) this._targetID = targetID - this._length = decoder.readVarUint() + this._length = decoding.readVarUint(decoder) if (y.os.getItem(targetID) === null) { return [targetID] } else { @@ -77,12 +89,12 @@ export default class Delete { * * This is called when this Item is sent to a remote peer. * - * @param {BinaryEncoder} encoder The encoder to write data to. + * @param {encoding.Encoder} encoder The encoder to write data to. */ _toBinary (encoder) { - encoder.writeUint8(getStructReference(this.constructor)) - encoder.writeID(this._targetID) - encoder.writeVarUint(this._length) + encoding.writeUint8(encoder, getStructReference(this.constructor)) + this._targetID.encode(encoder) + encoding.writeVarUint(encoder, this._length) } /** @@ -102,12 +114,6 @@ export default class Delete { // from remote const id = this._targetID deleteItemRange(y, id.user, id.clock, this._length, false) - } else if (y.connector !== null) { - // from local - y.connector.broadcastStruct(this) - } - if (y.persistence !== null) { - y.persistence.saveStruct(y, this) } writeStructToTransaction(y._transaction, this) } @@ -119,6 +125,6 @@ export default class Delete { * @private */ _logString () { - return `Delete - target: ${logID(this._targetID)}, len: ${this._length}` + return `Delete - target: ${stringifyID(this._targetID)}, len: ${this._length}` } } diff --git a/src/Struct/GC.js b/src/Struct/GC.js index 7d997321..3496b809 100644 --- a/src/Struct/GC.js +++ b/src/Struct/GC.js @@ -1,11 +1,15 @@ import { getStructReference } from '../Util/structReferences.js' -import { RootFakeUserID } from '../Util/ID/RootID.js' -import ID from '../Util/ID/ID.js' +import * as ID from '../Util/ID.js' import { writeStructToTransaction } from '../Util/Transaction.js' +import * as decoding from '../../lib/decoding.js' +import * as encoding from '../../lib/encoding.js' // TODO should have the same base class as Item export default class GC { constructor () { + /** + * @type {ID.ID} + */ this._id = null this._length = 0 } @@ -37,13 +41,7 @@ export default class GC { n._length += next._length y.os.delete(next._id) } - if (id.user !== RootFakeUserID) { - if (y.connector !== null && (y.connector._forwardAppliedStructs || id.user === y.userID)) { - y.connector.broadcastStruct(this) - } - if (y.persistence !== null) { - y.persistence.saveStruct(y, this) - } + if (id.user !== ID.RootFakeUserID) { writeStructToTransaction(y._transaction, this) } } @@ -54,13 +52,13 @@ export default class GC { * * This is called when this Item is sent to a remote peer. * - * @param {BinaryEncoder} encoder The encoder to write data to. + * @param {encoding.Encoder} encoder The encoder to write data to. * @private */ _toBinary (encoder) { - encoder.writeUint8(getStructReference(this.constructor)) - encoder.writeID(this._id) - encoder.writeVarUint(this._length) + encoding.writeUint8(encoder, getStructReference(this.constructor)) + this._id.encode(encoder) + encoding.writeVarUint(encoder, this._length) } /** @@ -68,17 +66,20 @@ export default class GC { * * This is called when data is received from a remote peer. * - * @param {Y} y The Yjs instance that this Item belongs to. - * @param {BinaryDecoder} decoder The decoder object to read data from. + * @param {import('../Y.js').default} y The Yjs instance that this Item belongs to. + * @param {decoding.Decoder} decoder The decoder object to read data from. * @private */ _fromBinary (y, decoder) { - const id = decoder.readID() + /** + * @type {any} + */ + const id = ID.decode(decoder) this._id = id - this._length = decoder.readVarUint() + this._length = decoding.readVarUint(decoder) const missing = [] if (y.ss.getState(id.user) < id.clock) { - missing.push(new ID(id.user, id.clock - 1)) + missing.push(ID.createID(id.user, id.clock - 1)) } return missing } @@ -89,7 +90,7 @@ export default class GC { _clonePartial (diff) { const gc = new GC() - gc._id = new ID(this._id.user, this._id.clock + diff) + gc._id = ID.createID(this._id.user, this._id.clock + diff) gc._length = this._length - diff return gc } diff --git a/src/Struct/Item.js b/src/Struct/Item.js index 241fc3de..c344ba8a 100644 --- a/src/Struct/Item.js +++ b/src/Struct/Item.js @@ -1,9 +1,15 @@ import { getStructReference } from '../Util/structReferences.js' -import ID from '../Util/ID/ID.js' -import { default as RootID, RootFakeUserID } from '../Util/ID/RootID.js' +import * as ID from '../Util/ID.js' import Delete from './Delete.js' import { transactionTypeChanged, writeStructToTransaction } from '../Util/Transaction.js' import GC from './GC.js' +import * as encoding from '../../lib/encoding.js' +import * as decoding from '../../lib/decoding.js' +import Y from '../Y.js' + +/** + * @typedef {import('./Type.js').default} YType + */ /** * @private @@ -15,7 +21,7 @@ import GC from './GC.js' */ export function splitHelper (y, a, b, diff) { const aID = a._id - b._id = new ID(aID.user, aID.clock + diff) + b._id = ID.createID(aID.user, aID.clock + diff) b._origin = a b._left = a b._right = a._right @@ -55,7 +61,7 @@ export default class Item { constructor () { /** * The uniqe identifier of this type. - * @type {ID} + * @type {ID.ID | ID.RootID} */ this._id = null /** @@ -99,7 +105,7 @@ export default class Item { /** * If this type's effect is reundone this type refers to the type that undid * this operation. - * @type {Item} + * @type {YType} */ this._redone = null } @@ -110,7 +116,8 @@ export default class Item { * @private */ _copy () { - return new this.constructor() + const C = this.constructor + return C() } /** @@ -124,6 +131,9 @@ export default class Item { if (this._redone !== null) { return this._redone } + if (this._parent instanceof Y) { + return + } let struct = this._copy() let left, right if (this._parentSub === null) { @@ -146,7 +156,7 @@ export default class Item { } if (parent._redone !== null) { parent = parent._redone - // find next cloned items + // find next cloned_redo items while (left !== null) { if (left._redone !== null && left._redone._parent === parent) { left = left._redone @@ -178,7 +188,11 @@ export default class Item { * @private */ get _lastId () { - return new ID(this._id.user, this._id.clock + this._length - 1) + /** + * @type {any} + */ + const id = this._id + return ID.createID(id.user, id.clock + this._length - 1) } /** @@ -227,10 +241,11 @@ export default class Item { * @param {Y} y The Yjs instance * @param {boolean} createDelete Whether to propagate a message that this * Type was deleted. + * @param {boolean} gcChildren * * @private */ - _delete (y, createDelete = true) { + _delete (y, createDelete = true, gcChildren) { if (!this._deleted) { this._deleted = true y.ds.mark(this._id, this._length, false) @@ -240,9 +255,6 @@ export default class Item { if (createDelete) { // broadcast and persists Delete del._integrate(y, true) - } else if (y.persistence !== null) { - // only persist Delete - y.persistence.saveStruct(y, del) } transactionTypeChanged(y, this._parent, this._parentSub) y._transaction.deletedStructs.add(this) @@ -280,21 +292,30 @@ export default class Item { * * Add this struct to y.os * * Check if this is struct deleted * + * @param {Y} y + * * @private */ _integrate (y) { y._transaction.newTypes.add(this) + /** + * @type {any} + */ const parent = this._parent + /** + * @type {any} + */ const selfID = this._id const user = selfID === null ? y.userID : selfID.user const userState = y.ss.getState(user) if (selfID === null) { this._id = y.ss.getNextID(this._length) - } else if (selfID.user === RootFakeUserID) { - // nop + } else if (selfID.user === ID.RootFakeUserID) { + // is parent + return } else if (selfID.clock < userState) { // already applied.. - return [] + return } else if (selfID.clock === userState) { y.ss.setState(selfID.user, userState + this._length) } else { @@ -304,7 +325,7 @@ export default class Item { if (!parent._deleted && !y._transaction.changedTypes.has(parent) && !y._transaction.newTypes.has(parent)) { // this is the first time parent is updated // or this types is new - this._parent._beforeChange() + parent._beforeChange() } /* @@ -328,9 +349,9 @@ export default class Item { if (this._left !== null) { o = this._left._right } else if (this._parentSub !== null) { - o = this._parent._map.get(this._parentSub) || null + o = parent._map.get(this._parentSub) || null } else { - o = this._parent._start + o = parent._start } let conflictingItems = new Set() let itemsBeforeOrigin = new Set() @@ -386,17 +407,11 @@ export default class Item { } } if (parent._deleted) { - this._delete(y, false) + this._delete(y, false, true) } y.os.put(this) transactionTypeChanged(y, parent, parentSub) - if (this._id.user !== RootFakeUserID) { - if (y.connector !== null && (y.connector._forwardAppliedStructs || this._id.user === y.userID)) { - y.connector.broadcastStruct(this) - } - if (y.persistence !== null) { - y.persistence.saveStruct(y, this) - } + if (this._id.user !== ID.RootFakeUserID) { writeStructToTransaction(y._transaction, this) } } @@ -407,12 +422,12 @@ export default class Item { * * This is called when this Item is sent to a remote peer. * - * @param {BinaryEncoder} encoder The encoder to write data to. + * @param {encoding.Encoder} encoder The encoder to write data to. * * @private */ _toBinary (encoder) { - encoder.writeUint8(getStructReference(this.constructor)) + encoding.writeUint8(encoder, getStructReference(this.constructor)) let info = 0 if (this._origin !== null) { info += 0b1 // origin is defined @@ -429,10 +444,10 @@ export default class Item { if (this._parentSub !== null) { info += 0b1000 } - encoder.writeUint8(info) - encoder.writeID(this._id) + encoding.writeUint8(encoder, info) + this._id.encode(encoder) if (info & 0b1) { - encoder.writeID(this._origin._lastId) + this._origin._lastId.encode(encoder) } // TODO: remove /* see above @@ -441,14 +456,14 @@ export default class Item { } */ if (info & 0b100) { - encoder.writeID(this._right_origin._id) + this._right_origin._id.encode(encoder) } if ((info & 0b101) === 0) { // neither origin nor right is defined - encoder.writeID(this._parent._id) + this._parent._id.encode(encoder) } if (info & 0b1000) { - encoder.writeVarString(JSON.stringify(this._parentSub)) + encoding.writeVarString(encoder, JSON.stringify(this._parentSub)) } } @@ -458,19 +473,19 @@ export default class Item { * This is called when data is received from a remote peer. * * @param {Y} y The Yjs instance that this Item belongs to. - * @param {BinaryDecoder} decoder The decoder object to read data from. + * @param {decoding.Decoder} decoder The decoder object to read data from. * * @private */ _fromBinary (y, decoder) { let missing = [] - const info = decoder.readUint8() - const id = decoder.readID() + const info = decoding.readUint8(decoder) + const id = ID.decode(decoder) this._id = id // read origin if (info & 0b1) { // origin != null - const originID = decoder.readID() + const originID = ID.decode(decoder) // we have to query for left again because it might have been split/merged.. const origin = y.os.getItemCleanEnd(originID) if (origin === null) { @@ -483,7 +498,7 @@ export default class Item { // read right if (info & 0b100) { // right != null - const rightID = decoder.readID() + const rightID = ID.decode(decoder) // we have to query for right again because it might have been split/merged.. const right = y.os.getItemCleanStart(rightID) if (right === null) { @@ -496,11 +511,11 @@ export default class Item { // read parent if ((info & 0b101) === 0) { // neither origin nor right is defined - const parentID = decoder.readID() + const parentID = ID.decode(decoder) // parent does not change, so we don't have to search for it again if (this._parent === null) { let parent - if (parentID.constructor === RootID) { + if (parentID.constructor === ID.RootID) { parent = y.os.get(parentID) } else { parent = y.os.getItem(parentID) @@ -513,27 +528,17 @@ export default class Item { } } else if (this._parent === null) { if (this._origin !== null) { - if (this._origin.constructor === GC) { - // if origin is a gc, set parent also gc'd - this._parent = this._origin - } else { - this._parent = this._origin._parent - } + this._parent = this._origin._parent } else if (this._right_origin !== null) { - // if origin is a gc, set parent also gc'd - if (this._right_origin.constructor === GC) { - this._parent = this._right_origin - } else { - this._parent = this._right_origin._parent - } + this._parent = this._right_origin._parent } } if (info & 0b1000) { // TODO: maybe put this in read parent condition (you can also read parentsub from left/right) - this._parentSub = JSON.parse(decoder.readVarString()) + this._parentSub = JSON.parse(decoding.readVarString(decoder)) } - if (y.ss.getState(id.user) < id.clock) { - missing.push(new ID(id.user, id.clock - 1)) + if (id instanceof ID.ID && y.ss.getState(id.user) < id.clock) { + missing.push(ID.createID(id.user, id.clock - 1)) } return missing } diff --git a/src/Struct/ItemEmbed.js b/src/Struct/ItemEmbed.js index 892d4cbb..df7c3ee9 100644 --- a/src/Struct/ItemEmbed.js +++ b/src/Struct/ItemEmbed.js @@ -1,5 +1,11 @@ import Item from './Item.js' -import { logItemHelper } from '../MessageHandler/messageToString.js' +import { logItemHelper } from '../message.js' +import * as encoding from '../../lib/encoding.js' +import * as decoding from '../../lib/decoding.js' + +/** + * @typedef {import('../index.js').Y} Y + */ export default class ItemEmbed extends Item { constructor () { @@ -7,21 +13,28 @@ export default class ItemEmbed extends Item { this.embed = null } _copy (undeleteChildren, copyPosition) { - let struct = super._copy(undeleteChildren, copyPosition) + let struct = super._copy() struct.embed = this.embed return struct } get _length () { return 1 } + /** + * @param {Y} y + * @param {decoding.Decoder} decoder + */ _fromBinary (y, decoder) { const missing = super._fromBinary(y, decoder) - this.embed = JSON.parse(decoder.readVarString()) + this.embed = JSON.parse(decoding.readVarString(decoder)) return missing } + /** + * @param {encoding.Encoder} encoder + */ _toBinary (encoder) { super._toBinary(encoder) - encoder.writeVarString(JSON.stringify(this.embed)) + encoding.writeVarString(encoder, JSON.stringify(this.embed)) } /** * Transform this YXml Type to a readable format. diff --git a/src/Struct/ItemFormat.js b/src/Struct/ItemFormat.js index 0efbcde2..9b019364 100644 --- a/src/Struct/ItemFormat.js +++ b/src/Struct/ItemFormat.js @@ -1,5 +1,11 @@ import Item from './Item.js' -import { logItemHelper } from '../MessageHandler/messageToString.js' +import { logItemHelper } from '../message.js' +import * as encoding from '../../lib/encoding.js' +import * as decoding from '../../lib/decoding.js' + +/** + * @typedef {import('../index.js').Y} Y + */ export default class ItemFormat extends Item { constructor () { @@ -8,7 +14,7 @@ export default class ItemFormat extends Item { this.value = null } _copy (undeleteChildren, copyPosition) { - let struct = super._copy(undeleteChildren, copyPosition) + let struct = super._copy() struct.key = this.key struct.value = this.value return struct @@ -19,16 +25,23 @@ export default class ItemFormat extends Item { get _countable () { return false } + /** + * @param {Y} y + * @param {decoding.Decoder} decoder + */ _fromBinary (y, decoder) { const missing = super._fromBinary(y, decoder) - this.key = decoder.readVarString() - this.value = JSON.parse(decoder.readVarString()) + this.key = decoding.readVarString(decoder) + this.value = JSON.parse(decoding.readVarString(decoder)) return missing } + /** + * @param {encoding.Encoder} encoder + */ _toBinary (encoder) { super._toBinary(encoder) - encoder.writeVarString(this.key) - encoder.writeVarString(JSON.stringify(this.value)) + encoding.writeVarString(encoder, this.key) + encoding.writeVarString(encoder, JSON.stringify(this.value)) } /** * Transform this YXml Type to a readable format. diff --git a/src/Struct/ItemJSON.js b/src/Struct/ItemJSON.js index 56e63756..b0dffb20 100644 --- a/src/Struct/ItemJSON.js +++ b/src/Struct/ItemJSON.js @@ -1,5 +1,11 @@ import Item, { splitHelper } from './Item.js' -import { logItemHelper } from '../MessageHandler/messageToString.js' +import { logItemHelper } from '../message.js' +import * as encoding from '../../lib/encoding.js' +import * as decoding from '../../lib/decoding.js' + +/** + * @typedef {import('../index.js').Y} Y + */ export default class ItemJSON extends Item { constructor () { @@ -14,12 +20,16 @@ export default class ItemJSON extends Item { get _length () { return this._content.length } + /** + * @param {Y} y + * @param {decoding.Decoder} decoder + */ _fromBinary (y, decoder) { let missing = super._fromBinary(y, decoder) - let len = decoder.readVarUint() + let len = decoding.readVarUint(decoder) this._content = new Array(len) for (let i = 0; i < len; i++) { - const ctnt = decoder.readVarString() + const ctnt = decoding.readVarString(decoder) let parsed if (ctnt === 'undefined') { parsed = undefined @@ -30,10 +40,13 @@ export default class ItemJSON extends Item { } return missing } + /** + * @param {encoding.Encoder} encoder + */ _toBinary (encoder) { super._toBinary(encoder) let len = this._content.length - encoder.writeVarUint(len) + encoding.writeVarUint(encoder, len) for (let i = 0; i < len; i++) { let encoded let content = this._content[i] @@ -42,7 +55,7 @@ export default class ItemJSON extends Item { } else { encoded = JSON.stringify(content) } - encoder.writeVarString(encoded) + encoding.writeVarString(encoder, encoded) } } /** diff --git a/src/Struct/ItemString.js b/src/Struct/ItemString.js index 40b9123a..ffc38622 100644 --- a/src/Struct/ItemString.js +++ b/src/Struct/ItemString.js @@ -1,5 +1,11 @@ import Item, { splitHelper } from './Item.js' -import { logItemHelper } from '../MessageHandler/messageToString.js' +import { logItemHelper } from '../message.js' +import * as encoding from '../../lib/encoding.js' +import * as decoding from '../../lib/decoding.js' + +/** + * @typedef {import('../index.js').Y} Y + */ export default class ItemString extends Item { constructor () { @@ -14,14 +20,21 @@ export default class ItemString extends Item { get _length () { return this._content.length } + /** + * @param {Y} y + * @param {decoding.Decoder} decoder + */ _fromBinary (y, decoder) { let missing = super._fromBinary(y, decoder) - this._content = decoder.readVarString() + this._content = decoding.readVarString(decoder) return missing } + /** + * @param {encoding.Encoder} encoder + */ _toBinary (encoder) { super._toBinary(encoder) - encoder.writeVarString(this._content) + encoding.writeVarString(encoder, this._content) } /** * Transform this YXml Type to a readable format. diff --git a/src/Struct/Type.js b/src/Struct/Type.js index c8b63395..bdc1d0f7 100644 --- a/src/Struct/Type.js +++ b/src/Struct/Type.js @@ -1,6 +1,11 @@ import Item from './Item.js' import EventHandler from '../Util/EventHandler.js' -import ID from '../Util/ID/ID.js' +import { createID } from '../Util/ID.js' +import YEvent from '../Util/YEvent.js' + +/** + * @typedef {import("../Y.js").default} Y + */ // restructure children as if they were inserted one after another function integrateChildren (y, start) { @@ -22,7 +27,7 @@ export function getListItemIDByPosition (type, i) { if (!n._deleted) { if (pos <= i && i < pos + n._length) { const id = n._id - return new ID(id.user, id.clock + i - pos) + return createID(id.user, id.clock + i - pos) } pos++ } @@ -61,7 +66,7 @@ export default class Type extends Item { * console.log(path) // might look like => [2, 'key1'] * child === type.get(path[0]).get(path[1]) * - * @param {YType} type Type target + * @param {Type | Y | any} type Type target * @return {Array} Path to the target */ getPathTo (type) { @@ -91,6 +96,14 @@ export default class Type extends Item { return path } + /** + * @private + * Creates YArray Event and calls observers. + */ + _callObserver (transaction, parentSubs, remote) { + this._callEventHandler(transaction, new YEvent(this)) + } + /** * @private * Call event listeners with an event. This will also add an event to all @@ -99,6 +112,9 @@ export default class Type extends Item { _callEventHandler (transaction, event) { const changedParentTypes = transaction.changedParentTypes this._eventHandler.callEventListeners(transaction, event) + /** + * @type {any} + */ let type = this while (type !== this._y) { let events = changedParentTypes.get(type) @@ -183,7 +199,7 @@ export default class Type extends Item { this._start = null integrateChildren(y, start) } - // integrate map children + // integrate map children_integrate const map = this._map this._map = new Map() for (let t of map.values()) { @@ -206,6 +222,12 @@ export default class Type extends Item { super._gc(y) } + /** + * @abstract + * @return {Object | Array | number | string} + */ + toJSON () {} + /** * @private * Mark this Item as deleted. @@ -213,7 +235,7 @@ export default class Type extends Item { * @param {Y} y The Yjs instance * @param {boolean} createDelete Whether to propagate a message that this * Type was deleted. - * @param {boolean} [gcChildren=y._hasUndoManager===false] Whether to garbage + * @param {boolean} [gcChildren=(y._hasUndoManager===false)] Whether to garbage * collect the children of this type. */ _delete (y, createDelete, gcChildren) { diff --git a/src/Types/YArray/YArray.js b/src/Types/YArray/YArray.js index 5914dc7b..ffe96017 100644 --- a/src/Types/YArray/YArray.js +++ b/src/Types/YArray/YArray.js @@ -1,9 +1,15 @@ import Type from '../../Struct/Type.js' import ItemJSON from '../../Struct/ItemJSON.js' import ItemString from '../../Struct/ItemString.js' -import { logID, logItemHelper } from '../../MessageHandler/messageToString.js' +import { stringifyItemID, logItemHelper } from '../../message.js' import YEvent from '../../Util/YEvent.js' +/** + * @typedef {import('../../Struct/Item.js').default} Item + * @typedef {import('../../Util/Transaction.js').default} Transaction + * @typedef {import('../../Y.js').default} Y + */ + /** * Event that describes the changes on a YArray * @@ -76,7 +82,7 @@ export default class YArray extends Type { /** * Returns the i-th element from a YArray. * - * @param {Integer} index The index of the element to return from the YArray + * @param {number} index The index of the element to return from the YArray */ get (index) { let n = this._start @@ -112,11 +118,7 @@ export default class YArray extends Type { toJSON () { return this.map(c => { if (c instanceof Type) { - if (c.toJSON !== null) { - return c.toJSON() - } else { - return c.toString() - } + return c.toJSON() } return c }) @@ -211,8 +213,8 @@ export default class YArray extends Type { /** * Deletes elements starting from an index. * - * @param {Integer} index Index at which to start deleting elements - * @param {Integer} length The number of elements to remove. Defaults to 1. + * @param {number} index Index at which to start deleting elements + * @param {number} length The number of elements to remove. Defaults to 1. */ delete (index, length = 1) { this._y.transact(() => { @@ -318,7 +320,7 @@ export default class YArray extends Type { * // Insert numbers 1, 2 at position 1 * yarray.insert(2, [1, 2]) * - * @param {Integer} index The index to insert content at. + * @param {number} index The index to insert content at. * @param {Array} content The array of content */ insert (index, content) { @@ -373,6 +375,6 @@ export default class YArray extends Type { * @private */ _logString () { - return logItemHelper('YArray', this, `start:${logID(this._start)}"`) + return logItemHelper('YArray', this, `start:${stringifyItemID(this._start)}"`) } } diff --git a/src/Types/YMap/YMap.js b/src/Types/YMap/YMap.js index 3af90a5c..d0d226d3 100644 --- a/src/Types/YMap/YMap.js +++ b/src/Types/YMap/YMap.js @@ -1,9 +1,14 @@ import Item from '../../Struct/Item.js' import Type from '../../Struct/Type.js' import ItemJSON from '../../Struct/ItemJSON.js' -import { logItemHelper } from '../../MessageHandler/messageToString.js' +import { logItemHelper } from '../../message.js' import YEvent from '../../Util/YEvent.js' +/** + * @typedef {import('../../Y.js').encodable} encodable + * @typedef {import('../../Struct/Type.js')} YType + */ + /** * Event that describes the changes on a YMap. * diff --git a/src/Types/YText/YText.js b/src/Types/YText/YText.js index a5e5a01d..57cb29d3 100644 --- a/src/Types/YText/YText.js +++ b/src/Types/YText/YText.js @@ -1,7 +1,7 @@ import ItemEmbed from '../../Struct/ItemEmbed.js' import ItemString from '../../Struct/ItemString.js' import ItemFormat from '../../Struct/ItemFormat.js' -import { logItemHelper } from '../../MessageHandler/messageToString.js' +import { logItemHelper } from '../../message.js' import { YArrayEvent, default as YArray } from '../YArray/YArray.js' /** @@ -304,6 +304,9 @@ class YTextEvent extends YArrayEvent { let deleteLen = 0 const addOp = function addOp () { if (action !== null) { + /** + * @type {any} + */ let op switch (action) { case 'delete': @@ -483,6 +486,9 @@ export default class YText extends YArray { */ toString () { let str = '' + /** + * @type {any} + */ let n = this._start while (n !== null) { if (!n._deleted && n._countable) { @@ -529,6 +535,9 @@ export default class YText extends YArray { let ops = [] let currentAttributes = new Map() let str = '' + /** + * @type {any} + */ let n = this._start function packStr () { if (str.length > 0) { @@ -568,12 +577,11 @@ export default class YText extends YArray { /** * Insert text at a given index. * - * @param {Integer} index The index at which to start inserting. + * @param {number} index The index at which to start inserting. * @param {String} text The text to insert at the specified position. * @param {TextAttributes} attributes Optionally define some formatting * information to apply on the inserted * Text. - * * @public */ insert (index, text, attributes = {}) { @@ -589,7 +597,7 @@ export default class YText extends YArray { /** * Inserts an embed at a index. * - * @param {Integer} index The index to insert the embed at. + * @param {number} index The index to insert the embed at. * @param {Object} embed The Object that represents the embed. * @param {TextAttributes} attributes Attribute information to apply on the * embed @@ -609,8 +617,8 @@ export default class YText extends YArray { /** * Deletes text starting from an index. * - * @param {Integer} index Index at which to start deleting. - * @param {Integer} length The number of characters to remove. Defaults to 1. + * @param {number} index Index at which to start deleting. + * @param {number} length The number of characters to remove. Defaults to 1. * * @public */ @@ -627,8 +635,8 @@ export default class YText extends YArray { /** * Assigns properties to a range of text. * - * @param {Integer} index The position where to start formatting. - * @param {Integer} length The amount of characters to assign properties to. + * @param {number} index The position where to start formatting. + * @param {number} length The amount of characters to assign properties to. * @param {TextAttributes} attributes Attribute information to apply on the * text. * diff --git a/src/Types/YXml/YXmlElement.js b/src/Types/YXml/YXmlElement.js index 9c7280f7..fa1e72d2 100644 --- a/src/Types/YXml/YXmlElement.js +++ b/src/Types/YXml/YXmlElement.js @@ -1,6 +1,12 @@ import YMap from '../YMap/YMap.js' import YXmlFragment from './YXmlFragment.js' import { createAssociation } from '../../Bindings/DomBinding/util.js' +import * as encoding from '../../../lib/encoding.js' +import * as decoding from '../../../lib/decoding.js' + +/** + * @typedef {import('../../Y.js').default} Y + */ /** * An YXmlElement imitates the behavior of a @@ -8,8 +14,6 @@ import { createAssociation } from '../../Bindings/DomBinding/util.js' * * * An YXmlElement has attributes (key value pairs) * * An YXmlElement has childElements that must inherit from YXmlElement - * - * @param {String} nodeName Node name */ export default class YXmlElement extends YXmlFragment { constructor (nodeName = 'UNDEFINED') { @@ -34,11 +38,11 @@ export default class YXmlElement extends YXmlFragment { * This is called when data is received from a remote peer. * * @param {Y} y The Yjs instance that this Item belongs to. - * @param {BinaryDecoder} decoder The decoder object to read data from. + * @param {decoding.Decoder} decoder The decoder object to read data from. */ _fromBinary (y, decoder) { const missing = super._fromBinary(y, decoder) - this.nodeName = decoder.readVarString() + this.nodeName = decoding.readVarString(decoder) return missing } @@ -48,13 +52,13 @@ export default class YXmlElement extends YXmlFragment { * * This is called when this Item is sent to a remote peer. * - * @param {BinaryEncoder} encoder The encoder to write data to. + * @param {encoding.Encoder} encoder The encoder to write data to. * * @private */ _toBinary (encoder) { super._toBinary(encoder) - encoder.writeVarString(this.nodeName) + encoding.writeVarString(encoder, this.nodeName) } /** @@ -164,9 +168,9 @@ export default class YXmlElement extends YXmlFragment { * @param {Document} [_document=document] The document object (you must define * this when calling this method in * nodejs) - * @param {Object} [hooks={}] Optional property to customize how hooks + * @param {Object} [hooks={}] Optional property to customize how hooks * are presented in the DOM - * @param {DomBinding} [binding] You should not set this property. This is + * @param {import('../../Bindings/DomBinding/DomBinding.js').default} [binding] You should not set this property. This is * used if DomBinding wants to create a * association to the created DOM type. * @return {Element} The {@link https://developer.mozilla.org/en-US/docs/Web/API/Element|Dom Element} @@ -187,4 +191,12 @@ export default class YXmlElement extends YXmlFragment { } } -YXmlFragment._YXmlElement = YXmlElement +// reassign yxmlfragment to {any} type to prevent warnings +// assign yxmlelement to YXmlFragment so it has a reference to YXmlElement. + +/** + * @type {any} + */ +const _reasgn = YXmlFragment + +_reasgn._YXmlElement = YXmlElement diff --git a/src/Types/YXml/YXmlEvent.js b/src/Types/YXml/YXmlEvent.js index 49d8f680..747a50a0 100644 --- a/src/Types/YXml/YXmlEvent.js +++ b/src/Types/YXml/YXmlEvent.js @@ -1,5 +1,10 @@ import YEvent from '../../Util/YEvent.js' +/** + * @typedef {import('../../Struct/Type.js').default} YType + * @typedef {import('../../Util/Transaction.js').default} Transaction + */ + /** * An Event that describes changes on a YXml Element or Yxml Fragment * diff --git a/src/Types/YXml/YXmlFragment.js b/src/Types/YXml/YXmlFragment.js index 3c450756..61a0864e 100644 --- a/src/Types/YXml/YXmlFragment.js +++ b/src/Types/YXml/YXmlFragment.js @@ -3,7 +3,13 @@ import YXmlTreeWalker from './YXmlTreeWalker.js' import YArray from '../YArray/YArray.js' import YXmlEvent from './YXmlEvent.js' -import { logItemHelper } from '../../MessageHandler/messageToString.js' +import { logItemHelper } from '../../message.js' + +/** + * @typedef {import('./YXmlElement.js').default} YXmlElement + * @typedef {import('../../Bindings/DomBinding/DomBinding.js').default} DomBinding + * @typedef {import('../../Y.js').default} Y + */ /** * Dom filter function. @@ -48,7 +54,7 @@ export default class YXmlFragment extends YArray { * @param {Function} filter Function that is called on each child element and * returns a Boolean indicating whether the child * is to be included in the subtree. - * @return {TreeWalker} A subtree and a position within it. + * @return {YXmlTreeWalker} A subtree and a position within it. * * @public */ @@ -67,7 +73,7 @@ export default class YXmlFragment extends YArray { * - attribute * * @param {CSS_Selector} query The query on the children. - * @return {?YXmlElement} The first element that matches the query or null. + * @return {?import('./YXmlElement.js')} The first element that matches the query or null. * * @public */ @@ -116,29 +122,13 @@ export default class YXmlFragment extends YArray { return this.map(xml => xml.toString()).join('') } - /** - * @private - * Unbind from Dom and mark this Item as deleted. - * - * @param {Y} y The Yjs instance - * @param {boolean} createDelete Whether to propagate a message that this - * Type was deleted. - * @param {boolean} [gcChildren=y._hasUndoManager===false] Whether to garbage - * collect the children of this type. - * - * @private - */ - _delete (y, createDelete, gcChildren) { - super._delete(y, createDelete, gcChildren) - } - /** * Creates a Dom Element that mirrors this YXmlElement. * * @param {Document} [_document=document] The document object (you must define * this when calling this method in * nodejs) - * @param {Object} [hooks={}] Optional property to customize how hooks + * @param {Object.} [hooks={}] Optional property to customize how hooks * are presented in the DOM * @param {DomBinding} [binding] You should not set this property. This is * used if DomBinding wants to create a diff --git a/src/Types/YXml/YXmlHook.js b/src/Types/YXml/YXmlHook.js index 08ad5935..4bbb3d51 100644 --- a/src/Types/YXml/YXmlHook.js +++ b/src/Types/YXml/YXmlHook.js @@ -1,5 +1,12 @@ import YMap from '../YMap/YMap.js' import { createAssociation } from '../../Bindings/DomBinding/util.js' +import * as encoding from '../../../lib/encoding.js' +import * as decoding from '../../../lib/decoding.js' + +/** + * @typedef {import('../../Bindings/DomBinding/DomBinding.js').default} DomBinding + * @typedef {import('../../Y.js').default} Y + */ /** * You can manage binding to a custom type with YXmlHook. @@ -35,7 +42,7 @@ export default class YXmlHook extends YMap { * @param {Document} [_document=document] The document object (you must define * this when calling this method in * nodejs) - * @param {Object} [hooks] Optional property to customize how hooks + * @param {Object.} [hooks] Optional property to customize how hooks * are presented in the DOM * @param {DomBinding} [binding] You should not set this property. This is * used if DomBinding wants to create a @@ -63,13 +70,13 @@ export default class YXmlHook extends YMap { * This is called when data is received from a remote peer. * * @param {Y} y The Yjs instance that this Item belongs to. - * @param {BinaryDecoder} decoder The decoder object to read data from. + * @param {decoding.Decoder} decoder The decoder object to read data from. * * @private */ _fromBinary (y, decoder) { const missing = super._fromBinary(y, decoder) - this.hookName = decoder.readVarString() + this.hookName = decoding.readVarString(decoder) return missing } @@ -79,13 +86,13 @@ export default class YXmlHook extends YMap { * * This is called when this Item is sent to a remote peer. * - * @param {BinaryEncoder} encoder The encoder to write data to. + * @param {encoding.Encoder} encoder The encoder to write data to. * * @private */ _toBinary (encoder) { super._toBinary(encoder) - encoder.writeVarString(this.hookName) + encoding.writeVarString(encoder, this.hookName) } /** diff --git a/src/Types/YXml/YXmlText.js b/src/Types/YXml/YXmlText.js index 4c34c982..34baade4 100644 --- a/src/Types/YXml/YXmlText.js +++ b/src/Types/YXml/YXmlText.js @@ -1,6 +1,11 @@ import YText from '../YText/YText.js' import { createAssociation } from '../../Bindings/DomBinding/util.js' +/** + * @typedef {import('../../Bindings/DomBinding/DomBinding.js').default} DomBinding + * @typedef {import('../../index.js').Y} Y + */ + /** * Represents text in a Dom Element. In the future this type will also handle * simple formatting information like bold and italic. @@ -14,12 +19,12 @@ export default class YXmlText extends YText { * @param {Document} [_document=document] The document object (you must define * this when calling this method in * nodejs) - * @param {Object} [hooks] Optional property to customize how hooks + * @param {Object} [hooks] Optional property to customize how hooks * are presented in the DOM * @param {DomBinding} [binding] You should not set this property. This is * used if DomBinding wants to create a * association to the created DOM type. - * @return {Element} The {@link https://developer.mozilla.org/en-US/docs/Web/API/Element|Dom Element} + * @return {Text} The {@link https://developer.mozilla.org/en-US/docs/Web/API/Element|Dom Element} * * @public */ diff --git a/src/Types/YXml/YXmlTreeWalker.js b/src/Types/YXml/YXmlTreeWalker.js index 1ee724e6..6b0dc992 100644 --- a/src/Types/YXml/YXmlTreeWalker.js +++ b/src/Types/YXml/YXmlTreeWalker.js @@ -33,7 +33,7 @@ export default class YXmlTreeWalker { /** * Get the next node. * - * @return {YXmlElement} The next node. + * @return {import('./YXmlElement.js').default} The next node. * * @public */ diff --git a/src/Util/ID.js b/src/Util/ID.js new file mode 100644 index 00000000..da022bbe --- /dev/null +++ b/src/Util/ID.js @@ -0,0 +1,90 @@ +import { getStructReference } from './structReferences.js' +import * as decoding from '../../lib/decoding.js' +import * as encoding from '../../lib/encoding.js' + +export class ID { + constructor (user, clock) { + this.user = user // TODO: rename to client + this.clock = clock + } + clone () { + return new ID(this.user, this.clock) + } + equals (id) { + return id !== null && id.user === this.user && id.clock === this.clock + } + lessThan (id) { + if (id.constructor === ID) { + return this.user < id.user || (this.user === id.user && this.clock < id.clock) + } else { + return false + } + } + /** + * @param {encoding.Encoder} encoder + */ + encode (encoder) { + encoding.writeVarUint(encoder, this.user) + encoding.writeVarUint(encoder, this.clock) + } +} + +export const createID = (user, clock) => new ID(user, clock) + +export const RootFakeUserID = 0xFFFFFF + +export class RootID { + constructor (name, typeConstructor) { + this.user = RootFakeUserID + this.name = name + this.type = getStructReference(typeConstructor) + } + equals (id) { + return id !== null && id.user === this.user && id.name === this.name && id.type === this.type + } + lessThan (id) { + if (id.constructor === RootID) { + return this.user < id.user || (this.user === id.user && (this.name < id.name || (this.name === id.name && this.type < id.type))) + } else { + return true + } + } + /** + * @param {encoding.Encoder} encoder + */ + encode (encoder) { + encoding.writeVarUint(encoder, this.user) + encoding.writeVarString(encoder, this.name) + encoding.writeVarUint(encoder, this.type) + } +} + +/** + * Create a new root id. + * + * @example + * y.define('name', Y.Array) // name, and typeConstructor + * + * @param {string} name + * @param {Function} typeConstructor must be defined in structReferences + */ +export const createRootID = (name, typeConstructor) => new RootID(name, typeConstructor) + +/** + * Read ID. + * * If first varUint read is 0xFFFFFF a RootID is returned. + * * Otherwise an ID is returned + * + * @param {decoding.Decoder} decoder + * @return {ID|RootID} + */ +export const decode = decoder => { + const user = decoding.readVarUint(decoder) + if (user === RootFakeUserID) { + // read property name and type id + const rid = createRootID(decoding.readVarString(decoder), null) + rid.type = decoding.readVarUint(decoder) + return rid + } + return createID(user, decoding.readVarUint(decoder)) +} diff --git a/src/Util/ID/ID.js b/src/Util/ID/ID.js deleted file mode 100644 index 1708c120..00000000 --- a/src/Util/ID/ID.js +++ /dev/null @@ -1,20 +0,0 @@ - -export default class ID { - constructor (user, clock) { - this.user = user // TODO: rename to client - this.clock = clock - } - clone () { - return new ID(this.user, this.clock) - } - equals (id) { - return id !== null && id.user === this.user && id.clock === this.clock - } - lessThan (id) { - if (id.constructor === ID) { - return this.user < id.user || (this.user === id.user && this.clock < id.clock) - } else { - return false - } - } -} diff --git a/src/Util/ID/RootID.js b/src/Util/ID/RootID.js deleted file mode 100644 index 9e4672c5..00000000 --- a/src/Util/ID/RootID.js +++ /dev/null @@ -1,21 +0,0 @@ -import { getStructReference } from '../structReferences.js' - -export const RootFakeUserID = 0xFFFFFF - -export default class RootID { - constructor (name, typeConstructor) { - this.user = RootFakeUserID - this.name = name - this.type = getStructReference(typeConstructor) - } - equals (id) { - return id !== null && id.user === this.user && id.name === this.name && id.type === this.type - } - lessThan (id) { - if (id.constructor === RootID) { - return this.user < id.user || (this.user === id.user && (this.name < id.name || (this.name === id.name && this.type < id.type))) - } else { - return true - } - } -} diff --git a/src/Util/Transaction.js b/src/Util/Transaction.js index 3642b3c8..2507e431 100644 --- a/src/Util/Transaction.js +++ b/src/Util/Transaction.js @@ -1,4 +1,10 @@ -import BinaryEncoder from './Binary/Encoder.js' +import * as encoding from '../../lib/encoding.js' +/** + * @typedef {import("../Y.js").default} Y + * @typedef {import("../Struct/Type.js").default} YType + * @typedef {import("../Struct/Item.js").default} Item + * @typedef {import("./YEvent.js").default} YEvent + */ /** * A transaction is created for every change on the Yjs model. It is possible @@ -26,7 +32,7 @@ import BinaryEncoder from './Binary/Encoder.js' export default class Transaction { constructor (y) { /** - * @type {Y} The Yjs instance. + * @type {import("../Y.js")} The Yjs instance. */ this.y = y /** @@ -38,7 +44,7 @@ export default class Transaction { * All types that were directly modified (property added or child * inserted/deleted). New types are not included in this Set. * Maps from type to parentSubs (`item._parentSub = null` for YArray) - * @type {Set} + * @type {Map} */ this.changedTypes = new Map() // TODO: rename deletedTypes @@ -60,7 +66,7 @@ export default class Transaction { */ this.changedParentTypes = new Map() this.encodedStructsLen = 0 - this.encodedStructs = new BinaryEncoder() + this.encodedStructs = encoding.createEncoder() } } diff --git a/src/Util/UndoManager.js b/src/Util/UndoManager.js index 9b8dce50..93bba58a 100644 --- a/src/Util/UndoManager.js +++ b/src/Util/UndoManager.js @@ -1,4 +1,4 @@ -import ID from './ID/ID.js' +import * as ID from './ID.js' import isParentOf from './isParentOf.js' class ReverseOperation { @@ -6,8 +6,8 @@ class ReverseOperation { this.created = new Date() const beforeState = transaction.beforeState if (beforeState.has(y.userID)) { - this.toState = new ID(y.userID, y.ss.getState(y.userID) - 1) - this.fromState = new ID(y.userID, beforeState.get(y.userID)) + this.toState = ID.createID(y.userID, y.ss.getState(y.userID) - 1) + this.fromState = ID.createID(y.userID, beforeState.get(y.userID)) } else { this.toState = null this.fromState = null @@ -28,7 +28,7 @@ class ReverseOperation { function applyReverseOperation (y, scope, reverseBuffer) { let performedUndo = false - let undoOp + let undoOp = null y.transact(() => { while (!performedUndo && reverseBuffer.length > 0) { undoOp = reverseBuffer.pop() @@ -49,7 +49,7 @@ function applyReverseOperation (y, scope, reverseBuffer) { const redoitems = new Set() for (let del of undoOp.deletedStructs) { const fromState = del.from - const toState = new ID(fromState.user, fromState.clock + del.len - 1) + const toState = ID.createID(fromState.user, fromState.clock + del.len - 1) y.os.getItemCleanStart(fromState) y.os.getItemCleanEnd(toState) y.os.iterate(fromState, toState, op => { @@ -73,7 +73,7 @@ function applyReverseOperation (y, scope, reverseBuffer) { }) } }) - if (performedUndo) { + if (performedUndo && undoOp !== null) { // should be performed after the undo transaction undoOp.bindingInfos.forEach((info, binding) => { binding._restoreUndoStackInfo(info) diff --git a/src/Util/YEvent.js b/src/Util/YEvent.js index 0775527f..92a1939e 100644 --- a/src/Util/YEvent.js +++ b/src/Util/YEvent.js @@ -1,3 +1,8 @@ +/** + * @typedef {import("../Y.js").default} Y + * @typedef {import("../Struct/Type.js").default} YType + * @typedef {import("../Struct/Item.js").default} Item + */ /** * YEvent describes the changes on a YType. diff --git a/src/Util/defragmentItemContent.js b/src/Util/defragmentItemContent.js index 586c33f1..537098b6 100644 --- a/src/Util/defragmentItemContent.js +++ b/src/Util/defragmentItemContent.js @@ -1,5 +1,5 @@ -import ID from '../Util/ID/ID.js' +import * as ID from '../Util/ID.js' import ItemJSON from '../Struct/ItemJSON.js' import ItemString from '../Struct/ItemString.js' @@ -32,7 +32,7 @@ export function defragmentItemContent (y) { a.constructor === b.constructor && a._deleted === b._deleted && a._right === b && - (new ID(a._id.user, a._id.clock + a._length)).equals(b._id) + (ID.createID(a._id.user, a._id.clock + a._length)).equals(b._id) ) { a._right = b._right if (a instanceof ItemJSON) { diff --git a/src/Util/generateRandomUint32.js b/src/Util/generateRandomUint32.js index 23ac9b27..0342aaf7 100644 --- a/src/Util/generateRandomUint32.js +++ b/src/Util/generateRandomUint32.js @@ -1,7 +1,7 @@ /* global crypto */ export function generateRandomUint32 () { - if (typeof crypto !== 'undefined' && crypto.getRandomValue != null) { + if (typeof crypto !== 'undefined' && crypto.getRandomValues != null) { // browser let arr = new Uint32Array(1) crypto.getRandomValues(arr) diff --git a/src/MessageHandler/integrateRemoteStructs.js b/src/Util/integrateRemoteStructs.js similarity index 79% rename from src/MessageHandler/integrateRemoteStructs.js rename to src/Util/integrateRemoteStructs.js index 0259dc39..d9904836 100644 --- a/src/MessageHandler/integrateRemoteStructs.js +++ b/src/Util/integrateRemoteStructs.js @@ -1,8 +1,12 @@ import { getStruct } from '../Util/structReferences.js' -import BinaryDecoder from '../Util/Binary/Decoder.js' -import { logID } from './messageToString.js' +import * as decoding from '../../lib/decoding.js' import GC from '../Struct/GC.js' +/** + * @typedef {import('../index').Y} Y + * @typedef {import('../Struct/Item.js').default} YItem + */ + class MissingEntry { constructor (decoder, missing, struct) { this.decoder = decoder @@ -16,6 +20,8 @@ class MissingEntry { * Integrate remote struct * When a remote struct is integrated, other structs might be ready to ready to * integrate. + * @param {Y} y + * @param {YItem} struct */ function _integrateRemoteStructHelper (y, struct) { const id = struct._id @@ -57,29 +63,21 @@ function _integrateRemoteStructHelper (y, struct) { msu.delete(clock) } } + if (msu.size === 0) { + y._missingStructs.delete(id.user) + } } } } -export function stringifyStructs (y, decoder, strBuilder) { - const len = decoder.readUint32() +/** + * @param {decoding.Decoder} decoder + * @param {Y} y + */ +export function integrateRemoteStructs (decoder, y) { + const len = decoding.readUint32(decoder) for (let i = 0; i < len; i++) { - let reference = decoder.readVarUint() - let Constr = getStruct(reference) - let struct = new Constr() - let missing = struct._fromBinary(y, decoder) - let logMessage = ' ' + struct._logString() - if (missing.length > 0) { - logMessage += ' .. missing: ' + missing.map(logID).join(', ') - } - strBuilder.push(logMessage) - } -} - -export function integrateRemoteStructs (y, decoder) { - const len = decoder.readUint32() - for (let i = 0; i < len; i++) { - let reference = decoder.readVarUint() + let reference = decoding.readVarUint(decoder) let Constr = getStruct(reference) let struct = new Constr() let decoderPos = decoder.pos @@ -90,7 +88,7 @@ export function integrateRemoteStructs (y, decoder) { struct = y._readyToIntegrate.shift() } } else { - let _decoder = new BinaryDecoder(decoder.uint8arr) + let _decoder = decoding.createDecoder(decoder.arr.buffer) _decoder.pos = decoderPos let missingEntry = new MissingEntry(_decoder, missing, struct) let missingStructs = y._missingStructs @@ -111,8 +109,12 @@ export function integrateRemoteStructs (y, decoder) { } // TODO: use this above / refactor -export function integrateRemoteStruct (y, decoder) { - let reference = decoder.readVarUint() +/** + * @param {decoding.Decoder} decoder + * @param {Y} y + */ +export function integrateRemoteStruct (decoder, y) { + let reference = decoding.readVarUint(decoder) let Constr = getStruct(reference) let struct = new Constr() let decoderPos = decoder.pos @@ -123,7 +125,7 @@ export function integrateRemoteStruct (y, decoder) { struct = y._readyToIntegrate.shift() } } else { - let _decoder = new BinaryDecoder(decoder.uint8arr) + let _decoder = decoding.createDecoder(decoder.arr.buffer) _decoder.pos = decoderPos let missingEntry = new MissingEntry(_decoder, missing, struct) let missingStructs = y._missingStructs diff --git a/src/Util/isParentOf.js b/src/Util/isParentOf.js index f56b5f66..c2eee374 100644 --- a/src/Util/isParentOf.js +++ b/src/Util/isParentOf.js @@ -1,9 +1,14 @@ +/** + * @typedef {import('../Struct/Type.js').default} YType + * @typedef {import('../Y.js').default} Y + */ + /** * Check if `parent` is a parent of `child`. * - * @param {Type} parent - * @param {Type} child + * @param {YType | Y} parent + * @param {YType | Y} child * @return {Boolean} Whether `parent` is a parent of `child`. * * @public diff --git a/src/Util/relativePosition.js b/src/Util/relativePosition.js index 421a5d2f..e11720bf 100644 --- a/src/Util/relativePosition.js +++ b/src/Util/relativePosition.js @@ -1,5 +1,4 @@ -import ID from './ID/ID.js' -import RootID from './ID/RootID.js' +import * as ID from './ID.js' import GC from '../Struct/GC.js' // TODO: Implement function to describe ranges @@ -72,9 +71,9 @@ export function fromRelativePosition (y, rpos) { if (rpos[0] === 'endof') { let id if (rpos[3] === null) { - id = new ID(rpos[1], rpos[2]) + id = ID.createID(rpos[1], rpos[2]) } else { - id = new RootID(rpos[3], rpos[4]) + id = ID.createRootID(rpos[3], rpos[4]) } let type = y.os.get(id) while (type._redone !== null) { @@ -89,7 +88,7 @@ export function fromRelativePosition (y, rpos) { } } else { let offset = 0 - let struct = y.os.findNodeWithUpperBound(new ID(rpos[0], rpos[1])).val + let struct = y.os.findNodeWithUpperBound(ID.createID(rpos[0], rpos[1])).val const diff = rpos[1] - struct._id.clock while (struct._redone !== null) { struct = struct._redone diff --git a/src/Util/structReferences.js b/src/Util/structReferences.js index 819c9ada..4d1f40a6 100644 --- a/src/Util/structReferences.js +++ b/src/Util/structReferences.js @@ -1,18 +1,3 @@ -import Delete from '../Struct/Delete.js' -import ItemJSON from '../Struct/ItemJSON.js' -import ItemString from '../Struct/ItemString.js' -import ItemFormat from '../Struct/ItemFormat.js' -import ItemEmbed from '../Struct/ItemEmbed.js' -import GC from '../Struct/GC.js' - -import YArray from '../Types/YArray/YArray.js' -import YMap from '../Types/YMap/YMap.js' -import YText from '../Types/YText/YText.js' -import YXmlText from '../Types/YXml/YXmlText.js' -import YXmlHook from '../Types/YXml/YXmlHook.js' -import YXmlFragment from '../Types/YXml/YXmlFragment.js' -import YXmlElement from '../Types/YXml/YXmlElement.js' - const structs = new Map() const references = new Map() @@ -21,7 +6,7 @@ const references = new Map() * reference on all clients! * * @param {Number} reference - * @param {class} structConstructor + * @param {Function} structConstructor * * @public */ @@ -43,20 +28,3 @@ export function getStruct (reference) { export function getStructReference (typeConstructor) { return references.get(typeConstructor) } - -// TODO: reorder (Item* should have low numbers) -registerStruct(0, ItemJSON) -registerStruct(1, ItemString) -registerStruct(10, ItemFormat) -registerStruct(11, ItemEmbed) -registerStruct(2, Delete) - -registerStruct(3, YArray) -registerStruct(4, YMap) -registerStruct(5, YText) -registerStruct(6, YXmlFragment) -registerStruct(7, YXmlElement) -registerStruct(8, YXmlText) -registerStruct(9, YXmlHook) - -registerStruct(12, GC) diff --git a/src/Y.dist.js b/src/Y.dist.js deleted file mode 100644 index c1d5ca5b..00000000 --- a/src/Y.dist.js +++ /dev/null @@ -1,54 +0,0 @@ - -export { default as Y } from './Y.js' -export { default as UndoManager } from './Util/UndoManager.js' -export { integrateRemoteStructs } from './MessageHandler/integrateRemoteStructs.js' - -import Connector from './Connector.js' -import Persistence from './Persistence.js' -import YArray from './Types/YArray/YArray.js' -import YMap from './Types/YMap/YMap.js' -import YText from './Types/YText/YText.js' -import YXmlText from './Types/YXml/YXmlText.js' -import YXmlHook from './Types/YXml/YXmlHook.js' -import YXmlFragment from './Types/YXml/YXmlFragment.js' -import YXmlElement from './Types/YXml/YXmlElement.js' -import BinaryDecoder from './Util/Binary/Decoder.js' -import { getRelativePosition, fromRelativePosition } from './Util/relativePosition.js' -import { registerStruct } from './Util/structReferences.js' -import TextareaBinding from './Bindings/TextareaBinding/TextareaBinding.js' -import QuillBinding from './Bindings/QuillBinding/QuillBinding.js' -import DomBinding from './Bindings/DomBinding/DomBinding.js' -import { toBinary, fromBinary } from './MessageHandler/binaryEncode.js' - -import domToType from './Bindings/DomBinding/domToType.js' -import { domsToTypes, switchAssociation } from './Bindings/DomBinding/util.js' - -// TODO: The following assignments should be moved to yjs-dist -Y.AbstractConnector = Connector -Y.AbstractPersistence = Persistence -Y.Array = YArray -Y.Map = YMap -Y.Text = YText -Y.XmlElement = YXmlElement -Y.XmlFragment = YXmlFragment -Y.XmlText = YXmlText -Y.XmlHook = YXmlHook - -Y.TextareaBinding = TextareaBinding -Y.QuillBinding = QuillBinding -Y.DomBinding = DomBinding - -DomBinding.domToType = domToType -DomBinding.domsToTypes = domsToTypes -DomBinding.switchAssociation = switchAssociation - -Y.utils = { - BinaryDecoder, - UndoManager, - getRelativePosition, - fromRelativePosition, - registerStruct, - integrateRemoteStructs, - toBinary, - fromBinary -} diff --git a/src/Y.js b/src/Y.js index ce999b77..2287f531 100644 --- a/src/Y.js +++ b/src/Y.js @@ -2,11 +2,17 @@ import DeleteStore from './Store/DeleteStore.js' import OperationStore from './Store/OperationStore.js' import StateStore from './Store/StateStore.js' import { generateRandomUint32 } from './Util/generateRandomUint32.js' -import RootID from './Util/ID/RootID.js' +import { createRootID } from './Util/ID.js' import NamedEventHandler from '../lib/NamedEventHandler.js' import Transaction from './Util/Transaction.js' +import * as encoding from '../lib/encoding.js' +import * as message from './message.js' +import { integrateRemoteStructs } from './Util/integrateRemoteStructs.js' -export { default as DomBinding } from './Bindings/DomBinding/DomBinding.js' +/** + * @typedef {import('./Struct/Type.js').default} YType + * @typedef {import('../lib/decoding.js').Decoder} Decoder + */ /** * Anything that can be encoded with `JSON.stringify` and can be decoded with @@ -17,18 +23,17 @@ export { default as DomBinding } from './Bindings/DomBinding/DomBinding.js' * * At the moment the only safe values are number and string. * - * @typedef {(number|string)} encodable + * @typedef {(number|string|Object)} encodable */ /** * A Yjs instance handles the state of shared data. * * @param {string} room Users in the same room share the same content - * @param {Object} opts Connector definition - * @param {AbstractPersistence} persistence Persistence adapter instance + * @param {Object} conf configuration */ export default class Y extends NamedEventHandler { - constructor (room, connector, persistence, conf = {}) { + constructor (room, conf = {}) { super() this.gcEnabled = conf.gc || false /** @@ -39,60 +44,46 @@ export default class Y extends NamedEventHandler { this._contentReady = false this.userID = generateRandomUint32() // TODO: This should be a Map so we can use encodables as keys - this.share = {} - this.ds = new DeleteStore(this) + this._map = new Map() + this.ds = new DeleteStore() this.os = new OperationStore(this) this.ss = new StateStore(this) this._missingStructs = new Map() this._readyToIntegrate = [] this._transaction = null - /** - * The {@link AbstractConnector}.that is used by this Yjs instance. - * @type {AbstractConnector} - */ - this.connector = null this.connected = false - let initConnection = () => { - if (connector != null) { - if (connector.constructor === Object) { - connector.connector.room = room - this.connector = new Y[connector.connector.name](this, connector.connector) - this.connected = true - this.emit('connectorReady') - } - } - } - /** - * The {@link AbstractPersistence} that is used by this Yjs instance. - * @type {AbstractPersistence} - */ - this.persistence = null - if (persistence != null) { - this.persistence = persistence - persistence._init(this).then(initConnection) - } else { - initConnection() - } // for compatibility with isParentOf this._parent = null this._hasUndoManager = false + this._deleted = false // for compatiblity of having this as a parent for types + this._id = null } - _setContentReady () { - if (!this._contentReady) { - this._contentReady = true - this.emit('content') - } + + /** + * Read the Decoder and fill the Yjs instance with data in the decoder. + * + * @param {Decoder} decoder The BinaryDecoder to read from. + */ + importModel (decoder) { + this.transact(function () { + integrateRemoteStructs(decoder, this) + message.readDeleteSet(decoder, this) + }) } - whenContentReady () { - if (this._contentReady) { - return Promise.resolve() - } else { - return new Promise(resolve => { - this.once('content', resolve) - }) - } + + /** + * Encode the Yjs model to ArrayBuffer + * + * @return {ArrayBuffer} The Yjs model as ArrayBuffer + */ + exportModel () { + const encoder = encoding.createEncoder() + message.writeStructs(encoder, this, new Map()) + message.writeDeleteSet(encoder, this) + return encoding.toBuffer(encoder) } _beforeChange () {} + _callObserver (transaction, subs, remote) {} /** * Changes that happen inside of a transaction are bundled. This means that * the observer fires _after_ the transaction is finished and that all changes @@ -157,9 +148,7 @@ export default class Y extends NamedEventHandler { * @private * Fake _start for root properties (y.set('name', type)) */ - set _start (start) { - return null - } + set _start (start) {} /** * Define a shared data type. @@ -168,7 +157,7 @@ export default class Y extends NamedEventHandler { * and do not overwrite each other. I.e. * `y.define(name, type) === y.define(name, type)` * - * After this method is called, the type is also available on `y.share[name]`. + * After this method is called, the type is also available on `y._map.get(name)`. * * *Best Practices:* * Either define all types right after the Yjs instance is created or always @@ -179,7 +168,7 @@ export default class Y extends NamedEventHandler { * const y = new Y(..) * y.define('myArray', YArray) * y.define('myMap', YMap) - * // .. when accessing the type use y.share[name] + * // .. when accessing the type use y._map.get(name) * y.share.myArray.insert(..) * y.share.myMap.set(..) * @@ -190,15 +179,15 @@ export default class Y extends NamedEventHandler { * y.define('myMap', YMap).set(..) * * @param {String} name - * @param {YType Constructor} TypeConstructor The constructor of the type definition - * @returns {YType} The created type + * @param {Function} TypeConstructor The constructor of the type definition + * @returns {YType} The created type. Constructed with TypeConstructor */ define (name, TypeConstructor) { - let id = new RootID(name, TypeConstructor) + let id = createRootID(name, TypeConstructor) let type = this.os.get(id) - if (this.share[name] === undefined) { - this.share[name] = type - } else if (this.share[name] !== type) { + if (this._map.get(name) === undefined) { + this._map.set(name, type) + } else if (this._map.get(name) !== type) { throw new Error('Type is already defined with a different constructor') } return type @@ -213,66 +202,18 @@ export default class Y extends NamedEventHandler { * @param {String} name The typename */ get (name) { - return this.share[name] - } - - /** - * Disconnect this Yjs Instance from the network. The connector will - * unsubscribe from the room and document updates are not shared anymore. - */ - disconnect () { - if (this.connected) { - this.connected = false - return this.connector.disconnect() - } else { - return Promise.resolve() - } - } - - /** - * If disconnected, tell the connector to reconnect to the room. - */ - reconnect () { - if (!this.connected) { - this.connected = true - return this.connector.reconnect() - } else { - return Promise.resolve() - } + return this._map.get(name) } /** * Disconnect from the room, and destroy all traces of this Yjs instance. - * Persisted data will remain until removed by the persistence adapter. */ destroy () { this.emit('destroyed', true) super.destroy() - this.share = null - if (this.connector != null) { - if (this.connector.destroy != null) { - this.connector.destroy() - } else { - this.connector.disconnect() - } - } - if (this.persistence !== null) { - this.persistence.deinit(this) - this.persistence = null - } + this._map = null this.os = null this.ds = null this.ss = null } } - -Y.extend = function extendYjs () { - for (var i = 0; i < arguments.length; i++) { - var f = arguments[i] - if (typeof f === 'function') { - f(Y) - } else { - throw new Error('Expected a function!') - } - } -} diff --git a/src/YdbClient/YdbClient.js b/src/YdbClient/YdbClient.js index e540a3c5..abf240c7 100644 --- a/src/YdbClient/YdbClient.js +++ b/src/YdbClient/YdbClient.js @@ -1,15 +1,15 @@ /* eslint-env browser */ import * as idbactions from './idbactions.js' -import * as globals from './globals.js' +import * as globals from '../../lib/globals.js' import * as message from './message.js' import * as bc from './broadcastchannel.js' -import * as encoding from './encoding.js' -import * as logging from './logging.js' -import * as idb from './idb.js' -import Y from '../src/Y.js' -import BinaryDecoder from '../src/Util/Binary/Decoder.js' -import { integrateRemoteStruct } from '../src/MessageHandler/integrateRemoteStructs.js' -import { createMutualExclude } from '../src/Util/mutualExclude.js' +import * as encoding from '../../lib/encoding.js' +import * as logging from '../../lib/logging.js' +import * as idb from '../../lib/idb.js' +import * as decoding from '../../lib/decoding.js' +import Y from '../Y.js' +import { integrateRemoteStruct } from '../MessageHandler/integrateRemoteStructs.js' +import { createMutualExclude } from '../../lib/mutualExclude.js' import * as NamedEventHandler from './NamedEventHandler.js' @@ -70,8 +70,8 @@ export class YdbClient extends NamedEventHandler.Class { })) subscribe(this, roomname, update => mutex(() => { y.transact(() => { - const decoder = new BinaryDecoder(update) - while (decoder.hasContent()) { + const decoder = decoding.createDecoder(update) + while (decoding.hasContent(decoder)) { integrateRemoteStruct(y, decoder) } }, true) diff --git a/src/YdbClient/idbactions.js b/src/YdbClient/idbactions.js index b3e78b40..9bf99a1f 100644 --- a/src/YdbClient/idbactions.js +++ b/src/YdbClient/idbactions.js @@ -29,10 +29,10 @@ * - A client may update a room when the room is in either US or Co */ -import * as encoding from './encoding.js' -import * as decoding from './decoding.js' -import * as idb from './idb.js' -import * as globals from './globals.js' +import * as encoding from '../../lib/encoding.js' +import * as decoding from '../../lib/decoding.js' +import * as idb from '../../lib/idb.js' +import * as globals from '../../lib/globals.js' import * as message from './message.js' /** diff --git a/src/index.js b/src/index.js new file mode 100644 index 00000000..78202a1b --- /dev/null +++ b/src/index.js @@ -0,0 +1,55 @@ + +import Delete from './Struct/Delete.js' +import ItemJSON from './Struct/ItemJSON.js' +import ItemString from './Struct/ItemString.js' +import ItemFormat from './Struct/ItemFormat.js' +import ItemEmbed from './Struct/ItemEmbed.js' +import GC from './Struct/GC.js' + +import YArray from './Types/YArray/YArray.js' +import YMap from './Types/YMap/YMap.js' +import YText from './Types/YText/YText.js' +import YXmlText from './Types/YXml/YXmlText.js' +import YXmlHook from './Types/YXml/YXmlHook.js' +import YXmlFragment from './Types/YXml/YXmlFragment.js' +import YXmlElement from './Types/YXml/YXmlElement.js' + +import { registerStruct } from './Util/structReferences.js' + +export { default as Y } from './Y.js' +export { default as UndoManager } from './Util/UndoManager.js' +export { default as Transaction } from './Util/Transaction.js' + +export { default as Array } from './Types/YArray/YArray.js' +export { default as Map } from './Types/YMap/YMap.js' +export { default as Text } from './Types/YText/YText.js' +export { default as XmlText } from './Types/YXml/YXmlText.js' +export { default as XmlHook } from './Types/YXml/YXmlHook.js' +export { default as XmlFragment } from './Types/YXml/YXmlFragment.js' +export { default as XmlElement } from './Types/YXml/YXmlElement.js' + +export { getRelativePosition, fromRelativePosition } from './Util/relativePosition.js' +export { registerStruct as registerType } from './Util/structReferences.js' +export { default as TextareaBinding } from './Bindings/TextareaBinding/TextareaBinding.js' +export { default as QuillBinding } from './Bindings/QuillBinding/QuillBinding.js' +export { default as DomBinding } from './Bindings/DomBinding/DomBinding.js' + +export { default as domToType } from './Bindings/DomBinding/domToType.js' +export { domsToTypes, switchAssociation } from './Bindings/DomBinding/util.js' + +// TODO: reorder (Item* should have low numbers) +registerStruct(0, ItemJSON) +registerStruct(1, ItemString) +registerStruct(10, ItemFormat) +registerStruct(11, ItemEmbed) +registerStruct(2, Delete) + +registerStruct(3, YArray) +registerStruct(4, YMap) +registerStruct(5, YText) +registerStruct(6, YXmlFragment) +registerStruct(7, YXmlElement) +registerStruct(8, YXmlText) +registerStruct(9, YXmlHook) + +registerStruct(12, GC) diff --git a/src/message.js b/src/message.js new file mode 100644 index 00000000..0ce1ae3d --- /dev/null +++ b/src/message.js @@ -0,0 +1,487 @@ + +import * as encoding from '../lib/encoding.js' +import * as decoding from '../lib/decoding.js' +import * as ID from './Util/ID.js' +import { getStruct } from './Util/structReferences.js' +import { deleteItemRange } from './Struct/Delete.js' +import { integrateRemoteStruct } from './Util/integrateRemoteStructs.js' +import Item from './Struct/Item.js' + +/** + * @typedef {import('./Store/StateStore.js').default} StateStore + * @typedef {import('./Y.js').default} Y + * @typedef {import('./Struct/Item.js').default} Item + * @typedef {import('./Store/StateStore.js').StateSet} StateSet + */ + +/** + * Core Yjs only defines three message types: + * • YjsSyncStep1: Includes the State Set of the sending client. When received, the client should reply with YjsSyncStep2. + * • YjsSyncStep2: Includes all missing structs and the complete delete set. When received, the the client is assured that + * it received all information from the remote client. + * + * In a peer-to-peer network, you may want to introduce a SyncDone message type. Both parties should initiate the connection + * with SyncStep1. When a client received SyncStep2, it should reply with SyncDone. When the local client received both + * SyncStep2 and SyncDone, it is assured that it is synced to the remote client. + * + * In a client-server model, you want to handle this differently: The client should initiate the connection with SyncStep1. + * When the server receives SyncStep1, it should reply with SyncStep2 immediately followed by SyncStep1. The client replies + * with SyncStep2 when it receives SyncStep1. Optionally the server may send a SyncDone after it received SyncStep2, so the + * client knows that the sync is finished. There are two reasons for this more elaborated sync model: 1. This protocol can + * easily be implemented on top of http and websockets. 2. The server shoul only reply to requests, and not initiate them. + * Therefore it is necesarry that the client initiates the sync. + * + * Construction of a message: + * [messageType : varUint, message definition..] + * + * Note: A message does not include information about the room name. This must to be handled by the upper layer protocol! + * + * stringify[messageType] stringifies a message definition (messageType is already read from the bufffer) + */ + +const messageYjsSyncStep1 = 0 +const messageYjsSyncStep2 = 1 +const messageYjsUpdate = 2 + +/** + * Stringifies a message-encoded Delete Set. + * + * @param {decoding.Decoder} decoder + * @return {string} + */ +export const stringifyDeleteSet = (decoder) => { + let str = '' + const dsLength = decoding.readUint32(decoder) + for (let i = 0; i < dsLength; i++) { + str += ' -' + decoding.readVarUint(decoder) + ':\n' // decodes user + const dvLength = decoding.readVarUint(decoder) + for (let j = 0; j < dvLength; j++) { + str += `clock: ${decoding.readVarUint(decoder)}, length: ${decoding.readVarUint(decoder)}, gc: ${decoding.readUint8(decoder) === 1}\n` + } + } + return str +} + +/** + * Write the DeleteSet of a shared document to an Encoder. + * + * @param {encoding.Encoder} encoder + * @param {Y} y + */ +export const writeDeleteSet = (encoder, y) => { + let currentUser = null + let currentLength + let lastLenPos + let numberOfUsers = 0 + const laterDSLenPus = encoding.length(encoder) + encoding.writeUint32(encoder, 0) + y.ds.iterate(null, null, n => { + const user = n._id.user + const clock = n._id.clock + const len = n.len + const gc = n.gc + if (currentUser !== user) { + numberOfUsers++ + // a new user was found + if (currentUser !== null) { // happens on first iteration + encoding.setUint32(encoder, lastLenPos, currentLength) + } + currentUser = user + encoding.writeVarUint(encoder, user) + // pseudo-fill pos + lastLenPos = encoding.length(encoder) + encoding.writeUint32(encoder, 0) + currentLength = 0 + } + encoding.writeVarUint(encoder, clock) + encoding.writeVarUint(encoder, len) + encoding.writeUint8(encoder, gc ? 1 : 0) + currentLength++ + }) + if (currentUser !== null) { // happens on first iteration + encoding.setUint32(encoder, lastLenPos, currentLength) + } + encoding.setUint32(encoder, laterDSLenPus, numberOfUsers) +} + +/** + * Read delete set from Decoder and apply it to a shared document. + * + * @param {decoding.Decoder} decoder + * @param {Y} y + */ +export const readDeleteSet = (decoder, y) => { + const dsLength = decoding.readUint32(decoder) + for (let i = 0; i < dsLength; i++) { + const user = decoding.readVarUint(decoder) + const dv = [] + const dvLength = decoding.readUint32(decoder) + for (let j = 0; j < dvLength; j++) { + const from = decoding.readVarUint(decoder) + const len = decoding.readVarUint(decoder) + const gc = decoding.readUint8(decoder) === 1 + dv.push({from, len, gc}) + } + if (dvLength > 0) { + const deletions = [] + let pos = 0 + let d = dv[pos] + y.ds.iterate(ID.createID(user, 0), ID.createID(user, Number.MAX_VALUE), n => { + // cases: + // 1. d deletes something to the right of n + // => go to next n (break) + // 2. d deletes something to the left of n + // => create deletions + // => reset d accordingly + // *)=> if d doesn't delete anything anymore, go to next d (continue) + // 3. not 2) and d deletes something that also n deletes + // => reset d so that it doesn't contain n's deletion + // *)=> if d does not delete anything anymore, go to next d (continue) + while (d != null) { + var diff = 0 // describe the diff of length in 1) and 2) + if (n._id.clock + n.len <= d.from) { + // 1) + break + } else if (d.from < n._id.clock) { + // 2) + // delete maximum the len of d + // else delete as much as possible + diff = Math.min(n._id.clock - d.from, d.len) + // deleteItemRange(y, user, d.from, diff, true) + deletions.push([user, d.from, diff]) + } else { + // 3) + diff = n._id.clock + n.len - d.from // never null (see 1) + if (d.gc && !n.gc) { + // d marks as gc'd but n does not + // then delete either way + // deleteItemRange(y, user, d.from, Math.min(diff, d.len), true) + deletions.push([user, d.from, Math.min(diff, d.len)]) + } + } + if (d.len <= diff) { + // d doesn't delete anything anymore + d = dv[++pos] + } else { + d.from = d.from + diff // reset pos + d.len = d.len - diff // reset length + } + } + }) + // TODO: It would be more performant to apply the deletes in the above loop + // Adapt the Tree implementation to support delete while iterating + for (let i = deletions.length - 1; i >= 0; i--) { + const del = deletions[i] + deleteItemRange(y, del[0], del[1], del[2], true) + } + // for the rest.. just apply it + for (; pos < dv.length; pos++) { + d = dv[pos] + deleteItemRange(y, user, d.from, d.len, true) + // deletions.push([user, d.from, d.len, d.gc) + } + } + } +} + +/** + * Read a StateSet from Decoder and return it as string. + * + * @param {decoding.Decoder} decoder + * @return {string} + */ +export const stringifyStateSet = decoder => { + let s = 'State Set: ' + readStateSet(decoder).forEach((user, userState) => { + s += `(${user}: ${userState}), ` + }) + return s +} + +/** + * Write StateSet to Encoder + * + * @param {Y} y + * @param {encoding.Encoder} encoder + */ +export const writeStateSet = (encoder, y) => { + const state = y.ss.state + // write as fixed-size number to stay consistent with the other encode functions. + // => anytime we write the number of objects that follow, encode as fixed-size number. + encoding.writeUint32(encoder, state.size) + state.forEach((user, clock) => { + encoding.writeVarUint(encoder, user) + encoding.writeVarUint(encoder, clock) + }) +} + +/** + * Read StateSet from Decoder and return as Map + * + * @param {decoding.Decoder} decoder + * @return {StateSet} + */ +export const readStateSet = decoder => { + const ss = new Map() + const ssLength = decoding.readUint32(decoder) + for (let i = 0; i < ssLength; i++) { + const user = decoding.readVarUint(decoder) + const clock = decoding.readVarUint(decoder) + ss.set(user, clock) + } + return ss +} + +/** + * Stringify an item id. + * + * @param {ID.ID | ID.RootID} id + * @return {string} + */ +export const stringifyID = id => id instanceof ID.ID ? `(${id.user},${id.clock})` : `(${id.name},${id.type})` + +/** + * Stringify an item as ID. HHere, an item could also be a Yjs instance (e.g. item._parent). + * + * @param {Item | Y | null} item + * @return {string} + */ +export const stringifyItemID = item => { + let result + if (item === null) { + result = '()' + } else if (item instanceof Item) { + result = stringifyID(item._id) + } else { + // must be a Yjs instance + // Don't include Y in this module, so we prevent circular dependencies. + result = 'y' + } + return result +} + +/** + * Helper utility to convert an item to a readable format. + * + * @param {String} name The name of the item class (YText, ItemString, ..). + * @param {Item} item The item instance. + * @param {String} [append] Additional information to append to the returned + * string. + * @return {String} A readable string that represents the item object. + * + */ +export const logItemHelper = (name, item, append) => { + const left = item._left !== null ? stringifyID(item._left._lastId) : '()' + const origin = item._origin !== null ? stringifyID(item._origin._lastId) : '()' + return `${name}(id:${stringifyItemID(item)},left:${left},origin:${origin},right:${stringifyItemID(item._right)},parent:${stringifyItemID(item._parent)},parentSub:${item._parentSub}${append !== undefined ? ' - ' + append : ''})` +} + +/** + * @param {decoding.Decoder} decoder + * @param {Y} y + * @return {string} + */ +export const stringifyStructs = (decoder, y) => { + let str = '' + const len = decoding.readUint32(decoder) + for (let i = 0; i < len; i++) { + let reference = decoding.readVarUint(decoder) + let Constr = getStruct(reference) + let struct = new Constr() + let missing = struct._fromBinary(y, decoder) + let logMessage = ' ' + struct._logString() + if (missing.length > 0) { + logMessage += ' .. missing: ' + missing.map(stringifyItemID).join(', ') + } + str += logMessage + '\n' + } + return str +} + +/** + * Write all Items that are not not included in ss to + * the encoder object. + * + * @param {encoding.Encoder} encoder + * @param {Y} y + * @param {StateSet} ss State Set received from a remote client. Maps from client id to number of created operations by client id. + */ +export const writeStructs = (encoder, y, ss) => { + const lenPos = encoding.length(encoder) + encoding.writeUint32(encoder, 0) + let len = 0 + for (let user of y.ss.state.keys()) { + let clock = ss.get(user) || 0 + if (user !== ID.RootFakeUserID) { + const minBound = ID.createID(user, clock) + const overlappingLeft = y.os.findPrev(minBound) + const rightID = overlappingLeft === null ? null : overlappingLeft._id + if (rightID !== null && rightID.user === user && rightID.clock + overlappingLeft._length > clock) { + const struct = overlappingLeft._clonePartial(clock - rightID.clock) + struct._toBinary(encoder) + len++ + } + y.os.iterate(minBound, ID.createID(user, Number.MAX_VALUE), struct => { + struct._toBinary(encoder) + len++ + }) + } + } + encoding.setUint32(encoder, lenPos, len) +} + +/** + * Read structs and delete operations from decoder and apply them on a shared document. + * + * @param {decoding.Decoder} decoder + * @param {Y} y + */ +export const readStructs = (decoder, y) => { + const len = decoding.readUint32(decoder) + for (let i = 0; i < len; i++) { + integrateRemoteStruct(decoder, y) + } +} + +/** + * Read SyncStep1 and return it as a readable string. + * + * @param {decoding.Decoder} decoder + * @return {string} + */ +export const stringifySyncStep1 = (decoder) => { + let s = 'SyncStep1: ' + const len = decoding.readUint32(decoder) + for (let i = 0; i < len; i++) { + const user = decoding.readVarUint(decoder) + const clock = decoding.readVarUint(decoder) + s += `(${user}:${clock})` + } + return s +} + +/** + * Create a sync step 1 message based on the state of the current shared document. + * + * @param {encoding.Encoder} encoder + * @param {Y} y + */ +export const writeSyncStep1 = (encoder, y) => { + encoding.writeVarUint(encoder, messageYjsSyncStep1) + writeStateSet(encoder, y) +} + +/** + * Read SyncStep1 message and reply with SyncStep2. + * + * @param {decoding.Decoder} decoder The reply to the received message + * @param {encoding.Encoder} encoder The received message + * @param {Y} y + */ +export const readSyncStep1 = (decoder, encoder, y) => { + // read sync step 1 message + const ss = readStateSet(decoder) + // write sync step 2 + encoding.writeVarUint(encoder, messageYjsSyncStep2) + writeStructs(encoder, y, ss) + writeDeleteSet(encoder, y) +} + +/** + * @param {decoding.Decoder} decoder + * @param {Y} y + * @return {string} + */ +export const stringifySyncStep2 = (decoder, y) => { + let str = ' == Sync step 2:\n' + str += ' + Structs:\n' + str += stringifyStructs(decoder, y) + // write DS to string + str += ' + Delete Set:\n' + str += stringifyDeleteSet(decoder) + return str +} + +/** + * Read and apply Structs and then DeleteSet to a y instance. + * + * @param {decoding.Decoder} decoder + * @param {encoding.Encoder} encoder + * @param {Y} y + */ +export const readSyncStep2 = (decoder, encoder, y) => { + readStructs(decoder, y) + readDeleteSet(decoder, y) +} + +/** + * @param {decoding.Decoder} decoder + * @param {Y} y + * @return {string} + */ +export const stringifyUpdate = (decoder, y) => + ' == Update:\n' + stringifyStructs(decoder, y) + +/** + * @param {encoding.Encoder} encoder + * @param {encoding.Encoder} updates + */ +export const writeUpdate = (encoder, numOfStructs, updates) => { + encoding.writeVarUint(encoder, messageYjsUpdate) + encoding.writeUint32(encoder, numOfStructs) + encoding.writeBinaryEncoder(encoder, updates) +} + +export const readUpdate = readStructs + +/** + * @param {decoding.Decoder} decoder + * @param {Y} y + * @return {string} The message converted to string + */ +export const stringifyMessage = (decoder, y) => { + const messageType = decoding.readVarUint(decoder) + let stringifiedMessage + let stringifiedMessageType + switch (messageType) { + case messageYjsSyncStep1: + stringifiedMessageType = 'YjsSyncStep1' + stringifiedMessage = stringifySyncStep1(decoder) + break + case messageYjsSyncStep2: + stringifiedMessageType = 'YjsSyncStep2' + stringifiedMessage = stringifySyncStep2(decoder, y) + break + case messageYjsUpdate: + stringifiedMessageType = 'YjsUpdate' + stringifiedMessage = stringifyStructs(decoder, y) + break + default: + stringifiedMessageType = 'Unknown' + stringifiedMessage = 'Unknown' + } + return `Message ${stringifiedMessageType}:\n${stringifiedMessage}` +} + +/** + * @param {decoding.Decoder} decoder A message received from another client + * @param {encoding.Encoder} encoder The reply message. Will not be sent if empty. + * @param {Y} y + */ +export const readMessage = (decoder, encoder, y) => { + const messageType = decoding.readVarUint(decoder) + switch (messageType) { + case messageYjsSyncStep1: + readSyncStep1(decoder, encoder, y) + break + case messageYjsSyncStep2: + y.transact(() => readSyncStep2(decoder, encoder, y), true) + break + case messageYjsUpdate: + y.transact(() => readUpdate(decoder, y), true) + break + default: + throw new Error('Unknown message type') + } + return messageType +} diff --git a/test/DeleteStore.tests.js b/test/DeleteStore.tests.js index ed6549cf..0c41921c 100644 --- a/test/DeleteStore.tests.js +++ b/test/DeleteStore.tests.js @@ -1,16 +1,16 @@ -import { test } from '../node_modules/cutest/cutest.js' -import Chance from 'chance' +import { test } from '../node_modules/cutest/cutest.mjs' +import * as random from '../lib/random/random.js' import DeleteStore from '../src/Store/DeleteStore.js' -import ID from '../src/Util/ID/ID.js' +import * as ID from '../src/Util/ID.js' /** * Converts a DS to an array of length 10. * * @example * const ds = new DeleteStore() - * ds.mark(new ID(0, 0), 1, false) - * ds.mark(new ID(0, 1), 1, true) - * ds.mark(new ID(0, 3), 1, false) + * ds.mark(ID.createID(0, 0), 1, false) + * ds.mark(ID.createID(0, 1), 1, true) + * ds.mark(ID.createID(0, 3), 1, false) * dsToArray(ds) // => [0, 1, undefined, 0] * * @return {Array<(undefined|number)>} Array of numbers indicating if array[i] is deleted (0), garbage collected (1), or undeleted (undefined). @@ -32,32 +32,32 @@ function dsToArray (ds) { test('DeleteStore', async function ds1 (t) { const ds = new DeleteStore() - ds.mark(new ID(0, 1), 1, false) - ds.mark(new ID(0, 2), 1, false) - ds.mark(new ID(0, 3), 1, false) + ds.mark(ID.createID(0, 1), 1, false) + ds.mark(ID.createID(0, 2), 1, false) + ds.mark(ID.createID(0, 3), 1, false) t.compare(dsToArray(ds), [null, 0, 0, 0]) - ds.mark(new ID(0, 2), 1, true) + ds.mark(ID.createID(0, 2), 1, true) t.compare(dsToArray(ds), [null, 0, 1, 0]) - ds.mark(new ID(0, 1), 1, true) + ds.mark(ID.createID(0, 1), 1, true) t.compare(dsToArray(ds), [null, 1, 1, 0]) - ds.mark(new ID(0, 3), 1, true) + ds.mark(ID.createID(0, 3), 1, true) t.compare(dsToArray(ds), [null, 1, 1, 1]) - ds.mark(new ID(0, 5), 1, true) - ds.mark(new ID(0, 4), 1, true) + ds.mark(ID.createID(0, 5), 1, true) + ds.mark(ID.createID(0, 4), 1, true) t.compare(dsToArray(ds), [null, 1, 1, 1, 1, 1]) - ds.mark(new ID(0, 0), 3, false) + ds.mark(ID.createID(0, 0), 3, false) t.compare(dsToArray(ds), [0, 0, 0, 1, 1, 1]) }) test('random DeleteStore tests', async function randomDS (t) { - const chance = new Chance(t.getSeed() * 1000000000) + const prng = random.createPRNG(t.getSeed()) const ds = new DeleteStore() const dsArray = [] for (let i = 0; i < 200; i++) { - const pos = chance.integer({ min: 0, max: 10 }) - const len = chance.integer({ min: 0, max: 4 }) - const gc = chance.bool() - ds.mark(new ID(0, pos), len, gc) + const pos = random.int32(prng, 0, 10) + const len = random.int32(prng, 0, 4) + const gc = random.bool(prng) + ds.mark(ID.createID(0, pos), len, gc) for (let j = 0; j < len; j++) { dsArray[pos + j] = gc ? 1 : 0 } diff --git a/test/diff.tests.js b/test/diff.tests.js index d2e8e363..217759fe 100644 --- a/test/diff.tests.js +++ b/test/diff.tests.js @@ -1,6 +1,6 @@ -import { test } from '../node_modules/cutest/cutest.js' +import { test } from '../node_modules/cutest/cutest.mjs' import simpleDiff from '../lib/simpleDiff.js' -import Chance from 'chance' +import * as random from '../lib/random/random.js' function runDiffTest (t, a, b, expected) { let result = simpleDiff(a, b) @@ -19,9 +19,9 @@ test('diff tests', async function diff1 (t) { }) test('random diff tests', async function randomDiff (t) { - const chance = new Chance(t.getSeed() * 1000000000) - let a = chance.word() - let b = chance.word() + const gen = random.createPRNG(t.getSeed() * 1000000000) + let a = random.word(gen) + let b = random.word(gen) let change = simpleDiff(a, b) let arr = Array.from(a) arr.splice(change.pos, change.remove, ...Array.from(change.insert)) diff --git a/test/encode-decode.tests.js b/test/encode-decode.tests.js index 5be42adf..ad668245 100644 --- a/test/encode-decode.tests.js +++ b/test/encode-decode.tests.js @@ -1,15 +1,15 @@ -import { test } from '../node_modules/cutest/cutest.js' -import BinaryEncoder from '../src/Util/Binary/Encoder.js' -import BinaryDecoder from '../src/Util/Binary/Decoder.js' +import { test } from '../node_modules/cutest/cutest.mjs' import { generateRandomUint32 } from '../src/Util/generateRandomUint32.js' -import Chance from 'chance' +import * as encoding from '../lib/encoding.js' +import * as decoding from '../lib/decoding.js' +import * as random from '../lib/random/random.js' function testEncoding (t, write, read, val) { - let encoder = new BinaryEncoder() + let encoder = encoding.createEncoder() write(encoder, val) - let reader = new BinaryDecoder(encoder.createBuffer()) + let reader = decoding.createDecoder(encoding.toBuffer(encoder)) let result = read(reader) - t.log(`string encode: ${JSON.stringify(val).length} bytes / binary encode: ${encoder.length} bytes`) + t.log(`string encode: ${JSON.stringify(val).length} bytes / binary encode: ${encoding.length(encoder)} bytes`) t.compare(val, result, 'Compare results') } @@ -37,8 +37,8 @@ test('varUint of 2839012934', async function varUint2839012934 (t) { }) test('varUint random', async function varUintRandom (t) { - const chance = new Chance(t.getSeed() * Math.pow(Number.MAX_SAFE_INTEGER)) - testEncoding(t, writeVarUint, readVarUint, chance.integer({min: 0, max: (1 << 28) - 1})) + const prng = random.createPRNG(t.getSeed() * Math.pow(Number.MAX_SAFE_INTEGER, 2)) + testEncoding(t, writeVarUint, readVarUint, random.int32(prng, 0, (1 << 28) - 1)) }) test('varUint random user id', async function varUintRandomUserId (t) { @@ -60,6 +60,6 @@ test('varString', async function varString (t) { }) test('varString random', async function varStringRandom (t) { - const chance = new Chance(t.getSeed() * 1000000000) - testEncoding(t, writeVarString, readVarString, chance.string()) + const prng = random.createPRNG(t.getSeed() * 10000000) + testEncoding(t, writeVarString, readVarString, random.utf16String(prng)) }) diff --git a/test/index.js b/test/index.js index 1ef3afc8..869d5b34 100644 --- a/test/index.js +++ b/test/index.js @@ -1,3 +1,5 @@ +// TODO: include all tests + import './red-black-tree.js' import './y-array.tests.js' import './y-text.tests.js' diff --git a/test/red-black-tree.js b/test/red-black-tree.js index 75f9b5ad..6afd839b 100644 --- a/test/red-black-tree.js +++ b/test/red-black-tree.js @@ -1,7 +1,7 @@ import RedBlackTree from '../lib/Tree.js' -import ID from '../src/Util/ID/ID.js' -import Chance from 'chance' -import { test, proxyConsole } from 'cutest' +import * as ID from '../src/Util/ID.js' +import { test, proxyConsole } from '../node_modules/cutest/cutest.mjs' +import * as random from '../lib/random/random.js' proxyConsole() @@ -55,28 +55,28 @@ function checkRootNodeIsBlack (t, tree) { test('RedBlack Tree', async function redBlackTree (t) { let tree = new RedBlackTree() - tree.put({_id: new ID(8433, 0)}) - tree.put({_id: new ID(12844, 0)}) - tree.put({_id: new ID(1795, 0)}) - tree.put({_id: new ID(30302, 0)}) - tree.put({_id: new ID(64287)}) - tree.delete(new ID(8433, 0)) - tree.put({_id: new ID(28996)}) - tree.delete(new ID(64287)) - tree.put({_id: new ID(22721)}) + tree.put({_id: ID.createID(8433, 0)}) + tree.put({_id: ID.createID(12844, 0)}) + tree.put({_id: ID.createID(1795, 0)}) + tree.put({_id: ID.createID(30302, 0)}) + tree.put({_id: ID.createID(64287)}) + tree.delete(ID.createID(8433, 0)) + tree.put({_id: ID.createID(28996)}) + tree.delete(ID.createID(64287)) + tree.put({_id: ID.createID(22721)}) checkRootNodeIsBlack(t, tree) checkBlackHeightOfSubTreesAreEqual(t, tree) checkRedNodesDoNotHaveBlackChildren(t, tree) }) -test(`random tests (${numberOfRBTreeTests})`, async function random (t) { - let chance = new Chance(t.getSeed() * 1000000000) +test(`random tests (${numberOfRBTreeTests})`, async function randomRBTree (t) { + let prng = random.createPRNG(t.getSeed() * 1000000000) let tree = new RedBlackTree() let elements = [] for (var i = 0; i < numberOfRBTreeTests; i++) { - if (chance.bool({likelihood: 80})) { + if (random.int32(prng, 0, 100) < 80) { // 80% chance to insert an element - let obj = new ID(chance.integer({min: 0, max: numberOfRBTreeTests}), chance.integer({min: 0, max: 1})) + let obj = ID.createID(random.int32(prng, 0, numberOfRBTreeTests), random.int32(prng, 0, 1)) let nodeExists = tree.find(obj) if (nodeExists === null) { if (elements.some(e => e.equals(obj))) { @@ -87,7 +87,7 @@ test(`random tests (${numberOfRBTreeTests})`, async function random (t) { } } else if (elements.length > 0) { // ~20% chance to delete an element - var elem = chance.pickone(elements) + var elem = random.oneOf(prng, elements) elements = elements.filter(function (e) { return !e.equals(elem) }) @@ -119,7 +119,7 @@ test(`random tests (${numberOfRBTreeTests})`, async function random (t) { 'Find every object with lower bound search' ) // TEST iteration (with lower bound search) - let lowerBound = chance.pickone(elements) + let lowerBound = random.oneOf(prng, elements) let expectedResults = elements.filter((e, pos) => (lowerBound.lessThan(e) || e.equals(lowerBound)) && elements.indexOf(e) === pos @@ -151,7 +151,7 @@ test(`random tests (${numberOfRBTreeTests})`, async function random (t) { 'iterating over a tree without bounds yields the right amount of results' ) - let upperBound = chance.pickone(elements) + let upperBound = random.oneOf(prng, elements) expectedResults = elements.filter((e, pos) => (e.lessThan(upperBound) || e.equals(upperBound)) && elements.indexOf(e) === pos @@ -168,8 +168,8 @@ test(`random tests (${numberOfRBTreeTests})`, async function random (t) { 'iterating over a tree with upper bound yields the right amount of results' ) - upperBound = chance.pickone(elements) - lowerBound = chance.pickone(elements) + upperBound = random.oneOf(prng, elements) + lowerBound = random.oneOf(prng, elements) if (upperBound.lessThan(lowerBound)) { [lowerBound, upperBound] = [upperBound, lowerBound] } diff --git a/test/y-array.tests.js b/test/y-array.tests.js index 8cb1b266..26a0d909 100644 --- a/test/y-array.tests.js +++ b/test/y-array.tests.js @@ -1,5 +1,7 @@ -import { wait, initArrays, compareUsers, Y, flushAll, applyRandomTests } from '../tests-lib/helper.js' +import { initArrays, compareUsers, applyRandomTests } from '../tests-lib/helper.js' +import * as Y from '../src/index.js' import { test, proxyConsole } from 'cutest' +import * as random from '../lib/random/random.js' proxyConsole() test('basic spec', async function array0 (t) { @@ -24,10 +26,10 @@ test('basic spec', async function array0 (t) { }) test('insert three elements, try re-get property', async function array1 (t) { - var { users, array0, array1 } = await initArrays(t, { users: 2 }) + var { testConnector, users, array0, array1 } = initArrays(t, { users: 2 }) array0.insert(0, [1, 2, 3]) t.compare(array0.toJSON(), [1, 2, 3], '.toJSON() works') - await flushAll(t, users) + testConnector.flushAllMessages() t.compare(array1.toJSON(), [1, 2, 3], '.toJSON() works after sync') await compareUsers(t, users) }) @@ -41,9 +43,9 @@ test('concurrent insert (handle three conflicts)', async function array2 (t) { }) test('concurrent insert&delete (handle three conflicts)', async function array3 (t) { - var { users, array0, array1, array2 } = await initArrays(t, { users: 3 }) + var { testConnector, users, array0, array1, array2 } = await initArrays(t, { users: 3 }) array0.insert(0, ['x', 'y', 'z']) - await flushAll(t, users) + testConnector.flushAllMessages() array0.insert(1, [0]) array1.delete(0) array1.delete(1, 1) @@ -53,56 +55,52 @@ test('concurrent insert&delete (handle three conflicts)', async function array3 }) test('insertions work in late sync', async function array4 (t) { - var { users, array0, array1, array2 } = await initArrays(t, { users: 3 }) + var { testConnector, users, array0, array1, array2 } = await initArrays(t, { users: 3 }) array0.insert(0, ['x', 'y']) - await flushAll(t, users) + testConnector.flushAllMessages() users[1].disconnect() users[2].disconnect() array0.insert(1, ['user0']) array1.insert(1, ['user1']) array2.insert(1, ['user2']) - await users[1].reconnect() - await users[2].reconnect() + await users[1].connect() + await users[2].connect() await compareUsers(t, users) }) test('disconnect really prevents sending messages', async function array5 (t) { - var { users, array0, array1 } = await initArrays(t, { users: 3 }) + var { testConnector, users, array0, array1 } = await initArrays(t, { users: 3 }) array0.insert(0, ['x', 'y']) - await flushAll(t, users) + testConnector.flushAllMessages() users[1].disconnect() users[2].disconnect() array0.insert(1, ['user0']) array1.insert(1, ['user1']) - await wait(1000) t.compare(array0.toJSON(), ['x', 'user0', 'y']) t.compare(array1.toJSON(), ['x', 'user1', 'y']) - await users[1].reconnect() - await users[2].reconnect() + await users[1].connect() + await users[2].connect() await compareUsers(t, users) }) test('deletions in late sync', async function array6 (t) { - var { users, array0, array1 } = await initArrays(t, { users: 2 }) + var { testConnector, users, array0, array1 } = await initArrays(t, { users: 2 }) array0.insert(0, ['x', 'y']) - await flushAll(t, users) + testConnector.flushAllMessages() await users[1].disconnect() array1.delete(1, 1) array0.delete(0, 2) - await wait() - await users[1].reconnect() + await users[1].connect() await compareUsers(t, users) }) test('insert, then marge delete on sync', async function array7 (t) { - var { users, array0, array1 } = await initArrays(t, { users: 2 }) + var { testConnector, users, array0, array1 } = await initArrays(t, { users: 2 }) array0.insert(0, ['x', 'y', 'z']) - await flushAll(t, users) - await wait() - await users[0].disconnect() + testConnector.flushAllMessages() + users[0].disconnect() array1.delete(0, 3) - await wait() - await users[0].reconnect() + users[0].connect() await compareUsers(t, users) }) @@ -174,14 +172,13 @@ test('insert & delete events for types (2)', async function array10 (t) { }) test('garbage collector', async function gc1 (t) { - var { users, array0 } = await initArrays(t, { users: 3 }) + var { testConnector, users, array0 } = await initArrays(t, { users: 3 }) array0.insert(0, ['x', 'y', 'z']) - await flushAll(t, users) + testConnector.flushAllMessages() users[0].disconnect() array0.delete(0, 3) - await wait() - await users[0].reconnect() - await flushAll(t, users) + await users[0].connect() + testConnector.flushAllMessages() await compareUsers(t, users) }) @@ -197,13 +194,13 @@ test('event target is set correctly (local)', async function array11 (t) { }) test('event target is set correctly (remote user)', async function array12 (t) { - let { array0, array1, users } = await initArrays(t, { users: 3 }) + let { testConnector, array0, array1, users } = await initArrays(t, { users: 3 }) var event array0.observe(function (e) { event = e }) array1.insert(0, ['stuff']) - await flushAll(t, users) + testConnector.flushAllMessages() compareEvent(t, event, { remote: true }) @@ -217,45 +214,45 @@ function getUniqueNumber () { } var arrayTransactions = [ - function insert (t, user, chance) { + function insert (t, user, prng) { const yarray = user.get('array', Y.Array) var uniqueNumber = getUniqueNumber() var content = [] - var len = chance.integer({ min: 1, max: 4 }) + var len = random.int32(prng, 1, 4) for (var i = 0; i < len; i++) { content.push(uniqueNumber) } - var pos = chance.integer({ min: 0, max: yarray.length }) + var pos = random.int32(prng, 0, yarray.length) yarray.insert(pos, content) }, - function insertTypeArray (t, user, chance) { + function insertTypeArray (t, user, prng) { const yarray = user.get('array', Y.Array) - var pos = chance.integer({ min: 0, max: yarray.length }) + var pos = random.int32(prng, 0, yarray.length) yarray.insert(pos, [Y.Array]) var array2 = yarray.get(pos) array2.insert(0, [1, 2, 3, 4]) }, - function insertTypeMap (t, user, chance) { + function insertTypeMap (t, user, prng) { const yarray = user.get('array', Y.Array) - var pos = chance.integer({ min: 0, max: yarray.length }) + var pos = random.int32(prng, 0, yarray.length) yarray.insert(pos, [Y.Map]) var map = yarray.get(pos) map.set('someprop', 42) map.set('someprop', 43) map.set('someprop', 44) }, - function _delete (t, user, chance) { + function _delete (t, user, prng) { const yarray = user.get('array', Y.Array) var length = yarray.length if (length > 0) { - var somePos = chance.integer({ min: 0, max: length - 1 }) - var delLength = chance.integer({ min: 1, max: Math.min(2, length - somePos) }) + var somePos = random.int32(prng, 0, length - 1) + var delLength = random.int32(prng, 1, Math.min(2, length - somePos)) if (yarray instanceof Y.Array) { - if (chance.bool()) { + if (random.bool(prng)) { var type = yarray.get(somePos) if (type.length > 0) { - somePos = chance.integer({ min: 0, max: type.length - 1 }) - delLength = chance.integer({ min: 0, max: Math.min(2, type.length - somePos) }) + somePos = random.int32(prng, 0, type.length - 1) + delLength = random.int32(prng, 0, Math.min(2, type.length - somePos)) type.delete(somePos, delLength) } } else { diff --git a/test/y-map.tests.js b/test/y-map.tests.js index de248081..ffb3b8c5 100644 --- a/test/y-map.tests.js +++ b/test/y-map.tests.js @@ -1,10 +1,12 @@ -import { initArrays, compareUsers, Y, flushAll, applyRandomTests } from '../tests-lib/helper.js' +import { initArrays, compareUsers, applyRandomTests } from '../tests-lib/helper.js' +import * as Y from '../src/index.js' import { test, proxyConsole } from 'cutest' +import * as random from '../lib/random/random.js' proxyConsole() test('basic map tests', async function map0 (t) { - let { users, map0, map1, map2 } = await initArrays(t, { users: 3 }) + let { testConnector, users, map0, map1, map2 } = await initArrays(t, { users: 3 }) users[2].disconnect() map0.set('number', 1) @@ -22,8 +24,8 @@ test('basic map tests', async function map0 (t) { t.compare(map0.get('object'), { key: { key2: 'value' } }, 'client 0 computed the change (object)') t.assert(map0.get('y-map').get('y-array').get(0) === -1, 'client 0 computed the change (type)') - await users[2].reconnect() - await flushAll(t, users) + await users[2].connect() + testConnector.flushAllMessages() t.assert(map1.get('number') === 1, 'client 1 received the update (number)') t.assert(map1.get('string') === 'hello Y', 'client 1 received the update (string)') @@ -39,13 +41,13 @@ test('basic map tests', async function map0 (t) { }) test('Basic get&set of Map property (converge via sync)', async function map1 (t) { - let { users, map0 } = await initArrays(t, { users: 2 }) + let { testConnector, users, map0 } = await initArrays(t, { users: 2 }) map0.set('stuff', 'stuffy') map0.set('undefined', undefined) map0.set('null', null) t.compare(map0.get('stuff'), 'stuffy') - await flushAll(t, users) + testConnector.flushAllMessages() for (let user of users) { var u = user.get('map', Y.Map) @@ -85,11 +87,11 @@ test('Map can set custom types (Array)', async function map4 (t) { }) test('Basic get&set of Map property (converge via update)', async function map5 (t) { - let { users, map0 } = await initArrays(t, { users: 2 }) + let { testConnector, users, map0 } = await initArrays(t, { users: 2 }) map0.set('stuff', 'stuffy') t.compare(map0.get('stuff'), 'stuffy') - await flushAll(t, users) + testConnector.flushAllMessages() for (let user of users) { var u = user.get('map', Y.Map) @@ -99,12 +101,11 @@ test('Basic get&set of Map property (converge via update)', async function map5 }) test('Basic get&set of Map property (handle conflict)', async function map6 (t) { - let { users, map0, map1 } = await initArrays(t, { users: 3 }) + let { testConnector, users, map0, map1 } = await initArrays(t, { users: 3 }) map0.set('stuff', 'c0') map1.set('stuff', 'c1') - await flushAll(t, users) - await flushAll(t, users) + testConnector.flushAllMessages() for (let user of users) { var u = user.get('map', Y.Map) @@ -114,12 +115,11 @@ test('Basic get&set of Map property (handle conflict)', async function map6 (t) }) test('Basic get&set&delete of Map property (handle conflict)', async function map7 (t) { - let { users, map0, map1 } = await initArrays(t, { users: 3 }) + let { testConnector, users, map0, map1 } = await initArrays(t, { users: 3 }) map0.set('stuff', 'c0') map0.delete('stuff') map1.set('stuff', 'c1') - await flushAll(t, users) - await flushAll(t, users) + testConnector.flushAllMessages() for (let user of users) { var u = user.get('map', Y.Map) t.assert(u.get('stuff') === undefined) @@ -128,13 +128,12 @@ test('Basic get&set&delete of Map property (handle conflict)', async function ma }) test('Basic get&set of Map property (handle three conflicts)', async function map8 (t) { - let { users, map0, map1, map2 } = await initArrays(t, { users: 3 }) + let { testConnector, users, map0, map1, map2 } = await initArrays(t, { users: 3 }) map0.set('stuff', 'c0') map1.set('stuff', 'c1') map1.set('stuff', 'c2') map2.set('stuff', 'c3') - await flushAll(t, users) - await flushAll(t, users) + testConnector.flushAllMessages() for (let user of users) { var u = user.get('map', Y.Map) t.compare(u.get('stuff'), 'c0') @@ -143,19 +142,18 @@ test('Basic get&set of Map property (handle three conflicts)', async function ma }) test('Basic get&set&delete of Map property (handle three conflicts)', async function map9 (t) { - let { users, map0, map1, map2, map3 } = await initArrays(t, { users: 4 }) + let { testConnector, users, map0, map1, map2, map3 } = await initArrays(t, { users: 4 }) map0.set('stuff', 'c0') map1.set('stuff', 'c1') map1.set('stuff', 'c2') map2.set('stuff', 'c3') - await flushAll(t, users) + testConnector.flushAllMessages() map0.set('stuff', 'deleteme') map0.delete('stuff') map1.set('stuff', 'c1') map2.set('stuff', 'c2') map3.set('stuff', 'c3') - await flushAll(t, users) - await flushAll(t, users) + testConnector.flushAllMessages() for (let user of users) { var u = user.get('map', Y.Map) t.assert(u.get('stuff') === undefined) @@ -173,7 +171,7 @@ test('observePath properties', async function map10 (t) { } }) map1.set('map', new Y.Map()) - await flushAll(t, users) + testConnector.flushAllMessages() map = map2.get('map') t.compare(map.get('yay'), 4) await compareUsers(t, users) @@ -181,7 +179,7 @@ test('observePath properties', async function map10 (t) { */ test('observe deep properties', async function map11 (t) { - let { users, map1, map2, map3 } = await initArrays(t, { users: 4 }) + let { testConnector, users, map1, map2, map3 } = await initArrays(t, { users: 4 }) var _map1 = map1.set('map', new Y.Map()) var calls = 0 var dmapid @@ -194,15 +192,13 @@ test('observe deep properties', async function map11 (t) { dmapid = event.target.get('deepmap')._id }) }) - await flushAll(t, users) - await flushAll(t, users) + testConnector.flushAllMessages() var _map3 = map3.get('map') _map3.set('deepmap', new Y.Map()) - await flushAll(t, users) + testConnector.flushAllMessages() var _map2 = map2.get('map') _map2.set('deepmap', new Y.Map()) - await flushAll(t, users) - await flushAll(t, users) + testConnector.flushAllMessages() var dmap1 = _map1.get('deepmap') var dmap2 = _map2.get('deepmap') var dmap3 = _map3.get('deepmap') @@ -304,14 +300,14 @@ test('event has correct value when setting a primitive on a YMap (received from */ var mapTransactions = [ - function set (t, user, chance) { - let key = chance.pickone(['one', 'two']) - var value = chance.string() + function set (t, user, prng) { + let key = random.oneOf(prng, ['one', 'two']) + var value = random.utf16String(prng) user.get('map', Y.Map).set(key, value) }, - function setType (t, user, chance) { - let key = chance.pickone(['one', 'two']) - var type = chance.pickone([new Y.Array(), new Y.Map()]) + function setType (t, user, prng) { + let key = random.oneOf(prng, ['one', 'two']) + var type = random.oneOf(prng, [new Y.Array(), new Y.Map()]) user.get('map', Y.Map).set(key, type) if (type instanceof Y.Array) { type.insert(0, [1, 2, 3, 4]) @@ -319,8 +315,8 @@ var mapTransactions = [ type.set('deepkey', 'deepvalue') } }, - function _delete (t, user, chance) { - let key = chance.pickone(['one', 'two']) + function _delete (t, user, prng) { + let key = random.oneOf(prng, ['one', 'two']) user.get('map', Y.Map).delete(key) } ] diff --git a/test/y-text.tests.js b/test/y-text.tests.js index 4b73ec11..48c6c265 100644 --- a/test/y-text.tests.js +++ b/test/y-text.tests.js @@ -1,4 +1,4 @@ -import { initArrays, compareUsers, flushAll } from '../tests-lib/helper.js' +import { initArrays, compareUsers } from '../tests-lib/helper.js' import { test, proxyConsole } from 'cutest' proxyConsole() @@ -64,23 +64,23 @@ test('basic format', async function text1 (t) { }) test('quill issue 1', async function quill1 (t) { - let { users, quill0 } = await initArrays(t, { users: 2 }) + let { testConnector, users, quill0 } = await initArrays(t, { users: 2 }) quill0.insertText(0, 'x') - await flushAll(t, users) + testConnector.flushAllMessages() quill0.insertText(1, '\n', 'list', 'ordered') - await flushAll(t, users) + testConnector.flushAllMessages() quill0.insertText(1, '\n', 'list', 'ordered') await compareUsers(t, users) }) test('quill issue 2', async function quill2 (t) { - let { users, quill0, text0 } = await initArrays(t, { users: 2 }) + let { testConnector, users, quill0, text0 } = await initArrays(t, { users: 2 }) let delta text0.observe(function (event) { delta = event.delta }) quill0.insertText(0, 'abc', 'bold', true) - await flushAll(t, users) + testConnector.flushAllMessages() quill0.insertText(1, 'x') quill0.update() t.compare(delta, [{ retain: 1 }, { insert: 'x', attributes: { bold: true } }]) diff --git a/test/y-xml.tests.js b/test/y-xml.tests.js index 6d77515c..4bccabba 100644 --- a/test/y-xml.tests.js +++ b/test/y-xml.tests.js @@ -1,19 +1,21 @@ -import { wait, initArrays, compareUsers, Y, flushAll, applyRandomTests } from '../../yjs/tests-lib/helper.js' +import { initArrays, compareUsers, applyRandomTests } from '../../yjs/tests-lib/helper.js' import { test } from 'cutest' +import * as Y from '../src/index.js' +import * as random from '../lib/random/random.js' test('set property', async function xml0 (t) { - var { users, xml0, xml1 } = await initArrays(t, { users: 2 }) + var { testConnector, users, xml0, xml1 } = await initArrays(t, { users: 2 }) xml0.setAttribute('height', '10') t.assert(xml0.getAttribute('height') === '10', 'Simple set+get works') - await flushAll(t, users) + testConnector.flushAllMessages() t.assert(xml1.getAttribute('height') === '10', 'Simple set+get works (remote)') await compareUsers(t, users) }) test('events', async function xml1 (t) { - var { users, xml0, xml1 } = await initArrays(t, { users: 2 }) - var event - var remoteEvent + var { testConnector, users, xml0, xml1 } = await initArrays(t, { users: 2 }) + var event = { attributesChanged: new Set() } + var remoteEvent = { attributesChanged: new Set() } xml0.observe(function (e) { delete e._content delete e.nodes @@ -28,21 +30,21 @@ test('events', async function xml1 (t) { }) xml0.setAttribute('key', 'value') t.assert(event.attributesChanged.has('key'), 'YXmlEvent.attributesChanged on updated key') - await flushAll(t, users) + testConnector.flushAllMessages() t.assert(remoteEvent.attributesChanged.has('key'), 'YXmlEvent.attributesChanged on updated key (remote)') // check attributeRemoved xml0.removeAttribute('key') t.assert(event.attributesChanged.has('key'), 'YXmlEvent.attributesChanged on removed attribute') - await flushAll(t, users) + testConnector.flushAllMessages() t.assert(remoteEvent.attributesChanged.has('key'), 'YXmlEvent.attributesChanged on removed attribute (remote)') xml0.insert(0, [new Y.XmlText('some text')]) t.assert(event.childListChanged, 'YXmlEvent.childListChanged on inserted element') - await flushAll(t, users) + testConnector.flushAllMessages() t.assert(remoteEvent.childListChanged, 'YXmlEvent.childListChanged on inserted element (remote)') // test childRemoved xml0.delete(0) t.assert(event.childListChanged, 'YXmlEvent.childListChanged on deleted element') - await flushAll(t, users) + testConnector.flushAllMessages() t.assert(remoteEvent.childListChanged, 'YXmlEvent.childListChanged on deleted element (remote)') await compareUsers(t, users) }) @@ -50,13 +52,10 @@ test('events', async function xml1 (t) { test('attribute modifications (y -> dom)', async function xml2 (t) { var { users, xml0, dom0 } = await initArrays(t, { users: 3 }) xml0.setAttribute('height', '100px') - await wait() t.assert(dom0.getAttribute('height') === '100px', 'setAttribute') xml0.removeAttribute('height') - await wait() t.assert(dom0.getAttribute('height') == null, 'removeAttribute') xml0.setAttribute('class', 'stuffy stuff') - await wait() t.assert(dom0.getAttribute('class') === 'stuffy stuff', 'set class attribute') await compareUsers(t, users) }) @@ -64,13 +63,10 @@ test('attribute modifications (y -> dom)', async function xml2 (t) { test('attribute modifications (dom -> y)', async function xml3 (t) { var { users, xml0, dom0 } = await initArrays(t, { users: 3 }) dom0.setAttribute('height', '100px') - await wait() t.assert(xml0.getAttribute('height') === '100px', 'setAttribute') dom0.removeAttribute('height') - await wait() t.assert(xml0.getAttribute('height') == null, 'removeAttribute') dom0.setAttribute('class', 'stuffy stuff') - await wait() t.assert(xml0.getAttribute('class') === 'stuffy stuff', 'set class attribute') await compareUsers(t, users) }) @@ -79,7 +75,6 @@ test('element insert (dom -> y)', async function xml4 (t) { var { users, xml0, dom0 } = await initArrays(t, { users: 3 }) dom0.insertBefore(document.createTextNode('some text'), null) dom0.insertBefore(document.createElement('p'), null) - await wait() t.assert(xml0.get(0).toString() === 'some text', 'Retrieve Text Node') t.assert(xml0.get(1).nodeName === 'P', 'Retrieve Element node') await compareUsers(t, users) @@ -97,10 +92,8 @@ test('element insert (y -> dom)', async function xml5 (t) { test('y on insert, then delete (dom -> y)', async function xml6 (t) { var { users, xml0, dom0 } = await initArrays(t, { users: 3 }) dom0.insertBefore(document.createElement('p'), null) - await wait() t.assert(xml0.length === 1, 'one node present') dom0.childNodes[0].remove() - await wait() t.assert(xml0.length === 0, 'no node present after delete') await compareUsers(t, users) }) @@ -117,9 +110,7 @@ test('y on insert, then delete (y -> dom)', async function xml7 (t) { test('delete consecutive (1) (Text)', async function xml8 (t) { var { users, xml0, dom0 } = await initArrays(t, { users: 3 }) xml0.insert(0, [new Y.XmlText('1'), new Y.XmlText('2'), new Y.XmlText('3')]) - await wait() xml0.delete(1, 2) - await wait() t.assert(xml0.length === 1, 'check length (y)') t.assert(dom0.childNodes.length === 1, 'check length (dom)') t.assert(dom0.childNodes[0].textContent === '1', 'check content') @@ -129,10 +120,8 @@ test('delete consecutive (1) (Text)', async function xml8 (t) { test('delete consecutive (2) (Text)', async function xml9 (t) { var { users, xml0, dom0 } = await initArrays(t, { users: 3 }) xml0.insert(0, [new Y.XmlText('1'), new Y.XmlText('2'), new Y.XmlText('3')]) - await wait() xml0.delete(0, 1) xml0.delete(1, 1) - await wait() t.assert(xml0.length === 1, 'check length (y)') t.assert(dom0.childNodes.length === 1, 'check length (dom)') t.assert(dom0.childNodes[0].textContent === '2', 'check content') @@ -142,9 +131,7 @@ test('delete consecutive (2) (Text)', async function xml9 (t) { test('delete consecutive (1) (Element)', async function xml10 (t) { var { users, xml0, dom0 } = await initArrays(t, { users: 3 }) xml0.insert(0, [new Y.XmlElement('A'), new Y.XmlElement('B'), new Y.XmlElement('C')]) - await wait() xml0.delete(1, 2) - await wait() t.assert(xml0.length === 1, 'check length (y)') t.assert(dom0.childNodes.length === 1, 'check length (dom)') t.assert(dom0.childNodes[0].nodeName === 'A', 'check content') @@ -154,10 +141,8 @@ test('delete consecutive (1) (Element)', async function xml10 (t) { test('delete consecutive (2) (Element)', async function xml11 (t) { var { users, xml0, dom0 } = await initArrays(t, { users: 3 }) xml0.insert(0, [new Y.XmlElement('A'), new Y.XmlElement('B'), new Y.XmlElement('C')]) - await wait() xml0.delete(0, 1) xml0.delete(1, 1) - await wait() t.assert(xml0.length === 1, 'check length (y)') t.assert(dom0.childNodes.length === 1, 'check length (dom)') t.assert(dom0.childNodes[0].nodeName === 'B', 'check content') @@ -165,12 +150,12 @@ test('delete consecutive (2) (Element)', async function xml11 (t) { }) test('Receive a bunch of elements (with disconnect)', async function xml12 (t) { - var { users, xml0, xml1, dom0, dom1 } = await initArrays(t, { users: 3 }) + var { testConnector, users, xml0, xml1, dom0, dom1 } = await initArrays(t, { users: 3 }) users[1].disconnect() xml0.insert(0, [new Y.XmlElement('A'), new Y.XmlElement('B'), new Y.XmlElement('C')]) xml0.insert(0, [new Y.XmlElement('X'), new Y.XmlElement('Y'), new Y.XmlElement('Z')]) - await users[1].reconnect() - await flushAll(t, users) + await users[1].connect() + testConnector.flushAllMessages() t.assert(xml0.length === 6, 'check length (y)') t.assert(xml1.length === 6, 'check length (y) (reconnected user)') t.assert(dom0.childNodes.length === 6, 'check length (dom)') @@ -179,10 +164,10 @@ test('Receive a bunch of elements (with disconnect)', async function xml12 (t) { }) test('move element to a different position', async function xml13 (t) { - var { users, dom0, dom1 } = await initArrays(t, { users: 3 }) + var { testConnector, users, dom0, dom1 } = await initArrays(t, { users: 3 }) dom0.append(document.createElement('div')) dom0.append(document.createElement('h1')) - await flushAll(t, users) + testConnector.flushAllMessages() dom1.insertBefore(dom1.childNodes[0], null) t.assert(dom1.childNodes[0].nodeName === 'H1', 'div was deleted (user 0)') t.assert(dom1.childNodes[1].nodeName === 'DIV', 'div was moved to the correct position (user 0)') @@ -192,7 +177,7 @@ test('move element to a different position', async function xml13 (t) { }) test('filter node', async function xml14 (t) { - var { users, dom0, dom1, domBinding0, domBinding1 } = await initArrays(t, { users: 3 }) + var { testConnector, users, dom0, dom1, domBinding0, domBinding1 } = await initArrays(t, { users: 3 }) let domFilter = (nodeName, attrs) => { if (nodeName === 'H1') { return null @@ -204,14 +189,14 @@ test('filter node', async function xml14 (t) { domBinding1.setFilter(domFilter) dom0.append(document.createElement('div')) dom0.append(document.createElement('h1')) - await flushAll(t, users) + testConnector.flushAllMessages() t.assert(dom1.childNodes.length === 1, 'Only one node was not transmitted') t.assert(dom1.childNodes[0].nodeName === 'DIV', 'div node was transmitted') await compareUsers(t, users) }) test('filter attribute', async function xml15 (t) { - var { users, dom0, dom1, domBinding0, domBinding1 } = await initArrays(t, { users: 3 }) + var { testConnector, users, dom0, dom1, domBinding0, domBinding1 } = await initArrays(t, { users: 3 }) let domFilter = (nodeName, attrs) => { attrs.delete('hidden') return attrs @@ -221,7 +206,7 @@ test('filter attribute', async function xml15 (t) { dom0.setAttribute('hidden', 'true') dom0.setAttribute('style', 'height: 30px') dom0.setAttribute('data-me', '77') - await flushAll(t, users) + testConnector.flushAllMessages() t.assert(dom0.getAttribute('hidden') === 'true', 'User 0 still has the attribute') t.assert(dom1.getAttribute('hidden') == null, 'User 1 did not receive update') t.assert(dom1.getAttribute('style') === 'height: 30px', 'User 1 received style update') @@ -230,7 +215,7 @@ test('filter attribute', async function xml15 (t) { }) test('deep element insert', async function xml16 (t) { - var { users, dom0, dom1 } = await initArrays(t, { users: 3 }) + var { testConnector, users, dom0, dom1 } = await initArrays(t, { users: 3 }) let deepElement = document.createElement('p') let boldElement = document.createElement('b') let attrElement = document.createElement('img') @@ -240,7 +225,7 @@ test('deep element insert', async function xml16 (t) { deepElement.append(attrElement) dom0.append(deepElement) let str0 = dom0.outerHTML - await flushAll(t, users) + testConnector.flushAllMessages() let str1 = dom1.outerHTML t.compare(str0, str1, 'Dom string representation matches') await compareUsers(t, users) @@ -268,7 +253,7 @@ test('treeWalker', async function xml17 (t) { * Incoming changes that contain malicious attributes should be deleted. */ test('Filtering remote changes', async function xmlFilteringRemote (t) { - var { users, xml0, xml1, domBinding0 } = await initArrays(t, { users: 3 }) + var { testConnector, users, xml0, xml1, domBinding0 } = await initArrays(t, { users: 3 }) domBinding0.setFilter(function (nodeName, attributes) { attributes.delete('malicious') if (nodeName === 'HIDEME') { @@ -290,70 +275,71 @@ test('Filtering remote changes', async function xmlFilteringRemote (t) { let tag2 = new Y.XmlElement('tag') tag2.setAttribute('isHidden', 'true') paragraph.insert(0, [tag2]) - await flushAll(t, users) + testConnector.flushAllMessages() // check dom domBinding0.typeToDom.get(paragraph).setAttribute('malicious', 'true') domBinding0.typeToDom.get(span).setAttribute('malicious', 'true') // check incoming attributes xml1.get(0).get(0).setAttribute('malicious', 'true') xml1.insert(0, [new Y.XmlElement('hideMe')]) - await flushAll(t, users) + testConnector.flushAllMessages() await compareUsers(t, users) }) // TODO: move elements var xmlTransactions = [ - function attributeChange (t, user, chance) { - user.dom.setAttribute(chance.word(), chance.word()) + function attributeChange (t, user, prng) { + // random.word generates non-empty words. prepend something + user.dom.setAttribute('_' + random.word(prng), random.word(prng)) }, - function attributeChangeHidden (t, user, chance) { - user.dom.setAttribute('hidden', chance.word()) + function attributeChangeHidden (t, user, prng) { + user.dom.setAttribute('hidden', random.word(prng)) }, - function insertText (t, user, chance) { + function insertText (t, user, prng) { let dom = user.dom - var succ = dom.children.length > 0 ? chance.pickone(dom.children) : null - dom.insertBefore(document.createTextNode(chance.word()), succ) + var succ = dom.children.length > 0 ? random.oneOf(prng, dom.children) : null + dom.insertBefore(document.createTextNode(random.word(prng)), succ) }, - function insertHiddenDom (t, user, chance) { + function insertHiddenDom (t, user, prng) { let dom = user.dom - var succ = dom.children.length > 0 ? chance.pickone(dom.children) : null + var succ = dom.children.length > 0 ? random.oneOf(prng, dom.children) : null dom.insertBefore(document.createElement('hidden'), succ) }, - function insertDom (t, user, chance) { + function insertDom (t, user, prng) { let dom = user.dom - var succ = dom.children.length > 0 ? chance.pickone(dom.children) : null - dom.insertBefore(document.createElement(chance.word()), succ) + var succ = dom.children.length > 0 ? random.oneOf(prng, dom.children) : null + dom.insertBefore(document.createElement('my-' + random.word(prng)), succ) }, - function deleteChild (t, user, chance) { + function deleteChild (t, user, prng) { let dom = user.dom if (dom.childNodes.length > 0) { - var d = chance.pickone(dom.childNodes) + var d = random.oneOf(prng, dom.childNodes) d.remove() } }, - function insertTextSecondLayer (t, user, chance) { + function insertTextSecondLayer (t, user, prng) { let dom = user.dom if (dom.children.length > 0) { - let dom2 = chance.pickone(dom.children) - let succ = dom2.childNodes.length > 0 ? chance.pickone(dom2.childNodes) : null - dom2.insertBefore(document.createTextNode(chance.word()), succ) + let dom2 = random.oneOf(prng, dom.children) + let succ = dom2.childNodes.length > 0 ? random.oneOf(prng, dom2.childNodes) : null + dom2.insertBefore(document.createTextNode(random.word(prng)), succ) } }, - function insertDomSecondLayer (t, user, chance) { + function insertDomSecondLayer (t, user, prng) { let dom = user.dom if (dom.children.length > 0) { - let dom2 = chance.pickone(dom.children) - let succ = dom2.childNodes.length > 0 ? chance.pickone(dom2.childNodes) : null - dom2.insertBefore(document.createElement(chance.word()), succ) + let dom2 = random.oneOf(prng, dom.children) + let succ = dom2.childNodes.length > 0 ? random.oneOf(prng, dom2.childNodes) : null + dom2.insertBefore(document.createElement('my-' + random.word(prng)), succ) } }, - function deleteChildSecondLayer (t, user, chance) { + function deleteChildSecondLayer (t, user, prng) { let dom = user.dom if (dom.children.length > 0) { - let dom2 = chance.pickone(dom.children) + let dom2 = random.oneOf(prng, dom.children) if (dom2.childNodes.length > 0) { - let d = chance.pickone(dom2.childNodes) + let d = random.oneOf(prng, dom2.childNodes) d.remove() } } diff --git a/tests-lib/helper.js b/tests-lib/helper.js index fe59c55f..35738540 100644 --- a/tests-lib/helper.js +++ b/tests-lib/helper.js @@ -1,31 +1,224 @@ -import _Y from '../src/Y.dist.js' -import { DomBinding } from '../src/Y.js' -import TestConnector from './test-connector.js' - -import Chance from 'chance' +import * as Y from '../src/index.js' import ItemJSON from '../src/Struct/ItemJSON.js' import ItemString from '../src/Struct/ItemString.js' import { defragmentItemContent } from '../src/Util/defragmentItemContent.js' import Quill from 'quill' import GC from '../src/Struct/GC.js' +import * as random from '../lib/random/random.js' +import * as message from '../src/message.js' +import * as encoding from '../lib/encoding.js' +import * as decoding from '../lib/decoding.js' +import { createMutex } from '../lib/mutex.js' -export const Y = _Y +export * from '../src/index.js' -export const database = { name: 'memory' } -export const connector = { name: 'test', url: 'http://localhost:1234' } - -Y.test = TestConnector - -function getStateSet (y) { - let ss = {} - for (let [user, clock] of y.ss.state) { - ss[user] = clock - } - return ss +/** + * @param {TestYInstance} y + * @param {Y.Transaction} transaction + */ +const afterTransaction = (y, transaction) => { + y.mMux(() => { + if (transaction.encodedStructsLen > 0) { + const encoder = encoding.createEncoder() + message.writeUpdate(encoder, transaction.encodedStructsLen, transaction.encodedStructs) + broadcastMessage(y, encoding.toBuffer(encoder)) + } + }) } -function getDeleteSet (y) { +export class TestYInstance extends Y.Y { + /** + * @param {TestConnector} testConnector + */ + constructor (testConnector) { + super() + /** + * @type {TestConnector} + */ + this.tc = testConnector + /** + * @type {Map>} + */ + this.receiving = new Map() + /** + * Message mutex + * @type {Function} + */ + this.mMux = createMutex() + testConnector.allConns.add(this) + // set up observe on local model + this.on('afterTransaction', afterTransaction) + this.connect() + } + /** + * Disconnect from TestConnector. + */ + disconnect () { + this.receiving = new Map() + this.tc.onlineConns.delete(this) + } + /** + * Append yourself to the list of known Y instances in testconnector. + * Also initiate sync with all clients. + */ + connect () { + if (!this.tc.onlineConns.has(this)) { + const encoder = encoding.createEncoder() + message.writeSyncStep1(encoder, this) + // publish SyncStep1 + broadcastMessage(this, encoding.toBuffer(encoder)) + this.tc.onlineConns.forEach(remoteYInstance => { + // remote instance sends instance to this instance + const encoder = encoding.createEncoder() + message.writeSyncStep1(encoder, remoteYInstance) + this._receive(encoding.toBuffer(encoder), remoteYInstance) + }) + this.tc.onlineConns.add(this) + } + } + /** + * Receive a message from another client. This message is only appended to the list of receiving messages. + * TestConnector decides when this client actually reads this message. + * + * @param {ArrayBuffer} message + * @param {TestYInstance} remoteClient + */ + _receive (message, remoteClient) { + let messages = this.receiving.get(remoteClient) + if (messages === undefined) { + messages = [] + this.receiving.set(remoteClient, messages) + } + messages.push(message) + } +} + +/** + * Keeps track of TestYInstances. + * + * The TestYInstances add/remove themselves from the list of connections maintained in this object. + * I think it makes sense. Deal with it. + */ +export class TestConnector { + constructor (prng) { + /** + * @type {Set} + */ + this.allConns = new Set() + /** + * @type {Set} + */ + this.onlineConns = new Set() + /** + * @type {random.PRNG} + */ + this.prng = prng + } + /** + * Create a new Y instance and add it to the list of connections + */ + createY () { + return new TestYInstance(this) + } + /** + * Choose random connection and flush a random message from a random sender. + * + * If this function was unable to flush a message, because there are no more messages to flush, it returns false. true otherwise. + * @return {boolean} + */ + flushRandomMessage () { + const prng = this.prng + const conns = Array.from(this.onlineConns).filter(conn => conn.receiving.size > 0) + if (conns.length > 0) { + const receiver = random.oneOf(prng, conns) + const [sender, messages] = random.oneOf(prng, Array.from(receiver.receiving)) + const m = messages.shift() + if (messages.length === 0) { + receiver.receiving.delete(sender) + } + const encoder = encoding.createEncoder() + receiver.mMux(() => { + // do not publish data created when this function is executed (could be ss2 or update message) + message.readMessage(decoding.createDecoder(m), encoder, receiver) + }) + if (encoding.length(encoder) > 0) { + // send reply message + sender._receive(encoding.toBuffer(encoder), receiver) + } + return true + } + return false + } + /** + * @return {boolean} True iff this function actually flushed something + */ + flushAllMessages () { + let didSomething = false + while (this.flushRandomMessage()) { + didSomething = true + } + return didSomething + } + reconnectAll () { + this.allConns.forEach(conn => conn.connect()) + } + disconnectAll () { + this.allConns.forEach(conn => conn.disconnect()) + } + syncAll () { + this.reconnectAll() + this.flushAllMessages() + } + /** + * @return {boolean} Whether it was possible to disconnect a randon connection. + */ + disconnectRandom () { + if (this.onlineConns.size === 0) { + return false + } + random.oneOf(this.prng, Array.from(this.onlineConns)).disconnect() + return true + } + /** + * @return {boolean} Whether it was possible to reconnect a random connection. + */ + reconnectRandom () { + const reconnectable = [] + this.allConns.forEach(conn => { + if (!this.onlineConns.has(conn)) { + reconnectable.push(conn) + } + }) + if (reconnectable.length === 0) { + return false + } + random.oneOf(this.prng, reconnectable).connect() + return true + } +} + +/** + * @param {TestYInstance} y // publish message created by `y` to all other online clients + * @param {ArrayBuffer} m + */ +const broadcastMessage = (y, m) => + y.tc.onlineConns.forEach(remoteYInstance => { + if (remoteYInstance !== y) { + remoteYInstance._receive(m, y) + } + }) + +/** + * Convert DS to a proper DeleteSet of Map. + * + * @param {Y.Y} y + * @return {Object>} + */ +const getDeleteSet = y => { + /** + * @type {Object} + */ var ds = {} y.ds.iterate(null, null, function (n) { var user = n._id.user @@ -42,29 +235,28 @@ function getDeleteSet (y) { return ds } -/* +/** * 1. reconnect and flush all * 2. user 0 gc * 3. get type content * 4. disconnect & reconnect all (so gc is propagated) * 5. compare os, ds, ss + * + * @param {any} t + * @param {Array} users */ -export async function compareUsers (t, users) { - await Promise.all(users.map(u => u.reconnect())) - if (users[0].connector.testRoom == null) { - await wait(100) - } - await flushAll(t, users) - await wait() - await flushAll(t, users) - await wait() - await flushAll(t, users) - await wait() - await flushAll(t, users) +export function compareUsers (t, users) { + users.forEach(u => u.connect()) + do { + users.forEach(u => { + // flush dom changes + u.domBinding._beforeTransactionHandler(null, null, false) + }) + } while (users[0].tc.flushAllMessages()) var userArrayValues = users.map(u => u.define('array', Y.Array).toJSON().map(val => JSON.stringify(val))) var userMapValues = users.map(u => u.define('map', Y.Map).toJSON()) - var userXmlValues = users.map(u => u.define('xml', Y.Xml).toString()) + var userXmlValues = users.map(u => u.define('xml', Y.XmlElement).toString()) var userTextValues = users.map(u => u.define('text', Y.Text).toDelta()) var userQuillValues = users.map(u => { u.quill.update('yjs') // get latest changes @@ -81,7 +273,8 @@ export async function compareUsers (t, users) { json = { type: 'GC', id: op._id, - length: op._length + length: op._length, + content: null } } else { json = { @@ -90,7 +283,8 @@ export async function compareUsers (t, users) { right: op._right === null ? null : op._right._id, length: op._length, deleted: op._deleted, - parent: op._parent._id + parent: op._parent._id, + content: null } } if (op instanceof ItemJSON || op instanceof ItemString) { @@ -100,11 +294,15 @@ export async function compareUsers (t, users) { }) data.os = ops data.ds = getDeleteSet(u) - data.ss = getStateSet(u) + const ss = {} + u.ss.state.forEach((user, clock) => { + ss[user] = clock + }) + data.ss = ss return data }) for (var i = 0; i < data.length - 1; i++) { - await t.asyncGroup(async () => { + t.group(() => { t.compare(userArrayValues[i], userArrayValues[i + 1], 'array types') t.compare(userMapValues[i], userMapValues[i + 1], 'map types') t.compare(userXmlValues[i], userXmlValues[i + 1], 'xml types') @@ -115,10 +313,20 @@ export async function compareUsers (t, users) { t.compare(data[i].ss, data[i + 1].ss, 'ss') }, `Compare user${i} with user${i + 1}`) } + users.forEach(user => { + if (user._missingStructs.size !== 0) { + t.fail('missing structs should mes empty!') + } + }) users.map(u => u.destroy()) } -function domFilter (nodeName, attrs) { +/** + * @param {string} nodeName + * @param {Map} attrs + * @return {null|Map} + */ +function filter (nodeName, attrs) { if (nodeName === 'HIDDEN') { return null } @@ -126,30 +334,27 @@ function domFilter (nodeName, attrs) { return attrs } -export async function initArrays (t, opts) { +/** + * @param {any} t + * @param {any} opts + * @return {any} + */ +export function initArrays (t, opts) { var result = { users: [] } - var chance = opts.chance || new Chance(t.getSeed() * 1000000000) - var conn = Object.assign({ room: 'debugging_' + t.name, testContext: t, chance }, connector) + var prng = opts.prng || random.createPRNG(t.getSeed()) + const testConnector = new TestConnector(prng) + result.testConnector = testConnector for (let i = 0; i < opts.users; i++) { - let connOpts - if (i === 0) { - connOpts = Object.assign({ role: 'master' }, conn) - } else { - connOpts = Object.assign({ role: 'slave' }, conn) - } - let y = new Y(connOpts.room, { - userID: i, // evil hackery, don't try this at home - connector: connOpts - }) + let y = testConnector.createY() result.users.push(y) result['array' + i] = y.define('array', Y.Array) result['map' + i] = y.define('map', Y.Map) const yxml = y.define('xml', Y.XmlElement) result['xml' + i] = yxml const dom = document.createElement('my-dom') - const domBinding = new DomBinding(yxml, dom, { domFilter }) + const domBinding = new Y.DomBinding(yxml, dom, { filter }) result['domBinding' + i] = domBinding result['dom' + i] = dom const textType = y.define('text', Y.Text) @@ -159,116 +364,38 @@ export async function initArrays (t, opts) { result['quill' + i] = quill y.quill = quill // put quill on the y object (so we can use it later) y.dom = dom - y.on('afterTransaction', function () { - for (let missing of y._missingStructs.values()) { - if (missing.size > 0) { - console.error(new Error('Test check in "afterTransaction": missing should be empty!')) - } - } - }) + y.domBinding = domBinding } - result.array0.delete(0, result.array0.length) - if (result.users[0].connector.testRoom != null) { - // flush for sync if test-connector - await result.users[0].connector.testRoom.flushAll(result.users) - } - await Promise.all(result.users.map(u => { - return new Promise(function (resolve) { - u.connector.whenSynced(resolve) - }) - })) - await flushAll(t, result.users) + testConnector.syncAll() return result } -export async function flushAll (t, users) { - // users = users.filter(u => u.connector.isSynced) - if (users.length === 0) { - return - } - await wait(10) - if (users[0].connector.testRoom != null) { - // use flushAll method specified in Test Connector - await users[0].connector.testRoom.flushAll(users) - } else { - var flushCounter = users[0].get('flushHelper', Y.Map).get('0') || 0 - flushCounter++ - await Promise.all(users.map(async (u, i) => { - // wait for all users to set the flush counter to the same value - await new Promise(resolve => { - function observer () { - var allUsersReceivedUpdate = true - for (var i = 0; i < users.length; i++) { - if (u.get('flushHelper', Y.Map).get(i + '') !== flushCounter) { - allUsersReceivedUpdate = false - break - } - } - if (allUsersReceivedUpdate) { - resolve() - } - } - u.get('flushHelper', Y.Map).observe(observer) - u.get('flushHelper').set(i + '', flushCounter) - }) - })) - } -} - -export async function flushSome (t, users) { - if (users[0].connector.testRoom == null) { - // if not test-connector, wait for some time for operations to arrive - await wait(100) - } -} - -export function wait (t) { - return new Promise(function (resolve) { - setTimeout(resolve, t != null ? t : 100) - }) -} - -export async function applyRandomTests (t, mods, iterations) { - const chance = new Chance(t.getSeed() * 1000000000) - var initInformation = await initArrays(t, { users: 5, chance: chance }) - let { users } = initInformation +export function applyRandomTests (t, mods, iterations) { + const prng = random.createPRNG(t.getSeed()) + const result = initArrays(t, { users: 5, prng }) + const { testConnector, users } = result for (var i = 0; i < iterations; i++) { - if (chance.bool({likelihood: 10})) { - // 10% chance to disconnect/reconnect a user - // we make sure that the first users always is connected - let user = chance.pickone(users.slice(1)) - if (user.connector.isSynced) { - if (users.filter(u => u.connector.isSynced).length > 1) { - // make sure that at least one user remains in the room - await user.disconnect() - if (users[0].connector.testRoom == null) { - await wait(100) - } - } + if (random.int32(prng, 0, 100) <= 2) { + // 2% chance to disconnect/reconnect a random user + if (random.bool(prng)) { + testConnector.disconnectRandom() } else { - await user.reconnect() - if (users[0].connector.testRoom == null) { - await wait(100) - } - await new Promise(function (resolve) { - user.connector.whenSynced(resolve) - }) + testConnector.reconnectRandom() } - } else if (chance.bool({likelihood: 5})) { - // 20%*!prev chance to flush all & garbagecollect + } else if (random.int32(prng, 0, 100) <= 1) { + // 1% chance to flush all & garbagecollect // TODO: We do not gc all users as this does not work yet // await garbageCollectUsers(t, users) - await flushAll(t, users) - // await users[0].db.emptyGarbageCollector() - await flushAll(t, users) - } else if (chance.bool({likelihood: 10})) { - // 20%*!prev chance to flush some operations - await flushSome(t, users) + testConnector.flushAllMessages() + // await users[0].db.emptyGarbageCollector() // TODO: reintroduce GC tests! + } else if (random.int32(prng, 0, 100) <= 50) { + // 50% chance to flush a random message + testConnector.flushRandomMessage() } - let user = chance.pickone(users) - var test = chance.pickone(mods) - test(t, user, chance) + let user = random.oneOf(prng, users) + var test = random.oneOf(prng, mods) + test(t, user, prng) } - await compareUsers(t, users) - return initInformation + compareUsers(t, users) + return result } diff --git a/tests-lib/test-connector.js b/tests-lib/test-connector.js deleted file mode 100644 index e9701c48..00000000 --- a/tests-lib/test-connector.js +++ /dev/null @@ -1,162 +0,0 @@ -import { wait } from './helper.js' -import { messageToString } from '../src/MessageHandler/messageToString.js' -import AbstractConnector from '../src/Connector.js' - -var rooms = {} - -export class TestRoom { - constructor (roomname) { - this.room = roomname - this.users = new Map() - } - join (connector) { - const userID = connector.y.userID - this.users.set(userID, connector) - for (let [uid, user] of this.users) { - if (uid !== userID && (user.role === 'master' || connector.role === 'master')) { - // The order is important because there is no timeout in send/receiveMessage - // (the user that receives a sync step must already now about the sender) - if (user.role === 'master') { - connector.userJoined(uid, user.role) - user.userJoined(userID, connector.role) - } else if (connector.role === 'master') { - user.userJoined(userID, connector.role) - connector.userJoined(uid, user.role) - } - } - } - } - leave (connector) { - this.users.delete(connector.y.userID) - this.users.forEach(user => { - user.userLeft(connector.y.userID) - }) - } - send (sender, receiver, m) { - var user = this.users.get(receiver) - if (user != null) { - user.receiveMessage(sender, m) - } - } - broadcast (sender, m) { - this.users.forEach((user, receiver) => { - this.send(sender, receiver, m) - }) - } - async flushAll (users) { - let flushing = true - let allUsers = Array.from(this.users.values()) - if (users == null) { - users = allUsers.map(user => user.y) - } - while (flushing) { - await wait(10) - let res = await Promise.all(allUsers.map(user => user._flushAll(users))) - flushing = res.some(status => status === 'flushing') - } - } -} - -function getTestRoom (roomname) { - if (rooms[roomname] == null) { - rooms[roomname] = new TestRoom(roomname) - } - return rooms[roomname] -} - -export default class TestConnector extends AbstractConnector { - constructor (y, options) { - if (options === undefined) { - throw new Error('Options must not be undefined!') - } - if (options.room == null) { - throw new Error('You must define a room name!') - } - options.forwardAppliedOperations = options.role === 'master' - super(y, options) - this.options = options - this.room = options.room - this.chance = options.chance - this.testRoom = getTestRoom(this.room) - this.testRoom.join(this) - } - disconnect () { - this.testRoom.leave(this) - return super.disconnect() - } - logBufferParsed () { - console.log(' === Logging buffer of user ' + this.y.userID + ' === ') - for (let [user, conn] of this.connections) { - console.log(` ${user}:`) - for (let i = 0; i < conn.buffer.length; i++) { - console.log(messageToString(conn.buffer[i])) - } - } - } - reconnect () { - this.testRoom.join(this) - super.reconnect() - return new Promise(resolve => { - this.whenSynced(resolve) - }) - } - send (uid, message) { - super.send(uid, message) - this.testRoom.send(this.y.userID, uid, message) - } - broadcast (message) { - super.broadcast(message) - this.testRoom.broadcast(this.y.userID, message) - } - async whenSynced (f) { - var synced = false - var periodicFlushTillSync = () => { - if (synced) { - f() - } else { - this.testRoom.flushAll([this.y]).then(function () { - setTimeout(periodicFlushTillSync, 10) - }) - } - } - periodicFlushTillSync() - return super.whenSynced(function () { - synced = true - }) - } - receiveMessage (sender, m) { - if (this.y.userID !== sender && this.connections.has(sender)) { - var buffer = this.connections.get(sender).buffer - if (buffer == null) { - buffer = this.connections.get(sender).buffer = [] - } - buffer.push(m) - if (this.chance.bool({likelihood: 30})) { - // flush 1/2 with 30% chance - var flushLength = Math.round(buffer.length / 2) - buffer.splice(0, flushLength).forEach(m => { - super.receiveMessage(sender, m) - }) - } - } - } - async _flushAll (flushUsers) { - if (flushUsers.some(u => u.connector.y.userID === this.y.userID)) { - // this one needs to sync with every other user - flushUsers = Array.from(this.connections.keys()).map(uid => this.testRoom.users.get(uid).y) - } - for (let i = 0; i < flushUsers.length; i++) { - let userID = flushUsers[i].connector.y.userID - if (userID !== this.y.userID && this.connections.has(userID)) { - let buffer = this.connections.get(userID).buffer - if (buffer != null) { - var messages = buffer.splice(0) - for (let j = 0; j < messages.length; j++) { - super.receiveMessage(userID, messages[j]) - } - } - } - } - return 'done' - } -}